Merge remote-tracking branch 'runelite/master'
This commit is contained in:
@@ -2056,6 +2056,18 @@ public interface Client extends GameEngine
|
||||
*/
|
||||
void setSpellSelected(boolean selected);
|
||||
|
||||
/**
|
||||
* Get if an item is selected with "Use"
|
||||
* @return 1 if selected, else 0
|
||||
*/
|
||||
int getSelectedItem();
|
||||
|
||||
/**
|
||||
* If an item is selected, this is the item index in the inventory.
|
||||
* @return
|
||||
*/
|
||||
int getSelectedItemIndex();
|
||||
|
||||
/**
|
||||
* Returns client item composition cache
|
||||
*/
|
||||
|
||||
@@ -48,8 +48,6 @@ public interface NPCComposition extends ParamHolder
|
||||
|
||||
boolean isClickable();
|
||||
|
||||
boolean isFollower();
|
||||
|
||||
/**
|
||||
* NPC can be interacting with via menu options
|
||||
* @return
|
||||
@@ -98,4 +96,11 @@ public interface NPCComposition extends ParamHolder
|
||||
* Gets the displayed overhead icon of the NPC.
|
||||
*/
|
||||
HeadIcon getOverheadIcon();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the npc is a follower, such as a pet. Is affected by the
|
||||
* "Move follower options lower down" setting.
|
||||
* @return
|
||||
*/
|
||||
boolean isFollower();
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ public class RuneLite
|
||||
{
|
||||
Locale.setDefault(Locale.ENGLISH);
|
||||
|
||||
final OptionParser parser = new OptionParser();
|
||||
final OptionParser parser = new OptionParser(false);
|
||||
parser.accepts("developer-mode", "Enable developer tools");
|
||||
parser.accepts("debug", "Show extra debugging output");
|
||||
parser.accepts("safe-mode", "Disables external plugins and the GPU plugin");
|
||||
|
||||
@@ -984,7 +984,10 @@ public class ConfigManager
|
||||
return methods;
|
||||
}
|
||||
|
||||
@Subscribe(priority = 100)
|
||||
@Subscribe(
|
||||
// run after plugins, in the event they save config on shutdown
|
||||
priority = -100
|
||||
)
|
||||
private void onClientShutdown(ClientShutdown e)
|
||||
{
|
||||
Future<Void> f = sendConfig();
|
||||
|
||||
@@ -38,5 +38,9 @@ import java.lang.annotation.Target;
|
||||
@Documented
|
||||
public @interface Subscribe
|
||||
{
|
||||
/**
|
||||
* Priority relative to other event subscribers. Higher priorities run first.
|
||||
* @return
|
||||
*/
|
||||
float priority() default 0;
|
||||
}
|
||||
|
||||
@@ -1293,7 +1293,11 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
|
||||
gl.glUniform1f(uniSmoothBanding, config.smoothBanding() ? 0f : 1f);
|
||||
gl.glUniform1i(uniColorBlindMode, config.colorBlindMode().ordinal());
|
||||
gl.glUniform1f(uniTextureLightMode, config.brightTextures() ? 1f : 0f);
|
||||
gl.glUniform1i(uniTick, client.getGameCycle());
|
||||
if (gameState == GameState.LOGGED_IN)
|
||||
{
|
||||
// avoid textures animating during loading
|
||||
gl.glUniform1i(uniTick, client.getGameCycle());
|
||||
}
|
||||
|
||||
// Calculate projection matrix
|
||||
Matrix4 projectionMatrix = new Matrix4();
|
||||
|
||||
@@ -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