diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java
index b2d536e02d..329e75eedb 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpInfoBox.java
@@ -186,15 +186,15 @@ class XpInfoBox extends JPanel
// Update progress bar
progressBar.setValue(xpSnapshotSingle.getSkillProgressToGoal());
progressBar.setCenterLabel(xpSnapshotSingle.getSkillProgressToGoal() + "%");
- progressBar.setLeftLabel("Lvl. " + xpSnapshotSingle.getCurrentLevel());
- progressBar.setRightLabel("Lvl. " + (xpSnapshotSingle.getCurrentLevel() + 1));
+ progressBar.setLeftLabel("Lvl. " + xpSnapshotSingle.getStartLevel());
+ progressBar.setRightLabel("Lvl. " + (xpSnapshotSingle.getEndLevel()));
progressBar.setToolTipText(""
+ xpSnapshotSingle.getActionsInSession() + " actions done"
+ "
"
+ xpSnapshotSingle.getActionsPerHour() + " actions/hr"
+ "
"
- + xpSnapshotSingle.getTimeTillGoal() + " till next lvl"
+ + xpSnapshotSingle.getTimeTillGoal() + " till goal lvl"
+ "");
progressBar.repaint();
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpSnapshotSingle.java b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpSnapshotSingle.java
index 350a501f47..0745078a98 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpSnapshotSingle.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/xptracker/XpSnapshotSingle.java
@@ -31,7 +31,8 @@ import lombok.Value;
@Value
class XpSnapshotSingle
{
- private int currentLevel;
+ private int startLevel;
+ private int endLevel;
private int xpGainedInSession;
private int xpRemainingToGoal;
private int xpPerHour;
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 1b0adfb17c..c70f460a3d 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
@@ -82,9 +82,11 @@ class XpState
* and also first-login when the skills are not initialized (the start XP will be -1 in this case).
* @param skill Skill to update
* @param currentXp Current known XP for this skill
+ * @param goalStartXp Possible XP start goal
+ * @param goalEndXp Possible XP end goal
* @return Whether or not the skill has been initialized, there was no change, or it has been updated
*/
- XpUpdateResult updateSkill(Skill skill, int currentXp)
+ XpUpdateResult updateSkill(Skill skill, int currentXp, int goalStartXp, int goalEndXp)
{
XpStateSingle state = getSkill(skill);
@@ -113,7 +115,7 @@ class XpState
}
else
{
- return state.update(currentXp) ? XpUpdateResult.UPDATED : XpUpdateResult.NO_CHANGE;
+ return state.update(currentXp, goalStartXp, goalEndXp) ? XpUpdateResult.UPDATED : XpUpdateResult.NO_CHANGE;
}
}
}
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 b8ddf5f61b..a04318f3ad 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
@@ -27,30 +27,35 @@ package net.runelite.client.plugins.xptracker;
import java.time.Duration;
import java.time.Instant;
-import lombok.Data;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Experience;
import net.runelite.api.Skill;
-@Data
@Slf4j
+@RequiredArgsConstructor
class XpStateSingle
{
private final Skill skill;
+
+ @Getter
private final int startXp;
- private Instant skillTimeStart = null;
+
+ @Getter
private int xpGained = 0;
+
+ private Instant skillTimeStart = null;
private int actions = 0;
- private int nextLevelExp = 0;
private int startLevelExp = 0;
- private int level = 0;
+ private int endLevelExp = 0;
private boolean actionsHistoryInitialized = false;
private int[] actionExps = new int[10];
private int actionExpIndex = 0;
- int getXpHr()
+ private int getCurrentXp()
{
- return toHourly(xpGained);
+ return startXp + xpGained;
}
private int getActionsHr()
@@ -84,7 +89,7 @@ class XpStateSingle
private int getXpRemaining()
{
- return nextLevelExp - (startXp + xpGained);
+ return endLevelExp - getCurrentXp();
}
private int getActionsRemaining()
@@ -92,19 +97,19 @@ class XpStateSingle
if (actionsHistoryInitialized)
{
long xpRemaining = getXpRemaining() * actionExps.length;
- long actionExp = 0;
+ long totalActionXp = 0;
- for (int i = 0; i < actionExps.length; i++)
+ for (int actionXp : actionExps)
{
- actionExp += actionExps[i];
+ totalActionXp += actionXp;
}
// Let's not divide by zero (or negative)
- if (actionExp > 0)
+ if (totalActionXp > 0)
{
// Make sure to account for the very last action at the end
- long remainder = xpRemaining % actionExp;
- long quotient = xpRemaining / actionExp;
+ long remainder = xpRemaining % totalActionXp;
+ long quotient = xpRemaining / totalActionXp;
return Math.toIntExact(quotient + (remainder > 0 ? 1 : 0));
}
}
@@ -114,10 +119,8 @@ class XpStateSingle
private int getSkillProgress()
{
- int currentXp = startXp + xpGained;
-
- double xpGained = currentXp - startLevelExp;
- double xpGoal = nextLevelExp - startLevelExp;
+ double xpGained = getCurrentXp() - startLevelExp;
+ double xpGoal = endLevelExp - startLevelExp;
return (int) ((xpGained / xpGoal) * 100);
}
@@ -163,7 +166,12 @@ class XpStateSingle
}
- boolean update(int currentXp)
+ int getXpHr()
+ {
+ return toHourly(xpGained);
+ }
+
+ boolean update(int currentXp, int goalStartXp, int goalEndXp)
{
if (startXp == -1)
{
@@ -174,6 +182,7 @@ class XpStateSingle
int originalXp = xpGained + startXp;
int actionExp = currentXp - originalXp;
+ // No experience gained
if (actionExp == 0)
{
return false;
@@ -195,17 +204,32 @@ class XpStateSingle
}
actionExpIndex = (actionExpIndex + 1) % actionExps.length;
-
actions++;
+
+ // Calculate experience gained
xpGained = currentXp - startXp;
- startLevelExp = Experience.getXpForLevel(Experience.getLevelForXp(currentXp));
- int currentLevel = Experience.getLevelForXp(currentXp);
+ // Determine XP goals
+ if (goalStartXp <= 0)
+ {
+ startLevelExp = Experience.getXpForLevel(Experience.getLevelForXp(currentXp));
+ }
+ else
+ {
+ startLevelExp = goalStartXp;
+ }
- level = currentLevel;
-
- nextLevelExp = currentLevel + 1 <= Experience.MAX_VIRT_LEVEL ? Experience.getXpForLevel(currentLevel + 1) : -1;
+ if (goalEndXp <= 0 || currentXp > goalEndXp)
+ {
+ int currentLevel = Experience.getLevelForXp(currentXp);
+ endLevelExp = currentLevel + 1 <= Experience.MAX_VIRT_LEVEL ? Experience.getXpForLevel(currentLevel + 1) : -1;
+ }
+ else
+ {
+ endLevelExp = goalEndXp;
+ }
+ // If this is first time we are updating, we just started tracking
if (skillTimeStart == null)
{
skillTimeStart = Instant.now();
@@ -217,12 +241,13 @@ class XpStateSingle
XpSnapshotSingle snapshot()
{
return XpSnapshotSingle.builder()
- .currentLevel(getLevel())
- .xpGainedInSession(getXpGained())
+ .startLevel(Experience.getLevelForXp(startLevelExp))
+ .endLevel(Experience.getLevelForXp(endLevelExp))
+ .xpGainedInSession(xpGained)
.xpRemainingToGoal(getXpRemaining())
.xpPerHour(getXpHr())
.skillProgressToGoal(getSkillProgress())
- .actionsInSession(getActions())
+ .actionsInSession(actions)
.actionsRemainingToGoal(getActionsRemaining())
.actionsPerHour(getActionsHr())
.timeTillGoal(getTimeTillLevel())
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 9443f36935..981bf63393 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
@@ -40,6 +40,7 @@ import net.runelite.api.Client;
import net.runelite.api.GameState;
import net.runelite.api.Player;
import net.runelite.api.Skill;
+import net.runelite.api.VarPlayer;
import net.runelite.api.events.ExperienceChanged;
import net.runelite.api.events.GameStateChanged;
import net.runelite.api.events.GameTick;
@@ -259,12 +260,15 @@ public class XpTrackerPlugin extends Plugin
public void onXpChanged(ExperienceChanged event)
{
final Skill skill = event.getSkill();
- int currentXp = client.getSkillExperience(skill);
+ final int currentXp = client.getSkillExperience(skill);
+ final VarPlayer startGoal = startGoalVarpForSkill(skill);
+ final VarPlayer endGoal = endGoalVarpForSkill(skill);
+ final int startGoalXp = startGoal != null ? client.getVar(startGoal) : -1;
+ final int endGoalXp = endGoal != null ? client.getVar(endGoal) : -1;
- XpUpdateResult updateResult = xpState.updateSkill(skill, currentXp);
-
- boolean updated = XpUpdateResult.UPDATED.equals(updateResult);
+ final XpUpdateResult updateResult = xpState.updateSkill(skill, currentXp, startGoalXp, endGoalXp);
+ final boolean updated = XpUpdateResult.UPDATED.equals(updateResult);
xpPanel.updateSkillExperience(updated, skill, xpState.getSkillSnapshot(skill));
xpState.recalculateTotal();
xpPanel.updateTotal(xpState.getTotalSnapshot());
@@ -287,4 +291,114 @@ public class XpTrackerPlugin extends Plugin
{
return xpState.getSkillSnapshot(skill);
}
+
+ private static VarPlayer startGoalVarpForSkill(final Skill skill)
+ {
+ switch (skill)
+ {
+ case ATTACK:
+ return VarPlayer.ATTACK_GOAL_START;
+ case MINING:
+ return VarPlayer.MINING_GOAL_START;
+ case WOODCUTTING:
+ return VarPlayer.WOODCUTTING_GOAL_START;
+ case DEFENCE:
+ return VarPlayer.DEFENCE_GOAL_START;
+ case MAGIC:
+ return VarPlayer.MAGIC_GOAL_START;
+ case RANGED:
+ return VarPlayer.RANGED_GOAL_START;
+ case HITPOINTS:
+ return VarPlayer.HITPOINTS_GOAL_START;
+ case AGILITY:
+ return VarPlayer.AGILITY_GOAL_START;
+ case STRENGTH:
+ return VarPlayer.STRENGTH_GOAL_START;
+ case PRAYER:
+ return VarPlayer.PRAYER_GOAL_START;
+ case SLAYER:
+ return VarPlayer.SLAYER_GOAL_START;
+ case FISHING:
+ return VarPlayer.FISHING_GOAL_START;
+ case RUNECRAFT:
+ return VarPlayer.RUNECRAFT_GOAL_START;
+ case HERBLORE:
+ return VarPlayer.HERBLORE_GOAL_START;
+ case FIREMAKING:
+ return VarPlayer.FIREMAKING_GOAL_START;
+ case CONSTRUCTION:
+ return VarPlayer.CONSTRUCTION_GOAL_START;
+ case HUNTER:
+ return VarPlayer.HUNTER_GOAL_START;
+ case COOKING:
+ return VarPlayer.COOKING_GOAL_START;
+ case FARMING:
+ return VarPlayer.FARMING_GOAL_START;
+ case CRAFTING:
+ return VarPlayer.CRAFTING_GOAL_START;
+ case SMITHING:
+ return VarPlayer.SMITHING_GOAL_START;
+ case THIEVING:
+ return VarPlayer.THIEVING_GOAL_START;
+ case FLETCHING:
+ return VarPlayer.FLETCHING_GOAL_START;
+ default:
+ return null;
+ }
+ }
+
+ private static VarPlayer endGoalVarpForSkill(final Skill skill)
+ {
+ switch (skill)
+ {
+ case ATTACK:
+ return VarPlayer.ATTACK_GOAL_END;
+ case MINING:
+ return VarPlayer.MINING_GOAL_END;
+ case WOODCUTTING:
+ return VarPlayer.WOODCUTTING_GOAL_END;
+ case DEFENCE:
+ return VarPlayer.DEFENCE_GOAL_END;
+ case MAGIC:
+ return VarPlayer.MAGIC_GOAL_END;
+ case RANGED:
+ return VarPlayer.RANGED_GOAL_END;
+ case HITPOINTS:
+ return VarPlayer.HITPOINTS_GOAL_END;
+ case AGILITY:
+ return VarPlayer.AGILITY_GOAL_END;
+ case STRENGTH:
+ return VarPlayer.STRENGTH_GOAL_END;
+ case PRAYER:
+ return VarPlayer.PRAYER_GOAL_END;
+ case SLAYER:
+ return VarPlayer.SLAYER_GOAL_END;
+ case FISHING:
+ return VarPlayer.FISHING_GOAL_END;
+ case RUNECRAFT:
+ return VarPlayer.RUNECRAFT_GOAL_END;
+ case HERBLORE:
+ return VarPlayer.HERBLORE_GOAL_END;
+ case FIREMAKING:
+ return VarPlayer.FIREMAKING_GOAL_END;
+ case CONSTRUCTION:
+ return VarPlayer.CONSTRUCTION_GOAL_END;
+ case HUNTER:
+ return VarPlayer.HUNTER_GOAL_END;
+ case COOKING:
+ return VarPlayer.COOKING_GOAL_END;
+ case FARMING:
+ return VarPlayer.FARMING_GOAL_END;
+ case CRAFTING:
+ return VarPlayer.CRAFTING_GOAL_END;
+ case SMITHING:
+ return VarPlayer.SMITHING_GOAL_END;
+ case THIEVING:
+ return VarPlayer.THIEVING_GOAL_END;
+ case FLETCHING:
+ return VarPlayer.FLETCHING_GOAL_END;
+ default:
+ return null;
+ }
+ }
}