diff --git a/runelite-api/src/main/java/net/runelite/api/events/ItemLayerChanged.java b/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java
similarity index 79%
rename from runelite-api/src/main/java/net/runelite/api/events/ItemLayerChanged.java
rename to runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java
index d730e5ac38..4e60837d9d 100644
--- a/runelite-api/src/main/java/net/runelite/api/events/ItemLayerChanged.java
+++ b/runelite-api/src/main/java/net/runelite/api/events/ItemDespawned.java
@@ -25,24 +25,16 @@
package net.runelite.api.events;
import lombok.Value;
+import net.runelite.api.Item;
import net.runelite.api.Tile;
/**
- * An event called when an item pile on a {@link Tile} is modified.
- *
- * Examples of when this event may trigger include:
- *
- * - Dropping an item
- * - Picking up an item
- * - A dropped item spawning
- * - Loading a region with dropped items
- *
+ * 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 ItemLayerChanged
+public class ItemDespawned
{
- /**
- * The affected tile.
- */
- private Tile tile;
+ 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-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java
index b30b95c74a..06a33a285c 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/agility/AgilityPlugin.java
@@ -35,8 +35,6 @@ import lombok.Getter;
import net.runelite.api.Client;
import net.runelite.api.Item;
import net.runelite.api.ItemID;
-import net.runelite.api.ItemLayer;
-import net.runelite.api.Node;
import net.runelite.api.Player;
import static net.runelite.api.Skill.AGILITY;
import net.runelite.api.Tile;
@@ -55,7 +53,8 @@ import net.runelite.api.events.GameTick;
import net.runelite.api.events.GroundObjectChanged;
import net.runelite.api.events.GroundObjectDespawned;
import net.runelite.api.events.GroundObjectSpawned;
-import net.runelite.api.events.ItemLayerChanged;
+import net.runelite.api.events.ItemDespawned;
+import net.runelite.api.events.ItemSpawned;
import net.runelite.api.events.WallObjectChanged;
import net.runelite.api.events.WallObjectDespawned;
import net.runelite.api.events.WallObjectSpawned;
@@ -182,8 +181,8 @@ public class AgilityPlugin extends Plugin
Courses course = Courses.getCourse(client.getLocalPlayer().getWorldLocation().getRegionID());
if (course == null
|| (course.getCourseEndWorldPoints().length == 0
- ? Math.abs(course.getLastObstacleXp() - skillGained) > 1
- : Arrays.stream(course.getCourseEndWorldPoints()).noneMatch(wp -> wp.equals(client.getLocalPlayer().getWorldLocation()))))
+ ? Math.abs(course.getLastObstacleXp() - skillGained) > 1
+ : Arrays.stream(course.getCourseEndWorldPoints()).noneMatch(wp -> wp.equals(client.getLocalPlayer().getWorldLocation()))))
{
return;
}
@@ -202,47 +201,31 @@ public class AgilityPlugin extends Plugin
}
@Subscribe
- public void onItemLayerChanged(ItemLayerChanged event)
+ public void onItemSpawned(ItemSpawned itemSpawned)
{
if (obstacles.isEmpty())
{
return;
}
- final Tile tile = event.getTile();
- final ItemLayer itemLayer = tile.getItemLayer();
- final boolean hasMark = tileHasMark(itemLayer);
+ final Item item = itemSpawned.getItem();
+ final Tile tile = itemSpawned.getTile();
- if (markOfGrace != null && tile.getWorldLocation().equals(markOfGrace.getWorldLocation()) && !hasMark)
- {
- markOfGrace = null;
- }
- else if (hasMark)
+ if (item.getId() == ItemID.MARK_OF_GRACE)
{
markOfGrace = tile;
}
}
- private boolean tileHasMark(ItemLayer itemLayer)
+ @Subscribe
+ public void onItemDespawned(ItemDespawned itemDespawned)
{
- if (itemLayer != null)
+ final Item item = itemDespawned.getItem();
+
+ if (item.getId() == ItemID.MARK_OF_GRACE)
{
- Node currentItem = itemLayer.getBottom();
-
- while (currentItem instanceof Item)
- {
- final Item item = (Item) currentItem;
-
- currentItem = currentItem.getNext();
-
- if (item.getId() == ItemID.MARK_OF_GRACE)
- {
- return true;
- }
- }
+ markOfGrace = null;
}
-
- return false;
}
@Subscribe
@@ -377,9 +360,9 @@ public class AgilityPlugin extends Plugin
}
if (Obstacles.COURSE_OBSTACLE_IDS.contains(newObject.getId()) ||
- Obstacles.SHORTCUT_OBSTACLE_IDS.contains(newObject.getId()) ||
- (Obstacles.TRAP_OBSTACLE_IDS.contains(newObject.getId())
- && Obstacles.TRAP_OBSTACLE_REGIONS.contains(newObject.getWorldLocation().getRegionID())))
+ Obstacles.SHORTCUT_OBSTACLE_IDS.contains(newObject.getId()) ||
+ (Obstacles.TRAP_OBSTACLE_IDS.contains(newObject.getId())
+ && Obstacles.TRAP_OBSTACLE_REGIONS.contains(newObject.getWorldLocation().getRegionID())))
{
obstacles.put(newObject, tile);
}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
index be06ab8163..67cf9b5192 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItem.java
@@ -44,6 +44,16 @@ class GroundItem
private int offset;
private boolean tradeable;
+ int getHaPrice()
+ {
+ return haPrice * quantity;
+ }
+
+ int getGePrice()
+ {
+ return gePrice * quantity;
+ }
+
@Value
static class GroundItemKey
{
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 20bf6ad373..b3006eeaf6 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
@@ -43,7 +43,6 @@ import net.runelite.api.Player;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
-import net.runelite.client.game.ItemManager;
import static net.runelite.client.plugins.grounditems.config.ItemHighlightMode.MENU;
import net.runelite.client.plugins.grounditems.config.PriceDisplayMode;
import net.runelite.client.ui.overlay.Overlay;
@@ -53,7 +52,6 @@ import net.runelite.client.ui.overlay.OverlayUtil;
import net.runelite.client.ui.overlay.components.BackgroundComponent;
import net.runelite.client.ui.overlay.components.TextComponent;
import net.runelite.client.util.StackFormatter;
-import net.runelite.http.api.item.ItemPrice;
public class GroundItemsOverlay extends Overlay
{
@@ -76,17 +74,15 @@ public class GroundItemsOverlay extends Overlay
private final BackgroundComponent backgroundComponent = new BackgroundComponent();
private final TextComponent textComponent = new TextComponent();
private final Map offsetMap = new HashMap<>();
- private final ItemManager itemManager;
@Inject
- private GroundItemsOverlay(Client client, GroundItemsPlugin plugin, GroundItemsConfig config, ItemManager itemManager)
+ private GroundItemsOverlay(Client client, GroundItemsPlugin plugin, GroundItemsConfig config)
{
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_SCENE);
this.client = client;
this.plugin = plugin;
this.config = config;
- this.itemManager = itemManager;
}
@Override
@@ -107,8 +103,6 @@ public class GroundItemsOverlay extends Overlay
return null;
}
- plugin.checkItems();
-
offsetMap.clear();
final LocalPoint localLocation = player.getLocalLocation();
final Point mousePos = client.getMouseCanvasPosition();
@@ -177,14 +171,6 @@ public class GroundItemsOverlay extends Overlay
continue;
}
- // Update GE price for item
- final ItemPrice itemPrice = itemManager.getItemPrice(item.getItemId());
-
- if (itemPrice != null && itemPrice.getPrice() > 0)
- {
- item.setGePrice(itemPrice.getPrice() * item.getQuantity());
- }
-
final Color highlighted = plugin.getHighlighted(item.getName(), item.getGePrice(), item.getHaPrice());
final Color hidden = plugin.getHidden(item.getName(), item.getGePrice(), item.getHaPrice(), item.isTradeable());
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
index abb8200340..850e6c307b 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java
@@ -29,13 +29,11 @@ import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
-import com.google.common.collect.Lists;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Provides;
import java.awt.Color;
import java.awt.Rectangle;
import static java.lang.Boolean.TRUE;
-import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -43,17 +41,12 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-import java.util.stream.Collector;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
import javax.inject.Inject;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
-import static net.runelite.api.Constants.SCENE_SIZE;
import net.runelite.api.GameState;
import net.runelite.api.Item;
import net.runelite.api.ItemComposition;
@@ -62,14 +55,14 @@ import net.runelite.api.ItemLayer;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.Node;
-import net.runelite.api.Player;
import net.runelite.api.Scene;
import net.runelite.api.Tile;
-import net.runelite.api.coords.LocalPoint;
import net.runelite.api.events.ConfigChanged;
import net.runelite.api.events.FocusChanged;
import net.runelite.api.events.GameStateChanged;
-import net.runelite.api.events.ItemLayerChanged;
+import net.runelite.api.events.ItemDespawned;
+import net.runelite.api.events.ItemQuantityChanged;
+import net.runelite.api.events.ItemSpawned;
import net.runelite.api.events.MenuEntryAdded;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager;
@@ -122,7 +115,6 @@ public class GroundItemsPlugin extends Plugin
private List hiddenItemList = new CopyOnWriteArrayList<>();
private List highlightedItemsList = new CopyOnWriteArrayList<>();
- private boolean dirty;
@Inject
private GroundItemInputListener inputListener;
@@ -150,23 +142,10 @@ public class GroundItemsPlugin extends Plugin
@Getter
private final Map collectedGroundItems = new LinkedHashMap<>();
- private final List groundItems = new ArrayList<>();
private final Map priceChecks = new LinkedHashMap<>();
private LoadingCache highlightedItems;
private LoadingCache hiddenItems;
- // Collects similar ground items
- private final Collector> groundItemMapCollector = Collectors
- .toMap
- ((item) -> new GroundItem.GroundItemKey(item.getItemId(), item.getLocation()), Function.identity(), (a, b) ->
- {
- b.setHaPrice(a.getHaPrice() + b.getHaPrice());
- b.setGePrice(a.getGePrice() + b.getGePrice());
- b.setQuantity(a.getQuantity() + b.getQuantity());
- return b;
- },
- () -> collectedGroundItems);
-
@Provides
GroundItemsConfig provideConfig(ConfigManager configManager)
{
@@ -188,13 +167,13 @@ public class GroundItemsPlugin extends Plugin
overlayManager.remove(overlay);
mouseManager.unregisterMouseListener(inputListener);
keyManager.unregisterKeyListener(inputListener);
- groundItems.clear();
highlightedItems.invalidateAll();
highlightedItems = null;
hiddenItems.invalidateAll();
hiddenItems = null;
hiddenItemList = null;
highlightedItemsList = null;
+ collectedGroundItems.clear();
}
@Subscribe
@@ -209,80 +188,68 @@ public class GroundItemsPlugin extends Plugin
@Subscribe
public void onGameStateChanged(final GameStateChanged event)
{
- if (event.getGameState() == GameState.LOGGED_IN)
+ if (event.getGameState() == GameState.LOADING)
{
- dirty = true;
+ collectedGroundItems.clear();
}
}
@Subscribe
- public void onItemLayerChanged(ItemLayerChanged event)
+ public void onItemSpawned(ItemSpawned itemSpawned)
{
- dirty = true;
+ Item item = itemSpawned.getItem();
+ Tile tile = itemSpawned.getTile();
+
+ GroundItem groundItem = buildGroundItem(tile, item);
+
+ GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation());
+ GroundItem existing = collectedGroundItems.putIfAbsent(groundItemKey, groundItem);
+ if (existing != null)
+ {
+ existing.setQuantity(existing.getQuantity() + groundItem.getQuantity());
+ }
}
- void checkItems()
+ @Subscribe
+ public void onItemDespawned(ItemDespawned itemDespawned)
{
- final Player player = client.getLocalPlayer();
+ Item item = itemDespawned.getItem();
+ Tile tile = itemDespawned.getTile();
- if (!dirty || player == null || client.getViewportWidget() == null)
+ GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation());
+ GroundItem groundItem = collectedGroundItems.get(groundItemKey);
+ if (groundItem == null)
{
return;
}
- dirty = false;
-
- final Scene scene = client.getScene();
- final Tile[][][] tiles = scene.getTiles();
- final int z = client.getPlane();
- final LocalPoint from = player.getLocalLocation();
-
- groundItems.clear();
-
- for (int x = 0; x < SCENE_SIZE; ++x)
+ if (groundItem.getQuantity() <= item.getQuantity())
{
- for (int y = 0; y < SCENE_SIZE; ++y)
- {
- Tile tile = tiles[z][x][y];
- if (tile == null)
- {
- continue;
- }
-
- ItemLayer itemLayer = tile.getItemLayer();
- if (itemLayer == null)
- {
- continue;
- }
-
- Node current = itemLayer.getBottom();
-
- // adds the items on the ground to the ArrayList to be drawn
- while (current instanceof Item)
- {
- final Item item = (Item) current;
-
- // Continue iteration
- current = current.getNext();
-
- // Build ground item
- final GroundItem groundItem = buildGroundItem(tile, item);
-
- if (groundItem != null)
- {
- groundItem.setHeight(itemLayer.getHeight());
- groundItems.add(groundItem);
- }
- }
- }
+ collectedGroundItems.remove(groundItemKey);
+ }
+ else
+ {
+ groundItem.setQuantity(groundItem.getQuantity() - item.getQuantity());
+ }
+ }
+
+ @Subscribe
+ public void onItemQuantityChanged(ItemQuantityChanged itemQuantityChanged)
+ {
+ Item item = itemQuantityChanged.getItem();
+ Tile tile = itemQuantityChanged.getTile();
+ int oldQuantity = itemQuantityChanged.getOldQuantity();
+ int newQuantity = itemQuantityChanged.getNewQuantity();
+
+ int diff = newQuantity - oldQuantity;
+ GroundItem.GroundItemKey groundItemKey = new GroundItem.GroundItemKey(item.getId(), tile.getWorldLocation());
+ GroundItem groundItem = collectedGroundItems.get(groundItemKey);
+ if (groundItem != null)
+ {
+ groundItem.setQuantity(groundItem.getQuantity() + diff);
}
-
- // Group ground items together and sort them properly
- collectedGroundItems.clear();
- Lists.reverse(groundItems).stream().collect(groundItemMapCollector);
}
- @Nullable
private GroundItem buildGroundItem(final Tile tile, final Item item)
{
// Collect the data for the item
@@ -297,7 +264,7 @@ public class GroundItemsPlugin extends Plugin
.itemId(realItemId)
.quantity(item.getQuantity())
.name(itemComposition.getName())
- .haPrice(alchPrice * item.getQuantity())
+ .haPrice(alchPrice)
.tradeable(itemComposition.isTradeable())
.build();
@@ -305,8 +272,16 @@ public class GroundItemsPlugin extends Plugin
// Update item price in case it is coins
if (realItemId == COINS)
{
- groundItem.setHaPrice(groundItem.getQuantity());
- groundItem.setGePrice(groundItem.getQuantity());
+ groundItem.setHaPrice(1);
+ groundItem.setGePrice(1);
+ }
+ else
+ {
+ final ItemPrice itemPrice = itemManager.getItemPrice(realItemId);
+ if (itemPrice != null)
+ {
+ groundItem.setGePrice(itemPrice.getPrice());
+ }
}
return groundItem;
@@ -330,8 +305,6 @@ public class GroundItemsPlugin extends Plugin
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(new WildcardMatchLoader(hiddenItemList));
- dirty = true;
-
// Cache colors
priceChecks.clear();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/mta/enchantment/EnchantmentRoom.java b/runelite-client/src/main/java/net/runelite/client/plugins/mta/enchantment/EnchantmentRoom.java
index b74c696b6d..a084b12606 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/mta/enchantment/EnchantmentRoom.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/mta/enchantment/EnchantmentRoom.java
@@ -33,13 +33,13 @@ import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.Item;
import net.runelite.api.ItemID;
-import net.runelite.api.ItemLayer;
import net.runelite.api.Player;
import net.runelite.api.Tile;
import net.runelite.api.coords.WorldPoint;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
-import net.runelite.api.events.ItemLayerChanged;
+import net.runelite.api.events.ItemDespawned;
+import net.runelite.api.events.ItemSpawned;
import net.runelite.client.plugins.mta.MTAConfig;
import net.runelite.client.plugins.mta.MTARoom;
@@ -61,12 +61,9 @@ public class EnchantmentRoom extends MTARoom
@Subscribe
public void onGameStateChanged(GameStateChanged gameStateChanged)
{
- if (gameStateChanged.getGameState() == GameState.LOGGED_IN)
+ if (gameStateChanged.getGameState() == GameState.LOADING)
{
- if (!inside())
- {
- dragonstones.clear();
- }
+ dragonstones.clear();
}
}
@@ -103,42 +100,30 @@ public class EnchantmentRoom extends MTARoom
}
@Subscribe
- public void onItemLayerChanged(ItemLayerChanged event)
+ public void onItemSpawned(ItemSpawned itemSpawned)
{
- if (!inside())
+ final Item item = itemSpawned.getItem();
+ final Tile tile = itemSpawned.getTile();
+
+ if (item.getId() == ItemID.DRAGONSTONE_6903)
{
- return;
+ WorldPoint location = tile.getWorldLocation();
+ log.debug("Adding dragonstone at {}", location);
+ dragonstones.add(location);
}
+ }
- Tile changed = event.getTile();
- ItemLayer itemLayer = changed.getItemLayer();
- WorldPoint worldPoint = changed.getWorldLocation();
+ @Subscribe
+ public void onItemDespawned(ItemDespawned itemDespawned)
+ {
+ final Item item = itemDespawned.getItem();
+ final Tile tile = itemDespawned.getTile();
- List- groundItems = changed.getGroundItems();
- if (groundItems == null)
+ if (item.getId() == ItemID.DRAGONSTONE_6903)
{
- boolean removed = dragonstones.remove(worldPoint);
- if (removed)
- {
- log.debug("Removed dragonstone at {}", worldPoint);
- }
- return;
- }
-
- for (Item item : changed.getGroundItems())
- {
- if (item.getId() == ItemID.DRAGONSTONE_6903)
- {
- log.debug("Adding dragonstone at {}", worldPoint);
- dragonstones.add(worldPoint);
- return;
- }
- }
-
- boolean removed = dragonstones.remove(worldPoint);
- if (removed)
- {
- log.debug("Removed dragonstone at {}", worldPoint);
+ WorldPoint location = tile.getWorldLocation();
+ log.debug("Removed dragonstone at {}", location);
+ dragonstones.remove(location);
}
}
diff --git a/runelite-client/src/main/java/net/runelite/client/util/SceneTileManager.java b/runelite-client/src/main/java/net/runelite/client/util/SceneTileManager.java
index 4027c189cd..a2ee3288b8 100644
--- a/runelite-client/src/main/java/net/runelite/client/util/SceneTileManager.java
+++ b/runelite-client/src/main/java/net/runelite/client/util/SceneTileManager.java
@@ -35,11 +35,14 @@ import javax.inject.Singleton;
import net.runelite.api.Client;
import net.runelite.api.Constants;
import net.runelite.api.GameState;
+import net.runelite.api.Item;
+import net.runelite.api.Node;
import net.runelite.api.Scene;
import net.runelite.api.Tile;
import net.runelite.api.events.DecorativeObjectSpawned;
import net.runelite.api.events.GameObjectSpawned;
import net.runelite.api.events.GroundObjectSpawned;
+import net.runelite.api.events.ItemSpawned;
import net.runelite.api.events.WallObjectSpawned;
@Singleton
@@ -56,6 +59,7 @@ public class SceneTileManager
/**
* Iterates over each tile in the scene if player is logged in
+ *
* @param consumer consumer accepting tile as parameter
*/
public void forEachTile(Consumer consumer)
@@ -91,6 +95,7 @@ public class SceneTileManager
/**
* Simulate object spawns for EventBus subscriber
+ *
* @param subscriber EventBus subscriber
*/
public void simulateObjectSpawns(Object subscriber)
@@ -132,6 +137,21 @@ public class SceneTileManager
objectSpawned.setGameObject(object);
eventBus.post(objectSpawned);
});
+
+ Optional.ofNullable(tile.getItemLayer()).ifPresent(itemLayer ->
+ {
+ Node current = itemLayer.getBottom();
+
+ while (current instanceof Item)
+ {
+ final Item item = (Item) current;
+
+ current = current.getNext();
+
+ final ItemSpawned itemSpawned = new ItemSpawned(tile, item);
+ eventBus.post(itemSpawned);
+ }
+ });
});
eventBus.unregister(subscriber);
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..a9056cbf1e 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,8 @@ 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.ItemLayerChanged;
+import net.runelite.api.events.ItemDespawned;
+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 +60,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,14 +275,94 @@ 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;
}
- ItemLayerChanged itemLayerChanged = new ItemLayerChanged(this);
- client.getCallbacks().post(itemLayerChanged);
+ 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));
}
@Inject
@@ -405,7 +490,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();
}