Class: Lich::Common::Spell
- Inherits:
-
Object
- Object
- Lich::Common::Spell
- Defined in:
- documented/common/spell.rb
Overview
Represents a spell from the game's spell database, loaded from effect-list.xml.
Provides spell metadata (name, circle, type, duration, costs), active tracking, casting methods, and convenience accessors for calculated values like duration formulas and spell bonuses. Spells are globally indexed by number and name for fast lookup.
Constant Summary collapse
- @@load_mutex =
Mutex.new
- @@after_stance =
nil- @@prepare_regex =
Regexp.union( /^You already have a spell readied! You must RELEASE it if you wish to prepare another!$/, /^Your spell(?:song)? is ready\./, /^You can't think clearly enough to prepare a spell!$/, /^You are concentrating too intently .*?to prepare a spell\.$/, /^You are too injured to make that dextrous of a movement/, /^The searing pain in your throat makes that impossible/, /^But you don't have any mana!\.$/, /^You can't make that dextrous of a move!$/, /^As you begin to prepare the spell the wind blows small objects at you thwarting your attempt\.$/, /^You do not know that spell!$/, /^All you manage to do is cough up some blood\.$/, /^The incantations of countless spells swirl through your mind as a golden light flashes before your eyes\./ )
- @@results_regex =
Regexp.union( /^(?:Cast|Sing) Roundtime [0-9]+ Seconds?\.$/, /^Cast at what\?$/, /^But you don't have any mana!$/, /^You don't have a spell prepared!$/, /keeps? the spell from working\./, /^Be at peace my child, there is no need for spells of war in here\.$/, /Spells of War cannot be cast/, /^As you focus on your magic, your vision swims with a swirling haze of crimson\.$/, /^Your magic fizzles ineffectually\.$/, /^All you manage to do is cough up some blood\.$/, /^And give yourself away! Never!$/, /^You are unable to do that right now\.$/, /^You feel a sudden rush of power as you absorb [0-9]+ mana!$/, /^You are unable to drain it!$/, /leaving you casting at nothing but thin air!$/, /^You don't seem to be able to move to do that\.$/, /^Provoking a GameMaster is not such a good idea\.$/, /^You can't think clearly enough to prepare a spell!$/, /^You do not currently have a target\.$/, /The incantations of countless spells swirl through your mind as a golden light flashes before your eyes\./, /You can only evoke certain spells\./, /You can only channel certain spells for extra power\./, /That is not something you can prepare\./, /^\[Spell preparation time: \d seconds?\]$/, /^You are too injured to make that dextrous of a movement/, /^You can't make that dextrous of a move!$/ )
Instance Attribute Summary collapse
-
#active ⇒ Object
Returns the value of attribute active.
-
#availability ⇒ Object
readonly
Returns the value of attribute availability.
-
#cast_proc ⇒ Object
readonly
Returns the value of attribute cast_proc.
-
#channel ⇒ Object
Returns the value of attribute channel.
-
#circle ⇒ Object
readonly
Returns the value of attribute circle.
-
#last_cast ⇒ Object
readonly
Returns the value of attribute last_cast.
-
#msgdn ⇒ Object
readonly
Returns the value of attribute msgdn.
-
#msgup ⇒ Object
readonly
Returns the value of attribute msgup.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#no_incant ⇒ Object
readonly
Returns the value of attribute no_incant.
-
#num ⇒ Object
readonly
Returns the value of attribute num.
-
#persist_on_death ⇒ Object
readonly
Returns the value of attribute persist_on_death.
-
#real_time ⇒ Object
readonly
Returns the value of attribute real_time.
-
#stance ⇒ Object
Returns the value of attribute stance.
-
#timestamp ⇒ Object
readonly
Returns the value of attribute timestamp.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
Instance Method Summary collapse
-
#_bonus ⇒ Hash{String => String}
private
Returns a copy of the spell's bonus formulas hash.
-
#_cost ⇒ Hash{String => Hash{String => String}}
private
Returns a copy of the spell's cost formulas hash.
-
#active? ⇒ Boolean
Tests whether this spell is currently active.
-
#affordable?(options = {}) ⇒ Boolean
Tests whether the player can currently cast this spell given resource constraints.
-
#available?(options = {}) ⇒ Boolean
Tests whether this spell can be cast by a given caster on a given target.
-
#boltAS ⇒ String
deprecated
Deprecated.
Use #bolt_as_formula instead
-
#boltDS ⇒ String
deprecated
Deprecated.
Use #bolt_ds_formula instead
-
#cast(target = nil, results_of_interest = nil, arg_options = nil, force_stance: nil) ⇒ String
Casts this spell, handling preparation, casting, and stance management.
-
#castProc ⇒ String?
deprecated
Deprecated.
Use the #cast_proc reader instead
-
#circle_name ⇒ String
Returns the human-readable name of this spell's circle.
-
#circlename ⇒ String
deprecated
Deprecated.
Use #circle_name instead
-
#clear_on_death ⇒ Boolean
Tests whether this spell is cleared (removed) when the character dies.
-
#command ⇒ nil
deprecated
Deprecated.
Legacy API; use #cast directly instead
-
#cost ⇒ String
deprecated
Deprecated.
Use #mana_cost_formula instead
-
#duration ⇒ Object
for backwards compatiblity.
-
#elementalCS ⇒ String
deprecated
Deprecated.
Use #elemental_cs_formula instead
-
#elementalTD ⇒ String
deprecated
Deprecated.
Use #elemental_td_formula instead
-
#force_cast(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String
Casts this spell using the standard "cast" command, bypassing incant.
-
#force_channel(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String
Casts this spell using the "channel" command, for spells that support channeling.
-
#force_evoke(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String
Casts this spell using the "evoke" command, for spells that support evoking.
-
#force_incant(arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String
Casts this spell using the "incant" command (verbal incantation).
-
#incant=(val) ⇒ Boolean
private
Sets whether this spell requires an incantation.
-
#incant? ⇒ Boolean
Tests whether this spell requires an incantation.
-
#initialize(xml_spell) ⇒ void
constructor
private
Initializes a Spell from an XML element (from effect-list.xml).
-
#known? ⇒ Boolean
Tests whether the player character knows this spell.
-
#manaCost ⇒ String
deprecated
Deprecated.
Use #mana_cost_formula instead
-
#max_duration(options = {}) ⇒ Float
Returns the maximum allowed duration for this spell in minutes.
-
#mentalCS ⇒ String
deprecated
Deprecated.
Use #mental_cs_formula instead
-
#mentalTD ⇒ String
deprecated
Deprecated.
Use #mental_td_formula instead
-
#method_missing(*args) ⇒ Integer, ...
private
Provides dynamic accessors for spell bonuses and costs based on XML data.
-
#minsleft ⇒ Float
Returns the remaining duration of this spell in minutes.
-
#multicastable?(options = {}) ⇒ Boolean
Tests whether this spell can be multicast (cast multiple times in rapid succession).
-
#physicalAS ⇒ String
deprecated
Deprecated.
Use #physical_as_formula instead
-
#physicalDS ⇒ String
deprecated
Deprecated.
Use #physical_ds_formula instead
-
#putdown ⇒ void
Marks this spell as inactive and clears its remaining duration.
-
#putup(options = {}) ⇒ void
Marks this spell as active and sets its duration.
-
#refreshable?(options = {}) ⇒ Boolean
Tests whether recasting this spell before expiration refreshes its duration.
-
#remaining ⇒ String
Returns a human-readable string of the remaining spell duration.
-
#secsleft ⇒ Float
Returns the remaining duration of this spell in seconds.
-
#selfonly ⇒ Boolean
deprecated
Deprecated.
Use #available? instead
-
#sorcererCS ⇒ String
deprecated
Deprecated.
Use #sorcerer_cs_formula instead
-
#sorcererTD ⇒ String
deprecated
Deprecated.
Use #sorcerer_td_formula instead
-
#spiritCost ⇒ String
deprecated
Deprecated.
Use #spirit_cost_formula instead
-
#spiritCS ⇒ String
deprecated
Deprecated.
Use #spirit_cs_formula instead
-
#spiritTD ⇒ String
deprecated
Deprecated.
Use #spirit_td_formula instead
-
#stackable?(options = {}) ⇒ Boolean
Tests whether this spell's duration stacks when recast before expiration.
-
#stacks ⇒ Boolean
deprecated
Deprecated.
Use #stackable? instead
-
#staminaCost ⇒ String
deprecated
Deprecated.
Use #stamina_cost_formula instead
-
#time_per(options = {}) ⇒ Float
Evaluates the duration formula and returns the calculated spell duration in minutes.
-
#time_per_formula(options = {}) ⇒ String
Returns the duration formula for this spell, with skill references substituted.
-
#timeleft ⇒ Float
Returns the remaining duration of this spell in minutes.
-
#timeleft=(val) ⇒ Numeric
private
Sets the remaining duration for this spell and updates the timestamp.
-
#to_s ⇒ String
Returns the spell's name as a string.
Constructor Details
#initialize(xml_spell) ⇒ 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.
Initializes a Spell from an XML element (from effect-list.xml).
Parses spell metadata including number, name, type, duration formulas, cost formulas, bonus formulas, and caster requirements from the XML node. Sets default duration to 250 seconds per self-cast unless specified. Registers this spell in the global list unless a spell with the same number already exists.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 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 166 |
# File 'documented/common/spell.rb', line 86 def initialize(xml_spell) @num = xml_spell['number'].to_i @name = xml_spell['name'] @type = xml_spell['type'] @no_incant = ((xml_spell['incant'] == 'no') ? true : false) if xml_spell['availability'] == 'all' @availability = 'all' elsif xml_spell['availability'] == 'group' @availability = 'group' else @availability = 'self-cast' end @bonus = Hash.new xml_spell.locate('bonus').each { |e| bonus_type = e['type'] next unless bonus_type # skip malformed bonus elements @bonus[bonus_type] = e.text } @msgup = xml_spell.locate('message').select { |e| e['type'].downcase == 'start' }.collect { |e| e.text }.join('$|^') @msgup = nil if @msgup.empty? @msgdn = xml_spell.locate('message').select { |e| e['type'].downcase == 'end' }.collect { |e| e.text }.join('$|^') @msgdn = nil if @msgdn.empty? @stance = ((xml_spell['stance'] =~ /^(yes|true)$/i) ? true : false) @channel = ((xml_spell['channel'] =~ /^(yes|true)$/i) ? true : false) @cost = Hash.new xml_spell.locate('cost').each { |xml_cost| cost_type = xml_cost['type']&.downcase next unless cost_type # skip malformed cost elements @cost[cost_type] ||= Hash.new # cast-type defaults to 'self' if not specified (most cost elements omit it) if xml_cost['cast-type']&.downcase == 'target' @cost[cost_type]['target'] = xml_cost.text else @cost[cost_type]['self'] = xml_cost.text end } @duration = Hash.new xml_spell.locate('duration').each { |xml_duration| # cast-type defaults to 'self' if not specified if xml_duration['cast-type']&.downcase == 'target' cast_type = 'target' else cast_type = 'self' if xml_duration['real-time'] =~ /^(yes|true)$/i @real_time = true else @real_time = false end end @duration[cast_type] = Hash.new @duration[cast_type][:duration] = xml_duration.text span = xml_duration['span']&.downcase @duration[cast_type][:stackable] = (span == 'stackable') @duration[cast_type][:refreshable] = (span == 'refreshable') if xml_duration['multicastable'] =~ /^(yes|true)$/i @duration[cast_type][:multicastable] = true else @duration[cast_type][:multicastable] = false end if xml_duration['persist-on-death'] =~ /^(yes|true)$/i @persist_on_death = true else @persist_on_death = false end if xml_duration['max'] @duration[cast_type][:max_duration] = xml_duration['max'].to_f else @duration[cast_type][:max_duration] = 250.0 end } @cast_proc = xml_spell.locate('cast-proc').first&.text @last_cast = Time.at(0) @timestamp = Time.now @timeleft = 0 @active = false @circle = (num.to_s.length == 3 ? num.to_s[0..0] : num.to_s[0..1]) @@list.push(self) unless @@list.find { |spell| spell.num == @num } # self # rubocop Lint/Void: self used in void context end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(*args) ⇒ Integer, ...
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.
Provides dynamic accessors for spell bonuses and costs based on XML data.
Supports three categories of dynamic methods:
-
Bonus accessors (e.g.,
bolt_as,physical_ds):- Method name keys map to hyphens (e.g.,
bolt_as→bolt-as). - Without
_formulasuffix: evaluates the formula and returns integer result. - With
_formulasuffix: returns the formula string as-is. - Returns 0 if the bonus key is not found.
- Method name keys map to hyphens (e.g.,
-
Cost accessors (e.g.,
mana_cost,spirit_cost):- Method name keys map to cost type with hyphens removed (e.g.,
mana_cost→mana). - Without
_formulasuffix: evaluates the formula with options and returns integer. - With
_formulasuffix: returns the formula string. - Handles :caster, :target, and :multicast options to select and rewrite formulas.
- For mana costs, adds 5 if spell 597 (Rapid Fire Penalty) is active.
- Returns 0 if the cost key is not found or formula is nil.
- Method name keys map to cost type with hyphens removed (e.g.,
-
Unknown methods raise NoMethodError with the method name.
Formula evaluation substitutes skill references (Spells.minorelemental, Skills.magicitemuse) with runtime values, including SpellRanks lookups for non-self casters.
1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 |
# File 'documented/common/spell.rb', line 1291 def method_missing(*args) if @@bonus_list.include?(args[0].to_s.gsub('_', '-')) if @bonus[args[0].to_s.gsub('_', '-')] proc { eval(@bonus[args[0].to_s.gsub('_', '-')]) }.call.to_i else 0 end elsif @@bonus_list.include?(args[0].to_s.sub(/_formula$/, '').gsub('_', '-')) @bonus[args[0].to_s.sub(/_formula$/, '').gsub('_', '-')].dup elsif (args[0].to_s =~ /_cost(?:_formula)?$/) and @@cost_list.include?(args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')) = args[1].to_hash if [:caster] and ([:caster] !~ /^(?:self|#{XMLData.name})$/i) if [:target] and ([:target].downcase == [:caster].downcase) formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['self'].dup else formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['target'].dup || @cost[args[0].to_s.gsub('_', '-')]['self'].dup end skills = { 'Spells.minorelemental' => "SpellRanks['#{[:caster]}'].minorelemental.to_i", 'Spells.majorelemental' => "SpellRanks['#{[:caster]}'].majorelemental.to_i", 'Spells.minorspiritual' => "SpellRanks['#{[:caster]}'].minorspiritual.to_i", 'Spells.majorspiritual' => "SpellRanks['#{[:caster]}'].majorspiritual.to_i", 'Spells.wizard' => "SpellRanks['#{[:caster]}'].wizard.to_i", 'Spells.sorcerer' => "SpellRanks['#{[:caster]}'].sorcerer.to_i", 'Spells.ranger' => "SpellRanks['#{[:caster]}'].ranger.to_i", 'Spells.paladin' => "SpellRanks['#{[:caster]}'].paladin.to_i", 'Spells.empath' => "SpellRanks['#{[:caster]}'].empath.to_i", 'Spells.cleric' => "SpellRanks['#{[:caster]}'].cleric.to_i", 'Spells.bard' => "SpellRanks['#{[:caster]}'].bard.to_i", 'Stats.level' => '100' } skills.each_pair { |a, b| formula.gsub!(a, b) } else if [:target] and ([:target] !~ /^(?:self|#{XMLData.name})$/i) formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['target'].dup || @cost[args[0].to_s.gsub('_', '-')]['self'].dup else formula = @cost[args[0].to_s.sub(/_formula$/, '').sub(/_cost$/, '')]['self'].dup end end if args[0].to_s =~ /mana/ and Spell[597].active? # Rapid Fire Penalty formula = "#{formula}+5" end if [:multicast].to_i > 1 formula = "(#{formula})*#{[:multicast].to_i}" end if args[0].to_s =~ /_formula$/ formula.dup else if formula proc { eval(formula) }.call.to_i else 0 end end else respond 'missing method: ' + args.inspect.to_s raise NoMethodError end end |
Instance Attribute Details
#active ⇒ Object
Returns the value of attribute active.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def active @active end |
#availability ⇒ Object (readonly)
Returns the value of attribute availability.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def availability @availability end |
#cast_proc ⇒ Object (readonly)
Returns the value of attribute cast_proc.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def cast_proc @cast_proc end |
#channel ⇒ Object
Returns the value of attribute channel.
29 30 31 |
# File 'documented/common/spell.rb', line 29 def channel @channel end |
#circle ⇒ Object (readonly)
Returns the value of attribute circle.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def circle @circle end |
#last_cast ⇒ Object (readonly)
Returns the value of attribute last_cast.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def last_cast @last_cast end |
#msgdn ⇒ Object (readonly)
Returns the value of attribute msgdn.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def msgdn @msgdn end |
#msgup ⇒ Object (readonly)
Returns the value of attribute msgup.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def msgup @msgup end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def name @name end |
#no_incant ⇒ Object (readonly)
Returns the value of attribute no_incant.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def no_incant @no_incant end |
#num ⇒ Object (readonly)
Returns the value of attribute num.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def num @num end |
#persist_on_death ⇒ Object (readonly)
Returns the value of attribute persist_on_death.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def persist_on_death @persist_on_death end |
#real_time ⇒ Object (readonly)
Returns the value of attribute real_time.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def real_time @real_time end |
#stance ⇒ Object
Returns the value of attribute stance.
29 30 31 |
# File 'documented/common/spell.rb', line 29 def stance @stance end |
#timestamp ⇒ Object (readonly)
Returns the value of attribute timestamp.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def @timestamp end |
#type ⇒ Object (readonly)
Returns the value of attribute type.
28 29 30 |
# File 'documented/common/spell.rb', line 28 def type @type end |
Instance Method Details
#_bonus ⇒ Hash{String => String}
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.
Returns a copy of the spell's bonus formulas hash.
Bonus keys are spell attribute types (e.g., 'bolt-as', 'physical-ds', 'elemental-cs'). Values are formula strings evaluated at runtime. Used by method_missing for dynamic bonus accessors like #bolt_as and #physical_ds.
1243 1244 1245 |
# File 'documented/common/spell.rb', line 1243 def _bonus @bonus.dup end |
#_cost ⇒ Hash{String => Hash{String => String}}
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.
Returns a copy of the spell's cost formulas hash.
Cost keys are resource types (e.g., 'mana', 'spirit', 'stamina'). Values are hashes mapping cast-type ('self', 'target') to formula strings. Used by method_missing for dynamic cost accessors like #mana_cost and #mana_cost_formula.
1255 1256 1257 |
# File 'documented/common/spell.rb', line 1255 def _cost @cost.dup end |
#active? ⇒ Boolean
535 536 537 |
# File 'documented/common/spell.rb', line 535 def active? (self.timeleft > 0) and @active end |
#affordable?(options = {}) ⇒ Boolean
Tests whether the player can currently cast this spell given resource constraints.
Checks if the player has sufficient mana, spirit, and stamina to cast the spell. For monks with the mental_acuity feat (circle 12 spells), uses stamina instead of mana for cost calculations. Accounts for active debuffs like Overexerted. For mana-costing spells with the mental_acuity feat, doubles the mana cost and checks stamina instead.
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 |
# File 'documented/common/spell.rb', line 867 def affordable?( = {}) # fixme: deal with them dirty bards! = .dup [:multicast] = nil if (self.stamina_cost() > 0) and (Spell[9699].active? or not Char.stamina >= self.stamina_cost() or Effects::Debuffs.active?("Overexerted")) false elsif (self.spirit_cost() > 0) and not (Char.spirit >= (self.spirit_cost() + 1 + [9912, 9913, 9914, 9916, 9916, 9916].delete_if { |num| !Spell[num].active? }.length)) false elsif (self.mana_cost() > 0) ## convert Spell[9699].active? to Effects::Debuffs test (if Debuffs is where it shows) if (Feat.known?(:mental_acuity) and self.num.between?(1201, 1220)) and (Spell[9699].active? or not Char.stamina >= (self.mana_cost() * 2) or Effects::Debuffs.active?("Overexerted")) false elsif (!(Feat.known?(:mental_acuity) and self.num.between?(1201, 1220))) and !(Char.mana >= self.mana_cost()) false else true end else true end end |
#available?(options = {}) ⇒ Boolean
Tests whether this spell can be cast by a given caster on a given target.
A spell is available if it is #known? by the relevant caster AND the spell's availability allows casting on the target. Availability is either 'all' (group/other), 'group', or 'self-cast' (self only). When a :caster other than self is provided, availability is checked as 'all' for other targets. When a :target other than self is provided, availability is checked as 'all'.
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 |
# File 'documented/common/spell.rb', line 741 def available?( = {}) if self.known? if [:caster] and ([:caster] !~ /^(?:self|#{XMLData.name})$/i) if [:target] and ([:target].downcase == [:caster].downcase) true else @availability == 'all' end else if [:target] and ([:target] !~ /^(?:self|#{XMLData.name})$/i) @availability == 'all' else true end end else false end end |
#boltAS ⇒ String
Use #bolt_as_formula instead
Returns the spell's bolt attack strength formula (backward compatibility alias).
1384 |
# File 'documented/common/spell.rb', line 1384 def boltAS; self.bolt_as_formula; end |
#boltDS ⇒ String
Use #bolt_ds_formula instead
Returns the spell's bolt defense strength formula (backward compatibility alias).
1394 |
# File 'documented/common/spell.rb', line 1394 def boltDS; self.bolt_ds_formula; end |
#cast(target = nil, results_of_interest = nil, arg_options = nil, force_stance: nil) ⇒ String
Casts this spell, handling preparation, casting, and stance management.
A comprehensive casting method that handles spell preparation (if not incant), resource checks, target validation, the actual cast/incant/channel/evoke command, and post-cast stance restoration. Acquires the global cast lock to serialize casting across scripts.
Returns the final cast result string (e.g., "Cast Roundtime 5 Seconds.") or a status message if preparation or casting fails (e.g., "You don't have a spell prepared!").
Supports custom cast commands (incant, cast, channel, evoke) via arg_options. Handles stance enforcement: if spell.stance is true and force_stance is not false, moves to offensive stance before casting and returns to Spell.after_stance (if set) or guarded/defensive after casting.
For spells with a cast_proc (custom cast logic), evaluates that proc instead of generating a standard command.
Checks spell affordability (mana, spirit, stamina) before and during preparation. Automatically releases a prepared spell if a different spell is needed.
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 |
# File 'documented/common/spell.rb', line 954 def cast(target = nil, results_of_interest = nil, = nil, force_stance: nil) # fixme: find multicast in target and check mana for it check_energy = proc { if Feat.known?(:mental_acuity) unless (self.mana_cost <= 0) or Char.stamina >= (self.mana_cost * 2) echo 'cast: not enough stamina there, Monk!' sleep 0.1 return false end else unless (self.mana_cost <= 0) or Char.mana >= self.mana_cost echo 'cast: not enough mana' sleep 0.1 return false end end unless (self.spirit_cost <= 0) or Char.spirit >= (self.spirit_cost + 1 + [9912, 9913, 9914, 9916, 9916, 9916].delete_if { |num| !Spell[num].active? }.length) echo 'cast: not enough spirit' sleep 0.1 return false end unless (self.stamina_cost <= 0) or Char.stamina >= self.stamina_cost echo 'cast: not enough stamina' sleep 0.1 return false end } script = Script.current if @type.nil? echo "cast: spell missing type (#{@name})" sleep 0.1 return false end check_energy.call begin save_want_downstream = script.want_downstream save_want_downstream_xml = script.want_downstream_xml script.want_downstream = true script.want_downstream_xml = false @@cast_lock.push(script) until (@@cast_lock.first == script) or @@cast_lock.empty? sleep 0.1 Script.current # allows this loop to be paused @@cast_lock.delete_if { |s| s.paused or not Script.list.include?(s) } end check_energy.call if @cast_proc waitrt? waitcastrt? check_energy.call begin proc { eval(@cast_proc) }.call rescue echo "cast: error: #{$!}" respond $!.backtrace[0..2] return false end else if @channel cast_cmd = 'channel' else cast_cmd = 'cast' end unless (.nil? || .empty?) if .split(" ")[0] =~ /incant|channel|evoke|cast/ cast_cmd = .split(" ")[0] = .split(" ").drop(1) = .join(" ") unless .empty? end end if (((target.nil? || target.to_s.empty?) && !(@no_incant)) && (cast_cmd == "cast" && .nil?) || cast_cmd == "incant") && cast_cmd !~ /^(?:channel|evoke)/ cast_cmd = "incant #{@num}" elsif (target.nil? or target.to_s.empty?) and (@type =~ /attack/i) and not [410, 435, 525, 912, 909, 609].include?(@num) cast_cmd += ' target' elsif target.is_a?(GameObj) cast_cmd += " ##{target.id}" elsif target.is_a?(Integer) cast_cmd += " ##{target}" elsif cast_cmd !~ /^incant/ cast_cmd += " #{target}" end unless (.nil? || .empty?) cast_cmd += " #{}" end cast_result = nil loop { waitrt? if cast_cmd =~ /^incant/ if (checkprep != @name) and (checkprep != 'None') dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/ end else unless checkprep == @name unless checkprep == 'None' dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/ unless (self.mana_cost <= 0) or Char.mana >= self.mana_cost echo 'cast: not enough mana' sleep 0.1 return false end unless (self.spirit_cost <= 0) or Char.spirit >= (self.spirit_cost + 1 + (if checkspell(9912) then 1 else 0 end) + (if checkspell(9913) then 1 else 0 end) + (if checkspell(9914) then 1 else 0 end) + (if checkspell(9916) then 5 else 0 end)) echo 'cast: not enough spirit' sleep 0.1 return false end unless (self.stamina_cost <= 0) or Char.stamina >= self.stamina_cost echo 'cast: not enough stamina' sleep 0.1 return false end end loop { waitrt? waitcastrt? prepare_result = dothistimeout "prepare #{@num}", 8, @@prepare_regex if prepare_result =~ /^Your spell(?:song)? is ready\./ break elsif prepare_result == 'You already have a spell readied! You must RELEASE it if you wish to prepare another!' dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/ unless (self.mana_cost <= 0) or Char.mana >= self.mana_cost echo 'cast: not enough mana' sleep 0.1 return false end elsif prepare_result =~ /^You can't think clearly enough to prepare a spell!$|^You are concentrating too intently .*?to prepare a spell\.$|^You are too injured to make that dextrous of a movement|^The searing pain in your throat makes that impossible|^But you don't have any mana!\.$|^You can't make that dextrous of a move!$|^As you begin to prepare the spell the wind blows small objects at you thwarting your attempt\.$|^You do not know that spell!$|^All you manage to do is cough up some blood\.$|The incantations of countless spells swirl through your mind as a golden light flashes before your eyes\./ sleep 0.1 return prepare_result end } end end waitcastrt? if ((@stance && force_stance != false) || force_stance == true) && Char.stance != 'offensive' put 'stance offensive' # dothistimeout 'stance offensive', 5, /^You (?:are now in|move into) an? offensive stance|^You are unable to change your stance\.$/ end if results_of_interest.is_a?(Regexp) merged_results_regex = Regexp.union(@@results_regex, results_of_interest) else merged_results_regex = @@results_regex end if Effects::Spells.active?("Armored Casting") merged_results_regex = Regexp.union(/^Roundtime: \d+ sec.$/, merged_results_regex) else merged_results_regex = Regexp.union(/^\[Spell Hindrance for/, merged_results_regex) end cast_result = dothistimeout cast_cmd, 5, merged_results_regex if cast_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 } cast_result = dothistimeout cast_cmd, 5, merged_results_regex end if cast_cmd =~ /^incant/i && cast_result =~ /^\[Spell preparation time: (\d) seconds?\]$/ sleep(Regexp.last_match(1).to_i + 0.5) cast_result = dothistimeout cast_cmd, 5, merged_results_regex end if ((@stance && force_stance != false) || force_stance == true) if @@after_stance if Char.stance !~ /#{@@after_stance}/ waitrt? dothistimeout "stance #{@@after_stance}", 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/ end elsif Char.stance !~ /^guarded$|^defensive$/ waitrt? if checkcastrt > 0 dothistimeout 'stance guarded', 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/ else dothistimeout 'stance defensive', 3, /^You (?:are now in|move into) an? \w+ stance|^You are unable to change your stance\.$/ end end end if cast_result =~ /^Cast at what\?$|^Be at peace my child, there is no need for spells of war in here\.$|^Provoking a GameMaster is not such a good idea\.$/ dothistimeout 'release', 5, /^You feel the magic of your spell rush away from you\.$|^You don't have a prepared spell to release!$/ end if cast_result =~ /You can only evoke certain spells\.|You can only channel certain spells for extra power\./ echo "cast: can't evoke/channel #{@num}" cast_cmd = cast_cmd.gsub(/^(?:evoke|channel)/, "cast") next end break unless ((@circle.to_i == 10) && (cast_result =~ /^\[Spell Hindrance for/)) } cast_result end ensure @last_cast = Time.now script.want_downstream = save_want_downstream script.want_downstream_xml = save_want_downstream_xml @@cast_lock.delete(script) end end |
#castProc ⇒ String?
Use the #cast_proc reader instead
Returns this spell's custom cast procedure (backward compatibility alias).
1444 |
# File 'documented/common/spell.rb', line 1444 def castProc; @cast_proc; end |
#circle_name ⇒ String
Returns the human-readable name of this spell's circle.
1344 1345 1346 |
# File 'documented/common/spell.rb', line 1344 def circle_name Spells.get_circle_name(@circle) end |
#circlename ⇒ String
Use #circle_name instead
Returns the human-readable circle name (backward compatibility alias).
1461 |
# File 'documented/common/spell.rb', line 1461 def circlename; self.circle_name; end |
#clear_on_death ⇒ Boolean
Tests whether this spell is cleared (removed) when the character dies.
The inverse of #persist_on_death; returns true if the spell does NOT persist across death.
1354 1355 1356 |
# File 'documented/common/spell.rb', line 1354 def clear_on_death !@persist_on_death end |
#command ⇒ nil
1456 |
# File 'documented/common/spell.rb', line 1456 def command; nil; end |
#cost ⇒ String
Use #mana_cost_formula instead
Returns the mana cost formula for this spell (backward compatibility alias).
1364 |
# File 'documented/common/spell.rb', line 1364 def cost; self.mana_cost_formula || '0'; end |
#duration ⇒ Object
for backwards compatiblity
1359 |
# File 'documented/common/spell.rb', line 1359 def duration; self.time_per_formula; end |
#elementalCS ⇒ String
Use #elemental_cs_formula instead
Returns the spell's elemental cast strength formula (backward compatibility alias).
1404 |
# File 'documented/common/spell.rb', line 1404 def elementalCS; self.elemental_cs_formula; end |
#elementalTD ⇒ String
Use #elemental_td_formula instead
Returns the spell's elemental trivial difficulty formula (backward compatibility alias).
1424 |
# File 'documented/common/spell.rb', line 1424 def elementalTD; self.elemental_td_formula; end |
#force_cast(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String
Casts this spell using the standard "cast" command, bypassing incant.
Wrapper around #cast that prepends "cast" to arg_options, forcing the spell to use the cast command even if it would normally incant.
1161 1162 1163 1164 1165 1166 1167 1168 |
# File 'documented/common/spell.rb', line 1161 def force_cast(target = nil, = nil, results_of_interest = nil, force_stance: nil) unless .nil? || .empty? = "cast #{}" else = "cast" end cast(target, results_of_interest, , force_stance: force_stance) end |
#force_channel(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String
Casts this spell using the "channel" command, for spells that support channeling.
Wrapper around #cast that prepends "channel" to arg_options, forcing the spell to use the channel command. Returns an error string if the spell does not support channeling.
1183 1184 1185 1186 1187 1188 1189 1190 |
# File 'documented/common/spell.rb', line 1183 def force_channel(target = nil, = nil, results_of_interest = nil, force_stance: nil) unless .nil? || .empty? = "channel #{}" else = "channel" end cast(target, results_of_interest, , force_stance: force_stance) end |
#force_evoke(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String
Casts this spell using the "evoke" command, for spells that support evoking.
Wrapper around #cast that prepends "evoke" to arg_options, forcing the spell to use the evoke command. Returns an error string if the spell does not support evoking.
1205 1206 1207 1208 1209 1210 1211 1212 |
# File 'documented/common/spell.rb', line 1205 def force_evoke(target = nil, = nil, results_of_interest = nil, force_stance: nil) unless .nil? || .empty? = "evoke #{}" else = "evoke" end cast(target, results_of_interest, , force_stance: force_stance) end |
#force_incant(arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String
Casts this spell using the "incant" command (verbal incantation).
Wrapper around #cast that prepends "incant" to arg_options, forcing the spell to use verbal incantation. No target parameter; incanting is always self-initiated.
1226 1227 1228 1229 1230 1231 1232 1233 |
# File 'documented/common/spell.rb', line 1226 def force_incant( = nil, results_of_interest = nil, force_stance: nil) unless .nil? || .empty? = "incant #{}" else = "incant" end cast(nil, results_of_interest, , force_stance: force_stance) end |
#incant=(val) ⇒ Boolean
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.
Sets whether this spell requires an incantation.
773 774 775 |
# File 'documented/common/spell.rb', line 773 def incant=(val) @no_incant = !val end |
#incant? ⇒ Boolean
Tests whether this spell requires an incantation.
764 765 766 |
# File 'documented/common/spell.rb', line 764 def incant? !@no_incant end |
#known? ⇒ Boolean
Tests whether the player character knows this spell.
Checks if the spell number falls within the player's trained circle ranks for the appropriate spell school (e.g., minorspiritual for circle 1, majorelemental for circle 5). Circle 17 spells (1700) are available only to Wizard, Cleric, Empath, Sorcerer, or Savant. Circles 96-99 (society/custom magic) are handled specially via Society status and rank.
If Lich::Gemstone::SK (spell knowledge system) is available, uses that directly.
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 |
# File 'documented/common/spell.rb', line 663 def known? return true if defined?(Lich::Gemstone::SK) && Lich::Gemstone::SK.known?(self) if @num.to_s.length == 3 circle_num = @num.to_s[0..0].to_i elsif @num.to_s.length == 4 circle_num = @num.to_s[0..1].to_i else return false end if circle_num == 1 ranks = [Spells.minorspiritual, XMLData.level].min elsif circle_num == 2 ranks = [Spells.majorspiritual, XMLData.level].min elsif circle_num == 3 ranks = [Spells.cleric, XMLData.level].min elsif circle_num == 4 ranks = [Spells.minorelemental, XMLData.level].min elsif circle_num == 5 ranks = [Spells.majorelemental, XMLData.level].min elsif circle_num == 6 ranks = [Spells.ranger, XMLData.level].min elsif circle_num == 7 ranks = [Spells.sorcerer, XMLData.level].min elsif circle_num == 9 ranks = [Spells.wizard, XMLData.level].min elsif circle_num == 10 ranks = [Spells., XMLData.level].min elsif circle_num == 11 ranks = [Spells.empath, XMLData.level].min elsif circle_num == 12 ranks = [Spells.minormental, XMLData.level].min elsif circle_num == 16 ranks = [Spells.paladin, XMLData.level].min elsif circle_num == 17 if (@num == 1700) and (Stats.prof =~ /^(?:Wizard|Cleric|Empath|Sorcerer|Savant)$/) return true else return false end elsif (circle_num == 97) and (Society.status == 'Guardians of Sunfist') ranks = Society.rank elsif (circle_num == 98) and (Society.status == 'Order of Voln') ranks = Society.rank elsif (circle_num == 99) and (Society.status == 'Council of Light') ranks = Society.rank elsif (circle_num == 96) return false # deprecate CMan from Spell class .known? # See CMan, CMan.known? and CMan.available? methods in CMan class else return false end if (@num % 100) <= ranks.to_i return true else return false end end |
#manaCost ⇒ String
Use #mana_cost_formula instead
Returns the mana cost formula for this spell (backward compatibility alias).
1369 |
# File 'documented/common/spell.rb', line 1369 def manaCost; self.mana_cost_formula || '0'; end |
#max_duration(options = {}) ⇒ Float
Returns the maximum allowed duration for this spell in minutes.
Duration maxima depend on the spell's XML metadata and can differ for self-cast vs. target-cast variants. Defaults to 250 minutes per cast type if not specified in XML. When a :caster option is provided, uses the caster's spell data. When a :target is specified that differs from the caster, uses target-cast metadata; otherwise uses self-cast.
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 |
# File 'documented/common/spell.rb', line 796 def max_duration( = {}) if [:caster] and ([:caster] !~ /^(?:self|#{XMLData.name})$/i) if [:target] and ([:target].downcase == [:caster].downcase) @duration['self'][:max_duration] else @duration['target'][:max_duration] || @duration['self'][:max_duration] end else if [:target] and ([:target] !~ /^(?:self|#{XMLData.name})$/i) @duration['target'][:max_duration] || @duration['self'][:max_duration] else @duration['self'][:max_duration] end end end |
#mentalCS ⇒ String
Use #mental_cs_formula instead
Returns the spell's mental cast strength formula (backward compatibility alias).
1409 |
# File 'documented/common/spell.rb', line 1409 def mentalCS; self.mental_cs_formula; end |
#mentalTD ⇒ String
Use #mental_td_formula instead
Returns the spell's mental trivial difficulty formula (backward compatibility alias).
1429 |
# File 'documented/common/spell.rb', line 1429 def mentalTD; self.mental_td_formula; end |
#minsleft ⇒ Float
Returns the remaining duration of this spell in minutes.
Alias for #timeleft.
505 506 507 |
# File 'documented/common/spell.rb', line 505 def minsleft self.timeleft end |
#multicastable?(options = {}) ⇒ Boolean
Tests whether this spell can be multicast (cast multiple times in rapid succession).
Duration multicast behavior depends on the spell's duration XML metadata (multicastable="yes") and can differ for self-cast vs. target-cast variants. When a :caster option is provided, uses the caster's spell data if it differs from the player. When a :target is specified that differs from the caster, uses target-cast metadata; otherwise uses self-cast.
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 |
# File 'documented/common/spell.rb', line 626 def multicastable?( = {}) if [:caster] and ([:caster] !~ /^(?:self|#{XMLData.name})$/i) if [:target] and ([:target].downcase == [:caster].downcase) @duration['self'][:multicastable] else if @duration['target'][:multicastable].nil? @duration['self'][:multicastable] else @duration['target'][:multicastable] end end else if [:target] and ([:target] !~ /^(?:self|#{XMLData.name})$/i) if @duration['target'][:multicastable].nil? @duration['self'][:multicastable] else @duration['target'][:multicastable] end else @duration['self'][:multicastable] end end end |
#physicalAS ⇒ String
Use #physical_as_formula instead
Returns the spell's physical attack strength formula (backward compatibility alias).
1389 |
# File 'documented/common/spell.rb', line 1389 def physicalAS; self.physical_as_formula; end |
#physicalDS ⇒ String
Use #physical_ds_formula instead
Returns the spell's physical defense strength formula (backward compatibility alias).
1399 |
# File 'documented/common/spell.rb', line 1399 def physicalDS; self.physical_ds_formula; end |
#putdown ⇒ void
This method returns an undefined value.
Marks this spell as inactive and clears its remaining duration.
Sets timeleft to 0 and active flag to false.
840 841 842 843 |
# File 'documented/common/spell.rb', line 840 def putdown self.timeleft = 0 @active = false end |
#putup(options = {}) ⇒ void
This method returns an undefined value.
Marks this spell as active and sets its duration.
Calculates the spell's duration using #time_per and applies stacking rules: if #stackable? is true, adds the new duration to the current remaining duration; otherwise replaces the duration. Clamps the result to #max_duration. Sets the active flag to true.
825 826 827 828 829 830 831 832 |
# File 'documented/common/spell.rb', line 825 def putup( = {}) if stackable?() self.timeleft = [self.timeleft + self.time_per(), self.max_duration()].min else self.timeleft = [self.time_per(), self.max_duration()].min end @active = true end |
#refreshable?(options = {}) ⇒ Boolean
Tests whether recasting this spell before expiration refreshes its duration.
Duration refresh behavior depends on the spell's duration XML metadata (span="refreshable") and can differ for self-cast vs. target-cast variants. When a :caster option is provided, uses the caster's spell data if it differs from the player. When a :target is specified that differs from the caster, uses target-cast metadata; otherwise uses self-cast.
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 |
# File 'documented/common/spell.rb', line 589 def refreshable?( = {}) if [:caster] and ([:caster] !~ /^(?:self|#{XMLData.name})$/i) if [:target] and ([:target].downcase == [:caster].downcase) @duration['self'][:refreshable] else if @duration['target'][:refreshable].nil? @duration['self'][:refreshable] else @duration['target'][:refreshable] end end else if [:target] and ([:target] !~ /^(?:self|#{XMLData.name})$/i) if @duration['target'][:refreshable].nil? @duration['self'][:refreshable] else @duration['target'][:refreshable] end else @duration['self'][:refreshable] end end end |
#remaining ⇒ String
Returns a human-readable string of the remaining spell duration.
850 851 852 |
# File 'documented/common/spell.rb', line 850 def remaining self.timeleft.as_time end |
#secsleft ⇒ Float
Returns the remaining duration of this spell in seconds.
Multiplies #timeleft by 60.
514 515 516 |
# File 'documented/common/spell.rb', line 514 def secsleft self.timeleft * 60 end |
#selfonly ⇒ Boolean
Use #available? instead
Tests whether this spell is self-cast only (backward compatibility alias).
1466 |
# File 'documented/common/spell.rb', line 1466 def selfonly; @availability != 'all'; end |
#sorcererCS ⇒ String
Use #sorcerer_cs_formula instead
Returns the spell's sorcerer cast strength formula (backward compatibility alias).
1419 |
# File 'documented/common/spell.rb', line 1419 def sorcererCS; self.sorcerer_cs_formula; end |
#sorcererTD ⇒ String
Use #sorcerer_td_formula instead
Returns the spell's sorcerer trivial difficulty formula (backward compatibility alias).
1439 |
# File 'documented/common/spell.rb', line 1439 def sorcererTD; self.sorcerer_td_formula; end |
#spiritCost ⇒ String
Use #spirit_cost_formula instead
Returns the spirit cost formula for this spell (backward compatibility alias).
1374 |
# File 'documented/common/spell.rb', line 1374 def spiritCost; self.spirit_cost_formula || '0'; end |
#spiritCS ⇒ String
Use #spirit_cs_formula instead
Returns the spell's spirit cast strength formula (backward compatibility alias).
1414 |
# File 'documented/common/spell.rb', line 1414 def spiritCS; self.spirit_cs_formula; end |
#spiritTD ⇒ String
Use #spirit_td_formula instead
Returns the spell's spirit trivial difficulty formula (backward compatibility alias).
1434 |
# File 'documented/common/spell.rb', line 1434 def spiritTD; self.spirit_td_formula; end |
#stackable?(options = {}) ⇒ Boolean
Tests whether this spell's duration stacks when recast before expiration.
Duration stacking behavior depends on the spell's duration XML metadata (span="stackable") and can differ for self-cast vs. target-cast variants. When a :caster option is provided, uses the caster's spell data if it differs from the player. When a :target is specified that differs from the caster, uses target-cast metadata; otherwise uses self-cast.
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 |
# File 'documented/common/spell.rb', line 552 def stackable?( = {}) if [:caster] and ([:caster] !~ /^(?:self|#{XMLData.name})$/i) if [:target] and ([:target].downcase == [:caster].downcase) @duration['self'][:stackable] else if @duration['target'][:stackable].nil? @duration['self'][:stackable] else @duration['target'][:stackable] end end else if [:target] and ([:target] !~ /^(?:self|#{XMLData.name})$/i) if @duration['target'][:stackable].nil? @duration['self'][:stackable] else @duration['target'][:stackable] end else @duration['self'][:stackable] end end end |
#stacks ⇒ Boolean
Use #stackable? instead
Tests whether this spell stacks when recast (backward compatibility alias).
1449 |
# File 'documented/common/spell.rb', line 1449 def stacks; self.stackable? end |
#staminaCost ⇒ String
Use #stamina_cost_formula instead
Returns the stamina cost formula for this spell (backward compatibility alias).
1379 |
# File 'documented/common/spell.rb', line 1379 def staminaCost; self.stamina_cost_formula || '0'; end |
#time_per(options = {}) ⇒ Float
Evaluates the duration formula and returns the calculated spell duration in minutes.
Computes the duration by evaluating #time_per_formula with the game's current skill values and bonuses. For spells with spell knowledge (SK) integration, enforces a 10-minute minimum duration.
450 451 452 453 454 455 456 457 458 459 |
# File 'documented/common/spell.rb', line 450 def time_per( = {}) formula = self.time_per_formula() if [:line] # line = options[:line] rubocop useless assignment to line [:line] end result = proc { eval(formula) }.call.to_f return 10.0 if defined?(Lich::Gemstone::SK) && Lich::Gemstone::SK.known?(self) && (result.nil? || result < 10) return result end |
#time_per_formula(options = {}) ⇒ String
Returns the duration formula for this spell, with skill references substituted.
Retrieves the appropriate duration formula (self-cast or target-cast) and rewrites skill references (e.g., Spells.minorelemental, Skills.magicitemuse) to concrete expressions. The substitution depends on the :caster, :target, and :activator options.
Activators (tap, rub, wave, raise, drink, etc.) get scaled multipliers applied to skill values. Invocation methods (invoke, scroll) use arcanesymbols instead. Caster and target names are case-insensitive; a caster other than self receives lookups via SpellRanks instead of global Skills.
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 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 |
# File 'documented/common/spell.rb', line 382 def time_per_formula( = {}) activator_modifier = { 'tap' => 0.5, 'rub' => 1, 'wave' => 1, 'raise' => 1.33, 'drink' => 0, 'bite' => 0, 'eat' => 0, 'gobble' => 0 } can_haz_spell_ranks = /Spells\.(?:minorelemental|majorelemental|minorspiritual|majorspiritual|wizard|sorcerer|ranger|paladin|empath|cleric|bard|minormental)/ skills = ['Spells.minorelemental', 'Spells.majorelemental', 'Spells.minorspiritual', 'Spells.majorspiritual', 'Spells.wizard', 'Spells.sorcerer', 'Spells.ranger', 'Spells.paladin', 'Spells.empath', 'Spells.cleric', 'Spells.bard', 'Spells.minormental', 'Skills.magicitemuse', 'Skills.arcanesymbols'] if [:caster] and ([:caster] !~ /^(?:self|#{XMLData.name})$/i) if [:target] and ([:target].downcase == [:caster].downcase) formula = @duration['self'][:duration].to_s.dup else formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup end if [:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i if formula =~ can_haz_spell_ranks skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{[:caster]}'].magicitemuse * #{activator_modifier[[:activator]]}).to_i") } formula = "(#{formula})/2.0" elsif formula =~ /Skills\.(?:magicitemuse|arcanesymbols)/ skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{[:caster]}'].magicitemuse * #{activator_modifier[[:activator]]}).to_i") } end elsif [:activator] =~ /^(invoke|scroll)$/i if formula =~ can_haz_spell_ranks skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{[:caster]}'].arcanesymbols.to_i") } formula = "(#{formula})/2.0" elsif formula =~ /Skills\.(?:magicitemuse|arcanesymbols)/ skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{[:caster]}'].arcanesymbols.to_i") } end else skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks[#{[:caster].to_s.inspect}].#{skill_name.sub(/^(?:Spells|Skills)\./, '')}.to_i") } end else if [:target] and ([:target] !~ /^(?:self|#{XMLData.name})$/i) formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup else formula = @duration['self'][:duration].to_s.dup end if [:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i if formula =~ can_haz_spell_ranks skills.each { |skill_name| formula.gsub!(skill_name, "(Skills.magicitemuse * #{activator_modifier[[:activator]]}).to_i") } formula = "(#{formula})/2.0" elsif formula =~ /Skills\.(?:magicitemuse|arcanesymbols)/ skills.each { |skill_name| formula.gsub!(skill_name, "(Skills.magicitemuse * #{activator_modifier[[:activator]]}).to_i") } end elsif [:activator] =~ /^(invoke|scroll)$/i if formula =~ can_haz_spell_ranks skills.each { |skill_name| formula.gsub!(skill_name, "Skills.arcanesymbols.to_i") } formula = "(#{formula})/2.0" elsif formula =~ /Skills\.(?:magicitemuse|arcanesymbols)/ skills.each { |skill_name| formula.gsub!(skill_name, "Skills.arcanesymbols.to_i") } end end end formula end |
#timeleft ⇒ Float
Returns the remaining duration of this spell in minutes.
Subtracts elapsed time since the last #timeleft= or #timeleft call from the tracked duration. If the spell's duration formula is a Spellsong reference, queries Spellsong.timeleft directly instead. When remaining time drops to 0 or below, calls #putdown and returns 0.0.
Updates the timestamp on each call.
486 487 488 489 490 491 492 493 494 495 496 497 498 |
# File 'documented/common/spell.rb', line 486 def timeleft if self.time_per_formula.to_s == 'Spellsong.timeleft' @timeleft = Spellsong.timeleft else @timeleft = @timeleft - ((Time.now - @timestamp) / 60.to_f) if @timeleft <= 0 self.putdown return 0.to_f end end @timestamp = Time.now @timeleft end |
#timeleft=(val) ⇒ Numeric
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.
Sets the remaining duration for this spell and updates the timestamp.
Used internally to track spell duration across duration checks. Setting a new value resets the base timestamp to now.
469 470 471 472 |
# File 'documented/common/spell.rb', line 469 def timeleft=(val) @timeleft = val @timestamp = Time.now end |
#to_s ⇒ String
Returns the spell's name as a string.
780 781 782 |
# File 'documented/common/spell.rb', line 780 def to_s @name.to_s end |