From a2d404bbae2cfd1b841db62eb76905245f543539 Mon Sep 17 00:00:00 2001 From: Darkforge317 Date: Sun, 21 Jun 2026 18:36:48 -0600 Subject: [PATCH 1/6] Refactor SkillLevelTask.level to targetSkillLevel + fix task synchronization on login/creation Refactored `level` to `targetSkillLevel` across all task definitions and quest requirement mappings for code clarity, preventing confusion now that a distinct `currentSkillLevel` tracking field has been introduced. Implemented `currentSkillLevel` progression cache inside `SkillLevelTask` and `TaskUpdateService` to ensure active player stats are stored locally before cross-thread evaluations occur. Added a non-blocking frame loop to `onGameStateChanged` to resolve uninitialized player profile data on login, preventing accidental level-0 task completions caused by server packet lag. Wrapped the UI thread's `onTaskAdded` handler in a `clientThread` loop to bypass service thread guards safely, ensuring freshly created goals check active player stats immediately instead of remaining stuck as unverified. --- .../goaltracker/GoalTrackerPlugin.java | 61 +- .../models/task/SkillLevelTask.java | 55 +- .../presets/GoalPresetRepository.java | 40 +- .../services/TaskUpdateService.java | 3 +- .../ui/inputs/SkillLevelTaskInput.java | 2 +- .../goaltracker/utils/QuestRequirements.java | 612 +++++++++--------- .../goaltracker/GoalSerializerTest.java | 4 +- .../models/task/SkillLevelTaskTest.java | 2 +- .../services/TaskUpdateServiceTest.java | 12 +- .../utils/QuestRequirementsTest.java | 2 +- 10 files changed, 441 insertions(+), 352 deletions(-) diff --git a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java index 7c6ad35..3e25e61 100644 --- a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java +++ b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java @@ -229,17 +229,28 @@ protected void startUp() } goalTrackerPanel.onGoalUpdated((goal) -> goalManager.save()); + goalTrackerPanel.onTaskAdded((task) -> { - if (taskUpdateService.update(task)) { - if (task.getStatus().isCompleted()) { - notifyTask(task); - } + // Instantly save the new task so the UI stays responsive + goalManager.save(); - uiStatusManager.refresh(task); - } + // Send to the client thread to respect taskUpdateService's thread-safety guards. + // Otherwise, it would prevent an update that was called from this UI thread + clientThread.invokeLater(() -> { + // If the task status updated + if (taskUpdateService.update(task)) { + if (task.getStatus().isCompleted()) { + notifyTask(task); + } - goalManager.save(); + uiStatusManager.refresh(task); + + // Re-save only if verification math changed the completion state + goalManager.save(); + } + }); }); + goalTrackerPanel.onTaskUpdated((task) -> goalManager.save()); // Preload item icons at plugin startup so they are visible immediately @@ -282,11 +293,23 @@ public void onGameStateChanged(GameStateChanged event) { if (event.getGameState() == GameState.LOGGED_IN) { - // Re-check quest tasks after login - clientThread.invokeLater(() -> refreshQuestTasks()); + // Defer task refreshes until the player's data is loaded in, preventing a race condition. + // Would otherwise cause a check of level-0/0xp against tasks, invalidating our login check entirely. + clientThread.invokeLater(() -> { - // Refresh the panel once, 10s after login, after detection settles - schedulePanelRefresh(10_000); + // If player data hasn't loaded from the server yet, wait and try again next frame + if (client.getRealSkillLevel(Skill.ATTACK) <= 0) { return false; } + + // Refresh tasks now that player data exists + refreshQuestTasks(); + refreshSkillLevelTasks(); + + // Give the UI a moment to settle after data updates before repainting the panel + schedulePanelRefresh(200); + + // Stop the refresh frame loop + return true; + }); } } @@ -488,6 +511,22 @@ private void refreshQuestTasks() } } } + + private void refreshSkillLevelTasks() + { + if (goalManager == null || client == null) return; + List skillLevelTasks = goalManager.getIncompleteTasksByType(TaskType.SKILL_LEVEL); + for (SkillLevelTask task : skillLevelTasks) + { + task.refreshStatus(client); + uiStatusManager.refresh(task); + if (task.getStatus().isCompleted()) + { + notifyTask(task); + } + } + } + @Provides public GoalTrackerConfig provideConfig(ConfigManager configManager) { diff --git a/src/main/java/com/toofifty/goaltracker/models/task/SkillLevelTask.java b/src/main/java/com/toofifty/goaltracker/models/task/SkillLevelTask.java index 46ed71c..5c1c66f 100644 --- a/src/main/java/com/toofifty/goaltracker/models/task/SkillLevelTask.java +++ b/src/main/java/com/toofifty/goaltracker/models/task/SkillLevelTask.java @@ -1,10 +1,12 @@ package com.toofifty.goaltracker.models.task; import com.toofifty.goaltracker.models.enums.TaskType; +import com.toofifty.goaltracker.models.enums.Status; import lombok.Getter; import lombok.Setter; import lombok.experimental.SuperBuilder; import net.runelite.api.Skill; +import net.runelite.api.Client; @Setter @Getter @@ -16,18 +18,19 @@ public final class SkillLevelTask extends Task { private Skill skill; - private int level; + private int targetSkillLevel; + private int currentSkillLevel; @Override public String getDisplayName() { - return String.format("Reach level %d %s", level, skill.getName()); + return String.format("Reach level %d %s", targetSkillLevel, skill.getName()); } @Override public String toString() { - return String.format("%s %s", level, skill.getName()); + return String.format("%s %s", targetSkillLevel, skill.getName()); } @Override @@ -35,4 +38,50 @@ public TaskType getType() { return TaskType.SKILL_LEVEL; } + + /** + * Re-evaluate the player's level in the skill and update the Task.status field. + * Safe to call on login and whenever relevant varbits/varps change. + */ + public void refreshStatus(final Client client) + { + if (client == null || skill == null) + { + return; + } + + + currentSkillLevel = client.getRealSkillLevel(skill); + + + if(hasReachedTargetLevel()) + { + setStatus(Status.COMPLETED); + } + } + + /** + * Whether the player has reached the target level for this task. + * @return + */ + public boolean hasReachedTargetLevel() + { + return (currentSkillLevel >= targetSkillLevel); + } + + /** + * Whether the player would reach the target level for this task given the level provided. + * @param level The level to check against + * @return + */ + public boolean wouldReachTargetLevel(int level) + { + return (level >= targetSkillLevel); + } } diff --git a/src/main/java/com/toofifty/goaltracker/presets/GoalPresetRepository.java b/src/main/java/com/toofifty/goaltracker/presets/GoalPresetRepository.java index bdeeb4c..be93079 100644 --- a/src/main/java/com/toofifty/goaltracker/presets/GoalPresetRepository.java +++ b/src/main/java/com/toofifty/goaltracker/presets/GoalPresetRepository.java @@ -50,9 +50,9 @@ private static Preset buildEarlyIronman() { .description("Early Ironman Progression") .tasks(ReorderableList.from( // Core Early Skills (unlock movement & teleports) - SkillLevelTask.builder().skill(Skill.AGILITY).level(30).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(37).build(), // Falador/Camelot teles - SkillLevelTask.builder().skill(Skill.THIEVING).level(20).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(37).build(), // Falador/Camelot teles + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(20).build(), // Early set-up gear ItemTask.builder().itemId(GRACEFUL_HOOD).itemName("Graceful hood").quantity(1).build(), ItemTask.builder().itemId(GRACEFUL_TOP).itemName("Graceful top").quantity(1).build(), @@ -69,12 +69,12 @@ private static Preset buildEarlyIronman() { // Birdhouse runs (Fossil Island access) QuestTask.builder().quest(Quest.THE_DIG_SITE).build(), QuestTask.builder().quest(Quest.BONE_VOYAGE).build(), - SkillLevelTask.builder().skill(Skill.HUNTER).level(5).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(8).build(), // clockwork - SkillLevelTask.builder().skill(Skill.CONSTRUCTION).level(16).build(), // clockmaker's bench + SkillLevelTask.builder().skill(Skill.HUNTER).targetSkillLevel(5).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(8).build(), // clockwork + SkillLevelTask.builder().skill(Skill.CONSTRUCTION).targetSkillLevel(16).build(), // clockmaker's bench // Seaweed runs (Giant seaweed patches on Fossil Island) - SkillLevelTask.builder().skill(Skill.FARMING).level(23).build() + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(23).build() )) .build(); return new Preset("Early Ironman Progression", "Stats, gear, and quest goals for early game Ironman.", Arrays.asList(early)); @@ -85,13 +85,13 @@ private static Preset buildMidIronman() { .description("Mid Ironman Progression") .tasks(ReorderableList.from( // Midgame Skill Targets - SkillLevelTask.builder().skill(Skill.ATTACK).level(60).build(), - SkillLevelTask.builder().skill(Skill.STRENGTH).level(60).build(), - SkillLevelTask.builder().skill(Skill.DEFENCE).level(60).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(60).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(55).build(), // High Alch - SkillLevelTask.builder().skill(Skill.PRAYER).level(43).build(), // Protect prayers - SkillLevelTask.builder().skill(Skill.AGILITY).level(60).build(), + SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.STRENGTH).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.DEFENCE).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(55).build(), // High Alch + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(43).build(), // Protect prayers + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(60).build(), // Midgame Gear & Upgrades ItemTask.builder().itemId(DRAGON_SCIMITAR).itemName("Dragon scimitar").quantity(1).build(), ItemTask.builder().itemId(DRAGON_DEFENDER).itemName("Dragon defender").quantity(1).build(), @@ -116,12 +116,12 @@ private static Preset buildLateIronman() { .description("Late Ironman Progression") .tasks(ReorderableList.from( // Late Skill Targets - SkillLevelTask.builder().skill(Skill.ATTACK).level(85).build(), - SkillLevelTask.builder().skill(Skill.STRENGTH).level(85).build(), - SkillLevelTask.builder().skill(Skill.DEFENCE).level(85).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(94).build(), // Vengeance/Ice Barrage - SkillLevelTask.builder().skill(Skill.PRAYER).level(77).build(), // Rigour/Augury - SkillLevelTask.builder().skill(Skill.RANGED).level(85).build(), + SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(85).build(), + SkillLevelTask.builder().skill(Skill.STRENGTH).targetSkillLevel(85).build(), + SkillLevelTask.builder().skill(Skill.DEFENCE).targetSkillLevel(85).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(94).build(), // Vengeance/Ice Barrage + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(77).build(), // Rigour/Augury + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(85).build(), // Late Gear Goals ItemTask.builder().itemId(ABYSSAL_TENTACLE).itemName("Abyssal tentacle").quantity(1).build(), ItemTask.builder().itemId(TOXIC_BLOWPIPE).itemName("Toxic blowpipe").quantity(1).build(), diff --git a/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java b/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java index 6e30d14..5ca80fe 100644 --- a/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java +++ b/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java @@ -60,7 +60,8 @@ public boolean update(SkillLevelTask task, StatChanged event) public boolean update(SkillLevelTask task, int level) { final Status oldStatus = task.getStatus(); - task.setStatus(level >= task.getLevel() ? Status.COMPLETED : Status.NOT_STARTED); + task.setCurrentSkillLevel(level); + task.setStatus(level >= task.getTargetSkillLevel() ? Status.COMPLETED : Status.NOT_STARTED); return oldStatus != task.getStatus(); } diff --git a/src/main/java/com/toofifty/goaltracker/ui/inputs/SkillLevelTaskInput.java b/src/main/java/com/toofifty/goaltracker/ui/inputs/SkillLevelTaskInput.java index b8e9e54..302741c 100644 --- a/src/main/java/com/toofifty/goaltracker/ui/inputs/SkillLevelTaskInput.java +++ b/src/main/java/com/toofifty/goaltracker/ui/inputs/SkillLevelTaskInput.java @@ -63,7 +63,7 @@ protected void submit() addTask(SkillLevelTask.builder() .skill((Skill) skillField.getSelectedItem()) - .level(Integer.parseInt(levelField.getText())) + .targetSkillLevel(Integer.parseInt(levelField.getText())) .build()); } diff --git a/src/main/java/com/toofifty/goaltracker/utils/QuestRequirements.java b/src/main/java/com/toofifty/goaltracker/utils/QuestRequirements.java index 0f4eed8..c5bde0e 100644 --- a/src/main/java/com/toofifty/goaltracker/utils/QuestRequirements.java +++ b/src/main/java/com/toofifty/goaltracker/utils/QuestRequirements.java @@ -25,7 +25,7 @@ public final class QuestRequirements Arrays.asList( QuestTask.builder().quest(Quest.LOST_CITY).build(), QuestTask.builder().quest(Quest.NATURE_SPIRIT).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(18).build() + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(18).build() ) ); REQUIREMENT_MAP.put( @@ -33,13 +33,13 @@ public final class QuestRequirements Arrays.asList( QuestTask.builder().quest(Quest.PRIEST_IN_PERIL).build(), QuestTask.builder().quest(Quest.THE_RESTLESS_GHOST).build(), - SkillLevelTask.builder().skill(Skill.PRAYER).level(18).build() + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(18).build() ) ); REQUIREMENT_MAP.put( Quest.DRAGON_SLAYER_I, Arrays.asList( - SkillLevelTask.builder().skill(Skill.ATTACK).level(32).build(), + SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(32).build(), QuestTask.builder().quest(Quest.PRIEST_IN_PERIL).build() ) ); @@ -48,7 +48,7 @@ public final class QuestRequirements Quest.JUNGLE_POTION, Arrays.asList( QuestTask.builder().quest(Quest.DRUIDIC_RITUAL).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(3).build() + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(3).build() ) ); @@ -62,30 +62,30 @@ public final class QuestRequirements REQUIREMENT_MAP.put( Quest.EAGLES_PEAK, Arrays.asList( - SkillLevelTask.builder().skill(Skill.HUNTER).level(27).build() + SkillLevelTask.builder().skill(Skill.HUNTER).targetSkillLevel(27).build() ) ); REQUIREMENT_MAP.put( Quest.ELEMENTAL_WORKSHOP_I, Arrays.asList( - SkillLevelTask.builder().skill(Skill.MINING).level(20).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(20).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(20).build() + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(20).build() ) ); REQUIREMENT_MAP.put( Quest.FISHING_CONTEST, Arrays.asList( - SkillLevelTask.builder().skill(Skill.FISHING).level(10).build() + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(10).build() ) ); REQUIREMENT_MAP.put( Quest.OBSERVATORY_QUEST, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(10).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(10).build() ) ); @@ -99,7 +99,7 @@ public final class QuestRequirements REQUIREMENT_MAP.put( Quest.TOWER_OF_LIFE, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CONSTRUCTION).level(10).build() + SkillLevelTask.builder().skill(Skill.CONSTRUCTION).targetSkillLevel(10).build() ) ); @@ -109,10 +109,10 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.ERNEST_THE_CHICKEN).build(), QuestTask.builder().quest(Quest.PRIEST_IN_PERIL).build(), QuestTask.builder().quest(Quest.THE_RESTLESS_GHOST).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(18).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(30).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(19).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(35).build() + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(18).build(), + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(19).build(), + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(35).build() ) ); @@ -126,28 +126,28 @@ public final class QuestRequirements REQUIREMENT_MAP.put( Quest.BIG_CHOMPY_BIRD_HUNTING, Arrays.asList( - SkillLevelTask.builder().skill(Skill.COOKING).level(30).build(), - SkillLevelTask.builder().skill(Skill.FLETCHING).level(5).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(30).build() + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.FLETCHING).targetSkillLevel(5).build(), + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(30).build() ) ); REQUIREMENT_MAP.put( Quest.COLD_WAR, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(30).build(), - SkillLevelTask.builder().skill(Skill.CONSTRUCTION).level(34).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(30).build(), - SkillLevelTask.builder().skill(Skill.HUNTER).level(10).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(15).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.CONSTRUCTION).targetSkillLevel(34).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.HUNTER).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(15).build() ) ); REQUIREMENT_MAP.put( Quest.CREATURE_OF_FENKENSTRAIN, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(20).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(25).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(25).build() ) ); @@ -155,13 +155,13 @@ public final class QuestRequirements Quest.DARKNESS_OF_HALLOWVALE, Arrays.asList( QuestTask.builder().quest(Quest.IN_AID_OF_THE_MYREQUE).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(26).build(), - SkillLevelTask.builder().skill(Skill.CONSTRUCTION).level(5).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(32).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(33).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(20).build(), - SkillLevelTask.builder().skill(Skill.STRENGTH).level(40).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(22).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(26).build(), + SkillLevelTask.builder().skill(Skill.CONSTRUCTION).targetSkillLevel(5).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(32).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(33).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.STRENGTH).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(22).build() ) ); @@ -169,18 +169,18 @@ public final class QuestRequirements Quest.DEATH_TO_THE_DORGESHUUN, Arrays.asList( QuestTask.builder().quest(Quest.THE_LOST_TRIBE).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(23).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(17).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(23).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(23).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(17).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(23).build() ) ); REQUIREMENT_MAP.put( Quest.THE_DIG_SITE, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(10).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(10).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(25).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(25).build() ) ); @@ -188,19 +188,19 @@ public final class QuestRequirements Quest.ELEMENTAL_WORKSHOP_II, Arrays.asList( QuestTask.builder().quest(Quest.ELEMENTAL_WORKSHOP_I).build(), - SkillLevelTask.builder().skill(Skill.DEFENCE).level(20).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(20).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(20).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(30).build() + SkillLevelTask.builder().skill(Skill.DEFENCE).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(30).build() ) ); REQUIREMENT_MAP.put( Quest.ENLIGHTENED_JOURNEY, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(36).build(), - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(20).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(30).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(36).build(), + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(30).build() ) ); @@ -208,9 +208,9 @@ public final class QuestRequirements Quest.THE_EYES_OF_GLOUPHRIE, Arrays.asList( QuestTask.builder().quest(Quest.THE_GRAND_TREE).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(46).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(5).build(), - SkillLevelTask.builder().skill(Skill.CONSTRUCTION).level(5).build() + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(46).build(), + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(5).build(), + SkillLevelTask.builder().skill(Skill.CONSTRUCTION).targetSkillLevel(5).build() ) ); @@ -221,18 +221,18 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.THE_EYES_OF_GLOUPHRIE).build(), QuestTask.builder().quest(Quest.TREE_GNOME_VILLAGE).build(), QuestTask.builder().quest(Quest.THE_GRAND_TREE).build(), - SkillLevelTask.builder().skill(Skill.STRENGTH).level(60).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(56).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(56).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(47).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(45).build() + SkillLevelTask.builder().skill(Skill.STRENGTH).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(56).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(56).build(), + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(47).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(45).build() ) ); REQUIREMENT_MAP.put( Quest.THE_FEUD, Arrays.asList( - SkillLevelTask.builder().skill(Skill.THIEVING).level(30).build() + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(30).build() ) ); @@ -240,13 +240,13 @@ public final class QuestRequirements Quest.FORGETTABLE_TALE, Arrays.asList( QuestTask.builder().quest(Quest.THE_GIANT_DWARF).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(22).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(12).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(17).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(16).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(10).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(33).build(), - SkillLevelTask.builder().skill(Skill.STRENGTH).level(14).build() + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(22).build(), + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(12).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(17).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(16).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(33).build(), + SkillLevelTask.builder().skill(Skill.STRENGTH).targetSkillLevel(14).build() ) ); @@ -254,9 +254,9 @@ public final class QuestRequirements Quest.GARDEN_OF_TRANQUILLITY, Arrays.asList( QuestTask.builder().quest(Quest.CREATURE_OF_FENKENSTRAIN).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(25).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(20).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(25).build() + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(25).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(25).build() ) ); @@ -264,34 +264,34 @@ public final class QuestRequirements Quest.GHOSTS_AHOY, Arrays.asList( QuestTask.builder().quest(Quest.PRIEST_IN_PERIL).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(25).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(20).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(25).build(), + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(20).build() ) ); REQUIREMENT_MAP.put( Quest.THE_GIANT_DWARF, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(12).build(), - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(16).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(33).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(14).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(12).build(), + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(16).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(33).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(14).build() ) ); REQUIREMENT_MAP.put( Quest.THE_GOLEM, Arrays.asList( - SkillLevelTask.builder().skill(Skill.THIEVING).level(20).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(25).build() + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(25).build() ) ); REQUIREMENT_MAP.put( Quest.THE_HAND_IN_THE_SAND, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(49).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(17).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(49).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(17).build() ) ); @@ -299,7 +299,7 @@ public final class QuestRequirements Quest.HOLY_GRAIL, Arrays.asList( QuestTask.builder().quest(Quest.MERLINS_CRYSTAL).build(), - SkillLevelTask.builder().skill(Skill.ATTACK).level(20).build() + SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(20).build() ) ); @@ -314,10 +314,10 @@ public final class QuestRequirements Quest.IN_AID_OF_THE_MYREQUE, Arrays.asList( QuestTask.builder().quest(Quest.IN_SEARCH_OF_THE_MYREQUE).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(25).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(25).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(7).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(15).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(25).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(25).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(7).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(15).build() ) ); @@ -325,14 +325,14 @@ public final class QuestRequirements Quest.IN_SEARCH_OF_THE_MYREQUE, Arrays.asList( QuestTask.builder().quest(Quest.NATURE_SPIRIT).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(25).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(25).build() ) ); REQUIREMENT_MAP.put( Quest.THE_KNIGHTS_SWORD, Arrays.asList( - SkillLevelTask.builder().skill(Skill.MINING).level(10).build() + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(10).build() ) ); @@ -340,9 +340,9 @@ public final class QuestRequirements Quest.THE_LOST_TRIBE, Arrays.asList( QuestTask.builder().quest(Quest.GOBLIN_DIPLOMACY).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(13).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(17).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(13).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(13).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(17).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(13).build() ) ); @@ -356,7 +356,7 @@ public final class QuestRequirements REQUIREMENT_MAP.put( Quest.MOUNTAIN_DAUGHTER, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(20).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(20).build() ) ); @@ -364,19 +364,19 @@ public final class QuestRequirements Quest.MY_ARMS_BIG_ADVENTURE, Arrays.asList( QuestTask.builder().quest(Quest.JUNGLE_POTION).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(15).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(29).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(31).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(30).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(10).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(15).build(), + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(29).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(31).build(), + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(10).build() ) ); REQUIREMENT_MAP.put( Quest.OLAFS_QUEST, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(40).build(), - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(50).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(50).build() ) ); @@ -384,10 +384,10 @@ public final class QuestRequirements Quest.RATCATCHERS, Arrays.asList( QuestTask.builder().quest(Quest.ICTHLARINS_LITTLE_HELPER).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(12).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(16).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(33).build(), - SkillLevelTask.builder().skill(Skill.HUNTER).level(14).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(12).build(), + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(16).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(33).build(), + SkillLevelTask.builder().skill(Skill.HUNTER).targetSkillLevel(14).build() ) ); @@ -395,23 +395,23 @@ public final class QuestRequirements Quest.SCORPION_CATCHER, Arrays.asList( QuestTask.builder().quest(Quest.ALFRED_GRIMHANDS_BARCRAWL).build(), - SkillLevelTask.builder().skill(Skill.PRAYER).level(31).build() + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(31).build() ) ); REQUIREMENT_MAP.put( Quest.SEA_SLUG, Arrays.asList( - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(30).build() + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(30).build() ) ); REQUIREMENT_MAP.put( Quest.SHADES_OF_MORTTON, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(20).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(5).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(15).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(5).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(15).build() ) ); @@ -420,8 +420,8 @@ public final class QuestRequirements Arrays.asList( QuestTask.builder().quest(Quest.DEMON_SLAYER).build(), QuestTask.builder().quest(Quest.THE_GOLEM).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(30).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(25).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(25).build() ) ); @@ -429,30 +429,30 @@ public final class QuestRequirements Quest.THE_SLUG_MENACE, Arrays.asList( QuestTask.builder().quest(Quest.WANTED).build(), - SkillLevelTask.builder().skill(Skill.RUNECRAFT).level(13).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(30).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(30).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(17).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(30).build() + SkillLevelTask.builder().skill(Skill.RUNECRAFT).targetSkillLevel(13).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(17).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(30).build() ) ); REQUIREMENT_MAP.put( Quest.SPIRITS_OF_THE_ELID, Arrays.asList( - SkillLevelTask.builder().skill(Skill.MAGIC).level(33).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(37).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(37).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(37).build() + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(33).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(37).build(), + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(37).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(37).build() ) ); REQUIREMENT_MAP.put( Quest.TAI_BWO_WANNAI_TRIO, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(15).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(30).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(5).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(15).build(), + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(5).build() ) ); @@ -466,24 +466,24 @@ public final class QuestRequirements REQUIREMENT_MAP.put( Quest.TEARS_OF_GUTHIX, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(20).build(), - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(49).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(20).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(20).build(), + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(49).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(20).build() ) ); REQUIREMENT_MAP.put( Quest.THE_TOURIST_TRAP, Arrays.asList( - SkillLevelTask.builder().skill(Skill.FLETCHING).level(10).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(20).build() + SkillLevelTask.builder().skill(Skill.FLETCHING).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(20).build() ) ); REQUIREMENT_MAP.put( Quest.TRIBAL_TOTEM, Arrays.asList( - SkillLevelTask.builder().skill(Skill.THIEVING).level(21).build() + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(21).build() ) ); @@ -491,20 +491,20 @@ public final class QuestRequirements Quest.WANTED, Arrays.asList( QuestTask.builder().quest(Quest.RECRUITMENT_DRIVE).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(13).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(17).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(13).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(13).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(17).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(13).build() ) ); REQUIREMENT_MAP.put( Quest.WATCHTOWER, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(25).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(14).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(15).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(40).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(15).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(25).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(14).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(15).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(15).build() ) ); @@ -512,7 +512,7 @@ public final class QuestRequirements Quest.WHAT_LIES_BELOW, Arrays.asList( QuestTask.builder().quest(Quest.RUNE_MYSTERIES).build(), - SkillLevelTask.builder().skill(Skill.RUNECRAFT).level(35).build() + SkillLevelTask.builder().skill(Skill.RUNECRAFT).targetSkillLevel(35).build() ) ); @@ -521,10 +521,10 @@ public final class QuestRequirements Arrays.asList( QuestTask.builder().quest(Quest.JUNGLE_POTION).build(), QuestTask.builder().quest(Quest.BIG_CHOMPY_BIRD_HUNTING).build(), - SkillLevelTask.builder().skill(Skill.FLETCHING).level(30).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(8).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(4).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(30).build() + SkillLevelTask.builder().skill(Skill.FLETCHING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(8).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(4).build(), + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(30).build() ) ); @@ -532,10 +532,10 @@ public final class QuestRequirements Quest.BETWEEN_A_ROCK, Arrays.asList( QuestTask.builder().quest(Quest.DWARF_CANNON).build(), - SkillLevelTask.builder().skill(Skill.DEFENCE).level(30).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(10).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(40).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(50).build() + SkillLevelTask.builder().skill(Skill.DEFENCE).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(50).build() ) ); @@ -543,11 +543,11 @@ public final class QuestRequirements Quest.CABIN_FEVER, Arrays.asList( QuestTask.builder().quest(Quest.RUM_DEAL).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(42).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(30).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(45).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(40).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(50).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(42).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(45).build(), + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(50).build() ) ); @@ -555,10 +555,10 @@ public final class QuestRequirements Quest.DEVIOUS_MINDS, Arrays.asList( QuestTask.builder().quest(Quest.WANTED).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(50).build(), - SkillLevelTask.builder().skill(Skill.RUNECRAFT).level(50).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(65).build(), - SkillLevelTask.builder().skill(Skill.FLETCHING).level(50).build() + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.RUNECRAFT).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(65).build(), + SkillLevelTask.builder().skill(Skill.FLETCHING).targetSkillLevel(50).build() ) ); @@ -566,17 +566,17 @@ public final class QuestRequirements Quest.EADGARS_RUSE, Arrays.asList( QuestTask.builder().quest(Quest.DRUIDIC_RITUAL).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(31).build() + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(31).build() ) ); REQUIREMENT_MAP.put( Quest.ENAKHRAS_LAMENT, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(50).build(), - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(45).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(39).build(), - SkillLevelTask.builder().skill(Skill.PRAYER).level(43).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(45).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(39).build(), + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(43).build() ) ); @@ -584,20 +584,20 @@ public final class QuestRequirements Quest.FAIRYTALE_II__CURE_A_QUEEN, Arrays.asList( QuestTask.builder().quest(Quest.FAIRYTALE_I__GROWING_PAINS).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(40).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(49).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(57).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(37).build() + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(49).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(57).build(), + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(37).build() ) ); REQUIREMENT_MAP.put( Quest.FAMILY_CREST, Arrays.asList( - SkillLevelTask.builder().skill(Skill.MINING).level(40).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(59).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(40).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(40).build() + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(59).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(40).build() ) ); @@ -605,15 +605,15 @@ public final class QuestRequirements Quest.THE_FREMENNIK_ISLES, Arrays.asList( QuestTask.builder().quest(Quest.THE_FREMENNIK_TRIALS).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(40).build(), - SkillLevelTask.builder().skill(Skill.CONSTRUCTION).level(20).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.CONSTRUCTION).targetSkillLevel(20).build() ) ); REQUIREMENT_MAP.put( Quest.THE_GRAND_TREE, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(25).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(25).build() ) ); @@ -621,18 +621,18 @@ public final class QuestRequirements Quest.THE_GREAT_BRAIN_ROBBERY, Arrays.asList( QuestTask.builder().quest(Quest.CABIN_FEVER).build(), - SkillLevelTask.builder().skill(Skill.PRAYER).level(50).build(), - SkillLevelTask.builder().skill(Skill.CONSTRUCTION).level(30).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(31).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(45).build() + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.CONSTRUCTION).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(31).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(45).build() ) ); REQUIREMENT_MAP.put( Quest.HAUNTED_MINE, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(15).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(35).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(15).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(35).build() ) ); @@ -643,18 +643,18 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.DRAGON_SLAYER_I).build(), QuestTask.builder().quest(Quest.MERLINS_CRYSTAL).build(), QuestTask.builder().quest(Quest.LOST_CITY).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(53).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(25).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(53).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(25).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(50).build() + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(53).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(25).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(53).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(25).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(50).build() ) ); REQUIREMENT_MAP.put( Quest.HORROR_FROM_THE_DEEP, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(35).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(35).build() ) ); @@ -664,19 +664,19 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.BLACK_KNIGHTS_FORTRESS).build(), QuestTask.builder().quest(Quest.HOLY_GRAIL).build(), QuestTask.builder().quest(Quest.MURDER_MYSTERY).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(65).build(), - SkillLevelTask.builder().skill(Skill.DEFENCE).level(45).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(30).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(18).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(45).build() + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(65).build(), + SkillLevelTask.builder().skill(Skill.DEFENCE).targetSkillLevel(45).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(18).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(45).build() ) ); REQUIREMENT_MAP.put( Quest.LOST_CITY, Arrays.asList( - SkillLevelTask.builder().skill(Skill.CRAFTING).level(31).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(36).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(31).build(), + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(36).build() ) ); @@ -684,23 +684,23 @@ public final class QuestRequirements Quest.LUNAR_DIPLOMACY, Arrays.asList( QuestTask.builder().quest(Quest.THE_FREMENNIK_TRIALS).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(55).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(60).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(5).build(), - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(49).build(), - SkillLevelTask.builder().skill(Skill.DEFENCE).level(40).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(61).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(32).build() + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(55).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(5).build(), + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(49).build(), + SkillLevelTask.builder().skill(Skill.DEFENCE).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(61).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(32).build() ) ); REQUIREMENT_MAP.put( Quest.ONE_SMALL_FAVOUR, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(36).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(25).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(18).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(30).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(36).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(25).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(18).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(30).build() ) ); @@ -708,9 +708,9 @@ public final class QuestRequirements Quest.ROYAL_TROUBLE, Arrays.asList( QuestTask.builder().quest(Quest.THRONE_OF_MISCELLANIA).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(40).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(40).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(53).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(53).build() ) ); @@ -718,10 +718,10 @@ public final class QuestRequirements Quest.RUM_DEAL, Arrays.asList( QuestTask.builder().quest(Quest.ZOGRE_FLESH_EATERS).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(30).build(), - SkillLevelTask.builder().skill(Skill.PRAYER).level(47).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(50).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(40).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(47).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(40).build() ) ); @@ -729,16 +729,16 @@ public final class QuestRequirements Quest.SHILO_VILLAGE, Arrays.asList( QuestTask.builder().quest(Quest.JUNGLE_POTION).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(32).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(20).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(32).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(20).build() ) ); REQUIREMENT_MAP.put( Quest.TEMPLE_OF_IKOV, Arrays.asList( - SkillLevelTask.builder().skill(Skill.RANGED).level(40).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(42).build() + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(42).build() ) ); @@ -746,8 +746,8 @@ public final class QuestRequirements Quest.THRONE_OF_MISCELLANIA, Arrays.asList( QuestTask.builder().quest(Quest.HEROES_QUEST).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(45).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(40).build() + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(45).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(40).build() ) ); @@ -755,7 +755,7 @@ public final class QuestRequirements Quest.TROLL_ROMANCE, Arrays.asList( QuestTask.builder().quest(Quest.TROLL_STRONGHOLD).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(28).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(28).build() ) ); @@ -763,7 +763,7 @@ public final class QuestRequirements Quest.TROLL_STRONGHOLD, Arrays.asList( QuestTask.builder().quest(Quest.DEATH_PLATEAU).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(15).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(15).build() ) ); @@ -771,7 +771,7 @@ public final class QuestRequirements Quest.UNDERGROUND_PASS, Arrays.asList( QuestTask.builder().quest(Quest.BIOHAZARD).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(25).build() + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(25).build() ) ); @@ -789,11 +789,11 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.TEMPLE_OF_IKOV).build(), QuestTask.builder().quest(Quest.THE_DIG_SITE).build(), QuestTask.builder().quest(Quest.TROLL_STRONGHOLD).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(53).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(50).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(10).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(10).build(), - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(50).build() + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(53).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(50).build() ) ); @@ -801,24 +801,24 @@ public final class QuestRequirements Quest.DREAM_MENTOR, Arrays.asList( QuestTask.builder().quest(Quest.LUNAR_DIPLOMACY).build(), - SkillLevelTask.builder().skill(Skill.DEFENCE).level(85).build(), - SkillLevelTask.builder().skill(Skill.HITPOINTS).level(65).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(55).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(60).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(45).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(40).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(49).build() + SkillLevelTask.builder().skill(Skill.DEFENCE).targetSkillLevel(85).build(), + SkillLevelTask.builder().skill(Skill.HITPOINTS).targetSkillLevel(65).build(), + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(55).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(45).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(40).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(49).build() ) ); REQUIREMENT_MAP.put( Quest.GRIM_TALES, Arrays.asList( - SkillLevelTask.builder().skill(Skill.AGILITY).level(59).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(52).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(45).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(58).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(71).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(59).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(52).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(45).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(58).build(), + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(71).build() ) ); @@ -829,15 +829,15 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.SHILO_VILLAGE).build(), QuestTask.builder().quest(Quest.HEROES_QUEST).build(), QuestTask.builder().quest(Quest.UNDERGROUND_PASS).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(50).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(50).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(50).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(50).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(52).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(50).build(), - SkillLevelTask.builder().skill(Skill.STRENGTH).level(50).build(), - SkillLevelTask.builder().skill(Skill.PRAYER).level(42).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(50).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(52).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.STRENGTH).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(42).build(), + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(50).build() ) ); @@ -853,8 +853,8 @@ public final class QuestRequirements Quest.MOURNINGS_END_PART_I, Arrays.asList( QuestTask.builder().quest(Quest.ROVING_ELVES).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(60).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(50).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(50).build() ) ); @@ -862,7 +862,7 @@ public final class QuestRequirements Quest.MOURNINGS_END_PART_II, Arrays.asList( QuestTask.builder().quest(Quest.MOURNINGS_END_PART_I).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(60).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(60).build() ) ); @@ -877,9 +877,9 @@ public final class QuestRequirements Quest.REGICIDE, Arrays.asList( QuestTask.builder().quest(Quest.UNDERGROUND_PASS).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(56).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(10).build(), - SkillLevelTask.builder().skill(Skill.FLETCHING).level(25).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(56).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.FLETCHING).targetSkillLevel(25).build() ) ); @@ -887,9 +887,9 @@ public final class QuestRequirements Quest.ROVING_ELVES, Arrays.asList( QuestTask.builder().quest(Quest.REGICIDE).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(56).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(10).build(), - SkillLevelTask.builder().skill(Skill.RANGED).level(25).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(56).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(10).build(), + SkillLevelTask.builder().skill(Skill.RANGED).targetSkillLevel(25).build() ) ); @@ -897,12 +897,12 @@ public final class QuestRequirements Quest.SWAN_SONG, Arrays.asList( QuestTask.builder().quest(Quest.ONE_SMALL_FAVOUR).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(62).build(), - SkillLevelTask.builder().skill(Skill.COOKING).level(66).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(42).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(45).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(62).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(18).build() + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(62).build(), + SkillLevelTask.builder().skill(Skill.COOKING).targetSkillLevel(66).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(42).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(45).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(62).build(), + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(18).build() ) ); @@ -913,13 +913,13 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.A_TAIL_OF_TWO_CATS).build(), QuestTask.builder().quest(Quest.DREAM_MENTOR).build(), QuestTask.builder().quest(Quest.BONE_VOYAGE).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(60).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(50).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(60).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(68).build(), - SkillLevelTask.builder().skill(Skill.HUNTER).level(60).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(60).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(75).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(68).build(), + SkillLevelTask.builder().skill(Skill.HUNTER).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(75).build() ) ); @@ -929,11 +929,11 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.ENLIGHTENED_JOURNEY).build(), QuestTask.builder().quest(Quest.THE_EYES_OF_GLOUPHRIE).build(), QuestTask.builder().quest(Quest.THE_GRAND_TREE).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(69).build(), - SkillLevelTask.builder().skill(Skill.HUNTER).level(60).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(55).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(70).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(70).build() + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(69).build(), + SkillLevelTask.builder().skill(Skill.HUNTER).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(55).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(70).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(70).build() ) ); @@ -942,14 +942,14 @@ public final class QuestRequirements Arrays.asList( QuestTask.builder().quest(Quest.MOURNINGS_END_PART_II).build(), QuestTask.builder().quest(Quest.MAKING_HISTORY).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(70).build(), - SkillLevelTask.builder().skill(Skill.CONSTRUCTION).level(70).build(), - SkillLevelTask.builder().skill(Skill.FARMING).level(70).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(70).build(), - SkillLevelTask.builder().skill(Skill.HUNTER).level(70).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(70).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(70).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(70).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(70).build(), + SkillLevelTask.builder().skill(Skill.CONSTRUCTION).targetSkillLevel(70).build(), + SkillLevelTask.builder().skill(Skill.FARMING).targetSkillLevel(70).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(70).build(), + SkillLevelTask.builder().skill(Skill.HUNTER).targetSkillLevel(70).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(70).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(70).build(), + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(70).build() ) ); @@ -957,13 +957,13 @@ public final class QuestRequirements Quest.SINS_OF_THE_FATHER, Arrays.asList( QuestTask.builder().quest(Quest.A_TASTE_OF_HOPE).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(62).build(), - SkillLevelTask.builder().skill(Skill.FLETCHING).level(60).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(56).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(52).build(), - SkillLevelTask.builder().skill(Skill.ATTACK).level(50).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(50).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(49).build() + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(62).build(), + SkillLevelTask.builder().skill(Skill.FLETCHING).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(56).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(52).build(), + SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(49).build() ) ); @@ -984,13 +984,13 @@ public final class QuestRequirements QuestTask.builder().quest(Quest.THE_ASCENT_OF_ARCEUUS).build(), QuestTask.builder().quest(Quest.THE_FORSAKEN_TOWER).build(), QuestTask.builder().quest(Quest.TALE_OF_THE_RIGHTEOUS).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(54).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(52).build(), - SkillLevelTask.builder().skill(Skill.WOODCUTTING).level(52).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(50).build(), - SkillLevelTask.builder().skill(Skill.MINING).level(42).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(38).build(), - SkillLevelTask.builder().skill(Skill.MAGIC).level(35).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(54).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(52).build(), + SkillLevelTask.builder().skill(Skill.WOODCUTTING).targetSkillLevel(52).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(50).build(), + SkillLevelTask.builder().skill(Skill.MINING).targetSkillLevel(42).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(38).build(), + SkillLevelTask.builder().skill(Skill.MAGIC).targetSkillLevel(35).build() ) ); @@ -1005,11 +1005,11 @@ public final class QuestRequirements Quest.THE_FREMENNIK_EXILES, Arrays.asList( QuestTask.builder().quest(Quest.THE_FREMENNIK_ISLES).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(65).build(), - SkillLevelTask.builder().skill(Skill.SMITHING).level(60).build(), - SkillLevelTask.builder().skill(Skill.SLAYER).level(60).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(60).build(), - SkillLevelTask.builder().skill(Skill.RUNECRAFT).level(55).build() + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(65).build(), + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.SLAYER).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(60).build(), + SkillLevelTask.builder().skill(Skill.RUNECRAFT).targetSkillLevel(55).build() ) ); @@ -1023,7 +1023,7 @@ public final class QuestRequirements REQUIREMENT_MAP.put( Quest.SLEEPING_GIANTS, Arrays.asList( - SkillLevelTask.builder().skill(Skill.SMITHING).level(15).build() + SkillLevelTask.builder().skill(Skill.SMITHING).targetSkillLevel(15).build() ) ); @@ -1031,7 +1031,7 @@ public final class QuestRequirements Quest.TEMPLE_OF_THE_EYE, Arrays.asList( QuestTask.builder().quest(Quest.RUNE_MYSTERIES).build(), - SkillLevelTask.builder().skill(Skill.RUNECRAFT).level(10).build() + SkillLevelTask.builder().skill(Skill.RUNECRAFT).targetSkillLevel(10).build() ) ); @@ -1039,11 +1039,11 @@ public final class QuestRequirements Quest.LAND_OF_THE_GOBLINS, Arrays.asList( QuestTask.builder().quest(Quest.ANOTHER_SLICE_OF_HAM).build(), - SkillLevelTask.builder().skill(Skill.PRAYER).level(30).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(36).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(36).build(), - SkillLevelTask.builder().skill(Skill.HERBLORE).level(37).build(), - SkillLevelTask.builder().skill(Skill.FISHING).level(36).build() + SkillLevelTask.builder().skill(Skill.PRAYER).targetSkillLevel(30).build(), + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(36).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(36).build(), + SkillLevelTask.builder().skill(Skill.HERBLORE).targetSkillLevel(37).build(), + SkillLevelTask.builder().skill(Skill.FISHING).targetSkillLevel(36).build() ) ); @@ -1051,9 +1051,9 @@ public final class QuestRequirements Quest.BENEATH_CURSED_SANDS, Arrays.asList( QuestTask.builder().quest(Quest.CONTACT).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(62).build(), - SkillLevelTask.builder().skill(Skill.CRAFTING).level(55).build(), - SkillLevelTask.builder().skill(Skill.FIREMAKING).level(55).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(62).build(), + SkillLevelTask.builder().skill(Skill.CRAFTING).targetSkillLevel(55).build(), + SkillLevelTask.builder().skill(Skill.FIREMAKING).targetSkillLevel(55).build() ) ); @@ -1062,9 +1062,9 @@ public final class QuestRequirements Arrays.asList( QuestTask.builder().quest(Quest.MAKING_HISTORY).build(), QuestTask.builder().quest(Quest.HAZEEL_CULT).build(), - SkillLevelTask.builder().skill(Skill.AGILITY).level(69).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(64).build(), - SkillLevelTask.builder().skill(Skill.HUNTER).level(56).build() + SkillLevelTask.builder().skill(Skill.AGILITY).targetSkillLevel(69).build(), + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(64).build(), + SkillLevelTask.builder().skill(Skill.HUNTER).targetSkillLevel(56).build() ) ); @@ -1160,9 +1160,9 @@ public final class QuestRequirements Arrays.asList( QuestTask.builder().quest(Quest.THE_HEART_OF_DARKNESS).build(), QuestTask.builder().quest(Quest.PERILOUS_MOONS).build(), - SkillLevelTask.builder().skill(Skill.THIEVING).level(66).build(), - SkillLevelTask.builder().skill(Skill.FLETCHING).level(52).build(), - SkillLevelTask.builder().skill(Skill.RUNECRAFT).level(52).build() + SkillLevelTask.builder().skill(Skill.THIEVING).targetSkillLevel(66).build(), + SkillLevelTask.builder().skill(Skill.FLETCHING).targetSkillLevel(52).build(), + SkillLevelTask.builder().skill(Skill.RUNECRAFT).targetSkillLevel(52).build() ) ); @@ -1233,7 +1233,7 @@ private static Task copyWithIndent(Task task, int indentLevel) SkillLevelTask st = (SkillLevelTask) task; return SkillLevelTask.builder() .skill(st.getSkill()) - .level(st.getLevel()) + .targetSkillLevel(st.getTargetSkillLevel()) .indentLevel(indentLevel) .build(); } diff --git a/src/test/java/com/toofifty/goaltracker/GoalSerializerTest.java b/src/test/java/com/toofifty/goaltracker/GoalSerializerTest.java index 403b64d..2a4b548 100644 --- a/src/test/java/com/toofifty/goaltracker/GoalSerializerTest.java +++ b/src/test/java/com/toofifty/goaltracker/GoalSerializerTest.java @@ -79,7 +79,7 @@ public void deserialize_should_support_other_task_types() throws IOException { SkillLevelTask skillLevelTask = (SkillLevelTask) goals.get(0).getTasks().get(1); - assertEquals(99, skillLevelTask.getLevel()); + assertEquals(99, skillLevelTask.getTargetSkillLevel()); assertEquals(Skill.ATTACK, skillLevelTask.getSkill()); assertEquals(1, skillLevelTask.getIndentLevel()); @@ -150,7 +150,7 @@ public void serialize_should_support_other_task_types() throws IOException { .status(Status.IN_PROGRESS) .notified(false) .indentLevel(1) - .level(99) + .targetSkillLevel(99) .skill(Skill.ATTACK) .build(), SkillXpTask.builder() diff --git a/src/test/java/com/toofifty/goaltracker/models/task/SkillLevelTaskTest.java b/src/test/java/com/toofifty/goaltracker/models/task/SkillLevelTaskTest.java index d3d1675..4d4a9f9 100644 --- a/src/test/java/com/toofifty/goaltracker/models/task/SkillLevelTaskTest.java +++ b/src/test/java/com/toofifty/goaltracker/models/task/SkillLevelTaskTest.java @@ -10,7 +10,7 @@ class SkillLevelTaskTest { @Test void toString_shouldReturnTheLevelAndSkillName() { - SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).level(99).build(); + SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(99).build(); assertEquals("99 Attack", task.toString()); } diff --git a/src/test/java/com/toofifty/goaltracker/services/TaskUpdateServiceTest.java b/src/test/java/com/toofifty/goaltracker/services/TaskUpdateServiceTest.java index f8b1825..ae193ea 100644 --- a/src/test/java/com/toofifty/goaltracker/services/TaskUpdateServiceTest.java +++ b/src/test/java/com/toofifty/goaltracker/services/TaskUpdateServiceTest.java @@ -48,7 +48,7 @@ void update_shouldDynamicallyMapTasks() { when(quest.getState(client)).thenReturn(QuestState.FINISHED); when(itemCache.getTotalQuantity(314)).thenReturn(100); - assertTrue(service.update((Task) SkillLevelTask.builder().skill(Skill.ATTACK).level(90).build())); + assertTrue(service.update((Task) SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(90).build())); assertTrue(service.update((Task) SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build())); assertTrue(service.update((Task) QuestTask.builder().quest(quest).build())); assertTrue(service.update((Task) ItemTask.builder().itemId(314).acquired(0).quantity(100).build())); @@ -65,7 +65,7 @@ void update_skillLevelTask_shouldSupportLookingUpThePlayersLevel() { when(client.isClientThread()).thenReturn(true); when(client.getRealSkillLevel(Skill.ATTACK)).thenReturn(99); - SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).level(90).build(); + SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(90).build(); assertTrue(service.update(task)); assertEquals(Status.COMPLETED, task.getStatus()); @@ -75,7 +75,7 @@ void update_skillLevelTask_shouldSupportLookingUpThePlayersLevel() { void update_skillLevelTask_shouldReturnFalseIfWeAreNotLoggedIn() { when(client.getGameState()).thenReturn(GameState.STARTING); - SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).level(90).build(); + SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(90).build(); assertFalse(service.update(task)); } @@ -85,7 +85,7 @@ void update_skillLevelTask_shouldSupportStatChangedEvents() { when(statChangedEvent.getSkill()).thenReturn(Skill.ATTACK); when(statChangedEvent.getLevel()).thenReturn(99); - SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).level(90).build(); + SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(90).build(); assertTrue(service.update(task, statChangedEvent)); assertEquals(Status.COMPLETED, task.getStatus()); @@ -102,7 +102,7 @@ void update_skillLevelTask_shouldIgnoreStatChangedEventsForTheWrongClass() { @Test void update_skillLevelTask_shouldReturnTrueIfThePlayerLevelExceedsTheGoal() { - SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).level(90).build(); + SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(90).build(); assertTrue(service.update(task, 99)); assertEquals(Status.COMPLETED, task.getStatus()); @@ -110,7 +110,7 @@ void update_skillLevelTask_shouldReturnTrueIfThePlayerLevelExceedsTheGoal() { @Test void update_skillLevelTask_shouldReturnFalseIfThePlayerLevelDoesNotExceedTheGoal() { - SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).level(99).build(); + SkillLevelTask task = SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(99).build(); assertFalse(service.update(task, 90)); assertEquals(Status.NOT_STARTED, task.getStatus()); diff --git a/src/test/java/com/toofifty/goaltracker/utils/QuestRequirementsTest.java b/src/test/java/com/toofifty/goaltracker/utils/QuestRequirementsTest.java index 35db745..b168d35 100644 --- a/src/test/java/com/toofifty/goaltracker/utils/QuestRequirementsTest.java +++ b/src/test/java/com/toofifty/goaltracker/utils/QuestRequirementsTest.java @@ -41,7 +41,7 @@ void testGetRequirements() assertTrue(hasNatureSpirit, "Nature Spirit should be a requirement"); // Verify direct skill requirement (Farming 18) - boolean hasFarming18 = requirements.stream().anyMatch(t -> t instanceof SkillLevelTask && ((SkillLevelTask) t).getSkill() == Skill.FARMING && ((SkillLevelTask) t).getLevel() == 18); + boolean hasFarming18 = requirements.stream().anyMatch(t -> t instanceof SkillLevelTask && ((SkillLevelTask) t).getSkill() == Skill.FARMING && ((SkillLevelTask) t).getTargetSkillLevel() == 18); assertTrue(hasFarming18, "Farming 18 should be a requirement"); } From f861a40e7e6599694a0f5ac3d3b3d7d1f6ffcde8 Mon Sep 17 00:00:00 2001 From: Darkforge317 Date: Sun, 21 Jun 2026 20:32:33 -0600 Subject: [PATCH 2/6] fix: resolve prerequisite batch creation and JSON import crashes Prerequisites were never added to the Goal Manager and therefore would never get their statuses checked when running update(). This fixes that. Also, importing a JSON file that was malformed would cause a null reference pointer exception. Also fixed. # CHANGES: - Implemented `refreshAllTasks` gatekeeper to safely process batch updates (like pre-reqs) on the game thread. - Added an override for `Task.getStatus()` to intercept uninitialized constructor states and safely default to `NOT_STARTED`. - Added an override for `Goal.getTasks()` with a lazy-initialization pattern to prevent null pointer array crashes during malformed JSON imports. --- .../com/toofifty/goaltracker/GoalManager.java | 292 +++++++++--------- .../goaltracker/GoalTrackerPlugin.java | 19 ++ .../com/toofifty/goaltracker/models/Goal.java | 183 ++++++----- .../goaltracker/models/task/Task.java | 159 +++++----- .../toofifty/goaltracker/ui/GoalPanel.java | 20 +- 5 files changed, 365 insertions(+), 308 deletions(-) diff --git a/src/main/java/com/toofifty/goaltracker/GoalManager.java b/src/main/java/com/toofifty/goaltracker/GoalManager.java index b62a0a9..aa1b287 100644 --- a/src/main/java/com/toofifty/goaltracker/GoalManager.java +++ b/src/main/java/com/toofifty/goaltracker/GoalManager.java @@ -1,147 +1,147 @@ -package com.toofifty.goaltracker; - -import com.toofifty.goaltracker.models.Goal; -import com.toofifty.goaltracker.models.enums.Status; -import com.toofifty.goaltracker.models.enums.TaskType; -import com.toofifty.goaltracker.models.task.Task; -import com.toofifty.goaltracker.utils.ReorderableList; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -@Slf4j -@Singleton -/** - * Central manager for plugin goals. - * Handles create/load/save, JSON import/export, and listener notifications. - */ -public final class GoalManager -{ - @Inject - private GoalTrackerConfig config; - - @Inject - private GoalSerializer goalSerializer; - - @Getter - private final ReorderableList goals = new ReorderableList<>(); - - private final List goalsChangedListeners = new ArrayList<>(); - - public Goal createGoal() - { - Goal goal = Goal.builder().build(); - goals.add(goal); - return goal; - } - - /** - * Append a batch of goals and persist + notify listeners. - */ - public void addGoals(List newGoals) - { - if (newGoals == null || newGoals.isEmpty()) - { - return; - } - goals.addAll(newGoals); - save(); - } - - @SuppressWarnings("unchecked") - public List getTasksByTypeAndAnyStatus(TaskType type, Status... statuses) - { - List tasks = new ArrayList<>(); - for (Goal goal : goals) - { - tasks.addAll((List) goal.getTasks().stream() - .filter(task -> task.getType() == type && Arrays.stream(statuses).anyMatch(status -> status == task.getStatus())) - .collect(Collectors.toList())); - } - return tasks; - } - - public List getIncompleteTasksByType(TaskType type) - { - return this.getTasksByTypeAndAnyStatus(type, Status.NOT_STARTED, Status.IN_PROGRESS); - } - - public void save() - { - config.goalTrackerData(goalSerializer.serialize(goals)); - log.info("Saved " + goals.size() + " goals"); - notifyGoalsChanged(); - } - - public void load() - { - try - { - this.goals.clear(); - this.goals.addAll(goalSerializer.deserialize(config.goalTrackerData())); - notifyGoalsChanged(); - log.info("Loaded " + this.goals.size() + " goals"); - } - catch (Exception e) - { - log.error("Failed to load goals!", e); - } - } - /** - * Return the current goals as JSON for export. - * @param pretty pretty-print output - */ - public String exportJson(boolean pretty) - { - return goalSerializer.serialize(goals, pretty); - } - - public void importJson(String json) - { - // Backwards-compatible default: overwrite existing goals - importJson(json, true); - } - - /** - * Import goals from JSON, optionally merging with existing goals. - * @param json the JSON payload - * @param overwrite if true, clears existing goals first; if false, appends imported goals - */ - public void importJson(String json, boolean overwrite) - { - try - { - if (overwrite) - { - this.goals.clear(); - } - this.goals.addAll(goalSerializer.deserialize(json)); - save(); - } - catch (Exception e) - { - log.error("Failed to import goals!", e); - } - } - public void addGoalsChangedListener(Runnable listener) - { - if (listener != null && !goalsChangedListeners.contains(listener)) - { - goalsChangedListeners.add(listener); - } - } - - private void notifyGoalsChanged() - { - for (Runnable r : goalsChangedListeners) - { - try { r.run(); } catch (Exception ignored) {} - } - } +package com.toofifty.goaltracker; + +import com.toofifty.goaltracker.models.Goal; +import com.toofifty.goaltracker.models.enums.Status; +import com.toofifty.goaltracker.models.enums.TaskType; +import com.toofifty.goaltracker.models.task.Task; +import com.toofifty.goaltracker.utils.ReorderableList; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Singleton +/** + * Central manager for plugin goals. + * Handles create/load/save, JSON import/export, and listener notifications. + */ +public final class GoalManager +{ + @Inject + private GoalTrackerConfig config; + + @Inject + private GoalSerializer goalSerializer; + + @Getter + private final ReorderableList goals = new ReorderableList<>(); + + private final List goalsChangedListeners = new ArrayList<>(); + + public Goal createGoal() + { + Goal goal = Goal.builder().build(); + goals.add(goal); + return goal; + } + + /** + * Append a batch of goals and persist + notify listeners. + */ + public void addGoals(List newGoals) + { + if (newGoals == null || newGoals.isEmpty()) + { + return; + } + goals.addAll(newGoals); + save(); + } + + @SuppressWarnings("unchecked") + public List getTasksByTypeAndAnyStatus(TaskType type, Status... statuses) + { + List tasks = new ArrayList<>(); + for (Goal goal : goals) + { + tasks.addAll((List) goal.getTasks().stream() + .filter(task -> task.getType() == type && Arrays.stream(statuses).anyMatch(status -> status == task.getStatus())) + .collect(Collectors.toList())); + } + return tasks; + } + + public List getIncompleteTasksByType(TaskType type) + { + return this.getTasksByTypeAndAnyStatus(type, Status.NOT_STARTED, Status.IN_PROGRESS); + } + + public void save() + { + config.goalTrackerData(goalSerializer.serialize(goals)); + log.info("Saved " + goals.size() + " goals"); + notifyGoalsChanged(); + } + + public void load() + { + try + { + this.goals.clear(); + this.goals.addAll(goalSerializer.deserialize(config.goalTrackerData())); + notifyGoalsChanged(); + log.info("Loaded " + this.goals.size() + " goals"); + } + catch (Exception e) + { + log.error("Failed to load goals!", e); + } + } + /** + * Return the current goals as JSON for export. + * @param pretty pretty-print output + */ + public String exportJson(boolean pretty) + { + return goalSerializer.serialize(goals, pretty); + } + + public void importJson(String json) + { + // Backwards-compatible default: overwrite existing goals + importJson(json, true); + } + + /** + * Import goals from JSON, optionally merging with existing goals. + * @param json the JSON payload + * @param overwrite if true, clears existing goals first; if false, appends imported goals + */ + public void importJson(String json, boolean overwrite) + { + try + { + if (overwrite) + { + this.goals.clear(); + } + this.goals.addAll(goalSerializer.deserialize(json)); + save(); + } + catch (Exception e) + { + log.error("Failed to import goals!", e); + } + } + public void addGoalsChangedListener(Runnable listener) + { + if (listener != null && !goalsChangedListeners.contains(listener)) + { + goalsChangedListeners.add(listener); + } + } + + private void notifyGoalsChanged() + { + for (Runnable r : goalsChangedListeners) + { + try { r.run(); } catch (Exception ignored) {} + } + } } \ No newline at end of file diff --git a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java index 3e25e61..d383eba 100644 --- a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java +++ b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java @@ -497,6 +497,25 @@ private static String normalizeBarrowsName(final String raw) return s; } + /** + * Safe, multithreaded entry point to force a full data validation sweep across all task types. + * Typically used after batch mutations like adding the quest prerequisites. + */ + public void refreshAllTasks(Runnable onCompleteUIHandler) + { + // Force the execution to run safely on the OSRS client thread + clientThread.invokeLater(() -> { + refreshSkillLevelTasks(); + refreshQuestTasks(); + + // If the caller provided a UI update script, bounce it back to the Swing thread + if (onCompleteUIHandler != null) + { + javax.swing.SwingUtilities.invokeLater(onCompleteUIHandler); + } + }); + } + private void refreshQuestTasks() { if (goalManager == null || client == null) return; diff --git a/src/main/java/com/toofifty/goaltracker/models/Goal.java b/src/main/java/com/toofifty/goaltracker/models/Goal.java index bcfcf66..2b71d8d 100644 --- a/src/main/java/com/toofifty/goaltracker/models/Goal.java +++ b/src/main/java/com/toofifty/goaltracker/models/Goal.java @@ -1,85 +1,98 @@ -package com.toofifty.goaltracker.models; - -import com.google.gson.annotations.SerializedName; -import com.toofifty.goaltracker.models.enums.Status; -import com.toofifty.goaltracker.models.task.Task; -import com.toofifty.goaltracker.utils.ReorderableList; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.SuperBuilder; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * Represents a player goal containing one or more tasks. - * Provides helpers for status aggregation and task updates. - */ -@Setter -@Getter -@SuperBuilder -public final class Goal -{ - @Builder.Default - private String description = "New goal"; - - @Builder.Default - private int displayOrder = -1; - - @Builder.Default - private boolean pinned = false; - - @SerializedName("items") - @Builder.Default - private ReorderableList tasks = new ReorderableList<>(); - - private List filterBy(Predicate predicate) - { - return tasks.stream().filter(predicate).collect(Collectors.toList()); - } - - /** True if all tasks are of the given status. */ - public boolean isStatus(Status status) - { - return tasks.stream().allMatch(task -> task.getStatus() == status); - } - - /** True if any task matches one of the given statuses. */ - public boolean isAnyStatus(Status... statuses) - { - return tasks.stream().anyMatch(task -> - Arrays.stream(statuses).anyMatch(s -> s == task.getStatus())); - } - - /** List of completed tasks. */ - public List getComplete() - { - return filterBy(Task::isDone); - } - - /** Aggregated status of this goal. */ - public Status getStatus() - { - if (isStatus(Status.COMPLETED)) - { - return Status.COMPLETED; - } - if (isAnyStatus(Status.IN_PROGRESS, Status.COMPLETED)) - { - return Status.IN_PROGRESS; - } - return Status.NOT_STARTED; - } - - /** Mark all tasks as complete or not started. */ - public void setAllTasksCompleted(boolean completed) - { - for (Task task : tasks) - { - task.setStatus(completed ? Status.COMPLETED : Status.NOT_STARTED); - } - } -} +package com.toofifty.goaltracker.models; + +import com.google.gson.annotations.SerializedName; +import com.toofifty.goaltracker.models.enums.Status; +import com.toofifty.goaltracker.models.task.Task; +import com.toofifty.goaltracker.utils.ReorderableList; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Represents a player goal containing one or more tasks. + * Provides helpers for status aggregation and task updates. + */ +@Setter +@Getter +@SuperBuilder +public final class Goal +{ + @Builder.Default + private String description = "New goal"; + + @Builder.Default + private int displayOrder = -1; + + @Builder.Default + private boolean pinned = false; + + @SerializedName("items") + @Builder.Default + private ReorderableList tasks = new ReorderableList<>(); + + /** + * Safely retrieves the collection of tasks inside this goal. + * Intercepts null pointers injected during malformed third-party JSON imports + */ + public ReorderableList getTasks() + { + if (this.tasks == null) + { + this.tasks = new ReorderableList<>(); + } + return this.tasks; + } + + private List filterBy(Predicate predicate) + { + return getTasks().stream().filter(predicate).collect(Collectors.toList()); + } + + /** True if all tasks are of the given status. */ + public boolean isStatus(Status status) + { + return getTasks().stream().allMatch(task -> task.getStatus() == status); + } + + /** True if any task matches one of the given statuses. */ + public boolean isAnyStatus(Status... statuses) + { + return getTasks().stream().anyMatch(task -> + Arrays.stream(statuses).anyMatch(s -> s == task.getStatus())); + } + + /** List of completed tasks. */ + public List getComplete() + { + return filterBy(Task::isDone); + } + + /** Aggregated status of this goal. */ + public Status getStatus() + { + if (isStatus(Status.COMPLETED)) + { + return Status.COMPLETED; + } + if (isAnyStatus(Status.IN_PROGRESS, Status.COMPLETED)) + { + return Status.IN_PROGRESS; + } + return Status.NOT_STARTED; + } + + /** Mark all tasks as complete or not started. */ + public void setAllTasksCompleted(boolean completed) + { + for (Task task : getTasks()) + { + task.setStatus(completed ? Status.COMPLETED : Status.NOT_STARTED); + } + } +} diff --git a/src/main/java/com/toofifty/goaltracker/models/task/Task.java b/src/main/java/com/toofifty/goaltracker/models/task/Task.java index 9842f5d..6fa6f32 100644 --- a/src/main/java/com/toofifty/goaltracker/models/task/Task.java +++ b/src/main/java/com/toofifty/goaltracker/models/task/Task.java @@ -1,74 +1,85 @@ -package com.toofifty.goaltracker.models.task; - -import com.google.gson.annotations.SerializedName; -import com.toofifty.goaltracker.models.enums.Status; -import com.toofifty.goaltracker.models.enums.TaskType; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.SuperBuilder; - -@Getter -@Setter -@SuperBuilder -/** - * Base class for all goal tasks. - * Provides status tracking, indent level management, and type identification. - */ -public abstract class Task -{ - @Builder.Default - @SerializedName(value = "status", alternate = {"previous_result"}) - private Status status = Status.NOT_STARTED; - - @Builder.Default - @SerializedName("has_been_notified") - private boolean notified = false; - - @Builder.Default - @SerializedName("indent_level") - private int indentLevel = 0; - - public boolean isDone() { - return Status.COMPLETED.equals(this.status); - } - - public void indent() { - if (isFullyIndented()) return; - - indentLevel += 1; - } - - public void unindent() { - if (isNotIndented()) return; - - indentLevel -= 1; - } - - public boolean isIndented() { - return indentLevel > 0; - } - - public boolean isNotIndented() { - return !isIndented(); - } - - public boolean isFullyIndented() { - return indentLevel == 3; - } - - public boolean isNotFullyIndented() { - return !isFullyIndented(); - } - - - @Override - abstract public String toString(); - - /** - * Returns a human-readable name for the task. - */ - public abstract String getDisplayName(); - - abstract public TaskType getType(); -} +package com.toofifty.goaltracker.models.task; + +import com.google.gson.annotations.SerializedName; +import com.toofifty.goaltracker.models.enums.Status; +import com.toofifty.goaltracker.models.enums.TaskType; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@SuperBuilder +/** + * Base class for all goal tasks. + * Provides status tracking, indent level management, and type identification. + */ +public abstract class Task +{ + @Builder.Default + @SerializedName(value = "status", alternate = {"previous_result"}) + private Status status = Status.NOT_STARTED; + + @Builder.Default + @SerializedName("has_been_notified") + private boolean notified = false; + + @Builder.Default + @SerializedName("indent_level") + private int indentLevel = 0; + + public boolean isDone() { + return Status.COMPLETED.equals(this.status); + } + + public void indent() { + if (isFullyIndented()) return; + + indentLevel += 1; + } + + public void unindent() { + if (isNotIndented()) return; + + indentLevel -= 1; + } + + public boolean isIndented() { + return indentLevel > 0; + } + + public boolean isNotIndented() { + return !isIndented(); + } + + public boolean isFullyIndented() { + return indentLevel == 3; + } + + public boolean isNotFullyIndented() { + return !isFullyIndented(); + } + + public Status getStatus() + { + // If the task was built without an explicit status, + // ensure the application reads it safely as NOT_STARTED. + if (this.status == null) + { + return Status.NOT_STARTED; + } + return this.status; + } + + + @Override + abstract public String toString(); + + /** + * Returns a human-readable name for the task. + */ + public abstract String getDisplayName(); + + abstract public TaskType getType(); +} diff --git a/src/main/java/com/toofifty/goaltracker/ui/GoalPanel.java b/src/main/java/com/toofifty/goaltracker/ui/GoalPanel.java index f805e57..47ec3ed 100644 --- a/src/main/java/com/toofifty/goaltracker/ui/GoalPanel.java +++ b/src/main/java/com/toofifty/goaltracker/ui/GoalPanel.java @@ -359,9 +359,23 @@ private void addPrereqs() // Refresh UI/state after batch plugin.setValidateAll(true); - plugin.getUiStatusManager().refresh(goal); - refreshTaskList(); - updateUndoRedoButtons(); + + // If new prerequisites were actually added, run our clean validation sweep + if (invoked > 0) + { + plugin.refreshAllTasks(() -> { + plugin.getUiStatusManager().refresh(goal); + refreshTaskList(); + updateUndoRedoButtons(); + }); + } + else + { + // If nothing was added, refresh the UI instantly on the UI thread + plugin.getUiStatusManager().refresh(goal); + refreshTaskList(); + updateUndoRedoButtons(); + } if (processed == 0) { From 02eaf132b47633943f1d89ca8f343756067e0e43 Mon Sep 17 00:00:00 2001 From: Darkforge317 Date: Sun, 21 Jun 2026 23:53:17 -0600 Subject: [PATCH 3/6] fix: synchronize skill level task UI state and scaffold XP task loop Skill level tasks updated behind the scenes but never reflected their new status on the UI or saved to the disk. This fixes that, making sure player actions instantly repaint the sidebar and notify the chat. Also, added the necessary layout infrastructure so we can easily add experience tracking next without making a big mess of the method. # CHANGES: - Tied level updates inside `onStatChanged` directly to real-time `uiStatusManager` refreshes and player chat notifications. - Replaced individual, spammy disk writes with a single batch-save check at the very end of the method to protect processing power. - Implemented an extensible if/else loop structure to act as clean scaffolding for upcoming XP task processing. --- .../goaltracker/GoalTrackerPlugin.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java index d383eba..2f6353c 100644 --- a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java +++ b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java @@ -282,9 +282,32 @@ public void onSessionOpen(SessionOpen event) @Subscribe public void onStatChanged(StatChanged event) { + boolean anyTaskChanged = false; + + // 1. Process Skill Level task updates List skillLevelTasks = goalManager.getIncompleteTasksByType(TaskType.SKILL_LEVEL); for (SkillLevelTask task : skillLevelTasks) { + // If this skill level task did not receive a status change if (!taskUpdateService.update(task, event)) continue; + // If the skill level task DID receive a status change + else { + anyTaskChanged = true; + + // Update the UI immediately to reflect the new status + uiStatusManager.refresh(task); + + // If we completed the task, notify the player + if (task.getStatus().isCompleted()) { + notifyTask(task); + } + } + } + + // TODO: PROCESS XP TASKS + + // Save once if any status changes occurred + if (anyTaskChanged) { + goalManager.save(); } } From 13a7b989502198329c011b668a6e577bb8494ec6 Mon Sep 17 00:00:00 2001 From: Darkforge317 Date: Mon, 22 Jun 2026 00:24:37 -0600 Subject: [PATCH 4/6] fix: implement event-driven XP task tracking and login validation Skill XP tasks were completely ignored by the live skill training event hook (onStatChanged) and failed to catch up or initialize upon logging into the game. This fixes that, ensuring player experience gains are tracked smoothly in real-time. Also, added a dedicated experience verification sweep to the startup routine so milestones achieved on other devices synchronize instantly on login. # CHANGES: - Integrated a new `SkillXpTask` collection processing loop directly inside the `onStatChanged` event listener. - Appended `refreshSkillXpTasks()` to the `onGameStateChanged` login airlock loop to force a silent experience verification pass on startup. - Refactored `xp` to `targetSkillXp` within `SkillXpTask.java` to align perfectly with the skill level property layout naming conventions. --- .../goaltracker/GoalTrackerPlugin.java | 38 ++++++++++++- .../goaltracker/models/task/SkillXpTask.java | 53 +++++++++++++++++-- .../services/TaskUpdateService.java | 2 +- .../ui/inputs/SkillXpTaskInput.java | 2 +- .../goaltracker/GoalSerializerTest.java | 4 +- .../models/task/SkillXpTaskTest.java | 2 +- .../services/TaskUpdateServiceTest.java | 14 ++--- 7 files changed, 99 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java index 2f6353c..38c115c 100644 --- a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java +++ b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java @@ -6,6 +6,7 @@ import com.toofifty.goaltracker.models.task.ItemTask; import com.toofifty.goaltracker.models.task.QuestTask; import com.toofifty.goaltracker.models.task.SkillLevelTask; +import com.toofifty.goaltracker.models.task.SkillXpTask; import com.toofifty.goaltracker.models.task.Task; import com.toofifty.goaltracker.services.TaskIconService; import com.toofifty.goaltracker.services.TaskUpdateService; @@ -303,7 +304,24 @@ public void onStatChanged(StatChanged event) } } - // TODO: PROCESS XP TASKS + // 2. Process Skill XP task updates + List skillXpTasks = goalManager.getIncompleteTasksByType(TaskType.SKILL_XP); + for (SkillXpTask task : skillXpTasks) { + // If this skill XP task did not receive a status change + if (!taskUpdateService.update(task, event)) continue; + // If the skill XP task DID receive a status change + else { + anyTaskChanged = true; + + // Update the UI immediately to reflect the new status + uiStatusManager.refresh(task); + + // If we completed the task, notify the player + if (task.getStatus().isCompleted()) { + notifyTask(task); + } + } + } // Save once if any status changes occurred if (anyTaskChanged) { @@ -326,6 +344,7 @@ public void onGameStateChanged(GameStateChanged event) // Refresh tasks now that player data exists refreshQuestTasks(); refreshSkillLevelTasks(); + refreshSkillXpTasks(); // Give the UI a moment to settle after data updates before repainting the panel schedulePanelRefresh(200); @@ -530,6 +549,7 @@ public void refreshAllTasks(Runnable onCompleteUIHandler) clientThread.invokeLater(() -> { refreshSkillLevelTasks(); refreshQuestTasks(); + refreshSkillXpTasks(); // If the caller provided a UI update script, bounce it back to the Swing thread if (onCompleteUIHandler != null) @@ -569,6 +589,22 @@ private void refreshSkillLevelTasks() } } + private void refreshSkillXpTasks() + { + if (goalManager == null || client == null) return; + + List skillXpTasks = goalManager.getIncompleteTasksByType(TaskType.SKILL_XP); + for (SkillXpTask task : skillXpTasks) + { + task.refreshStatus(client); + uiStatusManager.refresh(task); + if (task.getStatus().isCompleted()) + { + notifyTask(task); + } + } + } + @Provides public GoalTrackerConfig provideConfig(ConfigManager configManager) { diff --git a/src/main/java/com/toofifty/goaltracker/models/task/SkillXpTask.java b/src/main/java/com/toofifty/goaltracker/models/task/SkillXpTask.java index d9f1318..33f2799 100644 --- a/src/main/java/com/toofifty/goaltracker/models/task/SkillXpTask.java +++ b/src/main/java/com/toofifty/goaltracker/models/task/SkillXpTask.java @@ -1,9 +1,11 @@ package com.toofifty.goaltracker.models.task; +import com.toofifty.goaltracker.models.enums.Status; import com.toofifty.goaltracker.models.enums.TaskType; import lombok.Getter; import lombok.Setter; import lombok.experimental.SuperBuilder; +import net.runelite.api.Client; import net.runelite.api.Skill; @Getter @@ -16,18 +18,19 @@ public final class SkillXpTask extends Task { private Skill skill; - private int xp; + private int currentSkillXp; + private int targetSkillXp; @Override public String toString() { - return String.format("%d %s XP", xp, skill.getName()); + return String.format("%d %s XP", targetSkillXp, skill.getName()); } @Override public String getDisplayName() { - return String.format("%d %s XP", xp, skill.getName()); + return String.format("%d %s XP", targetSkillXp, skill.getName()); } @Override @@ -35,4 +38,48 @@ public TaskType getType() { return TaskType.SKILL_XP; } + + /** + * Re-evaluate the player's Xp in the skill and update the Task.status field. + * Safe to call on login and whenever relevant varbits/varps change. + */ + public void refreshStatus(final Client client) + { + if (client == null || skill == null) + { + return; + } + + currentSkillXp = client.getSkillExperience(skill); + + if(hasReachedTargetLevel()) + { + setStatus(Status.COMPLETED); + } + } + + /** + * Whether the player has reached the target Xp for this task. + * @return
    + *
  • {@code true} - Target Xp HAS been reached
  • + *
  • {@code false} - Target Xp HAS NOT been reached
  • + *
+ */ + public boolean hasReachedTargetLevel() + { + return (currentSkillXp >= targetSkillXp); + } + + /** + * Whether the player would reach the target Xp for this task given the xp provided. + * @param xp The Xp value to check against + * @return
    + *
  • {@code true} - Target Xp HAS been reached
  • + *
  • {@code false} - Target Xp HAS NOT been reached
  • + *
+ */ + public boolean wouldReachTargetLevel(int xp) + { + return (xp >= targetSkillXp); + } } diff --git a/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java b/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java index 5ca80fe..f5e08cf 100644 --- a/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java +++ b/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java @@ -91,7 +91,7 @@ public boolean update(SkillXpTask task, StatChanged event) public boolean update(SkillXpTask task, int xp) { final Status oldStatus = task.getStatus(); - task.setStatus(xp >= task.getXp() ? Status.COMPLETED : Status.NOT_STARTED); + task.setStatus(xp >= task.getTargetSkillXp() ? Status.COMPLETED : Status.NOT_STARTED); return oldStatus != task.getStatus(); } diff --git a/src/main/java/com/toofifty/goaltracker/ui/inputs/SkillXpTaskInput.java b/src/main/java/com/toofifty/goaltracker/ui/inputs/SkillXpTaskInput.java index 6d2275e..d1fe0ff 100644 --- a/src/main/java/com/toofifty/goaltracker/ui/inputs/SkillXpTaskInput.java +++ b/src/main/java/com/toofifty/goaltracker/ui/inputs/SkillXpTaskInput.java @@ -82,7 +82,7 @@ protected void submit() addTask(SkillXpTask.builder() .skill((Skill) skillField.getSelectedItem()) - .xp(Integer.parseInt(xpField.getText())) + .targetSkillXp(Integer.parseInt(xpField.getText())) .build()); } diff --git a/src/test/java/com/toofifty/goaltracker/GoalSerializerTest.java b/src/test/java/com/toofifty/goaltracker/GoalSerializerTest.java index 2a4b548..b01f708 100644 --- a/src/test/java/com/toofifty/goaltracker/GoalSerializerTest.java +++ b/src/test/java/com/toofifty/goaltracker/GoalSerializerTest.java @@ -87,7 +87,7 @@ public void deserialize_should_support_other_task_types() throws IOException { SkillXpTask skillXpTask = (SkillXpTask) goals.get(0).getTasks().get(2); - assertEquals(1234, skillXpTask.getXp()); + assertEquals(1234, skillXpTask.getTargetSkillXp()); assertEquals(Skill.ATTACK, skillXpTask.getSkill()); assertEquals(2, skillXpTask.getIndentLevel()); @@ -157,7 +157,7 @@ public void serialize_should_support_other_task_types() throws IOException { .status(Status.NOT_STARTED) .notified(false) .indentLevel(2) - .xp(1234) + .targetSkillXp(1234) .skill(Skill.ATTACK) .build(), QuestTask.builder() diff --git a/src/test/java/com/toofifty/goaltracker/models/task/SkillXpTaskTest.java b/src/test/java/com/toofifty/goaltracker/models/task/SkillXpTaskTest.java index 5f92f4f..1181991 100644 --- a/src/test/java/com/toofifty/goaltracker/models/task/SkillXpTaskTest.java +++ b/src/test/java/com/toofifty/goaltracker/models/task/SkillXpTaskTest.java @@ -9,7 +9,7 @@ class SkillXpTaskTest { @Test void toString_shouldReturnTheXPAndSkillName() { - SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build(); + SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).targetSkillXp(1234).build(); assertEquals("1234 Attack XP", task.toString()); } diff --git a/src/test/java/com/toofifty/goaltracker/services/TaskUpdateServiceTest.java b/src/test/java/com/toofifty/goaltracker/services/TaskUpdateServiceTest.java index ae193ea..a76c228 100644 --- a/src/test/java/com/toofifty/goaltracker/services/TaskUpdateServiceTest.java +++ b/src/test/java/com/toofifty/goaltracker/services/TaskUpdateServiceTest.java @@ -49,7 +49,7 @@ void update_shouldDynamicallyMapTasks() { when(itemCache.getTotalQuantity(314)).thenReturn(100); assertTrue(service.update((Task) SkillLevelTask.builder().skill(Skill.ATTACK).targetSkillLevel(90).build())); - assertTrue(service.update((Task) SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build())); + assertTrue(service.update((Task) SkillXpTask.builder().skill(Skill.ATTACK).targetSkillXp(1234).build())); assertTrue(service.update((Task) QuestTask.builder().quest(quest).build())); assertTrue(service.update((Task) ItemTask.builder().itemId(314).acquired(0).quantity(100).build())); } @@ -122,7 +122,7 @@ void update_skillXpTask_shouldSupportLookingUpThePlayersLevel() { when(client.isClientThread()).thenReturn(true); when(client.getSkillExperience(Skill.ATTACK)).thenReturn(1234); - SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build(); + SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).targetSkillXp(1234).build(); assertTrue(service.update(task)); assertEquals(Status.COMPLETED, task.getStatus()); @@ -132,7 +132,7 @@ void update_skillXpTask_shouldSupportLookingUpThePlayersLevel() { void update_skillXpTask_shouldReturnFalseIfWeAreNotLoggedIn() { when(client.getGameState()).thenReturn(GameState.STARTING); - SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build(); + SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).targetSkillXp(1234).build(); assertFalse(service.update(task)); } @@ -142,7 +142,7 @@ void update_skillXpTask_shouldSupportStatChangedEvents() { when(statChangedEvent.getSkill()).thenReturn(Skill.ATTACK); when(statChangedEvent.getXp()).thenReturn(1234); - SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build(); + SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).targetSkillXp(1234).build(); assertTrue(service.update(task, statChangedEvent)); assertEquals(Status.COMPLETED, task.getStatus()); @@ -152,14 +152,14 @@ void update_skillXpTask_shouldSupportStatChangedEvents() { void update_skillXpTask_shouldIgnoreStatChangedEventsForTheWrongSkill() { when(statChangedEvent.getSkill()).thenReturn(Skill.AGILITY); - SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build(); + SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).targetSkillXp(1234).build(); assertFalse(service.update(task, statChangedEvent)); } @Test void update_skillXpTask_shouldReturnTrueIfThePlayerXPExceedsTheGoal() { - SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build(); + SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).targetSkillXp(1234).build(); assertTrue(service.update(task, 1234)); assertEquals(Status.COMPLETED, task.getStatus()); @@ -167,7 +167,7 @@ void update_skillXpTask_shouldReturnTrueIfThePlayerXPExceedsTheGoal() { @Test void update_skillXPTask_shouldReturnFalseIfThePlayerXPDoesNotExceedTheGoal() { - SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).xp(1234).build(); + SkillXpTask task = SkillXpTask.builder().skill(Skill.ATTACK).targetSkillXp(1234).build(); assertFalse(service.update(task, 1233)); assertEquals(Status.NOT_STARTED, task.getStatus()); From 734fc5df36578e1fb18125092941ba4fa5573771 Mon Sep 17 00:00:00 2001 From: Darkforge317 Date: Mon, 22 Jun 2026 23:12:43 -0600 Subject: [PATCH 5/6] fix: connect prerequisite batch pipeline and resolve task addition save bugs Skill XP tasks were completely ignored by the live training event hook and failed to initialize on creation or login. This fixes that, ensuring progress metrics are tracked smoothly in real-time. Also, refactored the task addition layout to resolve a critical data-health serialization bug that was forcing incomplete integer data onto the disk. # CHANGES: - Restructured onTaskAdded to execute askUpdateService.update prior to disk serialization, ensuring newly created tasks capture true player statistics instead of zero placeholders. - Offloaded goalManager.save() and otifyTask to the background game thread to insulate the main Swing UI thread from blocking I/O stutters. - Integrated the efreshAllTasks background gatekeeper to process prerequisite batch additions safely on the game thread. - Fortified Task and Goal models with self-healing null guards to prevent user interface crashes during malformed JSON data imports. --- .../goaltracker/GoalTrackerPlugin.java | 28 +++++++++---------- .../models/task/SkillLevelTask.java | 4 +++ .../goaltracker/models/task/SkillXpTask.java | 10 +++++-- .../services/TaskUpdateService.java | 4 +++ 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java index 38c115c..23620a9 100644 --- a/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java +++ b/src/main/java/com/toofifty/goaltracker/GoalTrackerPlugin.java @@ -2,6 +2,7 @@ import com.google.inject.Provides; +import com.toofifty.goaltracker.models.enums.Status; import com.toofifty.goaltracker.models.enums.TaskType; import com.toofifty.goaltracker.models.task.ItemTask; import com.toofifty.goaltracker.models.task.QuestTask; @@ -232,23 +233,23 @@ protected void startUp() goalTrackerPanel.onGoalUpdated((goal) -> goalManager.save()); goalTrackerPanel.onTaskAdded((task) -> { - // Instantly save the new task so the UI stays responsive - goalManager.save(); - - // Send to the client thread to respect taskUpdateService's thread-safety guards. - // Otherwise, it would prevent an update that was called from this UI thread + // Send directly to the client thread to fetch live player stats clientThread.invokeLater(() -> { - // If the task status updated - if (taskUpdateService.update(task)) { - if (task.getStatus().isCompleted()) { - notifyTask(task); - } + // Populate the live metrics into memory instantly upon creation + taskUpdateService.update(task); - uiStatusManager.refresh(task); + // Perform the disk write safely on the background game thread + goalManager.save(); - // Re-save only if verification math changed the completion state - goalManager.save(); + // If the task is instantly completed, notify the player + if (task.getStatus().isCompleted()) { + notifyTask(task); } + + // Send to the UI thread to handle screen graphics + SwingUtilities.invokeLater(() -> { + uiStatusManager.refresh(task); + }); }); }); @@ -592,7 +593,6 @@ private void refreshSkillLevelTasks() private void refreshSkillXpTasks() { if (goalManager == null || client == null) return; - List skillXpTasks = goalManager.getIncompleteTasksByType(TaskType.SKILL_XP); for (SkillXpTask task : skillXpTasks) { diff --git a/src/main/java/com/toofifty/goaltracker/models/task/SkillLevelTask.java b/src/main/java/com/toofifty/goaltracker/models/task/SkillLevelTask.java index 5c1c66f..f96df55 100644 --- a/src/main/java/com/toofifty/goaltracker/models/task/SkillLevelTask.java +++ b/src/main/java/com/toofifty/goaltracker/models/task/SkillLevelTask.java @@ -58,6 +58,10 @@ public void refreshStatus(final Client client) { setStatus(Status.COMPLETED); } + // This covers cases where players log into a separate character that no longer meets the task requirements + else { + setStatus(Status.NOT_STARTED); + } } /** diff --git a/src/main/java/com/toofifty/goaltracker/models/task/SkillXpTask.java b/src/main/java/com/toofifty/goaltracker/models/task/SkillXpTask.java index 33f2799..a95d807 100644 --- a/src/main/java/com/toofifty/goaltracker/models/task/SkillXpTask.java +++ b/src/main/java/com/toofifty/goaltracker/models/task/SkillXpTask.java @@ -52,10 +52,14 @@ public void refreshStatus(final Client client) currentSkillXp = client.getSkillExperience(skill); - if(hasReachedTargetLevel()) + if(hasReachedTargetXp()) { setStatus(Status.COMPLETED); } + // This covers cases where players log into a separate character that no longer meets the task requirements + else { + setStatus(Status.NOT_STARTED); + } } /** @@ -65,7 +69,7 @@ public void refreshStatus(final Client client) *
  • {@code false} - Target Xp HAS NOT been reached
  • * */ - public boolean hasReachedTargetLevel() + public boolean hasReachedTargetXp() { return (currentSkillXp >= targetSkillXp); } @@ -78,7 +82,7 @@ public boolean hasReachedTargetLevel() *
  • {@code false} - Target Xp HAS NOT been reached
  • * */ - public boolean wouldReachTargetLevel(int xp) + public boolean wouldReachTargetXp(int xp) { return (xp >= targetSkillXp); } diff --git a/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java b/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java index f5e08cf..e64da34 100644 --- a/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java +++ b/src/main/java/com/toofifty/goaltracker/services/TaskUpdateService.java @@ -91,6 +91,10 @@ public boolean update(SkillXpTask task, StatChanged event) public boolean update(SkillXpTask task, int xp) { final Status oldStatus = task.getStatus(); + + // Store the live player experience in the task's cache + task.setCurrentSkillXp(xp); + task.setStatus(xp >= task.getTargetSkillXp() ? Status.COMPLETED : Status.NOT_STARTED); return oldStatus != task.getStatus(); } From 326fdb37a442ef0e3266a71bdf401df2162224d9 Mon Sep 17 00:00:00 2001 From: Darkforge317 Date: Mon, 22 Jun 2026 23:15:13 -0600 Subject: [PATCH 6/6] Fixed Line Endings A few files were converted back to CRLF... so I bumped them back to LF again. --- .../com/toofifty/goaltracker/GoalManager.java | 292 +++++++++--------- .../com/toofifty/goaltracker/models/Goal.java | 196 ++++++------ .../goaltracker/models/task/Task.java | 170 +++++----- 3 files changed, 329 insertions(+), 329 deletions(-) diff --git a/src/main/java/com/toofifty/goaltracker/GoalManager.java b/src/main/java/com/toofifty/goaltracker/GoalManager.java index aa1b287..b62a0a9 100644 --- a/src/main/java/com/toofifty/goaltracker/GoalManager.java +++ b/src/main/java/com/toofifty/goaltracker/GoalManager.java @@ -1,147 +1,147 @@ -package com.toofifty.goaltracker; - -import com.toofifty.goaltracker.models.Goal; -import com.toofifty.goaltracker.models.enums.Status; -import com.toofifty.goaltracker.models.enums.TaskType; -import com.toofifty.goaltracker.models.task.Task; -import com.toofifty.goaltracker.utils.ReorderableList; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import javax.inject.Inject; -import javax.inject.Singleton; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -@Slf4j -@Singleton -/** - * Central manager for plugin goals. - * Handles create/load/save, JSON import/export, and listener notifications. - */ -public final class GoalManager -{ - @Inject - private GoalTrackerConfig config; - - @Inject - private GoalSerializer goalSerializer; - - @Getter - private final ReorderableList goals = new ReorderableList<>(); - - private final List goalsChangedListeners = new ArrayList<>(); - - public Goal createGoal() - { - Goal goal = Goal.builder().build(); - goals.add(goal); - return goal; - } - - /** - * Append a batch of goals and persist + notify listeners. - */ - public void addGoals(List newGoals) - { - if (newGoals == null || newGoals.isEmpty()) - { - return; - } - goals.addAll(newGoals); - save(); - } - - @SuppressWarnings("unchecked") - public List getTasksByTypeAndAnyStatus(TaskType type, Status... statuses) - { - List tasks = new ArrayList<>(); - for (Goal goal : goals) - { - tasks.addAll((List) goal.getTasks().stream() - .filter(task -> task.getType() == type && Arrays.stream(statuses).anyMatch(status -> status == task.getStatus())) - .collect(Collectors.toList())); - } - return tasks; - } - - public List getIncompleteTasksByType(TaskType type) - { - return this.getTasksByTypeAndAnyStatus(type, Status.NOT_STARTED, Status.IN_PROGRESS); - } - - public void save() - { - config.goalTrackerData(goalSerializer.serialize(goals)); - log.info("Saved " + goals.size() + " goals"); - notifyGoalsChanged(); - } - - public void load() - { - try - { - this.goals.clear(); - this.goals.addAll(goalSerializer.deserialize(config.goalTrackerData())); - notifyGoalsChanged(); - log.info("Loaded " + this.goals.size() + " goals"); - } - catch (Exception e) - { - log.error("Failed to load goals!", e); - } - } - /** - * Return the current goals as JSON for export. - * @param pretty pretty-print output - */ - public String exportJson(boolean pretty) - { - return goalSerializer.serialize(goals, pretty); - } - - public void importJson(String json) - { - // Backwards-compatible default: overwrite existing goals - importJson(json, true); - } - - /** - * Import goals from JSON, optionally merging with existing goals. - * @param json the JSON payload - * @param overwrite if true, clears existing goals first; if false, appends imported goals - */ - public void importJson(String json, boolean overwrite) - { - try - { - if (overwrite) - { - this.goals.clear(); - } - this.goals.addAll(goalSerializer.deserialize(json)); - save(); - } - catch (Exception e) - { - log.error("Failed to import goals!", e); - } - } - public void addGoalsChangedListener(Runnable listener) - { - if (listener != null && !goalsChangedListeners.contains(listener)) - { - goalsChangedListeners.add(listener); - } - } - - private void notifyGoalsChanged() - { - for (Runnable r : goalsChangedListeners) - { - try { r.run(); } catch (Exception ignored) {} - } - } +package com.toofifty.goaltracker; + +import com.toofifty.goaltracker.models.Goal; +import com.toofifty.goaltracker.models.enums.Status; +import com.toofifty.goaltracker.models.enums.TaskType; +import com.toofifty.goaltracker.models.task.Task; +import com.toofifty.goaltracker.utils.ReorderableList; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +@Singleton +/** + * Central manager for plugin goals. + * Handles create/load/save, JSON import/export, and listener notifications. + */ +public final class GoalManager +{ + @Inject + private GoalTrackerConfig config; + + @Inject + private GoalSerializer goalSerializer; + + @Getter + private final ReorderableList goals = new ReorderableList<>(); + + private final List goalsChangedListeners = new ArrayList<>(); + + public Goal createGoal() + { + Goal goal = Goal.builder().build(); + goals.add(goal); + return goal; + } + + /** + * Append a batch of goals and persist + notify listeners. + */ + public void addGoals(List newGoals) + { + if (newGoals == null || newGoals.isEmpty()) + { + return; + } + goals.addAll(newGoals); + save(); + } + + @SuppressWarnings("unchecked") + public List getTasksByTypeAndAnyStatus(TaskType type, Status... statuses) + { + List tasks = new ArrayList<>(); + for (Goal goal : goals) + { + tasks.addAll((List) goal.getTasks().stream() + .filter(task -> task.getType() == type && Arrays.stream(statuses).anyMatch(status -> status == task.getStatus())) + .collect(Collectors.toList())); + } + return tasks; + } + + public List getIncompleteTasksByType(TaskType type) + { + return this.getTasksByTypeAndAnyStatus(type, Status.NOT_STARTED, Status.IN_PROGRESS); + } + + public void save() + { + config.goalTrackerData(goalSerializer.serialize(goals)); + log.info("Saved " + goals.size() + " goals"); + notifyGoalsChanged(); + } + + public void load() + { + try + { + this.goals.clear(); + this.goals.addAll(goalSerializer.deserialize(config.goalTrackerData())); + notifyGoalsChanged(); + log.info("Loaded " + this.goals.size() + " goals"); + } + catch (Exception e) + { + log.error("Failed to load goals!", e); + } + } + /** + * Return the current goals as JSON for export. + * @param pretty pretty-print output + */ + public String exportJson(boolean pretty) + { + return goalSerializer.serialize(goals, pretty); + } + + public void importJson(String json) + { + // Backwards-compatible default: overwrite existing goals + importJson(json, true); + } + + /** + * Import goals from JSON, optionally merging with existing goals. + * @param json the JSON payload + * @param overwrite if true, clears existing goals first; if false, appends imported goals + */ + public void importJson(String json, boolean overwrite) + { + try + { + if (overwrite) + { + this.goals.clear(); + } + this.goals.addAll(goalSerializer.deserialize(json)); + save(); + } + catch (Exception e) + { + log.error("Failed to import goals!", e); + } + } + public void addGoalsChangedListener(Runnable listener) + { + if (listener != null && !goalsChangedListeners.contains(listener)) + { + goalsChangedListeners.add(listener); + } + } + + private void notifyGoalsChanged() + { + for (Runnable r : goalsChangedListeners) + { + try { r.run(); } catch (Exception ignored) {} + } + } } \ No newline at end of file diff --git a/src/main/java/com/toofifty/goaltracker/models/Goal.java b/src/main/java/com/toofifty/goaltracker/models/Goal.java index 2b71d8d..cee6fba 100644 --- a/src/main/java/com/toofifty/goaltracker/models/Goal.java +++ b/src/main/java/com/toofifty/goaltracker/models/Goal.java @@ -1,98 +1,98 @@ -package com.toofifty.goaltracker.models; - -import com.google.gson.annotations.SerializedName; -import com.toofifty.goaltracker.models.enums.Status; -import com.toofifty.goaltracker.models.task.Task; -import com.toofifty.goaltracker.utils.ReorderableList; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.SuperBuilder; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * Represents a player goal containing one or more tasks. - * Provides helpers for status aggregation and task updates. - */ -@Setter -@Getter -@SuperBuilder -public final class Goal -{ - @Builder.Default - private String description = "New goal"; - - @Builder.Default - private int displayOrder = -1; - - @Builder.Default - private boolean pinned = false; - - @SerializedName("items") - @Builder.Default - private ReorderableList tasks = new ReorderableList<>(); - - /** - * Safely retrieves the collection of tasks inside this goal. - * Intercepts null pointers injected during malformed third-party JSON imports - */ - public ReorderableList getTasks() - { - if (this.tasks == null) - { - this.tasks = new ReorderableList<>(); - } - return this.tasks; - } - - private List filterBy(Predicate predicate) - { - return getTasks().stream().filter(predicate).collect(Collectors.toList()); - } - - /** True if all tasks are of the given status. */ - public boolean isStatus(Status status) - { - return getTasks().stream().allMatch(task -> task.getStatus() == status); - } - - /** True if any task matches one of the given statuses. */ - public boolean isAnyStatus(Status... statuses) - { - return getTasks().stream().anyMatch(task -> - Arrays.stream(statuses).anyMatch(s -> s == task.getStatus())); - } - - /** List of completed tasks. */ - public List getComplete() - { - return filterBy(Task::isDone); - } - - /** Aggregated status of this goal. */ - public Status getStatus() - { - if (isStatus(Status.COMPLETED)) - { - return Status.COMPLETED; - } - if (isAnyStatus(Status.IN_PROGRESS, Status.COMPLETED)) - { - return Status.IN_PROGRESS; - } - return Status.NOT_STARTED; - } - - /** Mark all tasks as complete or not started. */ - public void setAllTasksCompleted(boolean completed) - { - for (Task task : getTasks()) - { - task.setStatus(completed ? Status.COMPLETED : Status.NOT_STARTED); - } - } -} +package com.toofifty.goaltracker.models; + +import com.google.gson.annotations.SerializedName; +import com.toofifty.goaltracker.models.enums.Status; +import com.toofifty.goaltracker.models.task.Task; +import com.toofifty.goaltracker.utils.ReorderableList; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Represents a player goal containing one or more tasks. + * Provides helpers for status aggregation and task updates. + */ +@Setter +@Getter +@SuperBuilder +public final class Goal +{ + @Builder.Default + private String description = "New goal"; + + @Builder.Default + private int displayOrder = -1; + + @Builder.Default + private boolean pinned = false; + + @SerializedName("items") + @Builder.Default + private ReorderableList tasks = new ReorderableList<>(); + + /** + * Safely retrieves the collection of tasks inside this goal. + * Intercepts null pointers injected during malformed third-party JSON imports + */ + public ReorderableList getTasks() + { + if (this.tasks == null) + { + this.tasks = new ReorderableList<>(); + } + return this.tasks; + } + + private List filterBy(Predicate predicate) + { + return getTasks().stream().filter(predicate).collect(Collectors.toList()); + } + + /** True if all tasks are of the given status. */ + public boolean isStatus(Status status) + { + return getTasks().stream().allMatch(task -> task.getStatus() == status); + } + + /** True if any task matches one of the given statuses. */ + public boolean isAnyStatus(Status... statuses) + { + return getTasks().stream().anyMatch(task -> + Arrays.stream(statuses).anyMatch(s -> s == task.getStatus())); + } + + /** List of completed tasks. */ + public List getComplete() + { + return filterBy(Task::isDone); + } + + /** Aggregated status of this goal. */ + public Status getStatus() + { + if (isStatus(Status.COMPLETED)) + { + return Status.COMPLETED; + } + if (isAnyStatus(Status.IN_PROGRESS, Status.COMPLETED)) + { + return Status.IN_PROGRESS; + } + return Status.NOT_STARTED; + } + + /** Mark all tasks as complete or not started. */ + public void setAllTasksCompleted(boolean completed) + { + for (Task task : getTasks()) + { + task.setStatus(completed ? Status.COMPLETED : Status.NOT_STARTED); + } + } +} diff --git a/src/main/java/com/toofifty/goaltracker/models/task/Task.java b/src/main/java/com/toofifty/goaltracker/models/task/Task.java index 6fa6f32..620f3de 100644 --- a/src/main/java/com/toofifty/goaltracker/models/task/Task.java +++ b/src/main/java/com/toofifty/goaltracker/models/task/Task.java @@ -1,85 +1,85 @@ -package com.toofifty.goaltracker.models.task; - -import com.google.gson.annotations.SerializedName; -import com.toofifty.goaltracker.models.enums.Status; -import com.toofifty.goaltracker.models.enums.TaskType; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.SuperBuilder; - -@Getter -@Setter -@SuperBuilder -/** - * Base class for all goal tasks. - * Provides status tracking, indent level management, and type identification. - */ -public abstract class Task -{ - @Builder.Default - @SerializedName(value = "status", alternate = {"previous_result"}) - private Status status = Status.NOT_STARTED; - - @Builder.Default - @SerializedName("has_been_notified") - private boolean notified = false; - - @Builder.Default - @SerializedName("indent_level") - private int indentLevel = 0; - - public boolean isDone() { - return Status.COMPLETED.equals(this.status); - } - - public void indent() { - if (isFullyIndented()) return; - - indentLevel += 1; - } - - public void unindent() { - if (isNotIndented()) return; - - indentLevel -= 1; - } - - public boolean isIndented() { - return indentLevel > 0; - } - - public boolean isNotIndented() { - return !isIndented(); - } - - public boolean isFullyIndented() { - return indentLevel == 3; - } - - public boolean isNotFullyIndented() { - return !isFullyIndented(); - } - - public Status getStatus() - { - // If the task was built without an explicit status, - // ensure the application reads it safely as NOT_STARTED. - if (this.status == null) - { - return Status.NOT_STARTED; - } - return this.status; - } - - - @Override - abstract public String toString(); - - /** - * Returns a human-readable name for the task. - */ - public abstract String getDisplayName(); - - abstract public TaskType getType(); -} +package com.toofifty.goaltracker.models.task; + +import com.google.gson.annotations.SerializedName; +import com.toofifty.goaltracker.models.enums.Status; +import com.toofifty.goaltracker.models.enums.TaskType; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@SuperBuilder +/** + * Base class for all goal tasks. + * Provides status tracking, indent level management, and type identification. + */ +public abstract class Task +{ + @Builder.Default + @SerializedName(value = "status", alternate = {"previous_result"}) + private Status status = Status.NOT_STARTED; + + @Builder.Default + @SerializedName("has_been_notified") + private boolean notified = false; + + @Builder.Default + @SerializedName("indent_level") + private int indentLevel = 0; + + public boolean isDone() { + return Status.COMPLETED.equals(this.status); + } + + public void indent() { + if (isFullyIndented()) return; + + indentLevel += 1; + } + + public void unindent() { + if (isNotIndented()) return; + + indentLevel -= 1; + } + + public boolean isIndented() { + return indentLevel > 0; + } + + public boolean isNotIndented() { + return !isIndented(); + } + + public boolean isFullyIndented() { + return indentLevel == 3; + } + + public boolean isNotFullyIndented() { + return !isFullyIndented(); + } + + public Status getStatus() + { + // If the task was built without an explicit status, + // ensure the application reads it safely as NOT_STARTED. + if (this.status == null) + { + return Status.NOT_STARTED; + } + return this.status; + } + + + @Override + abstract public String toString(); + + /** + * Returns a human-readable name for the task. + */ + public abstract String getDisplayName(); + + abstract public TaskType getType(); +}