Class: Lich::Common::Spell

Inherits:
Object
  • Object
show all
Defined in:
lib/common/spell.rb

Overview

Represents a spell in the Lich system.

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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(xml_spell) ⇒ Spell

Initializes a new Spell instance.

Examples:

xml_spell = REXML::Document.new("<spell number='1' name='Fireball' type='offensive'></spell>").root
spell = Spell.new(xml_spell)

Parameters:

  • xml_spell (REXML::Element)

    The XML element representing the spell.

Raises:

  • (StandardError)

    Raises an error if the XML structure is invalid.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
# File 'lib/common/spell.rb', line 72

def initialize(xml_spell)
  @num = xml_spell.attributes['number'].to_i
  @name = xml_spell.attributes['name']
  @type = xml_spell.attributes['type']
  @no_incant = ((xml_spell.attributes['incant'] == 'no') ? true : false)
  if xml_spell.attributes['availability'] == 'all'
    @availability = 'all'
  elsif xml_spell.attributes['availability'] == 'group'
    @availability = 'group'
  else
    @availability = 'self-cast'
  end
  @bonus = Hash.new
  xml_spell.elements.find_all { |e| e.name == 'bonus' }.each { |e|
    @bonus[e.attributes['type']] = e.text
  }
  @msgup = xml_spell.elements.find_all { |e| (e.name == 'message') and (e.attributes['type'].downcase == 'start') }.collect { |e| e.text }.join('$|^')
  @msgup = nil if @msgup.empty?
  @msgdn = xml_spell.elements.find_all { |e| (e.name == 'message') and (e.attributes['type'].downcase == 'end') }.collect { |e| e.text }.join('$|^')
  @msgdn = nil if @msgdn.empty?
  @stance = ((xml_spell.attributes['stance'] =~ /^(yes|true)$/i) ? true : false)
  @channel = ((xml_spell.attributes['channel'] =~ /^(yes|true)$/i) ? true : false)
  @cost = Hash.new
  xml_spell.elements.find_all { |e| e.name == 'cost' }.each { |xml_cost|
    @cost[xml_cost.attributes['type'].downcase] ||= Hash.new
    if xml_cost.attributes['cast-type'].downcase == 'target'
      @cost[xml_cost.attributes['type'].downcase]['target'] = xml_cost.text
    else
      @cost[xml_cost.attributes['type'].downcase]['self'] = xml_cost.text
    end
  }
  @duration = Hash.new
  xml_spell.elements.find_all { |e| e.name == 'duration' }.each { |xml_duration|
    if xml_duration.attributes['cast-type'].downcase == 'target'
      cast_type = 'target'
    else
      cast_type = 'self'
      if xml_duration.attributes['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
    @duration[cast_type][:stackable] = (xml_duration.attributes['span'].downcase == 'stackable')
    @duration[cast_type][:refreshable] = (xml_duration.attributes['span'].downcase == 'refreshable')
    if xml_duration.attributes['multicastable'] =~ /^(yes|true)$/i
      @duration[cast_type][:multicastable] = true
    else
      @duration[cast_type][:multicastable] = false
    end
    if xml_duration.attributes['persist-on-death'] =~ /^(yes|true)$/i
      @persist_on_death = true
    else
      @persist_on_death = false
    end
    if xml_duration.attributes['max']
      @duration[cast_type][:max_duration] = xml_duration.attributes['max'].to_f
    else
      @duration[cast_type][:max_duration] = 250.0
    end
  }
  @cast_proc = xml_spell.elements['cast-proc'].text
  @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) ⇒ Object

Handles missing methods dynamically.

Examples:

result = method_missing(:some_method)

Parameters:

  • args (Array)

    The arguments passed to the missing method.

Returns:

  • (Object)

    The result of the method call or raises NoMethodError.

Raises:

  • (NoMethodError)

    If the method is not found in the bonus or cost lists.



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
# File 'lib/common/spell.rb', line 1041

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$/, ''))
    options = args[1].to_hash
    if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
      if options[:target] and (options[:target].downcase == options[: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['#{options[:caster]}'].minorelemental.to_i", 'Spells.majorelemental' => "SpellRanks['#{options[:caster]}'].majorelemental.to_i", 'Spells.minorspiritual' => "SpellRanks['#{options[:caster]}'].minorspiritual.to_i", 'Spells.majorspiritual' => "SpellRanks['#{options[:caster]}'].majorspiritual.to_i", 'Spells.wizard' => "SpellRanks['#{options[:caster]}'].wizard.to_i", 'Spells.sorcerer' => "SpellRanks['#{options[:caster]}'].sorcerer.to_i", 'Spells.ranger' => "SpellRanks['#{options[:caster]}'].ranger.to_i", 'Spells.paladin' => "SpellRanks['#{options[:caster]}'].paladin.to_i", 'Spells.empath' => "SpellRanks['#{options[:caster]}'].empath.to_i", 'Spells.cleric' => "SpellRanks['#{options[:caster]}'].cleric.to_i", 'Spells.bard' => "SpellRanks['#{options[:caster]}'].bard.to_i", 'Stats.level' => '100' }
      skills.each_pair { |a, b| formula.gsub!(a, b) }
    else
      if options[:target] and (options[: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 options[:multicast].to_i > 1
      formula = "(#{formula})*#{options[: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

#activeObject

Returns the value of attribute active.



18
19
20
# File 'lib/common/spell.rb', line 18

def active
  @active
end

#availabilityObject (readonly)

Returns the value of attribute availability.



18
19
20
# File 'lib/common/spell.rb', line 18

def availability
  @availability
end

#cast_procObject (readonly)

Returns the value of attribute cast_proc.



18
19
20
# File 'lib/common/spell.rb', line 18

def cast_proc
  @cast_proc
end

#channelObject

Returns the value of attribute channel.



19
20
21
# File 'lib/common/spell.rb', line 19

def channel
  @channel
end

#circleObject (readonly)

Returns the value of attribute circle.



18
19
20
# File 'lib/common/spell.rb', line 18

def circle
  @circle
end

#msgdnObject (readonly)

Returns the value of attribute msgdn.



18
19
20
# File 'lib/common/spell.rb', line 18

def msgdn
  @msgdn
end

#msgupObject (readonly)

Returns the value of attribute msgup.



18
19
20
# File 'lib/common/spell.rb', line 18

def msgup
  @msgup
end

#nameObject (readonly)

Returns the value of attribute name.



18
19
20
# File 'lib/common/spell.rb', line 18

def name
  @name
end

#no_incantObject (readonly)

Returns the value of attribute no_incant.



18
19
20
# File 'lib/common/spell.rb', line 18

def no_incant
  @no_incant
end

#numObject (readonly)

Returns the value of attribute num.



18
19
20
# File 'lib/common/spell.rb', line 18

def num
  @num
end

#persist_on_deathObject (readonly)

Returns the value of attribute persist_on_death.



18
19
20
# File 'lib/common/spell.rb', line 18

def persist_on_death
  @persist_on_death
end

#real_timeObject (readonly)

Returns the value of attribute real_time.



18
19
20
# File 'lib/common/spell.rb', line 18

def real_time
  @real_time
end

#stanceObject

Returns the value of attribute stance.



19
20
21
# File 'lib/common/spell.rb', line 19

def stance
  @stance
end

#timestampObject (readonly)

Returns the value of attribute timestamp.



18
19
20
# File 'lib/common/spell.rb', line 18

def timestamp
  @timestamp
end

#typeObject (readonly)

Returns the value of attribute type.



18
19
20
# File 'lib/common/spell.rb', line 18

def type
  @type
end

Class Method Details

.[](val) ⇒ Spell?

Retrieves a spell by its number or name.

Examples:

spell = Spell[1] # Retrieves the spell with number 1

Parameters:

  • val (Integer, String, Spell)

    The spell number, name, or Spell instance to retrieve.

Returns:

  • (Spell, nil)

    The corresponding Spell instance or nil if not found.



229
230
231
232
233
234
235
236
237
238
239
# File 'lib/common/spell.rb', line 229

def Spell.[](val)
  Spell.load unless @@loaded
  if val.class == Spell
    val
  elsif (val.class == Integer) or (val.class == String and val =~ /^[0-9]+$/)
    @@list.find { |spell| spell.num == val.to_i }
  else
    val = Regexp.escape(val)
    (@@list.find { |s| s.name =~ /^#{val}$/i } || @@list.find { |s| s.name =~ /^#{val}/i } || @@list.find { |s| s.msgup =~ /#{val}/i or s.msgdn =~ /#{val}/i })
  end
end

.activeArray<Spell>

Retrieves all active spells.

Examples:

active_spells = Spell.active

Returns:

  • (Array<Spell>)

    An array of currently active Spell instances.



246
247
248
249
250
251
# File 'lib/common/spell.rb', line 246

def Spell.active
  Spell.load unless @@loaded
  active = Array.new
  @@list.each { |spell| active.push(spell) if spell.active? }
  active
end

.active?(val) ⇒ Boolean

Checks if a specific spell is active.

Examples:

is_active = Spell.active?('Fireball')

Parameters:

  • val (Integer, String)

    The spell number or name to check.

Returns:

  • (Boolean)

    True if the spell is active, false otherwise.



259
260
261
262
# File 'lib/common/spell.rb', line 259

def Spell.active?(val)
  Spell.load unless @@loaded
  Spell[val].active?
end

.after_stanceObject

Retrieves the after stance value for the Spell class.

Examples:

stance_value = Spell.after_stance

Returns:

  • (Object)

    The current after stance value.



159
160
161
# File 'lib/common/spell.rb', line 159

def Spell.after_stance
  @@after_stance
end

.after_stance=(val) ⇒ void

This method returns an undefined value.

Sets the after stance value for the Spell class.

Examples:

Spell.after_stance = 'some_value'

Parameters:

  • val (Object)

    The value to set for after stance.



150
151
152
# File 'lib/common/spell.rb', line 150

def Spell.after_stance=(val)
  @@after_stance = val
end

.dnmsgsArray<String>

Retrieves all end messages for spells.

Examples:

end_messages = Spell.dnmsgs

Returns:

  • (Array<String>)

    An array of end messages for all spells.



289
290
291
292
# File 'lib/common/spell.rb', line 289

def Spell.dnmsgs
  Spell.load unless @@loaded
  @@list.collect { |spell| spell.msgdn }.compact
end

.listArray<Spell>

Retrieves the list of all spells.

Examples:

all_spells = Spell.list

Returns:

  • (Array<Spell>)

    An array of all Spell instances.



269
270
271
272
# File 'lib/common/spell.rb', line 269

def Spell.list
  Spell.load unless @@loaded
  @@list
end

.load(filename = nil) ⇒ Boolean

Loads spell data from a specified file or a default location.

Examples:

Spell.load('path/to/effect-list.xml')

Parameters:

  • filename (String, nil) (defaults to: nil)

    The path to the XML file to load. If nil, defaults to ‘effect-list.xml’.

Returns:

  • (Boolean)

    Returns true if loading was successful, false otherwise.

Raises:

  • (StandardError)

    Raises an error if file operations fail.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/common/spell.rb', line 170

def Spell.load(filename = nil)
  if filename.nil?
    filename = File.join(DATA_DIR, 'effect-list.xml')
    unless File.exist?(filename)
      begin
        File.write(filename, URI.open('https://raw.githubusercontent.com/elanthia-online/scripts/master/scripts/effect-list.xml').read)
        Lich.log('effect-list.xml missing from DATA dir. Downloaded effect-list.xml from EO\Scripts GitHub complete.')
      rescue StandardError
        respond "--- Lich: error: Spell.load: #{$!}"
        Lich.log "error: Spell.load: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        Lich.log('Github retrieval of effect-list.xml failed, trying ;repository instead.')
        Script.run('repository', 'download effect-list.xml --game=gs')
        return false unless File.exist?(filename)
      end
    end
  end
  # script = Script.current #rubocop useless assignment to variable - script
  Script.current
  @@load_mutex.synchronize {
    return true if @loaded
    begin
      spell_times = Hash.new
      # reloading spell data should not reset spell tracking...
      unless @@list.empty?
        @@list.each { |spell| spell_times[spell.num] = spell.timeleft if spell.active? }
        @@list.clear
      end
      File.open(filename) { |file|
        xml_doc = REXML::Document.new(file)
        xml_root = xml_doc.root
        xml_root.elements.each { |xml_spell| Spell.new(xml_spell) }
      }
      @@list.each { |spell|
        if spell_times[spell.num]
          spell.timeleft = spell_times[spell.num]
          spell.active = true
        end
      }
      @@bonus_list = @@list.collect { |spell| spell._bonus.keys }.flatten
      # @@bonus_list = @@bonus_list # | @@bonus_list #rubocop Binary operator | has identical operands.
      @@cost_list = @@list.collect { |spell| spell._cost.keys }.flatten
      # @@cost_list = @@cost_list # | @@cost_list #rubocop Binary operator | has identical operands.
      @@loaded = true
      return true
    rescue
      respond "--- Lich: error: Spell.load: #{$!}"
      Lich.log "error: Spell.load: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
      @@loaded = false
      return false
    end
  }
end

.lock_castvoid

This method returns an undefined value.

Locks the casting process to prevent concurrent casts.



731
732
733
734
735
736
737
738
739
# File 'lib/common/spell.rb', line 731

def Spell.lock_cast
  script = Script.current
  @@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
end

.unlock_castvoid

This method returns an undefined value.

Unlocks the casting process, allowing other casts.



744
745
746
# File 'lib/common/spell.rb', line 744

def Spell.unlock_cast
  @@cast_lock.delete(Script.current)
end

.upmsgsArray<String>

Retrieves all start messages for spells.

Examples:

start_messages = Spell.upmsgs

Returns:

  • (Array<String>)

    An array of start messages for all spells.



279
280
281
282
# File 'lib/common/spell.rb', line 279

def Spell.upmsgs
  Spell.load unless @@loaded
  @@list.collect { |spell| spell.msgup }.compact
end

Instance Method Details

#_bonusObject

Returns a duplicate of the bonus.

Examples:

bonus = _bonus

Returns:

  • (Object)

    A duplicate of the bonus.



1021
1022
1023
# File 'lib/common/spell.rb', line 1021

def _bonus
  @bonus.dup
end

#_costObject

Returns a duplicate of the cost.

Examples:

cost = _cost

Returns:

  • (Object)

    A duplicate of the cost.



1030
1031
1032
# File 'lib/common/spell.rb', line 1030

def _cost
  @cost.dup
end

#active?Boolean

Checks if the spell or effect is currently active.

Examples:

is_active = active?

Returns:

  • (Boolean)

    true if active, false otherwise



438
439
440
# File 'lib/common/spell.rb', line 438

def active?
  (self.timeleft > 0) and @active
end

#affordable?(options = {}) ⇒ Boolean

Note:

This method may not handle certain edge cases for bards.

Checks if the spell can be afforded based on stamina, spirit, and mana costs.

Parameters:

  • options (Hash) (defaults to: {})

    options for checking affordability.

Returns:

  • (Boolean)

    true if the spell can be afforded, false otherwise.



706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
# File 'lib/common/spell.rb', line 706

def affordable?(options = {})
  # fixme: deal with them dirty bards!
  release_options = options.dup
  release_options[:multicast] = nil
  if (self.stamina_cost(options) > 0) and (Spell[9699].active? or not Char.stamina >= self.stamina_cost(options) or Effects::Debuffs.active?("Overexerted"))
    false
  elsif (self.spirit_cost(options) > 0) and not (Char.spirit >= (self.spirit_cost(options) + 1 + [9912, 9913, 9914, 9916, 9916, 9916].delete_if { |num| !Spell[num].active? }.length))
    false
  elsif (self.mana_cost(options) > 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(options) * 2) or Effects::Debuffs.active?("Overexerted"))
      false
    elsif (!(Feat.known?(:mental_acuity) and self.num.between?(1201, 1220))) and !(Char.mana >= self.mana_cost(options))
      false
    else
      true
    end
  else
    true
  end
end

#available?(options = {}) ⇒ Boolean

Checks if the spell or effect is available based on its known status and options.

Examples:

is_available = available?(caster: 'self', target: 'enemy')

Parameters:

  • options (Hash) (defaults to: {})

    options for checking availability, which may include:

    • :caster [String] the name of the caster

    • :target [String] the name of the target

Returns:

  • (Boolean)

    true if available, false otherwise



612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
# File 'lib/common/spell.rb', line 612

def available?(options = {})
  if self.known?
    if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
      if options[:target] and (options[:target].downcase == options[:caster].downcase)
        true
      else
        @availability == 'all'
      end
    else
      if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
        @availability == 'all'
      else
        true
      end
    end
  else
    false
  end
end

#boltASString

Returns the bolt attack strength formula.

Examples:

bolt_as_formula = boltAS

Returns:

  • (String)

    The formula for bolt attack strength.



1146
# File 'lib/common/spell.rb', line 1146

def boltAS;        self.bolt_as_formula;             end

#boltDSString

Returns the bolt defense strength formula.

Examples:

bolt_ds_formula = boltDS

Returns:

  • (String)

    The formula for bolt defense strength.



1160
# File 'lib/common/spell.rb', line 1160

def boltDS;        self.bolt_ds_formula;             end

#cast(target = nil, results_of_interest = nil, arg_options = nil) ⇒ Boolean, String

Note:

This method includes checks for energy and may have side effects based on the spell type.

Casts the spell on a target with optional arguments.

Parameters:

  • target (GameObj, Integer, nil) (defaults to: nil)

    the target of the spell.

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

    regex to match results of interest.

  • arg_options (String, nil) (defaults to: nil)

    additional options for casting.

Returns:

  • (Boolean, String)

    true if cast was successful, false otherwise, or an error message.

Raises:

  • (StandardError)

    if there is an error during casting.



756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
# File 'lib/common/spell.rb', line 756

def cast(target = nil, results_of_interest = nil, arg_options = 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 (arg_options.nil? || arg_options.empty?)
        if arg_options.split(" ")[0] =~ /incant|channel|evoke|cast/
          cast_cmd = arg_options.split(" ")[0]
          arg_options = arg_options.split(" ").drop(1)
          arg_options = arg_options.join(" ") unless arg_options.empty?
        end
      end

      if (((target.nil? || target.to_s.empty?) && !(@no_incant)) && (cast_cmd == "cast" && arg_options.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.class == GameObj
        cast_cmd += " ##{target.id}"
      elsif target.class == Integer
        cast_cmd += " ##{target}"
      elsif cast_cmd !~ /^incant/
        cast_cmd += " #{target}"
      end

      unless (arg_options.nil? || arg_options.empty?)
        cast_cmd += " #{arg_options}"
      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 and 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.class == 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
          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
    script.want_downstream = save_want_downstream
    script.want_downstream_xml = save_want_downstream_xml
    @@cast_lock.delete(script)
  end
end

#castProcObject

Returns the cast procedure.

Examples:

cast_proc = castProc

Returns:

  • (Object)

    The cast procedure.



1230
# File 'lib/common/spell.rb', line 1230

def castProc;      @cast_proc;                       end

#circle_nameString

Retrieves the name of the circle.

Examples:

name = circle_name

Returns:

  • (String)

    The name of the circle.



1093
1094
1095
# File 'lib/common/spell.rb', line 1093

def circle_name
  Spells.get_circle_name(@circle)
end

#circlenameString

Retrieves the circle name.

Examples:

circle_name_value = circlename

Returns:

  • (String)

    The name of the circle.



1251
# File 'lib/common/spell.rb', line 1251

def circlename;    self.circle_name;                 end

#clear_on_deathBoolean

Determines if the object should clear on death.

Examples:

should_clear = clear_on_death

Returns:

  • (Boolean)

    True if it should clear on death, false otherwise.



1102
1103
1104
# File 'lib/common/spell.rb', line 1102

def clear_on_death
  !@persist_on_death
end

#commandnil

Returns nil as the command.

Examples:

command_value = command

Returns:

  • (nil)

    Always returns nil.



1244
# File 'lib/common/spell.rb', line 1244

def command;       nil;                              end

#costString

Returns the cost of the effect.

Examples:

cost = cost

Returns:

  • (String)

    The cost of the effect as a string.



1118
# File 'lib/common/spell.rb', line 1118

def cost;          self.mana_cost_formula    || '0'; end

#durationInteger

Returns the duration of the effect.

Examples:

duration = duration

Returns:

  • (Integer)

    The duration of the effect.



1111
# File 'lib/common/spell.rb', line 1111

def duration;      self.time_per_formula;            end

#elementalCSString

Returns the elemental critical strength formula.

Examples:

elemental_cs_formula = elementalCS

Returns:

  • (String)

    The formula for elemental critical strength.



1174
# File 'lib/common/spell.rb', line 1174

def elementalCS;   self.elemental_cs_formula;        end

#elementalTDString

Returns the elemental target damage formula.

Examples:

elemental_td_formula = elementalTD

Returns:

  • (String)

    The formula for elemental target damage.



1202
# File 'lib/common/spell.rb', line 1202

def elementalTD;   self.elemental_td_formula;        end

#force_cast(target = nil, arg_options = nil, results_of_interest = nil) ⇒ Object

Casts a target with the specified argument options.

Examples:

force_cast(target, "some_option")

Parameters:

  • target (Object, nil) (defaults to: nil)

    The target to cast. Defaults to nil.

  • arg_options (String, nil) (defaults to: nil)

    Additional options for the cast. Defaults to nil.

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

    Results that are of interest. Defaults to nil.

Returns:

  • (Object)

    The result of the cast operation.



957
958
959
960
961
962
963
964
# File 'lib/common/spell.rb', line 957

def force_cast(target = nil, arg_options = nil, results_of_interest = nil)
  unless arg_options.nil? || arg_options.empty?
    arg_options = "cast #{arg_options}"
  else
    arg_options = "cast"
  end
  cast(target, results_of_interest, arg_options)
end

#force_channel(target = nil, arg_options = nil, results_of_interest = nil) ⇒ Object

Channels a target with the specified argument options.

Examples:

force_channel(target, "some_option")

Parameters:

  • target (Object, nil) (defaults to: nil)

    The target to channel. Defaults to nil.

  • arg_options (String, nil) (defaults to: nil)

    Additional options for the channel. Defaults to nil.

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

    Results that are of interest. Defaults to nil.

Returns:

  • (Object)

    The result of the channel operation.



974
975
976
977
978
979
980
981
# File 'lib/common/spell.rb', line 974

def force_channel(target = nil, arg_options = nil, results_of_interest = nil)
  unless arg_options.nil? || arg_options.empty?
    arg_options = "channel #{arg_options}"
  else
    arg_options = "channel"
  end
  cast(target, results_of_interest, arg_options)
end

#force_evoke(target = nil, arg_options = nil, results_of_interest = nil) ⇒ Object

Evokes a target with the specified argument options.

Examples:

force_evoke(target, "some_option")

Parameters:

  • target (Object, nil) (defaults to: nil)

    The target to evoke. Defaults to nil.

  • arg_options (String, nil) (defaults to: nil)

    Additional options for the evoke. Defaults to nil.

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

    Results that are of interest. Defaults to nil.

Returns:

  • (Object)

    The result of the evoke operation.



991
992
993
994
995
996
997
998
# File 'lib/common/spell.rb', line 991

def force_evoke(target = nil, arg_options = nil, results_of_interest = nil)
  unless arg_options.nil? || arg_options.empty?
    arg_options = "evoke #{arg_options}"
  else
    arg_options = "evoke"
  end
  cast(target, results_of_interest, arg_options)
end

#force_incant(arg_options = nil, results_of_interest = nil) ⇒ Object

Incants with the specified argument options.

Examples:

force_incant("some_option")

Parameters:

  • arg_options (String, nil) (defaults to: nil)

    Additional options for the incant. Defaults to nil.

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

    Results that are of interest. Defaults to nil.

Returns:

  • (Object)

    The result of the incant operation.



1007
1008
1009
1010
1011
1012
1013
1014
# File 'lib/common/spell.rb', line 1007

def force_incant(arg_options = nil, results_of_interest = nil)
  unless arg_options.nil? || arg_options.empty?
    arg_options = "incant #{arg_options}"
  else
    arg_options = "incant"
  end
  cast(nil, results_of_interest, arg_options)
end

#incant=(val) ⇒ Object

Sets the incantation state.

Parameters:

  • val (Boolean)

    true to allow incantation, false to disallow.



642
643
644
# File 'lib/common/spell.rb', line 642

def incant=(val)
  @no_incant = !val
end

#incant?Boolean

Checks if incantation is allowed.

Returns:

  • (Boolean)

    true if incantation is allowed, false otherwise.



635
636
637
# File 'lib/common/spell.rb', line 635

def incant?
  !@no_incant
end

#known?Boolean

Checks if the spell or effect is known based on its number and other conditions.

Examples:

is_known = known?

Returns:

  • (Boolean)

    true if known, false otherwise



543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/common/spell.rb', line 543

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.bard, 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

#manaCostString

Returns the mana cost of the effect.

Examples:

mana_cost = manaCost

Returns:

  • (String)

    The mana cost of the effect as a string.



1125
# File 'lib/common/spell.rb', line 1125

def manaCost;      self.mana_cost_formula    || '0'; end

#max_duration(options = {}) ⇒ Integer

Calculates the maximum duration of the spell based on the caster and target.

Parameters:

  • options (Hash) (defaults to: {})

    options for calculating duration, including :caster and :target.

Returns:

  • (Integer)

    the maximum duration of the spell.



657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
# File 'lib/common/spell.rb', line 657

def max_duration(options = {})
  if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
    if options[:target] and (options[:target].downcase == options[:caster].downcase)
      @duration['self'][:max_duration]
    else
      @duration['target'][:max_duration] || @duration['self'][:max_duration]
    end
  else
    if options[:target] and (options[:target] !~ /^(?:self|#{XMLData.name})$/i)
      @duration['target'][:max_duration] || @duration['self'][:max_duration]
    else
      @duration['self'][:max_duration]
    end
  end
end

#mentalCSString

Returns the mental critical strength formula.

Examples:

mental_cs_formula = mentalCS

Returns:

  • (String)

    The formula for mental critical strength.



1181
# File 'lib/common/spell.rb', line 1181

def mentalCS;      self.mental_cs_formula;           end

#mentalTDString

Returns the mental target damage formula.

Examples:

mental_td_formula = mentalTD

Returns:

  • (String)

    The formula for mental target damage.



1209
# File 'lib/common/spell.rb', line 1209

def mentalTD;      self.mental_td_formula;           end

#minsleftFloat

Alias for timeleft method, returns the time left in minutes.

Examples:

remaining_minutes = minsleft

Returns:

  • (Float)

    the remaining time in minutes



410
411
412
# File 'lib/common/spell.rb', line 410

def minsleft
  self.timeleft
end

#multicastable?(options = {}) ⇒ Boolean

Checks if the spell or effect is multicastable based on the provided options.

Examples:

is_multicastable = multicastable?(caster: 'self', target: 'enemy')

Parameters:

  • options (Hash) (defaults to: {})

    options for checking multicastability, which may include:

    • :caster [String] the name of the caster

    • :target [String] the name of the target

Returns:

  • (Boolean)

    true if multicastable, false otherwise



514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/common/spell.rb', line 514

def multicastable?(options = {})
  if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
    if options[:target] and (options[:target].downcase == options[:caster].downcase)
      @duration['self'][:multicastable]
    else
      if @duration['target'][:multicastable].nil?
        @duration['self'][:multicastable]
      else
        @duration['target'][:multicastable]
      end
    end
  else
    if options[:target] and (options[: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

#physicalASString

Returns the physical attack strength formula.

Examples:

physical_as_formula = physicalAS

Returns:

  • (String)

    The formula for physical attack strength.



1153
# File 'lib/common/spell.rb', line 1153

def physicalAS;    self.physical_as_formula;         end

#physicalDSString

Returns the physical defense strength formula.

Examples:

physical_ds_formula = physicalDS

Returns:

  • (String)

    The formula for physical defense strength.



1167
# File 'lib/common/spell.rb', line 1167

def physicalDS;    self.physical_ds_formula;         end

#putdownvoid

This method returns an undefined value.

Deactivates the spell, setting time left to zero.



689
690
691
692
# File 'lib/common/spell.rb', line 689

def putdown
  self.timeleft = 0
  @active = false
end

#putup(options = {}) ⇒ void

This method returns an undefined value.

Activates the spell, adjusting the time left based on stackability.

Parameters:

  • options (Hash) (defaults to: {})

    options for putting up the spell.



677
678
679
680
681
682
683
684
# File 'lib/common/spell.rb', line 677

def putup(options = {})
  if stackable?(options)
    self.timeleft = [self.timeleft + self.time_per(options), self.max_duration(options)].min
  else
    self.timeleft = [self.time_per(options), self.max_duration(options)].min
  end
  @active = true
end

#refreshable?(options = {}) ⇒ Boolean

Checks if the spell or effect is refreshable based on the provided options.

Examples:

is_refreshable = refreshable?(caster: 'self', target: 'enemy')

Parameters:

  • options (Hash) (defaults to: {})

    options for checking refreshability, which may include:

    • :caster [String] the name of the caster

    • :target [String] the name of the target

Returns:

  • (Boolean)

    true if refreshable, false otherwise



482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/common/spell.rb', line 482

def refreshable?(options = {})
  if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
    if options[:target] and (options[:target].downcase == options[:caster].downcase)
      @duration['self'][:refreshable]
    else
      if @duration['target'][:refreshable].nil?
        @duration['self'][:refreshable]
      else
        @duration['target'][:refreshable]
      end
    end
  else
    if options[:target] and (options[: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

#remainingTime

Returns the remaining time left for the spell as a time object.

Returns:

  • (Time)

    the remaining time left for the spell.



697
698
699
# File 'lib/common/spell.rb', line 697

def remaining
  self.timeleft.as_time
end

#secsleftFloat

Returns the time left in seconds.

Examples:

remaining_seconds = secsleft

Returns:

  • (Float)

    the remaining time in seconds



419
420
421
# File 'lib/common/spell.rb', line 419

def secsleft
  self.timeleft * 60
end

#selfonlyBoolean

Checks if the availability is not ‘all’.

Examples:

is_self_only = selfonly

Returns:

  • (Boolean)

    True if the availability is not ‘all’, false otherwise.



1258
# File 'lib/common/spell.rb', line 1258

def selfonly;      @availability != 'all';           end

#sorcererCSString

Returns the sorcerer critical strength formula.

Examples:

sorcerer_cs_formula = sorcererCS

Returns:

  • (String)

    The formula for sorcerer critical strength.



1195
# File 'lib/common/spell.rb', line 1195

def sorcererCS;    self.sorcerer_cs_formula;         end

#sorcererTDString

Returns the sorcerer target damage formula.

Examples:

sorcerer_td_formula = sorcererTD

Returns:

  • (String)

    The formula for sorcerer target damage.



1223
# File 'lib/common/spell.rb', line 1223

def sorcererTD;    self.sorcerer_td_formula;         end

#spiritCostString

Returns the spirit cost of the effect.

Examples:

spirit_cost = spiritCost

Returns:

  • (String)

    The spirit cost of the effect as a string.



1132
# File 'lib/common/spell.rb', line 1132

def spiritCost;    self.spirit_cost_formula  || '0'; end

#spiritCSString

Returns the spirit critical strength formula.

Examples:

spirit_cs_formula = spiritCS

Returns:

  • (String)

    The formula for spirit critical strength.



1188
# File 'lib/common/spell.rb', line 1188

def spiritCS;      self.spirit_cs_formula;           end

#spiritTDString

Returns the spirit target damage formula.

Examples:

spirit_td_formula = spiritTD

Returns:

  • (String)

    The formula for spirit target damage.



1216
# File 'lib/common/spell.rb', line 1216

def spiritTD;      self.spirit_td_formula;           end

#stackable?(options = {}) ⇒ Boolean

Checks if the spell or effect is stackable based on the provided options.

Examples:

is_stackable = stackable?(caster: 'self', target: 'enemy')

Parameters:

  • options (Hash) (defaults to: {})

    options for checking stackability, which may include:

    • :caster [String] the name of the caster

    • :target [String] the name of the target

Returns:

  • (Boolean)

    true if stackable, false otherwise



450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/common/spell.rb', line 450

def stackable?(options = {})
  if options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
    if options[:target] and (options[:target].downcase == options[:caster].downcase)
      @duration['self'][:stackable]
    else
      if @duration['target'][:stackable].nil?
        @duration['self'][:stackable]
      else
        @duration['target'][:stackable]
      end
    end
  else
    if options[:target] and (options[: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

#stacksBoolean

Checks if the effect is stackable.

Examples:

is_stackable = stacks

Returns:

  • (Boolean)

    True if the effect is stackable, false otherwise.



1237
# File 'lib/common/spell.rb', line 1237

def stacks;        self.stackable?                   end

#staminaCostString

Returns the stamina cost of the effect.

Examples:

stamina_cost = staminaCost

Returns:

  • (String)

    The stamina cost of the effect as a string.



1139
# File 'lib/common/spell.rb', line 1139

def staminaCost;   self.stamina_cost_formula || '0'; end

#time_per(options = {}) ⇒ Float

Calculates the time based on the provided options and evaluates the formula.

Examples:

time_per(caster: 'self', target: 'enemy', activator: 'tap')

Parameters:

  • options (Hash) (defaults to: {})

    options for calculating time, which may include:

    • :line [String] optional line information

Returns:

  • (Float)

    the calculated time in seconds

Raises:

  • (StandardError)

    if the formula cannot be evaluated



363
364
365
366
367
368
369
370
371
372
# File 'lib/common/spell.rb', line 363

def time_per(options = {})
  formula = self.time_per_formula(options)
  if options[:line]
    # line = options[:line] rubocop useless assignment to line
    options[: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

Calculates the time required for a formula based on the provided options.

Examples:

time_per_formula(caster: 'self', target: 'enemy', activator: 'tap')

Parameters:

  • options (Hash) (defaults to: {})

    options for calculating time, which may include:

    • :caster [String] the name of the caster

    • :target [String] the name of the target

    • :activator [String] the type of activator used

Returns:

  • (String)

    the calculated formula as a string



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/common/spell.rb', line 303

def time_per_formula(options = {})
  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 options[:caster] and (options[:caster] !~ /^(?:self|#{XMLData.name})$/i)
    if options[:target] and (options[:target].downcase == options[:caster].downcase)
      formula = @duration['self'][:duration].to_s.dup
    else
      formula = @duration['target'][:duration].dup || @duration['self'][:duration].to_s.dup
    end
    if options[:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i
      if formula =~ can_haz_spell_ranks
        skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{options[:caster]}'].magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
        formula = "(#{formula})/2.0"
      elsif formula =~ /Skills\.(?:magicitemuse|arcanesymbols)/
        skills.each { |skill_name| formula.gsub!(skill_name, "(SpellRanks['#{options[:caster]}'].magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
      end
    elsif options[:activator] =~ /^(invoke|scroll)$/i
      if formula =~ can_haz_spell_ranks
        skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{options[:caster]}'].arcanesymbols.to_i") }
        formula = "(#{formula})/2.0"
      elsif formula =~ /Skills\.(?:magicitemuse|arcanesymbols)/
        skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks['#{options[:caster]}'].arcanesymbols.to_i") }
      end
    else
      skills.each { |skill_name| formula.gsub!(skill_name, "SpellRanks[#{options[:caster].to_s.inspect}].#{skill_name.sub(/^(?:Spells|Skills)\./, '')}.to_i") }
    end
  else
    if options[:target] and (options[: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 options[:activator] =~ /^(#{activator_modifier.keys.join('|')})$/i
      if formula =~ can_haz_spell_ranks
        skills.each { |skill_name| formula.gsub!(skill_name, "(Skills.magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
        formula = "(#{formula})/2.0"
      elsif formula =~ /Skills\.(?:magicitemuse|arcanesymbols)/
        skills.each { |skill_name| formula.gsub!(skill_name, "(Skills.magicitemuse * #{activator_modifier[options[:activator]]}).to_i") }
      end
    elsif options[: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

#timeleftFloat

Note:

If the formula is ‘Spellsong.timeleft’, it retrieves the time left from Spellsong.

Gets the time left for the spell or effect.

Examples:

remaining_time = timeleft

Returns:

  • (Float)

    the remaining time in minutes



391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/common/spell.rb', line 391

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) ⇒ void

This method returns an undefined value.

Sets the time left for the spell or effect.

Examples:

timeleft = 30.0

Parameters:

  • val (Float)

    the value to set for time left



380
381
382
383
# File 'lib/common/spell.rb', line 380

def timeleft=(val)
  @timeleft = val
  @timestamp = Time.now
end

#to_sString

Returns the name of the object as a string.

Returns:

  • (String)

    the name of the object.



649
650
651
# File 'lib/common/spell.rb', line 649

def to_s
  @name.to_s
end