From dd4a4dd8c91068dc440db6ea662e29e794b2aef5 Mon Sep 17 00:00:00 2001 From: Hydrox6 Date: Thu, 22 Aug 2019 02:41:31 +0100 Subject: [PATCH 01/13] loot tracker: truncate name instead of kills --- .../plugins/loottracker/LootTrackerBox.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java index 0f3b11cea7..372d1bd25a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerBox.java @@ -29,6 +29,7 @@ import com.google.common.base.Strings; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; +import java.awt.Dimension; import java.awt.GridLayout; import java.awt.image.BufferedImage; import java.util.ArrayList; @@ -36,6 +37,8 @@ import java.util.Arrays; import java.util.List; import java.util.function.BiConsumer; import javax.annotation.Nullable; +import javax.swing.Box; +import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JMenuItem; @@ -56,11 +59,12 @@ import net.runelite.client.util.Text; class LootTrackerBox extends JPanel { private static final int ITEMS_PER_ROW = 5; + private static final int TITLE_PADDING = 5; private final JPanel itemContainer = new JPanel(); private final JLabel priceLabel = new JLabel(); private final JLabel subTitleLabel = new JLabel(); - private final JPanel logTitle = new JPanel(new BorderLayout(5, 0)); + private final JPanel logTitle = new JPanel(); private final JLabel titleLabel = new JLabel(); private final ItemManager itemManager; @Getter(AccessLevel.PACKAGE) @@ -88,27 +92,33 @@ class LootTrackerBox extends JPanel setLayout(new BorderLayout(0, 1)); setBorder(new EmptyBorder(5, 0, 0, 0)); + logTitle.setLayout(new BoxLayout(logTitle, BoxLayout.X_AXIS)); logTitle.setBorder(new EmptyBorder(7, 7, 7, 7)); logTitle.setBackground(ColorScheme.DARKER_GRAY_COLOR.darker()); titleLabel.setText(Text.removeTags(id)); titleLabel.setFont(FontManager.getRunescapeSmallFont()); titleLabel.setForeground(Color.WHITE); - - logTitle.add(titleLabel, BorderLayout.WEST); + // Set a size to make BoxLayout truncate the name + titleLabel.setMinimumSize(new Dimension(1, titleLabel.getPreferredSize().height)); + logTitle.add(titleLabel); subTitleLabel.setFont(FontManager.getRunescapeSmallFont()); subTitleLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR); - logTitle.add(subTitleLabel, BorderLayout.CENTER); if (!Strings.isNullOrEmpty(subtitle)) { subTitleLabel.setText(subtitle); } + logTitle.add(Box.createRigidArea(new Dimension(TITLE_PADDING, 0))); + logTitle.add(subTitleLabel); + logTitle.add(Box.createHorizontalGlue()); + logTitle.add(Box.createRigidArea(new Dimension(TITLE_PADDING, 0))); + priceLabel.setFont(FontManager.getRunescapeSmallFont()); priceLabel.setForeground(ColorScheme.LIGHT_GRAY_COLOR); - logTitle.add(priceLabel, BorderLayout.EAST); + logTitle.add(priceLabel); add(logTitle, BorderLayout.NORTH); add(itemContainer, BorderLayout.CENTER); From f59d48fe15f6ea8eb1c3a40d49a45af3ca466aaf Mon Sep 17 00:00:00 2001 From: Hydrox6 Date: Mon, 9 Sep 2019 13:56:10 +0100 Subject: [PATCH 02/13] clues: fix emote hint ordering --- .../client/plugins/cluescrolls/clues/EmoteClue.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java index f3a6e9acc3..406fbc504b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java @@ -216,12 +216,6 @@ public class EmoteClue extends ClueScroll implements TextClueScroll, LocationClu .leftColor(TITLED_CONTENT_COLOR) .build()); - panelComponent.getChildren().add(LineComponent.builder().left("Location:").build()); - panelComponent.getChildren().add(LineComponent.builder() - .left(getLocationName()) - .leftColor(TITLED_CONTENT_COLOR) - .build()); - if (getSecondEmote() != null) { panelComponent.getChildren().add(LineComponent.builder() @@ -230,6 +224,12 @@ public class EmoteClue extends ClueScroll implements TextClueScroll, LocationClu .build()); } + panelComponent.getChildren().add(LineComponent.builder().left("Location:").build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(getLocationName()) + .leftColor(TITLED_CONTENT_COLOR) + .build()); + if (itemRequirements.length > 0) { Client client = plugin.getClient(); From 36bb7c8401fdd9ae88a185e9e193b4f7183aed54 Mon Sep 17 00:00:00 2001 From: Jordan Atwood Date: Tue, 10 Sep 2019 16:58:27 -0700 Subject: [PATCH 03/13] cluescrolls: Reset clue on new beginner/master step When receiving a new beginner or master clue step (which can be detected by checking the item ID shown in the chat dialog), the clue ID does not change, because all beginner and master clues share a single ID. Hence, we can reset the current clue when the "You've found a new clue!" dialog appears to prevent stale clue information from persisting between steps. Closes runelite/runelite#9830 --- .../plugins/cluescrolls/ClueScrollPlugin.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java index cd3dbed6c6..47c64a5488 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java @@ -233,14 +233,6 @@ public class ClueScrollPlugin extends Plugin ((SkillChallengeClue) clue).setChallengeCompleted(true); } } - - if (!event.getMessage().equals("The strange device cools as you find your treasure.") - && !event.getMessage().equals("Well done, you've completed the Treasure Trail!")) - { - return; - } - - resetClue(true); } @Subscribe @@ -408,6 +400,15 @@ public class ClueScrollPlugin extends Plugin } } + // Reset clue when receiving a new beginner or master clue + // These clues use a single item ID, so we cannot detect step changes based on the item ID changing + final Widget chatDialogClueItem = client.getWidget(WidgetInfo.DIALOG_SPRITE_SPRITE); + if (chatDialogClueItem != null + && (chatDialogClueItem.getItemId() == ItemID.CLUE_SCROLL_BEGINNER || chatDialogClueItem.getItemId() == ItemID.CLUE_SCROLL_MASTER)) + { + resetClue(true); + } + // If we have a clue, save that knowledge // so the clue window doesn't have to be open. updateClue(findClueScroll()); From 27b5d7308f23411092d5d1f5e2bb0ca214ab5ace Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 12 Sep 2019 21:08:04 -0400 Subject: [PATCH 04/13] menu entry swapper: fix menu searching optimization --- .../MenuEntrySwapperPlugin.java | 230 ++++++++++-------- .../MenuEntrySwapperPluginTest.java | 212 ++++++++++++++++ 2 files changed, 347 insertions(+), 95 deletions(-) create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPluginTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java index 8c0e99df93..74241613f2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java @@ -39,9 +39,9 @@ import net.runelite.api.ItemComposition; import net.runelite.api.MenuAction; import net.runelite.api.MenuEntry; import net.runelite.api.NPC; +import net.runelite.api.events.ClientTick; import net.runelite.api.events.ConfigChanged; import net.runelite.api.events.FocusChanged; -import net.runelite.api.events.MenuEntryAdded; import net.runelite.api.events.MenuOpened; import net.runelite.api.events.MenuOptionClicked; import net.runelite.api.events.PostItemComposition; @@ -355,31 +355,16 @@ public class MenuEntrySwapperPlugin extends Plugin } } - @Subscribe - public void onMenuEntryAdded(MenuEntryAdded event) + private void swapMenuEntry(int index, MenuEntry menuEntry) { - final String option = Text.removeTags(event.getOption()).toLowerCase(); - - if (event.getType() == MenuAction.CANCEL.getId()) - { - optionIndexes.clear(); - } - - int size = optionIndexes.size(); - optionIndexes.put(option, size); - - if (client.getGameState() != GameState.LOGGED_IN) - { - return; - } - - final int eventId = event.getIdentifier(); - final String target = Text.removeTags(event.getTarget()).toLowerCase(); + final int eventId = menuEntry.getIdentifier(); + final String option = Text.removeTags(menuEntry.getOption()).toLowerCase(); + final String target = Text.removeTags(menuEntry.getTarget()).toLowerCase(); final NPC hintArrowNpc = client.getHintArrowNpc(); if (hintArrowNpc != null && hintArrowNpc.getIndex() == eventId - && NPC_MENU_TYPES.contains(MenuAction.of(event.getType()))) + && NPC_MENU_TYPES.contains(MenuAction.of(menuEntry.getType()))) { return; } @@ -388,128 +373,128 @@ public class MenuEntrySwapperPlugin extends Plugin { if (config.swapPickpocket() && shouldSwapPickpocket(target)) { - swap("pickpocket", option, target, true); + swap("pickpocket", option, target, index); } if (config.swapAbyssTeleport() && target.contains("mage of zamorak")) { - swap("teleport", option, target, true); + swap("teleport", option, target, index); } if (config.swapHardWoodGrove() && target.contains("rionasta")) { - swap("send-parcel", option, target, true); + swap("send-parcel", option, target, index); } if (config.swapBank()) { - swap("bank", option, target, true); + swap("bank", option, target, index); } if (config.swapContract()) { - swap("contract", option, target, true); + swap("contract", option, target, index); } if (config.swapExchange()) { - swap("exchange", option, target, true); + swap("exchange", option, target, index); } if (config.swapDarkMage()) { - swap("repairs", option, target, true); + swap("repairs", option, target, index); } // make sure assignment swap is higher priority than trade swap for slayer masters if (config.swapAssignment()) { - swap("assignment", option, target, true); + swap("assignment", option, target, index); } if (config.swapTrade()) { - swap("trade", option, target, true); - swap("trade-with", option, target, true); - swap("shop", option, target, true); + swap("trade", option, target, index); + swap("trade-with", option, target, index); + swap("shop", option, target, index); } if (config.claimSlime() && target.equals("robin")) { - swap("claim-slime", option, target, true); + swap("claim-slime", option, target, index); } if (config.swapTravel()) { - swap("travel", option, target, true); - swap("pay-fare", option, target, true); - swap("charter", option, target, true); - swap("take-boat", option, target, true); - swap("fly", option, target, true); - swap("jatizso", option, target, true); - swap("neitiznot", option, target, true); - swap("rellekka", option, target, true); - swap("follow", option, target, true); - swap("transport", option, target, true); + swap("travel", option, target, index); + swap("pay-fare", option, target, index); + swap("charter", option, target, index); + swap("take-boat", option, target, index); + swap("fly", option, target, index); + swap("jatizso", option, target, index); + swap("neitiznot", option, target, index); + swap("rellekka", option, target, index); + swap("follow", option, target, index); + swap("transport", option, target, index); } if (config.swapPay()) { - swap("pay", option, target, true); - swap("pay (", option, target, false); + swap("pay", option, target, index); + swapContains("pay (", option, target, index); } if (config.swapDecant()) { - swap("decant", option, target, true); + swap("decant", option, target, index); } if (config.swapQuick()) { - swap("quick-travel", option, target, true); + swap("quick-travel", option, target, index); } if (config.swapEnchant()) { - swap("enchant", option, target, true); + swap("enchant", option, target, index); } } else if (config.swapTravel() && option.equals("pass") && target.equals("energy barrier")) { - swap("pay-toll(2-ecto)", option, target, true); + swap("pay-toll(2-ecto)", option, target, index); } else if (config.swapTravel() && option.equals("open") && target.equals("gate")) { - swap("pay-toll(10gp)", option, target, true); + swap("pay-toll(10gp)", option, target, index); } else if (config.swapHardWoodGrove() && option.equals("open") && target.equals("hardwood grove doors")) { - swap("quick-pay(100)", option, target, true); + swap("quick-pay(100)", option, target, index); } else if (config.swapTravel() && option.equals("inspect") && target.equals("trapdoor")) { - swap("travel", option, target, true); + swap("travel", option, target, index); } else if (config.swapHarpoon() && option.equals("cage")) { - swap("harpoon", option, target, true); + swap("harpoon", option, target, index); } else if (config.swapHarpoon() && (option.equals("big net") || option.equals("net"))) { - swap("harpoon", option, target, true); + swap("harpoon", option, target, index); } else if (config.swapHomePortal() != HouseMode.ENTER && option.equals("enter")) { switch (config.swapHomePortal()) { case HOME: - swap("home", option, target, true); + swap("home", option, target, index); break; case BUILD_MODE: - swap("build mode", option, target, true); + swap("build mode", option, target, index); break; case FRIENDS_HOUSE: - swap("friend's house", option, target, true); + swap("friend's house", option, target, index); break; } } @@ -518,68 +503,68 @@ public class MenuEntrySwapperPlugin extends Plugin { if (config.swapFairyRing() == FairyRingMode.LAST_DESTINATION) { - swap("last-destination", option, target, false); + swapContains("last-destination", option, target, index); } else if (config.swapFairyRing() == FairyRingMode.CONFIGURE) { - swap("configure", option, target, false); + swapContains("configure", option, target, index); } } else if (config.swapFairyRing() == FairyRingMode.ZANARIS && option.equals("tree")) { - swap("zanaris", option, target, false); + swapContains("zanaris", option, target, index); } else if (config.swapBoxTrap() && (option.equals("check") || option.equals("dismantle"))) { - swap("reset", option, target, true); + swap("reset", option, target, index); } else if (config.swapBoxTrap() && option.equals("take")) { - swap("lay", option, target, true); + swap("lay", option, target, index); } else if (config.swapChase() && option.equals("pick-up")) { - swap("chase", option, target, true); + swap("chase", option, target, index); } else if (config.swapBirdhouseEmpty() && option.equals("interact") && target.contains("birdhouse")) { - swap("empty", option, target, true); + swap("empty", option, target, index); } else if (config.swapQuick() && option.equals("enter")) { - swap("quick-enter", option, target, true); + swap("quick-enter", option, target, index); } else if (config.swapQuick() && option.equals("ring")) { - swap("quick-start", option, target, true); + swap("quick-start", option, target, index); } else if (config.swapQuick() && option.equals("pass")) { - swap("quick-pass", option, target, true); - swap("quick pass", option, target, true); + swap("quick-pass", option, target, index); + swap("quick pass", option, target, index); } else if (config.swapQuick() && option.equals("open")) { - swap("quick-open", option, target, true); + swap("quick-open", option, target, index); } else if (config.swapQuick() && option.equals("climb-down")) { - swap("quick-start", option, target, true); - swap("pay", option, target, true); + swap("quick-start", option, target, index); + swap("pay", option, target, index); } else if (config.swapAdmire() && option.equals("admire")) { - swap("teleport", option, target, true); - swap("spellbook", option, target, true); - swap("perks", option, target, true); + swap("teleport", option, target, index); + swap("spellbook", option, target, index); + swap("perks", option, target, index); } else if (config.swapPrivate() && option.equals("shared")) { - swap("private", option, target, true); + swap("private", option, target, index); } else if (config.swapPick() && option.equals("pick")) { - swap("pick-lots", option, target, true); + swap("pick-lots", option, target, index); } else if (config.shiftClickCustomization() && shiftModifier && !option.equals("use")) { @@ -587,25 +572,25 @@ public class MenuEntrySwapperPlugin extends Plugin if (customOption != null && customOption == -1) { - swap("use", option, target, true); + swap("use", option, target, index); } } // Put all item-related swapping after shift-click else if (config.swapTeleportItem() && option.equals("wear")) { - swap("rub", option, target, true); - swap("teleport", option, target, true); + swap("rub", option, target, index); + swap("teleport", option, target, index); } else if (option.equals("wield")) { if (config.swapTeleportItem()) { - swap("teleport", option, target, true); + swap("teleport", option, target, index); } } else if (config.swapBones() && option.equals("bury")) { - swap("use", option, target, true); + swap("use", option, target, index); } } @@ -614,6 +599,35 @@ public class MenuEntrySwapperPlugin extends Plugin return !target.startsWith("villager") && !target.startsWith("bandit") && !target.startsWith("menaphite thug"); } + @Subscribe + public void onClientTick(ClientTick clientTick) + { + // The menu is not rebuilt when it is open, so don't swap or else it will + // repeatedly swap entries + if (client.getGameState() != GameState.LOGGED_IN || client.isMenuOpen()) + { + return; + } + + MenuEntry[] menuEntries = client.getMenuEntries(); + + // Build option map for quick lookup in findIndex + int idx = 0; + optionIndexes.clear(); + for (MenuEntry entry : menuEntries) + { + String option = Text.removeTags(entry.getOption()).toLowerCase(); + optionIndexes.put(option, idx++); + } + + // Perform swaps + idx = 0; + for (MenuEntry entry : menuEntries) + { + swapMenuEntry(idx++, entry); + } + } + @Subscribe public void onPostItemComposition(PostItemComposition event) { @@ -635,7 +649,30 @@ public class MenuEntrySwapperPlugin extends Plugin } } - private int searchIndex(MenuEntry[] entries, String option, String target, boolean strict) + private void swap(String optionA, String optionB, String target, int index) + { + swap(optionA, optionB, target, index, true); + } + + private void swapContains(String optionA, String optionB, String target, int index) + { + swap(optionA, optionB, target, index, false); + } + + private void swap(String optionA, String optionB, String target, int index, boolean strict) + { + MenuEntry[] menuEntries = client.getMenuEntries(); + + int thisIndex = findIndex(menuEntries, index, optionB, target, strict); + int optionIdx = findIndex(menuEntries, thisIndex, optionA, target, strict); + + if (thisIndex >= 0 && optionIdx >= 0) + { + swap(optionIndexes, menuEntries, optionIdx, thisIndex); + } + } + + private int findIndex(MenuEntry[] entries, int limit, String option, String target, boolean strict) { if (strict) { @@ -649,7 +686,8 @@ public class MenuEntrySwapperPlugin extends Plugin MenuEntry entry = entries[idx]; String entryTarget = Text.removeTags(entry.getTarget()).toLowerCase(); - if (entryTarget.equals(target)) + // Limit to the last index which is prior to the current entry + if (idx <= limit && entryTarget.equals(target)) { return idx; } @@ -657,8 +695,8 @@ public class MenuEntrySwapperPlugin extends Plugin } else { - // Without strict matching we have to iterate all entries... - for (int i = entries.length - 1; i >= 0; i--) + // Without strict matching we have to iterate all entries up to the current limit... + for (int i = limit; i >= 0; i--) { MenuEntry entry = entries[i]; String entryOption = Text.removeTags(entry.getOption()).toLowerCase(); @@ -669,25 +707,27 @@ public class MenuEntrySwapperPlugin extends Plugin return i; } } + } return -1; } - private void swap(String optionA, String optionB, String target, boolean strict) + private void swap(ArrayListMultimap optionIndexes, MenuEntry[] entries, int index1, int index2) { - MenuEntry[] entries = client.getMenuEntries(); + MenuEntry entry = entries[index1]; + entries[index1] = entries[index2]; + entries[index2] = entry; - int idxA = searchIndex(entries, optionA, target, strict); - int idxB = searchIndex(entries, optionB, target, strict); + client.setMenuEntries(entries); - if (idxA >= 0 && idxB >= 0) + // Rebuild option indexes + optionIndexes.clear(); + int idx = 0; + for (MenuEntry menuEntry : entries) { - MenuEntry entry = entries[idxA]; - entries[idxA] = entries[idxB]; - entries[idxB] = entry; - - client.setMenuEntries(entries); + String option = Text.removeTags(menuEntry.getOption()).toLowerCase(); + optionIndexes.put(option, idx++); } } diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPluginTest.java new file mode 100644 index 0000000000..fd0fe86f91 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPluginTest.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2019, Adam + * 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.menuentryswapper; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.events.ClientTick; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.game.ItemManager; +import static org.junit.Assert.assertArrayEquals; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import static org.mockito.Matchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +@RunWith(MockitoJUnitRunner.class) +public class MenuEntrySwapperPluginTest +{ + @Mock + @Bind + Client client; + + @Mock + @Bind + ConfigManager configManager; + + @Mock + @Bind + ItemManager itemManager; + + @Mock + @Bind + MenuEntrySwapperConfig config; + + @Inject + MenuEntrySwapperPlugin menuEntrySwapperPlugin; + + private MenuEntry[] entries; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + + when(client.getMenuEntries()).thenAnswer((Answer) invocationOnMock -> + { + // The menu implementation returns a copy of the array, which causes swap() to not + // modify the same array being iterated in onClientTick + MenuEntry[] copy = new MenuEntry[entries.length]; + System.arraycopy(entries, 0, copy, 0, entries.length); + return copy; + }); + doAnswer((Answer) invocationOnMock -> + { + Object argument = invocationOnMock.getArguments()[0]; + entries = (MenuEntry[]) argument; + return null; + }).when(client).setMenuEntries(any(MenuEntry[].class)); + } + + private static MenuEntry menu(String option, String target, MenuAction menuAction) + { + MenuEntry menuEntry = new MenuEntry(); + menuEntry.setOption(option); + menuEntry.setTarget(target); + menuEntry.setType(menuAction.getId()); + return menuEntry; + } + + @Test + public void testSlayerMaster() + { + when(config.swapTrade()).thenReturn(true); + when(config.swapAssignment()).thenReturn(true); + + entries = new MenuEntry[]{ + menu("Cancel", "", MenuAction.CANCEL), + menu("Rewards", "Duradel", MenuAction.NPC_FIFTH_OPTION), + menu("Trade", "Duradel", MenuAction.NPC_FOURTH_OPTION), + menu("Assignment", "Duradel", MenuAction.NPC_THIRD_OPTION), + menu("Talk-to", "Duradel", MenuAction.NPC_FIRST_OPTION), + }; + menuEntrySwapperPlugin.onClientTick(new ClientTick()); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MenuEntry[].class); + // Once for assignment<->talk-to and once for trade<->talk-to + verify(client, times(2)).setMenuEntries(argumentCaptor.capture()); + + MenuEntry[] value = argumentCaptor.getValue(); + assertArrayEquals(new MenuEntry[]{ + menu("Cancel", "", MenuAction.CANCEL), + menu("Rewards", "Duradel", MenuAction.NPC_FIFTH_OPTION), + menu("Talk-to", "Duradel", MenuAction.NPC_FIRST_OPTION), + menu("Trade", "Duradel", MenuAction.NPC_FOURTH_OPTION), + menu("Assignment", "Duradel", MenuAction.NPC_THIRD_OPTION), + }, argumentCaptor.getValue()); + } + + @Test + public void testBankers() + { + when(config.swapBank()).thenReturn(true); + + entries = new MenuEntry[]{ + menu("Cancel", "", MenuAction.CANCEL), + menu("Examine", "Gnome banker", MenuAction.EXAMINE_NPC), + menu("Examine", "Gnome banker", MenuAction.EXAMINE_NPC), + menu("Walk here", "", MenuAction.WALK), + + // Banker 2 + menu("Collect", "Gnome banker", MenuAction.NPC_FOURTH_OPTION), + menu("Bank", "Gnome banker", MenuAction.NPC_THIRD_OPTION), + menu("Talk-to", "Gnome banker", MenuAction.NPC_FIRST_OPTION), + + // Banker 1 + menu("Collect", "Gnome banker", MenuAction.NPC_FOURTH_OPTION), + menu("Bank", "Gnome banker", MenuAction.NPC_THIRD_OPTION), + menu("Talk-to", "Gnome banker", MenuAction.NPC_FIRST_OPTION), + }; + + menuEntrySwapperPlugin.onClientTick(new ClientTick()); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MenuEntry[].class); + verify(client, times(2)).setMenuEntries(argumentCaptor.capture()); + + assertArrayEquals(new MenuEntry[]{ + menu("Cancel", "", MenuAction.CANCEL), + menu("Examine", "Gnome banker", MenuAction.EXAMINE_NPC), + menu("Examine", "Gnome banker", MenuAction.EXAMINE_NPC), + menu("Walk here", "", MenuAction.WALK), + + // Banker 2 + menu("Collect", "Gnome banker", MenuAction.NPC_FOURTH_OPTION), + menu("Talk-to", "Gnome banker", MenuAction.NPC_FIRST_OPTION), + menu("Bank", "Gnome banker", MenuAction.NPC_THIRD_OPTION), + + // Banker 1 + menu("Collect", "Gnome banker", MenuAction.NPC_FOURTH_OPTION), + menu("Talk-to", "Gnome banker", MenuAction.NPC_FIRST_OPTION), + menu("Bank", "Gnome banker", MenuAction.NPC_THIRD_OPTION), + }, argumentCaptor.getValue()); + } + + @Test + public void testContains() + { + when(config.swapPay()).thenReturn(true); + + entries = new MenuEntry[]{ + menu("Cancel", "", MenuAction.CANCEL), + menu("Examine", "Kragen", MenuAction.EXAMINE_NPC), + menu("Walk here", "", MenuAction.WALK), + + menu("Pay (south)", "Kragen", MenuAction.NPC_FOURTH_OPTION), + menu("Pay (north)", "Kragen", MenuAction.NPC_THIRD_OPTION), + menu("Talk-to", "Kragen", MenuAction.NPC_FIRST_OPTION), + }; + + menuEntrySwapperPlugin.onClientTick(new ClientTick()); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(MenuEntry[].class); + verify(client).setMenuEntries(argumentCaptor.capture()); + + assertArrayEquals(new MenuEntry[]{ + menu("Cancel", "", MenuAction.CANCEL), + menu("Examine", "Kragen", MenuAction.EXAMINE_NPC), + menu("Walk here", "", MenuAction.WALK), + + menu("Pay (south)", "Kragen", MenuAction.NPC_FOURTH_OPTION), + menu("Talk-to", "Kragen", MenuAction.NPC_FIRST_OPTION), + menu("Pay (north)", "Kragen", MenuAction.NPC_THIRD_OPTION), + }, argumentCaptor.getValue()); + } +} \ No newline at end of file From 80e99f0263f8d4f812011c518a0541fc624e0a5f Mon Sep 17 00:00:00 2001 From: eric-weaver Date: Sun, 15 Sep 2019 17:18:10 -0400 Subject: [PATCH 05/13] menu entry swapper: add house advertisement board --- .../HouseAdvertisementMode.java | 45 +++++++++++++++++++ .../MenuEntrySwapperConfig.java | 14 +++++- .../MenuEntrySwapperPlugin.java | 12 +++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/HouseAdvertisementMode.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/HouseAdvertisementMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/HouseAdvertisementMode.java new file mode 100644 index 0000000000..98860253bd --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/HouseAdvertisementMode.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, Adam + * 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.menuentryswapper; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum HouseAdvertisementMode +{ + VIEW("View"), + ADD_HOUSE("Add-House"), + VISIT_LAST("Visit-Last"); + + private final String name; + + @Override + public String toString() + { + return name; + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java index b7decee5d0..e687522286 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperConfig.java @@ -99,7 +99,7 @@ public interface MenuEntrySwapperConfig extends Config ) default boolean swapContract() { - return true; + return true; } @ConfigItem( @@ -122,7 +122,7 @@ public interface MenuEntrySwapperConfig extends Config return true; } - @ConfigItem( + @ConfigItem( keyName = "swapDarkMage", name = "Repairs", description = "Swap Talk-to with Repairs for Dark Mage" @@ -192,6 +192,16 @@ public interface MenuEntrySwapperConfig extends Config return HouseMode.HOME; } + @ConfigItem( + keyName = "swapHouseAdvertisement", + name = "House Advertisement", + description = "Swap View with Add-House or Visit-Last on House Advertisement board" + ) + default HouseAdvertisementMode swapHouseAdvertisement() + { + return HouseAdvertisementMode.VIEW; + } + @ConfigItem( keyName = "swapPickpocket", name = "Pickpocket", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java index 74241613f2..d19b47455f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/menuentryswapper/MenuEntrySwapperPlugin.java @@ -498,6 +498,18 @@ public class MenuEntrySwapperPlugin extends Plugin break; } } + else if (config.swapHouseAdvertisement() != HouseAdvertisementMode.VIEW && option.equals("view")) + { + switch (config.swapHouseAdvertisement()) + { + case ADD_HOUSE: + swap("add-house", option, target, index); + break; + case VISIT_LAST: + swap("visit-last", option, target, index); + break; + } + } else if (config.swapFairyRing() != FairyRingMode.OFF && config.swapFairyRing() != FairyRingMode.ZANARIS && (option.equals("zanaris") || option.equals("configure") || option.equals("tree"))) { From 76653070d7bfeac0dce7a3ee4cb08ecc8b4abef5 Mon Sep 17 00:00:00 2001 From: John Polich Date: Mon, 16 Sep 2019 10:03:51 -0400 Subject: [PATCH 06/13] clues: update "Salty peter" solution for Hosidius rework --- .../runelite/client/plugins/cluescrolls/clues/CrypticClue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java index 7b4bec5941..73a1d95331 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java @@ -274,7 +274,7 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc new CrypticClue("Search the crates in Horvik's armoury.", CRATE_5106, new WorldPoint(3228, 3433, 0), "Horvik's in Varrock"), new CrypticClue("Ghommal wishes to be impressed by how strong your equipment is.", "Ghommal", new WorldPoint(2878, 3546, 0), "Speak to Ghommal at the Warriors' Guild with a total Melee Strength bonus of over 100."), new CrypticClue("Shhhh!", "Logosia", new WorldPoint(1633, 3808, 0), "Speak to Logosia in the Arceuus Library's ground floor."), - new CrypticClue("Salty peter.", "Konoo", new WorldPoint(1703, 3524, 0), "Talk to Konoo who is digging saltpetre in Hosidius, south of the bank."), + new CrypticClue("Salty peter.", "Konoo", new WorldPoint(1703, 3524, 0), "Talk to Konoo who is digging saltpetre in Hosidius, north-east of the Woodcutting Guild."), new CrypticClue("Talk to Zeke in Al Kharid.", "Zeke", new WorldPoint(3287, 3190, 0), "Zeke is the owner of the scimitar shop in Al Kharid."), new CrypticClue("Guthix left his mark in a fiery lake, dig at the tip of it.", new WorldPoint(3069, 3935, 0), "Dig at the tip of the lava lake that is shaped like a Guthixian symbol, west of the Mage Arena."), new CrypticClue("Search the drawers in the upstairs of a house in Catherby.", DRAWERS_350, new WorldPoint(2809, 3451, 1), "Perdu's house in Catherby."), From 6decc19213f601157e23bcf63393bba1be8866fa Mon Sep 17 00:00:00 2001 From: John Polich Date: Mon, 16 Sep 2019 10:13:17 -0400 Subject: [PATCH 07/13] clues: add fairy ring code to "Graveyard west of Shayzien" solution --- .../client/plugins/cluescrolls/clues/CoordinateClue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java index 268e03f5a0..55735a3767 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CoordinateClue.java @@ -77,7 +77,7 @@ public class CoordinateClue extends ClueScroll implements TextClueScroll, Locati .put(new WorldPoint(2363, 3531, 0), "North-east of Eagles' Peak.") .put(new WorldPoint(2919, 3535, 0), "East of Burthorpe pub.") .put(new WorldPoint(3548, 3560, 0), "Inside Fenkenstrain's Castle.") - .put(new WorldPoint(1456, 3620, 0), "Graveyard west of Shayzien.") + .put(new WorldPoint(1456, 3620, 0), "Graveyard west of Shayzien (DJR).") .put(new WorldPoint(2735, 3638, 0), "East of Rellekka, north-west of Golden Apple Tree (AJR).") .put(new WorldPoint(2681, 3653, 0), "Rellekka, in the garden of the south-east house.") .put(new WorldPoint(2537, 3881, 0), "Miscellania.") From 6c30d31a57def7db6bde32f2b3c39a94ada78e72 Mon Sep 17 00:00:00 2001 From: Daniel Bolink Date: Tue, 10 Sep 2019 17:20:05 -0700 Subject: [PATCH 08/13] Remove Half timers for Snare, Bind and Entangle --- .../client/plugins/timers/GameTimer.java | 3 -- .../client/plugins/timers/TimersPlugin.java | 37 ++----------------- 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java b/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java index 0a37f8cd96..37ca5e82a3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timers/GameTimer.java @@ -52,11 +52,8 @@ enum GameTimer SUPERANTIFIRE(ItemID.SUPER_ANTIFIRE_POTION4, GameTimerImageType.ITEM, "Super antifire", 3, ChronoUnit.MINUTES), ANTIDOTEPLUSPLUS(ItemID.ANTIDOTE4_5952, GameTimerImageType.ITEM, "Antidote++", 12, ChronoUnit.MINUTES), BIND(SpriteID.SPELL_BIND, GameTimerImageType.SPRITE, "Bind", GraphicID.BIND, 5, ChronoUnit.SECONDS, true), - HALFBIND(SpriteID.SPELL_BIND, GameTimerImageType.SPRITE, "Half Bind", GraphicID.BIND, 2500, ChronoUnit.MILLIS, true), SNARE(SpriteID.SPELL_SNARE, GameTimerImageType.SPRITE, "Snare", GraphicID.SNARE, 10, ChronoUnit.SECONDS, true), - HALFSNARE(SpriteID.SPELL_SNARE, GameTimerImageType.SPRITE, "Half Snare", GraphicID.SNARE, 5, ChronoUnit.SECONDS, true), ENTANGLE(SpriteID.SPELL_ENTANGLE, GameTimerImageType.SPRITE, "Entangle", GraphicID.ENTANGLE, 15, ChronoUnit.SECONDS, true), - HALFENTANGLE(SpriteID.SPELL_ENTANGLE, GameTimerImageType.SPRITE, "Half Entangle", GraphicID.ENTANGLE, 7500, ChronoUnit.MILLIS, true), ICERUSH(SpriteID.SPELL_ICE_RUSH, GameTimerImageType.SPRITE, "Ice rush", GraphicID.ICE_RUSH, 5, ChronoUnit.SECONDS, true), ICEBURST(SpriteID.SPELL_ICE_BURST, GameTimerImageType.SPRITE, "Ice burst", GraphicID.ICE_BURST, 10, ChronoUnit.SECONDS, true), ICEBLITZ(SpriteID.SPELL_ICE_BLITZ, GameTimerImageType.SPRITE, "Ice blitz", GraphicID.ICE_BLITZ, 15, ChronoUnit.SECONDS, true), diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java index 8e8704b677..1390cc883b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/timers/TimersPlugin.java @@ -43,7 +43,6 @@ import net.runelite.api.ItemID; import net.runelite.api.NPC; import net.runelite.api.NpcID; import net.runelite.api.Player; -import net.runelite.api.Prayer; import net.runelite.api.Varbits; import net.runelite.api.WorldType; import net.runelite.api.coords.WorldPoint; @@ -324,11 +323,8 @@ public class TimersPlugin extends Plugin if (!config.showFreezes()) { removeGameTimer(BIND); - removeGameTimer(HALFBIND); removeGameTimer(SNARE); - removeGameTimer(HALFSNARE); removeGameTimer(ENTANGLE); - removeGameTimer(HALFENTANGLE); removeGameTimer(ICERUSH); removeGameTimer(ICEBURST); removeGameTimer(ICEBLITZ); @@ -787,44 +783,17 @@ public class TimersPlugin extends Plugin { if (actor.getGraphic() == BIND.getGraphicId()) { - if (client.isPrayerActive(Prayer.PROTECT_FROM_MAGIC) - && !client.getWorldType().contains(WorldType.SEASONAL_DEADMAN) - && !client.getWorldType().contains(WorldType.DEADMAN_TOURNAMENT)) - { - createGameTimer(HALFBIND); - } - else - { - createGameTimer(BIND); - } + createGameTimer(BIND); } if (actor.getGraphic() == SNARE.getGraphicId()) { - if (client.isPrayerActive(Prayer.PROTECT_FROM_MAGIC) - && !client.getWorldType().contains(WorldType.SEASONAL_DEADMAN) - && !client.getWorldType().contains(WorldType.DEADMAN_TOURNAMENT)) - { - createGameTimer(HALFSNARE); - } - else - { - createGameTimer(SNARE); - } + createGameTimer(SNARE); } if (actor.getGraphic() == ENTANGLE.getGraphicId()) { - if (client.isPrayerActive(Prayer.PROTECT_FROM_MAGIC) - && !client.getWorldType().contains(WorldType.SEASONAL_DEADMAN) - && !client.getWorldType().contains(WorldType.DEADMAN_TOURNAMENT)) - { - createGameTimer(HALFENTANGLE); - } - else - { - createGameTimer(ENTANGLE); - } + createGameTimer(ENTANGLE); } // downgrade freeze based on graphic, if at the same tick as the freeze message From 5bee3dcbb475baa41f0fa626927f153a4d1ebc1c Mon Sep 17 00:00:00 2001 From: Krysa <46086365+Krysaczek@users.noreply.github.com> Date: Mon, 16 Sep 2019 16:42:25 +0200 Subject: [PATCH 09/13] clues: update Warriors' Guild emote clue Update clue text and add Avernic defender --- .../runelite/client/plugins/cluescrolls/clues/EmoteClue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java index 406fbc504b..7231020f74 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/EmoteClue.java @@ -138,7 +138,7 @@ public class EmoteClue extends ClueScroll implements TextClueScroll, LocationClu new EmoteClue("Panic by the big egg where no one dare goes and the ground is burnt. Beware of double agents! Equip a dragon med helm, a TokTz-Ket-Xil, a brine sabre, rune platebody and an uncharged amulet of glory.", "Lava dragon isle", SOUTHEAST_CORNER_OF_LAVA_DRAGON_ISLE, new WorldPoint(3227, 3831, 0), PANIC, item(DRAGON_MED_HELM), item(TOKTZKETXIL), item(BRINE_SABRE), item(RUNE_PLATEBODY), item(AMULET_OF_GLORY)), new EmoteClue("Panic at the area flowers meet snow. Equip Blue D'hide vambs, a dragon spear and a rune plateskirt.", "Trollweiss mountain", HALFWAY_DOWN_TROLLWEISS_MOUNTAIN, new WorldPoint(2776, 3781, 0), PANIC, item(BLUE_DHIDE_VAMB), item(DRAGON_SPEAR), item(RUNE_PLATESKIRT), item(SLED_4084)), new EmoteClue("Do a push up at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", "Warriors' guild", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), PUSH_UP, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I))), - new EmoteClue("Blow a raspberry at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a dragon defender and a slayer helm of any kind.", "Warriors' guild", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), RASPBERRY, item(DRAGON_BATTLEAXE), item(DRAGON_DEFENDER), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I))), + new EmoteClue("Blow a raspberry at the bank of the Warrior's guild. Beware of double agents! Equip a dragon battleaxe, a slayer helm of any kind and a dragon defender or avernic defender.", "Warriors' guild", WARRIORS_GUILD_BANK_29047, new WorldPoint(2843, 3543, 0), RASPBERRY, item(DRAGON_BATTLEAXE), any("Dragon defender or Avernic defender", item(DRAGON_DEFENDER), item(AVERNIC_DEFENDER)), any("Any slayer helmet", item(SLAYER_HELMET), item(BLACK_SLAYER_HELMET), item(GREEN_SLAYER_HELMET), item(PURPLE_SLAYER_HELMET), item(RED_SLAYER_HELMET), item(TURQUOISE_SLAYER_HELMET), item(SLAYER_HELMET_I), item(BLACK_SLAYER_HELMET_I), item(GREEN_SLAYER_HELMET_I), item(PURPLE_SLAYER_HELMET_I), item(RED_SLAYER_HELMET_I), item(TURQUOISE_SLAYER_HELMET_I), item(HYDRA_SLAYER_HELMET), item(HYDRA_SLAYER_HELMET_I))), new EmoteClue("Blow a raspberry at the monkey cage in Ardougne Zoo. Equip a studded leather body, bronze platelegs and a normal staff with no orb.", "Ardougne Zoo", NEAR_THE_PARROTS_IN_ARDOUGNE_ZOO, new WorldPoint(2607, 3282, 0), RASPBERRY, item(STUDDED_BODY), item(BRONZE_PLATELEGS), item(STAFF)), new EmoteClue("Blow raspberries outside the entrance to Keep Le Faye. Equip a coif, an iron platebody and leather gloves.", "Keep Le Faye", OUTSIDE_KEEP_LE_FAYE, new WorldPoint(2757, 3401, 0), RASPBERRY, item(COIF), item(IRON_PLATEBODY), item(LEATHER_GLOVES)), new EmoteClue("Blow a raspberry in the Fishing Guild bank. Beware of double agents! Equip an elemental shield, blue dragonhide chaps and a rune warhammer.", "Fishing Guild", FISHING_GUILD_BANK, new WorldPoint(2588, 3419, 0), RASPBERRY, item(ELEMENTAL_SHIELD), item(BLUE_DHIDE_CHAPS), item(RUNE_WARHAMMER)), From 659232e1018a481a941fdaf015a8648cbfc5128f Mon Sep 17 00:00:00 2001 From: Krysa <46086365+Krysaczek@users.noreply.github.com> Date: Mon, 16 Sep 2019 16:48:32 +0200 Subject: [PATCH 10/13] clues: add Deviant spectre variant to cryptic clue --- .../runelite/client/plugins/cluescrolls/clues/CrypticClue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java index 73a1d95331..70d397a305 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/CrypticClue.java @@ -170,7 +170,7 @@ public class CrypticClue extends ClueScroll implements TextClueScroll, NpcClueSc new CrypticClue("Search the bush at the digsite centre.", BUSH_2357, new WorldPoint(3345, 3378, 0), "The bush is on the east side of the first pathway towards the digsite from the Exam Centre."), new CrypticClue("Someone watching the fights in the Duel Arena is your next destination.", "Jeed", new WorldPoint(3360, 3242, 0), "Talk to Jeed, found on the upper floors, at the Duel Arena."), new CrypticClue("It seems to have reached the end of the line, and it's still empty.", MINE_CART_6045, new WorldPoint(3041, 9820, 0), "Search the carts in the northern part of the Dwarven Mine."), - new CrypticClue("You'll have to plug your nose if you use this source of herbs.", null, "Kill an Aberrant spectre."), + new CrypticClue("You'll have to plug your nose if you use this source of herbs.", null, "Kill an Aberrant or Deviant spectre."), new CrypticClue("When you get tired of fighting, go deep, deep down until you need an antidote.", CRATE_357, new WorldPoint(2576, 9583, 0), "Go to Yanille Agility dungeon and fall into the place with the poison spiders. Search the crate by the stairs leading up."), new CrypticClue("Search the bookcase in the monastery.", BOOKCASE_380, new WorldPoint(3054, 3484, 0), "Search the southeastern bookcase at Edgeville Monastery."), new CrypticClue("Surprising? I bet he is...", "Sir Prysin", new WorldPoint(3205, 3474, 0), "Talk to Sir Prysin in Varrock Palace."), From 4ca63f2cb50d4b62cf6a363edd4b40a4e115dc75 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Mon, 16 Sep 2019 10:03:44 -0500 Subject: [PATCH 11/13] item mappings: update birds nest mapping --- .../src/main/java/net/runelite/client/game/ItemMapping.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java b/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java index 6534022788..12ff493bc2 100644 --- a/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java +++ b/runelite-client/src/main/java/net/runelite/client/game/ItemMapping.java @@ -228,7 +228,7 @@ public enum ItemMapping ITEM_CRYSTAL_SHIELD(CRYSTAL_WEAPON_SEED, CRYSTAL_SHIELD, CRYSTAL_SHIELD_24127, CRYSTAL_SHIELD_INACTIVE), // Bird nests - ITEM_BIRD_NEST(BIRD_NEST, BIRD_NEST_5071, BIRD_NEST_5072, BIRD_NEST_5073, BIRD_NEST_5074, BIRD_NEST_5075, BIRD_NEST_7413, BIRD_NEST_13653, BIRD_NEST_22798, BIRD_NEST_22800); + ITEM_BIRD_NEST(BIRD_NEST_5075, BIRD_NEST, BIRD_NEST_5071, BIRD_NEST_5072, BIRD_NEST_5073, BIRD_NEST_5074, BIRD_NEST_7413, BIRD_NEST_13653, BIRD_NEST_22798, BIRD_NEST_22800, CLUE_NEST_EASY, CLUE_NEST_MEDIUM, CLUE_NEST_HARD, CLUE_NEST_ELITE); private static final Multimap MAPPINGS = HashMultimap.create(); private final int tradeableItem; From 4d63ce73346915b0165730829c1be0efd55dd9b7 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 16 Sep 2019 14:08:40 -0400 Subject: [PATCH 12/13] bank tags: use item name string indexof from script This allows additional plugins to use the bankSearchFilter event to selectively add or remove matches, as the bank tag plugin is no longer performing the string contains check. --- .../plugins/banktags/BankTagsPlugin.java | 9 ++------ .../src/main/scripts/BankSearchFilter.rs2asm | 21 ++++++++----------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java index 6a3a7f3416..ea8f6b2792 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/banktags/BankTagsPlugin.java @@ -169,10 +169,6 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis switch (eventName) { - case "bankTagsActive": - // tell the script the bank tag plugin is active - intStack[intStackSize - 1] = 1; - break; case "setSearchBankInputText": stringStack[stringStackSize - 1] = SEARCH_BANK_INPUT_TEXT; break; @@ -184,7 +180,6 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis } case "bankSearchFilter": int itemId = intStack[intStackSize - 1]; - String itemName = stringStack[stringStackSize - 2]; String search = stringStack[stringStackSize - 1]; boolean tagSearch = search.startsWith(TAG_SEARCH); @@ -198,9 +193,9 @@ public class BankTagsPlugin extends Plugin implements MouseWheelListener, KeyLis // return true intStack[intStackSize - 2] = 1; } - else if (!tagSearch) + else if (tagSearch) { - intStack[intStackSize - 2] = itemName.contains(search) ? 1 : 0; + intStack[intStackSize - 2] = 0; } break; case "getSearchingTagTab": diff --git a/runelite-client/src/main/scripts/BankSearchFilter.rs2asm b/runelite-client/src/main/scripts/BankSearchFilter.rs2asm index fd3f415b5d..5f9db325af 100644 --- a/runelite-client/src/main/scripts/BankSearchFilter.rs2asm +++ b/runelite-client/src/main/scripts/BankSearchFilter.rs2asm @@ -1,7 +1,7 @@ .id 279 .int_stack_count 1 .string_stack_count 0 -.int_var_count 1 +.int_var_count 2 ; +1 for storage of search filter result .string_var_count 2 sconst "" sstore 0 @@ -30,16 +30,7 @@ LABEL19: oc_name lowercase sstore 0 -LABEL1337:; check if the bank tags plugin is active - iconst 1 ; true - iconst 0 ; load active boolean - sconst "bankTagsActive" ; push event name - runelite_callback ; invoke callback - if_icmpeq LABEL1338 ; if the plugin is active then jump to the label that decides if the - ; item should be shown - jump LABEL23 ; if the plugin is not active then jump to the normal label -LABEL1338:; let the bank tag plugin decide if the item should be shown - iconst 0 ; load return value + iconst -1 ; load return value iload 0 ; load item id sload 0 ; load item name sload 1 ; load search string @@ -48,7 +39,13 @@ LABEL1338:; let the bank tag plugin decide if the item should be shown pop_int ; pop item id pop_string ; pop search string pop_string ; pop item name - return ; return value + istore 1 ; store return value for the below comparisons + iload 1 + iconst 0 + if_icmpeq LABEL32 ; return 0 + iload 1 + iconst 1 + if_icmpeq LABEL30 ; return 1 LABEL23: sload 0 sload 1 From bcbcf638e51c8bd1aca7c5e6b18bfe2437dc62d2 Mon Sep 17 00:00:00 2001 From: Ron Young Date: Mon, 16 Sep 2019 14:11:53 -0400 Subject: [PATCH 13/13] bank plugin: add item value searching Co-authored-by: Adam --- .../client/plugins/bank/BankPlugin.java | 171 ++++++++++++++++-- .../client/plugins/bank/BankPluginTest.java | 110 +++++++++++ 2 files changed, 262 insertions(+), 19 deletions(-) create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/bank/BankPluginTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java index 057e7c20e4..4435471399 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/bank/BankPlugin.java @@ -26,15 +26,24 @@ */ package net.runelite.client.plugins.bank; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Multiset; import com.google.inject.Provides; +import java.text.ParseException; import java.util.Arrays; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.inject.Inject; import net.runelite.api.Client; +import static net.runelite.api.Constants.HIGH_ALCHEMY_MULTIPLIER; import net.runelite.api.InventoryID; import net.runelite.api.Item; +import net.runelite.api.ItemComposition; import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; import net.runelite.api.MenuEntry; import net.runelite.api.Varbits; import net.runelite.api.events.ItemContainerChanged; @@ -48,6 +57,7 @@ import net.runelite.api.widgets.WidgetInfo; 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.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.banktags.tabs.BankSearch; @@ -77,12 +87,20 @@ public class BankPlugin extends Plugin private static final String DEPOSIT_LOOT = "Deposit loot"; private static final String SEED_VAULT_TITLE = "Seed Vault"; + private static final String NUMBER_REGEX = "[0-9]+(\\.[0-9]+)?[kmb]?"; + private static final Pattern VALUE_SEARCH_PATTERN = Pattern.compile("^(?ge|ha|alch)?" + + " *(((?[<>=]|>=|<=) *(?" + NUMBER_REGEX + "))|" + + "((?" + NUMBER_REGEX + ") *- *(?" + NUMBER_REGEX + ")))$", Pattern.CASE_INSENSITIVE); + @Inject private Client client; @Inject private ClientThread clientThread; + @Inject + private ItemManager itemManager; + @Inject private BankConfig config; @@ -96,6 +114,7 @@ public class BankPlugin extends Plugin private ContainerCalculation seedVaultCalculation; private boolean forceRightClickFlag; + private Multiset itemQuantities; // bank item quantities for bank value search @Provides BankConfig getConfig(ConfigManager configManager) @@ -108,6 +127,7 @@ public class BankPlugin extends Plugin { clientThread.invokeLater(() -> bankSearch.reset(false)); forceRightClickFlag = false; + itemQuantities = null; } @Subscribe @@ -146,23 +166,36 @@ public class BankPlugin extends Plugin @Subscribe public void onScriptCallbackEvent(ScriptCallbackEvent event) { - if (!event.getEventName().equals("setBankTitle")) - { - return; - } - - final ContainerPrices prices = bankCalculation.calculate(getBankTabItems()); - if (prices == null) - { - return; - } - - final String strCurrentTab = createValueText(prices); - + int[] intStack = client.getIntStack(); String[] stringStack = client.getStringStack(); + int intStackSize = client.getIntStackSize(); int stringStackSize = client.getStringStackSize(); - stringStack[stringStackSize - 1] += strCurrentTab; + switch (event.getEventName()) + { + case "setBankTitle": + final ContainerPrices prices = bankCalculation.calculate(getBankTabItems()); + if (prices == null) + { + return; + } + + final String strCurrentTab = createValueText(prices); + + stringStack[stringStackSize - 1] += strCurrentTab; + break; + case "bankSearchFilter": + int itemId = intStack[intStackSize - 1]; + String search = stringStack[stringStackSize - 1]; + + if (valueSearch(itemId, search)) + { + // return true + intStack[intStackSize - 2] = 1; + } + + break; + } } @Subscribe @@ -179,12 +212,16 @@ public class BankPlugin extends Plugin @Subscribe public void onItemContainerChanged(ItemContainerChanged event) { - if (event.getContainerId() != InventoryID.SEED_VAULT.getId() || !config.seedVaultValue()) - { - return; - } + int containerId = event.getContainerId(); - updateSeedVaultTotal(); + if (containerId == InventoryID.BANK.getId()) + { + itemQuantities = null; + } + else if (containerId == InventoryID.SEED_VAULT.getId() && config.seedVaultValue()) + { + updateSeedVaultTotal(); + } } private String createValueText(final ContainerPrices prices) @@ -297,4 +334,100 @@ public class BankPlugin extends Plugin return itemContainer.getItems(); } + + + @VisibleForTesting + boolean valueSearch(final int itemId, final String str) + { + final Matcher matcher = VALUE_SEARCH_PATTERN.matcher(str); + if (!matcher.matches()) + { + return false; + } + + // Count bank items and remember it for determining item quantity + if (itemQuantities == null) + { + itemQuantities = getBankItemSet(); + } + + final ItemComposition itemComposition = itemManager.getItemComposition(itemId); + long gePrice = (long) itemManager.getItemPrice(itemId) * (long) itemQuantities.count(itemId); + long haPrice = (long) (itemComposition.getPrice() * HIGH_ALCHEMY_MULTIPLIER) * (long) itemQuantities.count(itemId); + + long value = Math.max(gePrice, haPrice); + + final String mode = matcher.group("mode"); + if (mode != null) + { + value = mode.toLowerCase().equals("ge") ? gePrice : haPrice; + } + + final String op = matcher.group("op"); + if (op != null) + { + long compare; + try + { + compare = StackFormatter.stackSizeToQuantity(matcher.group("num")); + } + catch (ParseException e) + { + return false; + } + + switch (op) + { + case ">": + return value > compare; + case "<": + return value < compare; + case "=": + return value == compare; + case ">=": + return value >= compare; + case "<=": + return value <= compare; + } + } + + final String num1 = matcher.group("num1"); + final String num2 = matcher.group("num2"); + if (num1 != null && num2 != null) + { + long compare1, compare2; + try + { + compare1 = StackFormatter.stackSizeToQuantity(num1); + compare2 = StackFormatter.stackSizeToQuantity(num2); + } + catch (ParseException e) + { + return false; + } + + return compare1 <= value && compare2 >= value; + } + + return false; + } + + private Multiset getBankItemSet() + { + ItemContainer itemContainer = client.getItemContainer(InventoryID.BANK); + if (itemContainer == null) + { + return HashMultiset.create(); + } + + Multiset set = HashMultiset.create(); + for (Item item : itemContainer.getItems()) + { + if (item.getId() != ItemID.BANK_FILLER) + { + set.add(item.getId(), item.getQuantity()); + } + } + return set; + } } diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/bank/BankPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/bank/BankPluginTest.java new file mode 100644 index 0000000000..974b9dc248 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/bank/BankPluginTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019, Ron Young + * Copyright (c) 2019, Adam + * 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.bank; + +import com.google.inject.Guice; +import com.google.inject.testing.fieldbinder.Bind; +import com.google.inject.testing.fieldbinder.BoundFieldModule; +import javax.inject.Inject; +import net.runelite.api.Client; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemComposition; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.client.game.ItemManager; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class BankPluginTest +{ + @Mock + @Bind + private Client client; + + @Mock + @Bind + private ItemManager itemManager; + + @Mock + @Bind + private BankConfig bankConfig; + + @Inject + private BankPlugin bankPlugin; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + } + + @Test + public void testValueSearch() + { + int itemId = ItemID.ABYSSAL_WHIP; + + ItemContainer itemContainer = mock(ItemContainer.class); + when(itemContainer.getItems()).thenReturn(new Item[]{new Item(itemId, 30)}); + when(client.getItemContainer(InventoryID.BANK)).thenReturn(itemContainer); + + ItemComposition comp = mock(ItemComposition.class); + when(comp.getId()) + .thenReturn(itemId); + + // 60k HA price * 30 = 1.8m + when(comp.getPrice()) + .thenReturn(100_000); + + // 400k GE Price * 30 = 12m + when(itemManager.getItemPrice(itemId)) + .thenReturn(400_000); + when(itemManager.getItemComposition(itemId)) + .thenReturn(comp); + + assertTrue(bankPlugin.valueSearch(itemId, ">500k")); + assertTrue(bankPlugin.valueSearch(itemId, "< 5.5b")); + assertTrue(bankPlugin.valueSearch(itemId, "500k - 20.6m")); + + assertTrue(bankPlugin.valueSearch(itemId, "ha=1.8m")); + assertTrue(bankPlugin.valueSearch(itemId, "ha 500k - 20.6m")); + assertTrue(bankPlugin.valueSearch(itemId, "ha > 940k")); + + assertFalse(bankPlugin.valueSearch(itemId, "<500k")); + assertFalse(bankPlugin.valueSearch(itemId, "ha >2m")); + assertFalse(bankPlugin.valueSearch(itemId, "ge > 0.02b")); + + assertFalse(bankPlugin.valueSearch(itemId, "1000k")); + } +} \ No newline at end of file