From 1bac71f8402646dbd4cbb2e32fa8eab310c8eb31 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 15 Jul 2018 14:50:11 -0400 Subject: [PATCH] Add item spawn events --- .../runelite/api/events/ItemDespawned.java | 40 ++++++ .../api/events/ItemQuantityChanged.java | 41 ++++++ .../net/runelite/api/events/ItemSpawned.java | 40 ++++++ .../net/runelite/mixins/RSClientMixin.java | 18 +++ .../java/net/runelite/mixins/RSItemMixin.java | 125 ++++++++++++++++++ .../java/net/runelite/mixins/RSNodeMixin.java | 46 +++++++ .../java/net/runelite/mixins/RSTileMixin.java | 97 +++++++++++++- .../java/net/runelite/rs/api/RSClient.java | 4 + .../main/java/net/runelite/rs/api/RSItem.java | 15 +++ .../main/java/net/runelite/rs/api/RSNode.java | 8 ++ 10 files changed, 430 insertions(+), 4 deletions(-) create mode 100644 runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java create mode 100644 runelite-api/src/main/java/net/runelite/api/events/ItemQuantityChanged.java create mode 100644 runelite-api/src/main/java/net/runelite/api/events/ItemSpawned.java create mode 100644 runelite-mixins/src/main/java/net/runelite/mixins/RSItemMixin.java create mode 100644 runelite-mixins/src/main/java/net/runelite/mixins/RSNodeMixin.java diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java b/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java new file mode 100644 index 0000000000..4e60837d9d --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018, Adam + * 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.api.events; + +import lombok.Value; +import net.runelite.api.Item; +import net.runelite.api.Tile; + +/** + * Called when an item pile despawns from the ground. When the client loads a new scene, + * all item piles are implicitly despawned, and despawn events will not be sent. + */ +@Value +public class ItemDespawned +{ + private final Tile tile; + private final Item item; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemQuantityChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ItemQuantityChanged.java new file mode 100644 index 0000000000..fb6b4f37ae --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemQuantityChanged.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018, Adam + * 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.api.events; + +import lombok.Value; +import net.runelite.api.Item; +import net.runelite.api.Tile; + +/** + * Called when the quantity of an item pile changes. + */ +@Value +public class ItemQuantityChanged +{ + private final Item item; + private final Tile tile; + private final int oldQuantity; + private final int newQuantity; +} diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemSpawned.java b/runelite-api/src/main/java/net/runelite/api/events/ItemSpawned.java new file mode 100644 index 0000000000..e02d927cfc --- /dev/null +++ b/runelite-api/src/main/java/net/runelite/api/events/ItemSpawned.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018, Adam + * 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.api.events; + +import lombok.Value; +import net.runelite.api.Item; +import net.runelite.api.Tile; + +/** + * Called when an item pile spawns on the ground. When the client loads a new scene, + * all item piles are implicitly reset and a new spawn event will be sent. + */ +@Value +public class ItemSpawned +{ + private final Tile tile; + private final Item item; +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java index d128571a99..9708ae1379 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSClientMixin.java @@ -102,6 +102,7 @@ import net.runelite.rs.api.RSFriendContainer; import net.runelite.rs.api.RSFriendManager; import net.runelite.rs.api.RSHashTable; import net.runelite.rs.api.RSIndexedSprite; +import net.runelite.rs.api.RSItem; import net.runelite.rs.api.RSItemContainer; import net.runelite.rs.api.RSNPC; import net.runelite.rs.api.RSName; @@ -149,6 +150,9 @@ public abstract class RSClientMixin implements RSClient @Inject private static int oldMenuEntryCount; + @Inject + private static RSItem lastItemDespawn; + @Inject @Override public Callbacks getCallbacks() @@ -1148,4 +1152,18 @@ public abstract class RSClientMixin implements RSClient } } } + + @Inject + @Override + public RSItem getLastItemDespawn() + { + return lastItemDespawn; + } + + @Inject + @Override + public void setLastItemDespawn(RSItem lastItemDespawn) + { + RSClientMixin.lastItemDespawn = lastItemDespawn; + } } \ No newline at end of file diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSItemMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemMixin.java new file mode 100644 index 0000000000..1d0732d9f6 --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSItemMixin.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2018, Adam + * 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.mixins; + +import net.runelite.api.Tile; +import net.runelite.api.events.ItemQuantityChanged; +import net.runelite.api.mixins.FieldHook; +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.Mixin; +import net.runelite.api.mixins.Shadow; +import net.runelite.rs.api.RSClient; +import net.runelite.rs.api.RSItem; + +@Mixin(RSItem.class) +public abstract class RSItemMixin implements RSItem +{ + @Shadow("clientInstance") + private static RSClient client; + + @Inject + private int rl$sceneX = -1; + + @Inject + private int rl$sceneY = -1; + + @Inject + RSItemMixin() + { + } + + @Inject + @Override + public Tile getTile() + { + int x = rl$sceneX; + int y = rl$sceneY; + + if (x == -1 || y == -1) + { + return null; + } + + Tile[][][] tiles = client.getScene().getTiles(); + Tile tile = tiles[client.getPlane()][x][y]; + return tile; + } + + @Inject + @Override + public void onUnlink() + { + if (rl$sceneX != -1) + { + // on despawn, the first item unlinked is the item despawning. However on spawn + // items can be delinked in order to sort them, so we can't assume the item here is despawning + if (client.getLastItemDespawn() == null) + { + client.setLastItemDespawn(this); + } + } + } + + @Inject + @FieldHook(value = "quantity", before = true) + public void quantityChanged(int quantity) + { + if (rl$sceneX != -1) + { + client.getLogger().debug("Item quantity changed: {} ({} -> {})", getId(), getQuantity(), quantity); + + ItemQuantityChanged itemQuantityChanged = new ItemQuantityChanged(this, getTile(), getQuantity(), quantity); + client.getCallbacks().post(itemQuantityChanged); + } + } + + @Inject + @Override + public int getX() + { + return rl$sceneX; + } + + @Inject + @Override + public void setX(int x) + { + rl$sceneX = x; + } + + @Inject + @Override + public int getY() + { + return rl$sceneY; + } + + @Inject + @Override + public void setY(int y) + { + rl$sceneY = y; + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSNodeMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSNodeMixin.java new file mode 100644 index 0000000000..4a8f7162cf --- /dev/null +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSNodeMixin.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018, Adam + * 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.mixins; + +import net.runelite.api.mixins.Inject; +import net.runelite.api.mixins.MethodHook; +import net.runelite.api.mixins.Mixin; +import net.runelite.rs.api.RSNode; + +@Mixin(RSNode.class) +public abstract class RSNodeMixin implements RSNode +{ + @Inject + public void onUnlink() + { + } + + @Inject + @MethodHook("unlink") + public void rl$unlink() + { + onUnlink(); + } +} diff --git a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java index b0dab51ca2..23f5f163cb 100644 --- a/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java +++ b/runelite-mixins/src/main/java/net/runelite/mixins/RSTileMixin.java @@ -30,11 +30,11 @@ import net.runelite.api.Actor; import net.runelite.api.CollisionDataFlag; import net.runelite.api.DecorativeObject; import net.runelite.api.GameObject; -import net.runelite.api.GameState; import net.runelite.api.GroundObject; import net.runelite.api.Item; import net.runelite.api.ItemLayer; import net.runelite.api.Node; +import net.runelite.api.Perspective; import net.runelite.api.Point; import net.runelite.api.Tile; import net.runelite.api.WallObject; @@ -49,7 +49,9 @@ import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.GroundObjectChanged; import net.runelite.api.events.GroundObjectDespawned; import net.runelite.api.events.GroundObjectSpawned; +import net.runelite.api.events.ItemDespawned; import net.runelite.api.events.ItemLayerChanged; +import net.runelite.api.events.ItemSpawned; import net.runelite.api.events.WallObjectChanged; import net.runelite.api.events.WallObjectDespawned; import net.runelite.api.events.WallObjectSpawned; @@ -59,7 +61,11 @@ import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSCollisionData; +import net.runelite.rs.api.RSDeque; import net.runelite.rs.api.RSGameObject; +import net.runelite.rs.api.RSItem; +import net.runelite.rs.api.RSItemLayer; +import net.runelite.rs.api.RSNode; import net.runelite.rs.api.RSTile; @Mixin(RSTile.class) @@ -270,12 +276,95 @@ public abstract class RSTileMixin implements RSTile @Inject public void itemLayerChanged(int idx) { - if (client.getGameState() != GameState.LOGGED_IN) + RSItem lastUnlink = client.getLastItemDespawn(); + if (lastUnlink != null) { - // during loading this gets set to null 104x104 times + client.setLastItemDespawn(null); + } + + RSItemLayer itemLayer = (RSItemLayer) getItemLayer(); + if (itemLayer == null) + { + if (lastUnlink != null) + { + client.getLogger().debug("Item despawn: {} ({})", lastUnlink.getId(), lastUnlink.getQuantity()); + ItemDespawned itemDespawned = new ItemDespawned(this, lastUnlink); + client.getCallbacks().post(itemDespawned); + } return; } + int x = itemLayer.getX() / Perspective.LOCAL_TILE_SIZE; + int y = itemLayer.getY() / Perspective.LOCAL_TILE_SIZE; + int z = client.getPlane(); + + RSDeque[][][] groundItemDeque = client.getGroundItemDeque(); + RSDeque itemDeque = groundItemDeque[z][x][y]; + + if (itemDeque == null) + { + if (lastUnlink != null) + { + client.getLogger().debug("Item despawn: {} ({})", lastUnlink.getId(), lastUnlink.getQuantity()); + ItemDespawned itemDespawned = new ItemDespawned(this, lastUnlink); + client.getCallbacks().post(itemDespawned); + } + return; + } + + // The new item gets added to either the head, or the tail, depending on its price + RSNode head = itemDeque.getHead(); + RSNode current = null; + RSNode previous = head.getPrevious(); + boolean forward = false; + if (head != previous) + { + RSItem prev = (RSItem) previous; + if (x != prev.getX() || y != prev.getY()) + { + current = prev; + } + } + + RSNode next = head.getNext(); + if (current == null && head != next) + { + RSItem n = (RSItem) next; + if (x != n.getX() || y != n.getY()) + { + current = n; + forward = true; + } + } + + if (lastUnlink != null && lastUnlink != previous && lastUnlink != next) + { + client.getLogger().debug("Item despawn: {} ({})", lastUnlink.getId(), lastUnlink.getQuantity()); + ItemDespawned itemDespawned = new ItemDespawned(this, lastUnlink); + client.getCallbacks().post(itemDespawned); + } + + if (current == null) + { + return; // already seen this spawn, or no new item + } + + do + { + RSItem item = (RSItem) current; + client.getLogger().debug("Item spawn: {} ({})", item.getId(), item.getQuantity()); + item.setX(x); + item.setY(y); + + ItemSpawned itemSpawned = new ItemSpawned(this, item); + client.getCallbacks().post(itemSpawned); + + current = forward ? current.getNext() : current.getPrevious(); + + // Send spawn events for anything on this tile which is at the wrong location, which happens + // when the scene base changes + } while (current != head && (((RSItem) current).getX() != x || ((RSItem) current).getY() != y)); + ItemLayerChanged itemLayerChanged = new ItemLayerChanged(this); client.getCallbacks().post(itemLayerChanged); } @@ -405,7 +494,7 @@ public abstract class RSTileMixin implements RSTile Node node = layer.getBottom(); while (node instanceof Item) { - result.add((Item)node); + result.add((Item) node); node = node.getNext(); } return result; diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java index 2134363b54..5c64dd89ec 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSClient.java @@ -644,4 +644,8 @@ public interface RSClient extends RSGameEngine, Client @Import("oculusOrbNormalSpeed") @Override void setOculusOrbNormalSpeed(int state); + + RSItem getLastItemDespawn(); + + void setLastItemDespawn(RSItem lastItemDespawn); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSItem.java b/runescape-api/src/main/java/net/runelite/rs/api/RSItem.java index 9cd5604d70..4f451cf028 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSItem.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSItem.java @@ -25,6 +25,7 @@ package net.runelite.rs.api; import net.runelite.api.Item; +import net.runelite.api.Tile; import net.runelite.mapping.Import; public interface RSItem extends RSRenderable, Item @@ -42,4 +43,18 @@ public interface RSItem extends RSRenderable, Item @Import("quantity") void setQuantity(int quantity); + + int getX(); + + void setX(int x); + + int getY(); + + void setY(int y); + + /** + * Get the tile this item is on + * @return + */ + Tile getTile(); } diff --git a/runescape-api/src/main/java/net/runelite/rs/api/RSNode.java b/runescape-api/src/main/java/net/runelite/rs/api/RSNode.java index f279e2bda2..ab24b70dda 100644 --- a/runescape-api/src/main/java/net/runelite/rs/api/RSNode.java +++ b/runescape-api/src/main/java/net/runelite/rs/api/RSNode.java @@ -40,4 +40,12 @@ public interface RSNode extends Node @Import("previous") @Override RSNode getPrevious(); + + @Import("unlink") + void unlink(); + + /** + * Called when this node is unlinked + */ + void onUnlink(); }