diff --git a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java index 88218e2e19..6036aef522 100644 --- a/runelite-api/src/main/java/net/runelite/api/VarPlayer.java +++ b/runelite-api/src/main/java/net/runelite/api/VarPlayer.java @@ -38,7 +38,61 @@ public enum VarPlayer SPECIAL_ATTACK_PERCENT(300), SPECIAL_ATTACK_ENABLED(301), - IN_RAID_PARTY(1427); + IN_RAID_PARTY(1427), + + /** + * Experience tracker goal start + */ + ATTACK_GOAL_START(1229), + STRENGTH_GOAL_START(1230), + RANGED_GOAL_START(1231), + MAGIC_GOAL_START(1232), + DEFENCE_GOAL_START(1233), + HITPOINTS_GOAL_START(1234), + PRAYER_GOAL_START(1235), + AGILITY_GOAL_START(1236), + HERBLORE_GOAL_START(1237), + THIEVING_GOAL_START(1238), + CRAFTING_GOAL_START(1239), + RUNECRAFT_GOAL_START(1240), + MINING_GOAL_START(1241), + SMITHING_GOAL_START(1242), + FISHING_GOAL_START(1243), + COOKING_GOAL_START(1244), + FIREMAKING_GOAL_START(1245), + WOODCUTTING_GOAL_START(1246), + FLETCHING_GOAL_START(1247), + SLAYER_GOAL_START(1248), + FARMING_GOAL_START(1249), + CONSTRUCTION_GOAL_START(1250), + HUNTER_GOAL_START(1251), + + /** + * Experience tracker goal end + */ + ATTACK_GOAL_END(1253), + STRENGTH_GOAL_END(1254), + RANGED_GOAL_END(1255), + MAGIC_GOAL_END(1256), + DEFENCE_GOAL_END(1257), + HITPOINTS_GOAL_END(1258), + PRAYER_GOAL_END(1259), + AGILITY_GOAL_END(1260), + HERBLORE_GOAL_END(1261), + THIEVING_GOAL_END(1262), + CRAFTING_GOAL_END(1263), + RUNECRAFT_GOAL_END(1264), + MINING_GOAL_END(1265), + SMITHING_GOAL_END(1266), + FISHING_GOAL_END(1267), + COOKING_GOAL_END(1268), + FIREMAKING_GOAL_END(1269), + WOODCUTTING_GOAL_END(1270), + FLETCHING_GOAL_END(1271), + SLAYER_GOAL_END(1272), + FARMING_GOAL_END(1273), + CONSTRUCTION_GOAL_END(1274), + HUNTER_GOAL_END(1275); private final int id; } 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 98c956eab1..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,33 +27,38 @@ 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; } - int getActionsHr() + private int getActionsHr() { return toHourly(actions); } @@ -82,29 +87,29 @@ class XpStateSingle return Math.max(60, Duration.between(skillTimeStart, Instant.now()).getSeconds()); } - int getXpRemaining() + private int getXpRemaining() { - return nextLevelExp - (startXp + xpGained); + return endLevelExp - getCurrentXp(); } - int getActionsRemaining() + private int getActionsRemaining() { 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)); } } @@ -112,16 +117,14 @@ class XpStateSingle return Integer.MAX_VALUE; } - int getSkillProgress() + 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); } - String getTimeTillLevel() + private String getTimeTillLevel() { long seconds = getTimeElapsedInSeconds(); @@ -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; + } + } }