loot tracker: store aggregated drops

This modifies both the service and the client plugin to no longer store
loot per-kill, with the exception of the current sessions kill log. The
amount of loot data returned now from the service defaults to the last
1024 unique events
This commit is contained in:
Adam
2020-01-09 17:35:17 -05:00
committed by Adam
parent bb6ac3a2be
commit 97b4f0d56f
10 changed files with 203 additions and 163 deletions

View File

@@ -33,9 +33,11 @@ import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.swing.Box;
import javax.swing.BoxLayout;
@@ -48,10 +50,10 @@ import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import lombok.AccessLevel;
import lombok.Getter;
import net.runelite.client.util.AsyncBufferedImage;
import net.runelite.client.game.ItemManager;
import net.runelite.client.ui.ColorScheme;
import net.runelite.client.ui.FontManager;
import net.runelite.client.util.AsyncBufferedImage;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.QuantityFormatter;
import net.runelite.client.util.Text;
@@ -65,15 +67,15 @@ class LootTrackerBox extends JPanel
private final JLabel priceLabel = new JLabel();
private final JLabel subTitleLabel = new JLabel();
private final JPanel logTitle = new JPanel();
private final JLabel titleLabel = new JLabel();
private final ItemManager itemManager;
@Getter(AccessLevel.PACKAGE)
private final String id;
private final LootTrackerPriceType priceType;
private final boolean showPriceType;
private int kills;
@Getter
private final List<LootTrackerRecord> records = new ArrayList<>();
private final List<LootTrackerItem> items = new ArrayList<>();
private long totalPrice;
private boolean hideIgnoredItems;
@@ -102,6 +104,7 @@ class LootTrackerBox extends JPanel
logTitle.setBorder(new EmptyBorder(7, 7, 7, 7));
logTitle.setBackground(ColorScheme.DARKER_GRAY_COLOR.darker());
JLabel titleLabel = new JLabel();
titleLabel.setText(Text.removeTags(id));
titleLabel.setFont(FontManager.getRunescapeSmallFont());
titleLabel.setForeground(Color.WHITE);
@@ -131,15 +134,13 @@ class LootTrackerBox extends JPanel
}
/**
* Returns total amount of kills, removing ignored kills when necessary
* Returns total amount of kills
*
* @return total amount of kills
*/
private long getTotalKills()
private int getTotalKills()
{
return hideIgnoredItems
? records.stream().filter(r -> !Arrays.stream(r.getItems()).allMatch(LootTrackerItem::isIgnored)).count()
: records.size();
return kills;
}
/**
@@ -171,16 +172,32 @@ class LootTrackerBox extends JPanel
/**
* 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)
void addKill(final LootTrackerRecord record)
{
if (!matches(record))
{
throw new IllegalArgumentException(record.toString());
}
records.add(record);
kills += record.getKills();
outer:
for (LootTrackerItem item : record.getItems())
{
// Combine it into an existing item if one already exists
for (int idx = 0; idx < items.size(); ++idx)
{
LootTrackerItem i = items.get(idx);
if (item.getId() == i.getId())
{
items.set(idx, new LootTrackerItem(i.getId(), i.getName(), i.getQuantity() + item.getQuantity(), i.getGePrice(), i.getHaPrice(), i.isIgnored()));
continue outer;
}
}
items.add(item);
}
}
void rebuild()
@@ -245,69 +262,31 @@ class LootTrackerBox extends JPanel
*/
private void buildItems()
{
final List<LootTrackerItem> allItems = new ArrayList<>();
final List<LootTrackerItem> items = new ArrayList<>();
totalPrice = 0;
for (LootTrackerRecord record : records)
{
allItems.addAll(Arrays.asList(record.getItems()));
}
List<LootTrackerItem> items = this.items;
if (hideIgnoredItems)
{
/* If all the items in this box are ignored */
boolean hideBox = allItems.stream().allMatch(LootTrackerItem::isIgnored);
setVisible(!hideBox);
if (hideBox)
{
return;
}
items = items.stream().filter(item -> !item.isIgnored()).collect(Collectors.toList());
}
for (final LootTrackerItem entry : allItems)
boolean isHidden = items.isEmpty();
setVisible(!isHidden);
if (isHidden)
{
if (entry.isIgnored() && hideIgnoredItems)
{
continue;
}
totalPrice += priceType == LootTrackerPriceType.HIGH_ALCHEMY ? entry.getHaPrice() : entry.getGePrice();
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 gePricePerItem = entry.getGePrice() == 0 ? 0 : (entry.getGePrice() / entry.getQuantity());
long haPricePerItem = entry.getHaPrice() == 0 ? 0 : (entry.getHaPrice() / entry.getQuantity());
items.add(new LootTrackerItem(entry.getId(), entry.getName(), newQuantity, gePricePerItem * newQuantity, haPricePerItem * newQuantity, entry.isIgnored()));
}
else
{
items.add(entry);
}
return;
}
if (priceType == LootTrackerPriceType.HIGH_ALCHEMY)
{
items.sort((i1, i2) -> Long.compare(i2.getHaPrice(), i1.getHaPrice()));
}
else
{
items.sort((i1, i2) -> Long.compare(i2.getGePrice(), i1.getGePrice()));
}
ToLongFunction<LootTrackerItem> getPrice = priceType == LootTrackerPriceType.HIGH_ALCHEMY
? LootTrackerItem::getTotalHaPrice
: LootTrackerItem::getTotalGePrice;
totalPrice = items.stream()
.mapToLong(getPrice)
.sum();
items.sort(Comparator.comparingLong(getPrice).reversed());
// Calculates how many rows need to be display to fit all items
final int rowSize = ((items.size() % ITEMS_PER_ROW == 0) ? 0 : 1) + items.size() / ITEMS_PER_ROW;
@@ -372,8 +351,8 @@ class LootTrackerBox extends JPanel
{
final String name = item.getName();
final int quantity = item.getQuantity();
final long gePrice = item.getGePrice();
final long haPrice = item.getHaPrice();
final long gePrice = item.getTotalGePrice();
final long haPrice = item.getTotalHaPrice();
final String ignoredLabel = item.isIgnored() ? " - Ignored" : "";
return "<html>" + name + " x " + quantity + ignoredLabel
+ "<br>GE: " + QuantityFormatter.quantityToStackSize(gePrice)

View File

@@ -29,19 +29,24 @@ import lombok.Getter;
import lombok.Setter;
@AllArgsConstructor
@Getter
class LootTrackerItem
{
@Getter
private final int id;
@Getter
private final String name;
@Getter
private final int quantity;
@Getter
private final long gePrice;
@Getter
private final long haPrice;
@Getter
private int quantity;
private final int gePrice;
private final int haPrice;
@Setter
private boolean ignored;
long getTotalGePrice()
{
return (long) gePrice * quantity;
}
long getTotalHaPrice()
{
return (long) haPrice * quantity;
}
}

View File

@@ -25,6 +25,7 @@
*/
package net.runelite.client.plugins.loottracker;
import static com.google.common.collect.Iterables.concat;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
@@ -35,6 +36,7 @@ import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
@@ -101,8 +103,10 @@ class LootTrackerPanel extends PluginPanel
private final JLabel groupedLootBtn = new JLabel();
private final JLabel collapseBtn = new JLabel();
// Log collection
private final List<LootTrackerRecord> records = new ArrayList<>();
// Aggregate of all kills
private final List<LootTrackerRecord> aggregateRecords = new ArrayList<>();
// Individual records for the individual kills this session
private final List<LootTrackerRecord> sessionRecords = new ArrayList<>();
private final List<LootTrackerBox> boxes = new ArrayList<>();
private final ItemManager itemManager;
@@ -330,7 +334,8 @@ class LootTrackerPanel extends PluginPanel
}
// If not in detailed view, remove all, otherwise only remove for the currently detailed title
records.removeIf(r -> r.matches(currentView));
sessionRecords.removeIf(r -> r.matches(currentView));
aggregateRecords.removeIf(r -> r.matches(currentView));
boxes.removeIf(b -> b.matches(currentView));
updateOverall();
logsContainer.removeAll();
@@ -377,7 +382,7 @@ class LootTrackerPanel extends PluginPanel
private boolean isAllCollapsed()
{
return boxes.stream()
.filter(i -> i.isCollapsed())
.filter(LootTrackerBox::isCollapsed)
.count() == boxes.size();
}
@@ -394,8 +399,9 @@ class LootTrackerPanel extends PluginPanel
void add(final String eventName, final int actorLevel, LootTrackerItem[] items)
{
final String subTitle = actorLevel > -1 ? "(lvl-" + actorLevel + ")" : "";
final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, items);
records.add(record);
final LootTrackerRecord record = new LootTrackerRecord(eventName, subTitle, items, 1);
sessionRecords.add(record);
LootTrackerBox box = buildBox(record);
if (box != null)
{
@@ -409,7 +415,7 @@ class LootTrackerPanel extends PluginPanel
*/
void addRecords(Collection<LootTrackerRecord> recs)
{
records.addAll(recs);
aggregateRecords.addAll(recs);
rebuild();
}
@@ -466,14 +472,11 @@ class LootTrackerPanel extends PluginPanel
*/
void updateIgnoredRecords()
{
for (LootTrackerRecord r : records)
for (LootTrackerRecord record : concat(aggregateRecords, sessionRecords))
{
for (LootTrackerItem item : r.getItems())
for (LootTrackerItem item : record.getItems())
{
if (plugin.isIgnored(item.getName()) != item.isIgnored())
{
item.setIgnored(plugin.isIgnored(item.getName()));
}
item.setIgnored(plugin.isIgnored(item.getName()));
}
}
@@ -487,15 +490,25 @@ class LootTrackerPanel extends PluginPanel
{
logsContainer.removeAll();
boxes.clear();
int start = 0;
if (!groupLoot && records.size() > MAX_LOOT_BOXES)
if (groupLoot)
{
start = records.size() - MAX_LOOT_BOXES;
aggregateRecords.forEach(this::buildBox);
sessionRecords.forEach(this::buildBox);
}
for (int i = start; i < records.size(); i++)
else
{
buildBox(records.get(i));
int start = 0;
if (sessionRecords.size() > MAX_LOOT_BOXES)
{
start = sessionRecords.size() - MAX_LOOT_BOXES;
}
for (int i = start; i < sessionRecords.size(); i++)
{
buildBox(sessionRecords.get(i));
}
}
boxes.forEach(LootTrackerBox::rebuild);
updateOverall();
logsContainer.revalidate();
@@ -522,7 +535,7 @@ class LootTrackerPanel extends PluginPanel
{
if (box.matches(record))
{
box.combine(record);
box.addKill(record);
return box;
}
}
@@ -536,7 +549,7 @@ class LootTrackerPanel extends PluginPanel
// Create box
final LootTrackerBox box = new LootTrackerBox(itemManager, record.getTitle(), record.getSubTitle(),
hideIgnoredItems, config.priceType(), config.showPriceType(), plugin::toggleItem);
box.combine(record);
box.addKill(record);
// Create popup menu
final JPopupMenu popupMenu = new JPopupMenu();
@@ -568,7 +581,13 @@ class LootTrackerPanel extends PluginPanel
final JMenuItem reset = new JMenuItem("Reset");
reset.addActionListener(e ->
{
records.removeAll(box.getRecords());
Predicate<LootTrackerRecord> match = groupLoot
// With grouped loot, remove any record with this title
? r -> r.matches(record.getTitle())
// Otherwise remove specifically this entry
: r -> r.equals(record);
sessionRecords.removeIf(match);
aggregateRecords.removeIf(match);
boxes.remove(box);
updateOverall();
logsContainer.remove(box);
@@ -614,7 +633,7 @@ class LootTrackerPanel extends PluginPanel
long overallGe = 0;
long overallHa = 0;
for (LootTrackerRecord record : records)
for (LootTrackerRecord record : concat(aggregateRecords, sessionRecords))
{
if (!record.matches(currentView))
{
@@ -631,13 +650,13 @@ class LootTrackerPanel extends PluginPanel
continue;
}
overallGe += item.getGePrice();
overallHa += item.getHaPrice();
overallGe += item.getTotalGePrice();
overallHa += item.getTotalHaPrice();
}
if (present > 0)
{
overallKills++;
overallKills += record.getKills();
}
}

View File

@@ -90,6 +90,7 @@ import net.runelite.client.ui.NavigationButton;
import net.runelite.client.util.ImageUtil;
import net.runelite.client.util.Text;
import net.runelite.http.api.loottracker.GameItem;
import net.runelite.http.api.loottracker.LootAggregate;
import net.runelite.http.api.loottracker.LootRecord;
import net.runelite.http.api.loottracker.LootRecordType;
import net.runelite.http.api.loottracker.LootTrackerClient;
@@ -276,13 +277,12 @@ public class LootTrackerPlugin extends Plugin
executor.submit(() ->
{
Collection<LootRecord> lootRecords;
if (!config.syncPanel())
{
return;
}
Collection<LootAggregate> lootRecords;
try
{
lootRecords = lootTrackerClient.get();
@@ -624,8 +624,8 @@ public class LootTrackerPlugin extends Plugin
{
final ItemComposition itemComposition = itemManager.getItemComposition(itemId);
final int realItemId = itemComposition.getNote() != -1 ? itemComposition.getLinkedNoteId() : itemId;
final long gePrice = (long) itemManager.getItemPrice(realItemId) * (long) quantity;
final long haPrice = (long) Math.round(itemComposition.getPrice() * Constants.HIGH_ALCHEMY_MULTIPLIER) * (long) quantity;
final int gePrice = itemManager.getItemPrice(realItemId);
final int haPrice = Math.round(itemComposition.getPrice() * Constants.HIGH_ALCHEMY_MULTIPLIER);
final boolean ignored = ignoredItems.contains(itemComposition.getName());
return new LootTrackerItem(
@@ -651,17 +651,17 @@ public class LootTrackerPlugin extends Plugin
.collect(Collectors.toList());
}
private Collection<LootTrackerRecord> convertToLootTrackerRecord(final Collection<LootRecord> records)
private Collection<LootTrackerRecord> convertToLootTrackerRecord(final Collection<LootAggregate> records)
{
return records.stream()
.sorted(Comparator.comparing(LootRecord::getTime))
.sorted(Comparator.comparing(LootAggregate::getLast_time))
.map(record ->
{
LootTrackerItem[] drops = record.getDrops().stream().map(itemStack ->
buildLootTrackerItem(itemStack.getId(), itemStack.getQty())
).toArray(LootTrackerItem[]::new);
return new LootTrackerRecord(record.getEventId(), "", drops);
return new LootTrackerRecord(record.getEventId(), "", drops, record.getAmount());
})
.collect(Collectors.toCollection(ArrayList::new));
}

View File

@@ -32,9 +32,11 @@ class LootTrackerRecord
private final String title;
private final String subTitle;
private final LootTrackerItem[] items;
private final int kills;
/**
* Checks if this record matches specified id
*
* @param id other record id
* @return true if match is made
*/