1414import static com .google .common .base .Preconditions .checkArgument ;
1515
1616/**
17- * A model representing a skill within a skill set.
17+ * Represents a single skill within a {@link SkillSet}, tracking both the <b>static</b> (experience-based) level and
18+ * the <b>dynamic</b> (temporarily modified) level.
19+ *
20+ * <h2>Static vs dynamic levels</h2>
21+ * <ul>
22+ * <li><b>Static level</b> – the level derived from total {@link #experience}. This is what players “really” are,
23+ * and is recalculated via {@link SkillSet#levelForExperience(int)}. This value is cached in {@link #staticLevel}
24+ * and invalidated whenever experience changes.</li>
25+ * <li><b>Dynamic level</b> – the current, temporary level shown/used by game logic when boosts or drains apply
26+ * (potions, prayers, debuffs, etc). This is stored in {@link #level} and may differ from the static level.</li>
27+ * </ul>
28+ *
29+ * <h2>Events and restoration</h2>
30+ * <ul>
31+ * <li>Whenever experience or the dynamic level changes, a {@link SkillChangeEvent} may be posted if the owning
32+ * {@link SkillSet} is configured to fire events (see {@link SkillSet#isFiringEvents()}).</li>
33+ * <li>If the dynamic level changes away from the static level, a {@link SkillRestorationTask} may be scheduled
34+ * to gradually restore skills back toward their static level (see {@link SkillSet#isRestoring()}).</li>
35+ * </ul>
1836 *
1937 * @author lare96
2038 */
2139public final class Skill {
2240
2341 /**
24- * An immutable list of the names of all skills .
42+ * Ordered, immutable list of all skill names. The index into this list is the skill identifier .
2543 */
2644 public static final ImmutableList <String > NAMES = ImmutableList .of (
2745 "Attack" , "Defence" , "Strength" , "Hitpoints" , "Ranged" , "Prayer" , "Magic" ,
@@ -30,17 +48,23 @@ public final class Skill {
3048 );
3149
3250 /**
33- * The combat skill identifiers.
51+ * The range of skill identifiers considered in combat level calculations .
3452 */
3553 public static final Range <Integer > COMBAT_IDS = Range .closed (0 , 6 );
3654
3755 /**
38- * An immutable map of names to identifiers.
56+ * Maps skill name -> skill identifier.
57+ * <p>
58+ * Built from {@link #NAMES} in the static initializer.
59+ * </p>
3960 */
4061 public static final ImmutableMap <String , Integer > NAME_TO_ID ;
4162
4263 /**
43- * An immutable list of all identifiers.
64+ * Immutable list of all skill identifiers.
65+ * <p>
66+ * This is effectively {@code 0..NAMES.size()-1} but derived from {@link #NAME_TO_ID} for convenience.
67+ * </p>
4468 */
4569 public static final ImmutableList <Integer > IDS ;
4670
@@ -150,30 +174,36 @@ public final class Skill {
150174 public static final int RUNECRAFTING = 20 ;
151175
152176 /**
153- * Retrieves the name of a skill by its identifier .
177+ * Retrieves the skill name for {@code id} .
154178 *
155- * @param id The identifier.
179+ * @param id The skill identifier (0-based) .
156180 * @return The skill name.
181+ * @throws IndexOutOfBoundsException if {@code id} is not a valid skill identifier.
157182 */
158183 public static String getName (int id ) {
159184 return NAMES .get (id );
160185 }
161186
162187 /**
163- * Retrieves the identifier of a skill by its name.
188+ * Retrieves the skill identifier for {@code name}, or {@code -1} if none were found.
189+ * <p>
190+ * Matching is exact and case-sensitive.
191+ * </p>
164192 *
165- * @param name The skill name.
166- * @return The identifier.
193+ * @param name The skill name (must match an entry in {@link #NAMES}).
194+ * @return The skill identifier, {@code -1} if none were found.
195+ * @throws NullPointerException if {@code name} is {@code null}.
167196 */
168197 public static int getId (String name ) {
169- return NAME_TO_ID .get (name );
198+ Integer id = NAME_TO_ID .get (name );
199+ return id == null ? -1 : id ;
170200 }
171201
172202 /**
173- * Determines if a skill is a factor in combat level calculations .
203+ * Determines whether {@code id} is a combat skill .
174204 *
175205 * @param id The skill identifier.
176- * @return {@code true} if the identifier is a combat skill .
206+ * @return {@code true} if {@code id} is in {@link #COMBAT_IDS} .
177207 */
178208 public static boolean isCombatSkill (int id ) {
179209 return COMBAT_IDS .contains (id );
@@ -187,35 +217,50 @@ public static boolean isCombatSkill(int id) {
187217 }
188218
189219 /**
190- * The skill set.
220+ * The owning skill set.
191221 */
192222 private transient final SkillSet set ;
193223
194224 /**
195- * The skill identifier.
225+ * The skill identifier (0-based index into {@link #NAMES}) .
196226 */
197227 private transient final int id ;
198228
199229 /**
200- * The static (experience based) skill level. Cached to avoid potentially expensive {@link SkillSet#levelForExperience(int)} calls.
230+ * Cached static (experience-based) level.
231+ * <p>
232+ * This is computed lazily from {@link #experience} and cached to avoid repeated {@link SkillSet#levelForExperience(int)}
233+ * calls. It is invalidated by setting {@code staticLevel = -1} whenever experience changes.
234+ * </p>
201235 */
202236 private transient int staticLevel = -1 ;
203237
204238 /**
205- * The dynamic skill level.
239+ * The dynamic (temporarily modified) level.
240+ * <p>
241+ * This may be higher or lower than the static level due to buffs/drains. Restoration may move this back
242+ * toward {@link #getStaticLevel()} over time.
243+ * </p>
206244 */
207245 private int level = 1 ;
208246
209247 /**
210- * The attained experience.
248+ * Total accumulated experience for this skill.
249+ * <p>
250+ * Bounded to {@code [0, SkillSet.MAXIMUM_EXPERIENCE]} by {@link #setExperience(double)}.
251+ * </p>
211252 */
212253 private double experience ;
213254
214255 /**
215256 * Creates a new {@link Skill}.
257+ * <p>
258+ * Hitpoints starts at level 10 with 1300 experience (classic RuneScape default), while other skills start at
259+ * level 1 with 0 experience.
260+ * </p>
216261 *
217262 * @param id The skill identifier.
218- * @param set The skill set.
263+ * @param set The owning skill set.
219264 */
220265 public Skill (int id , SkillSet set ) {
221266 this .id = id ;
@@ -233,11 +278,18 @@ public String toString() {
233278 }
234279
235280 /**
236- * Restores depleted or buffed skills.
281+ * Schedules restoration if the dynamic level differs from the static level and restoration is enabled.
282+ * <p>
283+ * This method is intentionally conservative: it only schedules restoration when:
284+ * </p>
285+ * <ul>
286+ * <li>The {@link SkillSet} is not currently restoring ({@link SkillSet#isRestoring()} is {@code false}).</li>
287+ * <li>The dynamic {@link #level} differs from {@link #staticLevel}.</li>
288+ * </ul>
237289 */
238290 private void restoreSkills () {
239291 if (!set .isRestoring ()) {
240- if (level != staticLevel ) {
292+ if (level != getStaticLevel () ) {
241293 var world = set .getMob ().getWorld ();
242294 world .schedule (new SkillRestorationTask (set ));
243295 }
@@ -246,8 +298,13 @@ private void restoreSkills() {
246298
247299 /**
248300 * Adds experience to this skill.
301+ * <p>
302+ * For non-bot mobs, experience is multiplied by the configured game experience multiplier. Bots receive experience
303+ * at a fixed multiplier of {@code 1.0} (no bonus).
304+ * </p>
249305 *
250- * @param amount The amount of experience to add.
306+ * @param amount The raw amount of experience to add.
307+ * @throws IllegalArgumentException if {@code amount <= 0}.
251308 */
252309 public void addExperience (double amount ) {
253310 checkArgument (amount > 0 , "amount <= 0" );
@@ -256,11 +313,14 @@ public void addExperience(double amount) {
256313 }
257314
258315 /**
259- * Notifies plugins of any level or experience changes.
316+ * Posts a {@link SkillChangeEvent} to plugins if event firing is enabled.
317+ * <p>
318+ * The event includes the previous values so listeners can compute deltas or react to level-ups.
319+ * </p>
260320 *
261- * @param oldExperience The old experience amount .
262- * @param oldStaticLevel The old static level.
263- * @param oldLevel The old dynamic level.
321+ * @param oldExperience The previous experience value .
322+ * @param oldStaticLevel The previous static level.
323+ * @param oldLevel The previous dynamic level.
264324 */
265325 private void notifyListeners (double oldExperience , int oldStaticLevel , int oldLevel ) {
266326 if (set .isFiringEvents ()) {
@@ -271,21 +331,30 @@ private void notifyListeners(double oldExperience, int oldStaticLevel, int oldLe
271331 }
272332
273333 /**
274- * @return The name of this skill.
334+ * Returns the name of this skill.
335+ *
336+ * @return The skill name (from {@link #NAMES}).
275337 */
276338 public String getName () {
277339 return NAMES .get (id );
278340 }
279341
280342 /**
343+ * Returns the identifier of this skill.
344+ *
281345 * @return The skill identifier.
282346 */
283347 public int getId () {
284348 return id ;
285349 }
286350
287351 /**
288- * @return The static (experience based) skill level.
352+ * Returns the static (experience-based) skill level.
353+ * <p>
354+ * This is computed lazily and cached. The cache is invalidated whenever experience changes.
355+ * </p>
356+ *
357+ * @return The experience-based level.
289358 */
290359 public int getStaticLevel () {
291360 if (staticLevel == -1 ) {
@@ -295,22 +364,33 @@ public int getStaticLevel() {
295364 return staticLevel ;
296365 }
297366
367+ /**
368+ * Sets the static (experience-based) level by converting the desired level into experience.
369+ *
370+ * @param level The target static level.
371+ */
298372 public void setStaticLevel (int level ) {
299373 setExperience (SkillSet .experienceForLevel (level ));
300-
374+ staticLevel = level ;
301375 }
302376
303377 /**
304- * @return The dynamic skill level.
378+ * Returns the dynamic (temporarily modified) skill level.
379+ *
380+ * @return The current dynamic level.
305381 */
306382 public int getLevel () {
307383 return level ;
308384 }
309385
310386 /**
311- * Sets the dynamic skill level.
387+ * Sets the dynamic (temporarily modified) skill level.
388+ * <p>
389+ * The level is clamped to a minimum of {@code 0}. If the value does not change, no events are fired and restoration
390+ * is not scheduled.
391+ * </p>
312392 *
313- * @param newLevel The new level.
393+ * @param newLevel The new dynamic level.
314394 */
315395 public void setLevel (int newLevel ) {
316396 if (newLevel < 0 ) {
@@ -329,10 +409,14 @@ public void setLevel(int newLevel) {
329409
330410 /**
331411 * Increases the dynamic skill level by {@code amount}.
412+ * <p>
413+ * If {@code exceedStaticLevel} is {@code false}, the result is capped at {@link #getStaticLevel()}.
414+ * If {@code exceedStaticLevel} is {@code true}, the result is capped at {@code getStaticLevel() + amount}.
415+ * </p>
332416 *
333417 * @param amount The amount to increase by.
334- * @param exceedStaticLevel If the bound should be set higher than the static level, or at the
335- * static level.
418+ * @param exceedStaticLevel If {@code true}, allow the boost above the static level (up to {@code static + amount});
419+ * otherwise cap at the static level.
336420 */
337421 public void addLevels (int amount , boolean exceedStaticLevel ) {
338422 int bound = exceedStaticLevel ? getStaticLevel () + amount : getStaticLevel ();
@@ -344,6 +428,11 @@ public void addLevels(int amount, boolean exceedStaticLevel) {
344428
345429 /**
346430 * Decreases the dynamic skill level by {@code amount}.
431+ * <p>
432+ * The result is clamped to a minimum of {@code 0}.
433+ * </p>
434+ *
435+ * @param amount The amount to remove.
347436 */
348437 public void removeLevels (int amount ) {
349438 int newAmount = getLevel () - amount ;
@@ -352,14 +441,22 @@ public void removeLevels(int amount) {
352441 }
353442
354443 /**
355- * @return The attained experience.
444+ * Returns total accumulated experience.
445+ *
446+ * @return The experience value.
356447 */
357448 public double getExperience () {
358449 return experience ;
359450 }
360451
361452 /**
362- * Sets the attained experience.
453+ * Sets total accumulated experience for this skill.
454+ * <p>
455+ * The value is clamped into {@code [0, SkillSet.MAXIMUM_EXPERIENCE]}. If the value does not change, nothing happens.
456+ * </p>
457+ * <p>
458+ * If the value changes, the cached static level is invalidated and listeners may be notified.
459+ * </p>
363460 *
364461 * @param newExperience The new experience value.
365462 */
0 commit comments