diff --git a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java index fc945220f1..993959ee88 100644 --- a/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java +++ b/http-api/src/main/java/net/runelite/http/api/loottracker/LootRecord.java @@ -26,8 +26,10 @@ package net.runelite.http.api.loottracker; import java.time.Instant; import java.util.Collection; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; @Data @@ -36,7 +38,26 @@ import lombok.NoArgsConstructor; public class LootRecord { private String eventId; + @Getter + private String username; private LootRecordType type; private Collection drops; private Instant time; + + /** + * constructor for lootRecords retrieved by http api (doesn't store/retrieve username) + * @param eventId - the eventID or the name/title of the LootRecord + * @param type - The LootRecordType + * @param gameItems - the list of items/quantities + * @param time - the Instant that the Loot Record was received + */ + public LootRecord(String eventId, LootRecordType type, List gameItems, Instant time) + { + // Insert blank username + this.username = ""; + this.eventId = eventId; + this.type = type; + this.drops = gameItems; + this.time = time; + } } 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/LootRecordSortType.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootRecordSortType.java new file mode 100644 index 0000000000..61050d2552 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootRecordSortType.java @@ -0,0 +1,56 @@ +package net.runelite.client.plugins.loottracker; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.stream.IntStream; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.loottracker.LootRecordType; + +/** + * + */ +@Slf4j +public enum LootRecordSortType implements Comparator +{ + TIMESTAMP + { + @Override + public int compare(LootTrackerRecord o1, LootTrackerRecord o2) + { + return Long.compare(o1.getTimestamp(), o2.getTimestamp()); + } + }, + TOTAL_VALUE + { + @Override + public int compare(LootTrackerRecord o1, LootTrackerRecord o2) + { + + // We want deaths at the bottom of the list + if (o1.getSubTitle().equals(LootRecordType.DEATH.name())) + { + return Arrays.stream(o1.getItems()).flatMapToInt(lootTrackerItem -> + IntStream.of((int) lootTrackerItem.getPrice() * lootTrackerItem.getQuantity())).sum(); + } + if (o2.getSubTitle().equals(LootRecordType.DEATH.name())) + { + return Arrays.stream(o1.getItems()).flatMapToInt(lootTrackerItem -> + IntStream.of((int) lootTrackerItem.getPrice() * lootTrackerItem.getQuantity())).sum(); + } + int sum = Arrays.stream(o1.getItems()).flatMapToInt(lootTrackerItem -> + IntStream.of((int) lootTrackerItem.getPrice() * lootTrackerItem.getQuantity())).sum(); + log.info(String.valueOf(sum + Arrays.stream(o2.getItems()).flatMapToInt(lootTrackerItem -> + IntStream.of((int) lootTrackerItem.getPrice() * lootTrackerItem.getQuantity())).sum())); + return sum + Arrays.stream(o2.getItems()).flatMapToInt(lootTrackerItem -> + IntStream.of((int) lootTrackerItem.getPrice() * lootTrackerItem.getQuantity())).sum(); + } + }, + ITEM_COUNT + { + @Override + public int compare(LootTrackerRecord o1, LootTrackerRecord o2) + { + return Integer.compare(o1.getItems().length, o2.getItems().length); + } + } +} 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..b111fde570 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 @@ -120,7 +120,8 @@ class LootTrackerBox extends JPanel private long getTotalKills() { return hideIgnoredItems - ? records.stream().filter(r -> !Arrays.stream(r.getItems()).allMatch(LootTrackerItem::isIgnored)).count() + ? records.stream().filter( + r -> !Arrays.stream(r.getItems()).allMatch(LootTrackerItem::isIgnored)).count() : records.size(); } @@ -229,7 +230,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 +264,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/LootTrackerConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java index 597dcfddf9..0e800a8094 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerConfig.java @@ -80,4 +80,25 @@ public interface LootTrackerConfig extends Config { return true; } + + @ConfigItem( + keyName = "localPersistence", + name = "Local Record Persistence", + description = "Stores/syncs loot records locally in the JSON format. Note: records will not be saved locally" + + " if they are successfully saved online. " + ) + default boolean localPersistence() + { + return true; + } + @ConfigItem( + keyName = "sortType", + name = "Sorting", + description = "The method for sorting Loot Tracker entries" + ) + default LootRecordSortType sortType() + { + return LootRecordSortType.TIMESTAMP; + } + } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java index d9729ee97c..b26f83f0ff 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPanel.java @@ -33,8 +33,11 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.ImageIcon; @@ -43,6 +46,10 @@ import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.border.EmptyBorder; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.GameState; import net.runelite.client.game.ItemManager; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; @@ -53,6 +60,7 @@ import net.runelite.client.util.ImageUtil; import net.runelite.client.util.StackFormatter; import net.runelite.http.api.loottracker.LootTrackerClient; +@Slf4j class LootTrackerPanel extends PluginPanel { private static final int MAX_LOOT_BOXES = 500; @@ -131,6 +139,11 @@ class LootTrackerPanel extends PluginPanel INVISIBLE_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(invisibleImg, -220)); } + private final JPanel displaySelector; + + @Getter @Setter + private LootRecordSortType lootRecordSortType = LootRecordSortType.TIMESTAMP; + LootTrackerPanel(final LootTrackerPlugin plugin, final ItemManager itemManager, final LootTrackerConfig config) { this.itemManager = itemManager; @@ -289,6 +302,11 @@ class LootTrackerPanel extends PluginPanel overallPanel.add(overallIcon, BorderLayout.WEST); overallPanel.add(overallInfo, BorderLayout.CENTER); + displaySelector = new JPanel(); + displaySelector.setLayout(new GridLayout(1, 1)); + displaySelector.setBorder(new EmptyBorder(2, 10, 10, 10)); + displaySelector.setBackground(ColorScheme.DARKER_GRAY_COLOR); + // Create reset all menu final JMenuItem reset = new JMenuItem("Reset All"); reset.addActionListener(e -> @@ -318,6 +336,7 @@ class LootTrackerPanel extends PluginPanel logsContainer.setLayout(new BoxLayout(logsContainer, BoxLayout.Y_AXIS)); layoutPanel.add(actionsContainer); layoutPanel.add(overallPanel); + layoutPanel.add(displaySelector); layoutPanel.add(logsContainer); // Add error pane @@ -335,10 +354,10 @@ class LootTrackerPanel extends PluginPanel * Creates a subtitle, adds a new entry and then passes off to the render methods, that will decide * how to display this new data. */ - void add(final String eventName, final int actorLevel, LootTrackerItem[] items) + void add(final String eventName, final String localUsername, final int actorLevel, LootTrackerItem[] items) { final String subTitle = actorLevel > -1 ? "(lvl-" + actorLevel + ")" : ""; - final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, items, System.currentTimeMillis()); + final LootTrackerRecord record = new LootTrackerRecord( eventName, localUsername, subTitle, items, System.currentTimeMillis()); records.add(record); LootTrackerBox box = buildBox(record); if (box != null) @@ -405,23 +424,37 @@ class LootTrackerPanel extends PluginPanel /** * Rebuilds all the boxes from scratch using existing listed records, depending on the grouping mode. */ - private void rebuild() + public void rebuild() { + logsContainer.removeAll(); boxes.clear(); int start = 0; + records.sort(lootRecordSortType); if (!groupLoot && records.size() > MAX_LOOT_BOXES) { start = records.size() - MAX_LOOT_BOXES; } for (int i = start; i < records.size(); i++) { + + if (this.plugin.client.getGameState().equals(GameState.LOGGED_IN)) + { + if (!(this.plugin.client.getLocalPlayer().getName().equals(records.get(i).getLocalUsername()))) + { + continue; + } + } buildBox(records.get(i)); + log.info(String.valueOf(Arrays.stream(records.get(i).getItems()).flatMapToInt(a -> IntStream.of(a.getQuantity() * (int) a.getPrice())).sum())); + } boxes.forEach(LootTrackerBox::rebuild); updateOverall(); logsContainer.revalidate(); logsContainer.repaint(); + + } /** @@ -431,12 +464,21 @@ class LootTrackerPanel extends PluginPanel */ private LootTrackerBox buildBox(LootTrackerRecord record) { + // If this record is not part of current view, return if (!record.matches(currentView)) { return null; } + if (this.plugin.client.getGameState().equals(GameState.LOGGED_IN)) + { + if (!(this.plugin.client.getLocalPlayer().getName().equals(record.getLocalUsername()))) + { + return null; + } + } + // Group all similar loot together if (groupLoot) { @@ -456,7 +498,8 @@ class LootTrackerPanel extends PluginPanel overallPanel.setVisible(true); // Create box - final LootTrackerBox box = new LootTrackerBox(itemManager, record.getTitle(), record.getSubTitle(), hideIgnoredItems, plugin::toggleItem); + final LootTrackerBox box = new LootTrackerBox(itemManager, record.getTitle(), record.getSubTitle(), + hideIgnoredItems, plugin::toggleItem); box.combine(record); // Create popup menu @@ -519,6 +562,14 @@ class LootTrackerPanel extends PluginPanel { continue; } + if (Objects.nonNull(record.getLocalUsername()) && Objects.nonNull(plugin.client.getLocalPlayer())) + { + if (!record.getLocalUsername().equals(plugin.client.getLocalPlayer().getName())) + { + + continue; + } + } int present = record.getItems().length; @@ -542,7 +593,6 @@ class LootTrackerPanel extends PluginPanel overallKillsLabel.setText(htmlLabel("Total count: ", overallKills)); overallGpLabel.setText(htmlLabel("Total value: ", overallGp)); } - private static String htmlLabel(String key, long value) { final String valueStr = StackFormatter.quantityToStackSize(value); 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 7be13abf7f..2f572275eb 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 @@ -25,13 +25,25 @@ */ package net.runelite.client.plugins.loottracker; +import com.google.common.annotations.VisibleForTesting; 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.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonStreamParser; +import com.google.gson.reflect.TypeToken; import com.google.inject.Provides; import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.IOException; +import java.nio.file.Files; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -53,17 +65,22 @@ import net.runelite.api.ChatMessageType; import net.runelite.api.Client; import net.runelite.api.InventoryID; import net.runelite.api.Item; -import net.runelite.api.ItemDefinition; import net.runelite.api.ItemContainer; +import net.runelite.api.ItemDefinition; 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.PlayerSpawned; import net.runelite.api.events.WidgetLoaded; import net.runelite.api.widgets.WidgetID; +import net.runelite.client.RuneLite; import net.runelite.client.account.AccountSession; import net.runelite.client.account.SessionManager; import net.runelite.client.callback.ClientThread; @@ -87,6 +104,7 @@ import net.runelite.client.ui.NavigationButton; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.StackFormatter; import net.runelite.client.util.Text; +import net.runelite.http.api.RuneLiteAPI; import net.runelite.http.api.loottracker.GameItem; import net.runelite.http.api.loottracker.LootRecord; import net.runelite.http.api.loottracker.LootRecordType; @@ -116,6 +134,18 @@ public class LootTrackerPlugin extends Plugin 11573, "Crystal Chest" ); + // Player deaths + public static HashSet usernameSet = new HashSet(Arrays.stream(new String[]{"All Records"}).collect(Collectors.toList())); + + private static final File LOOT_RECORDS_FILE = new File(RuneLite.RUNELITE_DIR, "lootRecords.json"); + private static final Set RESPAWN_REGIONS = ImmutableSet.of( + 12850, // Lumbridge + 11828, // Falador + 12342, // Edgeville + 11062 // Camelot + ); + private boolean pvpDeath = false; + @Inject private ClientToolbar clientToolbar; @@ -132,7 +162,7 @@ public class LootTrackerPlugin extends Plugin private LootTrackerConfig config; @Inject - private Client client; + public Client client; @Inject private ClientThread clientThread; @@ -153,6 +183,10 @@ public class LootTrackerPlugin extends Plugin @Getter(AccessLevel.PACKAGE) private LootTrackerClient lootTrackerClient; + private BufferedReader bufferedReader; + private JsonStreamParser jsonStreamParser; + @VisibleForTesting + public Collection lootRecords = new ArrayList<>(); private static Collection stack(Collection items) { @@ -209,14 +243,33 @@ 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) { if (event.getGroup().equals("loottracker")) { - ignoredItems = Text.fromCSV(config.getIgnoredItems()); - SwingUtilities.invokeLater(panel::updateIgnoredRecords); + if (event.getKey().equals("ignoredItems")) + { + ignoredItems = Text.fromCSV(config.getIgnoredItems()); + SwingUtilities.invokeLater(panel::updateIgnoredRecords); + } + if (event.getKey().equals("sortType")) + { + panel.setLootRecordSortType(config.sortType()); + SwingUtilities.invokeLater(panel::rebuild); + } } + } @Override @@ -238,9 +291,10 @@ public class LootTrackerPlugin extends Plugin clientToolbar.addNavigation(navButton); AccountSession accountSession = sessionManager.getAccountSession(); - if (accountSession != null) + LOOT_RECORDS_FILE.createNewFile(); + bufferedReader = Files.newBufferedReader(LOOT_RECORDS_FILE.toPath()); + if (accountSession != null || config.localPersistence()) { - lootTrackerClient = new LootTrackerClient(accountSession.getUuid()); clientThread.invokeLater(() -> { @@ -253,28 +307,42 @@ public class LootTrackerPlugin extends Plugin executor.submit(() -> { - Collection lootRecords; - if (!config.syncPanel()) + if (config.syncPanel() && lootTrackerClient != null) { - return; + lootTrackerClient = new LootTrackerClient(accountSession.getUuid()); + try + { + lootRecords = lootTrackerClient.get(); + } + catch (IOException e) + { + log.debug("Unable to look up loot", e); + return; + } + log.debug("Loaded {} remote data entries", lootRecords.size()); } - try + if (config.localPersistence() ) { - lootRecords = lootTrackerClient.get(); - } - catch (IOException e) - { - log.debug("Unable to look up loot", e); - return; + try + { + lootRecords.addAll(RuneLiteAPI.GSON.fromJson(new FileReader(LOOT_RECORDS_FILE), + new TypeToken>() + { }.getType())); + + } + catch (IOException e) + { + e.printStackTrace(); + } } - log.debug("Loaded {} data entries", lootRecords.size()); + Collection finalLootRecords = lootRecords; clientThread.invokeLater(() -> { - Collection records = convertToLootTrackerRecord(lootRecords); + Collection records = convertToLootTrackerRecord(finalLootRecords); SwingUtilities.invokeLater(() -> panel.addRecords(records)); }); }); @@ -288,6 +356,7 @@ public class LootTrackerPlugin extends Plugin { clientToolbar.removeNavigation(navButton); lootTrackerClient = null; + lootRecords = new ArrayList(); } @Subscribe @@ -298,13 +367,29 @@ public class LootTrackerPlugin extends Plugin final String name = npc.getName(); final int combat = npc.getCombatLevel(); final LootTrackerItem[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.add(name, combat, entries)); + String localUsername = client.getLocalPlayer().getName(); + SwingUtilities.invokeLater(() -> panel.add(name, localUsername, combat, entries)); + LootRecord lootRecord = new LootRecord( name, localUsername, LootRecordType.NPC, + toGameItems(items), Instant.now()); if (lootTrackerClient != null && config.saveLoot()) { - LootRecord lootRecord = new LootRecord(name, LootRecordType.NPC, toGameItems(items), Instant.now()); lootTrackerClient.submit(lootRecord); } + if (config.localPersistence()) + { + saveLocalLootRecord(lootRecord); + } + } + + + @Subscribe + public void onPlayerSpawned(PlayerSpawned event) + { + if (event.getPlayer().equals(client.getLocalPlayer())) + { + SwingUtilities.invokeLater(() -> panel.rebuild()); + } } @Subscribe @@ -315,13 +400,18 @@ public class LootTrackerPlugin extends Plugin final String name = player.getName(); final int combat = player.getCombatLevel(); final LootTrackerItem[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.add(name, combat, entries)); - + String localUsername = client.getLocalPlayer().getName(); + SwingUtilities.invokeLater(() -> panel.add(name, localUsername, combat, entries)); + LootRecord lootRecord = new LootRecord( name, localUsername, LootRecordType.PLAYER, + toGameItems(items), Instant.now()); if (lootTrackerClient != null && config.saveLoot()) { - LootRecord lootRecord = new LootRecord(name, LootRecordType.PLAYER, toGameItems(items), Instant.now()); lootTrackerClient.submit(lootRecord); } + if (config.localPersistence() && lootTrackerClient == null) + { + saveLocalLootRecord(lootRecord); + } } @Subscribe @@ -384,6 +474,11 @@ public class LootTrackerPlugin extends Plugin .build()); } + if (event.getGroupId() == WidgetID.CHATBOX_GROUP_ID) + { + panel.rebuild(); + } + // Convert container items to array of ItemStack final Collection items = Arrays.stream(container.getItems()) .filter(item -> item.getId() > 0) @@ -397,11 +492,12 @@ public class LootTrackerPlugin extends Plugin } final LootTrackerItem[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.add(eventType, -1, entries)); + SwingUtilities.invokeLater(() -> panel.add(eventType, client.getLocalPlayer().getName(), -1, entries)); if (lootTrackerClient != null && config.saveLoot()) { - LootRecord lootRecord = new LootRecord(eventType, LootRecordType.EVENT, toGameItems(items), Instant.now()); + LootRecord lootRecord = new LootRecord(eventType, client.getLocalPlayer().getName(), LootRecordType.EVENT, + toGameItems(items), Instant.now()); lootTrackerClient.submit(lootRecord); } } @@ -470,6 +566,53 @@ 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().getName(), + client.getLocalPlayer().getCombatLevel(), entries)); + LootRecord lootRecord = new LootRecord(name, client.getLocalPlayer().getName(), LootRecordType.DEATH, + toGameItems(itemsLost), Instant.now()); + if (lootTrackerClient != null && config.saveLoot()) + { + lootTrackerClient.submit(lootRecord); + } + if (config.localPersistence() && lootTrackerClient == null) + { + saveLocalLootRecord(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 +625,58 @@ public class LootTrackerPlugin extends Plugin } } + private void saveLocalLootRecord(LootRecord lootRecord) + { + lootRecords.add(lootRecord); + try + { + BufferedWriter bufferedWriter = Files.newBufferedWriter(LOOT_RECORDS_FILE.toPath()); + + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.setPrettyPrinting(); + Gson gson = gsonBuilder.create(); + bufferedWriter.append(gson.toJson(lootRecords)); + bufferedWriter.close(); + } + catch (IOException e) + { + if (e instanceof FileNotFoundException) + { + try + { + LOOT_RECORDS_FILE.createNewFile(); + } + catch (IOException ex) + { + ex.printStackTrace(); + } + } + e.printStackTrace(); + } + } + + /** + * 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); @@ -508,11 +703,12 @@ public class LootTrackerPlugin extends Plugin .collect(Collectors.toList()); final LootTrackerItem[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.add(chestType, -1, entries)); + SwingUtilities.invokeLater(() -> panel.add(chestType, client.getLocalPlayer().getName(), -1, entries)); if (lootTrackerClient != null && config.saveLoot()) { - LootRecord lootRecord = new LootRecord(chestType, LootRecordType.EVENT, toGameItems(items), Instant.now()); + LootRecord lootRecord = new LootRecord(chestType, client.getLocalPlayer().getName(), + LootRecordType.EVENT, toGameItems(items), Instant.now()); lootTrackerClient.submit(lootRecord); } @@ -542,6 +738,7 @@ public class LootTrackerPlugin extends Plugin return ignoredItems.contains(name); } + @VisibleForTesting private LootTrackerItem buildLootTrackerItem(int itemId, int quantity) { final ItemDefinition itemComposition = itemManager.getItemDefinition(itemId); @@ -571,7 +768,7 @@ public class LootTrackerPlugin extends Plugin .collect(Collectors.toList()); } - private Collection convertToLootTrackerRecord(final Collection records) + public Collection convertToLootTrackerRecord(final Collection records) { Collection trackerRecords = new ArrayList<>(); for (LootRecord record : records) @@ -580,7 +777,8 @@ public class LootTrackerPlugin extends Plugin buildLootTrackerItem(itemStack.getId(), itemStack.getQty()) ).toArray(LootTrackerItem[]::new); - trackerRecords.add(new LootTrackerRecord(record.getEventId(), "", drops, -1)); + trackerRecords.add(new LootTrackerRecord(record.getEventId(), record.getUsername(), + "", drops, -1)); } return trackerRecords; 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 a47dc0c07f..a92ff0009f 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 @@ -24,13 +24,16 @@ */ package net.runelite.client.plugins.loottracker; +import com.google.gson.annotations.SerializedName; import lombok.Value; @Value class LootTrackerRecord { private final String title; + private String localUsername; private final String subTitle; + @SerializedName("item_records") private final LootTrackerItem[] items; private final long timestamp;