Add item spawn events

This commit is contained in:
Adam
2018-07-15 14:50:11 -04:00
parent ae4811c2a3
commit 1bac71f840
10 changed files with 430 additions and 4 deletions

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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;
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* 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();
}
}

View File

@@ -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;

View File

@@ -644,4 +644,8 @@ public interface RSClient extends RSGameEngine, Client
@Import("oculusOrbNormalSpeed")
@Override
void setOculusOrbNormalSpeed(int state);
RSItem getLastItemDespawn();
void setLastItemDespawn(RSItem lastItemDespawn);
}

View File

@@ -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();
}

View File

@@ -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();
}