Merge pull request #4510 from takuyakanbr/time-tracking
Add time tracking plugin
@@ -95,7 +95,15 @@ public enum VarPlayer
|
|||||||
SLAYER_GOAL_END(1272),
|
SLAYER_GOAL_END(1272),
|
||||||
FARMING_GOAL_END(1273),
|
FARMING_GOAL_END(1273),
|
||||||
CONSTRUCTION_GOAL_END(1274),
|
CONSTRUCTION_GOAL_END(1274),
|
||||||
HUNTER_GOAL_END(1275);
|
HUNTER_GOAL_END(1275),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bird house states
|
||||||
|
*/
|
||||||
|
BIRD_HOUSE_MEADOW_NORTH(1626),
|
||||||
|
BIRD_HOUSE_MEADOW_SOUTH(1627),
|
||||||
|
BIRD_HOUSE_VALLEY_NORTH(1628),
|
||||||
|
BIRD_HOUSE_VALLEY_SOUTH(1629);
|
||||||
|
|
||||||
private final int id;
|
private final int id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,6 +241,11 @@ public class WidgetID
|
|||||||
static final int SPEC_ORB = 28;
|
static final int SPEC_ORB = 28;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class LoginClickToPlayScreen
|
||||||
|
{
|
||||||
|
static final int MESSAGE_OF_THE_DAY = 3;
|
||||||
|
}
|
||||||
|
|
||||||
static class Viewport
|
static class Viewport
|
||||||
{
|
{
|
||||||
static final int MINIMAP_RESIZABLE_WIDGET = 17;
|
static final int MINIMAP_RESIZABLE_WIDGET = 17;
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ public enum WidgetInfo
|
|||||||
MINIMAP_SPEC_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.SPEC_ORB),
|
MINIMAP_SPEC_ORB(WidgetID.MINIMAP_GROUP_ID, WidgetID.Minimap.SPEC_ORB),
|
||||||
|
|
||||||
LOGIN_CLICK_TO_PLAY_SCREEN(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, 0),
|
LOGIN_CLICK_TO_PLAY_SCREEN(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, 0),
|
||||||
|
LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY(WidgetID.LOGIN_CLICK_TO_PLAY_GROUP_ID, WidgetID.LoginClickToPlayScreen.MESSAGE_OF_THE_DAY),
|
||||||
|
|
||||||
FIXED_VIEWPORT(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.Viewport.FIXED_VIEWPORT),
|
FIXED_VIEWPORT(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.Viewport.FIXED_VIEWPORT),
|
||||||
FIXED_VIEWPORT_ROOT_INTERFACE_CONTAINER(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.ROOT_INTERFACE_CONTAINER),
|
FIXED_VIEWPORT_ROOT_INTERFACE_CONTAINER(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.ROOT_INTERFACE_CONTAINER),
|
||||||
|
|||||||
@@ -1,390 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Abex
|
|
||||||
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.GridBagConstraints;
|
|
||||||
import java.awt.GridBagLayout;
|
|
||||||
import java.awt.Image;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.time.format.TextStyle;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.border.EmptyBorder;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.runelite.api.Client;
|
|
||||||
import net.runelite.api.vars.Autoweed;
|
|
||||||
import net.runelite.client.config.ConfigManager;
|
|
||||||
import net.runelite.client.game.AsyncBufferedImage;
|
|
||||||
import net.runelite.client.game.ItemManager;
|
|
||||||
import net.runelite.client.ui.ColorScheme;
|
|
||||||
import net.runelite.client.ui.FontManager;
|
|
||||||
import net.runelite.client.ui.PluginPanel;
|
|
||||||
import net.runelite.client.ui.components.materialtabs.MaterialTab;
|
|
||||||
import net.runelite.client.ui.components.materialtabs.MaterialTabGroup;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
class FarmingTrackerPanel extends PluginPanel
|
|
||||||
{
|
|
||||||
private final Client client;
|
|
||||||
private final ItemManager itemManager;
|
|
||||||
private final ConfigManager configManager;
|
|
||||||
private final FarmingTrackerConfig config;
|
|
||||||
|
|
||||||
private boolean active;
|
|
||||||
|
|
||||||
private List<FarmingPatchPanel> patchPanels = new ArrayList<>();
|
|
||||||
|
|
||||||
/* This is the panel the tabs' respective panels will be displayed on. */
|
|
||||||
private final JPanel display = new JPanel();
|
|
||||||
private final MaterialTabGroup tabGroup = new MaterialTabGroup(display);
|
|
||||||
|
|
||||||
FarmingTrackerPanel(
|
|
||||||
Client client,
|
|
||||||
ItemManager itemManager,
|
|
||||||
ConfigManager configManager,
|
|
||||||
FarmingTrackerConfig config,
|
|
||||||
FarmingWorld farmingWorld
|
|
||||||
)
|
|
||||||
{
|
|
||||||
super(false);
|
|
||||||
|
|
||||||
this.client = client;
|
|
||||||
this.itemManager = itemManager;
|
|
||||||
this.configManager = configManager;
|
|
||||||
this.config = config;
|
|
||||||
|
|
||||||
setLayout(new BorderLayout());
|
|
||||||
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
|
||||||
|
|
||||||
display.setBorder(new EmptyBorder(10, 10, 8, 10));
|
|
||||||
|
|
||||||
tabGroup.setBorder(new EmptyBorder(10, 1, 0, 0));
|
|
||||||
|
|
||||||
add(tabGroup, BorderLayout.NORTH);
|
|
||||||
add(display, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
farmingWorld.getTabs().forEach((tab, patches) ->
|
|
||||||
{
|
|
||||||
JPanel container = new JPanel(new GridBagLayout())
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public Dimension getPreferredSize()
|
|
||||||
{
|
|
||||||
return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
container.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
|
||||||
|
|
||||||
GridBagConstraints c = new GridBagConstraints();
|
|
||||||
c.fill = GridBagConstraints.HORIZONTAL;
|
|
||||||
c.weightx = 1;
|
|
||||||
c.gridx = 0;
|
|
||||||
c.gridy = 0;
|
|
||||||
|
|
||||||
PatchImplementation lastImpl = null;
|
|
||||||
|
|
||||||
boolean first = true;
|
|
||||||
for (FarmingPatch patch : patches)
|
|
||||||
{
|
|
||||||
FarmingPatchPanel p = new FarmingPatchPanel(patch);
|
|
||||||
|
|
||||||
/* Show labels to subdivide tabs into sections */
|
|
||||||
if (patch.getImplementation() != lastImpl && !Strings.isNullOrEmpty(patch.getImplementation().getName()))
|
|
||||||
{
|
|
||||||
JLabel groupLabel = new JLabel(patch.getImplementation().getName());
|
|
||||||
|
|
||||||
if (first)
|
|
||||||
{
|
|
||||||
first = false;
|
|
||||||
groupLabel.setBorder(new EmptyBorder(4, 0, 0, 0));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
groupLabel.setBorder(new EmptyBorder(15, 0, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
groupLabel.setFont(FontManager.getRunescapeSmallFont());
|
|
||||||
|
|
||||||
container.add(groupLabel, c);
|
|
||||||
c.gridy++;
|
|
||||||
lastImpl = patch.getImplementation();
|
|
||||||
}
|
|
||||||
|
|
||||||
patchPanels.add(p);
|
|
||||||
container.add(p, c);
|
|
||||||
c.gridy++;
|
|
||||||
|
|
||||||
/* This is a weird hack to remove the top border on the first tracker of every tab */
|
|
||||||
if (first)
|
|
||||||
{
|
|
||||||
first = false;
|
|
||||||
p.setBorder(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JPanel wrapped = new JPanel(new BorderLayout());
|
|
||||||
wrapped.add(container, BorderLayout.NORTH);
|
|
||||||
wrapped.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
|
||||||
|
|
||||||
JScrollPane scroller = new JScrollPane(wrapped);
|
|
||||||
scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
|
||||||
scroller.getVerticalScrollBar().setPreferredSize(new Dimension(16, 0));
|
|
||||||
scroller.getVerticalScrollBar().setBorder(new EmptyBorder(0, 9, 0, 0));
|
|
||||||
scroller.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
|
||||||
|
|
||||||
//Use a placeholder icon until the async image gets loaded
|
|
||||||
MaterialTab materialTab = new MaterialTab(new ImageIcon(), tabGroup, scroller);
|
|
||||||
materialTab.setPreferredSize(new Dimension(30, 27));
|
|
||||||
materialTab.setName(tab.getName());
|
|
||||||
|
|
||||||
AsyncBufferedImage icon = itemManager.getImage(tab.getItemID());
|
|
||||||
Runnable resize = () ->
|
|
||||||
{
|
|
||||||
BufferedImage subIcon = icon.getSubimage(0, 0, 32, 32);
|
|
||||||
materialTab.setIcon(new ImageIcon(subIcon.getScaledInstance(24, 24, Image.SCALE_SMOOTH)));
|
|
||||||
};
|
|
||||||
icon.onChanged(resize);
|
|
||||||
resize.run();
|
|
||||||
|
|
||||||
materialTab.setOnSelectEvent(() ->
|
|
||||||
{
|
|
||||||
config.setPatch(tab);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
tabGroup.addTab(materialTab);
|
|
||||||
if (config.patch() == tab)
|
|
||||||
{
|
|
||||||
tabGroup.select(materialTab);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void update()
|
|
||||||
{
|
|
||||||
if (!active)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
long unixNow = Instant.now().getEpochSecond();
|
|
||||||
log.debug("Updating panel with username {}", client.getUsername());
|
|
||||||
boolean autoweed = false;
|
|
||||||
{
|
|
||||||
String group = FarmingTrackerConfig.KEY_NAME + "." + client.getUsername();
|
|
||||||
autoweed = Integer.toString(Autoweed.ON.ordinal())
|
|
||||||
.equals(configManager.getConfiguration(group, FarmingTrackerConfig.AUTOWEED));
|
|
||||||
}
|
|
||||||
for (FarmingPatchPanel panel : patchPanels)
|
|
||||||
{
|
|
||||||
FarmingPatch patch = panel.getPatch();
|
|
||||||
String group = FarmingTrackerConfig.KEY_NAME + "." + client.getUsername() + "." + patch.getRegion().getRegionID();
|
|
||||||
String key = Integer.toString(patch.getVarbit().getId());
|
|
||||||
String storedValue = configManager.getConfiguration(group, key);
|
|
||||||
long unixTime = 0;
|
|
||||||
int value = 0;
|
|
||||||
if (storedValue != null)
|
|
||||||
{
|
|
||||||
String[] parts = storedValue.split(":");
|
|
||||||
if (parts.length == 2)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
value = Integer.parseInt(parts[0]);
|
|
||||||
unixTime = Long.parseLong(parts[1]);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PatchState state = unixTime <= 0 ? null : patch.getImplementation().forVarbitValue(value);
|
|
||||||
if (state == null)
|
|
||||||
{
|
|
||||||
itemManager.getImage(Produce.WEEDS.getItemID()).addTo(panel.getIcon());
|
|
||||||
panel.getIcon().setToolTipText("Unknown state");
|
|
||||||
panel.getProgress().setMaximumValue(0);
|
|
||||||
panel.getProgress().setValue(0);
|
|
||||||
panel.getProgress().setVisible(false);
|
|
||||||
panel.getEstimate().setText("Unknown");
|
|
||||||
panel.getProgress().setBackground(null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (state.getProduce().getItemID() < 0)
|
|
||||||
{
|
|
||||||
panel.getIcon().setIcon(null);
|
|
||||||
panel.getIcon().setToolTipText("Unknown state");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
itemManager.getImage(state.getProduce().getItemID()).addTo(panel.getIcon());
|
|
||||||
panel.getIcon().setToolTipText(state.getProduce().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
int stage = state.getStage();
|
|
||||||
int stages = state.getCropState() == CropState.HARVESTABLE ?
|
|
||||||
state.getProduce().getHarvestStages() :
|
|
||||||
state.getProduce().getStages();
|
|
||||||
int tickrate = 0;
|
|
||||||
switch (state.getCropState())
|
|
||||||
{
|
|
||||||
case HARVESTABLE:
|
|
||||||
tickrate = state.getProduce().getRegrowTickrate() * 60;
|
|
||||||
break;
|
|
||||||
case GROWING:
|
|
||||||
tickrate = state.getProduce().getTickrate() * 60;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (autoweed && state.getProduce() == Produce.WEEDS)
|
|
||||||
{
|
|
||||||
stage = 0;
|
|
||||||
stages = 1;
|
|
||||||
tickrate = 0;
|
|
||||||
}
|
|
||||||
if (tickrate > 0)
|
|
||||||
{
|
|
||||||
long tickNow = unixNow / tickrate;
|
|
||||||
long tickTime = unixTime / tickrate;
|
|
||||||
int delta = (int) (tickNow - tickTime);
|
|
||||||
|
|
||||||
long doneEstimate = ((stages - 1 - stage) + tickTime) * tickrate;
|
|
||||||
|
|
||||||
stage += delta;
|
|
||||||
if (stage >= stages)
|
|
||||||
{
|
|
||||||
stage = stages - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doneEstimate < unixNow)
|
|
||||||
{
|
|
||||||
panel.getEstimate().setText("Done");
|
|
||||||
}
|
|
||||||
else if (config.estimateRelative())
|
|
||||||
{
|
|
||||||
int remaining = (int) (59 + doneEstimate - unixNow) / 60;
|
|
||||||
StringBuilder f = new StringBuilder();
|
|
||||||
f.append("Done in ");
|
|
||||||
int min = remaining % 60;
|
|
||||||
int hours = (remaining / 60) % 24;
|
|
||||||
int days = remaining / (60 * 24);
|
|
||||||
if (days > 0)
|
|
||||||
{
|
|
||||||
f.append(days).append("d ");
|
|
||||||
}
|
|
||||||
if (hours > 0)
|
|
||||||
{
|
|
||||||
f.append(hours).append("h ");
|
|
||||||
}
|
|
||||||
if (min > 0)
|
|
||||||
{
|
|
||||||
f.append(min).append("m ");
|
|
||||||
}
|
|
||||||
panel.getEstimate().setText(f.toString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
StringBuilder f = new StringBuilder();
|
|
||||||
LocalDateTime ldtTime = LocalDateTime.ofEpochSecond(doneEstimate, 0, OffsetDateTime.now().getOffset());
|
|
||||||
LocalDateTime ldtNow = LocalDateTime.now();
|
|
||||||
f.append("Done ");
|
|
||||||
if (ldtTime.getDayOfWeek() != ldtNow.getDayOfWeek())
|
|
||||||
{
|
|
||||||
f.append(ldtTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" ");
|
|
||||||
}
|
|
||||||
f.append(String.format("at %d:%02d", ldtTime.getHour(), ldtTime.getMinute()));
|
|
||||||
panel.getEstimate().setText(f.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (state.getCropState())
|
|
||||||
{
|
|
||||||
case HARVESTABLE:
|
|
||||||
panel.getEstimate().setText("Done");
|
|
||||||
break;
|
|
||||||
case GROWING:
|
|
||||||
if (stage == stages - 1)
|
|
||||||
{
|
|
||||||
panel.getEstimate().setText("Done");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
panel.getEstimate().setText("Unknown");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DISEASED:
|
|
||||||
panel.getEstimate().setText("Diseased");
|
|
||||||
break;
|
|
||||||
case DEAD:
|
|
||||||
panel.getEstimate().setText("Dead");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide any fully grown weeds' progress bar. */
|
|
||||||
if (state.getProduce() != Produce.WEEDS
|
|
||||||
|| (state.getProduce() == Produce.WEEDS && !autoweed && stage < stages - 1))
|
|
||||||
{
|
|
||||||
panel.getProgress().setVisible(true);
|
|
||||||
panel.getProgress().setForeground(state.getCropState().getColor().darker());
|
|
||||||
panel.getProgress().setMaximumValue(stages - 1);
|
|
||||||
panel.getProgress().setValue(stage);
|
|
||||||
panel.getProgress().update();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
panel.getProgress().setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivate()
|
|
||||||
{
|
|
||||||
active = true;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDeactivate()
|
|
||||||
{
|
|
||||||
active = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2018 Abex
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
import com.google.inject.Provides;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import net.runelite.api.Client;
|
|
||||||
import net.runelite.api.GameState;
|
|
||||||
import net.runelite.api.Varbits;
|
|
||||||
import net.runelite.api.coords.WorldPoint;
|
|
||||||
import net.runelite.api.events.GameTick;
|
|
||||||
import net.runelite.api.events.UsernameChanged;
|
|
||||||
import net.runelite.client.config.ConfigManager;
|
|
||||||
import net.runelite.client.game.ItemManager;
|
|
||||||
import net.runelite.client.plugins.Plugin;
|
|
||||||
import net.runelite.client.plugins.PluginDescriptor;
|
|
||||||
import net.runelite.client.task.Schedule;
|
|
||||||
import net.runelite.client.ui.NavigationButton;
|
|
||||||
import net.runelite.client.ui.ClientToolbar;
|
|
||||||
import net.runelite.client.util.ImageUtil;
|
|
||||||
|
|
||||||
@PluginDescriptor(
|
|
||||||
name = "Farming Tracker",
|
|
||||||
description = "Show when your farming plots would be fully grown",
|
|
||||||
tags = {"skilling", "panel", "timers"}
|
|
||||||
)
|
|
||||||
@Slf4j
|
|
||||||
public class FarmingTrackerPlugin extends Plugin
|
|
||||||
{
|
|
||||||
@Inject
|
|
||||||
private ClientToolbar clientToolbar;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private ConfigManager configManager;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private Client client;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private FarmingWorld farmingWorld;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private ItemManager itemManager;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
private FarmingTrackerConfig config;
|
|
||||||
|
|
||||||
private FarmingTrackerPanel panel;
|
|
||||||
|
|
||||||
private NavigationButton navButton;
|
|
||||||
|
|
||||||
private WorldPoint lastTickLoc;
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
FarmingTrackerConfig provideConfig(ConfigManager configManager)
|
|
||||||
{
|
|
||||||
return configManager.getConfig(FarmingTrackerConfig.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void startUp() throws Exception
|
|
||||||
{
|
|
||||||
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "farming.png");
|
|
||||||
|
|
||||||
panel = new FarmingTrackerPanel(client, itemManager, configManager, config, farmingWorld);
|
|
||||||
|
|
||||||
navButton = NavigationButton.builder()
|
|
||||||
.tooltip("Farming Tracker")
|
|
||||||
.icon(icon)
|
|
||||||
.panel(panel)
|
|
||||||
.priority(4)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
clientToolbar.addNavigation(navButton);
|
|
||||||
|
|
||||||
updatePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onUsernameChanged(UsernameChanged e)
|
|
||||||
{
|
|
||||||
updatePanel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void shutDown() throws Exception
|
|
||||||
{
|
|
||||||
clientToolbar.removeNavigation(navButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onGameTick(GameTick t)
|
|
||||||
{
|
|
||||||
if (client.getGameState() != GameState.LOGGED_IN)
|
|
||||||
{
|
|
||||||
lastTickLoc = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WorldPoint loc = lastTickLoc;
|
|
||||||
lastTickLoc = client.getLocalPlayer().getWorldLocation();
|
|
||||||
if (loc == null || loc.getRegionID() != lastTickLoc.getRegionID())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean changed = false;
|
|
||||||
|
|
||||||
{
|
|
||||||
String group = FarmingTrackerConfig.KEY_NAME + "." + client.getUsername();
|
|
||||||
String autoweed = Integer.toString(client.getVar(Varbits.AUTOWEED));
|
|
||||||
if (!autoweed.equals(configManager.getConfiguration(group, FarmingTrackerConfig.AUTOWEED)))
|
|
||||||
{
|
|
||||||
configManager.setConfiguration(group, FarmingTrackerConfig.AUTOWEED, autoweed);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FarmingRegion region = farmingWorld.getRegions().get(loc.getRegionID());
|
|
||||||
if (region != null && region.isInBounds(loc))
|
|
||||||
{
|
|
||||||
// Write config with new varbits
|
|
||||||
// farmingTracker.<login-username>.<regionID>.<VarbitID>=<varbitValue>:<unix time>
|
|
||||||
String group = FarmingTrackerConfig.KEY_NAME + "." + client.getUsername() + "." + region.getRegionID();
|
|
||||||
long unixNow = Instant.now().getEpochSecond();
|
|
||||||
for (Varbits varbit : region.getVarbits())
|
|
||||||
{
|
|
||||||
// Write the config value if it doesn't match what is current, or it is more than 5 minutes old
|
|
||||||
String key = Integer.toString(varbit.getId());
|
|
||||||
String strVarbit = Integer.toString(client.getVar(varbit));
|
|
||||||
String storedValue = configManager.getConfiguration(group, key);
|
|
||||||
if (storedValue != null)
|
|
||||||
{
|
|
||||||
String[] parts = storedValue.split(":");
|
|
||||||
if (parts.length == 2 && parts[0].equals(strVarbit))
|
|
||||||
{
|
|
||||||
long unixTime = 0;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
unixTime = Long.parseLong(parts[1]);
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
if (unixTime + (5 * 60) > unixNow && unixNow + 30 > unixTime)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String value = strVarbit + ":" + unixNow;
|
|
||||||
configManager.setConfiguration(group, key, value);
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed)
|
|
||||||
{
|
|
||||||
updatePanel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Schedule(period = 10, unit = ChronoUnit.SECONDS)
|
|
||||||
public void updatePanel()
|
|
||||||
{
|
|
||||||
SwingUtilities.invokeLater(panel::update);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Cursor;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
import net.runelite.client.util.ImageUtil;
|
||||||
|
|
||||||
|
class OverviewItemPanel extends JPanel
|
||||||
|
{
|
||||||
|
private static final ImageIcon ARROW_RIGHT_ICON;
|
||||||
|
|
||||||
|
private static final Color HOVER_COLOR = ColorScheme.DARKER_GRAY_HOVER_COLOR;
|
||||||
|
|
||||||
|
private final JLabel statusLabel;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
ARROW_RIGHT_ICON = new ImageIcon(ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "/util/arrow_right.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
OverviewItemPanel(ItemManager itemManager, TimeTrackingPanel pluginPanel, Tab tab, String title)
|
||||||
|
{
|
||||||
|
setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(new EmptyBorder(7, 7, 7, 7));
|
||||||
|
|
||||||
|
JLabel iconLabel = new JLabel();
|
||||||
|
iconLabel.setMinimumSize(new Dimension(36, 32));
|
||||||
|
itemManager.getImage(tab.getItemID()).addTo(iconLabel);
|
||||||
|
add(iconLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
JPanel textContainer = new JPanel();
|
||||||
|
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
textContainer.setLayout(new GridLayout(2, 1));
|
||||||
|
textContainer.setBorder(new EmptyBorder(5, 7, 5, 7));
|
||||||
|
|
||||||
|
addMouseListener(new MouseAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent mouseEvent)
|
||||||
|
{
|
||||||
|
pluginPanel.switchTab(tab);
|
||||||
|
setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(MouseEvent e)
|
||||||
|
{
|
||||||
|
setBackground(HOVER_COLOR);
|
||||||
|
textContainer.setBackground(HOVER_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e)
|
||||||
|
{
|
||||||
|
setBackground(HOVER_COLOR);
|
||||||
|
textContainer.setBackground(HOVER_COLOR);
|
||||||
|
setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e)
|
||||||
|
{
|
||||||
|
setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
textContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JLabel titleLabel = new JLabel(title);
|
||||||
|
titleLabel.setForeground(Color.WHITE);
|
||||||
|
titleLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
|
||||||
|
statusLabel = new JLabel();
|
||||||
|
statusLabel.setForeground(Color.GRAY);
|
||||||
|
statusLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
|
||||||
|
textContainer.add(titleLabel);
|
||||||
|
textContainer.add(statusLabel);
|
||||||
|
|
||||||
|
add(textContainer, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
JLabel arrowLabel = new JLabel(ARROW_RIGHT_ICON);
|
||||||
|
add(arrowLabel, BorderLayout.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateStatus(String text, Color color)
|
||||||
|
{
|
||||||
|
statusLabel.setText(text);
|
||||||
|
statusLabel.setForeground(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.clocks.ClockManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.farming.FarmingTracker;
|
||||||
|
import net.runelite.client.plugins.timetracking.farming.PatchImplementation;
|
||||||
|
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
|
||||||
|
class OverviewTabPanel extends TabContentPanel
|
||||||
|
{
|
||||||
|
private final TimeTrackingConfig config;
|
||||||
|
private final FarmingTracker farmingTracker;
|
||||||
|
private final BirdHouseTracker birdHouseTracker;
|
||||||
|
private final ClockManager clockManager;
|
||||||
|
|
||||||
|
private final OverviewItemPanel timerOverview;
|
||||||
|
private final OverviewItemPanel stopwatchOverview;
|
||||||
|
private final Map<PatchImplementation, OverviewItemPanel> farmingOverviews;
|
||||||
|
private final OverviewItemPanel birdHouseOverview;
|
||||||
|
|
||||||
|
OverviewTabPanel(ItemManager itemManager, TimeTrackingConfig config, TimeTrackingPanel pluginPanel,
|
||||||
|
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager)
|
||||||
|
{
|
||||||
|
this.config = config;
|
||||||
|
this.farmingTracker = farmingTracker;
|
||||||
|
this.birdHouseTracker = birdHouseTracker;
|
||||||
|
this.clockManager = clockManager;
|
||||||
|
|
||||||
|
setLayout(new GridLayout(0, 1, 0, 8));
|
||||||
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
timerOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.CLOCK, "Timers");
|
||||||
|
add(timerOverview);
|
||||||
|
|
||||||
|
stopwatchOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.CLOCK, "Stopwatches");
|
||||||
|
add(stopwatchOverview);
|
||||||
|
|
||||||
|
birdHouseOverview = new OverviewItemPanel(itemManager, pluginPanel, Tab.BIRD_HOUSE, "Bird Houses");
|
||||||
|
add(birdHouseOverview);
|
||||||
|
|
||||||
|
farmingOverviews = new LinkedHashMap<>();
|
||||||
|
farmingOverviews.put(PatchImplementation.ALLOTMENT, new OverviewItemPanel(itemManager, pluginPanel, Tab.ALLOTMENT, "Allotment Patches"));
|
||||||
|
farmingOverviews.put(PatchImplementation.FLOWER, new OverviewItemPanel(itemManager, pluginPanel, Tab.FLOWER, "Flower Patches"));
|
||||||
|
farmingOverviews.put(PatchImplementation.HERB, new OverviewItemPanel(itemManager, pluginPanel, Tab.HERB, "Herb Patches"));
|
||||||
|
farmingOverviews.put(PatchImplementation.TREE, new OverviewItemPanel(itemManager, pluginPanel, Tab.TREE, "Tree Patches"));
|
||||||
|
farmingOverviews.put(PatchImplementation.FRUIT_TREE, new OverviewItemPanel(itemManager, pluginPanel, Tab.FRUIT_TREE, "Fruit Tree Patches"));
|
||||||
|
farmingOverviews.put(PatchImplementation.HOPS, new OverviewItemPanel(itemManager, pluginPanel, Tab.HOPS, "Hops Patches"));
|
||||||
|
farmingOverviews.put(PatchImplementation.BUSH, new OverviewItemPanel(itemManager, pluginPanel, Tab.BUSH, "Bush Patches"));
|
||||||
|
farmingOverviews.put(PatchImplementation.GRAPES, new OverviewItemPanel(itemManager, pluginPanel, Tab.GRAPE, "Grape Patches"));
|
||||||
|
|
||||||
|
for (OverviewItemPanel panel : farmingOverviews.values())
|
||||||
|
{
|
||||||
|
add(panel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUpdateInterval()
|
||||||
|
{
|
||||||
|
return 50; // 10 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update()
|
||||||
|
{
|
||||||
|
final long timers = clockManager.getActiveTimerCount();
|
||||||
|
final long stopwatches = clockManager.getActiveStopwatchCount();
|
||||||
|
|
||||||
|
if (timers == 0)
|
||||||
|
{
|
||||||
|
timerOverview.updateStatus("No active timers", Color.GRAY);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timerOverview.updateStatus(timers + " active timer" + (timers == 1 ? "" : "s"), ColorScheme.PROGRESS_COMPLETE_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stopwatches == 0)
|
||||||
|
{
|
||||||
|
stopwatchOverview.updateStatus("No active stopwatches", Color.GRAY);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stopwatchOverview.updateStatus(stopwatches + " active stopwatch" + (stopwatches == 1 ? "" : "es"), ColorScheme.PROGRESS_COMPLETE_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
farmingOverviews.forEach((patchType, panel) -> updateItemPanel(panel, farmingTracker.getCompletionTime(patchType)));
|
||||||
|
updateItemPanel(birdHouseOverview, birdHouseTracker.getCompletionTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateItemPanel(OverviewItemPanel panel, long completionTime)
|
||||||
|
{
|
||||||
|
long duration = completionTime - Instant.now().getEpochSecond();
|
||||||
|
|
||||||
|
if (completionTime < 0)
|
||||||
|
{
|
||||||
|
panel.updateStatus("Unknown", Color.GRAY);
|
||||||
|
}
|
||||||
|
else if (duration <= 0)
|
||||||
|
{
|
||||||
|
panel.updateStatus("Ready", ColorScheme.PROGRESS_COMPLETE_COLOR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
panel.updateStatus("Ready " + getFormattedEstimate(duration, config.estimateRelative()), Color.GRAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -32,12 +32,20 @@ import net.runelite.api.ItemID;
|
|||||||
@Getter
|
@Getter
|
||||||
public enum Tab
|
public enum Tab
|
||||||
{
|
{
|
||||||
ALLOTMENT("Allotments", ItemID.CABBAGE),
|
OVERVIEW("Overview", ItemID.OLD_NOTES),
|
||||||
HERB("Herbs", ItemID.GRIMY_RANARR_WEED),
|
CLOCK("Timers & Stopwatches", ItemID.WATCH),
|
||||||
TREE("Trees", ItemID.MAHOGANY_LOGS),
|
BIRD_HOUSE("Bird Houses", ItemID.OAK_BIRD_HOUSE),
|
||||||
FRUIT_TREE("Fruit Trees", ItemID.PINEAPPLE),
|
ALLOTMENT("Allotment Patches", ItemID.CABBAGE),
|
||||||
BUSH("Bushes", ItemID.REDBERRIES),
|
FLOWER("Flower Patches", ItemID.RED_FLOWERS),
|
||||||
SPECIAL("Special", ItemID.MUSHROOM);
|
HERB("Herb Patches", ItemID.GRIMY_RANARR_WEED),
|
||||||
|
TREE("Tree Patches", ItemID.YEW_LOGS),
|
||||||
|
FRUIT_TREE("Fruit Tree Patches", ItemID.PINEAPPLE),
|
||||||
|
HOPS("Hops Patches", ItemID.BARLEY),
|
||||||
|
BUSH("Bush Patches", ItemID.POISON_IVY_BERRIES),
|
||||||
|
GRAPE("Grape Patches", ItemID.GRAPES),
|
||||||
|
SPECIAL("Special Patches", ItemID.MUSHROOM);
|
||||||
|
|
||||||
|
public static final Tab[] FARMING_TABS = {ALLOTMENT, FLOWER, HERB, TREE, FRUIT_TREE, HOPS, BUSH, GRAPE, SPECIAL};
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final int itemID;
|
private final int itemID;
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.TextStyle;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
|
public abstract class TabContentPanel extends JPanel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets the update interval of this panel, in units of 200 milliseconds
|
||||||
|
* (the plugin panel checks if its contents should be updated every 200 ms;
|
||||||
|
* this can be considered its "tick rate").
|
||||||
|
*/
|
||||||
|
public abstract int getUpdateInterval();
|
||||||
|
|
||||||
|
public abstract void update();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredSize()
|
||||||
|
{
|
||||||
|
return super.getPreferredSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getFormattedEstimate(long remainingSeconds, boolean useRelativeTime)
|
||||||
|
{
|
||||||
|
if (useRelativeTime)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder("in ");
|
||||||
|
long duration = (remainingSeconds + 59) / 60;
|
||||||
|
long minutes = duration % 60;
|
||||||
|
long hours = (duration / 60) % 24;
|
||||||
|
long days = duration / (60 * 24);
|
||||||
|
if (days > 0)
|
||||||
|
{
|
||||||
|
sb.append(days).append("d ");
|
||||||
|
}
|
||||||
|
if (hours > 0)
|
||||||
|
{
|
||||||
|
sb.append(hours).append("h ");
|
||||||
|
}
|
||||||
|
if (minutes > 0)
|
||||||
|
{
|
||||||
|
sb.append(minutes).append("m ");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
LocalDateTime endTime = LocalDateTime.now().plus(remainingSeconds, ChronoUnit.SECONDS);
|
||||||
|
LocalDateTime currentTime = LocalDateTime.now();
|
||||||
|
if (endTime.getDayOfWeek() != currentTime.getDayOfWeek())
|
||||||
|
{
|
||||||
|
sb.append(endTime.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())).append(" ");
|
||||||
|
}
|
||||||
|
sb.append(String.format("at %d:%02d", endTime.getHour(), endTime.getMinute()));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,22 +22,26 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking;
|
||||||
|
|
||||||
import net.runelite.client.config.Config;
|
import net.runelite.client.config.Config;
|
||||||
import net.runelite.client.config.ConfigGroup;
|
import net.runelite.client.config.ConfigGroup;
|
||||||
import net.runelite.client.config.ConfigItem;
|
import net.runelite.client.config.ConfigItem;
|
||||||
|
|
||||||
@ConfigGroup("farmingTracker")
|
@ConfigGroup("timetracking")
|
||||||
public interface FarmingTrackerConfig extends Config
|
public interface TimeTrackingConfig extends Config
|
||||||
{
|
{
|
||||||
String KEY_NAME = "farmingTracker";
|
String CONFIG_GROUP = "timetracking";
|
||||||
String AUTOWEED = "autoweed";
|
String AUTOWEED = "autoweed";
|
||||||
|
String BIRD_HOUSE = "birdhouse";
|
||||||
|
String TIMERS = "timers";
|
||||||
|
String STOPWATCHES = "stopwatches";
|
||||||
|
|
||||||
@ConfigItem(
|
@ConfigItem(
|
||||||
keyName = "estimateRelative",
|
keyName = "estimateRelative",
|
||||||
name = "Show relative time",
|
name = "Show relative time",
|
||||||
description = "Show amount of time remaining for a patch, opposed to when the patch is finished"
|
description = "Show amount of time remaining instead of completion time",
|
||||||
|
position = 1
|
||||||
)
|
)
|
||||||
default boolean estimateRelative()
|
default boolean estimateRelative()
|
||||||
{
|
{
|
||||||
@@ -45,21 +49,43 @@ public interface FarmingTrackerConfig extends Config
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ConfigItem(
|
@ConfigItem(
|
||||||
keyName = "patch",
|
keyName = "timerNotification",
|
||||||
name = "Default patch",
|
name = "Timer notification",
|
||||||
description = "Default patch on opening the panel",
|
description = "Notify you whenever a timer has finished counting down",
|
||||||
hidden = true
|
position = 2
|
||||||
)
|
)
|
||||||
default Tab patch()
|
default boolean timerNotification()
|
||||||
{
|
{
|
||||||
return Tab.ALLOTMENT;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ConfigItem(
|
@ConfigItem(
|
||||||
keyName = "patch",
|
keyName = "birdHouseNotification",
|
||||||
|
name = "Bird house notification",
|
||||||
|
description = "Notify you when all bird houses are full",
|
||||||
|
position = 3
|
||||||
|
)
|
||||||
|
default boolean birdHouseNotification()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "activeTab",
|
||||||
|
name = "Active Tab",
|
||||||
|
description = "The currently selected tab",
|
||||||
|
hidden = true
|
||||||
|
)
|
||||||
|
default Tab activeTab()
|
||||||
|
{
|
||||||
|
return Tab.CLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigItem(
|
||||||
|
keyName = "activeTab",
|
||||||
name = "",
|
name = "",
|
||||||
description = "",
|
description = "",
|
||||||
hidden = true
|
hidden = true
|
||||||
)
|
)
|
||||||
void setPatch(Tab t);
|
void setActiveTab(Tab t);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Abex
|
||||||
|
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.client.game.AsyncBufferedImage;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.clocks.ClockManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.farming.FarmingTracker;
|
||||||
|
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.PluginPanel;
|
||||||
|
import net.runelite.client.ui.components.materialtabs.MaterialTab;
|
||||||
|
import net.runelite.client.ui.components.materialtabs.MaterialTabGroup;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class TimeTrackingPanel extends PluginPanel
|
||||||
|
{
|
||||||
|
private final ItemManager itemManager;
|
||||||
|
private final TimeTrackingConfig config;
|
||||||
|
|
||||||
|
/* This is the panel the tabs' respective panels will be displayed on. */
|
||||||
|
private final JPanel display = new JPanel();
|
||||||
|
private final Map<Tab, MaterialTab> uiTabs = new HashMap<>();
|
||||||
|
private final MaterialTabGroup tabGroup = new MaterialTabGroup(display);
|
||||||
|
|
||||||
|
private boolean active;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private TabContentPanel activeTabPanel = null;
|
||||||
|
|
||||||
|
TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config,
|
||||||
|
FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager)
|
||||||
|
{
|
||||||
|
super(false);
|
||||||
|
|
||||||
|
this.itemManager = itemManager;
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
display.setBorder(new EmptyBorder(10, 10, 8, 10));
|
||||||
|
|
||||||
|
tabGroup.setLayout(new GridLayout(0, 6, 7, 7));
|
||||||
|
tabGroup.setBorder(new EmptyBorder(10, 10, 0, 10));
|
||||||
|
|
||||||
|
add(tabGroup, BorderLayout.NORTH);
|
||||||
|
add(display, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
addTab(Tab.OVERVIEW, new OverviewTabPanel(itemManager, config, this, farmingTracker, birdHouseTracker, clockManager));
|
||||||
|
addTab(Tab.CLOCK, clockManager.getClockTabPanel());
|
||||||
|
addTab(Tab.BIRD_HOUSE, birdHouseTracker.createBirdHouseTabPanel());
|
||||||
|
|
||||||
|
for (Tab tab : Tab.FARMING_TABS)
|
||||||
|
{
|
||||||
|
addTab(tab, farmingTracker.createTabPanel(tab));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTab(Tab tab, TabContentPanel tabContentPanel)
|
||||||
|
{
|
||||||
|
JPanel wrapped = new JPanel(new BorderLayout());
|
||||||
|
wrapped.add(tabContentPanel, BorderLayout.NORTH);
|
||||||
|
wrapped.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
JScrollPane scroller = new JScrollPane(wrapped);
|
||||||
|
scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
scroller.getVerticalScrollBar().setPreferredSize(new Dimension(16, 0));
|
||||||
|
scroller.getVerticalScrollBar().setBorder(new EmptyBorder(0, 9, 0, 0));
|
||||||
|
scroller.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
// Use a placeholder icon until the async image gets loaded
|
||||||
|
MaterialTab materialTab = new MaterialTab(new ImageIcon(), tabGroup, scroller);
|
||||||
|
materialTab.setPreferredSize(new Dimension(30, 27));
|
||||||
|
materialTab.setName(tab.getName());
|
||||||
|
materialTab.setToolTipText(tab.getName());
|
||||||
|
|
||||||
|
AsyncBufferedImage icon = itemManager.getImage(tab.getItemID());
|
||||||
|
Runnable resize = () ->
|
||||||
|
{
|
||||||
|
BufferedImage subIcon = icon.getSubimage(0, 0, 32, 32);
|
||||||
|
materialTab.setIcon(new ImageIcon(subIcon.getScaledInstance(24, 24, Image.SCALE_SMOOTH)));
|
||||||
|
};
|
||||||
|
icon.onChanged(resize);
|
||||||
|
resize.run();
|
||||||
|
|
||||||
|
materialTab.setOnSelectEvent(() ->
|
||||||
|
{
|
||||||
|
config.setActiveTab(tab);
|
||||||
|
activeTabPanel = tabContentPanel;
|
||||||
|
|
||||||
|
tabContentPanel.update();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
uiTabs.put(tab, materialTab);
|
||||||
|
tabGroup.addTab(materialTab);
|
||||||
|
|
||||||
|
if (config.activeTab() == tab)
|
||||||
|
{
|
||||||
|
tabGroup.select(materialTab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void switchTab(Tab tab)
|
||||||
|
{
|
||||||
|
tabGroup.select(uiTabs.get(tab));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the update interval of the active tab panel, in units of 200 milliseconds.
|
||||||
|
*/
|
||||||
|
int getUpdateInterval()
|
||||||
|
{
|
||||||
|
return activeTabPanel == null ? Integer.MAX_VALUE : activeTabPanel.getUpdateInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the active tab panel, if this plugin panel is displayed.
|
||||||
|
*/
|
||||||
|
void update()
|
||||||
|
{
|
||||||
|
if (!active || activeTabPanel == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(activeTabPanel::update);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivate()
|
||||||
|
{
|
||||||
|
active = true;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDeactivate()
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Abex
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.GameState;
|
||||||
|
import net.runelite.api.coords.WorldPoint;
|
||||||
|
import net.runelite.api.events.ConfigChanged;
|
||||||
|
import net.runelite.api.events.GameTick;
|
||||||
|
import net.runelite.api.events.UsernameChanged;
|
||||||
|
import net.runelite.api.widgets.Widget;
|
||||||
|
import net.runelite.api.widgets.WidgetInfo;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
|
import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.CONFIG_GROUP;
|
||||||
|
import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.STOPWATCHES;
|
||||||
|
import static net.runelite.client.plugins.timetracking.TimeTrackingConfig.TIMERS;
|
||||||
|
import net.runelite.client.plugins.timetracking.clocks.ClockManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.farming.FarmingTracker;
|
||||||
|
import net.runelite.client.plugins.timetracking.hunter.BirdHouseTracker;
|
||||||
|
import net.runelite.client.task.Schedule;
|
||||||
|
import net.runelite.client.ui.NavigationButton;
|
||||||
|
import net.runelite.client.ui.ClientToolbar;
|
||||||
|
import net.runelite.client.util.ImageUtil;
|
||||||
|
|
||||||
|
@PluginDescriptor(
|
||||||
|
name = "Time Tracking",
|
||||||
|
description = "Enable the Time Tracking panel, which contains timers, stopwatches, and farming and bird house trackers",
|
||||||
|
tags = {"birdhouse", "farming", "hunter", "notifications", "skilling", "stopwatches", "timers", "panel"}
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class TimeTrackingPlugin extends Plugin
|
||||||
|
{
|
||||||
|
@Inject
|
||||||
|
private ClientToolbar clientToolbar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FarmingTracker farmingTracker;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private BirdHouseTracker birdHouseTracker;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ClockManager clockManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ItemManager itemManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private TimeTrackingConfig config;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ScheduledExecutorService executorService;
|
||||||
|
|
||||||
|
private ScheduledFuture panelUpdateFuture;
|
||||||
|
|
||||||
|
private TimeTrackingPanel panel;
|
||||||
|
|
||||||
|
private NavigationButton navButton;
|
||||||
|
|
||||||
|
private WorldPoint lastTickLocation;
|
||||||
|
private boolean lastTickPostLogin;
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
TimeTrackingConfig provideConfig(ConfigManager configManager)
|
||||||
|
{
|
||||||
|
return configManager.getConfig(TimeTrackingConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startUp() throws Exception
|
||||||
|
{
|
||||||
|
clockManager.loadTimers();
|
||||||
|
clockManager.loadStopwatches();
|
||||||
|
birdHouseTracker.loadFromConfig();
|
||||||
|
farmingTracker.loadCompletionTimes();
|
||||||
|
|
||||||
|
final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png");
|
||||||
|
|
||||||
|
panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker, clockManager);
|
||||||
|
|
||||||
|
navButton = NavigationButton.builder()
|
||||||
|
.tooltip("Time Tracking")
|
||||||
|
.icon(icon)
|
||||||
|
.panel(panel)
|
||||||
|
.priority(4)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
clientToolbar.addNavigation(navButton);
|
||||||
|
|
||||||
|
panelUpdateFuture = executorService.scheduleAtFixedRate(this::updatePanel, 200, 200, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutDown() throws Exception
|
||||||
|
{
|
||||||
|
lastTickLocation = null;
|
||||||
|
lastTickPostLogin = false;
|
||||||
|
|
||||||
|
if (panelUpdateFuture != null)
|
||||||
|
{
|
||||||
|
panelUpdateFuture.cancel(true);
|
||||||
|
panelUpdateFuture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientToolbar.removeNavigation(navButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onConfigChanged(ConfigChanged e)
|
||||||
|
{
|
||||||
|
if (!e.getGroup().equals(CONFIG_GROUP))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clockManager.getTimers().isEmpty() && e.getKey().equals(TIMERS))
|
||||||
|
{
|
||||||
|
clockManager.loadTimers();
|
||||||
|
}
|
||||||
|
else if (clockManager.getStopwatches().isEmpty() && e.getKey().equals(STOPWATCHES))
|
||||||
|
{
|
||||||
|
clockManager.loadStopwatches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onGameTick(GameTick t)
|
||||||
|
{
|
||||||
|
if (client.getGameState() != GameState.LOGGED_IN)
|
||||||
|
{
|
||||||
|
lastTickLocation = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bird house data is only sent after exiting the post-login screen
|
||||||
|
Widget motd = client.getWidget(WidgetInfo.LOGIN_CLICK_TO_PLAY_SCREEN_MESSAGE_OF_THE_DAY);
|
||||||
|
if (motd != null && !motd.isHidden())
|
||||||
|
{
|
||||||
|
lastTickPostLogin = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastTickPostLogin)
|
||||||
|
{
|
||||||
|
lastTickPostLogin = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldPoint loc = lastTickLocation;
|
||||||
|
lastTickLocation = client.getLocalPlayer().getWorldLocation();
|
||||||
|
|
||||||
|
if (loc == null || loc.getPlane() != 0 || loc.getRegionID() != lastTickLocation.getRegionID())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean birdHouseDataChanged = birdHouseTracker.updateData(loc);
|
||||||
|
boolean farmingDataChanged = farmingTracker.updateData(loc);
|
||||||
|
|
||||||
|
if (birdHouseDataChanged || farmingDataChanged)
|
||||||
|
{
|
||||||
|
panel.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onUsernameChanged(UsernameChanged e)
|
||||||
|
{
|
||||||
|
farmingTracker.migrateConfiguration();
|
||||||
|
farmingTracker.loadCompletionTimes();
|
||||||
|
birdHouseTracker.loadFromConfig();
|
||||||
|
panel.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schedule(period = 10, unit = ChronoUnit.SECONDS)
|
||||||
|
public void checkCompletion()
|
||||||
|
{
|
||||||
|
boolean birdHouseDataChanged = birdHouseTracker.checkCompletion();
|
||||||
|
|
||||||
|
if (birdHouseDataChanged)
|
||||||
|
{
|
||||||
|
panel.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePanel()
|
||||||
|
{
|
||||||
|
long unitTime = Instant.now().toEpochMilli() / 200;
|
||||||
|
|
||||||
|
boolean clockDataChanged = false;
|
||||||
|
|
||||||
|
if (unitTime % 5 == 0)
|
||||||
|
{
|
||||||
|
clockDataChanged = clockManager.checkCompletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unitTime % panel.getUpdateInterval() == 0 || clockDataChanged)
|
||||||
|
{
|
||||||
|
panel.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,25 +7,24 @@
|
|||||||
* modification, are permitted provided that the following conditions are met:
|
* modification, are permitted provided that the following conditions are met:
|
||||||
*
|
*
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
* list of conditions and the following disclaimer.
|
* list of conditions and the following disclaimer.
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
* and/or other materials provided with the distribution.
|
* and/or other materials provided with the distribution.
|
||||||
*
|
*
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
@@ -40,16 +39,16 @@ import net.runelite.client.ui.components.ThinProgressBar;
|
|||||||
import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
|
import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
class FarmingPatchPanel extends JPanel
|
public class TimeablePanel<T> extends JPanel
|
||||||
{
|
{
|
||||||
private final FarmingPatch patch;
|
private final T timeable;
|
||||||
private final JLabel icon = new JLabel();
|
private final JLabel icon = new JLabel();
|
||||||
private final JLabel estimate = new JLabel();
|
private final JLabel estimate = new JLabel();
|
||||||
private final ThinProgressBar progress = new ThinProgressBar();
|
private final ThinProgressBar progress = new ThinProgressBar();
|
||||||
|
|
||||||
FarmingPatchPanel(FarmingPatch patch)
|
public TimeablePanel(T timeable, String title, int maximumProgressValue)
|
||||||
{
|
{
|
||||||
this.patch = patch;
|
this.timeable = timeable;
|
||||||
|
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
setBorder(new EmptyBorder(7, 0, 0, 0));
|
setBorder(new EmptyBorder(7, 0, 0, 0));
|
||||||
@@ -66,8 +65,7 @@ class FarmingPatchPanel extends JPanel
|
|||||||
infoPanel.setLayout(new GridLayout(2, 1));
|
infoPanel.setLayout(new GridLayout(2, 1));
|
||||||
infoPanel.setBorder(new EmptyBorder(4, 4, 4, 0));
|
infoPanel.setBorder(new EmptyBorder(4, 4, 4, 0));
|
||||||
|
|
||||||
final JLabel location = new JShadowedLabel(patch.getRegion().getName()
|
final JLabel location = new JShadowedLabel(title);
|
||||||
+ (Strings.isNullOrEmpty(patch.getName()) ? "" : " (" + patch.getName() + ")"));
|
|
||||||
location.setFont(FontManager.getRunescapeSmallFont());
|
location.setFont(FontManager.getRunescapeSmallFont());
|
||||||
location.setForeground(Color.WHITE);
|
location.setForeground(Color.WHITE);
|
||||||
|
|
||||||
@@ -80,6 +78,9 @@ class FarmingPatchPanel extends JPanel
|
|||||||
topContainer.add(icon, BorderLayout.WEST);
|
topContainer.add(icon, BorderLayout.WEST);
|
||||||
topContainer.add(infoPanel, BorderLayout.CENTER);
|
topContainer.add(infoPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
progress.setValue(0);
|
||||||
|
progress.setMaximumValue(maximumProgressValue);
|
||||||
|
|
||||||
add(topContainer, BorderLayout.NORTH);
|
add(topContainer, BorderLayout.NORTH);
|
||||||
add(progress, BorderLayout.SOUTH);
|
add(progress, BorderLayout.SOUTH);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
abstract class Clock
|
||||||
|
{
|
||||||
|
protected String name;
|
||||||
|
|
||||||
|
// last updated time (recorded as seconds since epoch)
|
||||||
|
protected long lastUpdate;
|
||||||
|
|
||||||
|
// whether the clock is currently running
|
||||||
|
protected boolean active;
|
||||||
|
|
||||||
|
Clock(String name)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.lastUpdate = Instant.now().getEpochSecond();
|
||||||
|
this.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract long getDisplayTime();
|
||||||
|
|
||||||
|
abstract void setDuration(long duration);
|
||||||
|
|
||||||
|
abstract boolean start();
|
||||||
|
|
||||||
|
abstract boolean pause();
|
||||||
|
|
||||||
|
abstract void reset();
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import joptsimple.internal.Strings;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.runelite.client.Notifier;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ClockManager
|
||||||
|
{
|
||||||
|
private static final long DEFAULT_TIMER_DURATION = 60 * 5; // 5 minutes
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ConfigManager configManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private TimeTrackingConfig config;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Notifier notifier;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final List<Timer> timers = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final List<Stopwatch> stopwatches = new ArrayList<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private ClockTabPanel clockTabPanel = new ClockTabPanel(this);
|
||||||
|
|
||||||
|
void addTimer()
|
||||||
|
{
|
||||||
|
timers.add(new Timer("Timer " + (timers.size() + 1), DEFAULT_TIMER_DURATION));
|
||||||
|
saveTimers();
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(clockTabPanel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addStopwatch()
|
||||||
|
{
|
||||||
|
stopwatches.add(new Stopwatch("Stopwatch " + (stopwatches.size() + 1)));
|
||||||
|
saveStopwatches();
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(clockTabPanel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeTimer(Timer timer)
|
||||||
|
{
|
||||||
|
timers.remove(timer);
|
||||||
|
saveTimers();
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(clockTabPanel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeStopwatch(Stopwatch stopwatch)
|
||||||
|
{
|
||||||
|
stopwatches.remove(stopwatch);
|
||||||
|
saveStopwatches();
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(clockTabPanel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getActiveTimerCount()
|
||||||
|
{
|
||||||
|
return timers.stream().filter(Timer::isActive).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getActiveStopwatchCount()
|
||||||
|
{
|
||||||
|
return stopwatches.stream().filter(Stopwatch::isActive).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if any timers have completed, and send notifications if required.
|
||||||
|
*/
|
||||||
|
public boolean checkCompletion()
|
||||||
|
{
|
||||||
|
boolean changed = false;
|
||||||
|
|
||||||
|
for (Timer timer : timers)
|
||||||
|
{
|
||||||
|
if (timer.isActive() && timer.getDisplayTime() == 0)
|
||||||
|
{
|
||||||
|
timer.pause();
|
||||||
|
changed = true;
|
||||||
|
|
||||||
|
if (config.timerNotification())
|
||||||
|
{
|
||||||
|
notifier.notify("[" + timer.getName() + "] has finished counting down.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
saveTimers();
|
||||||
|
SwingUtilities.invokeLater(clockTabPanel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadTimers()
|
||||||
|
{
|
||||||
|
final String timersJson = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.TIMERS);
|
||||||
|
|
||||||
|
if (!Strings.isNullOrEmpty(timersJson))
|
||||||
|
{
|
||||||
|
final Gson gson = new Gson();
|
||||||
|
final List<Timer> timers = gson.fromJson(timersJson, new TypeToken<ArrayList<Timer>>()
|
||||||
|
{
|
||||||
|
}.getType());
|
||||||
|
|
||||||
|
this.timers.clear();
|
||||||
|
this.timers.addAll(timers);
|
||||||
|
SwingUtilities.invokeLater(clockTabPanel::rebuild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadStopwatches()
|
||||||
|
{
|
||||||
|
final String stopwatchesJson = configManager.getConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.STOPWATCHES);
|
||||||
|
|
||||||
|
if (!Strings.isNullOrEmpty(stopwatchesJson))
|
||||||
|
{
|
||||||
|
final Gson gson = new Gson();
|
||||||
|
final List<Stopwatch> stopwatches = gson.fromJson(stopwatchesJson, new TypeToken<ArrayList<Stopwatch>>()
|
||||||
|
{
|
||||||
|
}.getType());
|
||||||
|
|
||||||
|
this.stopwatches.clear();
|
||||||
|
this.stopwatches.addAll(stopwatches);
|
||||||
|
SwingUtilities.invokeLater(clockTabPanel::rebuild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear()
|
||||||
|
{
|
||||||
|
timers.clear();
|
||||||
|
stopwatches.clear();
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(clockTabPanel::rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveToConfig()
|
||||||
|
{
|
||||||
|
saveTimers();
|
||||||
|
saveStopwatches();
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveTimers()
|
||||||
|
{
|
||||||
|
final Gson gson = new Gson();
|
||||||
|
final String json = gson.toJson(timers);
|
||||||
|
configManager.setConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.TIMERS, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveStopwatches()
|
||||||
|
{
|
||||||
|
final Gson gson = new Gson();
|
||||||
|
final String json = gson.toJson(stopwatches);
|
||||||
|
configManager.setConfiguration(TimeTrackingConfig.CONFIG_GROUP, TimeTrackingConfig.STOPWATCHES, json);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.FlowLayout;
|
||||||
|
import java.awt.event.FocusEvent;
|
||||||
|
import java.awt.event.FocusListener;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.border.CompoundBorder;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.components.FlatTextField;
|
||||||
|
import net.runelite.client.ui.components.IconButton;
|
||||||
|
|
||||||
|
abstract class ClockPanel extends JPanel
|
||||||
|
{
|
||||||
|
private static final Border NAME_BOTTOM_BORDER = new CompoundBorder(
|
||||||
|
BorderFactory.createMatteBorder(0, 0, 1, 0, ColorScheme.DARK_GRAY_COLOR),
|
||||||
|
BorderFactory.createLineBorder(ColorScheme.DARKER_GRAY_COLOR));
|
||||||
|
|
||||||
|
private static final Color ACTIVE_CLOCK_COLOR = ColorScheme.LIGHT_GRAY_COLOR.brighter();
|
||||||
|
private static final Color INACTIVE_CLOCK_COLOR = ColorScheme.LIGHT_GRAY_COLOR.darker();
|
||||||
|
|
||||||
|
// additional content or buttons should be added to these panels in the subclasses
|
||||||
|
final JPanel contentContainer;
|
||||||
|
final JPanel leftActions;
|
||||||
|
final JPanel rightActions;
|
||||||
|
|
||||||
|
private final FlatTextField nameInput;
|
||||||
|
private final IconButton startPauseButton;
|
||||||
|
private final FlatTextField displayInput;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
|
private final String clockType;
|
||||||
|
private final boolean editable;
|
||||||
|
|
||||||
|
ClockPanel(ClockManager clockManager, Clock clock, String clockType, boolean editable)
|
||||||
|
{
|
||||||
|
this.clock = clock;
|
||||||
|
this.clockType = clockType;
|
||||||
|
this.editable = editable;
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(new EmptyBorder(3, 0, 0, 0));
|
||||||
|
|
||||||
|
JPanel nameWrapper = new JPanel(new BorderLayout());
|
||||||
|
nameWrapper.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
nameWrapper.setBorder(NAME_BOTTOM_BORDER);
|
||||||
|
|
||||||
|
nameInput = new FlatTextField();
|
||||||
|
nameInput.setText(clock.getName());
|
||||||
|
nameInput.setBorder(null);
|
||||||
|
nameInput.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
nameInput.setPreferredSize(new Dimension(0, 24));
|
||||||
|
nameInput.getTextField().setBorder(new EmptyBorder(0, 8, 0, 0));
|
||||||
|
nameInput.addActionListener(e -> getParent().requestFocusInWindow());
|
||||||
|
|
||||||
|
nameInput.getTextField().addFocusListener(new FocusListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent e)
|
||||||
|
{
|
||||||
|
nameInput.getTextField().selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent e)
|
||||||
|
{
|
||||||
|
clock.setName(nameInput.getText());
|
||||||
|
clockManager.saveToConfig();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nameWrapper.add(nameInput, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
JPanel mainContainer = new JPanel(new BorderLayout());
|
||||||
|
mainContainer.setBorder(new EmptyBorder(5, 0, 0, 0));
|
||||||
|
mainContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
|
||||||
|
contentContainer = new JPanel(new BorderLayout());
|
||||||
|
contentContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
|
||||||
|
displayInput = new FlatTextField();
|
||||||
|
displayInput.setEditable(editable);
|
||||||
|
displayInput.setBorder(null);
|
||||||
|
displayInput.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
displayInput.setPreferredSize(new Dimension(0, 24));
|
||||||
|
displayInput.getTextField().setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
displayInput.addActionListener(e -> getParent().requestFocusInWindow());
|
||||||
|
|
||||||
|
displayInput.getTextField().addFocusListener(new FocusListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void focusGained(FocusEvent e)
|
||||||
|
{
|
||||||
|
displayInput.getTextField().setForeground(INACTIVE_CLOCK_COLOR);
|
||||||
|
displayInput.getTextField().selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void focusLost(FocusEvent e)
|
||||||
|
{
|
||||||
|
String[] parts = displayInput.getText().split(":");
|
||||||
|
long duration = 0;
|
||||||
|
|
||||||
|
// parse from back to front, so as to accept hour:min:sec, min:sec, and sec formats
|
||||||
|
for (int i = parts.length - 1, multiplier = 1; i >= 0 && multiplier <= 3600; i--, multiplier *= 60)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
duration += Integer.parseInt(parts[i].trim()) * multiplier;
|
||||||
|
}
|
||||||
|
catch (NumberFormatException nfe)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clock.setDuration(Math.max(0, duration));
|
||||||
|
clock.reset();
|
||||||
|
updateDisplayInput();
|
||||||
|
updateActivityStatus();
|
||||||
|
clockManager.saveTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateDisplayInput();
|
||||||
|
|
||||||
|
contentContainer.add(displayInput, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
JPanel actionsBar = new JPanel(new BorderLayout());
|
||||||
|
actionsBar.setBorder(new EmptyBorder(4, 0, 4, 0));
|
||||||
|
actionsBar.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
|
||||||
|
leftActions = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||||
|
leftActions.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
|
||||||
|
startPauseButton = new IconButton(ClockTabPanel.START_ICON);
|
||||||
|
startPauseButton.setPreferredSize(new Dimension(16, 14));
|
||||||
|
updateActivityStatus();
|
||||||
|
|
||||||
|
startPauseButton.addMouseListener(new MouseAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e)
|
||||||
|
{
|
||||||
|
startPauseButton.setIcon(clock.isActive() ? ClockTabPanel.PAUSE_ICON_HOVER : ClockTabPanel.START_ICON_HOVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e)
|
||||||
|
{
|
||||||
|
startPauseButton.setIcon(clock.isActive() ? ClockTabPanel.PAUSE_ICON : ClockTabPanel.START_ICON);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
startPauseButton.addActionListener(e ->
|
||||||
|
{
|
||||||
|
if (clock.isActive())
|
||||||
|
{
|
||||||
|
clock.pause();
|
||||||
|
}
|
||||||
|
else if (!clock.start())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActivityStatus();
|
||||||
|
clockManager.saveToConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
IconButton resetButton = new IconButton(ClockTabPanel.RESET_ICON, ClockTabPanel.RESET_ICON_HOVER);
|
||||||
|
resetButton.setPreferredSize(new Dimension(16, 14));
|
||||||
|
resetButton.setToolTipText("Reset " + clockType);
|
||||||
|
|
||||||
|
resetButton.addActionListener(e ->
|
||||||
|
{
|
||||||
|
clock.reset();
|
||||||
|
reset();
|
||||||
|
clockManager.saveToConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
leftActions.add(startPauseButton);
|
||||||
|
leftActions.add(resetButton);
|
||||||
|
|
||||||
|
rightActions = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
|
rightActions.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
|
||||||
|
actionsBar.add(leftActions, BorderLayout.WEST);
|
||||||
|
actionsBar.add(rightActions, BorderLayout.EAST);
|
||||||
|
|
||||||
|
mainContainer.add(contentContainer, BorderLayout.CENTER);
|
||||||
|
mainContainer.add(actionsBar, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
add(nameWrapper, BorderLayout.NORTH);
|
||||||
|
add(mainContainer, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
updateDisplayInput();
|
||||||
|
updateActivityStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDisplayInput()
|
||||||
|
{
|
||||||
|
if (!displayInput.getTextField().hasFocus())
|
||||||
|
{
|
||||||
|
displayInput.setText(getFormattedDuration(clock.getDisplayTime()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateActivityStatus()
|
||||||
|
{
|
||||||
|
boolean isActive = clock.isActive();
|
||||||
|
|
||||||
|
displayInput.setEditable(editable && !isActive);
|
||||||
|
displayInput.getTextField().setForeground(isActive ? ACTIVE_CLOCK_COLOR : INACTIVE_CLOCK_COLOR);
|
||||||
|
startPauseButton.setToolTipText(isActive ? "Pause " + clockType : "Start " + clockType);
|
||||||
|
startPauseButton.setIcon(isActive ? ClockTabPanel.PAUSE_ICON : ClockTabPanel.START_ICON);
|
||||||
|
|
||||||
|
if (editable && clock.getDisplayTime() == 0 && !isActive)
|
||||||
|
{
|
||||||
|
displayInput.getTextField().setForeground(ColorScheme.PROGRESS_ERROR_COLOR.darker());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getFormattedDuration(long duration)
|
||||||
|
{
|
||||||
|
long hours = duration / (60 * 60);
|
||||||
|
long mins = (duration / 60) % 60;
|
||||||
|
long seconds = duration % 60;
|
||||||
|
|
||||||
|
return String.format("%02d:%02d:%02d", hours, mins, seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import net.runelite.client.plugins.timetracking.TabContentPanel;
|
||||||
|
import net.runelite.client.plugins.timetracking.TimeTrackingPlugin;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.DynamicGridLayout;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
import net.runelite.client.ui.components.IconButton;
|
||||||
|
import net.runelite.client.ui.components.shadowlabel.JShadowedLabel;
|
||||||
|
import net.runelite.client.util.ImageUtil;
|
||||||
|
|
||||||
|
public class ClockTabPanel extends TabContentPanel
|
||||||
|
{
|
||||||
|
static final ImageIcon DELETE_ICON;
|
||||||
|
static final ImageIcon DELETE_ICON_HOVER;
|
||||||
|
static final ImageIcon LAP_ICON;
|
||||||
|
static final ImageIcon LAP_ICON_HOVER;
|
||||||
|
static final ImageIcon PAUSE_ICON;
|
||||||
|
static final ImageIcon PAUSE_ICON_HOVER;
|
||||||
|
static final ImageIcon RESET_ICON;
|
||||||
|
static final ImageIcon RESET_ICON_HOVER;
|
||||||
|
static final ImageIcon START_ICON;
|
||||||
|
static final ImageIcon START_ICON_HOVER;
|
||||||
|
|
||||||
|
private static final ImageIcon ADD_ICON;
|
||||||
|
private static final ImageIcon ADD_ICON_HOVER;
|
||||||
|
|
||||||
|
private final ClockManager clockManager;
|
||||||
|
|
||||||
|
private final List<ClockPanel> clockPanels = new ArrayList<>();
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
BufferedImage deleteIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "delete_icon.png");
|
||||||
|
BufferedImage lapIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "lap_icon.png");
|
||||||
|
BufferedImage pauseIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "pause_icon.png");
|
||||||
|
BufferedImage resetIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "reset_icon.png");
|
||||||
|
BufferedImage startIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "start_icon.png");
|
||||||
|
BufferedImage addIcon = ImageUtil.getResourceStreamFromClass(TimeTrackingPlugin.class, "add_icon.png");
|
||||||
|
|
||||||
|
DELETE_ICON = new ImageIcon(deleteIcon);
|
||||||
|
DELETE_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(deleteIcon, -80));
|
||||||
|
LAP_ICON = new ImageIcon(lapIcon);
|
||||||
|
LAP_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(lapIcon, -80));
|
||||||
|
PAUSE_ICON = new ImageIcon(pauseIcon);
|
||||||
|
PAUSE_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(pauseIcon, -80));
|
||||||
|
RESET_ICON = new ImageIcon(resetIcon);
|
||||||
|
RESET_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(resetIcon, -80));
|
||||||
|
START_ICON = new ImageIcon(startIcon);
|
||||||
|
START_ICON_HOVER = new ImageIcon(ImageUtil.grayscaleOffset(startIcon, -80));
|
||||||
|
ADD_ICON = new ImageIcon(addIcon);
|
||||||
|
ADD_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f));
|
||||||
|
}
|
||||||
|
|
||||||
|
ClockTabPanel(ClockManager clockManager)
|
||||||
|
{
|
||||||
|
this.clockManager = clockManager;
|
||||||
|
|
||||||
|
setLayout(new DynamicGridLayout(0, 1, 0, 4));
|
||||||
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears and recreates the components of this panel.
|
||||||
|
* This should be done whenever a clock is added or removed.
|
||||||
|
*/
|
||||||
|
void rebuild()
|
||||||
|
{
|
||||||
|
removeAll();
|
||||||
|
clockPanels.clear();
|
||||||
|
|
||||||
|
add(createHeaderPanel("Timers", "timer", false, e -> clockManager.addTimer()));
|
||||||
|
|
||||||
|
for (Timer timer : clockManager.getTimers())
|
||||||
|
{
|
||||||
|
TimerPanel panel = new TimerPanel(clockManager, timer);
|
||||||
|
|
||||||
|
clockPanels.add(panel);
|
||||||
|
add(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clockManager.getTimers().isEmpty())
|
||||||
|
{
|
||||||
|
add(createInfoPanel("Click the + button to add a timer."));
|
||||||
|
}
|
||||||
|
|
||||||
|
add(createHeaderPanel("Stopwatches", "stopwatch", true, e -> clockManager.addStopwatch()));
|
||||||
|
|
||||||
|
for (Stopwatch stopwatch : clockManager.getStopwatches())
|
||||||
|
{
|
||||||
|
StopwatchPanel panel = new StopwatchPanel(clockManager, stopwatch);
|
||||||
|
|
||||||
|
clockPanels.add(panel);
|
||||||
|
add(panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clockManager.getStopwatches().isEmpty())
|
||||||
|
{
|
||||||
|
add(createInfoPanel("Click the + button to add a stopwatch."));
|
||||||
|
}
|
||||||
|
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createHeaderPanel(String title, String type, boolean largePadding, ActionListener actionListener)
|
||||||
|
{
|
||||||
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
panel.setBorder(new EmptyBorder(largePadding ? 11 : 0, 0, 0, 0));
|
||||||
|
panel.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
JLabel headerLabel = new JLabel(title);
|
||||||
|
headerLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
panel.add(headerLabel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
IconButton addButton = new IconButton(ADD_ICON, ADD_ICON_HOVER);
|
||||||
|
addButton.setPreferredSize(new Dimension(14, 14));
|
||||||
|
addButton.setToolTipText("Add a " + type);
|
||||||
|
addButton.addActionListener(actionListener);
|
||||||
|
panel.add(addButton, BorderLayout.EAST);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createInfoPanel(String text)
|
||||||
|
{
|
||||||
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
panel.setBorder(new EmptyBorder(7, 8, 6, 8));
|
||||||
|
panel.setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
JLabel infoLabel = new JShadowedLabel(text);
|
||||||
|
infoLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR.darker());
|
||||||
|
infoLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
panel.add(infoLabel);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUpdateInterval()
|
||||||
|
{
|
||||||
|
return 1; // 200 milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update()
|
||||||
|
{
|
||||||
|
for (ClockPanel panel : clockPanels)
|
||||||
|
{
|
||||||
|
if (panel.getClock().isActive())
|
||||||
|
{
|
||||||
|
panel.updateDisplayInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
class Stopwatch extends Clock
|
||||||
|
{
|
||||||
|
// the number of seconds elapsed, as of last updated time
|
||||||
|
private long elapsed = 0;
|
||||||
|
|
||||||
|
// a list of lap times (recorded as seconds since epoch)
|
||||||
|
private List<Long> laps = new ArrayList<>();
|
||||||
|
|
||||||
|
Stopwatch(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
long getDisplayTime()
|
||||||
|
{
|
||||||
|
if (!active)
|
||||||
|
{
|
||||||
|
return elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(0, elapsed + (Instant.now().getEpochSecond() - lastUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setDuration(long duration)
|
||||||
|
{
|
||||||
|
elapsed = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean start()
|
||||||
|
{
|
||||||
|
if (!active)
|
||||||
|
{
|
||||||
|
lastUpdate = Instant.now().getEpochSecond();
|
||||||
|
active = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean pause()
|
||||||
|
{
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
elapsed = Math.max(0, elapsed + (Instant.now().getEpochSecond() - lastUpdate));
|
||||||
|
lastUpdate = Instant.now().getEpochSecond();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lap()
|
||||||
|
{
|
||||||
|
laps.add(getDisplayTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
elapsed = 0;
|
||||||
|
laps.clear();
|
||||||
|
lastUpdate = Instant.now().getEpochSecond();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.GridBagConstraints;
|
||||||
|
import java.awt.GridBagLayout;
|
||||||
|
import java.awt.Insets;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
import net.runelite.client.ui.components.IconButton;
|
||||||
|
|
||||||
|
class StopwatchPanel extends ClockPanel
|
||||||
|
{
|
||||||
|
private static final Color LAP_DATA_COLOR = ColorScheme.LIGHT_GRAY_COLOR.darker();
|
||||||
|
|
||||||
|
private final JPanel lapsContainer;
|
||||||
|
private final Stopwatch stopwatch;
|
||||||
|
|
||||||
|
StopwatchPanel(ClockManager clockManager, Stopwatch stopwatch)
|
||||||
|
{
|
||||||
|
super(clockManager, stopwatch, "stopwatch", false);
|
||||||
|
|
||||||
|
this.stopwatch = stopwatch;
|
||||||
|
|
||||||
|
lapsContainer = new JPanel(new GridBagLayout());
|
||||||
|
lapsContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
rebuildLapList();
|
||||||
|
|
||||||
|
contentContainer.add(lapsContainer);
|
||||||
|
|
||||||
|
IconButton lapButton = new IconButton(ClockTabPanel.LAP_ICON, ClockTabPanel.LAP_ICON_HOVER);
|
||||||
|
lapButton.setPreferredSize(new Dimension(16, 14));
|
||||||
|
lapButton.setToolTipText("Add lap time");
|
||||||
|
|
||||||
|
lapButton.addActionListener(e ->
|
||||||
|
{
|
||||||
|
stopwatch.lap();
|
||||||
|
rebuildLapList();
|
||||||
|
clockManager.saveStopwatches();
|
||||||
|
});
|
||||||
|
|
||||||
|
leftActions.add(lapButton);
|
||||||
|
|
||||||
|
IconButton deleteButton = new IconButton(ClockTabPanel.DELETE_ICON, ClockTabPanel.DELETE_ICON_HOVER);
|
||||||
|
deleteButton.setPreferredSize(new Dimension(16, 14));
|
||||||
|
deleteButton.setToolTipText("Delete stopwatch");
|
||||||
|
deleteButton.addActionListener(e -> clockManager.removeStopwatch(stopwatch));
|
||||||
|
rightActions.add(deleteButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
super.reset();
|
||||||
|
rebuildLapList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildLapList()
|
||||||
|
{
|
||||||
|
lapsContainer.removeAll();
|
||||||
|
|
||||||
|
List<Long> laps = stopwatch.getLaps();
|
||||||
|
|
||||||
|
if (laps.isEmpty())
|
||||||
|
{
|
||||||
|
lapsContainer.setBorder(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lapsContainer.setBorder(new EmptyBorder(5, 0, 0, 0));
|
||||||
|
|
||||||
|
GridBagConstraints c = new GridBagConstraints();
|
||||||
|
c.insets = new Insets(4, 5, 3, 5);
|
||||||
|
c.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
c.weightx = 1;
|
||||||
|
c.gridx = 0;
|
||||||
|
c.gridy = 0;
|
||||||
|
|
||||||
|
long previousLap = 0;
|
||||||
|
for (long lap : stopwatch.getLaps())
|
||||||
|
{
|
||||||
|
c.gridx = 0;
|
||||||
|
lapsContainer.add(createSmallLabel("" + (c.gridy + 1)), c);
|
||||||
|
|
||||||
|
c.gridx = 1;
|
||||||
|
lapsContainer.add(createSmallLabel(getFormattedDuration(lap - previousLap)), c);
|
||||||
|
|
||||||
|
c.gridx = 2;
|
||||||
|
lapsContainer.add(createSmallLabel(getFormattedDuration(lap)), c);
|
||||||
|
|
||||||
|
previousLap = lap;
|
||||||
|
c.gridy++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lapsContainer.revalidate();
|
||||||
|
lapsContainer.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JLabel createSmallLabel(String text)
|
||||||
|
{
|
||||||
|
JLabel label = new JLabel(text, SwingConstants.CENTER);
|
||||||
|
label.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
label.setForeground(LAP_DATA_COLOR);
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
class Timer extends Clock
|
||||||
|
{
|
||||||
|
// the total number of seconds that the timer should run for
|
||||||
|
private long duration;
|
||||||
|
|
||||||
|
// the number of seconds remaining on the timer, as of last updated time
|
||||||
|
private long remaining;
|
||||||
|
|
||||||
|
Timer(String name, long duration)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
this.duration = duration;
|
||||||
|
this.remaining = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
long getDisplayTime()
|
||||||
|
{
|
||||||
|
if (!active)
|
||||||
|
{
|
||||||
|
return remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(0, remaining - (Instant.now().getEpochSecond() - lastUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean start()
|
||||||
|
{
|
||||||
|
if (!active && duration > 0)
|
||||||
|
{
|
||||||
|
if (remaining <= 0)
|
||||||
|
{
|
||||||
|
remaining = duration;
|
||||||
|
}
|
||||||
|
lastUpdate = Instant.now().getEpochSecond();
|
||||||
|
active = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean pause()
|
||||||
|
{
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
remaining = Math.max(0, remaining - (Instant.now().getEpochSecond() - lastUpdate));
|
||||||
|
lastUpdate = Instant.now().getEpochSecond();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
active = false;
|
||||||
|
remaining = duration;
|
||||||
|
lastUpdate = Instant.now().getEpochSecond();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.clocks;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import net.runelite.client.ui.components.IconButton;
|
||||||
|
|
||||||
|
class TimerPanel extends ClockPanel
|
||||||
|
{
|
||||||
|
TimerPanel(ClockManager clockManager, Timer timer)
|
||||||
|
{
|
||||||
|
super(clockManager, timer, "timer", true);
|
||||||
|
|
||||||
|
IconButton deleteButton = new IconButton(ClockTabPanel.DELETE_ICON, ClockTabPanel.DELETE_ICON_HOVER);
|
||||||
|
deleteButton.setPreferredSize(new Dimension(16, 14));
|
||||||
|
deleteButton.setToolTipText("Delete timer");
|
||||||
|
deleteButton.addActionListener(e -> clockManager.removeTimer(timer));
|
||||||
|
rightActions.add(deleteButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -34,7 +34,7 @@ import net.runelite.api.Varbits;
|
|||||||
access = AccessLevel.PACKAGE
|
access = AccessLevel.PACKAGE
|
||||||
)
|
)
|
||||||
@Getter
|
@Getter
|
||||||
public class FarmingPatch
|
class FarmingPatch
|
||||||
{
|
{
|
||||||
@Setter(AccessLevel.PACKAGE)
|
@Setter(AccessLevel.PACKAGE)
|
||||||
private FarmingRegion region;
|
private FarmingRegion region;
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.runelite.api.Varbits;
|
import net.runelite.api.Varbits;
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Abex
|
||||||
|
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import java.awt.GridBagConstraints;
|
||||||
|
import java.awt.GridBagLayout;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.vars.Autoweed;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.TabContentPanel;
|
||||||
|
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
|
||||||
|
import net.runelite.client.plugins.timetracking.TimeablePanel;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class FarmingTabPanel extends TabContentPanel
|
||||||
|
{
|
||||||
|
private final Client client;
|
||||||
|
private final ItemManager itemManager;
|
||||||
|
private final ConfigManager configManager;
|
||||||
|
private final TimeTrackingConfig config;
|
||||||
|
private final List<TimeablePanel<FarmingPatch>> patchPanels;
|
||||||
|
|
||||||
|
FarmingTabPanel(Client client, ItemManager itemManager, ConfigManager configManager,
|
||||||
|
TimeTrackingConfig config, Set<FarmingPatch> patches)
|
||||||
|
{
|
||||||
|
this.client = client;
|
||||||
|
this.itemManager = itemManager;
|
||||||
|
this.configManager = configManager;
|
||||||
|
this.config = config;
|
||||||
|
this.patchPanels = new ArrayList<>();
|
||||||
|
|
||||||
|
setLayout(new GridBagLayout());
|
||||||
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
GridBagConstraints c = new GridBagConstraints();
|
||||||
|
c.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
c.weightx = 1;
|
||||||
|
c.gridx = 0;
|
||||||
|
c.gridy = 0;
|
||||||
|
|
||||||
|
PatchImplementation lastImpl = null;
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
|
for (FarmingPatch patch : patches)
|
||||||
|
{
|
||||||
|
String title = patch.getRegion().getName() + (Strings.isNullOrEmpty(patch.getName()) ? "" : " (" + patch.getName() + ")");
|
||||||
|
TimeablePanel<FarmingPatch> p = new TimeablePanel<>(patch, title, 1);
|
||||||
|
|
||||||
|
/* Show labels to subdivide tabs into sections */
|
||||||
|
if (patch.getImplementation() != lastImpl && !Strings.isNullOrEmpty(patch.getImplementation().getName()))
|
||||||
|
{
|
||||||
|
JLabel groupLabel = new JLabel(patch.getImplementation().getName());
|
||||||
|
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
groupLabel.setBorder(new EmptyBorder(4, 0, 0, 0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
groupLabel.setBorder(new EmptyBorder(15, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
groupLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
|
||||||
|
add(groupLabel, c);
|
||||||
|
c.gridy++;
|
||||||
|
lastImpl = patch.getImplementation();
|
||||||
|
}
|
||||||
|
|
||||||
|
patchPanels.add(p);
|
||||||
|
add(p, c);
|
||||||
|
c.gridy++;
|
||||||
|
|
||||||
|
/* This is a weird hack to remove the top border on the first tracker of every tab */
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
p.setBorder(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUpdateInterval()
|
||||||
|
{
|
||||||
|
return 50; // 10 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update()
|
||||||
|
{
|
||||||
|
long unixNow = Instant.now().getEpochSecond();
|
||||||
|
log.debug("Updating panel with username {}", client.getUsername());
|
||||||
|
|
||||||
|
boolean autoweed;
|
||||||
|
{
|
||||||
|
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername();
|
||||||
|
autoweed = Integer.toString(Autoweed.ON.ordinal())
|
||||||
|
.equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TimeablePanel<FarmingPatch> panel : patchPanels)
|
||||||
|
{
|
||||||
|
FarmingPatch patch = panel.getTimeable();
|
||||||
|
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + patch.getRegion().getRegionID();
|
||||||
|
String key = Integer.toString(patch.getVarbit().getId());
|
||||||
|
String storedValue = configManager.getConfiguration(group, key);
|
||||||
|
long unixTime = 0;
|
||||||
|
int value = 0;
|
||||||
|
if (storedValue != null)
|
||||||
|
{
|
||||||
|
String[] parts = storedValue.split(":");
|
||||||
|
if (parts.length == 2)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = Integer.parseInt(parts[0]);
|
||||||
|
unixTime = Long.parseLong(parts[1]);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchState state = unixTime <= 0 ? null : patch.getImplementation().forVarbitValue(value);
|
||||||
|
if (state == null)
|
||||||
|
{
|
||||||
|
itemManager.getImage(Produce.WEEDS.getItemID()).addTo(panel.getIcon());
|
||||||
|
panel.getIcon().setToolTipText("Unknown state");
|
||||||
|
panel.getProgress().setMaximumValue(0);
|
||||||
|
panel.getProgress().setValue(0);
|
||||||
|
panel.getProgress().setVisible(false);
|
||||||
|
panel.getEstimate().setText("Unknown");
|
||||||
|
panel.getProgress().setBackground(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (state.getProduce().getItemID() < 0)
|
||||||
|
{
|
||||||
|
panel.getIcon().setIcon(null);
|
||||||
|
panel.getIcon().setToolTipText("Unknown state");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemManager.getImage(state.getProduce().getItemID()).addTo(panel.getIcon());
|
||||||
|
panel.getIcon().setToolTipText(state.getProduce().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
int stage = state.getStage();
|
||||||
|
int stages = state.getStages();
|
||||||
|
int tickrate = state.getTickRate() * 60;
|
||||||
|
|
||||||
|
if (autoweed && state.getProduce() == Produce.WEEDS)
|
||||||
|
{
|
||||||
|
stage = 0;
|
||||||
|
stages = 1;
|
||||||
|
tickrate = 0;
|
||||||
|
}
|
||||||
|
if (tickrate > 0)
|
||||||
|
{
|
||||||
|
long tickNow = unixNow / tickrate;
|
||||||
|
long tickTime = unixTime / tickrate;
|
||||||
|
int delta = (int) (tickNow - tickTime);
|
||||||
|
|
||||||
|
long doneEstimate = ((stages - 1 - stage) + tickTime) * tickrate;
|
||||||
|
|
||||||
|
stage += delta;
|
||||||
|
if (stage >= stages)
|
||||||
|
{
|
||||||
|
stage = stages - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doneEstimate < unixNow)
|
||||||
|
{
|
||||||
|
panel.getEstimate().setText("Done");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
panel.getEstimate().setText("Done " + getFormattedEstimate(doneEstimate - unixNow, config.estimateRelative()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (state.getCropState())
|
||||||
|
{
|
||||||
|
case HARVESTABLE:
|
||||||
|
panel.getEstimate().setText("Done");
|
||||||
|
break;
|
||||||
|
case GROWING:
|
||||||
|
if (stage == stages - 1)
|
||||||
|
{
|
||||||
|
panel.getEstimate().setText("Done");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
panel.getEstimate().setText("Unknown");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DISEASED:
|
||||||
|
panel.getEstimate().setText("Diseased");
|
||||||
|
break;
|
||||||
|
case DEAD:
|
||||||
|
panel.getEstimate().setText("Dead");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide any fully grown weeds' progress bar. */
|
||||||
|
if (state.getProduce() != Produce.WEEDS
|
||||||
|
|| (state.getProduce() == Produce.WEEDS && !autoweed && stage < stages - 1))
|
||||||
|
{
|
||||||
|
panel.getProgress().setVisible(true);
|
||||||
|
panel.getProgress().setForeground(state.getCropState().getColor().darker());
|
||||||
|
panel.getProgress().setMaximumValue(stages - 1);
|
||||||
|
panel.getProgress().setValue(stage);
|
||||||
|
panel.getProgress().update();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
panel.getProgress().setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Abex
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.Varbits;
|
||||||
|
import net.runelite.api.coords.WorldPoint;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.Tab;
|
||||||
|
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class FarmingTracker
|
||||||
|
{
|
||||||
|
@Deprecated
|
||||||
|
private static final String OLD_KEY_NAME = "farmingTracker";
|
||||||
|
|
||||||
|
private final Client client;
|
||||||
|
private final ItemManager itemManager;
|
||||||
|
private final ConfigManager configManager;
|
||||||
|
private final TimeTrackingConfig config;
|
||||||
|
private final FarmingWorld farmingWorld;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private FarmingTracker(Client client, ItemManager itemManager, ConfigManager configManager,
|
||||||
|
TimeTrackingConfig config, FarmingWorld farmingWorld)
|
||||||
|
{
|
||||||
|
this.client = client;
|
||||||
|
this.itemManager = itemManager;
|
||||||
|
this.configManager = configManager;
|
||||||
|
this.config = config;
|
||||||
|
this.farmingWorld = farmingWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at which all patches of a particular type will be ready to be harvested,
|
||||||
|
* or {@code -1} if we have no data about any patch of the given type.
|
||||||
|
*
|
||||||
|
* Each value is set to {@code 0} if all patches of that type have already completed
|
||||||
|
* when updating the value.
|
||||||
|
*/
|
||||||
|
private Map<PatchImplementation, Long> completionTimes = new EnumMap<>(PatchImplementation.class);
|
||||||
|
|
||||||
|
public FarmingTabPanel createTabPanel(Tab tab)
|
||||||
|
{
|
||||||
|
return new FarmingTabPanel(client, itemManager, configManager, config, farmingWorld.getTabs().get(tab));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates tracker data for the current region. Returns true if any data was changed.
|
||||||
|
*/
|
||||||
|
public boolean updateData(WorldPoint location)
|
||||||
|
{
|
||||||
|
boolean changed = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername();
|
||||||
|
String autoweed = Integer.toString(client.getVar(Varbits.AUTOWEED));
|
||||||
|
if (!autoweed.equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED)))
|
||||||
|
{
|
||||||
|
configManager.setConfiguration(group, TimeTrackingConfig.AUTOWEED, autoweed);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FarmingRegion region = farmingWorld.getRegions().get(location.getRegionID());
|
||||||
|
if (region != null && region.isInBounds(location))
|
||||||
|
{
|
||||||
|
// Write config with new varbits
|
||||||
|
// timetracking.<login-username>.<regionID>.<VarbitID>=<varbitValue>:<unix time>
|
||||||
|
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + region.getRegionID();
|
||||||
|
long unixNow = Instant.now().getEpochSecond();
|
||||||
|
for (FarmingPatch patch : region.getPatches())
|
||||||
|
{
|
||||||
|
// Write the config value if it doesn't match what is current, or it is more than 5 minutes old
|
||||||
|
Varbits varbit = patch.getVarbit();
|
||||||
|
String key = Integer.toString(varbit.getId());
|
||||||
|
String strVarbit = Integer.toString(client.getVar(varbit));
|
||||||
|
String storedValue = configManager.getConfiguration(group, key);
|
||||||
|
|
||||||
|
if (storedValue != null)
|
||||||
|
{
|
||||||
|
String[] parts = storedValue.split(":");
|
||||||
|
if (parts.length == 2 && parts[0].equals(strVarbit))
|
||||||
|
{
|
||||||
|
long unixTime = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
unixTime = Long.parseLong(parts[1]);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
if (unixTime + (5 * 60) > unixNow && unixNow + 30 > unixTime)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = strVarbit + ":" + unixNow;
|
||||||
|
configManager.setConfiguration(group, key, value);
|
||||||
|
updateCompletionTime(patch.getImplementation());
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadCompletionTimes()
|
||||||
|
{
|
||||||
|
completionTimes.clear();
|
||||||
|
|
||||||
|
for (PatchImplementation patchType : PatchImplementation.values())
|
||||||
|
{
|
||||||
|
updateCompletionTime(patchType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the overall completion time for the given patch type.
|
||||||
|
* @see #completionTimes
|
||||||
|
*/
|
||||||
|
public long getCompletionTime(PatchImplementation patchType)
|
||||||
|
{
|
||||||
|
Long completionTime = completionTimes.get(patchType);
|
||||||
|
return completionTime == null ? -1 : completionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the overall completion time for the given patch type.
|
||||||
|
* @see #completionTimes
|
||||||
|
*/
|
||||||
|
private void updateCompletionTime(PatchImplementation patchType)
|
||||||
|
{
|
||||||
|
long maxCompletionTime = 0;
|
||||||
|
boolean allUnknown = true;
|
||||||
|
|
||||||
|
for (FarmingPatch patch : farmingWorld.getPatchTypes().get(patchType))
|
||||||
|
{
|
||||||
|
String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + patch.getRegion().getRegionID();
|
||||||
|
String key = Integer.toString(patch.getVarbit().getId());
|
||||||
|
String storedValue = configManager.getConfiguration(group, key);
|
||||||
|
long unixTime = 0;
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
if (storedValue != null)
|
||||||
|
{
|
||||||
|
String[] parts = storedValue.split(":");
|
||||||
|
if (parts.length == 2)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = Integer.parseInt(parts[0]);
|
||||||
|
unixTime = Long.parseLong(parts[1]);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchState state = unixTime <= 0 ? null : patch.getImplementation().forVarbitValue(value);
|
||||||
|
if (state == null || state.getProduce().getItemID() < 0)
|
||||||
|
{
|
||||||
|
continue; // unknown state
|
||||||
|
}
|
||||||
|
|
||||||
|
int tickrate = state.getTickRate() * 60;
|
||||||
|
int stage = state.getStage();
|
||||||
|
int stages = state.getStages();
|
||||||
|
|
||||||
|
if (state.getProduce() != Produce.WEEDS && state.getProduce() != Produce.SCARECROW)
|
||||||
|
{
|
||||||
|
// update max duration if this patch takes longer to grow
|
||||||
|
if (tickrate > 0)
|
||||||
|
{
|
||||||
|
long tickTime = unixTime / tickrate;
|
||||||
|
long doneEstimate = ((stages - 1 - stage) + tickTime) * tickrate;
|
||||||
|
maxCompletionTime = Math.max(maxCompletionTime, doneEstimate);
|
||||||
|
}
|
||||||
|
else if (state.getCropState() == CropState.GROWING && stage != stages - 1)
|
||||||
|
{
|
||||||
|
continue; // unknown state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allUnknown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allUnknown)
|
||||||
|
{
|
||||||
|
completionTimes.put(patchType, -1L);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
completionTimes.put(patchType, (maxCompletionTime <= Instant.now().getEpochSecond()) ? 0 : maxCompletionTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates configuration data from {@code "farmingTracker"} key to {@code "timetracking"} key.
|
||||||
|
* This method should be removed after a reasonable amount of time.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void migrateConfiguration()
|
||||||
|
{
|
||||||
|
String username = client.getUsername();
|
||||||
|
|
||||||
|
// migrate autoweed config
|
||||||
|
{
|
||||||
|
String oldGroup = OLD_KEY_NAME + "." + username;
|
||||||
|
String newGroup = TimeTrackingConfig.CONFIG_GROUP + "." + username;
|
||||||
|
String storedValue = configManager.getConfiguration(oldGroup, TimeTrackingConfig.AUTOWEED);
|
||||||
|
|
||||||
|
if (storedValue != null)
|
||||||
|
{
|
||||||
|
configManager.setConfiguration(newGroup, TimeTrackingConfig.AUTOWEED, storedValue);
|
||||||
|
configManager.unsetConfiguration(oldGroup, TimeTrackingConfig.AUTOWEED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// migrate all saved data in all regions
|
||||||
|
for (FarmingRegion region : farmingWorld.getRegions().values())
|
||||||
|
{
|
||||||
|
String oldGroup = OLD_KEY_NAME + "." + username + "." + region.getRegionID();
|
||||||
|
String newGroup = TimeTrackingConfig.CONFIG_GROUP + "." + username + "." + region.getRegionID();
|
||||||
|
|
||||||
|
for (Varbits varbit : region.getVarbits())
|
||||||
|
{
|
||||||
|
String key = Integer.toString(varbit.getId());
|
||||||
|
String storedValue = configManager.getConfiguration(oldGroup, key);
|
||||||
|
|
||||||
|
if (storedValue != null)
|
||||||
|
{
|
||||||
|
configManager.setConfiguration(newGroup, key, storedValue);
|
||||||
|
configManager.unsetConfiguration(oldGroup, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,12 +23,15 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
@@ -36,9 +39,10 @@ import java.util.TreeSet;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.runelite.api.Varbits;
|
import net.runelite.api.Varbits;
|
||||||
import net.runelite.api.coords.WorldPoint;
|
import net.runelite.api.coords.WorldPoint;
|
||||||
|
import net.runelite.client.plugins.timetracking.Tab;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FarmingWorld
|
class FarmingWorld
|
||||||
{
|
{
|
||||||
@Getter
|
@Getter
|
||||||
private Map<Integer, FarmingRegion> regions = new HashMap<>();
|
private Map<Integer, FarmingRegion> regions = new HashMap<>();
|
||||||
@@ -46,12 +50,15 @@ public class FarmingWorld
|
|||||||
@Getter
|
@Getter
|
||||||
private Map<Tab, Set<FarmingPatch>> tabs = new HashMap<>();
|
private Map<Tab, Set<FarmingPatch>> tabs = new HashMap<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private Map<PatchImplementation, List<FarmingPatch>> patchTypes = new EnumMap<>(PatchImplementation.class);
|
||||||
|
|
||||||
private final Comparator<FarmingPatch> tabSorter = Comparator
|
private final Comparator<FarmingPatch> tabSorter = Comparator
|
||||||
.comparing(FarmingPatch::getImplementation)
|
.comparing(FarmingPatch::getImplementation)
|
||||||
.thenComparing((FarmingPatch p) -> p.getRegion().getName())
|
.thenComparing((FarmingPatch p) -> p.getRegion().getName())
|
||||||
.thenComparing(FarmingPatch::getName);
|
.thenComparing(FarmingPatch::getName);
|
||||||
|
|
||||||
public FarmingWorld()
|
FarmingWorld()
|
||||||
{
|
{
|
||||||
// Some of these patches get updated in multiple regions.
|
// Some of these patches get updated in multiple regions.
|
||||||
// It may be worth it to add a specialization for these patches
|
// It may be worth it to add a specialization for these patches
|
||||||
@@ -242,6 +249,10 @@ public class FarmingWorld
|
|||||||
tabs
|
tabs
|
||||||
.computeIfAbsent(p.getImplementation().getTab(), k -> new TreeSet<>(tabSorter))
|
.computeIfAbsent(p.getImplementation().getTab(), k -> new TreeSet<>(tabSorter))
|
||||||
.add(p);
|
.add(p);
|
||||||
|
|
||||||
|
patchTypes
|
||||||
|
.computeIfAbsent(p.getImplementation(), k -> new ArrayList<>())
|
||||||
|
.add(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,10 +22,11 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.runelite.client.plugins.timetracking.Tab;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@@ -733,7 +734,7 @@ public enum PatchImplementation
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FLOWER(Tab.HERB, "Flowers")
|
FLOWER(Tab.FLOWER, "")
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
PatchState forVarbitValue(int value)
|
PatchState forVarbitValue(int value)
|
||||||
@@ -1205,7 +1206,7 @@ public enum PatchImplementation
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FRUIT_TREE(Tab.FRUIT_TREE, "Fruit trees")
|
FRUIT_TREE(Tab.FRUIT_TREE, "")
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
PatchState forVarbitValue(int value)
|
PatchState forVarbitValue(int value)
|
||||||
@@ -1454,7 +1455,7 @@ public enum PatchImplementation
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
HOPS(Tab.SPECIAL, "Hops")
|
HOPS(Tab.HOPS, "")
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
PatchState forVarbitValue(int value)
|
PatchState forVarbitValue(int value)
|
||||||
@@ -2267,7 +2268,7 @@ public enum PatchImplementation
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GRAPES(Tab.BUSH, "Grapes")
|
GRAPES(Tab.GRAPE, "")
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
PatchState forVarbitValue(int value)
|
PatchState forVarbitValue(int value)
|
||||||
@@ -22,14 +22,32 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public class PatchState
|
class PatchState
|
||||||
{
|
{
|
||||||
private final Produce produce;
|
private final Produce produce;
|
||||||
private final CropState cropState;
|
private final CropState cropState;
|
||||||
private final int stage;
|
private final int stage;
|
||||||
|
|
||||||
|
int getStages()
|
||||||
|
{
|
||||||
|
return cropState == CropState.HARVESTABLE ? produce.getHarvestStages() : produce.getStages();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getTickRate()
|
||||||
|
{
|
||||||
|
switch (cropState)
|
||||||
|
{
|
||||||
|
case HARVESTABLE:
|
||||||
|
return produce.getRegrowTickrate();
|
||||||
|
case GROWING:
|
||||||
|
return produce.getTickrate();
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -127,7 +127,7 @@ public enum Produce
|
|||||||
*/
|
*/
|
||||||
private final int tickrate;
|
private final int tickrate;
|
||||||
/**
|
/**
|
||||||
* How many states this crop has during groth. Typically tickcount+1
|
* How many states this crop has during growth. Typically tickcount+1
|
||||||
*/
|
*/
|
||||||
private final int stages;
|
private final int stages;
|
||||||
/**
|
/**
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.hunter;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.runelite.api.ItemID;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
enum BirdHouse
|
||||||
|
{
|
||||||
|
NORMAL("Bird House", ItemID.BIRD_HOUSE),
|
||||||
|
OAK("Oak Bird House", ItemID.OAK_BIRD_HOUSE),
|
||||||
|
WILLOW("Willow Bird House", ItemID.WILLOW_BIRD_HOUSE),
|
||||||
|
TEAK("Teak Bird House", ItemID.TEAK_BIRD_HOUSE),
|
||||||
|
MAPLE("Maple Bird House", ItemID.MAPLE_BIRD_HOUSE),
|
||||||
|
MAHOGANY("Mahogany Bird House", ItemID.MAHOGANY_BIRD_HOUSE),
|
||||||
|
YEW("Yew Bird House", ItemID.YEW_BIRD_HOUSE),
|
||||||
|
MAGIC("Magic Bird House", ItemID.MAGIC_BIRD_HOUSE),
|
||||||
|
REDWOOD("Redwood Bird House", ItemID.REDWOOD_BIRD_HOUSE);
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final int itemID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@code BirdHouse} corresponding to the given {@code VarPlayer} value.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
static BirdHouse fromVarpValue(int varp)
|
||||||
|
{
|
||||||
|
int index = (varp - 1) / 3;
|
||||||
|
|
||||||
|
if (varp <= 0 || index >= values().length)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return values()[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.hunter;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains data about the state of a particular {@link BirdHouseSpace}, at a particular point in time.
|
||||||
|
*/
|
||||||
|
@Value
|
||||||
|
class BirdHouseData
|
||||||
|
{
|
||||||
|
private BirdHouseSpace space;
|
||||||
|
private int varp;
|
||||||
|
private long timestamp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.hunter;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.runelite.api.VarPlayer;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
enum BirdHouseSpace
|
||||||
|
{
|
||||||
|
MEADOW_NORTH("Mushroom Meadow (North)", VarPlayer.BIRD_HOUSE_MEADOW_NORTH),
|
||||||
|
MEADOW_SOUTH("Mushroom Meadow (South)", VarPlayer.BIRD_HOUSE_MEADOW_SOUTH),
|
||||||
|
VALLEY_NORTH("Verdant Valley (Northeast)", VarPlayer.BIRD_HOUSE_VALLEY_NORTH),
|
||||||
|
VALLEY_SOUTH("Verdant Valley (Southwest)", VarPlayer.BIRD_HOUSE_VALLEY_SOUTH);
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final VarPlayer varp;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.hunter;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
enum BirdHouseState
|
||||||
|
{
|
||||||
|
SEEDED(ColorScheme.PROGRESS_COMPLETE_COLOR),
|
||||||
|
BUILT(ColorScheme.PROGRESS_INPROGRESS_COLOR),
|
||||||
|
EMPTY(ColorScheme.MEDIUM_GRAY_COLOR),
|
||||||
|
UNKNOWN(ColorScheme.MEDIUM_GRAY_COLOR);
|
||||||
|
|
||||||
|
private final Color color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@code BirdHouseState} corresponding to the given {@code VarPlayer} value.
|
||||||
|
*/
|
||||||
|
static BirdHouseState fromVarpValue(int varp)
|
||||||
|
{
|
||||||
|
if (varp < 0 || varp > BirdHouse.values().length * 3)
|
||||||
|
{
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
else if (varp == 0)
|
||||||
|
{
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
else if (varp % 3 == 0)
|
||||||
|
{
|
||||||
|
return SEEDED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return BUILT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Abex
|
||||||
|
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.hunter;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import net.runelite.api.ItemID;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.TabContentPanel;
|
||||||
|
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
|
||||||
|
import net.runelite.client.plugins.timetracking.TimeablePanel;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.DynamicGridLayout;
|
||||||
|
|
||||||
|
public class BirdHouseTabPanel extends TabContentPanel
|
||||||
|
{
|
||||||
|
private static final Color COMPLETED_COLOR = ColorScheme.PROGRESS_COMPLETE_COLOR.darker();
|
||||||
|
|
||||||
|
private final ItemManager itemManager;
|
||||||
|
private final BirdHouseTracker birdHouseTracker;
|
||||||
|
private final TimeTrackingConfig config;
|
||||||
|
private final List<TimeablePanel<BirdHouseSpace>> spacePanels;
|
||||||
|
|
||||||
|
BirdHouseTabPanel(ItemManager itemManager, BirdHouseTracker birdHouseTracker, TimeTrackingConfig config)
|
||||||
|
{
|
||||||
|
this.itemManager = itemManager;
|
||||||
|
this.birdHouseTracker = birdHouseTracker;
|
||||||
|
this.config = config;
|
||||||
|
this.spacePanels = new ArrayList<>();
|
||||||
|
|
||||||
|
setLayout(new DynamicGridLayout(0, 1, 0, 0));
|
||||||
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
|
for (BirdHouseSpace space : BirdHouseSpace.values())
|
||||||
|
{
|
||||||
|
TimeablePanel<BirdHouseSpace> panel = new TimeablePanel<>(space, space.getName(), BirdHouseTracker.BIRD_HOUSE_DURATION);
|
||||||
|
|
||||||
|
spacePanels.add(panel);
|
||||||
|
add(panel);
|
||||||
|
|
||||||
|
// remove the top border on the first panel
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
panel.setBorder(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getUpdateInterval()
|
||||||
|
{
|
||||||
|
return 50; // 10 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update()
|
||||||
|
{
|
||||||
|
long unixNow = Instant.now().getEpochSecond();
|
||||||
|
|
||||||
|
for (TimeablePanel<BirdHouseSpace> panel : spacePanels)
|
||||||
|
{
|
||||||
|
BirdHouseSpace space = panel.getTimeable();
|
||||||
|
BirdHouseData data = birdHouseTracker.getBirdHouseData().get(space);
|
||||||
|
int value = -1;
|
||||||
|
long startTime = 0;
|
||||||
|
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
value = data.getVarp();
|
||||||
|
startTime = data.getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
BirdHouse birdHouse = BirdHouse.fromVarpValue(value);
|
||||||
|
BirdHouseState state = BirdHouseState.fromVarpValue(value);
|
||||||
|
|
||||||
|
if (birdHouse == null)
|
||||||
|
{
|
||||||
|
itemManager.getImage(ItemID.FEATHER).addTo(panel.getIcon());
|
||||||
|
panel.getProgress().setVisible(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemManager.getImage(birdHouse.getItemID()).addTo(panel.getIcon());
|
||||||
|
panel.getIcon().setToolTipText(birdHouse.getName());
|
||||||
|
panel.getProgress().setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.getProgress().setForeground(state.getColor().darker());
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case EMPTY:
|
||||||
|
panel.getIcon().setToolTipText("Empty");
|
||||||
|
panel.getEstimate().setText("Empty");
|
||||||
|
break;
|
||||||
|
case BUILT:
|
||||||
|
panel.getProgress().setValue(0);
|
||||||
|
panel.getEstimate().setText("Built");
|
||||||
|
break;
|
||||||
|
case SEEDED:
|
||||||
|
long remainingTime = startTime + BirdHouseTracker.BIRD_HOUSE_DURATION - unixNow;
|
||||||
|
if (remainingTime <= 0)
|
||||||
|
{
|
||||||
|
panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION);
|
||||||
|
panel.getProgress().setForeground(COMPLETED_COLOR);
|
||||||
|
panel.getEstimate().setText("Done");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
panel.getProgress().setValue((int) (BirdHouseTracker.BIRD_HOUSE_DURATION - remainingTime));
|
||||||
|
panel.getEstimate().setText("Done " + getFormattedEstimate(remainingTime, config.estimateRelative()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
panel.getIcon().setToolTipText("Unknown state");
|
||||||
|
panel.getEstimate().setText("Unknown");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.getProgress().update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Abex
|
||||||
|
* Copyright (c) 2018, Daniel Teo <https://github.com/takuyakanbr>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.timetracking.hunter;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.coords.WorldPoint;
|
||||||
|
import net.runelite.client.Notifier;
|
||||||
|
import net.runelite.client.config.ConfigManager;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.plugins.timetracking.TimeTrackingConfig;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class BirdHouseTracker
|
||||||
|
{
|
||||||
|
// average time taken to harvest 10 birds, in seconds
|
||||||
|
static final int BIRD_HOUSE_DURATION = (int) Duration.ofMinutes(50).getSeconds();
|
||||||
|
|
||||||
|
private static ImmutableSet<Integer> FOSSIL_ISLAND_REGIONS = ImmutableSet.of(14650, 14651, 14652, 14906, 14907, 15162, 15163);
|
||||||
|
|
||||||
|
private final Client client;
|
||||||
|
private final ItemManager itemManager;
|
||||||
|
private final ConfigManager configManager;
|
||||||
|
private final TimeTrackingConfig config;
|
||||||
|
private final Notifier notifier;
|
||||||
|
|
||||||
|
@Getter(AccessLevel.PACKAGE)
|
||||||
|
private final ConcurrentMap<BirdHouseSpace, BirdHouseData> birdHouseData = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private BirdHouseTracker(Client client, ItemManager itemManager, ConfigManager configManager,
|
||||||
|
TimeTrackingConfig config, Notifier notifier)
|
||||||
|
{
|
||||||
|
this.client = client;
|
||||||
|
this.itemManager = itemManager;
|
||||||
|
this.configManager = configManager;
|
||||||
|
this.config = config;
|
||||||
|
this.notifier = notifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at which all the bird houses will be ready to be dismantled,
|
||||||
|
* or {@code -1} if we have no data about any of the bird house spaces.
|
||||||
|
*
|
||||||
|
* This is set to {@code 0} if the bird houses have already completed
|
||||||
|
* when updating it.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private long completionTime = -1;
|
||||||
|
|
||||||
|
public BirdHouseTabPanel createBirdHouseTabPanel()
|
||||||
|
{
|
||||||
|
return new BirdHouseTabPanel(itemManager, this, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadFromConfig()
|
||||||
|
{
|
||||||
|
birdHouseData.clear();
|
||||||
|
|
||||||
|
final String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE;
|
||||||
|
|
||||||
|
for (BirdHouseSpace space : BirdHouseSpace.values())
|
||||||
|
{
|
||||||
|
String key = Integer.toString(space.getVarp().getId());
|
||||||
|
String storedValue = configManager.getConfiguration(group, key);
|
||||||
|
|
||||||
|
if (storedValue != null)
|
||||||
|
{
|
||||||
|
String[] parts = storedValue.split(":");
|
||||||
|
if (parts.length == 2)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int varp = Integer.parseInt(parts[0]);
|
||||||
|
long timestamp = Long.parseLong(parts[1]);
|
||||||
|
birdHouseData.put(space, new BirdHouseData(space, varp, timestamp));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCompletionTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates tracker data if player is within range of any bird house. Returns true if any data was changed.
|
||||||
|
*/
|
||||||
|
public boolean updateData(WorldPoint location)
|
||||||
|
{
|
||||||
|
boolean changed = false;
|
||||||
|
|
||||||
|
if (FOSSIL_ISLAND_REGIONS.contains(location.getRegionID()))
|
||||||
|
{
|
||||||
|
final Map<BirdHouseSpace, BirdHouseData> newData = new HashMap<>();
|
||||||
|
final long currentTime = Instant.now().getEpochSecond();
|
||||||
|
int removalCount = 0;
|
||||||
|
|
||||||
|
for (BirdHouseSpace space : BirdHouseSpace.values())
|
||||||
|
{
|
||||||
|
int varp = client.getVar(space.getVarp());
|
||||||
|
BirdHouseData oldData = birdHouseData.get(space);
|
||||||
|
int oldVarp = oldData == null ? -1 : oldData.getVarp();
|
||||||
|
|
||||||
|
// update data if there isn't one, or if the varp doesn't match
|
||||||
|
if (varp != oldVarp)
|
||||||
|
{
|
||||||
|
newData.put(space, new BirdHouseData(space, varp, currentTime));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varp <= 0 && oldVarp > 0)
|
||||||
|
{
|
||||||
|
removalCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent the resetting of bird house data that could occur if the varps have not been updated yet
|
||||||
|
// after the player enters the region. We assume that players would generally have 3 or 4 bird houses
|
||||||
|
// built at any time, and that dropping from 3/4 to 0 built bird houses is not normally possible.
|
||||||
|
if (removalCount > 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
birdHouseData.putAll(newData);
|
||||||
|
updateCompletionTime();
|
||||||
|
saveToConfig(newData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the bird houses have become ready to be dismantled,
|
||||||
|
* and sends a notification if required.
|
||||||
|
*/
|
||||||
|
public boolean checkCompletion()
|
||||||
|
{
|
||||||
|
if (completionTime > 0 && completionTime < Instant.now().getEpochSecond())
|
||||||
|
{
|
||||||
|
completionTime = 0;
|
||||||
|
|
||||||
|
if (config.birdHouseNotification())
|
||||||
|
{
|
||||||
|
notifier.notify("Your bird houses are ready to be dismantled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the overall completion time of the bird houses.
|
||||||
|
* @see #completionTime
|
||||||
|
*/
|
||||||
|
private void updateCompletionTime()
|
||||||
|
{
|
||||||
|
if (birdHouseData.isEmpty())
|
||||||
|
{
|
||||||
|
completionTime = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long maxCompletionTime = 0;
|
||||||
|
for (BirdHouseData data : birdHouseData.values())
|
||||||
|
{
|
||||||
|
if (BirdHouseState.fromVarpValue(data.getVarp()) == BirdHouseState.SEEDED)
|
||||||
|
{
|
||||||
|
maxCompletionTime = Math.max(maxCompletionTime, data.getTimestamp() + BIRD_HOUSE_DURATION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completionTime = (maxCompletionTime <= Instant.now().getEpochSecond()) ? 0 : maxCompletionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveToConfig(Map<BirdHouseSpace, BirdHouseData> updatedData)
|
||||||
|
{
|
||||||
|
final String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE;
|
||||||
|
|
||||||
|
for (BirdHouseData data : updatedData.values())
|
||||||
|
{
|
||||||
|
String key = Integer.toString(data.getSpace().getVarp().getId());
|
||||||
|
configManager.setConfiguration(group, key, data.getVarp() + ":" + data.getTimestamp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 500 B |
|
After Width: | Height: | Size: 122 B |
|
After Width: | Height: | Size: 647 B |
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 563 B |
|
After Width: | Height: | Size: 801 B |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 2.6 KiB |
@@ -21,7 +21,7 @@
|
|||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/package net.runelite.client.plugins.farmingtracker;
|
*/package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
package net.runelite.client.plugins.farmingtracker;
|
package net.runelite.client.plugins.timetracking.farming;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||