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