slayer: Fix name matching

The Slayer plugin highlights target monsters based on their name rather
than NPC ID, as many common monsters (skeletons, zombies, etc.) have
nearly endless variations for different models and combat levels.
Previously, this name matching was done via a simple
`String#contains()`, which led to some incorrect matches such as pirates
being highlighted while on rat tasks and Jonny the beard being
highlighted while on bear tasks.

This commit changes matching to use regex to match string boundaries or
whitespace at either end of the task string, ensuring these substring
matches can only happen when word breaks occur. The only known existing
case where this would apply is for baby dragons and brutal dragons,
which are valid alternatives for their respective chromatic dragon
tasks.
This commit is contained in:
Jordan Atwood
2022-02-24 21:02:24 -08:00
committed by Adam
parent 116036d542
commit d94abb884d
2 changed files with 47 additions and 13 deletions

View File

@@ -203,7 +203,7 @@ public class SlayerPlugin extends Plugin
private int cachedXp = -1;
private Instant infoTimer;
private boolean loginFlag;
private final List<String> targetNames = new ArrayList<>();
private final List<Pattern> targetNames = new ArrayList<>();
public final Function<NPC, HighlightedNpc> isTarget = (n) ->
{
@@ -664,7 +664,8 @@ public class SlayerPlugin extends Plugin
SlayerUnlock.GROTESQUE_GUARDIAN_DOUBLE_COUNT.isEnabled(client);
}
private boolean isTarget(NPC npc)
@VisibleForTesting
boolean isTarget(NPC npc)
{
if (targetNames.isEmpty())
{
@@ -681,16 +682,15 @@ public class SlayerPlugin extends Plugin
.replace('\u00A0', ' ')
.toLowerCase();
for (String target : targetNames)
for (Pattern target : targetNames)
{
if (name.contains(target))
{
if (ArrayUtils.contains(composition.getActions(), "Attack")
final Matcher targetMatcher = target.matcher(name);
if (targetMatcher.find()
&& (ArrayUtils.contains(composition.getActions(), "Attack")
// Pick action is for zygomite-fungi
|| ArrayUtils.contains(composition.getActions(), "Pick"))
{
return true;
}
|| ArrayUtils.contains(composition.getActions(), "Pick")))
{
return true;
}
}
return false;
@@ -703,13 +703,18 @@ public class SlayerPlugin extends Plugin
if (task != null)
{
Arrays.stream(task.getTargetNames())
.map(String::toLowerCase)
.map(SlayerPlugin::targetNamePattern)
.forEach(targetNames::add);
targetNames.add(taskName.toLowerCase().replaceAll("s$", ""));
targetNames.add(targetNamePattern(taskName.replaceAll("s$", "")));
}
}
private static Pattern targetNamePattern(final String targetName)
{
return Pattern.compile("(?:\\s|^)" + targetName + "(?:\\s|$)", Pattern.CASE_INSENSITIVE);
}
private void rebuildTargetList()
{
targets.clear();
@@ -723,7 +728,8 @@ public class SlayerPlugin extends Plugin
}
}
private void setTask(String name, int amt, int initAmt)
@VisibleForTesting
void setTask(String name, int amt, int initAmt)
{
setTask(name, amt, initAmt, null);
}

View File

@@ -60,6 +60,8 @@ import net.runelite.client.game.npcoverlay.NpcOverlayService;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.infobox.InfoBoxManager;
import static org.junit.Assert.assertEquals;
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;
@@ -926,4 +928,30 @@ public class SlayerPluginTest
assertEquals("Suqahs", slayerPlugin.getTaskName());
assertEquals(229, slayerPlugin.getAmount()); // 2 kills
}
@Test
public void npcMatching()
{
assertTrue(matches("Abyssal demon", Task.ABYSSAL_DEMONS));
assertTrue(matches("Baby blue dragon", Task.BLUE_DRAGONS));
assertTrue(matches("Duck", Task.BIRDS));
assertTrue(matches("Donny the Lad", Task.BANDITS));
assertFalse(matches("Rat", Task.PIRATES));
assertFalse(matches("Wolf", Task.WEREWOLVES));
assertFalse(matches("Scorpia's offspring", Task.SCORPIA));
assertFalse(matches("Jonny the beard", Task.BEARS));
}
private boolean matches(final String npcName, final Task task)
{
final NPC npc = mock(NPC.class);
final NPCComposition comp = mock(NPCComposition.class);
when(npc.getTransformedComposition()).thenReturn(comp);
when(comp.getName()).thenReturn(npcName);
when(comp.getActions()).thenReturn(new String[] { "Attack" });
slayerPlugin.setTask(task.getName(), 0, 0);
return slayerPlugin.isTarget(npc);
}
}