From 3fd8fb79aa171c924af5f3cb577ec9dce404e4da Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 18 Nov 2019 08:40:53 -0500 Subject: [PATCH] woodcutting plugin: add respawn timer Co-authored-by: David --- .../client/plugins/woodcutting/Tree.java | 40 +++++++++++++-- .../plugins/woodcutting/TreeRespawn.java | 46 +++++++++++++++++ .../woodcutting/WoodcuttingConfig.java | 11 +++++ .../woodcutting/WoodcuttingPlugin.java | 49 +++++++++++++++++-- .../woodcutting/WoodcuttingTreesOverlay.java | 48 ++++++++++++++++-- 5 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/TreeRespawn.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java index befc78df8c..e041108602 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/Tree.java @@ -25,20 +25,54 @@ package net.runelite.client.plugins.woodcutting; import com.google.common.collect.ImmutableMap; +import java.time.Duration; import java.util.Map; +import javax.annotation.Nullable; import lombok.Getter; -import static net.runelite.api.ObjectID.REDWOOD; +import net.runelite.api.ObjectID; +import static net.runelite.api.ObjectID.MAGIC_TREE_10834; +import static net.runelite.api.NullObjectID.NULL_10835; +import static net.runelite.api.ObjectID.MAHOGANY; +import static net.runelite.api.ObjectID.MAHOGANY_36688; +import static net.runelite.api.ObjectID.MAPLE_TREE_10832; +import static net.runelite.api.ObjectID.MAPLE_TREE_36681; +import static net.runelite.api.ObjectID.OAK_10820; +import static net.runelite.api.ObjectID.OAK_TREE_4540; import static net.runelite.api.ObjectID.REDWOOD_29670; +import static net.runelite.api.ObjectID.TEAK; +import static net.runelite.api.ObjectID.TEAK_36686; +import static net.runelite.api.ObjectID.TREE; +import static net.runelite.api.ObjectID.TREE_1277; +import static net.runelite.api.ObjectID.TREE_1278; +import static net.runelite.api.ObjectID.TREE_1279; +import static net.runelite.api.ObjectID.TREE_1280; +import static net.runelite.api.ObjectID.WILLOW; +import static net.runelite.api.ObjectID.WILLOW_10831; +import static net.runelite.api.ObjectID.WILLOW_10833; +import static net.runelite.api.ObjectID.YEW; +import static net.runelite.api.NullObjectID.NULL_10823; +import static net.runelite.api.ObjectID.YEW_36683; @Getter enum Tree { - REDWOOD_TREE_SPAWN(REDWOOD, REDWOOD_29670); + REGULAR_TREE(null, TREE, TREE_1277, TREE_1278, TREE_1279, TREE_1280), + OAK_TREE(Duration.ofMillis(8500), ObjectID.OAK_TREE, OAK_TREE_4540, OAK_10820), + WILLOW_TREE(Duration.ofMillis(8500), WILLOW, WILLOW_10833, WILLOW_10831), + MAPLE_TREE(Duration.ofSeconds(35), ObjectID.MAPLE_TREE, MAPLE_TREE_10832, MAPLE_TREE_36681), + TEAK_TREE(Duration.ofMillis(8500), TEAK, TEAK_36686), + MAHOGANY_TREE(Duration.ofMillis(8500), MAHOGANY, MAHOGANY_36688), + YEW_TREE(Duration.ofMinutes(1), YEW, NULL_10823, YEW_36683), + MAGIC_TREE(Duration.ofMinutes(2), MAGIC_TREE_10834, NULL_10835), + REDWOOD(Duration.ofMinutes(2), ObjectID.REDWOOD, REDWOOD_29670); + @Nullable + private final Duration respawnTime; private final int[] treeIds; - Tree(int... treeIds) + Tree(Duration respawnTime, int... treeIds) { + this.respawnTime = respawnTime; this.treeIds = treeIds; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/TreeRespawn.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/TreeRespawn.java new file mode 100644 index 0000000000..a3022ca8f3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/TreeRespawn.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019, Adam + * Copyright (c) 2019, David + * 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.woodcutting; + +import java.time.Instant; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.coords.LocalPoint; + +@AllArgsConstructor +@Getter +class TreeRespawn +{ + private final Tree tree; + private final LocalPoint location; + private final Instant startTime; + private final int respawnTime; + + boolean isExpired() + { + return Instant.now().isAfter(startTime.plusMillis(respawnTime)); + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingConfig.java index 55cb5e3637..7f992744c6 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingConfig.java @@ -74,4 +74,15 @@ public interface WoodcuttingConfig extends Config { return true; } + + @ConfigItem( + position = 5, + keyName = "showRespawnTimers", + name = "Show respawn timers", + description = "Configures whether to display the respawn timer overlay" + ) + default boolean showRespawnTimers() + { + return true; + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java index 01ced70693..b269bc1e99 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingPlugin.java @@ -27,15 +27,18 @@ package net.runelite.client.plugins.woodcutting; import com.google.inject.Provides; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.regex.Pattern; import javax.inject.Inject; +import lombok.AccessLevel; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.GameObject; -import net.runelite.api.GameState; import net.runelite.api.MenuAction; import net.runelite.api.Player; import net.runelite.api.events.AnimationChanged; @@ -62,6 +65,7 @@ import net.runelite.client.ui.overlay.OverlayMenuEntry; tags = {"birds", "nest", "notifications", "overlay", "skilling", "wc"} ) @PluginDependency(XpTrackerPlugin.class) +@Slf4j public class WoodcuttingPlugin extends Plugin { private static final Pattern WOOD_CUT_PATTERN = Pattern.compile("You get (?:some|an)[\\w ]+(?:logs?|mushrooms)\\."); @@ -93,6 +97,10 @@ public class WoodcuttingPlugin extends Plugin @Getter private final Set treeObjects = new HashSet<>(); + @Getter(AccessLevel.PACKAGE) + private final List respawns = new ArrayList<>(); + private boolean recentlyLoggedIn; + @Provides WoodcuttingConfig getConfig(ConfigManager configManager) { @@ -111,6 +119,7 @@ public class WoodcuttingPlugin extends Plugin { overlayManager.remove(overlay); overlayManager.remove(treesOverlay); + respawns.clear(); treeObjects.clear(); session = null; axe = null; @@ -131,6 +140,10 @@ public class WoodcuttingPlugin extends Plugin @Subscribe public void onGameTick(GameTick gameTick) { + recentlyLoggedIn = false; + + respawns.removeIf(TreeRespawn::isExpired); + if (session == null || session.getLastLogCut() == null) { return; @@ -174,7 +187,7 @@ public class WoodcuttingPlugin extends Plugin GameObject gameObject = event.getGameObject(); Tree tree = Tree.findTree(gameObject.getId()); - if (tree != null) + if (tree == Tree.REDWOOD) { treeObjects.add(gameObject); } @@ -183,7 +196,23 @@ public class WoodcuttingPlugin extends Plugin @Subscribe public void onGameObjectDespawned(final GameObjectDespawned event) { - treeObjects.remove(event.getGameObject()); + final GameObject object = event.getGameObject(); + + Tree tree = Tree.findTree(object.getId()); + if (tree != null) + { + if (tree.getRespawnTime() != null && !recentlyLoggedIn) + { + log.debug("Adding respawn timer for {} tree at {}", tree, object.getLocalLocation()); + TreeRespawn treeRespawn = new TreeRespawn(tree, object.getLocalLocation(), Instant.now(), (int) tree.getRespawnTime().toMillis()); + respawns.add(treeRespawn); + } + + if (tree == Tree.REDWOOD) + { + treeObjects.remove(event.getGameObject()); + } + } } @Subscribe @@ -195,9 +224,19 @@ public class WoodcuttingPlugin extends Plugin @Subscribe public void onGameStateChanged(final GameStateChanged event) { - if (event.getGameState() != GameState.LOGGED_IN) + switch (event.getGameState()) { - treeObjects.clear(); + case LOADING: + case HOPPING: + respawns.clear(); + treeObjects.clear(); + break; + case LOGGED_IN: + // After login trees that are depleted will be changed, + // wait for the next game tick before watching for + // trees to despawn + recentlyLoggedIn = true; + break; } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingTreesOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingTreesOverlay.java index 2415d17f7a..b070861e08 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingTreesOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/woodcutting/WoodcuttingTreesOverlay.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2018, Tomas Slusny * Copyright (c) 2018, Adam + * Copyright (c) 2019, David * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,16 +26,23 @@ */ package net.runelite.client.plugins.woodcutting; +import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; +import java.time.Instant; +import java.util.List; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.GameObject; +import net.runelite.api.Perspective; +import net.runelite.api.Point; +import net.runelite.api.coords.LocalPoint; import net.runelite.client.game.ItemManager; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; import net.runelite.client.ui.overlay.OverlayUtil; +import net.runelite.client.ui.overlay.components.ProgressPieComponent; class WoodcuttingTreesOverlay extends Overlay { @@ -56,16 +64,23 @@ class WoodcuttingTreesOverlay extends Overlay @Override public Dimension render(Graphics2D graphics) + { + renderAxes(graphics); + renderTimers(graphics); + return null; + } + + private void renderAxes(Graphics2D graphics) { if (plugin.getSession() == null || !config.showRedwoodTrees()) { - return null; + return; } Axe axe = plugin.getAxe(); if (axe == null) { - return null; + return; } for (GameObject treeObject : plugin.getTreeObjects()) @@ -75,7 +90,34 @@ class WoodcuttingTreesOverlay extends Overlay OverlayUtil.renderImageLocation(client, graphics, treeObject.getLocalLocation(), itemManager.getImage(axe.getItemId()), 120); } } + } - return null; + private void renderTimers(Graphics2D graphics) + { + List respawns = plugin.getRespawns(); + if (respawns.isEmpty() || !config.showRespawnTimers()) + { + return; + } + + Instant now = Instant.now(); + for (TreeRespawn treeRespawn : respawns) + { + LocalPoint loc = treeRespawn.getLocation(); + + float percent = (now.toEpochMilli() - treeRespawn.getStartTime().toEpochMilli()) / (float) treeRespawn.getRespawnTime(); + Point point = Perspective.localToCanvas(client, loc, client.getPlane()); + if (point == null || percent > 1.0f) + { + continue; + } + + ProgressPieComponent ppc = new ProgressPieComponent(); + ppc.setBorderColor(Color.ORANGE); + ppc.setFill(Color.YELLOW); + ppc.setPosition(point); + ppc.setProgress(percent); + ppc.render(graphics); + } } }