From 15e0fd4306823eb56eff63191b9017395bab91e1 Mon Sep 17 00:00:00 2001 From: takuyakanbr Date: Sat, 28 Jul 2018 23:15:02 +0800 Subject: [PATCH] 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()); + } + } +}