Module: Lich::Gemstone::CritRanks

Defined in:
documented/gemstone/critranks.rb

Overview

Resolves critical hits from combat messages into their mechanical results.

Queries critical hit tables loaded from lib/crit_tables/*.rb files and provides methods to parse incoming damage text, look up critical effects by type/location/rank, and reload table data on demand.

See Also:

  • #parse
  • #fetch

Class Method Summary collapse

Class Method Details

.clean_key(key) ⇒ Integer, ...

Normalizes a key for lookup: integers stay as-is; symbols and strings are downcased and whitespace/hyphens converted to underscores.

Examples:

clean_key("Head") #=> "head"
clean_key("Left Arm") #=> "left_arm"
clean_key(42) #=> 42

Parameters:

  • key (Integer, Symbol, String)

    the raw key

Returns:

  • (Integer, String, Symbol)

    the cleaned key in normalized form



107
108
109
110
111
112
# File 'documented/gemstone/critranks.rb', line 107

def self.clean_key(key)
  return key.to_i if key.is_a?(Integer) || key =~ (/^\d+$/)
  return key.downcase if key.is_a?(Symbol)

  key.strip.downcase.gsub(/[ -]/, '_')
end

.create_indicesObject

Builds the pattern indices. Tolerates nil location/rank rows in the table data (some tables carry explicit nil placeholders); a nil row means that crit is simply unrecognized rather than a load failure.

Patterns are indexed by the leading literal word of their (anchored) regex, so parse only has to test the handful of patterns that could possibly match a given line instead of all ~2400. Patterns that are not ^-anchored to a literal word are kept in a small residual list that is always checked.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'documented/gemstone/critranks.rb', line 141

def self.create_indices
  @index_buckets = Hash.new { |hash, key| hash[key] = [] }
  @index_residual = []
  @critical_table.each do |type, typedata|
    @types.append(type)
    typedata.each do |loc, locdata|
      @locations.append(loc) unless @locations.include?(loc)
      next if locdata.nil?
      locdata.each do |rank, record|
        @ranks.append(rank) unless @ranks.include?(rank)
        next if record.nil? || record[:regex].nil?
        source = record[:regex].source
        if source.start_with?('^') && (first_word = source[1..].match(/\A([A-Za-z']+)\b/))
          @index_buckets[first_word[1].downcase].push(record)
        else
          @index_residual.push(record)
        end
      end
    end
  end
  @index_buckets.each_value(&:freeze)
  @index_buckets.default = nil # drop the auto-vivifying default proc
  @index_buckets.freeze
  @index_residual.freeze
end

.fetch(type, location, rank) ⇒ Hash?

Looks up a critical hit record by type, location, and rank.

Validates all three arguments, then digs into the critical table. On any error, logs the error and returns nil.

Examples:

fetch("slash", "head", "moderate") #=> { regex: /.../, ...

Parameters:

  • type (Integer, Symbol, String)

    the critical type (e.g., "slash")

  • location (Integer, Symbol, String)

    the body location (e.g., "head")

  • rank (Integer, Symbol, String)

    the severity rank (e.g., "moderate")

Returns:

  • (Hash, nil)

    the critical record, or nil if not found or validation fails



197
198
199
200
201
202
203
204
205
# File 'documented/gemstone/critranks.rb', line 197

def self.fetch(type, location, rank)
  table.dig(
    validate(type, types),
    validate(location, locations),
    validate(rank, ranks)
  )
rescue StandardError => e
  Lich::Messaging.msg('error', "Error! #{e}")
end

.initvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Loads all critical hit table files from the critranks directory.

This is a no-op if tables are already loaded. Automatically called at module load time and may be called again after .reload!. Uses load instead of require to ensure reload can re-run the table definitions.



37
38
39
40
41
42
43
44
45
# File 'documented/gemstone/critranks.rb', line 37

def self.init
  return unless @critical_table.empty?
  Dir.glob("#{File.join(LIB_DIR, "gemstone", "critranks", "*critical_table.rb")}").each do |file|
    # load, not require: require makes reload! a no-op (already-required
    # table files never re-run, leaving the emptied table empty forever)
    load file
  end
  create_indices
end

.locationsArray

Returns a list of all body locations in the loaded tables.

Returns:

  • (Array)

    the location keys (e.g., :head, :chest, :limb)



87
88
89
# File 'documented/gemstone/critranks.rb', line 87

def self.locations
  @locations
end

.parse(line) ⇒ Hash

Matches a damage line against all loaded critical patterns and returns matching records.

Extracts the leading word from the line to index into a bucket of candidate patterns, then tests all candidates (plus residual non-anchored patterns) for a match. Returns a hash mapping matching Regexp objects to their record definitions.

Examples:

parse("Your slash wounds the creature!") #=> {/Your slash.../=>{...}, ...}

Parameters:

  • line (String)

    a raw damage message from the combat feed

Returns:

  • (Hash)

    a hash of => record_hash for all patterns that matched the line



177
178
179
180
181
182
183
184
# File 'documented/gemstone/critranks.rb', line 177

def self.parse(line)
  stripped = line.strip # need to strip spaces to support anchored regex in tables
  first_word = stripped[/\A[A-Za-z']+/]&.downcase
  candidates = @index_buckets.fetch(first_word, nil) ? @index_buckets[first_word] + @index_residual : @index_residual
  candidates.each_with_object({}) do |record, matches|
    matches[record[:regex]] = record if record[:regex] =~ stripped
  end
end

.ranksArray

Returns a list of all critical severity ranks in the loaded tables.

Returns:

  • (Array)

    the rank keys (e.g., :light, :moderate, :serious)



94
95
96
# File 'documented/gemstone/critranks.rb', line 94

def self.ranks
  @ranks
end

.reload!void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Clears all loaded critical hit tables and reloads them from disk.



58
59
60
61
62
63
64
# File 'documented/gemstone/critranks.rb', line 58

def self.reload!
  @critical_table = {}
  @types = []
  @locations = []
  @ranks = []
  init
end

.tableHash

Returns the in-memory critical hit table hash.

Returns:

  • (Hash)

    the full critical table, keyed by type > location > rank



50
51
52
# File 'documented/gemstone/critranks.rb', line 50

def self.table
  @critical_table
end

.tablesArray<String>

Returns a list of loaded critical table names, with namespace separators removed.

Returns:

  • (Array<String>)

    table names derived from loaded types



69
70
71
72
73
74
75
# File 'documented/gemstone/critranks.rb', line 69

def self.tables
  @tables = []
  @types.each do |type|
    @tables.push(type.to_s.gsub(':', ''))
  end
  @tables
end

.typesArray

Returns a list of all critical hit types in the loaded tables.

Returns:

  • (Array)

    the type keys (e.g., critical types defined in table files)



80
81
82
# File 'documented/gemstone/critranks.rb', line 80

def self.types
  @types
end

.validate(key, valid) ⇒ Integer, ...

Normalizes a key and checks it against a list of valid options.

Raises an exception if the cleaned key is not found in the valid list.

Examples:

validate(:head, [:head, :chest]) #=> :head
validate("head", [:head, :chest]) #=> :head

Parameters:

  • key (Integer, Symbol, String)

    the raw key to validate

  • valid (Array)

    the list of acceptable (already-cleaned) keys

Returns:

  • (Integer, String, Symbol)

    the cleaned key

Raises:

  • (RuntimeError)

    if the cleaned key is not in the valid list



125
126
127
128
129
130
# File 'documented/gemstone/critranks.rb', line 125

def self.validate(key, valid)
  clean = clean_key(key)
  raise "Invalid key '#{key}', expecting one of #{valid.join(',')}" unless valid.include?(clean)

  clean
end