From 4dd8322121e190cca16a22f0a865ecf721b36223 Mon Sep 17 00:00:00 2001 From: Ruben Amendoeira Date: Thu, 13 Sep 2018 01:03:50 +0100 Subject: [PATCH] loot tracker: add view for grouping loot by npc/event --- .../plugins/loottracker/LootTrackerBox.java | 161 ++++++++-- ...kerItemEntry.java => LootTrackerItem.java} | 2 +- .../plugins/loottracker/LootTrackerPanel.java | 279 ++++++++++++++++-- .../loottracker/LootTrackerPlugin.java | 20 +- .../loottracker/LootTrackerRecord.java | 51 ++++ .../client/plugins/loottracker/back_icon.png | Bin 0 -> 15109 bytes .../plugins/loottracker/grouped_loot_icon.png | Bin 0 -> 15596 bytes .../plugins/loottracker/single_loot_icon.png | Bin 0 -> 15393 bytes 8 files changed, 445 insertions(+), 68 deletions(-) rename runelite-client/src/main/java/net/runelite/client/plugins/loottracker/{LootTrackerItemEntry.java => LootTrackerItem.java} (98%) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/loottracker/back_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/loottracker/grouped_loot_icon.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/loottracker/single_loot_icon.png 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 6aaeea1977..d20ac8c1f0 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 @@ -29,6 +29,10 @@ import com.google.common.base.Strings; import java.awt.BorderLayout; import java.awt.Color; import java.awt.GridLayout; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nullable; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingConstants; @@ -39,14 +43,26 @@ import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; import net.runelite.client.util.StackFormatter; -@Getter class LootTrackerBox extends JPanel { private static final int ITEMS_PER_ROW = 5; - private final long totalPrice; + private final JPanel itemContainer = new JPanel(); + private final JLabel priceLabel = new JLabel(); + private final JLabel subTitleLabel = new JLabel(); + private final ItemManager itemManager; + private final String id; - LootTrackerBox(final ItemManager itemManager, final String title, final String subTitle, final LootTrackerItemEntry[] items) + @Getter + private final List records = new ArrayList<>(); + + @Getter + private long totalPrice; + + LootTrackerBox(final ItemManager itemManager, final String id, @Nullable final String subtitle) { + this.id = id; + this.itemManager = itemManager; + setLayout(new BorderLayout(0, 1)); setBorder(new EmptyBorder(5, 0, 0, 0)); @@ -54,43 +70,140 @@ class LootTrackerBox extends JPanel logTitle.setBorder(new EmptyBorder(7, 7, 7, 7)); logTitle.setBackground(ColorScheme.DARKER_GRAY_COLOR.darker()); - final JLabel titleLabel = new JLabel(title); + final JLabel titleLabel = new JLabel(id); titleLabel.setFont(FontManager.getRunescapeSmallFont()); titleLabel.setForeground(Color.WHITE); logTitle.add(titleLabel, BorderLayout.WEST); // If we have subtitle, add it - if (!Strings.isNullOrEmpty(subTitle)) + if (!Strings.isNullOrEmpty(subtitle)) { - final JLabel subTitleLabel = new JLabel(subTitle); + subTitleLabel.setText(subtitle); subTitleLabel.setFont(FontManager.getRunescapeSmallFont()); subTitleLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR); logTitle.add(subTitleLabel, BorderLayout.CENTER); } - totalPrice = calculatePrice(items); + priceLabel.setFont(FontManager.getRunescapeSmallFont()); + priceLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR); + logTitle.add(priceLabel, BorderLayout.EAST); - if (totalPrice > 0) + add(logTitle, BorderLayout.NORTH); + add(itemContainer, BorderLayout.CENTER); + } + + int getTotalKills() + { + return records.size(); + } + + /** + * Checks if this box matches specified record + * @param record loot record + * @return true if match is made + */ + boolean matches(final LootTrackerRecord record) + { + return record.getTitle().equals(id); + } + + /** + * Checks if this box matches specified id + * @param id other record id + * @return true if match is made + */ + boolean matches(final String id) + { + if (id == null) { - final JLabel priceLabel = new JLabel(StackFormatter.quantityToStackSize(totalPrice) + " gp"); - priceLabel.setFont(FontManager.getRunescapeSmallFont()); - priceLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR); - logTitle.add(priceLabel, BorderLayout.EAST); + return true; } + return this.id.equals(id); + } + + /** + * Adds an record's data into a loot box. + * This will add new items to the list, re-calculating price and kill count. + */ + void combine(final LootTrackerRecord record) + { + if (!matches(record)) + { + throw new IllegalArgumentException(record.toString()); + } + + records.add(record); + buildItems(); + + priceLabel.setText(StackFormatter.quantityToStackSize(totalPrice) + " gp"); + if (records.size() > 1) + { + subTitleLabel.setText("x " + records.size()); + } + + repaint(); + } + + /** + * This method creates stacked items from the item list, calculates total price and then + * displays all the items in the UI. + */ + private void buildItems() + { + final List allItems = new ArrayList<>(); + final List items = new ArrayList<>(); + totalPrice = 0; + + for (LootTrackerRecord records : records) + { + allItems.addAll(Arrays.asList(records.getItems())); + } + + for (final LootTrackerItem entry : allItems) + { + totalPrice += entry.getPrice(); + + int quantity = 0; + for (final LootTrackerItem i : items) + { + if (i.getId() == entry.getId()) + { + quantity = i.getQuantity(); + items.remove(i); + break; + } + } + if (quantity > 0) + { + int newQuantity = entry.getQuantity() + quantity; + long pricePerItem = entry.getPrice() == 0 ? 0 : (entry.getPrice() / entry.getQuantity()); + + items.add(new LootTrackerItem(entry.getId(), entry.getName(), newQuantity, pricePerItem * newQuantity)); + } + else + { + items.add(entry); + } + } + + items.sort((i1, i2) -> Long.compare(i2.getPrice(), i1.getPrice())); + // 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)); + final int rowSize = ((items.size() % ITEMS_PER_ROW == 0) ? 0 : 1) + items.size() / ITEMS_PER_ROW; + + itemContainer.removeAll(); + itemContainer.setLayout(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) + if (i < items.size()) { - final LootTrackerItemEntry item = items[i]; + final LootTrackerItem item = items.get(i); final JLabel imageLabel = new JLabel(); imageLabel.setToolTipText(buildToolTip(item)); imageLabel.setVerticalAlignment(SwingConstants.CENTER); @@ -102,26 +215,14 @@ class LootTrackerBox extends JPanel itemContainer.add(slotContainer); } - add(logTitle, BorderLayout.NORTH); - add(itemContainer, BorderLayout.CENTER); + itemContainer.repaint(); } - private String buildToolTip(LootTrackerItemEntry item) + private static String buildToolTip(LootTrackerItem item) { final String name = item.getName(); final int quantity = item.getQuantity(); final long price = item.getPrice(); - return name + " x " + quantity + " (" + StackFormatter.quantityToStackSize(price) + ")"; } - - private static long calculatePrice(final LootTrackerItemEntry[] itemStacks) - { - long total = 0; - for (LootTrackerItemEntry itemStack : itemStacks) - { - total += itemStack.getPrice(); - } - return total; - } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerItemEntry.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerItem.java similarity index 98% rename from runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerItemEntry.java rename to runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerItem.java index 3d18ce9c0b..9e3a36ad86 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerItemEntry.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerItem.java @@ -27,7 +27,7 @@ package net.runelite.client.plugins.loottracker; import lombok.Value; @Value -class LootTrackerItemEntry +class LootTrackerItem { private final int id; private final String name; 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 88b83775d2..2df6499d18 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 @@ -26,8 +26,15 @@ package net.runelite.client.plugins.loottracker; import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JLabel; @@ -41,17 +48,27 @@ 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.ImageUtil; import net.runelite.client.util.StackFormatter; class LootTrackerPanel extends PluginPanel { + private static final ImageIcon SINGLE_LOOT_VIEW; + private static final ImageIcon SINGLE_LOOT_VIEW_FADED; + private static final ImageIcon SINGLE_LOOT_VIEW_HOVER; + private static final ImageIcon GROUPED_LOOT_VIEW; + private static final ImageIcon GROUPED_LOOT_VIEW_FADED; + private static final ImageIcon GROUPED_LOOT_VIEW_HOVER; + private static final ImageIcon BACK_ARROW_ICON; + private static final ImageIcon BACK_ARROW_ICON_HOVER; + private static final String HTML_LABEL_TEMPLATE = "%s%s"; // When there is no loot, display this private final PluginErrorPanel errorPanel = new PluginErrorPanel(); - // Handle loot logs + // Handle loot boxes private final JPanel logsContainer = new JPanel(); // Handle overall session data @@ -59,9 +76,39 @@ class LootTrackerPanel extends PluginPanel private final JLabel overallKillsLabel = new JLabel(); private final JLabel overallGpLabel = new JLabel(); private final JLabel overallIcon = new JLabel(); + + // Details and navigation + private final JPanel actionsContainer = new JPanel(); + private final JLabel detailsTitle = new JLabel(); + private final JLabel backBtn = new JLabel(); + private final JLabel singleLootBtn = new JLabel(); + private final JLabel groupedLootBtn = new JLabel(); + + // Log collection + private final List records = new ArrayList<>(); + private final List boxes = new ArrayList<>(); + private final ItemManager itemManager; - private int overallKills; - private int overallGp; + private boolean groupLoot; + private String currentView; + + static + { + final BufferedImage singleLootImg = ImageUtil.getResourceStreamFromClass(LootTrackerPlugin.class, "single_loot_icon.png"); + final BufferedImage groupedLootImg = ImageUtil.getResourceStreamFromClass(LootTrackerPlugin.class, "grouped_loot_icon.png"); + final BufferedImage backArrowImg = ImageUtil.getResourceStreamFromClass(LootTrackerPlugin.class, "back_icon.png"); + + SINGLE_LOOT_VIEW = new ImageIcon(singleLootImg); + SINGLE_LOOT_VIEW_FADED = new ImageIcon(ImageUtil.alphaOffset(singleLootImg, -180)); + SINGLE_LOOT_VIEW_HOVER = new ImageIcon(ImageUtil.alphaOffset(singleLootImg, -220)); + + GROUPED_LOOT_VIEW = new ImageIcon(groupedLootImg); + GROUPED_LOOT_VIEW_FADED = new ImageIcon(ImageUtil.alphaOffset(groupedLootImg, -180)); + GROUPED_LOOT_VIEW_HOVER = new ImageIcon(ImageUtil.alphaOffset(groupedLootImg, -220)); + + BACK_ARROW_ICON = new ImageIcon(backArrowImg); + BACK_ARROW_ICON_HOVER = new ImageIcon(ImageUtil.alphaOffset(backArrowImg, -180)); + } LootTrackerPanel(final ItemManager itemManager) { @@ -75,8 +122,104 @@ class LootTrackerPanel extends PluginPanel layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS)); add(layoutPanel, BorderLayout.NORTH); + actionsContainer.setLayout(new BorderLayout()); + actionsContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + actionsContainer.setPreferredSize(new Dimension(0, 30)); + actionsContainer.setBorder(new EmptyBorder(5, 5, 5, 10)); + actionsContainer.setVisible(false); + + final JPanel viewControls = new JPanel(new GridLayout(1, 2, 10, 0)); + viewControls.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + singleLootBtn.setIcon(SINGLE_LOOT_VIEW); + singleLootBtn.setToolTipText("Show each kill separately"); + singleLootBtn.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + changeGrouping(false); + } + + @Override + public void mouseExited(MouseEvent mouseEvent) + { + singleLootBtn.setIcon(groupLoot ? SINGLE_LOOT_VIEW_FADED : SINGLE_LOOT_VIEW); + } + + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + singleLootBtn.setIcon(groupLoot ? SINGLE_LOOT_VIEW_HOVER : SINGLE_LOOT_VIEW); + } + }); + + groupedLootBtn.setIcon(GROUPED_LOOT_VIEW); + groupedLootBtn.setToolTipText("Group loot by source"); + groupedLootBtn.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + changeGrouping(true); + } + + @Override + public void mouseExited(MouseEvent mouseEvent) + { + groupedLootBtn.setIcon(groupLoot ? GROUPED_LOOT_VIEW : GROUPED_LOOT_VIEW_FADED); + } + + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + groupedLootBtn.setIcon(groupLoot ? GROUPED_LOOT_VIEW : GROUPED_LOOT_VIEW_HOVER); + } + }); + + viewControls.add(groupedLootBtn); + viewControls.add(singleLootBtn); + changeGrouping(true); + + final JPanel leftTitleContainer = new JPanel(new BorderLayout(5, 0)); + leftTitleContainer.setBackground(ColorScheme.DARKER_GRAY_COLOR); + + detailsTitle.setForeground(Color.WHITE); + + backBtn.setIcon(BACK_ARROW_ICON); + backBtn.setVisible(false); + backBtn.addMouseListener(new MouseAdapter() + { + @Override + public void mousePressed(MouseEvent mouseEvent) + { + currentView = null; + backBtn.setVisible(false); + detailsTitle.setText(""); + rebuild(); + } + + @Override + public void mouseExited(MouseEvent mouseEvent) + { + backBtn.setIcon(BACK_ARROW_ICON); + } + + @Override + public void mouseEntered(MouseEvent mouseEvent) + { + backBtn.setIcon(BACK_ARROW_ICON_HOVER); + } + }); + + leftTitleContainer.add(backBtn, BorderLayout.WEST); + leftTitleContainer.add(detailsTitle, BorderLayout.CENTER); + + actionsContainer.add(leftTitleContainer, BorderLayout.WEST); + actionsContainer.add(viewControls, BorderLayout.EAST); + // Create panel that will contain overall data - overallPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + overallPanel.setBorder(new EmptyBorder(8, 10, 8, 10)); overallPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR); overallPanel.setLayout(new BorderLayout()); overallPanel.setVisible(false); @@ -85,7 +228,7 @@ class LootTrackerPanel extends PluginPanel 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)); + overallInfo.setBorder(new EmptyBorder(2, 10, 2, 0)); overallKillsLabel.setFont(FontManager.getRunescapeSmallFont()); overallGpLabel.setFont(FontManager.getRunescapeSmallFont()); overallInfo.add(overallKillsLabel); @@ -97,8 +240,9 @@ class LootTrackerPanel extends PluginPanel final JMenuItem reset = new JMenuItem("Reset All"); reset.addActionListener(e -> { - overallKills = 0; - overallGp = 0; + // If not in detailed view, remove all, otherwise only remove for the currently detailed title + records.removeIf(r -> r.matches(currentView)); + boxes.removeIf(b -> b.matches(currentView)); updateOverall(); logsContainer.removeAll(); logsContainer.repaint(); @@ -110,8 +254,10 @@ class LootTrackerPanel extends PluginPanel popupMenu.add(reset); overallPanel.setComponentPopupMenu(popupMenu); - // Create loot logs wrapper + // Create loot boxes wrapper logsContainer.setLayout(new BoxLayout(logsContainer, BoxLayout.Y_AXIS)); + layoutPanel.add(actionsContainer); + layoutPanel.add(Box.createRigidArea(new Dimension(0, 5))); layoutPanel.add(overallPanel); layoutPanel.add(logsContainer); @@ -125,50 +271,129 @@ class LootTrackerPanel extends PluginPanel overallIcon.setIcon(new ImageIcon(img)); } - private static String htmlLabel(String key, long value) + /** + * Adds a new entry to the plugin. + * 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) { - final String valueStr = StackFormatter.quantityToStackSize(value); - return String.format(HTML_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR), key, valueStr); + final String subTitle = actorLevel > -1 ? "(lvl-" + actorLevel + ")" : ""; + final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, items, System.currentTimeMillis()); + records.add(record); + buildBox(record); } - void addLog(final String eventName, final int actorLevel, LootTrackerItemEntry[] items) + /** + * Changes grouping mode of panel + * @param group if loot should be grouped or not + */ + private void changeGrouping(boolean group) { - // Remove error and show overall + groupLoot = group; + rebuild(); + groupedLootBtn.setIcon(group ? GROUPED_LOOT_VIEW : GROUPED_LOOT_VIEW_FADED); + singleLootBtn.setIcon(group ? SINGLE_LOOT_VIEW_FADED : SINGLE_LOOT_VIEW); + } + + /** + * Rebuilds all the boxes from scratch using existing listed records, depending on the grouping mode. + */ + private void rebuild() + { + logsContainer.removeAll(); + boxes.clear(); + records.forEach(this::buildBox); + updateOverall(); + logsContainer.revalidate(); + logsContainer.repaint(); + } + + /** + * This method decides what to do with a new record, if a similar log exists, it will + * add its items to it, updating the log's overall price and kills. If not, a new log will be created + * to hold this entry's information. + */ + private void buildBox(LootTrackerRecord record) + { + // If this record is not part of current view, return + if (!record.matches(currentView)) + { + return; + } + + // Group all similar loot together + if (groupLoot) + { + for (LootTrackerBox box : boxes) + { + if (box.matches(record)) + { + box.combine(record); + updateOverall(); + return; + } + } + } + + // Show main view remove(errorPanel); + actionsContainer.setVisible(true); 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(); + final LootTrackerBox box = new LootTrackerBox(itemManager, record.getTitle(), record.getSubTitle()); + box.combine(record); - // Update overall - overallGp += box.getTotalPrice(); - overallKills += 1; - updateOverall(); + // Create popup menu + final JPopupMenu popupMenu = new JPopupMenu(); + popupMenu.setBorder(new EmptyBorder(5, 5, 5, 5)); + box.setComponentPopupMenu(popupMenu); // Create reset menu final JMenuItem reset = new JMenuItem("Reset"); reset.addActionListener(e -> { - overallGp -= box.getTotalPrice(); - overallKills -= 1; + records.removeAll(box.getRecords()); + boxes.remove(box); 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); + + // Create details menu + final JMenuItem details = new JMenuItem("View details"); + details.addActionListener(e -> + { + currentView = record.getTitle(); + detailsTitle.setText(currentView); + backBtn.setVisible(true); + rebuild(); + }); + + popupMenu.add(details); + + // Add box to panel + boxes.add(box); + logsContainer.add(box, 0); + + // Update overall + updateOverall(); } private void updateOverall() { + final long overallGp = boxes.stream().mapToLong(LootTrackerBox::getTotalPrice).sum(); + final int overallKills = boxes.stream().mapToInt(LootTrackerBox::getTotalKills).sum(); 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); + return String.format(HTML_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR), key, valueStr); + } } 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 9c359bad1d..2c75c0fd55 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 @@ -150,8 +150,8 @@ public class LootTrackerPlugin extends Plugin final Collection items = npcLootReceived.getItems(); final String name = npc.getName(); final int combat = npc.getCombatLevel(); - final LootTrackerItemEntry[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.addLog(name, combat, entries)); + final LootTrackerItem[] entries = buildEntries(stack(items)); + SwingUtilities.invokeLater(() -> panel.add(name, combat, entries)); } @Subscribe @@ -161,8 +161,8 @@ public class LootTrackerPlugin extends Plugin final Collection items = playerLootReceived.getItems(); final String name = player.getName(); final int combat = player.getCombatLevel(); - final LootTrackerItemEntry[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.addLog(name, combat, entries)); + final LootTrackerItem[] entries = buildEntries(stack(items)); + SwingUtilities.invokeLater(() -> panel.add(name, combat, entries)); } @Subscribe @@ -209,8 +209,8 @@ public class LootTrackerPlugin extends Plugin if (!items.isEmpty()) { - final LootTrackerItemEntry[] entries = buildEntries(stack(items)); - SwingUtilities.invokeLater(() -> panel.addLog(eventType, -1, entries)); + final LootTrackerItem[] entries = buildEntries(stack(items)); + SwingUtilities.invokeLater(() -> panel.add(eventType, -1, entries)); } else { @@ -252,19 +252,19 @@ public class LootTrackerPlugin extends Plugin } } - private LootTrackerItemEntry[] buildEntries(final Collection itemStacks) + private LootTrackerItem[] buildEntries(final Collection itemStacks) { return itemStacks.stream().map(itemStack -> { final ItemComposition itemComposition = itemManager.getItemComposition(itemStack.getId()); final int realItemId = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemStack.getId(); - final long price = (long)itemManager.getItemPrice(realItemId) * (long)itemStack.getQuantity(); + final long price = (long) itemManager.getItemPrice(realItemId) * (long) itemStack.getQuantity(); - return new LootTrackerItemEntry( + return new LootTrackerItem( itemStack.getId(), itemComposition.getName(), itemStack.getQuantity(), price); - }).toArray(LootTrackerItemEntry[]::new); + }).toArray(LootTrackerItem[]::new); } } 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 new file mode 100644 index 0000000000..0d75ec70c3 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerRecord.java @@ -0,0 +1,51 @@ +/* + * 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); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/loottracker/back_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/loottracker/back_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..96b9200f60ed5303ee3bd6c247e8811a250b83fd GIT binary patch literal 15109 zcmeI3PmB{)9LEPS3MClgpJ-NuNdZA&I{!L5ov|%}7ThHk*JY7K6T|DwYdfKxDKoRQ zyBEl+2M!oS5)Dz|U`$NZ;6)E48WRpCs3-qKH1XoWL=sK(LO9@?nf{r5ZC^G4t}h6ALapu&{r|SU75EW^!jfk!|ZJKm+=KZ8z$+r?+#-Fs_ci2g7`l z4X5}=bIDynLUy(^#TFbFvPx>4GlX%KRkf6;sA^i>!AgRt@Pf?C0w>9OTF}!H+j%AP z3G^-NniYMzxVMuIS-E7*_nSJ;w_2@KOHMg%l@~Ql;{}PABo58sym{LPZO-;KMM1i8 ziqJD$tLa;g%?5En*_rcm$z+hI_v);x(d;F%y$(A>k#B=0FQx>3n3HMre42A^J-js2 z;9(s$pzV9ePaNvktU12p)tnJ2d-BM{XirL|UhbjS+h`0;?fH9-BLp3!Lp{Bj`6lG2 zq36uG2HbNTEoIZN)%#YZzd1`H1Vmdi5OqO_1&L;2 zF-neURpy7skmPub*i1YI>K_yMU|CV(b7OH#L$5e)1NeEX0jiL1+SM%IAB@fHRh3?F z>W+&p3+Cl4KWsSW6;_%)X?s4f4LDiMqk)uVnR-R7ND5R%&Xi?^(}eUmSFR{!4yxk- zmZ zRG5}~iDJP95= z5wr(H6qbzkFRSO-akB=ub9o4&_%Zj zV?xI<+N#)XS=fWoD7xuG_nUn0hI61jd#Bi){L)|1os5dbqDe3yWRv0|^TAY#3j;zn zDK0V}Or^LmAY_x`BJ;siiVFimHYqMLA55jVFd$@;;v)0GREi4&LN+NbG9OH(xG*4O zlj0)t!BmP1141?_E;1iXrMNI4WRv0|^TAY#3j;znDK0V}Or^LmAY_x`BJ;siiVFim zHYqMLA55jVFd$@;;v)0GREi4&LN+NbG9OH(xG*4Olj0)t!BmP1141?_E;1iXrMNI4 zWRv0|^TAY#3j;znDK0V}Or^LmAY_x`BJ;siiVFimHi@_r{YN;Vjh@?Vp$9aNZ9itB zhdWtgV!FgI3tJfGT`cp*{LT+9 zzj)&2!mZbTc>L${n?HZ|xm7psP8|7r>(qT;pZ@5=zO6r9SpDgDAKv)YF*h>%UR}As Uefie+!Go%kd-fMU-hE{8Uz>eky#N3J literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/loottracker/grouped_loot_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/loottracker/grouped_loot_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5827719316db55d4832bc49a3dec68b40906fb35 GIT binary patch literal 15596 zcmeI3Yitx%6o97!3e}QEFd{!>SRX0i?9A+IcE|0~(nVY2!rE48F(J;*+-(PTXNH+6 zyRC?g=no%JjL-OtfNvo&K4OCM2udLS(5Q$Sd|?!X_=v`+5nS)=bN05qX(IkHceAf^ z&pqeN{q8wu?w`3mx$MfO#<`6YMKyJG#=GI~+3?di{Y?0s`g!X{_%p-k?6=_fEa%fe zJ@VKbifRa{9UaN!pl<8dpl;Az9UZh`>S;9tC@TNiw!2@xYHxCO+k1CzK9XECb*+)V zuchVn<2UTv$}DYu>EQeWA8&o?D)rn|s}63sb<6cPNP1!TwWC zEej6)+PMpT+;H@6!#Royxjz_278@&*Sa}{&(%vldy z`ofki-c5zGcBgiJqKW3V)%`b~IO*Sh^xeZ>+|xYTH*(p^_HggfFBjhR_?JhI?8{#; zQz;aZ+V9`6yZ0?Wb!_GG6h(sha)=qcdM zXAQ`kqN0oQh9s{6Hk|?ks@CfHX~!-Pttzda6~P3TFgn1X+Bsr^o{?p}^2i!FqIed^ z8l!m;I>-WBqVw5|W{LS$PtmUk|2xC1hb~UB*R*=toq%*-B1w1XCZL1f5F>LTffgcO zJ}3wQ;WFCC@j;gJvwn{8`NaSy27Gk+<%u=Ie^FCOi{0@n%Hg2Y>KU|cLuA=pF6Yhp zy}CKT@{ve{<$SEq$G{nkHLTfEp3$s}N=_<%;=qzk)v#4vqaD9eN*}UYJsu}e^;KS1 z)~E*3tTH=DkL?0;5C%Zsr7>h?ihvcF1sRZqF)e7vkGlq2P1iQ3ZV8TN!f+t3PGII} zN@m4WDD6-b=AYPM0?F3RUR}?`N?BHG6R=A-Sw*+3P&Gx*Sqq{pYEs+pnl})aY!HK% zVTOyqcP}3j1H9-DE#i2Q<6KTkGZ7yNnEQ%kOOx?ZHy8DzD{6XpQeJ8^>wP2=VwYyw zk|u+$cnq%HtE!6V~V*2!t4(2Vo{6M|@0J;RGM>2_WECDuG_M*4B?!6dASHK@7>XfK!;Qo=Rm_TTZp3I5x!s7bV&0Ju%ryUjQl8jn*!!YFRc@ z$8P835S0A8%qNbK8w46I{Y$QjvnF<8>1jJBnV@|DX2@hJv*fkj40Ru6onD<$|AURN zq;>yp8zxs(|BG#?WwAUcX#+rsv6Td_3`}n4O8Z$~S(R!XTZz$HXQ_08cG%gRg0<>1 zqFAs?q=EWT=d#QBQ)wRx2&Mx{n1g-E7iOe%AjO1*aG3Fj(m@}`DZIeR<0oBdv_3|Z zo!k|=7Sf?-bTgqVAm(>l*M@5#T~_e?n=BoF%azrAD;VE*fn7VOT})*N7*rB==?sX`(=iT^99I|<5=GLE}Od5 zsA)gm)U8HMYnIn)svYx{qanjCE9XQZ7BTn0!!L-1fU%A&)lMN-X!OkZ4u_dUz$ z-#kQ8v_Tc)8uq&kA&{D(OHno~<(KQ2TFm9rC_Dti^EX>PfR9P@>Rsb>{;93#bb3X6 zkst^Vyhw22`5;zSd|oTcAmT29k)wM7j2CTYg@HoLYSSo+Yao`3^P-9 zTM>)E2O|-U&-h9dd<%*35#tl1X(WEosE86%^ykr<*zaJ{qdy=`xrs9((8?Cacf z|L4y9?zv~~mwQ`c#Z^s>^BXCOYU=8Yb;I9t;HPoMboib8W%pM2Gt=r^<-qni-lu_j z@ZotB)ezJ>IueNi(>0v|)1tdNI%vx_Q+gUuRPNB8J6^c@-NgBAAKw1>(ZrIe8?4;j z&CRczSUb9#Sw8#u0}J+lw)?TG_4C%OIk0)#t{cyun|*uV{%fto>9g`}8%`XVyZ`z9*&;i*mB_muel&C|F+_nC%uU2=E-n?1~P_kph0ADVhs#B}XrelfEBj_F$YL!&P#O;apM-QjQY?j%w?ctD6Sx#Z{8#pxf&6c>`5mrTfZQ zm4OVW#12wqbBe|CevX|$6p!O&nasFK$6dY=UPAU(z;G`a z?;#x`QAu^atIh|xq4WJE!dm?Y1O3*iETwMuRm|~fwQf{eRbFT4Zd53Z zhBVixya%ydVA=!sj362e1jImuMVnDYLeGJ8UrdLO3&$&aJVa8^ppJ2ct7?Q0NL5(l zC<%)hRkOP*l!{_2+&jW;D_hyOj;SJ*V*79Ysov4Q85M~|20?(}O@a&02k{bI2oStU zaN+qNUV;k&f;R~+JRihMa3MhOCc%a0gLnxp1PI4T z@Fu~9=Yx0&E(8ePB)IT=5HG=n0KuCC7oHE|CAbhEc$475^Fh1>7Xk!t5?pvbh?n3( zfZ$Dn3(p7f5?lxnyh(83`5<0`3ju;R2`)Sz#7l4?K=39OS7Y@gC@|ph&n!IWIc4p( zQFum_Ry(`n6g7MqMUB8yx_{*1?{SLioenRz literal 0 HcmV?d00001