loot tracker: store loot in config

Since loot is now aggregated, the data is little enough to store in
config. This allows loot to persist between sessions even when not
logged in.
This commit is contained in:
Adam
2022-03-06 15:12:33 -05:00
parent f2b43743c3
commit 64abf450d9
8 changed files with 527 additions and 98 deletions

View File

@@ -58,6 +58,7 @@ import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
@@ -410,7 +411,27 @@ public class ConfigManager
public List<String> getConfigurationKeys(String prefix)
{
return properties.keySet().stream().filter(v -> ((String) v).startsWith(prefix)).map(String.class::cast).collect(Collectors.toList());
return properties.keySet().stream()
.map(String.class::cast)
.filter(k -> k.startsWith(prefix))
.collect(Collectors.toList());
}
public List<String> getRSProfileConfigurationKeys(String group, String profile, String keyPrefix)
{
if (profile == null)
{
return Collections.emptyList();
}
assert profile.startsWith(RSPROFILE_GROUP);
String prefix = group + "." + profile + "." + keyPrefix;
return properties.keySet().stream()
.map(String.class::cast)
.filter(k -> k.startsWith(prefix))
.map(k -> splitKey(k)[KEY_SPLITTER_KEY])
.collect(Collectors.toList());
}
public static String getWholeKey(String groupName, String profile, String key)

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2022, 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.client.plugins.loottracker;
import java.time.Instant;
import java.util.Arrays;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.runelite.http.api.loottracker.LootRecordType;
@Data
@NoArgsConstructor
@EqualsAndHashCode(of = {"type", "name"})
class ConfigLoot
{
LootRecordType type;
String name;
int kills;
Instant first = Instant.now();
Instant last;
int[] drops;
ConfigLoot(LootRecordType type, String name)
{
this.type = type;
this.name = name;
this.drops = new int[0];
}
void add(int id, int qty)
{
for (int i = 0; i < drops.length; i += 2)
{
if (drops[i] == id)
{
drops[i + 1] += qty;
return;
}
}
drops = Arrays.copyOf(drops, drops.length + 2);
drops[drops.length - 2] = id;
drops[drops.length - 1] = qty;
}
int numDrops()
{
return drops.length / 2;
}
}

View File

@@ -72,6 +72,7 @@ class LootTrackerBox extends JPanel
private final ItemManager itemManager;
@Getter(AccessLevel.PACKAGE)
private final String id;
@Getter(AccessLevel.PACKAGE)
private final LootRecordType lootRecordType;
private final LootTrackerPriceType priceType;
private final boolean showPriceType;

View File

@@ -30,9 +30,11 @@ import net.runelite.client.config.ConfigGroup;
import net.runelite.client.config.ConfigItem;
import net.runelite.client.config.ConfigSection;
@ConfigGroup("loottracker")
@ConfigGroup(LootTrackerConfig.GROUP)
public interface LootTrackerConfig extends Config
{
String GROUP = "loottracker";
@ConfigSection(
name = "Ignored Entries",
description = "The Ignore items and Ignore groups options",
@@ -79,28 +81,6 @@ public interface LootTrackerConfig extends Config
return false;
}
@ConfigItem(
keyName = "saveLoot",
name = "Submit loot tracker data",
description = "Submit loot tracker data"
)
default boolean saveLoot()
{
return true;
}
@ConfigItem(
keyName = "syncPanel",
name = "Synchronize panel contents",
description = "Synchronize your local loot tracker with your server data (requires being signed in).<br/>" +
" This means the panel is filled with portions of your remote data on startup<br/>" +
" and deleting data in the panel also deletes it on the server."
)
default boolean syncPanel()
{
return true;
}
@ConfigItem(
keyName = "ignoredEvents",
name = "Ignored Loot Sources",

View File

@@ -52,6 +52,7 @@ import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.plaf.basic.BasicToggleButtonUI;
@@ -84,29 +85,31 @@ class LootTrackerPanel extends PluginPanel
private static final ImageIcon INVISIBLE_ICON_HOVER;
private static final ImageIcon COLLAPSE_ICON;
private static final ImageIcon EXPAND_ICON;
private static final ImageIcon IMPORT_ICON;
private static final String HTML_LABEL_TEMPLATE =
"<html><body style='color:%s'>%s<span style='color:white'>%s</span></body></html>";
private static final String SYNC_RESET_ALL_WARNING_TEXT =
"This will permanently delete the current loot from both the client and the RuneLite website.";
private static final String NO_SYNC_RESET_ALL_WARNING_TEXT =
"This will permanently delete the current loot from the client.";
private static final String RESET_ALL_WARNING_TEXT =
"<html>This will permanently delete <b>all</b> loot.</html>";
private static final String RESET_CURRENT_WARNING_TEXT =
"This will permanently delete \"%s\" loot.";
private static final String RESET_ONE_WARNING_TEXT =
"This will delete one kill.";
// When there is no loot, display this
private final PluginErrorPanel errorPanel = new PluginErrorPanel();
// When there is loot, display this. This contains the actions, overall, and log panel.
private final JPanel layoutPanel = new JPanel();
// Handle loot boxes
private final JPanel logsContainer = new JPanel();
// Handle overall session data
private final JPanel overallPanel;
private final JLabel overallKillsLabel = new JLabel();
private final JLabel overallGpLabel = new JLabel();
private final JLabel overallIcon = new JLabel();
// Details and navigation
private final JPanel actionsPanel;
private final JLabel detailsTitle = new JLabel();
private final JButton backBtn = new JButton();
private final JToggleButton viewHiddenBtn = new JToggleButton();
@@ -114,6 +117,8 @@ class LootTrackerPanel extends PluginPanel
private final JRadioButton groupedLootBtn = new JRadioButton();
private final JButton collapseBtn = new JButton();
private final JPanel importNoticePanel;
// Aggregate of all kills
private final List<LootTrackerRecord> aggregateRecords = new ArrayList<>();
// Individual records for the individual kills this session
@@ -158,6 +163,8 @@ class LootTrackerPanel extends PluginPanel
COLLAPSE_ICON = new ImageIcon(collapseImg);
EXPAND_ICON = new ImageIcon(expandedImg);
IMPORT_ICON = new ImageIcon(ImageUtil.loadImageResource(LootTrackerPlugin.class, "import_icon.png"));
}
LootTrackerPanel(final LootTrackerPlugin plugin, final ItemManager itemManager, final LootTrackerConfig config)
@@ -172,16 +179,18 @@ class LootTrackerPanel extends PluginPanel
setLayout(new BorderLayout());
// Create layout panel for wrapping
final JPanel layoutPanel = new JPanel();
layoutPanel.setLayout(new BoxLayout(layoutPanel, BoxLayout.Y_AXIS));
layoutPanel.setVisible(false);
add(layoutPanel, BorderLayout.NORTH);
final JPanel actionsPanel = buildActionsPanel();
final JPanel overallPanel = buildOverallPanel();
actionsPanel = buildActionsPanel();
overallPanel = buildOverallPanel();
importNoticePanel = createImportNoticePanel();
// Create loot boxes wrapper
logsContainer.setLayout(new BoxLayout(logsContainer, BoxLayout.Y_AXIS));
layoutPanel.add(actionsPanel);
layoutPanel.add(importNoticePanel);
layoutPanel.add(overallPanel);
layoutPanel.add(logsContainer);
@@ -202,6 +211,7 @@ class LootTrackerPanel extends PluginPanel
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, 3, 10, 0));
viewControls.setBackground(ColorScheme.DARKER_GRAY_COLOR);
@@ -287,6 +297,7 @@ class LootTrackerPanel extends PluginPanel
));
overallPanel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
overallPanel.setLayout(new BorderLayout());
overallPanel.setVisible(false);
// Add icon and contents
final JPanel overallInfo = new JPanel();
@@ -304,10 +315,8 @@ class LootTrackerPanel extends PluginPanel
final JMenuItem reset = new JMenuItem("Reset All");
reset.addActionListener(e ->
{
final LootTrackerClient client = plugin.getLootTrackerClient();
final boolean syncLoot = client.getUuid() != null && config.syncPanel();
final int result = JOptionPane.showOptionDialog(overallPanel,
syncLoot ? SYNC_RESET_ALL_WARNING_TEXT : NO_SYNC_RESET_ALL_WARNING_TEXT,
currentView == null ? RESET_ALL_WARNING_TEXT : String.format(RESET_CURRENT_WARNING_TEXT, currentView),
"Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
null, new String[]{"Yes", "No"}, "No");
@@ -325,9 +334,14 @@ class LootTrackerPanel extends PluginPanel
logsContainer.repaint();
// Delete all loot, or loot matching the current view
if (syncLoot)
if (currentView != null)
{
client.delete(currentView);
assert currentType != null;
plugin.removeLootConfig(currentType, currentView);
}
else
{
plugin.removeAllLoot();
}
});
@@ -340,6 +354,30 @@ class LootTrackerPanel extends PluginPanel
return overallPanel;
}
private JPanel createImportNoticePanel()
{
JPanel panel = new JPanel();
panel.setBackground(ColorScheme.DARKER_GRAY_COLOR);
panel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(5, 0, 0, 0, ColorScheme.DARK_GRAY_COLOR),
BorderFactory.createEmptyBorder(8, 10, 8, 10)
));
panel.setLayout(new BorderLayout());
final JLabel importLabel = new JLabel("<html>Missing saved loot? Click the<br>import button to import it.</html>");
importLabel.setForeground(Color.YELLOW);
panel.add(importLabel, BorderLayout.WEST);
JButton importButton = new JButton();
SwingUtil.removeButtonDecorations(importButton);
importButton.setIcon(IMPORT_ICON);
importButton.setToolTipText("Import old loot tracker data to current profile");
importButton.addActionListener(l -> plugin.importLoot());
panel.add(importButton, BorderLayout.EAST);
return panel;
}
void updateCollapseText()
{
collapseBtn.setSelected(isAllCollapsed());
@@ -389,6 +427,14 @@ class LootTrackerPanel extends PluginPanel
}
}
/**
* Clear all records in the panel
*/
void clearRecords()
{
aggregateRecords.clear();
}
/**
* Adds a Collection of records to the panel
*/
@@ -530,7 +576,8 @@ class LootTrackerPanel extends PluginPanel
// Show main view
remove(errorPanel);
layoutPanel.setVisible(true);
actionsPanel.setVisible(true);
overallPanel.setVisible(true);
// Create box
final LootTrackerBox box = new LootTrackerBox(itemManager, record.getTitle(), record.getType(), record.getSubTitle(),
@@ -571,10 +618,8 @@ class LootTrackerPanel extends PluginPanel
final JMenuItem reset = new JMenuItem("Reset");
reset.addActionListener(e ->
{
final LootTrackerClient client = plugin.getLootTrackerClient();
final boolean syncLoot = client.getUuid() != null && config.syncPanel();
final int result = JOptionPane.showOptionDialog(box,
syncLoot ? SYNC_RESET_ALL_WARNING_TEXT : NO_SYNC_RESET_ALL_WARNING_TEXT,
groupLoot ? String.format(RESET_CURRENT_WARNING_TEXT, box.getId()) : RESET_ONE_WARNING_TEXT,
"Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE,
null, new String[]{"Yes", "No"}, "No");
@@ -596,9 +641,9 @@ class LootTrackerPanel extends PluginPanel
logsContainer.repaint();
// Without loot being grouped we have no way to identify single kills to be deleted
if (client.getUuid() != null && groupLoot && config.syncPanel())
if (groupLoot)
{
client.delete(box.getId());
plugin.removeLootConfig(box.getLootRecordType(), box.getId());
}
});
@@ -691,4 +736,9 @@ class LootTrackerPanel extends PluginPanel
final String valueStr = QuantityFormatter.quantityToStackSize(value);
return String.format(HTML_LABEL_TEMPLATE, ColorUtil.toHexColor(ColorScheme.LIGHT_GRAY_COLOR), key, valueStr);
}
void toggleImportNotice(boolean on)
{
SwingUtilities.invokeLater(() -> importNoticePanel.setVisible(on));
}
}

View File

@@ -26,6 +26,7 @@
package net.runelite.client.plugins.loottracker;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
@@ -33,9 +34,12 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.inject.Provides;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@@ -43,6 +47,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -55,6 +60,7 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import lombok.AccessLevel;
import lombok.Getter;
@@ -96,6 +102,7 @@ import net.runelite.client.events.ClientShutdown;
import net.runelite.client.events.ConfigChanged;
import net.runelite.client.events.NpcLootReceived;
import net.runelite.client.events.PlayerLootReceived;
import net.runelite.client.events.RuneScapeProfileChanged;
import net.runelite.client.events.SessionClose;
import net.runelite.client.events.SessionOpen;
import net.runelite.client.game.ItemManager;
@@ -119,12 +126,14 @@ import org.apache.commons.text.WordUtils;
@PluginDescriptor(
name = "Loot Tracker",
description = "Tracks loot from monsters and minigames",
tags = {"drops"},
enabledByDefault = false
tags = {"drops"}
)
@Slf4j
public class LootTrackerPlugin extends Plugin
{
private static final int MAX_DROPS = 1024;
private static final Duration MAX_AGE = Duration.ofDays(365L);
// Activity/Event loot handling
private static final Pattern CLUE_SCROLL_PATTERN = Pattern.compile("You have completed [0-9]+ ([a-z]+) Treasure Trails?\\.");
private static final int THEATRE_OF_BLOOD_REGION = 12867;
@@ -291,6 +300,12 @@ public class LootTrackerPlugin extends Plugin
@Inject
private LootManager lootManager;
@Inject
private ConfigManager configManager;
@Inject
private Gson gson;
private LootTrackerPanel panel;
private NavigationButton navButton;
@VisibleForTesting
@@ -310,6 +325,8 @@ public class LootTrackerPlugin extends Plugin
@Inject
private LootTrackerClient lootTrackerClient;
private final List<LootRecord> queuedLoots = new ArrayList<>();
private String profileKey;
private Instant lastLootImport = Instant.now().minus(1, ChronoUnit.MINUTES);
private static Collection<ItemStack> stack(Collection<ItemStack> items)
{
@@ -367,20 +384,120 @@ public class LootTrackerPlugin extends Plugin
lootTrackerClient.setUuid(null);
}
@Subscribe
public void onRuneScapeProfileChanged(RuneScapeProfileChanged e)
{
final String profileKey = configManager.getRSProfileKey();
if (profileKey == null)
{
return;
}
if (profileKey.equals(this.profileKey))
{
return;
}
log.debug("Profile changed to {}", profileKey);
switchProfile(profileKey);
}
private void switchProfile(String profileKey)
{
executor.execute(() ->
{
// Current queued loot is for the previous profile, so save it first with the current profile key
submitLoot();
this.profileKey = profileKey;
log.debug("Switched to profile {}", profileKey);
int drops = 0;
List<ConfigLoot> loots = new ArrayList<>();
Instant old = Instant.now().minus(MAX_AGE);
for (String key : configManager.getRSProfileConfigurationKeys(LootTrackerConfig.GROUP, profileKey, "drops_"))
{
String json = configManager.getConfiguration(LootTrackerConfig.GROUP, profileKey, key);
ConfigLoot configLoot;
try
{
configLoot = gson.fromJson(json, ConfigLoot.class);
}
catch (JsonSyntaxException ex)
{
log.warn("Removing loot with malformed json: {}", json, ex);
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profileKey, key);
continue;
}
if (configLoot.last.isBefore(old))
{
log.debug("Removing old loot for {} {}", configLoot.type, configLoot.name);
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profileKey, key);
continue;
}
if (drops >= MAX_DROPS && !loots.isEmpty() && loots.get(0).last.isAfter(configLoot.last))
{
// fast drop
continue;
}
sortedInsert(loots, configLoot, Comparator.comparing(ConfigLoot::getLast));
drops += configLoot.numDrops();
if (drops >= MAX_DROPS)
{
ConfigLoot top = loots.remove(0);
drops -= top.numDrops();
}
}
log.debug("Loaded {} records", loots.size());
clientThread.invokeLater(() ->
{
// convertToLootTrackerRecord must be called on client thread
List<LootTrackerRecord> records = loots.stream()
.map(this::convertToLootTrackerRecord)
.collect(Collectors.toList());
SwingUtilities.invokeLater(() ->
{
panel.clearRecords();
panel.addRecords(records);
});
});
panel.toggleImportNotice(!hasImported());
});
}
private static <T> void sortedInsert(List<T> list, T value, Comparator<? super T> c)
{
int idx = Collections.binarySearch(list, value, c);
list.add(idx < 0 ? -idx - 1 : idx, value);
}
@Subscribe
public void onConfigChanged(ConfigChanged event)
{
if (event.getGroup().equals("loottracker"))
if (event.getGroup().equals(LootTrackerConfig.GROUP))
{
ignoredItems = Text.fromCSV(config.getIgnoredItems());
ignoredEvents = Text.fromCSV(config.getIgnoredEvents());
SwingUtilities.invokeLater(panel::updateIgnoredRecords);
if ("ignoredItems".equals(event.getKey()) || "ignoredEvents".equals(event.getKey()))
{
ignoredItems = Text.fromCSV(config.getIgnoredItems());
ignoredEvents = Text.fromCSV(config.getIgnoredEvents());
SwingUtilities.invokeLater(panel::updateIgnoredRecords);
}
}
}
@Override
protected void startUp() throws Exception
{
profileKey = null;
ignoredItems = Text.fromCSV(config.getIgnoredItems());
ignoredEvents = Text.fromCSV(config.getIgnoredEvents());
panel = new LootTrackerPanel(this, itemManager, config);
@@ -401,44 +518,12 @@ public class LootTrackerPlugin extends Plugin
if (accountSession != null)
{
lootTrackerClient.setUuid(accountSession.getUuid());
}
clientThread.invokeLater(() ->
{
switch (client.getGameState())
{
case STARTING:
case UNKNOWN:
return false;
}
executor.submit(() ->
{
if (!config.syncPanel())
{
return;
}
Collection<LootAggregate> lootRecords;
try
{
lootRecords = lootTrackerClient.get();
}
catch (IOException e)
{
log.debug("Unable to look up loot", e);
return;
}
log.debug("Loaded {} data entries", lootRecords.size());
clientThread.invokeLater(() ->
{
Collection<LootTrackerRecord> records = convertToLootTrackerRecord(lootRecords);
SwingUtilities.invokeLater(() -> panel.addRecords(records));
});
});
return true;
});
String profileKey = configManager.getRSProfileKey();
if (profileKey != null)
{
switchProfile(profileKey);
}
}
@@ -475,13 +560,10 @@ public class LootTrackerPlugin extends Plugin
final LootTrackerItem[] entries = buildEntries(stack(items));
SwingUtilities.invokeLater(() -> panel.add(name, type, combatLevel, entries));
if (config.saveLoot())
LootRecord lootRecord = new LootRecord(name, type, metadata, toGameItems(items), Instant.now(), getLootWorldId());
synchronized (queuedLoots)
{
LootRecord lootRecord = new LootRecord(name, type, metadata, toGameItems(items), Instant.now(), getLootWorldId());
synchronized (queuedLoots)
{
queuedLoots.add(lootRecord);
}
queuedLoots.add(lootRecord);
}
eventBus.post(new LootReceived(name, combatLevel, type, items));
@@ -932,16 +1014,53 @@ public class LootTrackerPlugin extends Plugin
queuedLoots.clear();
}
if (!config.saveLoot())
{
return null;
}
saveLoot(copy);
log.debug("Submitting {} loot records", copy.size());
return lootTrackerClient.submit(copy);
}
private Collection<ConfigLoot> combine(List<LootRecord> records)
{
Map<ConfigLoot, ConfigLoot> map = new HashMap<>();
for (LootRecord record : records)
{
ConfigLoot key = new ConfigLoot(record.getType(), record.getEventId());
ConfigLoot loot = map.computeIfAbsent(key, k -> key);
loot.kills++;
for (GameItem item : record.getDrops())
{
loot.add(item.getId(), item.getQty());
}
}
return map.values();
}
private void saveLoot(List<LootRecord> records)
{
Instant now = Instant.now();
Collection<ConfigLoot> combinedRecords = combine(records);
for (ConfigLoot record : combinedRecords)
{
ConfigLoot lootConfig = getLootConfig(record.type, record.name);
if (lootConfig == null)
{
lootConfig = record;
}
else
{
lootConfig.kills += record.kills;
for (int i = 0; i < record.drops.length; i += 2)
{
lootConfig.add(record.drops[i], record.drops[i + 1]);
}
}
lootConfig.last = now;
setLootConfig(lootConfig.type, lootConfig.name, lootConfig);
}
}
private void setEvent(LootRecordType lootRecordType, String eventType, Object metadata)
{
this.lootRecordType = lootRecordType;
@@ -1113,6 +1232,18 @@ public class LootTrackerPlugin extends Plugin
.collect(Collectors.toCollection(ArrayList::new));
}
private LootTrackerRecord convertToLootTrackerRecord(final ConfigLoot configLoot)
{
LootTrackerItem[] items = new LootTrackerItem[configLoot.drops.length / 2];
for (int i = 0; i < configLoot.drops.length; i += 2)
{
int id = configLoot.drops[i];
int qty = configLoot.drops[i + 1];
items[i >> 1] = buildLootTrackerItem(id, qty);
}
return new LootTrackerRecord(configLoot.name, "", configLoot.type, items, configLoot.kills);
}
/**
* Is player currently within the provided map regions
*/
@@ -1152,4 +1283,172 @@ public class LootTrackerPlugin extends Plugin
.runeLiteFormattedMessage(message)
.build());
}
ConfigLoot getLootConfig(LootRecordType type, String name)
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
log.debug("Trying to get loot with no profile!");
return null;
}
String json = configManager.getConfiguration(LootTrackerConfig.GROUP, profile, "drops_" + type + "_" + name);
if (json == null)
{
return null;
}
return gson.fromJson(json, ConfigLoot.class);
}
void setLootConfig(LootRecordType type, String name, ConfigLoot loot)
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
log.debug("Trying to set loot with no profile!");
return;
}
String json = gson.toJson(loot);
configManager.setConfiguration(LootTrackerConfig.GROUP, profile, "drops_" + type + "_" + name, json);
}
void removeLootConfig(LootRecordType type, String name)
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
log.debug("Trying to remove loot with no profile!");
return;
}
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profile, "drops_" + type + "_" + name);
}
void removeAllLoot()
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
log.debug("Trying to clear loot with no profile!");
return;
}
for (String key : configManager.getRSProfileConfigurationKeys(LootTrackerConfig.GROUP, profile, "drops_"))
{
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profile, key);
}
clearImported();
panel.toggleImportNotice(true);
}
void importLoot()
{
if (configManager.getRSProfileKey() == null)
{
JOptionPane.showMessageDialog(panel, "You do not have an active profile to import loot into; log in to the game first.");
return;
}
if (lootTrackerClient.getUuid() == null)
{
JOptionPane.showMessageDialog(panel, "You are not logged into RuneLite, so loot can not be imported from your account. Log in first.");
return;
}
if (lastLootImport.isAfter(Instant.now().minus(1, ChronoUnit.MINUTES)))
{
JOptionPane.showMessageDialog(panel, "You imported too recently. Wait a minute and try again.");
return;
}
lastLootImport = Instant.now();
executor.execute(() ->
{
if (hasImported())
{
SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(panel, "You already have imported loot."));
return;
}
Collection<LootAggregate> lootRecords;
try
{
lootRecords = lootTrackerClient.get();
}
catch (IOException e)
{
log.debug("Unable to look up loot", e);
return;
}
log.debug("Loaded {} data entries", lootRecords.size());
for (LootAggregate record : lootRecords)
{
ConfigLoot lootConfig = getLootConfig(record.getType(), record.getEventId());
if (lootConfig == null)
{
lootConfig = new ConfigLoot(record.getType(), record.getEventId());
}
lootConfig.first = record.getFirst_time();
lootConfig.last = record.getLast_time();
lootConfig.kills += record.getAmount();
for (GameItem gameItem : record.getDrops())
{
lootConfig.add(gameItem.getId(), gameItem.getQty());
}
setLootConfig(record.getType(), record.getEventId(), lootConfig);
}
clientThread.invokeLater(() ->
{
Collection<LootTrackerRecord> records = convertToLootTrackerRecord(lootRecords);
SwingUtilities.invokeLater(() -> panel.addRecords(records));
});
SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(panel, "Imported " + lootRecords.size() + " loot entries."));
setHasImported();
panel.toggleImportNotice(false);
});
}
void setHasImported()
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
return;
}
configManager.setConfiguration(LootTrackerConfig.GROUP, profile, "imported", 1);
}
boolean hasImported()
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
return false;
}
Integer i = configManager.getConfiguration(LootTrackerConfig.GROUP, profile, "imported", Integer.class);
return i != null && i == 1;
}
void clearImported()
{
String profile = profileKey;
if (Strings.isNullOrEmpty(profile))
{
return;
}
configManager.unsetConfiguration(LootTrackerConfig.GROUP, profile, "imported");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 B

View File

@@ -55,6 +55,7 @@ import net.runelite.api.widgets.WidgetID;
import net.runelite.client.account.SessionManager;
import net.runelite.client.chat.ChatMessageManager;
import net.runelite.client.chat.QueuedMessage;
import net.runelite.client.config.ConfigManager;
import net.runelite.client.game.ItemManager;
import net.runelite.client.game.ItemStack;
import net.runelite.client.game.SpriteManager;
@@ -139,6 +140,10 @@ public class LootTrackerPluginTest
@Bind
private LootTrackerClient lootTrackerClient;
@Mock
@Bind
private ConfigManager configManager;
@Before
public void setUp()
{