Class: Lich::DragonRealms::DRSkill

Inherits:
Object
  • Object
show all
Defined in:
documented/dragonrealms/drinfomon/drskill.rb

Overview

Represents a trainable character skill in DragonRealms and tracks its rank, experience mindstate, and learning progress.

The class maintains a shared registry (@@list) of all known skills and supports session-based tracking of skill gain via baseline/current mechanics. Capped skills (rank >= 1750) are fixed at 34/34 mindstate per game convention.

See Also:

  • DRStats.getxp
  • DRStats.getrank

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, rank, exp, percent) ⇒ void

Initializes a skill instance and registers it in the shared skill list.

Capped skills (rank >= 1750) automatically set exp to 34 to enforce the "capped == 34/34" convention that trainer scripts rely upon. Baseline and current are set to the same value: rank + (percent / 100.0).

Examples:

DRSkill.new("Evasion", 100, 15, 50)

Parameters:

  • name (String) —

    the skill name, e.g. "Evasion"

  • rank (Integer) —

    earned ranks in the skill

  • exp (Integer) —

    mindstate (x/34) of the skill; capped skills become 34

  • percent (Integer) —

    percent to next rank (0–100)



45
46
47
48
49
50
51
52
53
54
55
56
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 45

def initialize(name, rank, exp, percent)
  @name = name # skill name like 'Evasion'
  @rank = rank.to_i # earned ranks in the skill
  # Skill mindstate x/34
  # Hardcode caped skills to 34/34
  @exp = rank.to_i >= 1750 ? 34 : exp.to_i
  @percent = percent.to_i # percent to next rank from 0 to 100
  @baseline = rank.to_i + (percent.to_i / 100.0)
  @current = rank.to_i + (percent.to_i / 100.0)
  @skillset = lookup_skillset(@name)
  @@list.push(self) unless @@list.find { |skill| skill.name == @name }
end

Instance Attribute Details

#baseline ⇒ Object

Returns the value of attribute baseline.



30
31
32
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 30

def baseline
  @baseline
end

#current ⇒ Object

Returns the value of attribute current.



30
31
32
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 30

def current
  @current
end

#exp ⇒ Object

Returns the value of attribute exp.



30
31
32
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 30

def exp
  @exp
end

#name ⇒ Object (readonly)

Returns the value of attribute name.



29
30
31
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 29

def name
  @name
end

#percent ⇒ Object

Returns the value of attribute percent.



30
31
32
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 30

def percent
  @percent
end

#rank ⇒ Object

Returns the value of attribute rank.



30
31
32
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 30

def rank
  @rank
end

#skillset ⇒ Object (readonly)

Returns the value of attribute skillset.



29
30
31
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 29

def skillset
  @skillset
end

Class Method Details

.clear_mind(val) ⇒ Object

BUG FIX: Added nil guard - find_skill can return nil if skill not found

Capped skills (rank >= 1750) are pinned to 34/34 the same way initialize and update do it. Without the cap guard, clearing the mindstate here would reset exp to 0 and defeat the "capped == 34" convention that trainer scripts rely on to avoid re-training a capped skill.



236
237
238
239
240
241
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 236

def self.clear_mind(val)
  skill = find_skill(val)
  return unless skill

  skill.exp = skill.rank.to_i >= 1750 ? 34 : 0
end

.convert_rexp_str_to_seconds(time_string) ⇒ Integer

Converts a rested experience time string to total seconds.

Parses formats like "4:38 hours", "6 hour", "38 minutes", and special cases like "none" or "less than a minute" (both return 0). Returns 0 for nil or empty strings. Time is always rounded to minute granularity (divisible by 60).

Examples:

DRSkill.convert_rexp_str_to_seconds("4:38 hours") #=> 16680
DRSkill.convert_rexp_str_to_seconds("38 minutes") #=> 2280
DRSkill.convert_rexp_str_to_seconds("none") #=> 0

Parameters:

  • time_string (String, nil) —

    the time string from the game, e.g. "4:38 hours"

Returns:

  • (Integer) —

    total seconds, always divisible by 60, or 0 for empty/nil/special cases



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 352

def self.convert_rexp_str_to_seconds(time_string)
  # Handle empty, nil, or specific "zero" cases (less than a minute is zero because it can get stuck there)
  return 0 if time_string.nil? ||
              time_string.to_s.strip.empty? ||
              time_string.include?('none') ||
              time_string.include?('less than a minute')

  total_seconds = 0

  # Extract hours and optional minutes (e.g., "4:38 hours" or "6 hour")
  # Ruby's match returns a MatchData object or nil
  if (hour_match = time_string.match(/(\d+):?(\d+)?\s*hour/))
    hours = hour_match[1].to_i
    total_seconds += hours * 60 * 60

    # Handle the minutes part of a "4:38" format
    if hour_match[2]
      total_seconds += hour_match[2].to_i * 60
      return total_seconds
    end
  end

  # Extract standalone minutes (e.g., "38 minutes")
  if (minute_match = time_string.match(/(\d+)\s*minute/))
    total_seconds += minute_match[1].to_i * 60
  end

  total_seconds
end

.exp_modifiers ⇒ Hash<String, Integer>

Returns the hash of skill rank modifiers (e.g. from buffs or gear).

Examples:

DRSkill.exp_modifiers #=> {"Evasion" => 5, "Parry Ability" => 3}

Returns:

  • (Hash<String, Integer>) —

    a hash mapping canonical skill names to modifier values



189
190
191
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 189

def self.exp_modifiers
  @@exp_modifiers
end

.find_skill(val) ⇒ DRSkill?

Finds a skill in the registry by name (canonical or alias).

Resolves guild-specific skill name aliases before searching. Returns nil if the skill is not found.

Examples:

skill = DRSkill.find_skill("Evasion")
skill.rank #=> 100

Parameters:

  • val (String) —

    the skill name (e.g. "Evasion" or "Inner Fire" for Barbarians)

Returns:

  • (DRSkill, nil) —

    the skill instance, or nil if not found



336
337
338
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 336

def self.find_skill(val)
  @@list.find { |data| data.name == lookup_alias(val) }
end

.gained_exp(val) ⇒ Object

Returns the amount of ranks that have been gained since the baseline was last reset. This allows you to track rank gain for a given play session.

Note, don't confuse the 'exp' in this method name with DRSkill.getxp(..) which returns the current learning rate of the skill.



92
93
94
95
96
97
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 92

def self.gained_exp(val)
  skill = find_skill(val)
  return 0.00 unless skill&.current

  (skill.current - skill.baseline).round(2)
end

.gained_skills ⇒ Object

List of skills that have increased their learning rates. Primarily used by exp-monitor script to echo which skills gained experience after you performed an action.



82
83
84
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 82

def self.gained_skills
  @@gained_skills
end

.getmodrank(val) ⇒ Integer

Returns the trained rank of a skill plus any applied modifiers (buffs, gear, etc.).

Returns 0 if the skill does not exist or no modifier is recorded. Modifiers are set via .update_mods and typically come from active buffs or equipment bonuses.

Examples:

DRSkill.getmodrank("Evasion") #=> 105  # rank 100 + modifier 5

Parameters:

  • val (String) —

    the skill name (canonical or alias)

Returns:

  • (Integer) —

    the modified rank (rank + modifier), or 0 if skill is unknown



260
261
262
263
264
265
266
267
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 260

def self.getmodrank(val)
  skill = find_skill(val)
  return 0 unless skill

  rank = skill.rank.to_i
  modifier = exp_modifiers[skill.name].to_i
  rank + modifier
end

.getpercent(val) ⇒ Object

BUG FIX: Added nil guard - find_skill can return nil if skill not found



285
286
287
288
289
290
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 285

def self.getpercent(val)
  skill = find_skill(val)
  return 0 unless skill

  skill.percent.to_i
end

.getrank(val) ⇒ Object

BUG FIX: Added nil guard - find_skill can return nil if skill not found



244
245
246
247
248
249
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 244

def self.getrank(val)
  skill = find_skill(val)
  return 0 unless skill

  skill.rank.to_i
end

.getskillset(val) ⇒ Object

BUG FIX: Added nil guard - find_skill can return nil if skill not found



293
294
295
296
297
298
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 293

def self.getskillset(val)
  skill = find_skill(val)
  return nil unless skill

  skill.skillset
end

.getxp(val) ⇒ Integer

Returns the learning rate mindstate (x/34) of a skill.

Capped skills (rank >= 1750) always return 34. Returns 0 if the skill does not exist.

Examples:

DRSkill.getxp("Evasion") #=> 18

Parameters:

  • val (String) —

    the skill name (canonical or alias)

Returns:

  • (Integer) —

    the mindstate (0–34), or 0 if skill is unknown



277
278
279
280
281
282
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 277

def self.getxp(val)
  skill = find_skill(val)
  return 0 unless skill

  skill.exp.to_i
end

.handle_exp_change(name, new_exp) ⇒ Object

Updates DRStats.gained_skills if the learning rate increased. The original consumer of this data is the exp-monitor script.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 101

def self.handle_exp_change(name, new_exp)
  return unless Lich.display_expgains

  # Only track gains for skills that already exist
  # (skip initial skill discovery on login)
  skill = find_skill(name)
  return unless skill

  old_exp = skill.exp.to_i
  change = new_exp.to_i - old_exp
  if change > 0
    @@gained_skills << { skill: name, change: change }
  end
end

.include?(val) ⇒ Boolean

Checks whether a skill name (canonical or colloquial) is registered.

Examples:

DRSkill.include?("Evasion") #=> true

Parameters:

  • val (String) —

    the skill name to check (e.g. "Evasion" or guild-specific alias)

Returns:

  • (Boolean) —

    true if the skill exists in the registry, false otherwise



122
123
124
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 122

def self.include?(val)
  !find_skill(val).nil?
end

.list ⇒ Array<DRSkill>

Returns the registry of all tracked skills.

Examples:

DRSkill.list.first.name #=> "Evasion"

Returns:

  • (Array<DRSkill>) —

    array of skill instances currently known



322
323
324
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 322

def self.list
  @@list
end

.listall ⇒ void

This method returns an undefined value.

Displays all tracked skills in the game output.

Prints each skill's name, rank, percent, and mindstate in the format: "DRSkill: #name: #rank.#percent% [#exp/34]". Intended for debugging and player inspection.

Examples:

DRSkill.listall
# DRSkill: Evasion: 100.50% [18/34]
# DRSkill: Parry Ability: 95.25% [12/34]


311
312
313
314
315
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 311

def self.listall
  @@list.each do |i|
    Lich::Messaging.msg('plain', "DRSkill: #{i.name}: #{i.rank}.#{i.percent}% [#{i.exp}/34]")
  end
end

.lookup_alias(skill) ⇒ Object

Some guilds rename skills, like Barbarians call "Primary Magic" as "Inner Fire". Given the canonical or colloquial name, this method returns the value that's usable with the other methods like getxp(skill) and getrank(skill).

BUG FIX: Added safe navigation with .dig() for nested hash access. Original code crashed with NoMethodError if DRStats.guild was nil or if the guild wasn't in the aliases hash.



389
390
391
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 389

def self.lookup_alias(skill)
  @@skills_data.dig(:guild_skill_aliases, DRStats.guild, skill) || skill
end

.reset ⇒ void

This method returns an undefined value.

Resets the session baseline for all tracked skills to their current values.

Use this to mark a checkpoint in skill gain tracking, clearing the @@gained_skills list and resetting @@start_time to now. Subsequent calls to .gained_exp will measure rank gain from this new baseline.

Examples:

DRSkill.reset  # Start a new tracking session


67
68
69
70
71
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 67

def self.reset
  @@gained_skills = []
  @@start_time = Time.now
  @@list.each { |skill| skill.baseline = skill.current }
end

.rested_active? ⇒ Boolean

Checks whether rested experience is both stored and available to use.

Examples:

DRSkill.rested_active? #=> true

Returns:

  • (Boolean) —

    true if both stored and usable are greater than 0, false otherwise



225
226
227
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 225

def self.rested_active?
  @@rexp_stored > 0 && @@rexp_usable > 0
end

.rested_exp_refresh ⇒ Integer

Returns the time until rested experience refreshes, in seconds.

Examples:

DRSkill.rested_exp_refresh #=> 43200  # 12 hours

Returns:

  • (Integer) —

    seconds until refresh, always divisible by 60



216
217
218
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 216

def self.rested_exp_refresh
  @@rexp_refresh
end

.rested_exp_stored ⇒ Integer

Returns the total rested experience stored, in seconds.

Examples:

DRSkill.rested_exp_stored #=> 16680  # 4 hours 38 minutes

Returns:

  • (Integer) —

    seconds of rested exp, always divisible by 60



198
199
200
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 198

def self.rested_exp_stored
  @@rexp_stored
end

.rested_exp_usable ⇒ Integer

Returns the rested experience currently available to use, in seconds.

Examples:

DRSkill.rested_exp_usable #=> 8100  # 2 hours 15 minutes

Returns:

  • (Integer) —

    seconds of usable rested exp, always divisible by 60



207
208
209
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 207

def self.rested_exp_usable
  @@rexp_usable
end

.start_time ⇒ Object

Primarily used by learned script to track how long it's been tracking your experience gains this session.



75
76
77
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 75

def self.start_time
  @@start_time
end

.update(name, rank, exp, percent) ⇒ void

This method returns an undefined value.

Updates an existing skill or creates a new one with the latest game state.

If the skill already exists, updates its rank, exp mindstate, percent, and current progress. If it does not exist, creates a new DRSkill. Also triggers .handle_exp_change to track learning rate increases for the exp-monitor script. Capped skills (rank >= 1750) are forced to 34/34 mindstate.

Examples:

DRSkill.update("Evasion", 102, 18, 75)

Parameters:

  • name (String) —

    the skill name

  • rank (Integer) —

    earned ranks

  • exp (Integer) —

    mindstate (x/34); capped skills become 34

  • percent (Integer) —

    percent to next rank (0–100)



140
141
142
143
144
145
146
147
148
149
150
151
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 140

def self.update(name, rank, exp, percent)
  handle_exp_change(name, exp)
  skill = find_skill(name)
  if skill
    skill.rank = rank.to_i
    skill.exp = skill.rank.to_i >= 1750 ? 34 : exp.to_i
    skill.percent = percent.to_i
    skill.current = rank.to_i + (percent.to_i / 100.0)
  else
    DRSkill.new(name, rank, exp, percent)
  end
end

.update_mods(name, rank) ⇒ void

This method returns an undefined value.

Records a skill rank modifier (e.g. from buffs or gear bonuses).

Stores the modifier indexed by the canonical skill name (resolved via .lookup_alias) for retrieval by .getmodrank.

Examples:

DRSkill.update_mods("Evasion", 5)

Parameters:

  • name (String) —

    the skill name (canonical or alias)

  • rank (Integer) —

    the rank modifier value



163
164
165
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 163

def self.update_mods(name, rank)
  exp_modifiers[lookup_alias(name)] = rank.to_i
end

.update_rested_exp(stored, usable, refresh) ⇒ void

This method returns an undefined value.

Updates the rested experience state from the game.

Converts human-readable time strings (e.g. "4:38 hours", "38 minutes") to seconds and stores them in class variables for access via the rested_exp_* methods.

Examples:

DRSkill.update_rested_exp("4:38 hours", "2:15 hours", "12 hours")

Parameters:

  • stored (String) —

    the total rested exp stored, e.g. "4:38 hours"

  • usable (String) —

    the rested exp available to use, e.g. "2:15 hours"

  • refresh (String) —

    the time until rested exp refreshes, e.g. "12 hours"



178
179
180
181
182
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 178

def self.update_rested_exp(stored, usable, refresh)
  @@rexp_stored = convert_rexp_str_to_seconds(stored)
  @@rexp_usable = convert_rexp_str_to_seconds(usable)
  @@rexp_refresh = convert_rexp_str_to_seconds(refresh)
end

Instance Method Details

#lookup_skillset(skill) ⇒ Object

This is an instance method, do not prefix with self. It is called from the initialize method (constructor). When it was defined as a class method then the initialize method complained that this method didn't yet exist.

BUG FIX: Added nil guard for .find result before calling .first



399
400
401
402
# File 'documented/dragonrealms/drinfomon/drskill.rb', line 399

def lookup_skillset(skill)
  result = @@skills_data[:skillsets].find { |_skillset, skills| skills.include?(skill) }
  result&.first
end