From b50296414209a8e645da80b77cc16ec66c586b6b Mon Sep 17 00:00:00 2001 From: takuyakanbr Date: Sat, 28 Jul 2018 23:11:12 +0800 Subject: [PATCH 1/4] Convert farming tracker to time tracking plugin --- .../{farmingtracker => timetracking}/Tab.java | 19 +- .../plugins/timetracking/TabContentPanel.java | 40 ++++ .../TimeTrackingConfig.java} | 24 +- .../timetracking/TimeTrackingPanel.java | 151 +++++++++++++ .../TimeTrackingPlugin.java} | 110 +++------ .../farming}/CropState.java | 2 +- .../farming}/FarmingPatch.java | 4 +- .../farming}/FarmingPatchPanel.java | 2 +- .../farming}/FarmingRegion.java | 2 +- .../farming/FarmingTabPanel.java} | 211 +++++------------- .../timetracking/farming/FarmingTracker.java | 169 ++++++++++++++ .../farming}/FarmingWorld.java | 7 +- .../farming}/PatchImplementation.java | 11 +- .../farming}/PatchState.java | 22 +- .../farming}/Produce.java | 4 +- .../client/plugins/farmingtracker/farming.png | Bin 1605 -> 0 bytes .../client/plugins/timetracking/watch.png | Bin 0 -> 2622 bytes .../farming}/FarmingWorldTest.java | 2 +- .../farming}/PatchImplementationTest.java | 2 +- 19 files changed, 509 insertions(+), 273 deletions(-) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking}/Tab.java (73%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker/FarmingTrackerConfig.java => timetracking/TimeTrackingConfig.java} (80%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker/FarmingTrackerPlugin.java => timetracking/TimeTrackingPlugin.java} (54%) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/CropState.java (96%) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/FarmingPatch.java (95%) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/FarmingPatchPanel.java (98%) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/FarmingRegion.java (97%) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker/FarmingTrackerPanel.java => timetracking/farming/FarmingTabPanel.java} (58%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/FarmingWorld.java (98%) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/PatchImplementation.java (99%) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/PatchState.java (79%) rename runelite-client/src/main/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/Produce.java (97%) delete mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/farmingtracker/farming.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/watch.png rename runelite-client/src/test/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/FarmingWorldTest.java (96%) rename runelite-client/src/test/java/net/runelite/client/plugins/{farmingtracker => timetracking/farming}/PatchImplementationTest.java (98%) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Tab.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java similarity index 73% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Tab.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java index 50f50b2ed8..5eafc8129f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Tab.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java @@ -22,7 +22,7 @@ * (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; +package net.runelite.client.plugins.timetracking; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -32,12 +32,17 @@ import net.runelite.api.ItemID; @Getter public enum Tab { - ALLOTMENT("Allotments", ItemID.CABBAGE), - HERB("Herbs", ItemID.GRIMY_RANARR_WEED), - TREE("Trees", ItemID.MAHOGANY_LOGS), - FRUIT_TREE("Fruit Trees", ItemID.PINEAPPLE), - BUSH("Bushes", ItemID.REDBERRIES), - SPECIAL("Special", ItemID.MUSHROOM); + ALLOTMENT("Allotment Patches", ItemID.CABBAGE), + FLOWER("Flower Patches", ItemID.RED_FLOWERS), + 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 int itemID; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java new file mode 100644 index 0000000000..199d6c920b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 javax.swing.JPanel; +import net.runelite.client.ui.PluginPanel; + +public abstract class TabContentPanel extends JPanel +{ + public abstract void update(); + + @Override + public Dimension getPreferredSize() + { + return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java similarity index 80% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerConfig.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java index 30653b5aa9..9d013445f0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java @@ -22,22 +22,22 @@ * (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; +package net.runelite.client.plugins.timetracking; import net.runelite.client.config.Config; import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigItem; -@ConfigGroup("farmingTracker") -public interface FarmingTrackerConfig extends Config +@ConfigGroup("timetracking") +public interface TimeTrackingConfig extends Config { - String KEY_NAME = "farmingTracker"; + String KEY_NAME = "timetracking"; String AUTOWEED = "autoweed"; @ConfigItem( keyName = "estimateRelative", 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" ) default boolean estimateRelative() { @@ -45,21 +45,21 @@ public interface FarmingTrackerConfig extends Config } @ConfigItem( - keyName = "patch", - name = "Default patch", - description = "Default patch on opening the panel", + keyName = "activeTab", + name = "Active Tab", + description = "The currently selected tab", hidden = true ) - default Tab patch() + default Tab activeTab() { return Tab.ALLOTMENT; } @ConfigItem( - keyName = "patch", + keyName = "activeTab", name = "", description = "", hidden = true ) - void setPatch(Tab t); -} \ No newline at end of file + void setActiveTab(Tab t); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java new file mode 100644 index 0000000000..72842e08a1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2018, 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 javax.annotation.Nullable; +import javax.swing.ImageIcon; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +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.farming.FarmingTracker; +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 MaterialTabGroup tabGroup = new MaterialTabGroup(display); + + private boolean active; + + @Nullable + private TabContentPanel activeTabPanel = null; + + TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, FarmingTracker farmingTracker) + { + 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); + + 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; + }); + + tabGroup.addTab(materialTab); + if (config.activeTab() == tab) + { + tabGroup.select(materialTab); + } + } + + void update() + { + if (!active || activeTabPanel == null) + { + return; + } + + activeTabPanel.update(); + } + + @Override + public void onActivate() + { + active = true; + update(); + } + + @Override + public void onDeactivate() + { + active = false; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java similarity index 54% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPlugin.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java index 81c4b9c495..200f133975 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2018 Abex + * Copyright (c) 2018, Daniel Teo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,19 +23,17 @@ * (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; +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 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; @@ -42,58 +41,56 @@ 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.plugins.timetracking.farming.FarmingTracker; 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"} + name = "Time Tracking", + description = "Enable the Time Tracking panel, which contains farming trackers", + tags = {"farming", "skilling", "panel"} ) @Slf4j -public class FarmingTrackerPlugin extends Plugin +public class TimeTrackingPlugin extends Plugin { @Inject private ClientToolbar clientToolbar; - @Inject - private ConfigManager configManager; - @Inject private Client client; @Inject - private FarmingWorld farmingWorld; + private FarmingTracker farmingTracker; @Inject private ItemManager itemManager; @Inject - private FarmingTrackerConfig config; + private TimeTrackingConfig config; - private FarmingTrackerPanel panel; + private TimeTrackingPanel panel; private NavigationButton navButton; - private WorldPoint lastTickLoc; + private WorldPoint lastTickLocation; @Provides - FarmingTrackerConfig provideConfig(ConfigManager configManager) + TimeTrackingConfig provideConfig(ConfigManager configManager) { - return configManager.getConfig(FarmingTrackerConfig.class); + return configManager.getConfig(TimeTrackingConfig.class); } @Override protected void startUp() throws Exception { - final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "farming.png"); + final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png"); - panel = new FarmingTrackerPanel(client, itemManager, configManager, config, farmingWorld); + panel = new TimeTrackingPanel(itemManager, config, farmingTracker); navButton = NavigationButton.builder() - .tooltip("Farming Tracker") + .tooltip("Time Tracking") .icon(icon) .panel(panel) .priority(4) @@ -104,15 +101,10 @@ public class FarmingTrackerPlugin extends Plugin updatePanel(); } - @Subscribe - public void onUsernameChanged(UsernameChanged e) - { - updatePanel(); - } - @Override protected void shutDown() throws Exception { + lastTickLocation = null; clientToolbar.removeNavigation(navButton); } @@ -121,73 +113,33 @@ public class FarmingTrackerPlugin extends Plugin { if (client.getGameState() != GameState.LOGGED_IN) { - lastTickLoc = null; + lastTickLocation = null; return; } - WorldPoint loc = lastTickLoc; - lastTickLoc = client.getLocalPlayer().getWorldLocation(); - if (loc == null || loc.getRegionID() != lastTickLoc.getRegionID()) + WorldPoint loc = lastTickLocation; + lastTickLocation = client.getLocalPlayer().getWorldLocation(); + + if (loc == null || loc.getRegionID() != lastTickLocation.getRegionID()) { return; } - boolean changed = false; + boolean farmingDataChanged = farmingTracker.updateData(loc); - { - 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...=: - 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) + if (farmingDataChanged) { updatePanel(); } } + @Subscribe + public void onUsernameChanged(UsernameChanged e) + { + farmingTracker.migrateConfiguration(); + updatePanel(); + } + @Schedule(period = 10, unit = ChronoUnit.SECONDS) public void updatePanel() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/CropState.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/CropState.java similarity index 96% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/CropState.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/CropState.java index 90bd66ff73..2d9ffced83 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/CropState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/CropState.java @@ -22,7 +22,7 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import java.awt.Color; import lombok.Getter; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatch.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatch.java similarity index 95% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatch.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatch.java index 7409687cd7..8c24588917 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatch.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatch.java @@ -22,7 +22,7 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import lombok.AccessLevel; import lombok.Getter; @@ -34,7 +34,7 @@ import net.runelite.api.Varbits; access = AccessLevel.PACKAGE ) @Getter -public class FarmingPatch +class FarmingPatch { @Setter(AccessLevel.PACKAGE) private FarmingRegion region; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatchPanel.java similarity index 98% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatchPanel.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatchPanel.java index aa3162b1ad..42bbcad621 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingPatchPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatchPanel.java @@ -23,7 +23,7 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import com.google.common.base.Strings; import java.awt.BorderLayout; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingRegion.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java similarity index 97% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingRegion.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java index 305a10863a..c768d30c14 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingRegion.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingRegion.java @@ -22,7 +22,7 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import lombok.Getter; import net.runelite.api.Varbits; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java similarity index 58% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPanel.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java index ab0f4b8d7b..986716ec5c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java @@ -23,15 +23,11 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; 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; @@ -39,180 +35,106 @@ import java.time.format.TextStyle; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import javax.swing.ImageIcon; +import java.util.Set; 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.plugins.timetracking.TabContentPanel; +import net.runelite.client.plugins.timetracking.TimeTrackingConfig; 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 +public class FarmingTabPanel extends TabContentPanel { private final Client client; private final ItemManager itemManager; private final ConfigManager configManager; - private final FarmingTrackerConfig config; + private final TimeTrackingConfig config; + private final List patchPanels; - private boolean active; - - private List 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 - ) + FarmingTabPanel(Client client, ItemManager itemManager, ConfigManager configManager, + TimeTrackingConfig config, Set patches) { - super(false); - this.client = client; this.itemManager = itemManager; this.configManager = configManager; this.config = config; + this.patchPanels = new ArrayList<>(); - setLayout(new BorderLayout()); + setLayout(new GridBagLayout()); setBackground(ColorScheme.DARK_GRAY_COLOR); - display.setBorder(new EmptyBorder(10, 10, 8, 10)); + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.gridx = 0; + c.gridy = 0; - tabGroup.setBorder(new EmptyBorder(10, 1, 0, 0)); + PatchImplementation lastImpl = null; - add(tabGroup, BorderLayout.NORTH); - add(display, BorderLayout.CENTER); - - farmingWorld.getTabs().forEach((tab, patches) -> + boolean first = true; + for (FarmingPatch patch : patches) { - JPanel container = new JPanel(new GridBagLayout()) + FarmingPatchPanel p = new FarmingPatchPanel(patch); + + /* Show labels to subdivide tabs into sections */ + if (patch.getImplementation() != lastImpl && !Strings.isNullOrEmpty(patch.getImplementation().getName())) { - @Override - public Dimension getPreferredSize() - { - return new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height); - } - }; - container.setBackground(ColorScheme.DARK_GRAY_COLOR); + JLabel groupLabel = new JLabel(patch.getImplementation().getName()); - 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); + 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(); } - JPanel wrapped = new JPanel(new BorderLayout()); - wrapped.add(container, BorderLayout.NORTH); - wrapped.setBackground(ColorScheme.DARK_GRAY_COLOR); + patchPanels.add(p); + add(p, c); + c.gridy++; - 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 = () -> + /* This is a weird hack to remove the top border on the first tracker of every tab */ + if (first) { - 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); + first = false; + p.setBorder(null); } - }); + } + } - void update() + @Override + public void update() { - if (!active) - { - return; - } - long unixNow = Instant.now().getEpochSecond(); log.debug("Updating panel with username {}", client.getUsername()); - boolean autoweed = false; + + boolean autoweed; { - String group = FarmingTrackerConfig.KEY_NAME + "." + client.getUsername(); + String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername(); autoweed = Integer.toString(Autoweed.ON.ordinal()) - .equals(configManager.getConfiguration(group, FarmingTrackerConfig.AUTOWEED)); + .equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED)); } + for (FarmingPatchPanel panel : patchPanels) { FarmingPatch patch = panel.getPatch(); - String group = FarmingTrackerConfig.KEY_NAME + "." + client.getUsername() + "." + patch.getRegion().getRegionID(); + String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + patch.getRegion().getRegionID(); String key = Integer.toString(patch.getVarbit().getId()); String storedValue = configManager.getConfiguration(group, key); long unixTime = 0; @@ -258,19 +180,9 @@ class FarmingTrackerPanel extends PluginPanel } 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; - } + int stages = state.getStages(); + int tickrate = state.getTickRate() * 60; + if (autoweed && state.getProduce() == Produce.WEEDS) { stage = 0; @@ -374,17 +286,4 @@ class FarmingTrackerPanel extends PluginPanel } } } - - @Override - public void onActivate() - { - active = true; - update(); - } - - @Override - public void onDeactivate() - { - active = false; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java new file mode 100644 index 0000000000..ba2ce8b0d3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java @@ -0,0 +1,169 @@ +/* + * 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 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; + } + + + 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.KEY_NAME + "." + 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...=: + String group = TimeTrackingConfig.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) + { + // ignored + } + if (unixTime + (5 * 60) > unixNow && unixNow + 30 > unixTime) + { + continue; + } + } + } + + String value = strVarbit + ":" + unixNow; + configManager.setConfiguration(group, key, value); + changed = true; + } + } + + return changed; + } + + /** + * 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.KEY_NAME + "." + 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.KEY_NAME + "." + 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); + } + } + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingWorld.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java similarity index 98% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingWorld.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java index d5079c1696..7af327ec69 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/FarmingWorld.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java @@ -23,7 +23,7 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import com.google.inject.Singleton; import java.util.Collections; @@ -36,9 +36,10 @@ import java.util.TreeSet; import lombok.Getter; import net.runelite.api.Varbits; import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.timetracking.Tab; @Singleton -public class FarmingWorld +class FarmingWorld { @Getter private Map regions = new HashMap<>(); @@ -51,7 +52,7 @@ public class FarmingWorld .thenComparing((FarmingPatch p) -> p.getRegion().getName()) .thenComparing(FarmingPatch::getName); - public FarmingWorld() + FarmingWorld() { // Some of these patches get updated in multiple regions. // It may be worth it to add a specialization for these patches diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchImplementation.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchImplementation.java similarity index 99% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchImplementation.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchImplementation.java index 89f4c40274..0a4000ac8e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchImplementation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchImplementation.java @@ -22,10 +22,11 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import lombok.Getter; import lombok.RequiredArgsConstructor; +import net.runelite.client.plugins.timetracking.Tab; @RequiredArgsConstructor @Getter @@ -733,7 +734,7 @@ public enum PatchImplementation return null; } }, - FLOWER(Tab.HERB, "Flowers") + FLOWER(Tab.FLOWER, "") { @Override PatchState forVarbitValue(int value) @@ -1205,7 +1206,7 @@ public enum PatchImplementation return null; } }, - FRUIT_TREE(Tab.FRUIT_TREE, "Fruit trees") + FRUIT_TREE(Tab.FRUIT_TREE, "") { @Override PatchState forVarbitValue(int value) @@ -1454,7 +1455,7 @@ public enum PatchImplementation return null; } }, - HOPS(Tab.SPECIAL, "Hops") + HOPS(Tab.HOPS, "") { @Override PatchState forVarbitValue(int value) @@ -2267,7 +2268,7 @@ public enum PatchImplementation return null; } }, - GRAPES(Tab.BUSH, "Grapes") + GRAPES(Tab.GRAPE, "") { @Override PatchState forVarbitValue(int value) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchState.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchState.java similarity index 79% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchState.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchState.java index c27e13d6c6..2857882b3f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/PatchState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/PatchState.java @@ -22,14 +22,32 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import lombok.Value; @Value -public class PatchState +class PatchState { private final Produce produce; private final CropState cropState; 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; + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Produce.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/Produce.java similarity index 97% rename from runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Produce.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/Produce.java index 867105fee7..96d4c6e189 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/farmingtracker/Produce.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/Produce.java @@ -23,7 +23,7 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -127,7 +127,7 @@ public enum Produce */ 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; /** diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/farmingtracker/farming.png b/runelite-client/src/main/resources/net/runelite/client/plugins/farmingtracker/farming.png deleted file mode 100644 index 5b1c5dff73e782679760929c1ce69fab121e558a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1605 zcmbVMZD<>19KX(P#JG)it#ry@57!S`?d4u_x!m2Fl{AS>bl1i%bgiOzxqFfv$=!3k zd%C2B>74U}i0Fq2B83gaGRE)~gfYe#I4HOe4kq}<{4i09g6I$&D)?O9=*D!ogXf;N z-}C#w|6j>ZO>PT67^Wy{TP~ZPA@dG0`a@gD|MRE*xg z#3B-7@S*_XN`#jnlSzb{TmJ?V(U-cRq5r?r2-DUX*7nsxpnnoArE_$`5aMNI64cgGbKEd)`tXn}C`4#jp)ZOcw1Q6G1 zT35zxb+PTP2p?w-5;NKkxh>i+EV+=Kfj(OF4464c)Eo}1an+Oub&R|MLbXny@*R~y zDg_=sU^ynL`y(6RFs);lph?VRh7%cHCGQ5kw!FbolH{y|06G2Fdvn0UH4$JOuTi6tYeAlY*4H%E7)5NY~F1s z><*lCi(uJpQ)JU^bkHH)%_D8h-udVVX@Otm(#eAS*}KQig(vu}FJF6NonAXc4JGqO z&xVE`x^VHk%k#az9(reFHU3)e=><7ox&FIoZKCcuIT(K6&o>WW(|6;mi`!3AXP%i( zZv7VjbOODF_VkPn`CmTUx3@Yl`141fz8IoIo6jfZYX7WeytH;|;nm)LaM=`Rf4un3 z{iE`gzD*y9A@fAm?EUcT^}hA_l_TQ%smFI+sy=e#`oim944gSSSUTMJc*~WcwLkW( zy#Ke@@()$%1XKV3 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/watch.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/watch.png new file mode 100644 index 0000000000000000000000000000000000000000..e418efc732e3281cdc9a294ab352f0debcefc624 GIT binary patch literal 2622 zcmbVO3se->8J-wJ6+$7JG^j~g#|IcQJ2U&<(N$!Z$BIjMtb$QvXJ=;FDZ4w%&cH4< ziP2L`Rs;$vqG(b?ZDW0$NP+~bs4*o}5~-x3#YdwUHHz9IX{;&joqee}8am{z8ESt7%5HHxWGT#jHkfvE_&Rua5mV9iZii5x>} z&=&a8!8So~5h&{Mc%&Y=lyeuLxK^u0F&Qe8Apn8!rA~qLB2IonNP~glDL3m9Sk5UC zHIf#tSkS?MX}|@CE3DSZ2g3vchI&aCic2xkB|ng+!Z=s4+wM0`Qz&C+9E?-o0TvHq zL6l6UF#LgP9gZ-X7g9<9qo8jOMDylS7lY<7JXh?dn3NJ=aYBfg%f<;DZ{r@~M_7EH zHv((D8_xSoijw=FjF)`~RFs6FE<*2SNP%;kInEwRe^!W_M6Z`5=CMwi^YBwd=0P}P zAO%JTgGx{!mWzfI(f(0>3qri7+~Ofpk)g(V&9?RVmBT1gTW2)M|?g(UMvk zQ7IG(#KLGvL_y0mn3b|kr z(W`K+N`=Uzz!9+w2*3k)L5jVjgF}<~Vj0?t1CweQ1}+pApTR}*A0fXhp6g^m^kuL_ zETjO@T(B(xLco@PE{pXt_Iv{ig30?y$h7xL=HDw(i5>6nJR+UtK_Qj~_mEtsC^)jS z5`VK1Bo&}fi6zU>aB!M^K;9oOZvnL)8v_FMBgGtm@tjrgkZxvH0f_D+S_OR=b)GEw zapI3xusp(N;p%#4uXdL%V?No)>Ld=onbdKP+8lZcR!YITA!Km_4qjrpH3-> zESp!8oii!*X`g*V;$*35VN2JMx;T&XbW3+)>!E0$Z&}&PV@H*)i5#74+VQs$YnoT2 z71z~bRnzv%J=d>#mQB7~+cd3w!10B)CU$vy_3+~p<{c4CcYeF3>HsnlJ^$3LVdIlV zG+t^dzO>A9I7a_`{!cQO-AYU{WR!h*x>Iv8`K5!H$-F3S?nL3Mu03yjv3TGgYvcMW zGnlP|#(h`Y?!+W*iK*Oz9+)xa=jmI{(+SqgM=QD==ITl5Ol04CpMBu{mXa=Po&CBi zVdgGXi?9>l2|ao2+3t0V&-C{0zU82f9gW{SvHY{te~7xwSQiJ z>`KSTmEG-Qd_5;m?l7I*G5BV+e4Bdgx#sffOJ|ek6>rQO{Rw94QCCkIlTvka;I_Q4 zrSx90o9*>7#W_HApr^=A!r-bGP8gQtGEw|X$H@$J`Wrs&1KYHl+46XYGvm;LXuTn~l+Q%f8mm-A#)}^=A}a`ZwG+%I71SUx@d`_7+|FzG&0hsNW@CtDh`ic>Ygon-uCEt*h_3*in@i zVzU`&jlsPB)TRoq?Z#lkxQPC7dGr6!S@Yu5rhzWJvkxlU&m<4uv$?VT_?NX)y;t+{ z-+%SO?2oQ2-Nz`aDkt~P`Kt-5>!7|vZZ%9vrtMdsnDzJ7r|?x<65ih_?OI5D{^{1} z1B(Y|%uV`c-hxAH@AftYezL<+)40@IV}8lEx!$(I_e@21)ZFVxUf0m{_LxHCz)(kN z{CAgU=ALa^RR62~+h;CfHHTLHK5q6q4TT0FdeKjPB_}j{y;bPfy*Hru{&OP=N*;S` z9AEaK&~jl!M(tC3hOZpHV%zpzS)bpoKT%)V-m#QFe&OtG?w<}>Z0~}$1o02gn38GO JmHdn4{{z4<$Z-Gw literal 0 HcmV?d00001 diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/farmingtracker/FarmingWorldTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/FarmingWorldTest.java similarity index 96% rename from runelite-client/src/test/java/net/runelite/client/plugins/farmingtracker/FarmingWorldTest.java rename to runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/FarmingWorldTest.java index 918a24a7f5..05a45ffe52 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/farmingtracker/FarmingWorldTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/FarmingWorldTest.java @@ -21,7 +21,7 @@ * 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; + */package net.runelite.client.plugins.timetracking.farming; import org.junit.Test; diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/farmingtracker/PatchImplementationTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/PatchImplementationTest.java similarity index 98% rename from runelite-client/src/test/java/net/runelite/client/plugins/farmingtracker/PatchImplementationTest.java rename to runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/PatchImplementationTest.java index 8023958af8..ef6f73b94f 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/farmingtracker/PatchImplementationTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/timetracking/farming/PatchImplementationTest.java @@ -22,7 +22,7 @@ * (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; +package net.runelite.client.plugins.timetracking.farming; import java.util.HashMap; import java.util.Map; From 15e0fd4306823eb56eff63191b9017395bab91e1 Mon Sep 17 00:00:00 2001 From: takuyakanbr Date: Sat, 28 Jul 2018 23:15:02 +0800 Subject: [PATCH 2/4] time tracking: add bird house tracker --- .../main/java/net/runelite/api/VarPlayer.java | 10 +- .../net/runelite/api/widgets/WidgetID.java | 5 + .../net/runelite/api/widgets/WidgetInfo.java | 1 + .../client/plugins/timetracking/Tab.java | 1 + .../timetracking/TimeTrackingConfig.java | 11 + .../timetracking/TimeTrackingPanel.java | 6 +- .../timetracking/TimeTrackingPlugin.java | 47 +++- ...mingPatchPanel.java => TimeablePanel.java} | 45 ++-- .../timetracking/farming/FarmingTabPanel.java | 10 +- .../timetracking/hunter/BirdHouse.java | 64 +++++ .../timetracking/hunter/BirdHouseData.java | 38 +++ .../timetracking/hunter/BirdHouseSpace.java | 42 ++++ .../timetracking/hunter/BirdHouseState.java | 65 +++++ .../hunter/BirdHouseTabPanel.java | 180 ++++++++++++++ .../timetracking/hunter/BirdHouseTracker.java | 227 ++++++++++++++++++ 15 files changed, 719 insertions(+), 33 deletions(-) rename runelite-client/src/main/java/net/runelite/client/plugins/timetracking/{farming/FarmingPatchPanel.java => TimeablePanel.java} (56%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouse.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseData.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseSpace.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseState.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java diff --git a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java index 512ed1ebd9..6547b26353 100644 --- a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java +++ b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java @@ -95,7 +95,15 @@ public enum VarPlayer SLAYER_GOAL_END(1272), FARMING_GOAL_END(1273), 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; } diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java index 3446f76dc6..84c13b5017 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetID.java @@ -241,6 +241,11 @@ public class WidgetID static final int SPEC_ORB = 28; } + static class LoginClickToPlayScreen + { + static final int MESSAGE_OF_THE_DAY = 3; + } + static class Viewport { static final int MINIMAP_RESIZABLE_WIDGET = 17; diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java index df62929276..9beee6509b 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/WidgetInfo.java @@ -139,6 +139,7 @@ public enum WidgetInfo 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_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_ROOT_INTERFACE_CONTAINER(WidgetID.FIXED_VIEWPORT_GROUP_ID, WidgetID.FixedViewport.ROOT_INTERFACE_CONTAINER), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java index 5eafc8129f..bdf38d0d68 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java @@ -32,6 +32,7 @@ import net.runelite.api.ItemID; @Getter public enum Tab { + BIRD_HOUSE("Bird Houses", ItemID.OAK_BIRD_HOUSE), ALLOTMENT("Allotment Patches", ItemID.CABBAGE), FLOWER("Flower Patches", ItemID.RED_FLOWERS), HERB("Herb Patches", ItemID.GRIMY_RANARR_WEED), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java index 9d013445f0..e36f4a2fd8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java @@ -33,6 +33,7 @@ public interface TimeTrackingConfig extends Config { String KEY_NAME = "timetracking"; String AUTOWEED = "autoweed"; + String BIRD_HOUSE = "birdhouse"; @ConfigItem( keyName = "estimateRelative", @@ -44,6 +45,16 @@ public interface TimeTrackingConfig extends Config return false; } + @ConfigItem( + keyName = "birdHouseNotification", + name = "Bird house notification", + description = "Notify you when all bird houses are full" + ) + default boolean birdHouseNotification() + { + return false; + } + @ConfigItem( keyName = "activeTab", name = "Active Tab", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java index 72842e08a1..4eb4ec175c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java @@ -39,6 +39,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.client.game.AsyncBufferedImage; import net.runelite.client.game.ItemManager; 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; @@ -59,7 +60,8 @@ class TimeTrackingPanel extends PluginPanel @Nullable private TabContentPanel activeTabPanel = null; - TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, FarmingTracker farmingTracker) + TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, + FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker) { super(false); @@ -77,6 +79,8 @@ class TimeTrackingPanel extends PluginPanel add(tabGroup, BorderLayout.NORTH); add(display, BorderLayout.CENTER); + addTab(Tab.BIRD_HOUSE, birdHouseTracker.createBirdHouseTabPanel()); + for (Tab tab : Tab.FARMING_TABS) { addTab(tab, farmingTracker.createTabPanel(tab)); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java index 200f133975..36a35afe26 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java @@ -37,11 +37,14 @@ import net.runelite.api.GameState; import net.runelite.api.coords.WorldPoint; 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 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; @@ -49,8 +52,8 @@ import net.runelite.client.util.ImageUtil; @PluginDescriptor( name = "Time Tracking", - description = "Enable the Time Tracking panel, which contains farming trackers", - tags = {"farming", "skilling", "panel"} + description = "Enable the Time Tracking panel, which contains farming and bird house trackers", + tags = {"birdhouse", "farming", "hunter", "notifications", "skilling", "panel"} ) @Slf4j public class TimeTrackingPlugin extends Plugin @@ -64,6 +67,9 @@ public class TimeTrackingPlugin extends Plugin @Inject private FarmingTracker farmingTracker; + @Inject + private BirdHouseTracker birdHouseTracker; + @Inject private ItemManager itemManager; @@ -75,6 +81,7 @@ public class TimeTrackingPlugin extends Plugin private NavigationButton navButton; private WorldPoint lastTickLocation; + private boolean lastTickPostLogin; @Provides TimeTrackingConfig provideConfig(ConfigManager configManager) @@ -85,9 +92,11 @@ public class TimeTrackingPlugin extends Plugin @Override protected void startUp() throws Exception { + birdHouseTracker.loadFromConfig(); + final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png"); - panel = new TimeTrackingPanel(itemManager, config, farmingTracker); + panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker); navButton = NavigationButton.builder() .tooltip("Time Tracking") @@ -105,6 +114,7 @@ public class TimeTrackingPlugin extends Plugin protected void shutDown() throws Exception { lastTickLocation = null; + lastTickPostLogin = false; clientToolbar.removeNavigation(navButton); } @@ -117,17 +127,32 @@ public class TimeTrackingPlugin extends Plugin 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.getRegionID() != lastTickLocation.getRegionID()) + if (loc == null || loc.getPlane() != 0 || loc.getRegionID() != lastTickLocation.getRegionID()) { return; } + boolean birdHouseDataChanged = birdHouseTracker.updateData(loc); boolean farmingDataChanged = farmingTracker.updateData(loc); - if (farmingDataChanged) + if (birdHouseDataChanged || farmingDataChanged) { updatePanel(); } @@ -137,9 +162,21 @@ public class TimeTrackingPlugin extends Plugin public void onUsernameChanged(UsernameChanged e) { farmingTracker.migrateConfiguration(); + birdHouseTracker.loadFromConfig(); updatePanel(); } + @Schedule(period = 10, unit = ChronoUnit.SECONDS) + public void checkCompletion() + { + boolean birdHouseDataChanged = birdHouseTracker.checkCompletion(); + + if (birdHouseDataChanged) + { + updatePanel(); + } + } + @Schedule(period = 10, unit = ChronoUnit.SECONDS) public void updatePanel() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeablePanel.java similarity index 56% rename from runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatchPanel.java rename to runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeablePanel.java index 42bbcad621..8bbb5ae4ab 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingPatchPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeablePanel.java @@ -7,25 +7,24 @@ * 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. + * 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. + * 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; +package net.runelite.client.plugins.timetracking; -import com.google.common.base.Strings; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -40,16 +39,16 @@ import net.runelite.client.ui.components.ThinProgressBar; import net.runelite.client.ui.components.shadowlabel.JShadowedLabel; @Getter -class FarmingPatchPanel extends JPanel +public class TimeablePanel extends JPanel { - private final FarmingPatch patch; + private final T timeable; private final JLabel icon = new JLabel(); private final JLabel estimate = new JLabel(); 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()); setBorder(new EmptyBorder(7, 0, 0, 0)); @@ -66,8 +65,7 @@ class FarmingPatchPanel extends JPanel infoPanel.setLayout(new GridLayout(2, 1)); infoPanel.setBorder(new EmptyBorder(4, 4, 4, 0)); - final JLabel location = new JShadowedLabel(patch.getRegion().getName() - + (Strings.isNullOrEmpty(patch.getName()) ? "" : " (" + patch.getName() + ")")); + final JLabel location = new JShadowedLabel(title); location.setFont(FontManager.getRunescapeSmallFont()); location.setForeground(Color.WHITE); @@ -80,6 +78,9 @@ class FarmingPatchPanel extends JPanel topContainer.add(icon, BorderLayout.WEST); topContainer.add(infoPanel, BorderLayout.CENTER); + progress.setValue(0); + progress.setMaximumValue(maximumProgressValue); + add(topContainer, BorderLayout.NORTH); add(progress, BorderLayout.SOUTH); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java index 986716ec5c..652dc3606f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java @@ -45,6 +45,7 @@ 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; @@ -55,7 +56,7 @@ public class FarmingTabPanel extends TabContentPanel private final ItemManager itemManager; private final ConfigManager configManager; private final TimeTrackingConfig config; - private final List patchPanels; + private final List> patchPanels; FarmingTabPanel(Client client, ItemManager itemManager, ConfigManager configManager, TimeTrackingConfig config, Set patches) @@ -80,7 +81,8 @@ public class FarmingTabPanel extends TabContentPanel boolean first = true; for (FarmingPatch patch : patches) { - FarmingPatchPanel p = new FarmingPatchPanel(patch); + String title = patch.getRegion().getName() + (Strings.isNullOrEmpty(patch.getName()) ? "" : " (" + patch.getName() + ")"); + TimeablePanel p = new TimeablePanel<>(patch, title, 1); /* Show labels to subdivide tabs into sections */ if (patch.getImplementation() != lastImpl && !Strings.isNullOrEmpty(patch.getImplementation().getName())) @@ -131,9 +133,9 @@ public class FarmingTabPanel extends TabContentPanel .equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED)); } - for (FarmingPatchPanel panel : patchPanels) + for (TimeablePanel panel : patchPanels) { - FarmingPatch patch = panel.getPatch(); + FarmingPatch patch = panel.getTimeable(); String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + patch.getRegion().getRegionID(); String key = Integer.toString(patch.getVarbit().getId()); String storedValue = configManager.getConfiguration(group, key); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouse.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouse.java new file mode 100644 index 0000000000..009a2bd7e6 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouse.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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]; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseData.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseData.java new file mode 100644 index 0000000000..f5af4893ef --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseData.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseSpace.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseSpace.java new file mode 100644 index 0000000000..635ce2a4e3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseSpace.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseState.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseState.java new file mode 100644 index 0000000000..8e99d92541 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseState.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java new file mode 100644 index 0000000000..d6a46e70ef --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2018, Psikoi + * Copyright (c) 2018, Daniel Teo + * 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.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.format.TextStyle; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +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> 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 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 void update() + { + long unixNow = Instant.now().getEpochSecond(); + + for (TimeablePanel 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 doneEstimate = startTime + BirdHouseTracker.BIRD_HOUSE_DURATION; + if (doneEstimate < unixNow) + { + panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION); + panel.getProgress().setForeground(COMPLETED_COLOR); + panel.getEstimate().setText("Done"); + } + else if (config.estimateRelative()) + { + int remainingSeconds = (int) (59 + doneEstimate - unixNow); + int remaining = remainingSeconds / 60; + panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION - remainingSeconds); + StringBuilder f = new StringBuilder("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()); + } + break; + default: + panel.getIcon().setToolTipText("Unknown state"); + panel.getEstimate().setText("Unknown"); + break; + } + + panel.getProgress().update(); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java new file mode 100644 index 0000000000..fcc10d54fe --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2018 Abex + * Copyright (c) 2018, Daniel Teo + * 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 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 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. + */ + private long completionTime = -1; + + public BirdHouseTabPanel createBirdHouseTabPanel() + { + return new BirdHouseTabPanel(itemManager, this, config); + } + + public void loadFromConfig() + { + birdHouseData.clear(); + + final String group = TimeTrackingConfig.KEY_NAME + "." + 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 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 updatedData) + { + final String group = TimeTrackingConfig.KEY_NAME + "." + 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()); + } + } +} From 8435004956b06f0a0b814e063c974dc691cd72b7 Mon Sep 17 00:00:00 2001 From: takuyakanbr Date: Sat, 28 Jul 2018 23:18:09 +0800 Subject: [PATCH 3/4] time tracking: add clock panel --- .../client/plugins/timetracking/Tab.java | 1 + .../plugins/timetracking/TabContentPanel.java | 10 +- .../timetracking/TimeTrackingConfig.java | 23 +- .../timetracking/TimeTrackingPanel.java | 18 +- .../timetracking/TimeTrackingPlugin.java | 76 ++++- .../plugins/timetracking/clocks/Clock.java | 63 ++++ .../timetracking/clocks/ClockManager.java | 187 ++++++++++++ .../timetracking/clocks/ClockPanel.java | 269 ++++++++++++++++++ .../timetracking/clocks/ClockTabPanel.java | 191 +++++++++++++ .../timetracking/clocks/Stopwatch.java | 107 +++++++ .../timetracking/clocks/StopwatchPanel.java | 137 +++++++++ .../plugins/timetracking/clocks/Timer.java | 99 +++++++ .../timetracking/clocks/TimerPanel.java | 42 +++ .../timetracking/farming/FarmingTabPanel.java | 10 +- .../timetracking/farming/FarmingTracker.java | 8 +- .../hunter/BirdHouseTabPanel.java | 6 + .../timetracking/hunter/BirdHouseTracker.java | 4 +- .../client/plugins/timetracking/add_icon.png | Bin 0 -> 500 bytes .../plugins/timetracking/delete_icon.png | Bin 0 -> 647 bytes .../client/plugins/timetracking/lap_icon.png | Bin 0 -> 905 bytes .../plugins/timetracking/pause_icon.png | Bin 0 -> 563 bytes .../plugins/timetracking/reset_icon.png | Bin 0 -> 801 bytes .../plugins/timetracking/start_icon.png | Bin 0 -> 654 bytes 23 files changed, 1224 insertions(+), 27 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Clock.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockTabPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Stopwatch.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/StopwatchPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Timer.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/TimerPanel.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/add_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/delete_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/lap_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/pause_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/reset_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/start_icon.png diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java index bdf38d0d68..134083611e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java @@ -32,6 +32,7 @@ import net.runelite.api.ItemID; @Getter public enum Tab { + CLOCK("Timers & Stopwatches", ItemID.WATCH), BIRD_HOUSE("Bird Houses", ItemID.OAK_BIRD_HOUSE), ALLOTMENT("Allotment Patches", ItemID.CABBAGE), FLOWER("Flower Patches", ItemID.RED_FLOWERS), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java index 199d6c920b..cb58e0c4e7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java @@ -26,15 +26,21 @@ package net.runelite.client.plugins.timetracking; import java.awt.Dimension; import javax.swing.JPanel; -import net.runelite.client.ui.PluginPanel; 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 new Dimension(PluginPanel.PANEL_WIDTH, super.getPreferredSize().height); + return super.getPreferredSize(); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java index e36f4a2fd8..744250a8db 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingConfig.java @@ -31,24 +31,39 @@ import net.runelite.client.config.ConfigItem; @ConfigGroup("timetracking") public interface TimeTrackingConfig extends Config { - String KEY_NAME = "timetracking"; + String CONFIG_GROUP = "timetracking"; String AUTOWEED = "autoweed"; String BIRD_HOUSE = "birdhouse"; + String TIMERS = "timers"; + String STOPWATCHES = "stopwatches"; @ConfigItem( keyName = "estimateRelative", name = "Show relative time", - description = "Show amount of time remaining instead of completion time" + description = "Show amount of time remaining instead of completion time", + position = 1 ) default boolean estimateRelative() { return false; } + @ConfigItem( + keyName = "timerNotification", + name = "Timer notification", + description = "Notify you whenever a timer has finished counting down", + position = 2 + ) + default boolean timerNotification() + { + return false; + } + @ConfigItem( keyName = "birdHouseNotification", name = "Bird house notification", - description = "Notify you when all bird houses are full" + description = "Notify you when all bird houses are full", + position = 3 ) default boolean birdHouseNotification() { @@ -63,7 +78,7 @@ public interface TimeTrackingConfig extends Config ) default Tab activeTab() { - return Tab.ALLOTMENT; + return Tab.CLOCK; } @ConfigItem( diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java index 4eb4ec175c..aab4b02c19 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java @@ -34,10 +34,12 @@ 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; @@ -61,7 +63,7 @@ class TimeTrackingPanel extends PluginPanel private TabContentPanel activeTabPanel = null; TimeTrackingPanel(ItemManager itemManager, TimeTrackingConfig config, - FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker) + FarmingTracker farmingTracker, BirdHouseTracker birdHouseTracker, ClockManager clockManager) { super(false); @@ -79,6 +81,7 @@ class TimeTrackingPanel extends PluginPanel add(tabGroup, BorderLayout.NORTH); add(display, BorderLayout.CENTER); + addTab(Tab.CLOCK, clockManager.getClockTabPanel()); addTab(Tab.BIRD_HOUSE, birdHouseTracker.createBirdHouseTabPanel()); for (Tab tab : Tab.FARMING_TABS) @@ -130,6 +133,17 @@ class TimeTrackingPanel extends PluginPanel } } + /** + * 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) @@ -137,7 +151,7 @@ class TimeTrackingPanel extends PluginPanel return; } - activeTabPanel.update(); + SwingUtilities.invokeLater(activeTabPanel::update); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java index 36a35afe26..1eba76a304 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java @@ -28,13 +28,17 @@ 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 javax.swing.SwingUtilities; 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; @@ -43,6 +47,10 @@ 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; @@ -52,8 +60,8 @@ import net.runelite.client.util.ImageUtil; @PluginDescriptor( name = "Time Tracking", - description = "Enable the Time Tracking panel, which contains farming and bird house trackers", - tags = {"birdhouse", "farming", "hunter", "notifications", "skilling", "panel"} + 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 @@ -70,12 +78,20 @@ public class TimeTrackingPlugin extends Plugin @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; @@ -92,11 +108,13 @@ public class TimeTrackingPlugin extends Plugin @Override protected void startUp() throws Exception { + clockManager.loadTimers(); + clockManager.loadStopwatches(); birdHouseTracker.loadFromConfig(); final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png"); - panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker); + panel = new TimeTrackingPanel(itemManager, config, farmingTracker, birdHouseTracker, clockManager); navButton = NavigationButton.builder() .tooltip("Time Tracking") @@ -107,7 +125,7 @@ public class TimeTrackingPlugin extends Plugin clientToolbar.addNavigation(navButton); - updatePanel(); + panelUpdateFuture = executorService.scheduleAtFixedRate(this::updatePanel, 200, 200, TimeUnit.MILLISECONDS); } @Override @@ -115,9 +133,34 @@ public class TimeTrackingPlugin extends Plugin { 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) { @@ -154,7 +197,7 @@ public class TimeTrackingPlugin extends Plugin if (birdHouseDataChanged || farmingDataChanged) { - updatePanel(); + panel.update(); } } @@ -163,7 +206,7 @@ public class TimeTrackingPlugin extends Plugin { farmingTracker.migrateConfiguration(); birdHouseTracker.loadFromConfig(); - updatePanel(); + panel.update(); } @Schedule(period = 10, unit = ChronoUnit.SECONDS) @@ -173,13 +216,24 @@ public class TimeTrackingPlugin extends Plugin if (birdHouseDataChanged) { - updatePanel(); + panel.update(); } } - @Schedule(period = 10, unit = ChronoUnit.SECONDS) - public void updatePanel() + private void updatePanel() { - SwingUtilities.invokeLater(panel::update); + long unitTime = Instant.now().toEpochMilli() / 200; + + boolean clockDataChanged = false; + + if (unitTime % 5 == 0) + { + clockDataChanged = clockManager.checkCompletion(); + } + + if (unitTime % panel.getUpdateInterval() == 0 || clockDataChanged) + { + panel.update(); + } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Clock.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Clock.java new file mode 100644 index 0000000000..31300069c8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Clock.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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(); +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java new file mode 100644 index 0000000000..5fc68fd375 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 timers = new CopyOnWriteArrayList<>(); + + @Getter + private final List 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); + } + + /** + * 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 timers = gson.fromJson(timersJson, new TypeToken>() + { + }.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 stopwatches = gson.fromJson(stopwatchesJson, new TypeToken>() + { + }.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); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockPanel.java new file mode 100644 index 0000000000..3b46a6c22b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockPanel.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockTabPanel.java new file mode 100644 index 0000000000..9837155eef --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockTabPanel.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 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(); + } + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Stopwatch.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Stopwatch.java new file mode 100644 index 0000000000..5e12f58901 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Stopwatch.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 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(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/StopwatchPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/StopwatchPanel.java new file mode 100644 index 0000000000..99bb8def81 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/StopwatchPanel.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 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; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Timer.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Timer.java new file mode 100644 index 0000000000..d4e0de6132 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/Timer.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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(); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/TimerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/TimerPanel.java new file mode 100644 index 0000000000..9f2bf047f8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/TimerPanel.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java index 652dc3606f..429ef56ef5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java @@ -120,6 +120,12 @@ public class FarmingTabPanel extends TabContentPanel } + @Override + public int getUpdateInterval() + { + return 50; // 10 seconds + } + @Override public void update() { @@ -128,7 +134,7 @@ public class FarmingTabPanel extends TabContentPanel boolean autoweed; { - String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername(); + String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername(); autoweed = Integer.toString(Autoweed.ON.ordinal()) .equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED)); } @@ -136,7 +142,7 @@ public class FarmingTabPanel extends TabContentPanel for (TimeablePanel panel : patchPanels) { FarmingPatch patch = panel.getTimeable(); - String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + patch.getRegion().getRegionID(); + 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; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java index ba2ce8b0d3..7d4d89b2a8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java @@ -72,7 +72,7 @@ public class FarmingTracker boolean changed = false; { - String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername(); + String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername(); String autoweed = Integer.toString(client.getVar(Varbits.AUTOWEED)); if (!autoweed.equals(configManager.getConfiguration(group, TimeTrackingConfig.AUTOWEED))) { @@ -86,7 +86,7 @@ public class FarmingTracker { // Write config with new varbits // timetracking...=: - String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + region.getRegionID(); + String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + region.getRegionID(); long unixNow = Instant.now().getEpochSecond(); for (Varbits varbit : region.getVarbits()) { @@ -137,7 +137,7 @@ public class FarmingTracker // migrate autoweed config { String oldGroup = OLD_KEY_NAME + "." + username; - String newGroup = TimeTrackingConfig.KEY_NAME + "." + username; + String newGroup = TimeTrackingConfig.CONFIG_GROUP + "." + username; String storedValue = configManager.getConfiguration(oldGroup, TimeTrackingConfig.AUTOWEED); if (storedValue != null) @@ -151,7 +151,7 @@ public class FarmingTracker for (FarmingRegion region : farmingWorld.getRegions().values()) { String oldGroup = OLD_KEY_NAME + "." + username + "." + region.getRegionID(); - String newGroup = TimeTrackingConfig.KEY_NAME + "." + username + "." + region.getRegionID(); + String newGroup = TimeTrackingConfig.CONFIG_GROUP + "." + username + "." + region.getRegionID(); for (Varbits varbit : region.getVarbits()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java index d6a46e70ef..3277938d5e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java @@ -78,6 +78,12 @@ public class BirdHouseTabPanel extends TabContentPanel } } + @Override + public int getUpdateInterval() + { + return 50; // 10 seconds + } + @Override public void update() { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java index fcc10d54fe..6604813681 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java @@ -89,7 +89,7 @@ public class BirdHouseTracker { birdHouseData.clear(); - final String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE; + final String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE; for (BirdHouseSpace space : BirdHouseSpace.values()) { @@ -216,7 +216,7 @@ public class BirdHouseTracker private void saveToConfig(Map updatedData) { - final String group = TimeTrackingConfig.KEY_NAME + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE; + final String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + TimeTrackingConfig.BIRD_HOUSE; for (BirdHouseData data : updatedData.values()) { diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/add_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/add_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..022dbff0ef979e91d4070a54efcb752362ed7ae7 GIT binary patch literal 500 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT2Y6FnVW(M3hAM`dB6B=jtV<?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKG$7NGt1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~as*kZ5aAo3 z;GAESs$i;TrkiYNVx(Yhsb^?rU}!BPbD z|2&z5_rS1A@pN$v(U_Q=pdf5uWB>$bwj59^7xr46d}7J`goK01D+KJMKW6^v^;Z_Q z^DN5!vghsS^678ASP8w|>J(wRc5);!6o eai15>!N4$6gUjd9lI_btk>%;?=d#Wzp$P!Fz?-oE literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/delete_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/delete_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ee9ea58a1857d0110e3b6f5971b320d5fe42799e GIT binary patch literal 647 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT2Y6FnVW(M3hAM`dB6B=jtV<?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKG$7NGt1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~as*kZ5aAo3 z;GAESs$i;TrkiYNVx(Yhsb^?rU}!BPbD z|2&z5_rS3G?&;zfqA_vqgoA#E9R!^BzII&O@?%b&h^MRcy0=@y7j%4E{>hS~v*V-Q zbl)jqoE~#G+isIDc+AR>ar@iRyW*2x2^?Bt@SJCoy7B9<)mMvI7(BfviJnY8S9z{P zCDJpBse!R!cX-;hnK#84g1ugEocN@D=GAxq)K*IND6)8D6`d=0{b0;-CeHKPyE-pT z)4w9p_0zVnDNKrHh-VN;zO(E1?j>g{{wkT}JR^F(iF^LEvX#YshIW1zwVySXaU?P>*c9~f_TBTr%YwEq g&rQ2_m#KJ1{FmTWCVlreIDpcRr>mdKI;Vst0EZ;(yZ`_I literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/lap_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/lap_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6b7acbb25c7d7473a48de6b5c1853b61ed944b77 GIT binary patch literal 905 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT2Y6FnVW(M3hAM`dB6B=jtV<?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKG$7NGt1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~as*kZ5aAo3 z;GAESs$i;TrkiYNVx(Yhsb^?rU}!BPbD z|2&z5_rPHN>FMGaqA_vqq`lozfg(rGSAV`&WAMl%y5mL!2bZFbrWuRkF};;Rxq>%B zxOZ=uCeLa)yGSQfge7;I#6bbJnsd_6f97$8E6QJ;_v_!ghxdvb zth*Q)1Q{8QJvMw~VK+Hw9*5$pImK(jR%gz8{@!cprOLHow;g&K^UXDuYI#ojV(~oo z{`d7;uB}hsd~>z}$2yDWcU3&AyjGr)?^}G4qxUA?v0apSum!j3a_w^rY|3`UoDm85llT5B-JW#clO)&QNzIw%$7jGPtR(WaeXLV$_ zUaGjXWlE4x;Ovfh(b@d10WI#`RngLcLrZY7jLhhHzYonZjG`{?eQ&NV7P4c*1DY$I^QId zGt6ddOh2ulWN?guVczq%%QLG|dS3oB=R00_>u6HpgjC5jTPFl5`ChJnbi>qLGgOkV zT|9Ey)+U7te;4}>jya!yXU>wVp7(v}#SEF&#EQcb%{Szv+aCIgxcUP_{`BWREC(Lu piUeops&_5=Y{2vW!mIsXq!%>(SMu7|HwTo1JYD@<);T3K0RW)uX7~UA literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/pause_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/pause_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..45d8b770ac5c116bec89ee122c25cc1211af2a3d GIT binary patch literal 563 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT2Y6FnVW(M3hAM`dB6B=jtV<?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKG$7NGt1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~as*kZ5aAo3 z;GAESs$i;TrkiYNVx(Yhsb^?rU}!BPbD z|2&z5_rS1Q?CIhdqA~I9BwMaS4k9h-a-AEp7e*}kEM!)#+W$b%rYYR(vFe*3mfW&~ z4#(1zH)X0MJU2P~`={J`iG+neBDdsLPb)Fme6PA*p>q0`UcmT>a9y`PjWdJ>qar!ZC0P8#5sv+Lf228KT-7`#1kYNLd(=My+j#zB-t@r td-5BTWvt4Fq-pls?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKG$7NGt1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~as*kZ5aAo3 z;GAESs$i;TrkiYNVx(Yhsb^?rU}!BPbD z|2&z5_rPGC=;`7ZqA_vlMBDDkfdZ}bjo+sl-aMq#+tKvEUO;uh0<)&6tyAZ}=xWu7 zxbZ{SMcbZBx-js_UAGMv!jcjdBbG)6vE*#o5OLv$P~6$Q$JIJ->Xyz*WScj$IPHPF zGeh&ihK>XiDc8#;+Jc3=792h~`=@>Jx@6UmyX&qVqk&xi%GXul=YGyzqV8~`F6DRY z66XW6-fnkbkZoYOVB&kqa=q&jL(l0A)3>QJOe*u*mGgVwTmA$24>FiKI*bn#dWPrN z-%opP{o%m3NlU6Xr!h{~@GnxPZ literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/start_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/start_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cf722905e9c3425a2001102ba3115b4db05e40c1 GIT binary patch literal 654 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT2Y6FnVW(M3hAM`dB6B=jtV<?;Zqle1Gx6p~WYGxKbf-tXS8q>!0ns}yePYv5bpoSKp8QB{;0T;&&% zT$P<{nWAKG$7NGt1vDTxwIorYA~z?m*s8)-32d$vkPQ;nS5g2gDap1~as*kZ5aAo3 z;GAESs$i;TrkiYNVx(Yhsb^?rU}!BPbD z|2&z5_rS3G=jq}YqA_vlc|*U$4g#$Y>#u2BPtx|D__)uRfz?%`s9z(du?q;ISqdjF zSzsU%va>08r(w{Zit-gg6BG&?!h20VXQn;qpO_ms?P&u93k&CumhO!1s_=V;*{5hsc7xpy1|IahV9n#aVQz}HjtaJ#%qn$jn( zGj*(tA*CX&7x(1ec8$HB9w;Kq&bVf)iLddxRjamKvtxQ0SP);8c)fH<<}RJCM|)oH z()@6Z+co9w-u+TMY||sB*;c=|kH66#@|AH1<2C!QZ>KvdD>3M=s^Zn*kKJs2RU&wM kZuRRqyA6)XxcooG?h+Ok_WSXaf1qUK>FVdQ&MBb@0QZdNR{#J2 literal 0 HcmV?d00001 From a8c418d180c020a4123b164cca46a6588a9df794 Mon Sep 17 00:00:00 2001 From: takuyakanbr Date: Sat, 28 Jul 2018 23:19:19 +0800 Subject: [PATCH 4/4] time tracking: add overview tab --- .../timetracking/OverviewItemPanel.java | 128 ++++++++++++++++ .../timetracking/OverviewTabPanel.java | 138 ++++++++++++++++++ .../client/plugins/timetracking/Tab.java | 1 + .../plugins/timetracking/TabContentPanel.java | 41 ++++++ .../timetracking/TimeTrackingPanel.java | 11 ++ .../timetracking/TimeTrackingPlugin.java | 2 + .../timetracking/clocks/ClockManager.java | 10 ++ .../timetracking/farming/FarmingTabPanel.java | 37 +---- .../timetracking/farming/FarmingTracker.java | 106 +++++++++++++- .../timetracking/farming/FarmingWorld.java | 10 ++ .../hunter/BirdHouseTabPanel.java | 43 +----- .../timetracking/hunter/BirdHouseTracker.java | 1 + .../plugins/timetracking/arrow_right.png | Bin 0 -> 122 bytes 13 files changed, 452 insertions(+), 76 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewItemPanel.java create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewTabPanel.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/arrow_right.png diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewItemPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewItemPanel.java new file mode 100644 index 0000000000..96cf62c6fc --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewItemPanel.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewTabPanel.java new file mode 100644 index 0000000000..38bd105709 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/OverviewTabPanel.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018, Daniel Teo + * 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 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); + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java index 134083611e..4dfa94c8b9 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/Tab.java @@ -32,6 +32,7 @@ import net.runelite.api.ItemID; @Getter public enum Tab { + OVERVIEW("Overview", ItemID.OLD_NOTES), CLOCK("Timers & Stopwatches", ItemID.WATCH), BIRD_HOUSE("Bird Houses", ItemID.OAK_BIRD_HOUSE), ALLOTMENT("Allotment Patches", ItemID.CABBAGE), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java index cb58e0c4e7..47740a8b17 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TabContentPanel.java @@ -25,6 +25,10 @@ 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 @@ -43,4 +47,41 @@ public abstract class TabContentPanel extends JPanel { 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(); + } + } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java index aab4b02c19..81a19cb483 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPanel.java @@ -30,6 +30,8 @@ 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; @@ -55,6 +57,7 @@ class TimeTrackingPanel extends PluginPanel /* This is the panel the tabs' respective panels will be displayed on. */ private final JPanel display = new JPanel(); + private final Map uiTabs = new HashMap<>(); private final MaterialTabGroup tabGroup = new MaterialTabGroup(display); private boolean active; @@ -81,6 +84,7 @@ class TimeTrackingPanel extends PluginPanel 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()); @@ -126,13 +130,20 @@ class TimeTrackingPanel extends PluginPanel 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. */ diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java index 1eba76a304..d8b4da011f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java @@ -111,6 +111,7 @@ public class TimeTrackingPlugin extends Plugin clockManager.loadTimers(); clockManager.loadStopwatches(); birdHouseTracker.loadFromConfig(); + farmingTracker.loadCompletionTimes(); final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "watch.png"); @@ -205,6 +206,7 @@ public class TimeTrackingPlugin extends Plugin public void onUsernameChanged(UsernameChanged e) { farmingTracker.migrateConfiguration(); + farmingTracker.loadCompletionTimes(); birdHouseTracker.loadFromConfig(); panel.update(); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java index 5fc68fd375..374bc43ae6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/clocks/ClockManager.java @@ -93,6 +93,16 @@ public class ClockManager 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. */ diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java index 429ef56ef5..11c1e2dd0b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTabPanel.java @@ -29,12 +29,8 @@ import com.google.common.base.Strings; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; 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 java.util.Set; import javax.swing.JLabel; import javax.swing.border.EmptyBorder; @@ -215,40 +211,9 @@ public class FarmingTabPanel extends TabContentPanel { 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()); + panel.getEstimate().setText("Done " + getFormattedEstimate(doneEstimate - unixNow, config.estimateRelative())); } } else diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java index 7d4d89b2a8..d88357c180 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingTracker.java @@ -27,6 +27,8 @@ 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; @@ -59,6 +61,15 @@ public class FarmingTracker } + /** + * 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 completionTimes = new EnumMap<>(PatchImplementation.class); + public FarmingTabPanel createTabPanel(Tab tab) { return new FarmingTabPanel(client, itemManager, configManager, config, farmingWorld.getTabs().get(tab)); @@ -88,9 +99,10 @@ public class FarmingTracker // timetracking...=: String group = TimeTrackingConfig.CONFIG_GROUP + "." + client.getUsername() + "." + region.getRegionID(); long unixNow = Instant.now().getEpochSecond(); - for (Varbits varbit : region.getVarbits()) + 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); @@ -118,6 +130,7 @@ public class FarmingTracker String value = strVarbit + ":" + unixNow; configManager.setConfiguration(group, key, value); + updateCompletionTime(patch.getImplementation()); changed = true; } } @@ -125,6 +138,97 @@ public class FarmingTracker 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. diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java index 7af327ec69..06f5aa8379 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/farming/FarmingWorld.java @@ -26,9 +26,12 @@ package net.runelite.client.plugins.timetracking.farming; import com.google.inject.Singleton; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.EnumMap; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -47,6 +50,9 @@ class FarmingWorld @Getter private Map> tabs = new HashMap<>(); + @Getter + private Map> patchTypes = new EnumMap<>(PatchImplementation.class); + private final Comparator tabSorter = Comparator .comparing(FarmingPatch::getImplementation) .thenComparing((FarmingPatch p) -> p.getRegion().getName()) @@ -243,6 +249,10 @@ class FarmingWorld tabs .computeIfAbsent(p.getImplementation().getTab(), k -> new TreeSet<>(tabSorter)) .add(p); + + patchTypes + .computeIfAbsent(p.getImplementation(), k -> new ArrayList<>()) + .add(p); } } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java index 3277938d5e..a43a9e6760 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTabPanel.java @@ -28,12 +28,8 @@ package net.runelite.client.plugins.timetracking.hunter; import java.awt.Color; 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 net.runelite.api.ItemID; import net.runelite.client.game.ItemManager; import net.runelite.client.plugins.timetracking.TabContentPanel; @@ -130,48 +126,17 @@ public class BirdHouseTabPanel extends TabContentPanel panel.getEstimate().setText("Built"); break; case SEEDED: - long doneEstimate = startTime + BirdHouseTracker.BIRD_HOUSE_DURATION; - if (doneEstimate < unixNow) + 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 if (config.estimateRelative()) - { - int remainingSeconds = (int) (59 + doneEstimate - unixNow); - int remaining = remainingSeconds / 60; - panel.getProgress().setValue(BirdHouseTracker.BIRD_HOUSE_DURATION - remainingSeconds); - StringBuilder f = new StringBuilder("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()); + panel.getProgress().setValue((int) (BirdHouseTracker.BIRD_HOUSE_DURATION - remainingTime)); + panel.getEstimate().setText("Done " + getFormattedEstimate(remainingTime, config.estimateRelative())); } break; default: diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java index 6604813681..bcc52c3349 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/hunter/BirdHouseTracker.java @@ -78,6 +78,7 @@ public class BirdHouseTracker * This is set to {@code 0} if the bird houses have already completed * when updating it. */ + @Getter private long completionTime = -1; public BirdHouseTabPanel createBirdHouseTabPanel() diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/arrow_right.png b/runelite-client/src/main/resources/net/runelite/client/plugins/timetracking/arrow_right.png new file mode 100644 index 0000000000000000000000000000000000000000..92048f0c7844249c635de465efc0b258c64ccd61 GIT binary patch literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|m96eneLp07OCrCVbV1N7nq5o@I zM1KDH|G%C$F}3oDrnZ7#q-wy;qCZ|0|K)%Fe_pR~b}EDD&VT>?95^FpsvH(zWC%QE VbfiC~pbBUhgQu&X%Q~loCIGE8ElvOc literal 0 HcmV?d00001