Module: Lich::Gemstone::Feat
- Defined in:
- documented/gemstone/psms/feat.rb
Overview
Provides access to Feats learned by a character in GemStone IV.
This module supports checking feat knowledge, availability, and cost (including FORCERT use), as well as triggering their usage. Each feat is described in a metadata hash which includes:
-
long and short names
-
cost
-
expected in-game output (regex)
-
optional usage string for execution
Dynamic accessor methods are created for each feat’s long and short name.
Constant Summary collapse
- @@feats =
A registry of all known feats and their properties.
{ "absorb_magic" => { :short_name => "absorbmagic", :type => :buff, :cost => { stamina: 0 }, :regex => Regexp.union(/You open yourself to the ravenous void at the core of your being, allowing it to surface\. Muted veins of metallic grey ripple just beneath your skin\./, /You strain, but the void within remains stubbornly out of reach\. You need more time\./), :usage => "absorbmagic" }, "chain_armor_proficiency" => { :short_name => "chainarmor", :type => :passive, :cost => { stamina: 0 }, :regex => /Chain Armor Proficiency is passive and cannot be activated\./, :usage => nil }, "chastise" => { :short_name => "chastise", :type => :attack, :cost => { stamina: 10 }, :regex => /as you lunge at .+? in a quick and vicious strike!$/, :usage => "chastise" }, "combat_mastery" => { :short_name => "combatmastery", :type => :passive, :cost => { stamina: 0 }, :regex => /Combat Mastery is passive and cannot be activated\./, :usage => nil }, "covert_art_escape_artist" => { :short_name => "escapeartist", :type => :reaction, :cost => { stamina: 0 }, :regex => Regexp.union(/You roll your shoulders and subtly test the flexion of your joints, staying limber for ready escapes\./, /You were unable to find any targets that meet Covert Art\: Escape Artist's reaction requirements\./), :usage => "escapeartist" }, "covert_art_keen_eye" => { :short_name => "keeneye", :type => :passive, :cost => { stamina: 0 }, :regex => /Covert Art\: Keen Eye is always active as long as you stay up to date on training\./, :usage => nil }, "covert_art_poisoncraft" => { :short_name => "poisoncraft", :type => :passive, :cost => { stamina: 0 }, :regex => /USAGE\: FEAT POISONCRAFT \{options\} \[args\]/, :usage => nil }, "covert_art_sidestep" => { :short_name => "sidestep", :type => :buff, :cost => { stamina: 10 }, :regex => /You tread lightly and keep your head on a swivel, prepared to sidestep any loose salvos that might stray your way\./, :usage => "sidestep" }, "covert_art_swift_recovery" => { :short_name => "swiftrecovery", :type => :passive, :cost => { stamina: 0 }, :regex => /Covert Art\: Swift Recovery is always active as long as you stay up to date on training\./, :usage => nil }, "covert_art_throw_poison" => { :short_name => "throwpoison", :type => :attack, :cost => { stamina: 15 }, :regex => Regexp.union(/What did the .+ ever do to you\?/, /You pop the cork on .+ and, with a nimble flick of the wrist, fling a portion of its contents in a wide arc\!/, /Covert Art\: Throw Poison what\?/), :usage => "throwpoison" }, "covert_arts" => { :short_name => "covert", :type => :passive, :cost => { stamina: 0 }, :regex => /USAGE\: FEAT COVERT \{options\} \[args\]/, :usage => nil }, "critical_counter" => { :short_name => "criticalcounter", :type => :passive, :cost => { stamina: 0 }, :regex => /Critical Counter is passive and cannot be activated\./, :usage => nil }, "dispel_magic" => { :short_name => "dispelmagic", :type => :buff, :cost => { stamina: 30 }, :regex => Regexp.union(/You reach for the emptiness within, but there are no spells afflicting you to dispel\./, /You reach for the emptiness within\. A single, hollow note reverberates through your core, resonating outward and scouring away the energies that cling to you\./), :usage => "dispelmagic" }, "dragonscale_skin" => { :short_name => "dragonscaleskin", :type => :passive, :cost => { stamina: 0 }, :regex => /The Dragonscale Skin\s{1,2}feat is always active once you have learned it\./, :usage => nil }, "excoriate" => { :short_name => "excoriate", :type => :attack, :cost => { mana: 10 }, :regex => /You level your .+? at .+? and call down the excoriating power of .+? to smite (?:him|her|it)!/, :usage => "excoriate" }, "guard" => { :short_name => "guard", :type => :passive, :cost => { stamina: 0 }, :regex => Regexp.union(/Guard what\?/, /You move over to .+ and prepare to guard [a-z]+ from attack\./, /You stop guarding .+, move over to .+, and prepare to guard [a-z]+ from attack\./, /You stop protecting .+, move over to .+, and prepare to guard [a-z]+ from attack\./, /You stop protecting .+ and prepare to guard [a-z]+ instead\./, /You are already guarding .+\./), :usage => "guard" }, "kroderine_soul" => { :short_name => "kroderinesoul", :type => :passive, :cost => { stamina: 0 }, :regex => /Kroderine Soul is passive and always active as long as you do not learn any spells\. You have access to two new abilities\:/, :usage => nil }, "light_armor_proficiency" => { :short_name => "lightarmor", :type => :passive, :cost => { stamina: 0 }, :regex => /Light Armor Proficiency is passive and cannot be activated\./, :usage => nil }, "martial_arts_mastery" => { :short_name => "martialarts", :type => :passive, :cost => { stamina: 0 }, :regex => /Martial Arts Mastery is passive and cannot be activated\./, :usage => nil }, "martial_mastery" => { :short_name => "martialmastery", :type => :passive, :cost => { stamina: 0 }, :regex => /Martial Mastery is passive and cannot be activated\./, :usage => nil }, "mental_acuity" => { :short_name => "mentalacuity", :type => :passive, :cost => { stamina: 0 }, :regex => /The Mental Acuity\s{1,2}feat is always active once you have learned it\./, :usage => nil }, "mystic_strike" => { :short_name => "mysticstrike", :type => :buff, :cost => { stamina: 10 }, :regex => /You prepare yourself to deliver a Mystic Strike with your next attack\./, :usage => "mysticstrike" }, "mystic_tattoo" => { :short_name => "tattoo", :type => :passive, :cost => { stamina: 0 }, :regex => /Usage\:/, :usage => nil }, "perfect_self" => { :short_name => "perfectself", :type => :passive, :cost => { stamina: 0 }, :regex => /The Perfect Self feat is always active once you have learned it\. It provides a constant enhancive bonus to all your stats\./, :usage => nil }, "plate_armor_proficiency" => { :short_name => "platearmor", :type => :passive, :cost => { stamina: 0 }, :regex => /Plate Armor Proficiency is passive and cannot be activated\./, :usage => nil }, "protect" => { :short_name => "protect", :type => :passive, :cost => { stamina: 0 }, :regex => Regexp.union(/Protect what\?/, /You move over to .+ and prepare to protect [a-z]+ from attack\./, /You stop protecting .+, move over to .+, and prepare to protect [a-z]+ from attack\./, /You stop guarding .+, move over to .+, and prepare to protect [a-z]+ from attack\./, /You stop guarding .+ and prepare to protect [a-z]+ instead\./, /You are already protecting .+\./), :usage => "protect" }, "scale_armor_proficiency" => { :short_name => "scalearmor", :type => :passive, :cost => { stamina: 0 }, :regex => /Scale Armor Proficiency is passive and cannot be activated\./, :usage => nil }, "shadow_dance" => { :short_name => "shadowdance", :type => :buff, :cost => { stamina: 30 }, :regex => /You focus your mind and body on the shadows\./, :usage => nil }, "silent_strike" => { :short_name => "silentstrike", :type => :attack, :cost => { stamina: 20 }, :regex => Regexp.union(/Silent Strike can not be used with fire as the attack type\./, /You quickly leap from hiding to attack\!/), :usage => "silentstrike" }, "vanish" => { :short_name => "vanish", :type => :buff, :cost => { stamina: 30 }, :regex => /With subtlety and speed, you aim to clandestinely vanish into the shadows\./, :usage => "vanish" }, "weapon_bonding" => { :short_name => "weaponbonding", :type => :passive, :cost => { stamina: 0 }, :regex => /USAGE\:/, :usage => nil }, "weighting" => { :short_name => "wps", :type => :passive, :cost => { stamina: 0 }, :regex => /USAGE\: FEAT WPS \{options\} \[args\]/, :usage => nil }, "padding" => { :short_name => "wps", :type => :passive, :cost => { stamina: 0 }, :regex => /USAGE\: FEAT WPS \{options\} \[args\]/, :usage => nil } }
Class Method Summary collapse
-
.[](name) ⇒ Integer
Looks up the rank known of a feat.
-
.affordable?(name, forcert_count: 0) ⇒ Boolean
Determines if an Feat is affordable, and optionally tests affordability with a given number of FORCERTs having been used (including the current one).
-
.available?(name, min_rank: 1, forcert_count: 0) ⇒ Boolean
Determines if a Feat is available to use right now by testing: - if the feat is known - if the feat is affordable - if the feat is not on cooldown - if the character is not overexerted - if the character is capable of performing the number of FORCERTs specified.
-
.buff_active?(name) ⇒ Boolean
Checks whether the feat’s buff is currently active.
-
.feat_lookups ⇒ Array<Hash>
Returns a simplified list of feat metadata.
-
.known?(name, min_rank: 1) ⇒ Boolean
Determines if the character knows a feat at all, and optionally if the character knows it at the specified rank.
-
.regexp(name) ⇒ Regexp
Returns the “success” regex associated with a given Feat technique name.
-
.use(name, target = "", results_of_interest: nil, forcert_count: 0) ⇒ String?
Attempts to use an Feat, optionally on a target.
Class Method Details
.[](name) ⇒ Integer
Looks up the rank known of a feat.
Looks up the rank known of a feat.
299 300 301 |
# File 'documented/gemstone/psms/feat.rb', line 299 def Feat.[](name) return PSMS.assess(name, 'Feat') end |
.affordable?(name, forcert_count: 0) ⇒ Boolean
Determines if an Feat is affordable, and optionally tests affordability with a given number of FORCERTs having been used (including the current one).
326 327 328 |
# File 'documented/gemstone/psms/feat.rb', line 326 def Feat.affordable?(name, forcert_count: 0) return PSMS.assess(name, 'Feat', true, forcert_count: forcert_count) end |
.available?(name, min_rank: 1, forcert_count: 0) ⇒ Boolean
Determines if a Feat is available to use right now by testing:
-
if the feat is known
-
if the feat is affordable
-
if the feat 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
344 345 346 347 348 |
# File 'documented/gemstone/psms/feat.rb', line 344 def Feat.available?(name, min_rank: 1, forcert_count: 0) Feat.known?(name, min_rank: min_rank) && Feat.affordable?(name, forcert_count: forcert_count) && PSMS.available?(name) end |
.buff_active?(name) ⇒ Boolean
Checks whether the feat’s buff is currently active.
354 355 356 357 |
# File 'documented/gemstone/psms/feat.rb', line 354 def Feat.buff_active?(name) return unless @@feats.fetch(PSMS.find_name(name, "Feat")[:long_name]).key?(:buff) Effects::Buffs.active?(@@feats.fetch(PSMS.find_name(name, "Feat")[:long_name])[:buff]) end |
.feat_lookups ⇒ Array<Hash>
Returns a simplified list of feat metadata.
281 282 283 284 285 286 287 288 289 |
# File 'documented/gemstone/psms/feat.rb', line 281 def self.feat_lookups @@feats.map do |long_name, psm| { long_name: long_name, short_name: psm[:short_name], cost: psm[:cost] } end end |
.known?(name, min_rank: 1) ⇒ Boolean
Determines if the character knows a feat at all, and optionally if the character knows it at the specified rank.
312 313 314 315 |
# File 'documented/gemstone/psms/feat.rb', line 312 def Feat.known?(name, min_rank: 1) min_rank = 1 unless min_rank >= 1 # in case a 0 or below is passed Feat[name] >= min_rank end |
.regexp(name) ⇒ Regexp
Returns the “success” regex associated with a given Feat 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.
422 423 424 |
# File 'documented/gemstone/psms/feat.rb', line 422 def Feat.regexp(name) @@feats.fetch(PSMS.find_name(name, "Feat")[:long_name])[:regex] end |
.use(name, target = "", results_of_interest: nil, forcert_count: 0) ⇒ String?
Attempts to use an Feat, optionally on a target.
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'documented/gemstone/psms/feat.rb', line 369 def Feat.use(name, target = "", results_of_interest: nil, forcert_count: 0) return unless Feat.available?(name, forcert_count: forcert_count) name_normalized = PSMS.name_normal(name) technique = @@feats.fetch(PSMS.find_name(name_normalized, "Feat")[: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\.$/, ) results_regex = Regexp.union(results_regex, results_of_interest) if results_of_interest.is_a?(Regexp) usage_cmd = (['guard', 'protect'].include?(usage) ? "#{usage}" : "feat #{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 |