From f6b44a502e3071275f845d5d0413ff4e4b8f6c5e Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 29 Jul 2018 21:26:55 +0200 Subject: [PATCH] Add LootManager Add loot manager that will manage item drops received and stream them as item drop events for correct entity with correct items. Signed-off-by: Tomas Slusny --- .../java/net/runelite/client/RuneLite.java | 5 + .../client/events/NpcLootReceived.java | 37 +++ .../client/events/PlayerLootReceived.java | 37 +++ .../net/runelite/client/game/ItemStack.java | 34 ++ .../net/runelite/client/game/LootManager.java | 300 ++++++++++++++++++ 5 files changed, 413 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/events/NpcLootReceived.java create mode 100644 runelite-client/src/main/java/net/runelite/client/events/PlayerLootReceived.java create mode 100644 runelite-client/src/main/java/net/runelite/client/game/ItemStack.java create mode 100644 runelite-client/src/main/java/net/runelite/client/game/LootManager.java diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 930c938019..bbc0039f98 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -52,6 +52,7 @@ import net.runelite.client.config.ConfigManager; import net.runelite.client.discord.DiscordService; import net.runelite.client.game.ClanManager; import net.runelite.client.game.ItemManager; +import net.runelite.client.game.LootManager; import net.runelite.client.menus.MenuManager; import net.runelite.client.plugins.PluginManager; import net.runelite.client.rs.ClientUpdateCheckMode; @@ -137,6 +138,9 @@ public class RuneLite @Inject private Provider worldMapOverlay; + @Inject + private Provider lootManager; + @Inject @Nullable private Client client; @@ -273,6 +277,7 @@ public class RuneLite eventBus.register(menuManager.get()); eventBus.register(chatMessageManager.get()); eventBus.register(commandManager.get()); + eventBus.register(lootManager.get()); // Add core overlays WidgetOverlay.createOverlays(client).forEach(overlayManager::add); diff --git a/runelite-client/src/main/java/net/runelite/client/events/NpcLootReceived.java b/runelite-client/src/main/java/net/runelite/client/events/NpcLootReceived.java new file mode 100644 index 0000000000..e3ef8a03da --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/NpcLootReceived.java @@ -0,0 +1,37 @@ +/* + * 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.client.events; + +import java.util.Collection; +import lombok.Value; +import net.runelite.api.NPC; +import net.runelite.client.game.ItemStack; + +@Value +public class NpcLootReceived +{ + private final NPC npc; + private final Collection items; +} diff --git a/runelite-client/src/main/java/net/runelite/client/events/PlayerLootReceived.java b/runelite-client/src/main/java/net/runelite/client/events/PlayerLootReceived.java new file mode 100644 index 0000000000..56eb722a83 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/events/PlayerLootReceived.java @@ -0,0 +1,37 @@ +/* + * 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.client.events; + +import java.util.Collection; +import lombok.Value; +import net.runelite.api.Player; +import net.runelite.client.game.ItemStack; + +@Value +public class PlayerLootReceived +{ + private final Player player; + private final Collection items; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemStack.java b/runelite-client/src/main/java/net/runelite/client/game/ItemStack.java new file mode 100644 index 0000000000..bc455e2cf8 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemStack.java @@ -0,0 +1,34 @@ +/* + * 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.client.game; + +import lombok.Value; + +@Value +public class ItemStack +{ + private final int id; + private final int quantity; +} diff --git a/runelite-client/src/main/java/net/runelite/client/game/LootManager.java b/runelite-client/src/main/java/net/runelite/client/game/LootManager.java new file mode 100644 index 0000000000..9443c3eff9 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/game/LootManager.java @@ -0,0 +1,300 @@ +/* + * 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.client.game; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.AnimationID; +import net.runelite.api.Client; +import net.runelite.api.Item; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.NpcID; +import net.runelite.api.Player; +import net.runelite.api.Tile; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.ItemQuantityChanged; +import net.runelite.api.events.ItemSpawned; +import net.runelite.api.events.NpcDespawned; +import net.runelite.api.events.PlayerDespawned; +import net.runelite.client.events.NpcLootReceived; +import net.runelite.client.events.PlayerLootReceived; + +@Singleton +@Slf4j +public class LootManager +{ + private static final Map NPC_DEATH_ANIMATIONS = ImmutableMap.of( + NpcID.CAVE_KRAKEN, AnimationID.CAVE_KRAKEN_DEATH, + NpcID.AIR_WIZARD, AnimationID.WIZARD_DEATH, + NpcID.WATER_WIZARD, AnimationID.WIZARD_DEATH, + NpcID.EARTH_WIZARD, AnimationID.WIZARD_DEATH, + NpcID.FIRE_WIZARD, AnimationID.WIZARD_DEATH + ); + + private final EventBus eventBus; + private final Client client; + private final ListMultimap itemSpawns = ArrayListMultimap.create(); + private WorldPoint playerLocationLastTick; + private WorldPoint krakenPlayerLocation; + + @Inject + private LootManager(EventBus eventBus, Client client) + { + this.eventBus = eventBus; + this.client = client; + } + + @Subscribe + public void onNpcDespawned(NpcDespawned npcDespawned) + { + final NPC npc = npcDespawned.getNpc(); + if (!npc.isDead()) + { + int id = npc.getId(); + switch (id) + { + case NpcID.GARGOYLE: + case NpcID.GARGOYLE_413: + case NpcID.GARGOYLE_1543: + case NpcID.MARBLE_GARGOYLE: + case NpcID.MARBLE_GARGOYLE_7408: + + case NpcID.ROCKSLUG: + case NpcID.ROCKSLUG_422: + case NpcID.GIANT_ROCKSLUG: + + case NpcID.SMALL_LIZARD: + case NpcID.SMALL_LIZARD_463: + case NpcID.DESERT_LIZARD: + case NpcID.DESERT_LIZARD_460: + case NpcID.DESERT_LIZARD_461: + case NpcID.LIZARD: + + case NpcID.ZYGOMITE: + case NpcID.ZYGOMITE_474: + case NpcID.ANCIENT_ZYGOMITE: + + // these monsters die with >0 hp, so we just look for coincident + // item spawn with despawn + break; + default: + return; + } + } + + processNpcLoot(npc); + } + + @Subscribe + public void onPlayerDespawned(PlayerDespawned playerDespawned) + { + final Player player = playerDespawned.getPlayer(); + final LocalPoint location = LocalPoint.fromWorld(client, player.getWorldLocation()); + if (location == null) + { + return; + } + + final int x = location.getSceneX(); + final int y = location.getSceneY(); + final int packed = x << 8 | y; + final Collection items = itemSpawns.get(packed); + + if (items.isEmpty()) + { + return; + } + + eventBus.post(new PlayerLootReceived(player, items)); + } + + @Subscribe + public void onItemSpawned(ItemSpawned itemSpawned) + { + final Item item = itemSpawned.getItem(); + final Tile tile = itemSpawned.getTile(); + final LocalPoint location = tile.getLocalLocation(); + final int packed = location.getSceneX() << 8 | location.getSceneY(); + itemSpawns.put(packed, new ItemStack(item.getId(), item.getQuantity())); + log.debug("Item spawn {} location {},{}", item.getId(), location); + } + + @Subscribe + public void onItemQuantityChanged(ItemQuantityChanged itemQuantityChanged) + { + final Item item = itemQuantityChanged.getItem(); + final Tile tile = itemQuantityChanged.getTile(); + final LocalPoint location = tile.getLocalLocation(); + final int packed = location.getSceneX() << 8 | location.getSceneY(); + final int diff = itemQuantityChanged.getNewQuantity() - itemQuantityChanged.getOldQuantity(); + + if (diff <= 0) + { + return; + } + + itemSpawns.put(packed, new ItemStack(item.getId(), diff)); + } + + @Subscribe + public void onAnimationChanged(AnimationChanged e) + { + if (!(e.getActor() instanceof NPC)) + { + return; + } + + final NPC npc = (NPC) e.getActor(); + int id = npc.getId(); + + // We only care about certain NPCs + final Integer deathAnim = NPC_DEATH_ANIMATIONS.get(id); + + // Current animation is death animation? + if (deathAnim != null && deathAnim == npc.getAnimation()) + { + if (id == NpcID.CAVE_KRAKEN) + { + // Big Kraken drops loot wherever player is standing when animation starts. + krakenPlayerLocation = client.getLocalPlayer().getWorldLocation(); + } + else + { + // These NPCs drop loot on death animation, which is right now. + processNpcLoot(npc); + } + } + } + + @Subscribe + public void onGameTick(GameTick gameTick) + { + playerLocationLastTick = client.getLocalPlayer().getWorldLocation(); + itemSpawns.clear(); + } + + private void processNpcLoot(NPC npc) + { + final LocalPoint location = LocalPoint.fromWorld(client, getDropLocation(npc, npc.getWorldLocation())); + if (location == null) + { + return; + } + + final int x = location.getSceneX(); + final int y = location.getSceneY(); + final int size = npc.getComposition().getSize(); + + // Some NPCs drop items onto multiple tiles + final List allItems = new ArrayList<>(); + for (int i = 0; i < size; ++i) + { + for (int j = 0; j < size; ++j) + { + final int packed = (x + i) << 8 | (y + j); + final Collection items = itemSpawns.get(packed); + allItems.addAll(items); + } + } + + if (allItems.isEmpty()) + { + return; + } + + eventBus.post(new NpcLootReceived(npc, allItems)); + } + + private WorldPoint getDropLocation(NPC npc, WorldPoint worldLocation) + { + switch (npc.getId()) + { + case NpcID.KRAKEN: + case NpcID.KRAKEN_6640: + case NpcID.KRAKEN_6656: + worldLocation = playerLocationLastTick; + break; + case NpcID.CAVE_KRAKEN: + worldLocation = krakenPlayerLocation; + break; + case NpcID.ZULRAH: // Green + case NpcID.ZULRAH_2043: // Red + case NpcID.ZULRAH_2044: // Blue + for (Map.Entry entry : itemSpawns.entries()) + { + if (entry.getValue().getId() == ItemID.ZULRAHS_SCALES) + { + int packed = entry.getKey(); + int unpackedX = packed >> 8; + int unpackedY = packed & 0xFF; + worldLocation = new WorldPoint(unpackedX, unpackedY, worldLocation.getPlane()); + break; + } + } + break; + case NpcID.VORKATH: + case NpcID.VORKATH_8058: + case NpcID.VORKATH_8059: + case NpcID.VORKATH_8060: + case NpcID.VORKATH_8061: + int x = worldLocation.getX() + 3; + int y = worldLocation.getY() + 3; + if (playerLocationLastTick.getX() < x) + { + x -= 4; + } + else if (playerLocationLastTick.getX() > x) + { + x += 4; + } + if (playerLocationLastTick.getY() < y) + { + y -= 4; + } + else if (playerLocationLastTick.getY() > y) + { + y += 4; + } + worldLocation = new WorldPoint(x, y, worldLocation.getPlane()); + break; + } + + return worldLocation; + } +}