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:
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 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.http.api.loottracker;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LootAggregate
|
||||
{
|
||||
private String eventId;
|
||||
private LootRecordType type;
|
||||
private Collection<GameItem> drops;
|
||||
private Instant first_time;
|
||||
private Instant last_time;
|
||||
private int amount;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ public class LootTrackerClient
|
||||
});
|
||||
}
|
||||
|
||||
public Collection<LootRecord> get() throws IOException
|
||||
public Collection<LootAggregate> get() throws IOException
|
||||
{
|
||||
HttpUrl url = RuneLiteAPI.getApiBase().newBuilder()
|
||||
.addPathSegment("loottracker")
|
||||
@@ -101,7 +101,7 @@ public class LootTrackerClient
|
||||
}
|
||||
|
||||
InputStream in = response.body().byteStream();
|
||||
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), new TypeToken<List<LootRecord>>()
|
||||
return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), new TypeToken<List<LootAggregate>>()
|
||||
{
|
||||
}.getType());
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ import net.runelite.http.api.loottracker.LootRecordType;
|
||||
class LootResult
|
||||
{
|
||||
private int killId;
|
||||
private Instant time;
|
||||
private Instant first_time;
|
||||
private Instant last_time;
|
||||
private LootRecordType type;
|
||||
private String eventId;
|
||||
private int amount;
|
||||
private int itemId;
|
||||
private int itemQuantity;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import net.runelite.http.api.loottracker.LootAggregate;
|
||||
import net.runelite.http.api.loottracker.LootRecord;
|
||||
import net.runelite.http.service.account.AuthFilter;
|
||||
import net.runelite.http.service.account.beans.SessionEntry;
|
||||
@@ -67,7 +68,7 @@ public class LootTrackerController
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Collection<LootRecord> getLootRecords(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "count", defaultValue = "1024") int count, @RequestParam(value = "start", defaultValue = "0") int start) throws IOException
|
||||
public Collection<LootAggregate> getLootAggregate(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "count", defaultValue = "1024") int count, @RequestParam(value = "start", defaultValue = "0") int start) throws IOException
|
||||
{
|
||||
SessionEntry e = auth.handle(request, response);
|
||||
if (e == null)
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import net.runelite.http.api.loottracker.GameItem;
|
||||
import net.runelite.http.api.loottracker.LootAggregate;
|
||||
import net.runelite.http.api.loottracker.LootRecord;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -41,35 +42,37 @@ import org.sql2o.Sql2o;
|
||||
@Service
|
||||
public class LootTrackerService
|
||||
{
|
||||
// Table for storing individual LootRecords
|
||||
private static final String CREATE_KILLS = "CREATE TABLE IF NOT EXISTS `kills` (\n"
|
||||
+ " `id` INT AUTO_INCREMENT UNIQUE,\n"
|
||||
+ " `time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\n"
|
||||
+ " `accountId` INT NOT NULL,\n"
|
||||
+ " `type` enum('NPC', 'PLAYER', 'EVENT', 'UNKNOWN') NOT NULL,\n"
|
||||
+ " `eventId` VARCHAR(255) NOT NULL,\n"
|
||||
+ " PRIMARY KEY (id),\n"
|
||||
+ " FOREIGN KEY (accountId) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,\n"
|
||||
+ " INDEX idx_acc (accountId, time),"
|
||||
+ " INDEX idx_time (time)"
|
||||
+ ") ENGINE=InnoDB";
|
||||
private static final String CREATE_KILLS = "CREATE TABLE IF NOT EXISTS `loottracker_kills` (\n" +
|
||||
" `id` int(11) NOT NULL AUTO_INCREMENT,\n" +
|
||||
" `first_time` timestamp NOT NULL DEFAULT current_timestamp(),\n" +
|
||||
" `last_time` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),\n" +
|
||||
" `accountId` int(11) NOT NULL,\n" +
|
||||
" `type` enum('NPC','PLAYER','EVENT','UNKNOWN') NOT NULL,\n" +
|
||||
" `eventId` varchar(255) NOT NULL,\n" +
|
||||
" `amount` int(11) NOT NULL,\n" +
|
||||
" PRIMARY KEY (`id`),\n" +
|
||||
" FOREIGN KEY (accountId) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,\n" +
|
||||
" INDEX idx_acc_lasttime (`accountId` ,`last_time`),\n" +
|
||||
" UNIQUE INDEX idx_acc_type_event (`accountId`, `type`, `eventId`),\n" +
|
||||
" INDEX idx_time (last_time)" +
|
||||
") ENGINE=InnoDB;";
|
||||
|
||||
// Table for storing Items received as loot for individual LootRecords
|
||||
private static final String CREATE_DROPS = "CREATE TABLE IF NOT EXISTS `drops` (\n"
|
||||
+ " `killId` INT NOT NULL,\n"
|
||||
+ " `itemId` INT NOT NULL,\n"
|
||||
+ " `itemQuantity` INT NOT NULL,\n"
|
||||
+ " FOREIGN KEY (killId) REFERENCES kills(id) ON DELETE CASCADE\n"
|
||||
+ ") ENGINE=InnoDB";
|
||||
private static final String CREATE_DROPS = "CREATE TABLE IF NOT EXISTS `loottracker_drops` (\n" +
|
||||
" `killId` int(11),\n" +
|
||||
" `itemId` int(11) NOT NULL,\n" +
|
||||
" `itemQuantity` int(11) NOT NULL,\n" +
|
||||
" UNIQUE INDEX idx_kill_item (`killId`, `itemId`),\n" +
|
||||
" FOREIGN KEY (killId) REFERENCES loottracker_kills(id) ON DELETE CASCADE\n" +
|
||||
") ENGINE=InnoDB;\n";
|
||||
|
||||
// Queries for inserting kills
|
||||
private static final String INSERT_KILL_QUERY = "INSERT INTO kills (accountId, type, eventId) VALUES (:accountId, :type, :eventId)";
|
||||
private static final String INSERT_DROP_QUERY = "INSERT INTO drops (killId, itemId, itemQuantity) VALUES (:killId, :itemId, :itemQuantity)";
|
||||
private static final String INSERT_KILL_QUERY = "INSERT INTO loottracker_kills (accountId, type, eventId, amount) VALUES (:accountId, :type, :eventId, 1) ON DUPLICATE KEY UPDATE amount = amount + 1";
|
||||
private static final String INSERT_DROP_QUERY = "INSERT INTO loottracker_drops (killId, itemId, itemQuantity) VALUES (:killId, :itemId, :itemQuantity) ON DUPLICATE KEY UPDATE itemQuantity = itemQuantity + :itemQuantity";
|
||||
|
||||
private static final String SELECT_LOOT_QUERY = "SELECT killId,time,type,eventId,itemId,itemQuantity FROM kills JOIN drops ON drops.killId = kills.id WHERE accountId = :accountId ORDER BY TIME DESC LIMIT :limit OFFSET :offset";
|
||||
private static final String SELECT_LOOT_QUERY = "SELECT killId,first_time,last_time,type,eventId,amount,itemId,itemQuantity FROM loottracker_kills JOIN loottracker_drops ON loottracker_drops.killId = loottracker_kills.id WHERE accountId = :accountId ORDER BY last_time DESC LIMIT :limit OFFSET :offset";
|
||||
|
||||
private static final String DELETE_LOOT_ACCOUNT = "DELETE FROM kills WHERE accountId = :accountId";
|
||||
private static final String DELETE_LOOT_ACCOUNT_EVENTID = "DELETE FROM kills WHERE accountId = :accountId AND eventId = :eventId";
|
||||
private static final String DELETE_LOOT_ACCOUNT = "DELETE FROM loottracker_kills WHERE accountId = :accountId";
|
||||
private static final String DELETE_LOOT_ACCOUNT_EVENTID = "DELETE FROM loottracker_kills WHERE accountId = :accountId AND eventId = :eventId";
|
||||
|
||||
private final Sql2o sql2o;
|
||||
|
||||
@@ -96,8 +99,8 @@ public class LootTrackerService
|
||||
{
|
||||
try (Connection con = sql2o.beginTransaction())
|
||||
{
|
||||
// Kill Entry Query
|
||||
Query killQuery = con.createQuery(INSERT_KILL_QUERY, true);
|
||||
Query insertDrop = con.createQuery(INSERT_DROP_QUERY);
|
||||
|
||||
for (LootRecord record : records)
|
||||
{
|
||||
@@ -105,41 +108,26 @@ public class LootTrackerService
|
||||
.addParameter("accountId", accountId)
|
||||
.addParameter("type", record.getType())
|
||||
.addParameter("eventId", record.getEventId())
|
||||
.addToBatch();
|
||||
}
|
||||
.executeUpdate();
|
||||
Object[] keys = con.getKeys();
|
||||
|
||||
killQuery.executeBatch();
|
||||
Object[] keys = con.getKeys();
|
||||
|
||||
if (keys.length != records.size())
|
||||
{
|
||||
throw new RuntimeException("Mismatch in keys vs records size");
|
||||
}
|
||||
|
||||
Query insertDrop = con.createQuery(INSERT_DROP_QUERY);
|
||||
|
||||
// Append all queries for inserting drops
|
||||
int idx = 0;
|
||||
for (LootRecord record : records)
|
||||
{
|
||||
for (GameItem drop : record.getDrops())
|
||||
{
|
||||
insertDrop
|
||||
.addParameter("killId", keys[idx])
|
||||
.addParameter("killId", keys[0])
|
||||
.addParameter("itemId", drop.getId())
|
||||
.addParameter("itemQuantity", drop.getQty())
|
||||
.addToBatch();
|
||||
}
|
||||
|
||||
++idx;
|
||||
insertDrop.executeBatch();
|
||||
}
|
||||
|
||||
insertDrop.executeBatch();
|
||||
con.commit(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<LootRecord> get(int accountId, int limit, int offset)
|
||||
public Collection<LootAggregate> get(int accountId, int limit, int offset)
|
||||
{
|
||||
List<LootResult> lootResults;
|
||||
|
||||
@@ -153,7 +141,7 @@ public class LootTrackerService
|
||||
}
|
||||
|
||||
LootResult current = null;
|
||||
List<LootRecord> lootRecords = new ArrayList<>();
|
||||
List<LootAggregate> lootRecords = new ArrayList<>();
|
||||
List<GameItem> gameItems = new ArrayList<>();
|
||||
|
||||
for (LootResult lootResult : lootResults)
|
||||
@@ -162,7 +150,7 @@ public class LootTrackerService
|
||||
{
|
||||
if (!gameItems.isEmpty())
|
||||
{
|
||||
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime());
|
||||
LootAggregate lootRecord = new LootAggregate(current.getEventId(), current.getType(), gameItems, current.getFirst_time(), current.getLast_time(), current.getAmount());
|
||||
lootRecords.add(lootRecord);
|
||||
|
||||
gameItems = new ArrayList<>();
|
||||
@@ -177,7 +165,7 @@ public class LootTrackerService
|
||||
|
||||
if (!gameItems.isEmpty())
|
||||
{
|
||||
LootRecord lootRecord = new LootRecord(current.getEventId(), current.getType(), gameItems, current.getTime());
|
||||
LootAggregate lootRecord = new LootAggregate(current.getEventId(), current.getType(), gameItems, current.getFirst_time(), current.getLast_time(), current.getAmount());
|
||||
lootRecords.add(lootRecord);
|
||||
}
|
||||
|
||||
@@ -204,12 +192,12 @@ public class LootTrackerService
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = 15 * 60 * 1000)
|
||||
@Scheduled(fixedDelay = 60 * 60 * 1000)
|
||||
public void expire()
|
||||
{
|
||||
try (Connection con = sql2o.open())
|
||||
{
|
||||
con.createQuery("delete from kills where time < current_timestamp() - interval 30 day")
|
||||
con.createQuery("delete from loottracker_kills where last_time < current_timestamp() - interval 30 day")
|
||||
.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user