diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java index c91817b410..daf515b896 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecordType.java @@ -29,5 +29,6 @@ public enum LootRecordType NPC, PLAYER, EVENT, + DEATH, UNKNOWN } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java index f37be3eb43..2022e5f72c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java @@ -229,7 +229,7 @@ class LootTrackerBox extends JPanel } } - if (quantity > 0) + if (quantity != 0) { int newQuantity = entry.getQuantity() + quantity; long pricePerItem = entry.getPrice() == 0 ? 0 : (entry.getPrice() / entry.getQuantity()); @@ -263,7 +263,7 @@ class LootTrackerBox extends JPanel imageLabel.setVerticalAlignment(SwingConstants.CENTER); imageLabel.setHorizontalAlignment(SwingConstants.CENTER); - AsyncBufferedImage itemImage = itemManager.getImage(item.getId(), item.getQuantity(), item.getQuantity() > 1); + AsyncBufferedImage itemImage = itemManager.getImage(item.getId(), Math.abs(item.getQuantity()), Math.abs(item.getQuantity()) > 1); if (item.isIgnored()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java index 77c151bfc4..24e2a92e80 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java @@ -27,6 +27,7 @@ package net.runelite.client.plugins.loottracker; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multiset; import com.google.common.collect.Multisets; import com.google.inject.Provides; @@ -58,10 +59,13 @@ import net.runelite.api.ItemContainer; import net.runelite.api.NPC; import net.runelite.api.Player; import net.runelite.api.SpriteID; +import net.runelite.api.Varbits; +import net.runelite.api.WorldType; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.ItemContainerChanged; +import net.runelite.api.events.LocalPlayerDeath; import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.WidgetID; import net.runelite.client.account.AccountSession; @@ -116,6 +120,16 @@ public class LootTrackerPlugin extends Plugin 11573, "Crystal Chest" ); + // Player death handling + private static final String PLAYER_DEATH_MESSAGE = "Oh dear, you are dead!"; + private static final Set RESPAWN_REGIONS = ImmutableSet.of( + 12850, // Lumbridge + 11828, // Falador + 12342, // Edgeville + 11062 // Camelot + ); + private boolean pvpDeath = false; + @Inject private ClientToolbar clientToolbar; @@ -151,6 +165,7 @@ public class LootTrackerPlugin extends Plugin private Multiset inventorySnapshot; + @Getter(AccessLevel.PACKAGE) private LootTrackerClient lootTrackerClient; @@ -209,6 +224,16 @@ public class LootTrackerPlugin extends Plugin lootTrackerClient = null; } + @Subscribe + public void onLocalPlayerDeath(LocalPlayerDeath event) + { + if (client.getVar(Varbits.IN_WILDERNESS) == 1 || WorldType.isPvpWorld(client.getWorldType())) + { + deathInventorySnapshot(); + pvpDeath = true; + } + } + @Subscribe public void onConfigChanged(ConfigChanged event) { @@ -416,6 +441,34 @@ public class LootTrackerPlugin extends Plugin final String message = event.getMessage(); + if (message.equals(PLAYER_DEATH_MESSAGE)) + { + ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY); + if (inventorySnapshot != null) + { + Multiset currentInventory = HashMultiset.create(); + if (inventory != null) + { + Arrays.stream(client.getItemContainer(InventoryID.INVENTORY).getItems()) + .forEach(item -> currentInventory.add(item.getId(), item.getQuantity())); + } + + final Multiset diff = Multisets.difference(inventorySnapshot, currentInventory); + + log.info(inventorySnapshot.toString()); + log.info(currentInventory.toString()); + log.info(diff.toString()); + + List itemsLost = diff.entrySet().stream() + .map(e -> new ItemStack(e.getElement(), (-1 * e.getCount()), client.getLocalPlayer().getLocalLocation())) + .collect(Collectors.toList()); + + final LootTrackerItem[] entries = buildEntries(stack(itemsLost)); + SwingUtilities.invokeLater(() -> panel.add("Death: " + client.getLocalPlayer().getName(), + client.getLocalPlayer().getCombatLevel(), entries)); + } + } + if (message.equals(CHEST_LOOTED_MESSAGE)) { final int regionID = client.getLocalPlayer().getWorldLocation().getRegionID(); @@ -470,6 +523,50 @@ public class LootTrackerPlugin extends Plugin @Subscribe public void onItemContainerChanged(ItemContainerChanged event) { + final ItemContainer itemContainer = event.getItemContainer(); + + if (pvpDeath && RESPAWN_REGIONS.contains(client.getLocalPlayer().getWorldLocation().getRegionID())) + { + Multiset snapshot = HashMultiset.create(); + snapshot = inventorySnapshot; + deathInventorySnapshot(); + if (inventorySnapshot != snapshot) + { + inventorySnapshot = snapshot; + ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY); + if (inventorySnapshot != null) + { + Multiset currentInventory = HashMultiset.create(); + if (inventory != null) + { + Arrays.stream(client.getItemContainer(InventoryID.INVENTORY).getItems()) + .forEach(item -> currentInventory.add(item.getId(), item.getQuantity())); + } + + final Multiset diff = Multisets.difference(inventorySnapshot, currentInventory); + + List itemsLost = diff.entrySet().stream() + .map(e -> new ItemStack(e.getElement(), (-1 * e.getCount()), client.getLocalPlayer().getLocalLocation())) + .collect(Collectors.toList()); + + final LootTrackerItem[] entries = buildEntries(stack(itemsLost)); + String name = "Death: " + client.getLocalPlayer().getName(); + SwingUtilities.invokeLater(() -> panel.add(name, + client.getLocalPlayer().getCombatLevel(), entries)); + + if (lootTrackerClient != null && config.saveLoot()) + { + LootRecord lootRecord = new LootRecord(name, LootRecordType.DEATH, toGameItems(itemsLost), + Instant.now()); + lootTrackerClient.submit(lootRecord); + } + + pvpDeath = false; + inventorySnapshot = null; + } + } + + } if (eventType != null && (CHEST_EVENT_TYPES.containsValue(eventType) || HERBIBOR_EVENT.equals(eventType))) { if (event.getItemContainer() != client.getItemContainer(InventoryID.INVENTORY)) @@ -482,6 +579,28 @@ public class LootTrackerPlugin extends Plugin } } + /** + * Takes a snapshot of the local player's inventory and equipment right before respawn. + */ + private void deathInventorySnapshot() + { + final ItemContainer inventory = client.getItemContainer(InventoryID.INVENTORY); + final ItemContainer equipment = client.getItemContainer(InventoryID.EQUIPMENT); + inventorySnapshot = HashMultiset.create(); + if (inventory != null) + { + + Arrays.stream(inventory.getItems()) + .forEach(item -> inventorySnapshot.add(item.getId(), item.getQuantity())); + } + + if (equipment != null) + { + Arrays.stream(equipment.getItems()) + .forEach(item -> inventorySnapshot.add(item.getId(), item.getQuantity())); + } + } + private void takeInventorySnapshot() { final ItemContainer itemContainer = client.getItemContainer(InventoryID.INVENTORY); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java index df03f7a75b..f3118b416a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java @@ -1,52 +1,54 @@ -/* - * Copyright (c) 2018, Psikoi - * 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 lombok.Value; - -@Value -class LootTrackerRecord -{ - private final String title; - private final String subTitle; - private final LootTrackerItem[] items; - private final long timestamp; - - /** - * Checks if this record matches specified id - * - * @param id other record id - * @return true if match is made - */ - boolean matches(final String id) - { - if (id == null) - { - return true; - } - - return title.equals(id); - } +/* + * Copyright (c) 2018, Psikoi + * 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.gson.annotations.SerializedName; +import lombok.Value; + +@Value +class LootTrackerRecord +{ + private final String title; + private final String subTitle; + @SerializedName("item_records") + private final LootTrackerItem[] items; + private final long timestamp; + + /** + * Checks if this record matches specified id + * + * @param id other record id + * @return true if match is made + */ + boolean matches(final String id) + { + if (id == null) + { + return true; + } + + return title.equals(id); + } } \ No newline at end of file