diff --git a/runelite-api/src/main/java/net/runelite/api/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/AnimationID.java index 4d7cee772d..a6c7d98acc 100644 --- a/runelite-api/src/main/java/net/runelite/api/AnimationID.java +++ b/runelite-api/src/main/java/net/runelite/api/AnimationID.java @@ -43,6 +43,12 @@ public final class AnimationID public static final int COOKING_FIRE = 897; public static final int COOKING_RANGE = 896; public static final int FLETCHING_BOW_CUTTING = 1248; + public static final int HUNTER_LAY_BOXTRAP_BIRDSNARE = 5208; //same for laying bird snares and box traps + public static final int HUNTER_LAY_DEADFALLTRAP = 5212; //setting up deadfall trap + public static final int HUNTER_LAY_NETTRAP = 5215; //setting up net trap + public static final int HUNTER_CHECK_BIRD_SNARE = 5207; + public static final int HUNTER_CHECK_BOX_TRAP = 5212; + public static final int HERBLORE_MAKE_TAR = 5249; public static final int FLETCHING_STRING_NORMAL_SHORTBOW = 6678; public static final int FLETCHING_STRING_NORMAL_LONGBOW = 6684; public static final int FLETCHING_STRING_OAK_SHORTBOW = 6679; diff --git a/runelite-api/src/main/java/net/runelite/api/MenuAction.java b/runelite-api/src/main/java/net/runelite/api/MenuAction.java index 1bedea4cf6..e8209804a2 100644 --- a/runelite-api/src/main/java/net/runelite/api/MenuAction.java +++ b/runelite-api/src/main/java/net/runelite/api/MenuAction.java @@ -26,25 +26,123 @@ package net.runelite.api; public enum MenuAction { - EXAMINE_OBJECT(1002), - EXAMINE_NPC(1003), /** - * Menu action triggered by examining item on ground + * Menu action triggered by examining an object. + */ + EXAMINE_OBJECT(1002), + + /** + * Menu action triggered by examining an NPC. + */ + EXAMINE_NPC(1003), + + /** + * Menu action triggered by examining item on ground. */ EXAMINE_ITEM_GROUND(1004), + /** - * Menu action triggered by examining item in inventory + * Menu action triggered by examining item in inventory. */ EXAMINE_ITEM(1005), + /** - * Menu action triggered by either examining item in bank, examining item - * in inventory while having bank open, or examining equipped item + * Menu action triggered by either examining item in bank, examining + * item in inventory while having bank open, or examining equipped item. */ EXAMINE_ITEM_BANK_EQ(1007), + /** - * Menu action injected by runelite for its menu items + * Menu action triggered by using an item in inventory on an item thats + * on the ground. + */ + USE_ON_ITEM_GROUND(1), + + /** + * Menu action triggered when checking a trap while hunting. + */ + CHECK(3), + + /** + * Menu action triggered when checking a trap while hunting. + */ + DISMANTLE(3), + + /** + * Menu action triggered by using an item in inventory on an NPC. + */ + USE_ON_NPC(7), + + /** + * Menu action triggered when clicking "Dismiss" on a random event NPC. + */ + DISSMISS(13), + + /** + * Menu action triggered by using an item in inventory on another + * player. + */ + USE_ON_PLAYER(14), + + /** + * Menu action triggered when laying a trap that is already on the + * ground. + */ + LAY_GROUND(21), + + /** + * Menu action triggered when clicking "CLICK HERE TO PLAY" just after + * login. + */ + PLAY_RUNESCAPE(24), + + /** + * Menu action triggered by using an item in your inventory on another + * item in your inventory. + */ + USE_ON_ITEM_INV(31), + + /** + * Menu action triggered when laying a trap that is in your inventory. + */ + LAY_INV(33), + + /** + * Menu action triggered when cleaning a herb. + */ + CLEAN(33), + + /** + * Menu action triggered when clicking on a wieldable item in your + * inventory. + */ + WIELD(34), + + /** + * Menu action triggered when clicking on a wieldable item in your + * inventory. + */ + WEAR(34), + + /** + * Menu action triggered when dropping an item from your inventory. + */ + DROP(37), + + /** + * Menu action triggered clicking on an item in your inventory to "Use" + * it, and select it to use it on something else. + */ + USE(38), + + /** + * Menu action injected by runelite for its menu items. */ RUNELITE(1500), + + /** + * Menu action triggered when the id is not defined in this class. + */ UNKNOWN(-1); private final int id; diff --git a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java index 46dce32017..3e00f0862b 100644 --- a/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java +++ b/runelite-api/src/main/java/net/runelite/api/widgets/Widget.java @@ -87,4 +87,6 @@ public interface Widget int getItemId(); int getItemQuantity(); + + boolean contains(Point point); } diff --git a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java index a455f04066..f9a50d178e 100644 --- a/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java +++ b/runelite-client/src/main/java/net/runelite/client/callback/Hooks.java @@ -33,6 +33,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Actor; import net.runelite.api.ChatMessageType; import net.runelite.api.Client; +import net.runelite.api.GameObject; import net.runelite.api.MainBufferProvider; import net.runelite.api.MenuAction; import net.runelite.api.MessageNode; @@ -40,6 +41,7 @@ import net.runelite.api.PacketBuffer; import net.runelite.api.Point; import net.runelite.api.Projectile; import net.runelite.api.Skill; +import net.runelite.api.Tile; import net.runelite.client.RuneLite; import net.runelite.client.chat.ChatMessageManager; import net.runelite.client.events.*; @@ -109,6 +111,14 @@ public class Hooks } } + /** + * + * @param name Hook-name that was used in the @Hook-annotation. + * @param idx The index if hooked to an array. -1 if not hooked to an + * array. + * @param object The object where the hook was placed in, NOT the + * variable that was hooked to. + */ public static void callHook(String name, int idx, Object object) { switch (name) @@ -176,6 +186,19 @@ public class Hooks eventBus.post(resizeableChanged); break; } + case "gameObjectsChanged": + if (idx != -1) // this happens from the field assignment + { + // GameObject that was changed. + GameObject go = ((Tile) object).getGameObjects()[idx]; + if (go != null) + { + GameObjectsChanged gameObjectsChanged = new GameObjectsChanged(); + gameObjectsChanged.setGameObject(go); + eventBus.post(gameObjectsChanged); + } + } + break; default: log.warn("Unknown event {} triggered on {}", name, object); return; diff --git a/runelite-client/src/main/java/net/runelite/client/events/GameObjectsChanged.java b/runelite-client/src/main/java/net/runelite/client/events/GameObjectsChanged.java new file mode 100644 index 0000000000..3e13b5f158 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/GameObjectsChanged.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, Robin Weymans + * 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.events; + +import lombok.Data; +import net.runelite.api.GameObject; + +@Data +public class GameObjectsChanged +{ + private GameObject gameObject; +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java index 4dd10c80df..18a4a67295 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsOverlay.java @@ -28,7 +28,6 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; -import java.awt.Rectangle; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -224,7 +223,7 @@ public class GroundItemsOverlay extends Overlay { Point point = itemLayer.getCanvasLocation(); // if the item is offscreen, don't bother drawing it - if (point == null || !pointInWidget(point, viewport)) + if (point == null || (viewport != null && !viewport.contains(point))) { continue; } @@ -314,14 +313,4 @@ public class GroundItemsOverlay extends Overlay return null; } - - private boolean pointInWidget(Point point, Widget widget) - { - if (widget != null) - { - Rectangle bounds = widget.getBounds(); - return bounds != null && bounds.contains(new java.awt.Point(point.getX(), point.getY())); - } - return false; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/CatchrateOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/CatchrateOverlay.java new file mode 100644 index 0000000000..c22fd7ac5e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/CatchrateOverlay.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017, Robin Weymans + * 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.hunter; + +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.time.Duration; +import java.time.Instant; +import javax.inject.Inject; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.PanelComponent; + +/** + * Represents the overlay that shows the catch rate (percentage). + */ +public class CatchrateOverlay extends Overlay +{ + /** + * The time after which the catch rate panel disappears, if the player + * stops hunting. + */ + private final Duration catchRatePanelTimeOut = Duration.ofSeconds(30); + private final PanelComponent catchRatePanel = new PanelComponent(); + + private final HunterPlugin plugin; + private final HunterConfig config; + + @Inject + CatchrateOverlay(HunterPlugin plugin, HunterConfig config) + { + setPosition(OverlayPosition.BOTTOM_RIGHT); + this.plugin = plugin; + this.config = config; + } + + @Override + public Dimension render(Graphics2D graphics, Point parent) + { + if (config.enabled()) + { + if (Duration.between(plugin.getLastActionTime(), Instant.now()).compareTo(catchRatePanelTimeOut) < 0) + { + final String attackStyleString = String.format("%.2f", plugin.getCatchRate() * 100) + " %"; + catchRatePanel.setTitle(attackStyleString); + catchRatePanel.setWidth(80); + return catchRatePanel.render(graphics, parent); + } + } + return null; + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterConfig.java new file mode 100644 index 0000000000..b1953bc80b --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterConfig.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2017, Robin Weymans + * 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.hunter; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup( + keyName = "hunterplugin", + name = "Hunter plugin", + description = "Configuration for the hunter plugin" +) +public interface HunterConfig extends Config +{ + @ConfigItem( + position = 0, + keyName = "enabled", + name = "Enabled", + description = "Configures whether or not the hunter plugin is enabled" + ) + default boolean enabled() + { + return true; + } + + @ConfigItem( + position = 1, + keyName = "hexColorOpenTrap", + name = "Open trap", + description = "Color of open trap timer" + ) + default Color getOpenTrapColor() + { + return Color.YELLOW; + } + + @ConfigItem( + position = 2, + keyName = "hexColorFullTrap", + name = "Full trap", + description = "Color of full trap timer" + ) + default Color getFullTrapColor() + { + return Color.GREEN; + } + + @ConfigItem( + position = 3, + keyName = "hexColorEmptyTrap", + name = "Empty trap", + description = "Color of empty trap timer" + ) + default Color getEmptyTrapColor() + { + return Color.RED; + } + + @ConfigItem( + position = 4, + keyName = "hexColorTransTrap", + name = "Transitioning trap", + description = "Color of transitioning trap timer" + ) + default Color getTransTrapColor() + { + return Color.ORANGE; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterPlugin.java new file mode 100644 index 0000000000..867d7b8547 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterPlugin.java @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2017, Robin Weymans + * 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.hunter; + +import com.google.common.eventbus.Subscribe; +import com.google.inject.Binder; +import com.google.inject.Provides; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import javax.inject.Inject; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.AnimationID; +import net.runelite.api.Client; +import net.runelite.api.GameObject; +import net.runelite.api.GameState; +import net.runelite.api.ObjectID; +import net.runelite.api.Player; +import net.runelite.api.Point; +import net.runelite.api.queries.GameObjectQuery; +import net.runelite.api.queries.PlayerQuery; +import net.runelite.client.RuneLite; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.GameObjectsChanged; +import net.runelite.client.events.GameStateChanged; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.task.Schedule; +import net.runelite.client.ui.overlay.Overlay; + +@Slf4j +@PluginDescriptor( + name = "Hunter plugin" +) +public class HunterPlugin extends Plugin +{ + @Inject + private Client client; + + @Inject + private RuneLite runelite; + + @Inject + private HunterConfig config; + + @Inject + private TrapOverlay trapOverlay; + + @Inject + private CatchrateOverlay catchrateOverlay; + + @Getter + private final Set traps = new HashSet<>(); + + private double catchAtempts = 0; + private double catchSuccess = 0; + + @Getter + private Instant lastActionTime = Instant.ofEpochMilli(0); + + @Override + public void configure(Binder binder) + { + binder.bind(TrapOverlay.class); + binder.bind(CatchrateOverlay.class); + } + + @Provides + HunterConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(HunterConfig.class); + } + + @Override + public Collection getOverlays() + { + return Arrays.asList(trapOverlay, catchrateOverlay); + } + + @Subscribe + public void onGameObjectsChanged(GameObjectsChanged event) + { + if (!config.enabled()) + { + return; + } + + GameObject gameObject = event.getGameObject(); + + HunterTrap myTrap = getTrapFromCollection(gameObject); + + Player localPlayer = client.getLocalPlayer(); + + switch (gameObject.getId()) + { + case ObjectID.DEADFALL: //Deadfall trap placed + if (localPlayer.getWorldLocation().distanceTo(gameObject.getWorldLocation()) <= 2 + && localPlayer.getAnimation() == AnimationID.HUNTER_LAY_DEADFALLTRAP) + { + log.debug("Deadfall trap placed by \"{}\" on {}", localPlayer.getName(), gameObject.getWorldLocation()); + traps.add(new HunterTrap(gameObject)); + lastActionTime = Instant.now(); + } + break; + case ObjectID.BOX_TRAP_9380: //Box trap placed + case ObjectID.BIRD_SNARE_9345: //Bird snare placed + case ObjectID.NET_TRAP_9343: //Net trap placed at green sallys + case ObjectID.NET_TRAP: //Net trap placed at orange sallys + case ObjectID.NET_TRAP_8992: //Net trap placed at red sallys + case ObjectID.NET_TRAP_9002: //Net trap placed at black sallys + //Look for players that are on the same tile + PlayerQuery playerQuery = new PlayerQuery().atLocalLocation(gameObject.getLocalLocation()); + List possiblePlayers = Arrays.asList(runelite.runQuery(playerQuery)); + + /* If the player is on that tile, and it has the correct animation, assume he is the one that placed the trap + * Special case: if you herb+tar, then move and place the trap, it does not detect laying the trap. It does work + * if you herb+tar en then place the trap. + */ + if (possiblePlayers.contains(localPlayer)) + { + switch (localPlayer.getAnimation()) + { + case AnimationID.HUNTER_LAY_BOXTRAP_BIRDSNARE: + case AnimationID.HUNTER_LAY_NETTRAP: + case AnimationID.HERBLORE_MAKE_TAR: // When 3 ticking + log.debug("Trap placed by \"{}\" on {}", localPlayer.getName(), localPlayer.getWorldLocation()); + traps.add(new HunterTrap(gameObject)); + + lastActionTime = Instant.now(); + break; + } + + } + break; + case ObjectID.SHAKING_BOX: //Black chinchompa caught + case ObjectID.SHAKING_BOX_9382: // Grey chinchompa caught + case ObjectID.SHAKING_BOX_9383: //Red chinchompa caught + case ObjectID.BOULDER_20648: //Prickly kebbit caught + case ObjectID.BOULDER_20649: //Sabre-tooth kebbit caught + case ObjectID.BOULDER_20650: //Barb-tailed kebbit caught + case ObjectID.BOULDER_20651: //Wild kebbit caught + case ObjectID.BIRD_SNARE_9373: //Crimson swift caught + case ObjectID.BIRD_SNARE_9375: //Cerulean twitch caught + case ObjectID.BIRD_SNARE_9377: //Golden warbler caught + case ObjectID.BIRD_SNARE_9379: //Copper longtail caught + case ObjectID.BIRD_SNARE_9348: //Tropical wagtail caught + case ObjectID.NET_TRAP_9004: //Green sally caught + case ObjectID.NET_TRAP_8986: //Red sally caught + case ObjectID.NET_TRAP_8734: //Orange sally caught + case ObjectID.NET_TRAP_8996: //Black sally caught + if (myTrap != null) + { + log.debug("Yay, you caught something"); + myTrap.setState(HunterTrap.State.FULL); + catchAtempts++; + catchSuccess++; + + lastActionTime = Instant.now(); + } + break; + case ObjectID.BOX_TRAP_9385: //Empty box trap + case ObjectID.BIRD_SNARE: //Empty box trap + if (myTrap != null) + { + log.debug("Your trap didn't catch anything"); + myTrap.setState(HunterTrap.State.EMPTY); + myTrap.resetTimer(); + catchAtempts++; + + lastActionTime = Instant.now(); + } + break; + //Black chin shaking box + case ObjectID.BOX_TRAP: + case ObjectID.BOX_TRAP_2026: + case ObjectID.BOX_TRAP_2028: + case ObjectID.BOX_TRAP_2029: + + //Red chin shaking box + case ObjectID.BOX_TRAP_9381: + case ObjectID.BOX_TRAP_9390: + case ObjectID.BOX_TRAP_9391: + case ObjectID.BOX_TRAP_9392: + case ObjectID.BOX_TRAP_9393: + + //Grey chin shaking box + case ObjectID.BOX_TRAP_9386: + case ObjectID.BOX_TRAP_9387: + case ObjectID.BOX_TRAP_9388: + + //Bird traps + case ObjectID.BIRD_SNARE_9346: + case ObjectID.BIRD_SNARE_9347: + case ObjectID.BIRD_SNARE_9349: + case ObjectID.BIRD_SNARE_9374: + case ObjectID.BIRD_SNARE_9376: + case ObjectID.BIRD_SNARE_9378: + + //Deadfall trap + case ObjectID.DEADFALL_19218: + case ObjectID.DEADFALL_19851: + case ObjectID.DEADFALL_20128: + case ObjectID.DEADFALL_20129: + case ObjectID.DEADFALL_20130: + case ObjectID.DEADFALL_20131: + + //Net trap + case ObjectID.NET_TRAP_9003: + case ObjectID.NET_TRAP_9005: + case ObjectID.NET_TRAP_8972: + case ObjectID.NET_TRAP_8974: + case ObjectID.NET_TRAP_8985: + case ObjectID.NET_TRAP_8987: + case ObjectID.NET_TRAP_8993: + case ObjectID.NET_TRAP_8997: + if (myTrap != null) + { + myTrap.setState(HunterTrap.State.TRANSITION); + } + break; + } + } + + @Subscribe + public void onGameStateChange(GameStateChanged event) + { + if (event.getGameState().equals(GameState.LOGIN_SCREEN)) + { + trapOverlay.updateConfig(); + } + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals("hunterplugin")) + { + trapOverlay.updateConfig(); + } + } + + /** + * Iterates over all the traps that were placed by the local player and + * checks if the trap is still there. If the trap is gone, it removes + * the trap from the local players trap collection. + */ + @Schedule( + period = 500, + unit = ChronoUnit.MILLIS + ) + public void updateTraps() + { + if (!config.enabled()) + { + return; + } + + //Check if all traps are still there, and remove the ones that are not. + Iterator it = traps.iterator(); + while (it.hasNext()) + { + HunterTrap trap = it.next(); + + //Look for gameobjects that are on the same location as the trap + GameObjectQuery goQuery = new GameObjectQuery() + .atWorldLocation(trap.getGameObject().getWorldLocation()); + //This is for placeable traps like box traps. There are no gameobjects on that location if the trap collapsed + if (runelite.runQuery(goQuery).length == 0) + { + it.remove(); + log.debug("Trap removed from personal trap collection, {} left", traps.size()); + } + else //For traps like deadfalls. This is different because when the trap is gone, there is still a GameObject (boulder) + { + goQuery = goQuery + .idEquals(ObjectID.BOULDER_19215); //Deadfalls are the only ones (that i can test) that have this behaviour. I think maniacal monkeys have this too. + if (runelite.runQuery(goQuery).length != 0) + { + it.remove(); + log.debug("Special trap removed from personal trap collection, {} left", traps.size()); + } + } + } + + } + + /** + * Looks for a trap in the local players trap collection, on the same + * place as the given GameObject. + * + * @param gameObject + * @return A HunterTrap object if the player has a trap on the same + * location as the GameObject. Otherwise it returns null. + */ + private HunterTrap getTrapFromCollection(GameObject gameObject) + { + Point gameObjectLocation = gameObject.getWorldLocation(); + for (HunterTrap trap : traps) + { + if (gameObjectLocation.equals(trap.getGameObject().getWorldLocation())) + { + return trap; + } + } + return null; + } + + /** + * Calculates the catch rate, i.e. the attempts to catch something + * compared to the times you succeed. + * + * @return Value between 0 (none) and 1 (all). + */ + public double getCatchRate() + { + return catchAtempts != 0 ? catchSuccess / catchAtempts : 0; + } + +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterTrap.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterTrap.java new file mode 100644 index 0000000000..e089ed6964 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/HunterTrap.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2017, Robin Weymans + * 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.hunter; + +import java.time.Duration; +import java.time.Instant; +import java.util.Objects; +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.GameObject; + +/** + * Wrapper class for a GameObject that represents a hunter trap. + */ +class HunterTrap +{ + /** + * A hunter trap stays up 1 minute before collapsing. + */ + private static final Duration TRAP_TIME = Duration.ofMinutes(1); + + /** + * The time in milliseconds when the trap was placed. + */ + @Getter + private Instant placedOn; + + /** + * The state of the trap. + */ + @Getter + @Setter + private State state; + + /** + * The gameobject thats corresponds with this trap. + */ + @Getter + private final GameObject gameObject; + + /** + * The states a trap can be in. + */ + enum State + { + /** + * A laid out trap. + */ + OPEN, + /** + * A trap that is empty. + */ + EMPTY, + /** + * A trap that caught something. + */ + FULL, + /** + * A trap that is closing. + */ + TRANSITION + } + + /** + * Constructor for a HunterTrap object + * + * @param gameObject The gameobject thats corresponds with this trap. + */ + HunterTrap(GameObject gameObject) + { + this.state = State.OPEN; + this.gameObject = gameObject; + this.placedOn = Instant.now(); + } + + /** + * Calculates how much time is left before the trap is collapsing. + * + * @return Value between 0 and 1. 0 means the trap was laid moments ago. + * 1 is a trap that's about to collapse. + */ + public double getTrapTimeRelative() + { + Duration duration = Duration.between(placedOn, Instant.now()); + return duration.compareTo(TRAP_TIME) < 0 ? (double) duration.toMillis() / TRAP_TIME.toMillis() : 1; + } + + /** + * Resets the time value when the trap was placed. + */ + public void resetTimer() + { + placedOn = Instant.now(); + } + + @Override + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + if (!(o instanceof HunterTrap)) + { + return false; + } + HunterTrap t = (HunterTrap) o; + return gameObject.getWorldLocation().equals(t.getGameObject().getWorldLocation()); + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 97 * hash + Objects.hashCode(this.gameObject); + return hash; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/hunter/TrapOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/TrapOverlay.java new file mode 100644 index 0000000000..c776dd631a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/hunter/TrapOverlay.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2017, Robin Weymans + * 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.hunter; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.geom.Arc2D; +import javax.annotation.Nullable; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.widgets.Widget; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayPosition; + +/** + * Represents the overlay that shows timers on traps that are placed by the + * player. + */ +public class TrapOverlay extends Overlay +{ + /** + * Size of the trap timer. + */ + private static final int TIMER_SIZE = 25; + + /** + * Width of the border around the trap timer. + */ + private static final int TIMER_BORDER_WIDTH = 1; + + /** + * The timer is low when only 25% is left. + */ + private static final double TIMER_LOW = 0.25; // When the timer is under a quarter left, if turns red. + + private final Client client; + private final HunterPlugin plugin; + private final HunterConfig config; + + private Color colorOpen, colorOpenBorder; + private Color colorEmpty, colorEmptyBorder; + private Color colorFull, colorFullBorder; + private Color colorTrans, colorTransBorder; + + @Inject + TrapOverlay(@Nullable Client client, HunterPlugin plugin, HunterConfig config) + { + setPosition(OverlayPosition.DYNAMIC); + this.plugin = plugin; + this.config = config; + this.client = client; + } + + @Override + public Dimension render(Graphics2D graphics, Point parent) + { + if (config.enabled()) + { + drawTraps(graphics); + } + return null; + } + + /** + * Updates the timer colors. + */ + public void updateConfig() + { + colorEmptyBorder = config.getEmptyTrapColor(); + colorEmpty = new Color(colorEmptyBorder.getRed(), colorEmptyBorder.getGreen(), colorEmptyBorder.getBlue(), 100); + colorFullBorder = config.getFullTrapColor(); + colorFull = new Color(colorFullBorder.getRed(), colorFullBorder.getGreen(), colorFullBorder.getBlue(), 100); + colorOpenBorder = config.getOpenTrapColor(); + colorOpen = new Color(colorOpenBorder.getRed(), colorOpenBorder.getGreen(), colorOpenBorder.getBlue(), 100); + colorTransBorder = config.getTransTrapColor(); + colorTrans = new Color(colorTransBorder.getRed(), colorTransBorder.getGreen(), colorTransBorder.getBlue(), 100); + } + + /** + * Iterates over all the traps that were placed by the local player, and + * draws a circle or a timer on the trap, depending on the trap state. + * + * @param graphics + */ + private void drawTraps(Graphics2D graphics) + { + Widget viewport = client.getViewportWidget(); + for (HunterTrap trap : plugin.getTraps()) + { + if (viewport != null && viewport.contains(trap.getGameObject().getCanvasLocation())) + { + switch (trap.getState()) + { + case OPEN: + drawTimerOnTrap(graphics, trap, colorOpen, colorOpenBorder, colorEmpty, colorOpenBorder); + break; + case EMPTY: + drawTimerOnTrap(graphics, trap, colorEmpty, colorEmptyBorder, colorEmpty, colorEmptyBorder); + break; + case FULL: + drawCircleOnTrap(graphics, trap, colorFull, colorFullBorder); + break; + case TRANSITION: + drawCircleOnTrap(graphics, trap, colorTrans, colorTransBorder); + break; + } + } + } + } + + /** + * Draws a timer on a given trap. + * + * @param graphics + * @param trap The trap on which the timer needs to be drawn + * @param fill The fill color of the timer + * @param border The border color of the timer + * @param fillTimeLow The fill color of the timer when it is low + * @param borderTimeLow The border color of the timer when it is low + */ + private void drawTimerOnTrap(Graphics2D graphics, HunterTrap trap, Color fill, Color border, Color fillTimeLow, Color borderTimeLow) + { + net.runelite.api.Point loc = trap.getGameObject().getCanvasLocation(); + + //Construct the arc + Arc2D.Float arc = new Arc2D.Float(Arc2D.PIE); + arc.setAngleStart(90); + double timeLeft = 1 - trap.getTrapTimeRelative(); + arc.setAngleExtent(timeLeft * 360); + arc.setFrame(loc.getX() - TIMER_SIZE / 2, loc.getY() - TIMER_SIZE / 2, TIMER_SIZE, TIMER_SIZE); + + //Draw the inside of the arc + graphics.setColor(timeLeft > TIMER_LOW ? fill : fillTimeLow); + graphics.fill(arc); + + //Draw the outlines of the arc + graphics.setStroke(new BasicStroke(TIMER_BORDER_WIDTH)); + graphics.setColor(timeLeft > TIMER_LOW ? border : borderTimeLow); + graphics.drawOval(loc.getX() - TIMER_SIZE / 2, loc.getY() - TIMER_SIZE / 2, TIMER_SIZE, TIMER_SIZE); + } + + /** + * Draws a timer on a given trap. + * + * @param graphics + * @param trap The trap on which the timer needs to be drawn + * @param fill The fill color of the timer + * @param border The border color of the timer + */ + private void drawCircleOnTrap(Graphics2D graphics, HunterTrap trap, Color fill, Color border) + { + net.runelite.api.Point loc = trap.getGameObject().getCanvasLocation(); + + //Draw the inside of the arc + graphics.setColor(fill); + graphics.fillOval(loc.getX() - TIMER_SIZE / 2, loc.getY() - TIMER_SIZE / 2, TIMER_SIZE, TIMER_SIZE); + + //Draw the border of the cirlce + graphics.setColor(border); + graphics.setStroke(new BasicStroke(TIMER_BORDER_WIDTH)); + graphics.drawOval(loc.getX() - TIMER_SIZE / 2, loc.getY() - TIMER_SIZE / 2, TIMER_SIZE, TIMER_SIZE); + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java index 05366fbd04..0f95e5a14b 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSWidgetMixin.java @@ -307,4 +307,11 @@ public abstract class RSWidgetMixin implements RSWidget return widgets.toArray(new Widget[widgets.size()]); } + @Inject + @Override + public boolean contains(Point point) + { + Rectangle bounds = getBounds(); + return bounds != null && bounds.contains(new java.awt.Point(point.getX(), point.getY())); + } }