From 1f09833022bd51c935ea4ff8fafb114f438474f2 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Thu, 26 Mar 2020 11:06:59 -0600 Subject: [PATCH 1/6] grounditems: manually match item thresholds The regex used was explosive and wouldn't complete with large inputs that can appear when the user has a mildly corrupted config --- .../plugins/grounditems/ItemThreshold.java | 43 +++++++++++----- .../grounditems/ItemThresholdTest.java | 49 +++++++++++++++++++ .../grounditems/WildcardMatchLoaderTest.java | 10 ++++ 3 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/grounditems/ItemThresholdTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/ItemThreshold.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/ItemThreshold.java index b92522bc1f..d19473d47d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/ItemThreshold.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/ItemThreshold.java @@ -25,8 +25,6 @@ package net.runelite.client.plugins.grounditems; import com.google.common.base.Strings; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import lombok.Value; @Value @@ -38,8 +36,6 @@ class ItemThreshold MORE_THAN } - private static final Pattern QUANTITY_THRESHOLD_PATTERN = Pattern.compile("(.+)(<|>)\\s*(\\d+)"); - private final String itemName; private final int quantity; private final Inequality inequality; @@ -51,19 +47,40 @@ class ItemThreshold return null; } - Matcher matcher = QUANTITY_THRESHOLD_PATTERN.matcher(entry); + Inequality operator = Inequality.MORE_THAN; + int qty = 0; - if (matcher.find()) + for (int i = entry.length() - 1; i >= 0; i--) { - String name = matcher.group(1).trim(); - String sign = matcher.group(2); - int quantity = Integer.parseInt(matcher.group(3)); - Inequality inequality = sign.equals("<") ? Inequality.LESS_THAN : Inequality.MORE_THAN; - - return new ItemThreshold(name, quantity, inequality); + char c = entry.charAt(i); + if (c >= '0' && c <= '9' || Character.isWhitespace(c)) + { + continue; + } + switch (c) + { + case '<': + operator = Inequality.LESS_THAN; + // fallthrough + case '>': + if (i + 1 < entry.length()) + { + try + { + qty = Integer.parseInt(entry.substring(i + 1).trim()); + } + catch (NumberFormatException e) + { + qty = 0; + operator = Inequality.MORE_THAN; + } + entry = entry.substring(0, i); + } + } + break; } - return new ItemThreshold(entry, 0, Inequality.MORE_THAN); + return new ItemThreshold(entry.trim(), qty, operator); } boolean quantityHolds(int itemCount) diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/ItemThresholdTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/ItemThresholdTest.java new file mode 100644 index 0000000000..3456eed65d --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/ItemThresholdTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 Abex + * 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.grounditems; + +import joptsimple.internal.Strings; +import org.junit.Assert; +import static net.runelite.client.plugins.grounditems.ItemThreshold.Inequality.*; +import org.junit.Test; + +public class ItemThresholdTest +{ + @Test + public void test() + { + Assert.assertEquals(ItemThreshold.fromConfigEntry("Dharok's platebody 100"), new ItemThreshold("Dharok's platebody 100", 0, MORE_THAN)); + Assert.assertEquals(ItemThreshold.fromConfigEntry("Dharok's platebody 100<100"), new ItemThreshold("Dharok's platebody 100", 100, LESS_THAN)); + Assert.assertEquals(ItemThreshold.fromConfigEntry("Dharok's platebody > 100"), new ItemThreshold("Dharok's platebody", 100, MORE_THAN)); + Assert.assertEquals(ItemThreshold.fromConfigEntry("Dharok's platebody < 10 0"), new ItemThreshold("Dharok's platebody", 0, MORE_THAN)); + } + + @Test(timeout = 100) + public void testExplosive() + { + String name = "archer" + Strings.repeat('e', 50000) + "s ring"; + Assert.assertEquals(ItemThreshold.fromConfigEntry(name + " < 387"), new ItemThreshold(name, 387, LESS_THAN)); + } +} \ No newline at end of file diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/WildcardMatchLoaderTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/WildcardMatchLoaderTest.java index a7ade4c7d5..6f994946c9 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/WildcardMatchLoaderTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/grounditems/WildcardMatchLoaderTest.java @@ -25,6 +25,7 @@ package net.runelite.client.plugins.grounditems; import java.util.Arrays; +import joptsimple.internal.Strings; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -53,4 +54,13 @@ public class WildcardMatchLoaderTest assertFalse(loader.load(new NamedQuantity("Abyssal dagger", 1))); assertTrue(loader.load(new NamedQuantity("Rune Longsword", 2))); } + + @Test(timeout = 1000) + public void testExplosive() + { + String name = "archer" + Strings.repeat('e', 50000) + "s ring"; + WildcardMatchLoader loader = new WildcardMatchLoader(Arrays.asList(name + "* < 100")); + assertTrue(loader.load(new NamedQuantity(name, 50))); + assertFalse(loader.load(new NamedQuantity(name, 150))); + } } From 6b1da824a811ce833b404ba519b0bfc0f8ae5f73 Mon Sep 17 00:00:00 2001 From: Max Weber Date: Thu, 26 Mar 2020 11:25:31 -0600 Subject: [PATCH 2/6] grounditems: make priceChecks threadsafe --- .../plugins/grounditems/GroundItemsPlugin.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index 8691cae1d3..66f9417c3b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -28,6 +28,7 @@ package net.runelite.client.plugins.grounditems; import com.google.common.cache.CacheBuilder; import com.google.common.cache.LoadingCache; import com.google.common.collect.EvictingQueue; +import com.google.common.collect.ImmutableMap; import com.google.inject.Provides; import java.awt.Color; import java.awt.Rectangle; @@ -163,7 +164,7 @@ public class GroundItemsPlugin extends Plugin @Getter private final Map collectedGroundItems = new LinkedHashMap<>(); - private final Map priceChecks = new LinkedHashMap<>(); + private Map priceChecks = ImmutableMap.of(); private LoadingCache highlightedItems; private LoadingCache hiddenItems; private final Queue droppedItemQueue = EvictingQueue.create(16); // recently dropped items @@ -429,32 +430,33 @@ public class GroundItemsPlugin extends Plugin .build(new WildcardMatchLoader(hiddenItemList)); // Cache colors - priceChecks.clear(); - + ImmutableMap.Builder priceCheckBuilder = ImmutableMap.builder(); if (config.getHighlightOverValue() > 0) { - priceChecks.put(config.getHighlightOverValue(), config.highlightedColor()); + priceCheckBuilder.put(config.getHighlightOverValue(), config.highlightedColor()); } if (config.insaneValuePrice() > 0) { - priceChecks.put(config.insaneValuePrice(), config.insaneValueColor()); + priceCheckBuilder.put(config.insaneValuePrice(), config.insaneValueColor()); } if (config.highValuePrice() > 0) { - priceChecks.put(config.highValuePrice(), config.highValueColor()); + priceCheckBuilder.put(config.highValuePrice(), config.highValueColor()); } if (config.mediumValuePrice() > 0) { - priceChecks.put(config.mediumValuePrice(), config.mediumValueColor()); + priceCheckBuilder.put(config.mediumValuePrice(), config.mediumValueColor()); } if (config.lowValuePrice() > 0) { - priceChecks.put(config.lowValuePrice(), config.lowValueColor()); + priceCheckBuilder.put(config.lowValuePrice(), config.lowValueColor()); } + + priceChecks = priceCheckBuilder.build(); } @Subscribe From 08703d011b6455999de2ff2ad17646cbfe43b15b Mon Sep 17 00:00:00 2001 From: Max Weber Date: Thu, 26 Mar 2020 11:33:43 -0600 Subject: [PATCH 3/6] grounditems: reset on the executor thread let startup run slightly more parallel --- .../client/plugins/grounditems/GroundItemsPlugin.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java index 66f9417c3b..f6e758043c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/grounditems/GroundItemsPlugin.java @@ -42,6 +42,7 @@ import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import lombok.AccessLevel; @@ -162,6 +163,9 @@ public class GroundItemsPlugin extends Plugin @Inject private Notifier notifier; + @Inject + private ScheduledExecutorService executor; + @Getter private final Map collectedGroundItems = new LinkedHashMap<>(); private Map priceChecks = ImmutableMap.of(); @@ -179,9 +183,9 @@ public class GroundItemsPlugin extends Plugin protected void startUp() { overlayManager.add(overlay); - reset(); mouseManager.registerMouseListener(inputListener); keyManager.registerKeyListener(inputListener); + executor.execute(this::reset); } @Override @@ -204,7 +208,7 @@ public class GroundItemsPlugin extends Plugin { if (event.getGroup().equals("grounditems")) { - reset(); + executor.execute(this::reset); } } From 844a85392bdf77993e72e2997d8fe5e3a05ae6a8 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 26 Mar 2020 22:06:42 -0400 Subject: [PATCH 4/6] speccounter: fix some bugs and edge cases 1) fix plugin to correctly identify blocked hits as a spec being used 2) fix when hitsplats appear on the same tick as uninteracting with the target npc 3) fix when hitsplats appear in between the spec and its hitsplat on other targets 4) fix specs not registering when not having to path to monsters due to the spec varbit changing prior to the interact event firing --- .../specialcounter/SpecialCounterPlugin.java | 87 +++++-- .../SpecialCounterPluginTest.java | 232 ++++++++++++++++++ 2 files changed, 294 insertions(+), 25 deletions(-) create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/specialcounter/SpecialCounterPluginTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java index 946c4d2cde..c33384f7fb 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; import net.runelite.api.Actor; import net.runelite.api.Client; import net.runelite.api.EquipmentInventorySlot; @@ -40,6 +41,7 @@ import net.runelite.api.NPC; import net.runelite.api.VarPlayer; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.InteractingChanged; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.VarbitChanged; import net.runelite.client.callback.ClientThread; @@ -57,11 +59,13 @@ import net.runelite.client.ws.WSClient; tags = {"combat", "npcs", "overlay"}, enabledByDefault = false ) +@Slf4j public class SpecialCounterPlugin extends Plugin { - private int currentWorld = -1; - private int specialPercentage = -1; - private NPC specedNPC; + private int currentWorld; + private int specialPercentage; + private Actor lastSpecTarget; + private int lastSpecTick; private SpecialWeapon specialWeapon; private final Set interactedNpcIds = new HashSet<>(); @@ -89,6 +93,11 @@ public class SpecialCounterPlugin extends Plugin protected void startUp() { wsClient.registerMessage(SpecialCounterUpdate.class); + currentWorld = -1; + specialPercentage = -1; + lastSpecTarget = null; + lastSpecTick = -1; + interactedNpcIds.clear(); } @Override @@ -115,6 +124,20 @@ public class SpecialCounterPlugin extends Plugin } } + @Subscribe + public void onInteractingChanged(InteractingChanged interactingChanged) + { + Actor source = interactingChanged.getSource(); + Actor target = interactingChanged.getTarget(); + if (lastSpecTick != client.getTickCount() || source != client.getLocalPlayer() || target == null) + { + return; + } + + log.debug("Updating last spec target to {} (was {})", target.getName(), lastSpecTarget); + lastSpecTarget = target; + } + @Subscribe public void onVarbitChanged(VarbitChanged event) { @@ -129,11 +152,13 @@ public class SpecialCounterPlugin extends Plugin this.specialPercentage = specialPercentage; this.specialWeapon = usedSpecialWeapon(); - Actor interacting = client.getLocalPlayer().getInteracting(); - if (interacting instanceof NPC) - { - specedNPC = (NPC) interacting; - } + log.debug("Special attack used - percent: {} weapon: {}", specialPercentage, specialWeapon); + + // spec was used; since the varbit change event fires before the interact change event, + // this will be specing on the target of interact changed *if* it fires this tick, + // otherwise it is what we are currently interacting with + lastSpecTarget = client.getLocalPlayer().getInteracting(); + lastSpecTick = client.getTickCount(); } @Subscribe @@ -141,7 +166,25 @@ public class SpecialCounterPlugin extends Plugin { Actor target = hitsplatApplied.getActor(); Hitsplat hitsplat = hitsplatApplied.getHitsplat(); - if (hitsplat.getHitsplatType() != Hitsplat.HitsplatType.DAMAGE_ME || !(target instanceof NPC)) + Hitsplat.HitsplatType hitsplatType = hitsplat.getHitsplatType(); + // Ignore all hitsplats other than mine + if ((hitsplatType != Hitsplat.HitsplatType.DAMAGE_ME && hitsplatType != Hitsplat.HitsplatType.BLOCK_ME) || target == client.getLocalPlayer()) + { + return; + } + + log.debug("Hitsplat target: {} spec target: {}", target, lastSpecTarget); + + // If waiting for a spec, ignore hitsplats not on the actor we specced + if (lastSpecTarget != null && lastSpecTarget != target) + { + return; + } + + boolean wasSpec = lastSpecTarget != null; + lastSpecTarget = null; + + if (!(target instanceof NPC)) { return; } @@ -156,22 +199,17 @@ public class SpecialCounterPlugin extends Plugin addInteracting(interactingId); } - if (specedNPC == hitsplatApplied.getActor()) + if (wasSpec && specialWeapon != null && hitsplat.getAmount() > 0) { - specedNPC = null; + int hit = getHit(specialWeapon, hitsplat); - if (specialWeapon != null) + updateCounter(specialWeapon, null, hit); + + if (!party.getMembers().isEmpty()) { - int hit = getHit(specialWeapon, hitsplat); - - updateCounter(specialWeapon, null, hit); - - if (!party.getMembers().isEmpty()) - { - final SpecialCounterUpdate specialCounterUpdate = new SpecialCounterUpdate(interactingId, specialWeapon, hit); - specialCounterUpdate.setMemberId(party.getLocalMember().getMemberId()); - wsClient.send(specialCounterUpdate); - } + final SpecialCounterUpdate specialCounterUpdate = new SpecialCounterUpdate(interactingId, specialWeapon, hit); + specialCounterUpdate.setMemberId(party.getLocalMember().getMemberId()); + wsClient.send(specialCounterUpdate); } } } @@ -193,10 +231,9 @@ public class SpecialCounterPlugin extends Plugin { NPC actor = npcDespawned.getNpc(); - // if the NPC despawns before the hitsplat is shown - if (specedNPC == actor) + if (lastSpecTarget == actor) { - specedNPC = null; + lastSpecTarget = null; } if (actor.isDead() && interactedNpcIds.contains(actor.getId())) diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/specialcounter/SpecialCounterPluginTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/specialcounter/SpecialCounterPluginTest.java new file mode 100644 index 0000000000..c090e7e865 --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/specialcounter/SpecialCounterPluginTest.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2020, 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.specialcounter; + +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.Actor; +import net.runelite.api.Client; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.Hitsplat; +import net.runelite.api.InventoryID; +import net.runelite.api.Item; +import net.runelite.api.ItemContainer; +import net.runelite.api.ItemID; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.VarPlayer; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.InteractingChanged; +import net.runelite.api.events.VarbitChanged; +import net.runelite.client.game.ItemManager; +import net.runelite.client.ui.overlay.infobox.InfoBoxManager; +import net.runelite.client.ws.PartyService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SpecialCounterPluginTest +{ + @Mock + @Bind + private Client client; + + @Mock + @Bind + private InfoBoxManager infoBoxManager; + + @Mock + @Bind + private PartyService partyService; + + @Mock + @Bind + private ItemManager itemManager; + + @Inject + private SpecialCounterPlugin specialCounterPlugin; + + @Before + public void before() + { + Guice.createInjector(BoundFieldModule.of(this)).injectMembers(this); + + // Set up spec weapon + ItemContainer equipment = mock(ItemContainer.class); + Item[] items = new Item[EquipmentInventorySlot.WEAPON.getSlotIdx() + 1]; + items[EquipmentInventorySlot.WEAPON.getSlotIdx()] = new Item(ItemID.BANDOS_GODSWORD, 1); + when(equipment.getItems()).thenReturn(items); + when(client.getItemContainer(InventoryID.EQUIPMENT)).thenReturn(equipment); + + // Set up special attack energy + when(client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT)).thenReturn(100); + specialCounterPlugin.onVarbitChanged(new VarbitChanged()); + + } + + private static HitsplatApplied hitsplat(Actor target, Hitsplat.HitsplatType type) + { + Hitsplat hitsplat = new Hitsplat(type, type == Hitsplat.HitsplatType.DAMAGE_ME ? 1 : 0, 42); + HitsplatApplied hitsplatApplied = new HitsplatApplied(); + hitsplatApplied.setActor(target); + hitsplatApplied.setHitsplat(hitsplat); + return hitsplatApplied; + } + + @Test + public void testSpecDamage() + { + NPC target = mock(NPC.class); + + Player player = mock(Player.class); + when(client.getLocalPlayer()).thenReturn(player); + + // spec npc + when(client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT)).thenReturn(50); + specialCounterPlugin.onVarbitChanged(new VarbitChanged()); + lenient().when(player.getInteracting()).thenReturn(target); + specialCounterPlugin.onInteractingChanged(new InteractingChanged(player, target)); + + // hit 1 + specialCounterPlugin.onHitsplatApplied(hitsplat(target, Hitsplat.HitsplatType.DAMAGE_ME)); + + verify(infoBoxManager).addInfoBox(any(SpecialCounter.class)); + } + + @Test + public void testSpecBlock() + { + NPC target = mock(NPC.class); + + Player player = mock(Player.class); + when(client.getLocalPlayer()).thenReturn(player); + + // spec npc + when(client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT)).thenReturn(50); + specialCounterPlugin.onVarbitChanged(new VarbitChanged()); + lenient().when(player.getInteracting()).thenReturn(target); + specialCounterPlugin.onInteractingChanged(new InteractingChanged(player, target)); + + // block 0 + specialCounterPlugin.onHitsplatApplied(hitsplat(target, Hitsplat.HitsplatType.BLOCK_ME)); + + // hit 1 + specialCounterPlugin.onHitsplatApplied(hitsplat(target, Hitsplat.HitsplatType.DAMAGE_ME)); + + verify(infoBoxManager, never()).addInfoBox(any(SpecialCounter.class)); + } + + @Test + public void testUnaggro() + { + NPC target = mock(NPC.class); + + Player player = mock(Player.class); + when(client.getLocalPlayer()).thenReturn(player); + + // tick 1: attack npc + when(player.getInteracting()).thenReturn(target); + specialCounterPlugin.onInteractingChanged(new InteractingChanged(player, target)); + + // tick 2: spec fires and un-interact npc + when(client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT)).thenReturn(50); + specialCounterPlugin.onVarbitChanged(new VarbitChanged()); + lenient().when(player.getInteracting()).thenReturn(null); + specialCounterPlugin.onInteractingChanged(new InteractingChanged(player, null)); + + // tick 3: hit 1 + specialCounterPlugin.onHitsplatApplied(hitsplat(target, Hitsplat.HitsplatType.DAMAGE_ME)); + + verify(infoBoxManager).addInfoBox(any(SpecialCounter.class)); + } + + @Test + public void testSameTick() + { + NPC targetA = mock(NPC.class); + NPC targetB = mock(NPC.class); + + Player player = mock(Player.class); + when(client.getLocalPlayer()).thenReturn(player); + + // tick 1: attack npc A + when(player.getInteracting()).thenReturn(targetA); + specialCounterPlugin.onInteractingChanged(new InteractingChanged(player, targetA)); + + // tick 2: spec npc B + when(client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT)).thenReturn(50); + specialCounterPlugin.onVarbitChanged(new VarbitChanged()); + lenient().when(player.getInteracting()).thenReturn(targetB); + specialCounterPlugin.onInteractingChanged(new InteractingChanged(player, targetB)); + + // tick 3: hitsplat A, hitsplat B + specialCounterPlugin.onHitsplatApplied(hitsplat(targetA, Hitsplat.HitsplatType.DAMAGE_ME)); + verify(infoBoxManager, never()).addInfoBox(any(SpecialCounter.class)); + + specialCounterPlugin.onHitsplatApplied(hitsplat(targetB, Hitsplat.HitsplatType.DAMAGE_ME)); + verify(infoBoxManager).addInfoBox(any(SpecialCounter.class)); + } + + @Test + public void testReset() + { + NPC targetA = mock(NPC.class); + NPC targetB = mock(NPC.class); + when(targetB.getId()).thenReturn(1); // a different npc type + + Player player = mock(Player.class); + when(client.getLocalPlayer()).thenReturn(player); + + // spec npc + when(client.getVar(VarPlayer.SPECIAL_ATTACK_PERCENT)).thenReturn(50); + specialCounterPlugin.onVarbitChanged(new VarbitChanged()); + lenient().when(player.getInteracting()).thenReturn(targetA); + specialCounterPlugin.onInteractingChanged(new InteractingChanged(player, targetA)); + + // hit 1 + specialCounterPlugin.onHitsplatApplied(hitsplat(targetA, Hitsplat.HitsplatType.DAMAGE_ME)); + + verify(infoBoxManager).addInfoBox(any(SpecialCounter.class)); + + // attack npc 2 + specialCounterPlugin.onInteractingChanged(new InteractingChanged(player, targetB)); + + // hit 1 + specialCounterPlugin.onHitsplatApplied(hitsplat(targetB, Hitsplat.HitsplatType.DAMAGE_ME)); + + verify(infoBoxManager).removeInfoBox(any(SpecialCounter.class)); + } +} \ No newline at end of file From ec11040150b57803832a07f33e0604a76f2834fa Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Mar 2020 11:29:07 -0400 Subject: [PATCH 5/6] stretchedmode: copy mouse event consumed flag when translating mouse events --- .../plugins/stretchedmode/TranslateMouseListener.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/stretchedmode/TranslateMouseListener.java b/runelite-client/src/main/java/net/runelite/client/plugins/stretchedmode/TranslateMouseListener.java index c99fa20950..83a68f8e9f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/stretchedmode/TranslateMouseListener.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/stretchedmode/TranslateMouseListener.java @@ -92,7 +92,12 @@ public class TranslateMouseListener implements MouseListener int newX = (int) (e.getX() / (stretchedDimensions.width / realDimensions.getWidth())); int newY = (int) (e.getY() / (stretchedDimensions.height / realDimensions.getHeight())); - return new MouseEvent((Component) e.getSource(), e.getID(), e.getWhen(), e.getModifiersEx(), - newX, newY, e.getClickCount(), e.isPopupTrigger(), e.getButton()); + MouseEvent mouseEvent = new MouseEvent((Component) e.getSource(), e.getID(), e.getWhen(), e.getModifiersEx(), + newX, newY, e.getClickCount(), e.isPopupTrigger(), e.getButton()); + if (e.isConsumed()) + { + mouseEvent.consume(); + } + return mouseEvent; } } From 22235925be7c061d6d3633d67c956f6e003d2147 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Mar 2020 18:02:12 -0400 Subject: [PATCH 6/6] client: add config option for blocking extra mouse buttons --- .../net/runelite/client/config/RuneLiteConfig.java | 11 +++++++++++ .../java/net/runelite/client/input/MouseManager.java | 9 +++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java index 1d4e0c1b71..11a8b3deb7 100644 --- a/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/config/RuneLiteConfig.java @@ -288,4 +288,15 @@ public interface RuneLiteConfig extends Config { return 35; } + + @ConfigItem( + keyName = "blockExtraMouseButtons", + name = "Block Extra Mouse Buttons", + description = "Blocks extra mouse buttons (4 and above)", + position = 43 + ) + default boolean blockExtraMouseButtons() + { + return true; + } } \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/input/MouseManager.java b/runelite-client/src/main/java/net/runelite/client/input/MouseManager.java index d444f93fe6..b88d43d3d6 100644 --- a/runelite-client/src/main/java/net/runelite/client/input/MouseManager.java +++ b/runelite-client/src/main/java/net/runelite/client/input/MouseManager.java @@ -28,7 +28,9 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import javax.inject.Inject; import javax.inject.Singleton; +import net.runelite.client.config.RuneLiteConfig; @Singleton public class MouseManager @@ -39,6 +41,9 @@ public class MouseManager private final List mouseListeners = new CopyOnWriteArrayList<>(); private final List mouseWheelListeners = new CopyOnWriteArrayList<>(); + @Inject + private RuneLiteConfig runeLiteConfig; + public void registerMouseListener(MouseListener mouseListener) { if (!mouseListeners.contains(mouseListener)) @@ -107,10 +112,10 @@ public class MouseManager private void checkExtraMouseButtons(MouseEvent mouseEvent) { - // Prevent extra mouse buttins from being passed into the client, + // Prevent extra mouse buttons from being passed into the client, // as it treats them all as left click int button = mouseEvent.getButton(); - if (button >= MOUSE_BUTTON_4) + if (button >= MOUSE_BUTTON_4 && runeLiteConfig.blockExtraMouseButtons()) { mouseEvent.consume(); }