@@ -157,4 +157,13 @@ public final class AnimationID
|
|||||||
|
|
||||||
// Battlestaff Crafting
|
// Battlestaff Crafting
|
||||||
public static final int CRAFTING_BATTLESTAVES = 7531;
|
public static final int CRAFTING_BATTLESTAVES = 7531;
|
||||||
|
|
||||||
|
// Death Animations
|
||||||
|
public static final int CAVE_KRAKEN_DEATH = 3993;
|
||||||
|
public static final int WIZARD_DEATH = 2553;
|
||||||
|
public static final int GARGOYLE_DEATH = 1520;
|
||||||
|
public static final int MARBLE_GARGOYLE_DEATH = 7813;
|
||||||
|
public static final int LIZARD_DEATH = 2778;
|
||||||
|
public static final int ROCKSLUG_DEATH = 1568;
|
||||||
|
public static final int ZYGOMITE_DEATH = 3327;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,15 @@ public enum InventoryID
|
|||||||
/**
|
/**
|
||||||
* Barrows reward chest inventory.
|
* Barrows reward chest inventory.
|
||||||
*/
|
*/
|
||||||
BARROWS_REWARD(141);
|
BARROWS_REWARD(141),
|
||||||
|
/**
|
||||||
|
* Chambers of Xeric chest inventory.
|
||||||
|
*/
|
||||||
|
CHAMBERS_OF_XERIC_CHEST(581),
|
||||||
|
/**
|
||||||
|
* Theater of Blood reward chest inventory (Raids 2)
|
||||||
|
*/
|
||||||
|
THEATRE_OF_BLOOD_CHEST(10);
|
||||||
|
|
||||||
private final int id;
|
private final int id;
|
||||||
|
|
||||||
|
|||||||
@@ -259,6 +259,11 @@ public enum Varbits
|
|||||||
PERSONAL_POINTS(5422),
|
PERSONAL_POINTS(5422),
|
||||||
RAID_PARTY_SIZE(5424),
|
RAID_PARTY_SIZE(5424),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theatre of Blood 1=In Party, 2=Inside/Spectator, 3=Dead Spectating
|
||||||
|
*/
|
||||||
|
THEATRE_OF_BLOOD(6440),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nightmare Zone
|
* Nightmare Zone
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ public class WidgetID
|
|||||||
public static final int VARROCK_MUSEUM_QUIZ_GROUP_ID = 533;
|
public static final int VARROCK_MUSEUM_QUIZ_GROUP_ID = 533;
|
||||||
public static final int KILL_LOGS_GROUP_ID = 549;
|
public static final int KILL_LOGS_GROUP_ID = 549;
|
||||||
public static final int DIARY_QUEST_GROUP_ID = 275;
|
public static final int DIARY_QUEST_GROUP_ID = 275;
|
||||||
|
public static final int THEATRE_OF_BLOOD_GROUP_ID = 23;
|
||||||
|
|
||||||
static class WorldMap
|
static class WorldMap
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import net.runelite.client.config.ConfigManager;
|
|||||||
import net.runelite.client.discord.DiscordService;
|
import net.runelite.client.discord.DiscordService;
|
||||||
import net.runelite.client.game.ClanManager;
|
import net.runelite.client.game.ClanManager;
|
||||||
import net.runelite.client.game.ItemManager;
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.game.LootManager;
|
||||||
import net.runelite.client.menus.MenuManager;
|
import net.runelite.client.menus.MenuManager;
|
||||||
import net.runelite.client.plugins.PluginManager;
|
import net.runelite.client.plugins.PluginManager;
|
||||||
import net.runelite.client.rs.ClientUpdateCheckMode;
|
import net.runelite.client.rs.ClientUpdateCheckMode;
|
||||||
@@ -137,6 +138,9 @@ public class RuneLite
|
|||||||
@Inject
|
@Inject
|
||||||
private Provider<WorldMapOverlay> worldMapOverlay;
|
private Provider<WorldMapOverlay> worldMapOverlay;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Provider<LootManager> lootManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Nullable
|
@Nullable
|
||||||
private Client client;
|
private Client client;
|
||||||
@@ -273,6 +277,7 @@ public class RuneLite
|
|||||||
eventBus.register(menuManager.get());
|
eventBus.register(menuManager.get());
|
||||||
eventBus.register(chatMessageManager.get());
|
eventBus.register(chatMessageManager.get());
|
||||||
eventBus.register(commandManager.get());
|
eventBus.register(commandManager.get());
|
||||||
|
eventBus.register(lootManager.get());
|
||||||
|
|
||||||
// Add core overlays
|
// Add core overlays
|
||||||
WidgetOverlay.createOverlays(client).forEach(overlayManager::add);
|
WidgetOverlay.createOverlays(client).forEach(overlayManager::add);
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.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<ItemStack> items;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.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<ItemStack> items;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.client.game;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public class ItemStack
|
||||||
|
{
|
||||||
|
private final int id;
|
||||||
|
private final int quantity;
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
/*
|
||||||
|
* 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.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<Integer, Integer> 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<Integer, ItemStack> 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<ItemStack> 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<ItemStack> 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<ItemStack> 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<Integer, ItemStack> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||||
|
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.loottracker;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.runelite.api.ItemID;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.game.ItemStack;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
import net.runelite.client.util.StackFormatter;
|
||||||
|
import net.runelite.http.api.item.ItemPrice;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
class LootTrackerBox extends JPanel
|
||||||
|
{
|
||||||
|
private static final int ITEMS_PER_ROW = 5;
|
||||||
|
private final long totalPrice;
|
||||||
|
|
||||||
|
LootTrackerBox(final ItemManager itemManager, final String title, final String subTitle, final ItemStack[] items)
|
||||||
|
{
|
||||||
|
setLayout(new BorderLayout(0, 1));
|
||||||
|
setBorder(new EmptyBorder(5, 0, 0, 0));
|
||||||
|
|
||||||
|
final JPanel logTitle = new JPanel(new BorderLayout(5, 0));
|
||||||
|
logTitle.setBorder(new EmptyBorder(7, 7, 7, 7));
|
||||||
|
logTitle.setBackground(ColorScheme.DARKER_GRAY_COLOR.darker());
|
||||||
|
|
||||||
|
final JLabel titleLabel = new JLabel(title);
|
||||||
|
titleLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
titleLabel.setForeground(Color.WHITE);
|
||||||
|
|
||||||
|
logTitle.add(titleLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
// If we have subtitle, add it
|
||||||
|
if (!Strings.isNullOrEmpty(subTitle))
|
||||||
|
{
|
||||||
|
final JLabel subTitleLabel = new JLabel(subTitle);
|
||||||
|
subTitleLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
subTitleLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
|
||||||
|
logTitle.add(subTitleLabel, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPrice = calculatePrice(itemManager, items);
|
||||||
|
|
||||||
|
if (totalPrice > 0)
|
||||||
|
{
|
||||||
|
final JLabel priceLabel = new JLabel(StackFormatter.quantityToStackSize(totalPrice) + " gp");
|
||||||
|
priceLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
priceLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR);
|
||||||
|
logTitle.add(priceLabel, BorderLayout.EAST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates how many rows need to be display to fit all items
|
||||||
|
final int rowSize = ((items.length % ITEMS_PER_ROW == 0) ? 0 : 1) + items.length / ITEMS_PER_ROW;
|
||||||
|
final JPanel itemContainer = new JPanel(new GridLayout(rowSize, ITEMS_PER_ROW, 1, 1));
|
||||||
|
|
||||||
|
for (int i = 0; i < rowSize * ITEMS_PER_ROW; i++)
|
||||||
|
{
|
||||||
|
final JPanel slotContainer = new JPanel();
|
||||||
|
slotContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
|
||||||
|
if (i < items.length)
|
||||||
|
{
|
||||||
|
final ItemStack item = items[i];
|
||||||
|
final JLabel imageLabel = new JLabel();
|
||||||
|
imageLabel.setToolTipText(buildToolTip(itemManager, item));
|
||||||
|
imageLabel.setVerticalAlignment(SwingConstants.CENTER);
|
||||||
|
imageLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
itemManager.getImage(item.getId(), item.getQuantity(), item.getQuantity() > 1).addTo(imageLabel);
|
||||||
|
slotContainer.add(imageLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
itemContainer.add(slotContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(logTitle, BorderLayout.NORTH);
|
||||||
|
add(itemContainer, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildToolTip(ItemManager itemManager, ItemStack item)
|
||||||
|
{
|
||||||
|
final String name = itemManager.getItemComposition(item.getId()).getName();
|
||||||
|
final int quantity = item.getQuantity();
|
||||||
|
final long price = calculatePrice(itemManager, new ItemStack[]{item});
|
||||||
|
|
||||||
|
return name + " x " + quantity + " (" + price + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long calculatePrice(final ItemManager itemManager, final ItemStack[] itemStacks)
|
||||||
|
{
|
||||||
|
long total = 0;
|
||||||
|
for (ItemStack itemStack : itemStacks)
|
||||||
|
{
|
||||||
|
ItemPrice itemPrice = itemManager.getItemPrice(itemStack.getId());
|
||||||
|
if (itemPrice != null)
|
||||||
|
{
|
||||||
|
total += (long) itemPrice.getPrice() * itemStack.getQuantity();
|
||||||
|
}
|
||||||
|
else if (itemStack.getId() == ItemID.COINS_995)
|
||||||
|
{
|
||||||
|
total += itemStack.getQuantity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||||
|
* Copyright (c) 2018, Tomas Slusny <slusnucky@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package net.runelite.client.plugins.loottracker;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import javax.swing.BoxLayout;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JPopupMenu;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.game.ItemStack;
|
||||||
|
import net.runelite.client.ui.ColorScheme;
|
||||||
|
import net.runelite.client.ui.FontManager;
|
||||||
|
import net.runelite.client.ui.PluginPanel;
|
||||||
|
import net.runelite.client.ui.components.PluginErrorPanel;
|
||||||
|
import net.runelite.client.util.ColorUtil;
|
||||||
|
import net.runelite.client.util.StackFormatter;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class LootTrackerPanel extends PluginPanel
|
||||||
|
{
|
||||||
|
private static final String HTML_LABEL_TEMPLATE =
|
||||||
|
"<html><body style='color:%s'>%s<span style='color:white'>%s</span></body></html>";
|
||||||
|
|
||||||
|
// When there is no loot, display this
|
||||||
|
private final PluginErrorPanel errorPanel = new PluginErrorPanel();
|
||||||
|
|
||||||
|
// Handle loot logs
|
||||||
|
private final JPanel logsContainer = new JPanel();
|
||||||
|
|
||||||
|
// Handle overall session data
|
||||||
|
private final JPanel overallPanel = new JPanel();
|
||||||
|
private final JLabel overallKillsLabel = new JLabel();
|
||||||
|
private final JLabel overallGpLabel = new JLabel();
|
||||||
|
private final JLabel overallIcon = new JLabel();
|
||||||
|
private final ItemManager itemManager;
|
||||||
|
private int overallKills;
|
||||||
|
private int overallGp;
|
||||||
|
|
||||||
|
LootTrackerPanel(final ItemManager itemManager)
|
||||||
|
{
|
||||||
|
this.itemManager = itemManager;
|
||||||
|
setBorder(new EmptyBorder(6, 6, 6, 6));
|
||||||
|
setBackground(ColorScheme.DARK_GRAY_COLOR);
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
// Create layout panel for wrapping
|
||||||
|
final JPanel layoutPanel = new JPanel();
|
||||||
|
layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS));
|
||||||
|
add(layoutPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Create panel that will contain overall data
|
||||||
|
overallPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||||
|
overallPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
overallPanel.setLayout(new BorderLayout());
|
||||||
|
overallPanel.setVisible(false);
|
||||||
|
|
||||||
|
// Add icon and contents
|
||||||
|
final JPanel overallInfo = new JPanel();
|
||||||
|
overallInfo.setBackground(ColorScheme.DARKER_GRAY_COLOR);
|
||||||
|
overallInfo.setLayout(new GridLayout(2, 1));
|
||||||
|
overallInfo.setBorder(new EmptyBorder(0, 10, 0, 0));
|
||||||
|
overallKillsLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
overallGpLabel.setFont(FontManager.getRunescapeSmallFont());
|
||||||
|
overallInfo.add(overallKillsLabel);
|
||||||
|
overallInfo.add(overallGpLabel);
|
||||||
|
overallPanel.add(overallIcon, BorderLayout.WEST);
|
||||||
|
overallPanel.add(overallInfo, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Create reset all menu
|
||||||
|
final JMenuItem reset = new JMenuItem("Reset All");
|
||||||
|
reset.addActionListener(e ->
|
||||||
|
{
|
||||||
|
overallKills = 0;
|
||||||
|
overallGp = 0;
|
||||||
|
updateOverall();
|
||||||
|
logsContainer.removeAll();
|
||||||
|
logsContainer.repaint();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create popup menu
|
||||||
|
final JPopupMenu popupMenu = new JPopupMenu();
|
||||||
|
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
|
popupMenu.add(reset);
|
||||||
|
overallPanel.setComponentPopupMenu(popupMenu);
|
||||||
|
|
||||||
|
// Create loot logs wrapper
|
||||||
|
logsContainer.setLayout(new BoxLayout(logsContainer, BoxLayout.Y_AXIS));
|
||||||
|
layoutPanel.add(overallPanel);
|
||||||
|
layoutPanel.add(logsContainer);
|
||||||
|
|
||||||
|
// Add error pane
|
||||||
|
errorPanel.setContent("Loot trackers", "You have not killed anything yet.");
|
||||||
|
add(errorPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadHeaderIcon(BufferedImage img)
|
||||||
|
{
|
||||||
|
overallIcon.setIcon(new ImageIcon(img));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String htmlLabel(String key, long value)
|
||||||
|
{
|
||||||
|
final String valueStr = StackFormatter.quantityToStackSize(value);
|
||||||
|
return String.format(HTML_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR), key, valueStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addLog(final String eventName, final int actorLevel, ItemStack[] items)
|
||||||
|
{
|
||||||
|
// Remove error and show overall
|
||||||
|
remove(errorPanel);
|
||||||
|
overallPanel.setVisible(true);
|
||||||
|
|
||||||
|
// Create box
|
||||||
|
final String subTitle = actorLevel > -1 ? "(lvl-" + actorLevel + ")" : "";
|
||||||
|
final LootTrackerBox box = new LootTrackerBox(itemManager, eventName, subTitle, items);
|
||||||
|
logsContainer.add(box, 0);
|
||||||
|
logsContainer.repaint();
|
||||||
|
|
||||||
|
// Update overall
|
||||||
|
overallGp += box.getTotalPrice();
|
||||||
|
overallKills += 1;
|
||||||
|
updateOverall();
|
||||||
|
|
||||||
|
// Create reset menu
|
||||||
|
final JMenuItem reset = new JMenuItem("Reset");
|
||||||
|
reset.addActionListener(e ->
|
||||||
|
{
|
||||||
|
overallGp -= box.getTotalPrice();
|
||||||
|
overallKills -= 1;
|
||||||
|
updateOverall();
|
||||||
|
logsContainer.remove(box);
|
||||||
|
logsContainer.repaint();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create popup menu
|
||||||
|
final JPopupMenu popupMenu = new JPopupMenu();
|
||||||
|
popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
|
popupMenu.add(reset);
|
||||||
|
box.setComponentPopupMenu(popupMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOverall()
|
||||||
|
{
|
||||||
|
overallKillsLabel.setText(htmlLabel("Total count: ", overallKills));
|
||||||
|
overallGpLabel.setText(htmlLabel("Total value: ", overallGp));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Psikoi <https://github.com/psikoi>
|
||||||
|
* 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.client.plugins.loottracker;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.runelite.api.ChatMessageType;
|
||||||
|
import net.runelite.api.Client;
|
||||||
|
import net.runelite.api.InventoryID;
|
||||||
|
import net.runelite.api.ItemContainer;
|
||||||
|
import net.runelite.api.NPC;
|
||||||
|
import net.runelite.api.Player;
|
||||||
|
import net.runelite.api.SpriteID;
|
||||||
|
import net.runelite.api.events.ChatMessage;
|
||||||
|
import net.runelite.api.events.WidgetLoaded;
|
||||||
|
import net.runelite.api.widgets.WidgetID;
|
||||||
|
import net.runelite.client.events.NpcLootReceived;
|
||||||
|
import net.runelite.client.events.PlayerLootReceived;
|
||||||
|
import net.runelite.client.game.ItemManager;
|
||||||
|
import net.runelite.client.game.ItemStack;
|
||||||
|
import net.runelite.client.game.SpriteManager;
|
||||||
|
import net.runelite.client.plugins.Plugin;
|
||||||
|
import net.runelite.client.plugins.PluginDescriptor;
|
||||||
|
import net.runelite.client.ui.ClientToolbar;
|
||||||
|
import net.runelite.client.ui.NavigationButton;
|
||||||
|
import net.runelite.client.util.Text;
|
||||||
|
|
||||||
|
@PluginDescriptor(
|
||||||
|
name = "Loot Tracker",
|
||||||
|
description = "Tracks loot from monsters and minigames",
|
||||||
|
tags = {"drops"},
|
||||||
|
enabledByDefault = false
|
||||||
|
)
|
||||||
|
@Slf4j
|
||||||
|
public class LootTrackerPlugin extends Plugin
|
||||||
|
{
|
||||||
|
// Activity/Event loot handling
|
||||||
|
private static final Pattern CLUE_SCROLL_PATTERN = Pattern.compile("You have completed [0-9]+ ([a-z]+) Treasure Trails.");
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ClientToolbar clientToolbar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ItemManager itemManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private SpriteManager spriteManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
private LootTrackerPanel panel;
|
||||||
|
private NavigationButton navButton;
|
||||||
|
private String eventType;
|
||||||
|
|
||||||
|
private static Collection<ItemStack> stack(Collection<ItemStack> items)
|
||||||
|
{
|
||||||
|
final List<ItemStack> list = new ArrayList<>();
|
||||||
|
|
||||||
|
for (final ItemStack item : items)
|
||||||
|
{
|
||||||
|
int quantity = 0;
|
||||||
|
for (final ItemStack i : list)
|
||||||
|
{
|
||||||
|
if (i.getId() == item.getId())
|
||||||
|
{
|
||||||
|
quantity = i.getQuantity();
|
||||||
|
list.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (quantity > 0)
|
||||||
|
{
|
||||||
|
list.add(new ItemStack(item.getId(), item.getQuantity() + quantity));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startUp() throws Exception
|
||||||
|
{
|
||||||
|
panel = new LootTrackerPanel(itemManager);
|
||||||
|
spriteManager.getSpriteAsync(SpriteID.UNUSED_TAB_INVENTORY, 0, panel::loadHeaderIcon);
|
||||||
|
|
||||||
|
final BufferedImage icon;
|
||||||
|
synchronized (ImageIO.class)
|
||||||
|
{
|
||||||
|
icon = ImageIO.read(LootTrackerPanel.class.getResourceAsStream("panel_icon.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
navButton = NavigationButton.builder()
|
||||||
|
.tooltip("Loot Tracker")
|
||||||
|
.icon(icon)
|
||||||
|
.priority(5)
|
||||||
|
.panel(panel)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
clientToolbar.addNavigation(navButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void shutDown()
|
||||||
|
{
|
||||||
|
clientToolbar.removeNavigation(navButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onNpcLootReceived(final NpcLootReceived npcLootReceived)
|
||||||
|
{
|
||||||
|
final NPC npc = npcLootReceived.getNpc();
|
||||||
|
final Collection<ItemStack> items = npcLootReceived.getItems();
|
||||||
|
final String name = npc.getName();
|
||||||
|
final int combat = npc.getCombatLevel();
|
||||||
|
final Collection<ItemStack> stackedItems = stack(items);
|
||||||
|
SwingUtilities.invokeLater(() -> panel.addLog(name, combat, stackedItems.toArray(new ItemStack[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPlayerLootReceived(final PlayerLootReceived playerLootReceived)
|
||||||
|
{
|
||||||
|
final Player player = playerLootReceived.getPlayer();
|
||||||
|
final Collection<ItemStack> items = playerLootReceived.getItems();
|
||||||
|
final String name = player.getName();
|
||||||
|
final int combat = player.getCombatLevel();
|
||||||
|
final Collection<ItemStack> stackedItems = stack(items);
|
||||||
|
SwingUtilities.invokeLater(() -> panel.addLog(name, combat, stackedItems.toArray(new ItemStack[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onWidgetLoaded(WidgetLoaded event)
|
||||||
|
{
|
||||||
|
final ItemContainer container;
|
||||||
|
switch (event.getGroupId())
|
||||||
|
{
|
||||||
|
case (WidgetID.BARROWS_REWARD_GROUP_ID):
|
||||||
|
eventType = "Barrows";
|
||||||
|
container = client.getItemContainer(InventoryID.BARROWS_REWARD);
|
||||||
|
break;
|
||||||
|
case (WidgetID.CHAMBERS_OF_XERIC_REWARD_GROUP_ID):
|
||||||
|
eventType = "Chambers of Xeric";
|
||||||
|
container = client.getItemContainer(InventoryID.CHAMBERS_OF_XERIC_CHEST);
|
||||||
|
break;
|
||||||
|
case (WidgetID.THEATRE_OF_BLOOD_GROUP_ID):
|
||||||
|
eventType = "Theatre of Blood";
|
||||||
|
container = client.getItemContainer(InventoryID.THEATRE_OF_BLOOD_CHEST);
|
||||||
|
break;
|
||||||
|
case (WidgetID.CLUE_SCROLL_REWARD_GROUP_ID):
|
||||||
|
// event type should be set via ChatMessage for clue scrolls.
|
||||||
|
// Clue Scrolls use same InventoryID as Barrows
|
||||||
|
container = client.getItemContainer(InventoryID.BARROWS_REWARD);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert container items to array of ItemStack
|
||||||
|
final ItemStack[] items = Arrays.stream(container.getItems())
|
||||||
|
.map(item -> new ItemStack(item.getId(), item.getQuantity()))
|
||||||
|
.toArray(ItemStack[]::new);
|
||||||
|
|
||||||
|
if (items.length > 0)
|
||||||
|
{
|
||||||
|
log.debug("Loot Received from Event: {}", eventType);
|
||||||
|
for (ItemStack item : items)
|
||||||
|
{
|
||||||
|
log.debug("Item Received: {}x {}", item.getQuantity(), item.getId());
|
||||||
|
}
|
||||||
|
SwingUtilities.invokeLater(() -> panel.addLog(eventType, -1, items));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log.debug("No items to find for Event: {} | Container: {}", eventType, container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onChatMessage(ChatMessage event)
|
||||||
|
{
|
||||||
|
if (event.getType() != ChatMessageType.SERVER && event.getType() != ChatMessageType.FILTERED)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if message is for a clue scroll reward
|
||||||
|
final Matcher m = CLUE_SCROLL_PATTERN.matcher(Text.removeTags(event.getMessage()));
|
||||||
|
if (m.find())
|
||||||
|
{
|
||||||
|
final String type = m.group(1).toLowerCase();
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "easy":
|
||||||
|
eventType = "Clue Scroll (Easy)";
|
||||||
|
break;
|
||||||
|
case "medium":
|
||||||
|
eventType = "Clue Scroll (Medium)";
|
||||||
|
break;
|
||||||
|
case "hard":
|
||||||
|
eventType = "Clue Scroll (Hard)";
|
||||||
|
break;
|
||||||
|
case "elite":
|
||||||
|
eventType = "Clue Scroll (Elite)";
|
||||||
|
break;
|
||||||
|
case "master":
|
||||||
|
eventType = "Clue Scroll (Master)";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Reference in New Issue
Block a user