Merge remote-tracking branch 'runelite/master'

This commit is contained in:
Owain van Brakel
2022-03-09 01:50:40 +01:00
13 changed files with 539 additions and 103 deletions

View File

@@ -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
*/

View File

@@ -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();
}

View File

@@ -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");

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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();

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()
{