diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetup.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetup.java new file mode 100644 index 0000000000..f4d4c87f91 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetup.java @@ -0,0 +1,14 @@ +package net.runelite.client.plugins.inventorysetups; + +import java.util.ArrayList; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class InventorySetup +{ + @Getter + private ArrayList inventory; + @Getter + private ArrayList equipment; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupBankOverlay.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupBankOverlay.java new file mode 100644 index 0000000000..c25c0108c0 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupBankOverlay.java @@ -0,0 +1,76 @@ +package net.runelite.client.plugins.inventorysetups; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.stream.IntStream; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.widgets.WidgetItem; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.WidgetItemOverlay; + +@Slf4j +public class InventorySetupBankOverlay extends WidgetItemOverlay +{ + private final ItemManager itemManager; + private final InventorySetupPlugin plugin; + private final InventorySetupConfig config; + + @Inject + public InventorySetupBankOverlay(ItemManager itemManager, InventorySetupPlugin plugin, InventorySetupConfig config) + { + this.itemManager = itemManager; + this.plugin = plugin; + this.config = config; + showOnBank(); + } + + @Override + public void renderItemOverlay(Graphics2D graphics, int itemId, WidgetItem itemWidget) + { + if (config.getBankHighlight()) + { + int[] ids = plugin.getCurrentInventorySetupIds(); + if (ids == null) + { + return; + } + + if (IntStream.of(ids).noneMatch(x -> x == itemId)) + { + return; + } + + + final Color color = config.getBankHighlightColor(); + + if (color != null) + { + Rectangle bounds = itemWidget.getCanvasBounds(); + final BufferedImage outline = itemManager.getItemOutline(itemId, itemWidget.getQuantity(), color); + graphics.drawImage(outline, (int) bounds.getX() + 1, (int) bounds.getY() + 1, null); + + if (itemWidget.getQuantity() > 1) + { + drawQuantity(graphics, itemWidget, Color.YELLOW); + } + else if (itemWidget.getQuantity() == 0) + { + drawQuantity(graphics, itemWidget, Color.YELLOW.darker()); + } + } + } + } + + private void drawQuantity(Graphics2D graphics, WidgetItem item, Color darker) + { + graphics.setColor(Color.BLACK); + graphics.drawString(String.valueOf(item.getQuantity()), item.getCanvasLocation().getX() + 2, item.getCanvasLocation().getY() + 11); + graphics.setColor(darker); + graphics.setFont(FontManager.getRunescapeSmallFont()); + graphics.drawString(String.valueOf(item.getQuantity()), item.getCanvasLocation().getX() + 1, item.getCanvasLocation().getY() + 10); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupConfig.java new file mode 100644 index 0000000000..f754b02c1a --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupConfig.java @@ -0,0 +1,82 @@ +package net.runelite.client.plugins.inventorysetups; + +import java.awt.Color; +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("inventorysetups") +public interface InventorySetupConfig extends Config +{ + @ConfigItem( + keyName = "highlightDifferences", + name = "Highlight Differences", + description = "Highlight slots that don't match the selected setup", + position = 0 + ) + + default boolean getHighlightDifferences() + { + return false; + } + + @ConfigItem( + keyName = "highlightDifferenceColor", + name = "Highlight Color", + description = "The color used to highlight differences between setups", + position = 1 + ) + + default Color getHighlightColor() + { + return Color.RED; + } + + @ConfigItem( + keyName = "stackDifference", + name = "Stack Difference", + description = "Differences between setups will be highlighted if the stack size is different", + position = 2 + ) + + default boolean getStackDifference() + { + return false; + } + + @ConfigItem( + keyName = "variationDifference", + name = "Variation Difference", + description = "Variations of items (E.g., charged jewellery) will be counted as different", + position = 2 + ) + + default boolean getVariationDifference() + { + return false; + } + + @ConfigItem( + keyName = "bankHighlight", + name = "Bank Highlight", + description = "Highlight setup items in bank", + position = 4 + ) + + default boolean getBankHighlight() + { + return false; + } + + @ConfigItem( + keyName = "bankHighlightColor", + name = "Bank Highlight Color", + description = "The color used to highlight setup items in bank", + position = 5 + ) + + default Color getBankHighlightColor() + { + return Color.RED; + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupItem.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupItem.java new file mode 100644 index 0000000000..0f624ffeaa --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupItem.java @@ -0,0 +1,15 @@ +package net.runelite.client.plugins.inventorysetups; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public class InventorySetupItem +{ + @Getter + private final int id; + @Getter + private final String name; + @Getter + private final int quantity; +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupPlugin.java new file mode 100644 index 0000000000..99cb72fdcb --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/InventorySetupPlugin.java @@ -0,0 +1,407 @@ +package net.runelite.client.plugins.inventorysetups; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.inject.Provides; +import java.awt.image.BufferedImage; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Objects; +import javax.inject.Inject; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; +import net.runelite.api.events.ConfigChanged; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.ItemContainerChanged; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemVariationMapping; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.PluginType; +import net.runelite.client.plugins.inventorysetups.ui.InventorySetupPluginPanel; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.ImageUtil; + +@PluginDescriptor( + name = "Inventory Setups", + description = "Save inventory setups", + tags = {"items", "inventory", "setups"}, + enabledByDefault = false, + type = PluginType.UTILITY +) + +@Slf4j +public class InventorySetupPlugin extends Plugin +{ + private static final String CONFIG_GROUP = "inventorysetups"; + private static final String CONFIG_KEY = "setups"; + private static final int NUM_INVENTORY_ITEMS = 28; + private static final int NUM_EQUIPMENT_ITEMS = 14; + + @Inject + private Client client; + + @Inject + private ItemManager itemManager; + + @Inject + private InventorySetupBankOverlay overlay; + + @Inject + private ClientToolbar clientToolbar; + + @Inject + private InventorySetupConfig config; + + @Inject + private OverlayManager overlayManager; + + @Inject + private ClientThread clientThread; + + @Inject + private ConfigManager configManager; + + private InventorySetupPluginPanel panel; + + private HashMap inventorySetups; + + private NavigationButton navButton; + + private boolean highlightDifference; + + @Override + public void startUp() + { + overlayManager.add(overlay); + + panel = new InventorySetupPluginPanel(this, itemManager); + + final BufferedImage icon = ImageUtil.getResourceStreamFromClass(getClass(), "inventorysetups_icon.png"); + + navButton = NavigationButton.builder() + .tooltip("Inventory Setups") + .icon(icon) + .priority(9) + .panel(panel) + .build(); + + clientToolbar.addNavigation(navButton); + + // load all the inventory setups from the config file + clientThread.invokeLater(() -> + { + if (client.getGameState() != GameState.LOGIN_SCREEN) + { + return false; + } + + loadConfig(); + panel.showNoSetupsPanel(); + return true; + }); + + } + + public void addInventorySetup() + { + final String name = JOptionPane.showInputDialog(panel, + "Enter the name of this setup.", + "Add New Setup", + JOptionPane.PLAIN_MESSAGE); + + // cancel button was clicked + if (name == null) + { + return; + } + + if (name.isEmpty()) + { + JOptionPane.showMessageDialog(panel, + "Invalid Setup Name", + "Names must not be empty.", + JOptionPane.PLAIN_MESSAGE); + return; + } + + if (inventorySetups.containsKey(name)) + { + String builder = "The setup " + name + " already exists. " + + "Would you like to replace it with the current setup?"; + int confirm = JOptionPane.showConfirmDialog(panel, + builder, + "Warning", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE); + + if (confirm == JOptionPane.CANCEL_OPTION) + { + return; + } + + // delete the old setup, no need to ask for confirmation + // because the user confirmed above + removeInventorySetup(name, false); + } + + clientThread.invoke(() -> + { + ArrayList inv = getNormalizedContainer(InventoryID.INVENTORY); + ArrayList eqp = getNormalizedContainer(InventoryID.EQUIPMENT); + + final InventorySetup invSetup = new InventorySetup(inv, eqp); + SwingUtilities.invokeLater(() -> + { + inventorySetups.put(name, invSetup); + panel.addInventorySetup(name); + panel.setCurrentInventorySetup(name); + + updateConfig(); + }); + }); + } + + public void removeInventorySetup(final String name, boolean askForConfirmation) + { + if (inventorySetups.containsKey(name)) + { + int confirm = JOptionPane.YES_OPTION; + + if (askForConfirmation) + { + confirm = JOptionPane.showConfirmDialog(panel, + "Are you sure you want to remove this setup?", + "Warning", + JOptionPane.YES_NO_OPTION, + JOptionPane.PLAIN_MESSAGE); + } + + if (confirm == JOptionPane.YES_OPTION) + { + inventorySetups.remove(name); + panel.removeInventorySetup(name); + } + + updateConfig(); + } + } + + public final InventorySetup getInventorySetup(final String name) + { + return inventorySetups.get(name); + } + + @Provides + InventorySetupConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(InventorySetupConfig.class); + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (event.getGroup().equals(CONFIG_GROUP)) + { + // only allow highlighting if the config is enabled and the player is logged in + highlightDifference = config.getHighlightDifferences() && client.getGameState() == GameState.LOGGED_IN; + final String setupName = panel.getSelectedInventorySetup(); + if (highlightDifference && !setupName.isEmpty()) + { + panel.setCurrentInventorySetup(setupName); + } + } + } + + private void updateConfig() + { + if (inventorySetups.isEmpty()) + { + configManager.unsetConfiguration(CONFIG_GROUP, CONFIG_KEY); + return; + } + + final Gson gson = new Gson(); + final String json = gson.toJson(inventorySetups); + configManager.setConfiguration(CONFIG_GROUP, CONFIG_KEY, json); + } + + private void loadConfig() + { + // serialize the internal data structure from the json in the configuration + final String json = configManager.getConfiguration(CONFIG_GROUP, CONFIG_KEY); + if (json == null || json.isEmpty()) + { + inventorySetups = new HashMap<>(); + } + else + { + // TODO add last resort?, serialize exception just make empty map + final Gson gson = new Gson(); + Type type = new TypeToken>() + { + + }.getType(); + inventorySetups = gson.fromJson(json, type); + } + + for (final String key : inventorySetups.keySet()) + { + panel.addInventorySetup(key); + } + + highlightDifference = false; + } + + @Subscribe + public void onItemContainerChanged(ItemContainerChanged event) + { + + if (!highlightDifference || client.getGameState() != GameState.LOGGED_IN) + { + return; + } + + // empty entry, no need to compare anything + final String selectedInventorySetup = panel.getSelectedInventorySetup(); + if (selectedInventorySetup.isEmpty()) + { + return; + } + + // check to see that the container is the equipment or inventory + ItemContainer container = event.getItemContainer(); + + if (container == client.getItemContainer(InventoryID.INVENTORY)) + { + ArrayList normContainer = getNormalizedContainer(InventoryID.INVENTORY); + final InventorySetup setup = inventorySetups.get(selectedInventorySetup); + panel.highlightDifferences(normContainer, setup, InventoryID.INVENTORY); + } + else if (container == client.getItemContainer(InventoryID.EQUIPMENT)) + { + ArrayList normContainer = getNormalizedContainer(InventoryID.EQUIPMENT); + final InventorySetup setup = inventorySetups.get(selectedInventorySetup); + panel.highlightDifferences(normContainer, setup, InventoryID.EQUIPMENT); + } + + } + + @Subscribe + public void onGameStateChanged(GameStateChanged event) + { + switch (event.getGameState()) + { + // set the highlighting off if login screen shows up + case LOGIN_SCREEN: + highlightDifference = false; + final String setupName = panel.getSelectedInventorySetup(); + if (!setupName.isEmpty()) + { + panel.setCurrentInventorySetup(setupName); + } + break; + + // set highlighting + case LOGGED_IN: + highlightDifference = config.getHighlightDifferences(); + break; + } + } + + public ArrayList getNormalizedContainer(final InventoryID id) + { + assert id == InventoryID.INVENTORY || id == InventoryID.EQUIPMENT : "invalid inventory ID"; + + final ItemContainer container = client.getItemContainer(id); + + ArrayList newContainer = new ArrayList<>(); + + Item[] items = null; + if (container != null) + { + items = container.getItems(); + } + + int size = id == InventoryID.INVENTORY ? NUM_INVENTORY_ITEMS : NUM_EQUIPMENT_ITEMS; + + for (int i = 0; i < size; i++) + { + if (items == null || i >= items.length) + { + newContainer.add(new InventorySetupItem(-1, "", 0)); + } + else + { + final Item item = items[i]; + String itemName = ""; + if (client.isClientThread()) + { + itemName = itemManager.getItemComposition(item.getId()).getName(); + } + newContainer.add(new InventorySetupItem(item.getId(), itemName, item.getQuantity())); + } + } + + return newContainer; + } + + public final InventorySetupConfig getConfig() + { + return config; + } + + public boolean getHighlightDifference() + { + return highlightDifference; + } + + @Override + public void shutDown() + { + overlayManager.remove(overlay); + clientToolbar.removeNavigation(navButton); + } + + final int[] getCurrentInventorySetupIds() + { + InventorySetup setup = inventorySetups.get(panel.getSelectedInventorySetup()); + if (setup == null) + { + return null; + } + ArrayList items = new ArrayList<>(); + items.addAll(setup.getEquipment()); + items.addAll(setup.getInventory()); + ArrayList itemIds = new ArrayList<>(); + for (InventorySetupItem item : items) + { + int id = item.getId(); + ItemComposition itemComposition = itemManager.getItemComposition(id); + if (id > 0) + { + itemIds.add(ItemVariationMapping.map(id)); + itemIds.add(itemComposition.getPlaceholderId()); + } + + } + return itemIds.stream() + .mapToInt(i -> i) + .filter(Objects::nonNull) + .filter(id -> id != -1) + .toArray(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupContainerPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupContainerPanel.java new file mode 100644 index 0000000000..f6cd5364a1 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupContainerPanel.java @@ -0,0 +1,106 @@ +package net.runelite.client.plugins.inventorysetups.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.util.ArrayList; +import javax.swing.JLabel; +import javax.swing.JPanel; +import net.runelite.client.game.AsyncBufferedImage; +import net.runelite.client.game.ItemManager; +import net.runelite.client.game.ItemVariationMapping; +import net.runelite.client.plugins.inventorysetups.InventorySetupConfig; +import net.runelite.client.plugins.inventorysetups.InventorySetupItem; +import net.runelite.client.plugins.inventorysetups.InventorySetupPlugin; +import net.runelite.client.ui.ColorScheme; + +public abstract class InventorySetupContainerPanel extends JPanel +{ + protected ItemManager itemManager; + + private final InventorySetupPlugin plugin; + + InventorySetupContainerPanel(final ItemManager itemManager, final InventorySetupPlugin plugin, String captionText) + { + this.itemManager = itemManager; + this.plugin = plugin; + JPanel containerPanel = new JPanel(); + + final JPanel containerSlotsPanel = new JPanel(); + + setupContainerPanel(containerSlotsPanel); + + // caption + final JLabel caption = new JLabel(captionText); + caption.setHorizontalAlignment(JLabel.CENTER); + caption.setVerticalAlignment(JLabel.CENTER); + + // panel that holds the caption and any other graphics + final JPanel captionPanel = new JPanel(); + captionPanel.add(caption); + + containerPanel.setLayout(new BorderLayout()); + containerPanel.add(captionPanel, BorderLayout.NORTH); + containerPanel.add(containerSlotsPanel, BorderLayout.CENTER); + + add(containerPanel); + } + + void setContainerSlot(int index, + final InventorySetupSlot containerSlot, + final ArrayList items) + { + if (index >= items.size() || items.get(index).getId() == -1) + { + containerSlot.setImageLabel(null, null); + return; + } + + int itemId = items.get(index).getId(); + int quantity = items.get(index).getQuantity(); + final String itemName = items.get(index).getName(); + AsyncBufferedImage itemImg = itemManager.getImage(itemId, quantity, quantity > 1); + String toolTip = itemName; + if (quantity > 1) + { + toolTip += " (" + quantity + ")"; + } + containerSlot.setImageLabel(toolTip, itemImg); + } + + void highlightDifferentSlotColor(InventorySetupItem savedItem, + InventorySetupItem currItem, + final InventorySetupSlot containerSlot) + { + // important note: do not use item names for comparisons + // they are all empty to avoid clientThread usage when highlighting + + final InventorySetupConfig config = plugin.getConfig(); + final Color highlightColor = config.getHighlightColor(); + + if (config.getStackDifference() && currItem.getQuantity() != savedItem.getQuantity()) + { + containerSlot.setBackground(highlightColor); + return; + } + + int currId = currItem.getId(); + int checkId = savedItem.getId(); + + if (!config.getVariationDifference()) + { + currId = ItemVariationMapping.map(currId); + checkId = ItemVariationMapping.map(checkId); + } + + if (currId != checkId) + { + containerSlot.setBackground(highlightColor); + return; + } + + // set the color back to the original, because they match + containerSlot.setBackground(ColorScheme.DARKER_GRAY_COLOR); + } + + abstract public void setupContainerPanel(final JPanel containerSlotsPanel); +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupEquipmentPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupEquipmentPanel.java new file mode 100644 index 0000000000..54ce9eb147 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupEquipmentPanel.java @@ -0,0 +1,90 @@ +package net.runelite.client.plugins.inventorysetups.ui; + +import java.awt.GridLayout; +import java.util.ArrayList; +import java.util.HashMap; +import javax.swing.JPanel; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.inventorysetups.InventorySetup; +import net.runelite.client.plugins.inventorysetups.InventorySetupItem; +import net.runelite.client.plugins.inventorysetups.InventorySetupPlugin; +import net.runelite.client.ui.ColorScheme; + +public class InventorySetupEquipmentPanel extends InventorySetupContainerPanel +{ + private HashMap equipmentSlots; + + InventorySetupEquipmentPanel(final ItemManager itemManager, final InventorySetupPlugin plugin) + { + super(itemManager, plugin, "Equipment"); + } + + @Override + public void setupContainerPanel(final JPanel containerSlotsPanel) + { + this.equipmentSlots = new HashMap<>(); + for (EquipmentInventorySlot slot : EquipmentInventorySlot.values()) + { + equipmentSlots.put(slot, new InventorySetupSlot(ColorScheme.DARKER_GRAY_COLOR)); + } + + final GridLayout gridLayout = new GridLayout(5, 3, 1, 1); + containerSlotsPanel.setLayout(gridLayout); + + // add the grid layouts, including invisible ones + containerSlotsPanel.add(new InventorySetupSlot(ColorScheme.DARK_GRAY_COLOR)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.HEAD)); + containerSlotsPanel.add(new InventorySetupSlot(ColorScheme.DARK_GRAY_COLOR)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.CAPE)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.AMULET)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.AMMO)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.WEAPON)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.BODY)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.SHIELD)); + containerSlotsPanel.add(new InventorySetupSlot(ColorScheme.DARK_GRAY_COLOR)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.LEGS)); + containerSlotsPanel.add(new InventorySetupSlot(ColorScheme.DARK_GRAY_COLOR)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.GLOVES)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.BOOTS)); + containerSlotsPanel.add(equipmentSlots.get(EquipmentInventorySlot.RING)); + + } + + void setEquipmentSetupSlots(final InventorySetup setup) + { + final ArrayList equipment = setup.getEquipment(); + + for (final EquipmentInventorySlot slot : EquipmentInventorySlot.values()) + { + int i = slot.getSlotIdx(); + super.setContainerSlot(i, equipmentSlots.get(slot), equipment); + } + + validate(); + repaint(); + + } + + void highlightDifferences(final ArrayList currEquipment, final InventorySetup inventorySetup) + { + final ArrayList equipToCheck = inventorySetup.getEquipment(); + + assert currEquipment.size() == equipToCheck.size() : "size mismatch"; + + for (final EquipmentInventorySlot slot : EquipmentInventorySlot.values()) + { + + int slotIdx = slot.getSlotIdx(); + super.highlightDifferentSlotColor(equipToCheck.get(slotIdx), currEquipment.get(slotIdx), equipmentSlots.get(slot)); + } + } + + void resetEquipmentSlotsColor() + { + for (final EquipmentInventorySlot slot : EquipmentInventorySlot.values()) + { + equipmentSlots.get(slot).setBackground(ColorScheme.DARKER_GRAY_COLOR); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupInventoryPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupInventoryPanel.java new file mode 100644 index 0000000000..59358ad5c2 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupInventoryPanel.java @@ -0,0 +1,76 @@ +package net.runelite.client.plugins.inventorysetups.ui; + +import java.awt.GridLayout; +import java.util.ArrayList; +import javax.swing.JPanel; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.inventorysetups.InventorySetup; +import net.runelite.client.plugins.inventorysetups.InventorySetupItem; +import net.runelite.client.plugins.inventorysetups.InventorySetupPlugin; +import net.runelite.client.ui.ColorScheme; + +public class InventorySetupInventoryPanel extends InventorySetupContainerPanel +{ + private static final int ITEMS_PER_ROW = 4; + private static final int NUM_INVENTORY_ITEMS = 28; + + private ArrayList inventorySlots; + + InventorySetupInventoryPanel(final ItemManager itemManager, final InventorySetupPlugin plugin) + { + super(itemManager, plugin, "Inventory"); + } + + + @Override + public void setupContainerPanel(final JPanel containerSlotsPanel) + { + this.inventorySlots = new ArrayList<>(); + for (int i = 0; i < NUM_INVENTORY_ITEMS; i++) + { + inventorySlots.add(new InventorySetupSlot(ColorScheme.DARKER_GRAY_COLOR)); + } + + int numRows = (NUM_INVENTORY_ITEMS + ITEMS_PER_ROW - 1) / ITEMS_PER_ROW; + containerSlotsPanel.setLayout(new GridLayout(numRows, ITEMS_PER_ROW, 1, 1)); + for (int i = 0; i < NUM_INVENTORY_ITEMS; i++) + { + containerSlotsPanel.add(inventorySlots.get(i)); + } + } + + void setInventorySetupSlots(final InventorySetup setup) + { + ArrayList inventory = setup.getInventory(); + + for (int i = 0; i < NUM_INVENTORY_ITEMS; i++) + { + super.setContainerSlot(i, inventorySlots.get(i), inventory); + } + + validate(); + repaint(); + + } + + void highlightDifferentSlots(final ArrayList currInventory, final InventorySetup inventorySetup) + { + + final ArrayList inventoryToCheck = inventorySetup.getInventory(); + + assert currInventory.size() == inventoryToCheck.size() : "size mismatch"; + + for (int i = 0; i < NUM_INVENTORY_ITEMS; i++) + { + super.highlightDifferentSlotColor(inventoryToCheck.get(i), currInventory.get(i), inventorySlots.get(i)); + } + } + + void resetInventorySlotsColor() + { + for (InventorySetupSlot inventorySlot : inventorySlots) + { + inventorySlot.setBackground(ColorScheme.DARKER_GRAY_COLOR); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupPluginPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupPluginPanel.java new file mode 100644 index 0000000000..fdcc52a10c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupPluginPanel.java @@ -0,0 +1,283 @@ +package net.runelite.client.plugins.inventorysetups.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ItemEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.border.EmptyBorder; +import net.runelite.api.InventoryID; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.inventorysetups.InventorySetup; +import net.runelite.client.plugins.inventorysetups.InventorySetupItem; +import net.runelite.client.plugins.inventorysetups.InventorySetupPlugin; +import net.runelite.client.ui.PluginPanel; +import net.runelite.client.ui.components.PluginErrorPanel; +import net.runelite.client.util.ImageUtil; + +public class InventorySetupPluginPanel extends PluginPanel +{ + private static ImageIcon ADD_ICON; + private static ImageIcon ADD_HOVER_ICON; + private static ImageIcon REMOVE_ICON; + private static ImageIcon REMOVE_HOVER_ICON; + + private final JPanel noSetupsPanel; + private final JPanel invEqPanel; + + private final InventorySetupInventoryPanel invPanel; + private final InventorySetupEquipmentPanel eqpPanel; + + private final JComboBox setupComboBox; + + private final JLabel removeMarker; + + private final InventorySetupPlugin plugin; + + static + { + final BufferedImage addIcon = ImageUtil.getResourceStreamFromClass(InventorySetupPlugin.class, "add_icon.png"); + ADD_ICON = new ImageIcon(addIcon); + ADD_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(addIcon, 0.53f)); + + final BufferedImage removeIcon = ImageUtil.getResourceStreamFromClass(InventorySetupPlugin.class, "remove_icon.png"); + REMOVE_ICON = new ImageIcon(removeIcon); + REMOVE_HOVER_ICON = new ImageIcon(ImageUtil.alphaOffset(removeIcon, 0.53f)); + } + + public InventorySetupPluginPanel(final InventorySetupPlugin plugin, final ItemManager itemManager) + { + super(false); + this.plugin = plugin; + this.removeMarker = new JLabel(REMOVE_ICON); + this.invPanel = new InventorySetupInventoryPanel(itemManager, plugin); + this.eqpPanel = new InventorySetupEquipmentPanel(itemManager, plugin); + this.noSetupsPanel = new JPanel(); + this.invEqPanel = new JPanel(); + this.setupComboBox = new JComboBox<>(); + + // setup the title + final JLabel addMarker = new JLabel(ADD_ICON); + final JLabel title = new JLabel(); + title.setText("Inventory Setups"); + title.setForeground(Color.WHITE); + + // setup the add marker (+ sign in the top right) + addMarker.setToolTipText("Add a new inventory setup"); + addMarker.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + plugin.addInventorySetup(); + } + + @Override + public void mouseEntered(MouseEvent e) + { + addMarker.setIcon(ADD_HOVER_ICON); + } + + @Override + public void mouseExited(MouseEvent e) + { + addMarker.setIcon(ADD_ICON); + } + }); + + // setup the remove marker (X sign in the top right) + removeMarker.setToolTipText("Remove the current inventory setup"); + removeMarker.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + final String name = (String) setupComboBox.getSelectedItem(); + plugin.removeInventorySetup(name, true); + } + + @Override + public void mouseEntered(MouseEvent e) + { + if (removeMarker.isEnabled()) + { + removeMarker.setIcon(REMOVE_HOVER_ICON); + } + } + + @Override + public void mouseExited(MouseEvent e) + { + removeMarker.setIcon(REMOVE_ICON); + } + }); + + // setup the combo box for selection switching + // add empty to indicate the empty position + setupComboBox.addItem(""); + setupComboBox.setSelectedIndex(0); + setupComboBox.addItemListener(e -> + { + if (e.getStateChange() == ItemEvent.SELECTED) + { + String selection = (String) e.getItem(); + setCurrentInventorySetup(selection); + } + }); + + // the panel on the top right that holds the add and delete buttons + final JPanel markersPanel = new JPanel(); + markersPanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 0)); + markersPanel.add(removeMarker); + markersPanel.add(addMarker); + + // the top panel that has the title and the buttons + final JPanel titleAndMarkersPanel = new JPanel(); + titleAndMarkersPanel.setLayout(new BorderLayout()); + titleAndMarkersPanel.add(title, BorderLayout.WEST); + titleAndMarkersPanel.add(markersPanel, BorderLayout.EAST); + + // the panel that stays at the top and doesn't scroll + // contains the title, buttons, and the combo box + final JPanel northAnchoredPanel = new JPanel(); + northAnchoredPanel.setLayout(new BoxLayout(northAnchoredPanel, BoxLayout.Y_AXIS)); + northAnchoredPanel.setBorder(new EmptyBorder(0, 0, 10, 0)); + northAnchoredPanel.add(titleAndMarkersPanel); + northAnchoredPanel.add(Box.createRigidArea(new Dimension(0, 10))); + northAnchoredPanel.add(setupComboBox); + + // the panel that holds the inventory and equipment panels + final BoxLayout invEqLayout = new BoxLayout(invEqPanel, BoxLayout.Y_AXIS); + invEqPanel.setLayout(invEqLayout); + invEqPanel.add(invPanel); + invEqPanel.add(Box.createRigidArea(new Dimension(0, 10))); + invEqPanel.add(eqpPanel); + + // setup the error panel. It's wrapped around a normal panel + // so it doesn't stretch to fill the parent panel + final PluginErrorPanel errorPanel = new PluginErrorPanel(); + errorPanel.setContent("Inventory Setups", "Select or create an inventory setup."); + noSetupsPanel.add(errorPanel); + + // the panel that holds the inventory panels, and the error panel + final JPanel contentPanel = new JPanel(); + final BoxLayout contentLayout = new BoxLayout(contentPanel, BoxLayout.Y_AXIS); + contentPanel.setLayout(contentLayout); + contentPanel.add(invEqPanel); + contentPanel.add(noSetupsPanel); + + // wrapper for the main content panel to keep it from stretching + final JPanel contentWrapper = new JPanel(new BorderLayout()); + contentWrapper.add(Box.createGlue(), BorderLayout.CENTER); + contentWrapper.add(contentPanel, BorderLayout.NORTH); + final JScrollPane contentWrapperPane = new JScrollPane(contentWrapper); + contentWrapperPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + + setLayout(new BorderLayout()); + setBorder(new EmptyBorder(10, 10, 10, 10)); + add(northAnchoredPanel, BorderLayout.NORTH); + add(contentWrapperPane, BorderLayout.CENTER); + + // show the no setups panel on startup + showNoSetupsPanel(); + + } + + public void showNoSetupsPanel() + { + setupComboBox.setSelectedIndex(0); + removeMarker.setEnabled(false); + noSetupsPanel.setVisible(true); + invEqPanel.setVisible(false); + } + + private void showHasSetupPanel(final String name) + { + setupComboBox.setSelectedItem(name); + removeMarker.setEnabled(true); + noSetupsPanel.setVisible(false); + invEqPanel.setVisible(true); + } + + public void setCurrentInventorySetup(final String name) + { + if (name.isEmpty()) + { + showNoSetupsPanel(); + return; + } + + showHasSetupPanel(name); + + final InventorySetup inventorySetup = plugin.getInventorySetup(name); + + invPanel.setInventorySetupSlots(inventorySetup); + eqpPanel.setEquipmentSetupSlots(inventorySetup); + + if (plugin.getHighlightDifference()) + { + final ArrayList normInv = plugin.getNormalizedContainer(InventoryID.INVENTORY); + final ArrayList normEqp = plugin.getNormalizedContainer(InventoryID.EQUIPMENT); + + highlightDifferences(normInv, inventorySetup, InventoryID.INVENTORY); + highlightDifferences(normEqp, inventorySetup, InventoryID.EQUIPMENT); + } + else + { + invPanel.resetInventorySlotsColor(); + eqpPanel.resetEquipmentSlotsColor(); + } + + validate(); + repaint(); + } + + public void addInventorySetup(final String name) + { + setupComboBox.addItem(name); + } + + public void removeInventorySetup(final String name) + { + setupComboBox.removeItem(name); + showNoSetupsPanel(); + + invPanel.resetInventorySlotsColor(); + eqpPanel.resetEquipmentSlotsColor(); + + validate(); + repaint(); + } + + public void highlightDifferences(final ArrayList container, + final InventorySetup setupToCheck, + final InventoryID type) + { + switch (type) + { + case INVENTORY: + invPanel.highlightDifferentSlots(container, setupToCheck); + break; + + case EQUIPMENT: + eqpPanel.highlightDifferences(container, setupToCheck); + break; + } + } + + public final String getSelectedInventorySetup() + { + return (String) setupComboBox.getSelectedItem(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupSlot.java b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupSlot.java new file mode 100644 index 0000000000..3c01f4694e --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/inventorysetups/ui/InventorySetupSlot.java @@ -0,0 +1,40 @@ +package net.runelite.client.plugins.inventorysetups.ui; + +import java.awt.Color; +import java.awt.Dimension; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import net.runelite.client.game.AsyncBufferedImage; + +public class InventorySetupSlot extends JPanel +{ + private final JLabel imageLabel; + + public InventorySetupSlot(Color color) + { + imageLabel = new JLabel(); + imageLabel.setVerticalAlignment(SwingConstants.CENTER); + setPreferredSize(new Dimension(46, 42)); + setBackground(color); + add(imageLabel); + + } + + public void setImageLabel(String toolTip, AsyncBufferedImage itemImage) + { + if (itemImage == null || toolTip == null) + { + imageLabel.setToolTipText(""); + imageLabel.setIcon(null); + imageLabel.revalidate(); + return; + } + + imageLabel.setToolTipText(toolTip); + itemImage.addTo(imageLabel); + + validate(); + repaint(); + } +} \ No newline at end of file diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/add_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/add_icon.png new file mode 100644 index 0000000000..343c3dce0c Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/add_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/inventorysetups_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/inventorysetups_icon.png new file mode 100644 index 0000000000..70e415beec Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/inventorysetups_icon.png differ diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/remove_icon.png b/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/remove_icon.png new file mode 100644 index 0000000000..3f4915d041 Binary files /dev/null and b/runelite-client/src/main/resources/net/runelite/client/plugins/inventorysetups/remove_icon.png differ