From a97b4384f6ab960055ead3dc3fd213d2d0bad041 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Wed, 24 Oct 2018 15:35:26 +0200 Subject: [PATCH] Add support for kills left to XpTrackerPlugin Fixes #1594 Closes #4015 Signed-off-by: Tomas Slusny --- .../plugins/xptracker/XpActionType.java | 3 +- .../client/plugins/xptracker/XpState.java | 79 ++++++++++++++++++- .../plugins/xptracker/XpStateSingle.java | 2 +- .../plugins/xptracker/XpTrackerPlugin.java | 51 +++++++++++- 4 files changed, 130 insertions(+), 5 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpActionType.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpActionType.java index 502a77a3d8..5c23ac39d8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpActionType.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpActionType.java @@ -31,7 +31,8 @@ import lombok.Getter; @AllArgsConstructor enum XpActionType { - EXPERIENCE("Actions"); + EXPERIENCE("Actions"), + ACTOR_HEALTH("Kills"); private final String label; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpState.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpState.java index da981077ef..616536bd1e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpState.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpState.java @@ -27,6 +27,7 @@ package net.runelite.client.plugins.xptracker; import java.util.EnumMap; import java.util.Map; import lombok.NonNull; +import net.runelite.api.NPC; import net.runelite.api.Skill; /** @@ -37,8 +38,11 @@ import net.runelite.api.Skill; */ class XpState { + private static final double DEFAULT_XP_MODIFIER = 4.0; + private static final double SHARED_XP_MODIFIER = DEFAULT_XP_MODIFIER / 3.0; private final XpStateTotal xpTotal = new XpStateTotal(); private final Map xpSkills = new EnumMap<>(Skill.class); + private NPC interactedNPC; /** * Destroys all internal state, however any XpSnapshotSingle or XpSnapshotTotal remain unaffected. @@ -120,6 +124,79 @@ class XpState } } + private double getCombatXPModifier(Skill skill) + { + if (skill == Skill.HITPOINTS) + { + return SHARED_XP_MODIFIER; + } + + return DEFAULT_XP_MODIFIER; + } + + /** + * Updates skill with average actions based on currently interacted NPC. + * @param skill experience gained skill + * @param npc currently interacted NPC + * @param npcHealth health of currently interacted NPC + */ + void updateNpcExperience(Skill skill, NPC npc, Integer npcHealth) + { + if (npc == null || npc.getCombatLevel() <= 0 || npcHealth == null) + { + return; + } + + final XpStateSingle state = getSkill(skill); + final int actionExp = (int) (npcHealth * getCombatXPModifier(skill)); + final XpAction action = state.getXpAction(XpActionType.ACTOR_HEALTH); + + if (action.isActionsHistoryInitialized()) + { + action.getActionExps()[action.getActionExpIndex()] = actionExp; + + if (interactedNPC != npc) + { + action.setActionExpIndex((action.getActionExpIndex() + 1) % action.getActionExps().length); + } + } + else + { + // So we have a decent average off the bat, lets populate all values with what we see. + for (int i = 0; i < action.getActionExps().length; i++) + { + action.getActionExps()[i] = actionExp; + } + + action.setActionsHistoryInitialized(true); + } + + interactedNPC = npc; + state.setActionType(XpActionType.ACTOR_HEALTH); + } + + /** + * Update number of actions performed for skill (e.g amount of kills in this case) if last interacted + * NPC died + * @param skill skill to update actions for + * @param npc npc that just died + * @param npcHealth max health of npc that just died + * @return UPDATED in case new kill was successfully added + */ + XpUpdateResult updateNpcKills(Skill skill, NPC npc, Integer npcHealth) + { + XpStateSingle state = getSkill(skill); + + if (state.getStartXp() == -1 || npcHealth == null || npc != interactedNPC) + { + return XpUpdateResult.NO_CHANGE; + } + + final XpAction xpAction = state.getXpAction(XpActionType.ACTOR_HEALTH); + xpAction.setActions(xpAction.getActions() + 1); + return xpAction.isActionsHistoryInitialized() ? XpUpdateResult.UPDATED : XpUpdateResult.NO_CHANGE; + } + void tick(Skill skill, long delta) { getSkill(skill).tick(delta); @@ -137,7 +214,7 @@ class XpState } @NonNull - private XpStateSingle getSkill(Skill skill) + XpStateSingle getSkill(Skill skill) { return xpSkills.computeIfAbsent(skill, (s) -> new XpStateSingle(s, -1)); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java index 0354f947ef..22dfd1b646 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpStateSingle.java @@ -54,7 +54,7 @@ class XpStateSingle private int startLevelExp = 0; private int endLevelExp = 0; - private XpAction getXpAction(final XpActionType type) + XpAction getXpAction(final XpActionType type) { actions.putIfAbsent(type, new XpAction()); return actions.get(type); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java index 796e1ecbc1..f3ca559e4a 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpTrackerPlugin.java @@ -26,18 +26,22 @@ package net.runelite.client.plugins.xptracker; import static com.google.common.base.MoreObjects.firstNonNull; +import com.google.common.collect.ImmutableList; import com.google.common.eventbus.Subscribe; import com.google.inject.Binder; import com.google.inject.Provides; import java.awt.image.BufferedImage; import java.time.temporal.ChronoUnit; import java.util.EnumSet; +import java.util.List; import java.util.Objects; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; import net.runelite.api.Client; import net.runelite.api.Experience; import net.runelite.api.GameState; +import net.runelite.api.NPC; import net.runelite.api.Player; import net.runelite.api.Skill; import net.runelite.api.VarPlayer; @@ -45,14 +49,16 @@ import net.runelite.api.WorldType; import net.runelite.api.events.ExperienceChanged; import net.runelite.api.events.GameStateChanged; import net.runelite.api.events.GameTick; +import net.runelite.api.events.NpcDespawned; import net.runelite.client.config.ConfigManager; +import net.runelite.client.game.NPCManager; import net.runelite.client.game.SkillIconManager; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.PluginDescriptor; import static net.runelite.client.plugins.xptracker.XpWorldType.NORMAL; import net.runelite.client.task.Schedule; -import net.runelite.client.ui.NavigationButton; import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; import net.runelite.client.util.ImageUtil; import net.runelite.http.api.xp.XpClient; @@ -64,6 +70,14 @@ import net.runelite.http.api.xp.XpClient; @Slf4j public class XpTrackerPlugin extends Plugin { + static final List COMBAT = ImmutableList.of( + Skill.ATTACK, + Skill.STRENGTH, + Skill.DEFENCE, + Skill.RANGED, + Skill.HITPOINTS, + Skill.MAGIC); + @Inject private ClientToolbar clientToolbar; @@ -76,6 +90,9 @@ public class XpTrackerPlugin extends Plugin @Inject private XpTrackerConfig xpTrackerConfig; + @Inject + private NPCManager npcManager; + private NavigationButton navButton; private XpPanel xpPanel; private XpWorldType lastWorldType; @@ -242,14 +259,44 @@ public class XpTrackerPlugin extends Plugin return; } - final XpUpdateResult updateResult = xpState.updateSkill(skill, currentXp, startGoalXp, endGoalXp); + final XpStateSingle state = xpState.getSkill(skill); + state.setActionType(XpActionType.EXPERIENCE); + final Actor interacting = client.getLocalPlayer().getInteracting(); + if (interacting instanceof NPC) + { + final NPC npc = (NPC) interacting; + xpState.updateNpcExperience(skill, npc, npcManager.getHealth(npc.getName(), npc.getCombatLevel())); + } + + final XpUpdateResult updateResult = xpState.updateSkill(skill, currentXp, startGoalXp, endGoalXp); final boolean updated = XpUpdateResult.UPDATED.equals(updateResult); xpPanel.updateSkillExperience(updated, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill)); xpState.recalculateTotal(); xpPanel.updateTotal(xpState.getTotalSnapshot()); } + @Subscribe + public void onNpcDespawned(NpcDespawned event) + { + final NPC npc = event.getNpc(); + + if (!npc.isDead()) + { + return; + } + + for (Skill skill : COMBAT) + { + final XpUpdateResult updateResult = xpState.updateNpcKills(skill, npc, npcManager.getHealth(npc.getName(), npc.getCombatLevel())); + final boolean updated = XpUpdateResult.UPDATED.equals(updateResult); + xpPanel.updateSkillExperience(updated, xpPauseState.isPaused(skill), skill, xpState.getSkillSnapshot(skill)); + } + + xpState.recalculateTotal(); + xpPanel.updateTotal(xpState.getTotalSnapshot()); + } + @Subscribe public void onGameTick(GameTick event) {