slayer plugin: better support multikills
This observes deaths of tagged npcs each tick and will use those if available instead of assuming one kill per xpdrop
This commit is contained in:
@@ -30,11 +30,14 @@ import com.google.inject.Provides;
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import static java.lang.Integer.max;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -44,18 +47,22 @@ import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.runelite.api.Actor;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.Hitsplat;
|
||||
import net.runelite.api.ItemID;
|
||||
import net.runelite.api.MessageNode;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.NPCComposition;
|
||||
import static net.runelite.api.Skill.SLAYER;
|
||||
import net.runelite.api.coords.WorldPoint;
|
||||
import net.runelite.api.events.ActorDeath;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.HitsplatApplied;
|
||||
import net.runelite.api.events.NpcDespawned;
|
||||
import net.runelite.api.events.NpcSpawned;
|
||||
import net.runelite.api.events.StatChanged;
|
||||
@@ -174,6 +181,10 @@ public class SlayerPlugin extends Plugin
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
private List<NPC> highlightedTargets = new ArrayList<>();
|
||||
|
||||
private final Set<NPC> taggedNpcs = new HashSet<>();
|
||||
private int taggedNpcsDiedPrevTick;
|
||||
private int taggedNpcsDiedThisTick;
|
||||
|
||||
@Getter(AccessLevel.PACKAGE)
|
||||
@Setter(AccessLevel.PACKAGE)
|
||||
private int amount;
|
||||
@@ -237,6 +248,7 @@ public class SlayerPlugin extends Plugin
|
||||
overlayManager.remove(targetMinimapOverlay);
|
||||
removeCounter();
|
||||
highlightedTargets.clear();
|
||||
taggedNpcs.clear();
|
||||
cachedXp = -1;
|
||||
|
||||
chatCommandManager.unregisterCommand(TASK_COMMAND_STRING);
|
||||
@@ -260,6 +272,7 @@ public class SlayerPlugin extends Plugin
|
||||
amount = 0;
|
||||
loginFlag = true;
|
||||
highlightedTargets.clear();
|
||||
taggedNpcs.clear();
|
||||
break;
|
||||
case LOGGED_IN:
|
||||
if (config.amount() != -1
|
||||
@@ -299,6 +312,7 @@ public class SlayerPlugin extends Plugin
|
||||
public void onNpcDespawned(NpcDespawned npcDespawned)
|
||||
{
|
||||
NPC npc = npcDespawned.getNpc();
|
||||
taggedNpcs.remove(npc);
|
||||
highlightedTargets.remove(npc);
|
||||
}
|
||||
|
||||
@@ -391,6 +405,9 @@ public class SlayerPlugin extends Plugin
|
||||
removeCounter();
|
||||
}
|
||||
}
|
||||
|
||||
taggedNpcsDiedPrevTick = taggedNpcsDiedThisTick;
|
||||
taggedNpcsDiedThisTick = 0;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -541,20 +558,49 @@ public class SlayerPlugin extends Plugin
|
||||
return;
|
||||
}
|
||||
|
||||
final Task task = Task.getTask(taskName);
|
||||
|
||||
// null tasks are technically valid, it only means they arent explicitly defined in the Task enum
|
||||
// allow them through so that if there is a task capture failure the counter will still work
|
||||
final int taskKillExp = task != null ? task.getExpectedKillExp() : 0;
|
||||
|
||||
// Only count exp gain as a kill if the task either has no expected exp for a kill, or if the exp gain is equal
|
||||
// to the expected exp gain for the task.
|
||||
if (taskKillExp == 0 || taskKillExp == slayerExp - cachedXp)
|
||||
{
|
||||
killedOne();
|
||||
}
|
||||
|
||||
final int delta = slayerExp - cachedXp;
|
||||
cachedXp = slayerExp;
|
||||
|
||||
log.debug("Slayer xp change delta: {}, killed npcs: {}", delta, taggedNpcsDiedPrevTick);
|
||||
|
||||
final Task task = Task.getTask(taskName);
|
||||
if (task != null && task.getExpectedKillExp() > 0)
|
||||
{
|
||||
// Only decrement a kill if the xp drop matches the expected drop. This is just for Tzhaar tasks.
|
||||
if (task.getExpectedKillExp() == delta)
|
||||
{
|
||||
killed(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is at least one kill, but if we observe multiple tagged NPCs dieing on the previous tick, count them
|
||||
// instead.
|
||||
killed(max(taggedNpcsDiedPrevTick, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onHitsplatApplied(HitsplatApplied hitsplatApplied)
|
||||
{
|
||||
Actor actor = hitsplatApplied.getActor();
|
||||
Hitsplat hitsplat = hitsplatApplied.getHitsplat();
|
||||
if (hitsplat.getHitsplatType() == Hitsplat.HitsplatType.DAMAGE_ME && highlightedTargets.contains(actor))
|
||||
{
|
||||
// If the actor is in highlightedTargets it must be an NPC and also a task assignment
|
||||
taggedNpcs.add((NPC) actor);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onActorDeath(ActorDeath actorDeath)
|
||||
{
|
||||
Actor actor = actorDeath.getActor();
|
||||
if (taggedNpcs.contains(actor))
|
||||
{
|
||||
log.debug("Tagged NPC {} has died", actor.getName());
|
||||
++taggedNpcsDiedThisTick;
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -576,16 +622,17 @@ public class SlayerPlugin extends Plugin
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void killedOne()
|
||||
void killed(int amt)
|
||||
{
|
||||
if (amount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
amount--;
|
||||
amount -= amt;
|
||||
if (doubleTroubleExtraKill())
|
||||
{
|
||||
assert amt == 1;
|
||||
amount--;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,19 +28,25 @@ import com.google.inject.Guice;
|
||||
import com.google.inject.testing.fieldbinder.Bind;
|
||||
import com.google.inject.testing.fieldbinder.BoundFieldModule;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.inject.Inject;
|
||||
import net.runelite.api.ChatMessageType;
|
||||
import static net.runelite.api.ChatMessageType.GAMEMESSAGE;
|
||||
import net.runelite.api.Client;
|
||||
import net.runelite.api.GameState;
|
||||
import net.runelite.api.Hitsplat;
|
||||
import net.runelite.api.MessageNode;
|
||||
import net.runelite.api.NPC;
|
||||
import net.runelite.api.NPCComposition;
|
||||
import net.runelite.api.Player;
|
||||
import net.runelite.api.Skill;
|
||||
import net.runelite.api.coords.LocalPoint;
|
||||
import net.runelite.api.events.ActorDeath;
|
||||
import net.runelite.api.events.ChatMessage;
|
||||
import net.runelite.api.events.GameStateChanged;
|
||||
import net.runelite.api.events.GameTick;
|
||||
import net.runelite.api.events.HitsplatApplied;
|
||||
import net.runelite.api.events.StatChanged;
|
||||
import net.runelite.api.widgets.Widget;
|
||||
import net.runelite.api.widgets.WidgetInfo;
|
||||
@@ -771,7 +777,7 @@ public class SlayerPluginTest
|
||||
slayerPlugin.onChatMessage(chatMessage);
|
||||
|
||||
assertEquals("Suqahs", slayerPlugin.getTaskName());
|
||||
slayerPlugin.killedOne();
|
||||
slayerPlugin.killed(1);
|
||||
assertEquals(30, slayerPlugin.getAmount());
|
||||
}
|
||||
|
||||
@@ -863,4 +869,67 @@ public class SlayerPluginTest
|
||||
|
||||
verify(infoBoxManager, never()).addInfoBox(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultikill()
|
||||
{
|
||||
final Player player = mock(Player.class);
|
||||
when(player.getLocalLocation()).thenReturn(new LocalPoint(0, 0));
|
||||
when(client.getLocalPlayer()).thenReturn(player);
|
||||
|
||||
// Setup xp cache
|
||||
StatChanged statChanged = new StatChanged(
|
||||
Skill.SLAYER,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
);
|
||||
slayerPlugin.onStatChanged(statChanged);
|
||||
|
||||
NPCComposition npcComposition = mock(NPCComposition.class);
|
||||
when(npcComposition.getActions()).thenReturn(new String[]{"Attack"});
|
||||
|
||||
NPC npc1 = mock(NPC.class);
|
||||
when(npc1.getName()).thenReturn("Suqah");
|
||||
when(npc1.getTransformedComposition()).thenReturn(npcComposition);
|
||||
|
||||
NPC npc2 = mock(NPC.class);
|
||||
when(npc2.getName()).thenReturn("Suqah");
|
||||
when(npc2.getTransformedComposition()).thenReturn(npcComposition);
|
||||
|
||||
when(client.getNpcs()).thenReturn(Arrays.asList(npc1, npc2));
|
||||
|
||||
// Set task
|
||||
Widget npcDialog = mock(Widget.class);
|
||||
when(npcDialog.getText()).thenReturn(TASK_NEW);
|
||||
when(client.getWidget(WidgetInfo.DIALOG_NPC_TEXT)).thenReturn(npcDialog);
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
// Damage both npcs
|
||||
Hitsplat hitsplat = new Hitsplat(Hitsplat.HitsplatType.DAMAGE_ME, 1, 1);
|
||||
HitsplatApplied hitsplatApplied = new HitsplatApplied();
|
||||
hitsplatApplied.setHitsplat(hitsplat);
|
||||
hitsplatApplied.setActor(npc1);
|
||||
slayerPlugin.onHitsplatApplied(hitsplatApplied);
|
||||
|
||||
hitsplatApplied.setActor(npc2);
|
||||
slayerPlugin.onHitsplatApplied(hitsplatApplied);
|
||||
|
||||
// Kill both npcs
|
||||
slayerPlugin.onActorDeath(new ActorDeath(npc1));
|
||||
slayerPlugin.onActorDeath(new ActorDeath(npc2));
|
||||
|
||||
slayerPlugin.onGameTick(new GameTick());
|
||||
|
||||
statChanged = new StatChanged(
|
||||
Skill.SLAYER,
|
||||
105,
|
||||
2,
|
||||
2
|
||||
);
|
||||
slayerPlugin.onStatChanged(statChanged);
|
||||
|
||||
assertEquals("Suqahs", slayerPlugin.getTaskName());
|
||||
assertEquals(229, slayerPlugin.getAmount()); // 2 kills
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user