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:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user