Module: Lich::Gemstone::Armor

Defined in:
documented/gemstone/psms/armor.rb

Overview

Provides logic for detecting, checking, and using PSM3 armor techniques in GemStone IV.

This module defines a registry of available armor-related abilities and wraps common queries like whether a technique is known, affordable, or currently usable. It also provides the ‘use` method to execute the appropriate command in-game, handling roundtime and feedback matching.

Techniques are stored in a constant hash, and dynamic methods are defined for both long and short names of each technique.

Examples:

if Armor.available?("armor_blessing")
  Armor.use("armor_blessing")
end

Constant Summary collapse

@@armor_techniques =

Mapping of armor technique identifiers to their associated data, including:

  • short name

  • usage command

  • regex to match expected in-game output

  • cost to use

  • type of technique (buff, passive, etc.)

Returns:

{
  "armor_blessing"      => {
    :short_name => "blessing",
    :type       => :buff,
    :cost       => { stamina: 0 },
    :regex      => /As \w+ prays? over \w+(?:'s)? [\w\s]+, you sense that (?:the Arkati's|a) blessing will be granted against magical attacks\./i,
    :usage      => "blessing"
  },
  "armor_reinforcement" => {
    :short_name => "reinforcement",
    :type       => :buff,
    :cost       => { stamina: 0 },
    :regex      => /\w+ adjusts? \w+(?:'s)? [\w\s]+, reinforcing weak spots\./i,
    :usage      => "reinforcement"
  },
  "armor_spike_mastery" => {
    :short_name => "spikemastery",
    :type       => :passive,
    :cost       => { stamina: 0 },
    :regex      => /Armor Spike Mastery is passive and always active once learned\./i,
    :usage      => "spikemastery"
  },
  "armor_support"       => {
    :short_name => "support",
    :type       => :buff,
    :cost       => { stamina: 0 },
    :regex      => /\w+ adjusts? \w+(?:'s)? [\w\s]+, improving its ability to support the weight of \w+ gear\./i,
    :usage      => "support"
  },
  "armored_casting"     => {
    :short_name => "casting",
    :type       => :buff,
    :cost       => { stamina: 0 },
    :regex      => /\w+ adjusts? \w+(?:'s)? [\w\s]+, making it easier for \w+ to recover from failed spell casting\./i,
    :usage      => "casting"
  },
  "armored_evasion"     => {
    :short_name => "evasion",
    :type       => :buff,
    :cost       => { stamina: 0 },
    :regex      => /\w+ adjusts? \w+(?:'s)? [\w\s]+, improving its comfort and maneuverability\./i,
    :usage      => "evasion"
  },
  "armored_fluidity"    => {
    :short_name => "fluidity",
    :type       => :buff,
    :cost       => { stamina: 0 },
    :regex      => /\w+ adjusts? \w+(?:'s)? [\w\s]+, making it easier for \w+ to cast spells\./i,
    :usage      => "fluidity"
  },
  "armored_stealth"     => {
    :short_name => "stealth",
    :type       => :buff,
    :cost       => { stamina: 0 },
    :regex      => /\w+ adjusts? \w+(?:'s)? [\w\s]+ to cushion \w+ movements\./i,
    :usage      => "stealth"
  },
  "crush_protection"    => {
    :short_name => "crush",
    :type       => :passive,
    :cost       => { stamina: 0 },
    :regex      => Regexp.union(
      /You adjust \w+(?:'s)? [\w\s]+ with your (?:cloth|leather|scale|chain|plate|accessory) armor fittings, rearranging and reinforcing the armor to better protect against crushing damage\./i,
      /You must specify an armor slot\./,
      /You don't seem to have the necessary armor fittings in hand\./
    ),
    :usage      => "crush"
  },
  "puncture_protection" => {
    :short_name => "puncture",
    :type       => :passive,
    :cost       => { stamina: 0 },
    :regex      => Regexp.union(
      /You adjust \w+(?:'s)? [\w\s]+ with your (?:cloth|leather|scale|chain|plate|accessory) armor fittings, rearranging and reinforcing the armor to better protect against puncturing damage\./i,
      /You must specify an armor slot\./,
      /You don't seem to have the necessary armor fittings in hand\./
    ),
    :usage      => "puncture"
  },
  "slash_protection"    => {
    :short_name => "slash",
    :type       => :passive,
    :cost       => { stamina: 0 },
    :regex      => Regexp.union(
      /You adjust \w+(?:'s)? [\w\s]+ with your (?:cloth|leather|scale|chain|plate|accessory) armor fittings, rearranging and reinforcing the armor to better protect against slashing damage\./i,
      /You must specify an armor slot\./,
      /You don't seem to have the necessary armor fittings in hand\./
    ),
    :usage      => "slash"
  }
}

Class Method Summary collapse

Class Method Details

.[](name) ⇒ Integer

Looks up the rank known of an armor technique.

Examples:

Armor["armor_blessing"] => 2
Armor["armor_blessing"] => 0 # if not known

Parameters:

  • name (String)

    The name of the armor technique

Returns:

  • (Integer)

    The rank of the technique, or 0 if unknown



140
141
142
# File 'documented/gemstone/psms/armor.rb', line 140

def Armor.[](name)
  return PSMS.assess(name, 'Armor')
end

.affordable?(name, forcert_count: 0) ⇒ Boolean

Determines if an armor technique is affordable, and optionally tests affordability with a given number of FORCERTs having been used (including the current one).

Examples:

Armor.affordable?("armor_blessing") => true # if enough skill and stamina
Armor.affordable?("armor_blessing", forcert_count: 1) => false  # if not enough skill or stamina

Parameters:

  • name (String)

    The name of the armor technique

  • forcert_count (Integer) (defaults to: 0)

    Optionally, the count of FORCERTs being used, including for this execution (default: 0)

Returns:

  • (Boolean)

    True if the technique can be used with available FORCERTs



167
168
169
# File 'documented/gemstone/psms/armor.rb', line 167

def Armor.affordable?(name, forcert_count: 0)
  return PSMS.assess(name, 'Armor', true, forcert_count: forcert_count)
end

.armor_lookupsArray<Hash>

Returns a simplified array, for lookup purposes, of armor technique hashes with long name, short name, and cost.

Returns:

  • (Array<Hash>)

    An array of hashes with keys :long_name, :short_name, and :cost



123
124
125
126
127
128
129
130
131
# File 'documented/gemstone/psms/armor.rb', line 123

def self.armor_lookups
  @@armor_techniques.map do |long_name, psm|
    {
      long_name: long_name,
      short_name: psm[:short_name],
      cost: psm[:cost]
    }
  end
end

.available?(name, min_rank: 1, forcert_count: 0) ⇒ Boolean

Determines if an armor technique is available to use right now by testing:

  • if the technique is known

  • if the technique is affordable

  • if the technique is not on cooldown

  • if the character is not overexerted

  • if the character is capable of performing the number of FORCERTs specified

blocked by overexertion

Examples:

Armor.available?("armor_blessing") => true # if known, affordable, not on cooldown, and not overexerted

Parameters:

  • name (String)

    The name of the armor technique

  • min_rank (Integer) (defaults to: 1)

    Optionally, the minimum rank to check (default: 1)

  • forcert_count (Integer) (defaults to: 0)

    Optionally, the count of FORCERTs being used (default: 0)

Returns:

  • (Boolean)

    True if the technique is known, affordable, and not on cooldown or



185
186
187
188
189
# File 'documented/gemstone/psms/armor.rb', line 185

def Armor.available?(name, min_rank: 1, forcert_count: 0)
  Armor.known?(name, min_rank: min_rank) &&
    Armor.affordable?(name, forcert_count: forcert_count) &&
    PSMS.available?(name)
end

.buff_active?(name) ⇒ Boolean

Checks whether the technique’s buff is currently active.

Parameters:

  • name (String)

    The technique’s name

Returns:

  • (Boolean)

    True if buff is already active



195
196
197
198
# File 'documented/gemstone/psms/armor.rb', line 195

def Armor.buff_active?(name)
  return unless @@armor_techniques.fetch(PSMS.find_name(name, "Armor")[:long_name]).key?(:buff)
  Effects::Buffs.active?(@@armor_techniques.fetch(PSMS.find_name(name, "Armor")[:long_name])[:buff])
end

.known?(name, min_rank: 1) ⇒ Boolean

Determines if the character knows an armor technique at all, and optionally if the character knows it at the specified rank.

Examples:

Armor.known?("armor_blessing") => true # if any number of ranks is known
Armor.known?("armor_blessing", min_rank: 2) => false # if only rank 1 is known

Parameters:

  • name (String)

    The name of the armor technique

  • min_rank (Integer) (defaults to: 1)

    Optionally, the minimum rank to test against (default: 1, so known)

Returns:

  • (Boolean)

    True if the technique is known at or above the given rank



153
154
155
156
# File 'documented/gemstone/psms/armor.rb', line 153

def Armor.known?(name, min_rank: 1)
  min_rank = 1 unless min_rank >= 1 # in case a 0 or below is passed
  Armor[name] >= min_rank
end

.regexp(name) ⇒ Regexp

Returns the “success” regex associated with a given armor technique name. This regex is used to match the expected output when the technique is successfully attempted. It does not necessarily indicate that the technique was successful in its effect, or even that the technique was executed at all.

Examples:

Armor.regexp("armor_blessing") => /As \w+ prays? over \w+(?:'s)? [\w\s]+, you sense that (?:the Arkati's|a) blessing will be granted against magical attacks\./i

Parameters:

  • name (String)

    The technique name

Returns:

  • (Regexp)

    The regex used to match technique success or effects



264
265
266
# File 'documented/gemstone/psms/armor.rb', line 264

def Armor.regexp(name)
  @@armor_techniques.fetch(PSMS.find_name(name, "Armor")[:long_name])[:regex]
end

.use(name, target = "", results_of_interest: nil, forcert_count: 0) ⇒ String?

Attempts to use an armor technique, optionally on a target.

Examples:

Armor.use("armor_blessing") # attempt to use armor blessing on self
Armor.use("armor_blessing", "Dissonance") # attempt to use armor blessing on Dissonance

Parameters:

  • name (String)

    The name of the armor technique

  • target (String, Integer, GameObj) (defaults to: "")

    The target of the technique (optional). If unspecified, the technique will be used on the character.

  • results_of_interest (Regexp, nil) (defaults to: nil)

    Additional regex to capture from result (optional)

  • forcert_count (Integer) (defaults to: 0)

    Number of FORCERTs to use (default: 0)

Returns:

  • (String, nil)

    The result of the regex match, or nil if unavailable



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'documented/gemstone/psms/armor.rb', line 210

def Armor.use(name, target = "", results_of_interest: nil, forcert_count: 0)
  return unless Armor.available?(name, forcert_count: forcert_count)

  name_normalized = PSMS.name_normal(name)
  technique = @@armor_techniques.fetch(PSMS.find_name(name_normalized, "Armor")[:long_name])
  usage = technique[:usage]
  return if usage.nil?

  in_cooldown_regex = /^#{name} is still in cooldown\./i

  results_regex = Regexp.union(
    PSMS::FAILURES_REGEXES,
    /^#{name} what\?$/i,
    in_cooldown_regex,
    technique[:regex],
    /^Roundtime: [0-9]+ sec\.$/,
    /^\w+ [a-z]+ not wearing any armor that you can work with\.$/
  )

  results_regex = Regexp.union(results_regex, results_of_interest) if results_of_interest.is_a?(Regexp)

  usage_cmd = "armor #{usage}"
  if target.is_a?(GameObj)
    usage_cmd += " ##{target.id}"
  elsif target.is_a?(Integer)
    usage_cmd += " ##{target}"
  elsif target != ""
    usage_cmd += " #{target}"
  end

  if forcert_count > 0
    usage_cmd += " forcert"
  else # if we're using forcert, we don't want to wait for rt, but we need to otherwise
    waitrt?
    waitcastrt?
  end

  usage_result = dothistimeout usage_cmd, 5, results_regex
  if usage_result == "You don't seem to be able to move to do that."
    100.times { break if clear.any? { |line| line =~ /^You regain control of your senses!$/ }; sleep 0.1 }
    usage_result = dothistimeout usage_cmd, 5, results_regex
  end
  usage_result
end