Class: Lich::Common::Spell

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

Overview

Manages spell data and behavior in the Lich system. This class handles loading, casting, and tracking spells.

Examples:

Creating a new spell

spell = Spell.new(xml_spell)

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 from XML data.

Parameters:

  • xml_spell (REXML::Element)

    The XML element representing the spell.

Raises:

  • (StandardError)

    If the XML data is invalid.



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

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 for dynamic bonus and cost retrieval.

Parameters:

  • args (Array)

    The arguments passed to the missing method.

Raises:

  • (NoMethodError)

    If the method is not found.



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
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
# File 'documented/common/spell.rb', line 923

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.



21
22
23
# File 'documented/common/spell.rb', line 21

def active
  @active
end

#availabilityObject (readonly)

Returns the value of attribute availability.



21
22
23
# File 'documented/common/spell.rb', line 21

def availability
  @availability
end

#cast_procObject (readonly)

Returns the value of attribute cast_proc.



21
22
23
# File 'documented/common/spell.rb', line 21

def cast_proc
  @cast_proc
end

#channelObject

Returns the value of attribute channel.



22
23
24
# File 'documented/common/spell.rb', line 22

def channel
  @channel
end

#circleObject (readonly)

Returns the value of attribute circle.



21
22
23
# File 'documented/common/spell.rb', line 21

def circle
  @circle
end

#msgdnObject (readonly)

Returns the value of attribute msgdn.



21
22
23
# File 'documented/common/spell.rb', line 21

def msgdn
  @msgdn
end

#msgupObject (readonly)

Returns the value of attribute msgup.



21
22
23
# File 'documented/common/spell.rb', line 21

def msgup
  @msgup
end

#nameObject (readonly)

Returns the value of attribute name.



21
22
23
# File 'documented/common/spell.rb', line 21

def name
  @name
end

#no_incantObject (readonly)

Returns the value of attribute no_incant.



21
22
23
# File 'documented/common/spell.rb', line 21

def no_incant
  @no_incant
end

#numObject (readonly)

Returns the value of attribute num.



21
22
23
# File 'documented/common/spell.rb', line 21

def num
  @num
end

#persist_on_deathObject (readonly)

Returns the value of attribute persist_on_death.



21
22
23
# File 'documented/common/spell.rb', line 21

def persist_on_death
  @persist_on_death
end

#real_timeObject (readonly)

Returns the value of attribute real_time.



21
22
23
# File 'documented/common/spell.rb', line 21

def real_time
  @real_time
end

#stanceObject

Returns the value of attribute stance.



22
23
24
# File 'documented/common/spell.rb', line 22

def stance
  @stance
end

#timestampObject (readonly)

Returns the value of attribute timestamp.



21
22
23
# File 'documented/common/spell.rb', line 21

def timestamp
  @timestamp
end

#typeObject (readonly)

Returns the value of attribute type.



21
22
23
# File 'documented/common/spell.rb', line 21

def type
  @type
end

Class Method Details

.[](val) ⇒ Spell?

Retrieves a spell by its number or name.

Parameters:

  • val (Integer, String, Spell)

    The spell number, name, or Spell instance.

Returns:

  • (Spell, nil)

    The corresponding Spell instance or nil if not found.



215
216
217
218
219
220
221
222
223
224
225
# File 'documented/common/spell.rb', line 215

def Spell.[](val)
  Spell.load unless @@loaded
  if val.is_a?(Spell)
    val
  elsif (val.is_a?(Integer)) or (val.is_a?(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>

Returns a list of currently active spells.

Returns:

  • (Array<Spell>)

    An array of active Spell instances.



229
230
231
232
233
234
# File 'documented/common/spell.rb', line 229

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 currently active.

Parameters:

  • val (Integer, String)

    The spell number or name.

Returns:

  • (Boolean)

    True if the spell is active, false otherwise.



239
240
241
242
# File 'documented/common/spell.rb', line 239

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

.after_stanceString

Retrieves the current after stance setting.

Returns:

  • (String)

    The current after stance value.



151
152
153
# File 'documented/common/spell.rb', line 151

def Spell.after_stance
  @@after_stance
end

.after_stance=(val) ⇒ Object

Sets the stance to be applied after casting a spell.

Parameters:

  • val (String)

    The stance to set.



145
146
147
# File 'documented/common/spell.rb', line 145

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

.dnmsgsArray<String>

Retrieves all ‘down’ messages for spells.

Returns:

  • (Array<String>)

    An array of ‘down’ messages.



260
261
262
263
# File 'documented/common/spell.rb', line 260

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

.listArray<Spell>

Returns the list of all loaded spells.

Returns:

  • (Array<Spell>)

    An array of all Spell instances.



246
247
248
249
# File 'documented/common/spell.rb', line 246

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

.load(filename = nil) ⇒ Boolean

Loads spell data from an XML file.

Parameters:

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

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

Returns:

  • (Boolean)

    True if loading was successful, false otherwise.

Raises:

  • (StandardError)

    If there is an error during loading.



159
160
161
162
163
164
165
166
167
168
169
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
# File 'documented/common/spell.rb', line 159

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_castObject

Locks the casting process to prevent concurrent casts. This method ensures that only one script can cast at a time.



635
636
637
638
639
640
641
642
643
# File 'documented/common/spell.rb', line 635

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_castObject

Unlocks the casting process, allowing other scripts to cast.



646
647
648
# File 'documented/common/spell.rb', line 646

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

.upmsgsArray<String>

Retrieves all ‘up’ messages for spells.

Returns:

  • (Array<String>)

    An array of ‘up’ messages.



253
254
255
256
# File 'documented/common/spell.rb', line 253

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

Instance Method Details

#_bonusHash

Retrieves the bonus attributes of the spell.

Returns:

  • (Hash)

    A hash of bonus attributes.



910
911
912
# File 'documented/common/spell.rb', line 910

def _bonus
  @bonus.dup
end

#_costHash

Retrieves the cost attributes of the spell.

Returns:

  • (Hash)

    A hash of cost attributes.



916
917
918
# File 'documented/common/spell.rb', line 916

def _cost
  @cost.dup
end

#active?Boolean

Checks if the spell is currently active.

Returns:

  • (Boolean)

    True if the spell is active, false otherwise.



377
378
379
# File 'documented/common/spell.rb', line 377

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

#affordable?(options = {}) ⇒ Boolean

Checks if the spell can be cast based on resource costs.

Parameters:

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

    Options for the check, including caster and target.

Returns:

  • (Boolean)

    True if the spell can be afforded, false otherwise.



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

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 is available for casting.

Parameters:

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

    Options for the check, including caster and target.

Returns:

  • (Boolean)

    True if the spell is available, false otherwise.



528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'documented/common/spell.rb', line 528

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

Retrieves the bolt attack strength of the spell.

Returns:

  • (String)

    The bolt attack strength formula.



1000
# File 'documented/common/spell.rb', line 1000

def boltAS;        self.bolt_as_formula;             end

#boltDSString

Retrieves the bolt defense strength of the spell.

Returns:

  • (String)

    The bolt defense strength formula.



1006
# File 'documented/common/spell.rb', line 1006

def boltDS;        self.bolt_ds_formula;             end

#cast(target = nil, results_of_interest = nil, arg_options = nil, force_stance: nil) ⇒ String?

Casts the spell on a target with optional parameters.

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.

  • force_stance (Boolean, nil) (defaults to: nil)

    Whether to force a specific stance.

Returns:

  • (String, nil)

    The result of the cast or nil if unsuccessful.



656
657
658
659
660
661
662
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
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
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
# File 'documented/common/spell.rb', line 656

def cast(target = nil, results_of_interest = nil, arg_options = 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 (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.is_a?(GameObj)
        cast_cmd += " ##{target.id}"
      elsif target.is_a?(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 && 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
    script.want_downstream = save_want_downstream
    script.want_downstream_xml = save_want_downstream_xml
    @@cast_lock.delete(script)
  end
end

#castProcString

Retrieves the cast procedure of the spell.

Returns:

  • (String)

    The cast procedure.



1036
# File 'documented/common/spell.rb', line 1036

def castProc;      @cast_proc;                       end

#circle_nameString

Retrieves the name of the spell’s circle.

Returns:

  • (String)

    The name of the circle.



972
973
974
# File 'documented/common/spell.rb', line 972

def circle_name
  Spells.get_circle_name(@circle)
end

#circlenameString

Retrieves the name of the spell’s circle.

Returns:

  • (String)

    The name of the circle.



1045
# File 'documented/common/spell.rb', line 1045

def circlename;    self.circle_name;                 end

#clear_on_deathBoolean

Checks if the spell should be cleared upon death.

Returns:

  • (Boolean)

    True if the spell should be cleared, false otherwise.



978
979
980
# File 'documented/common/spell.rb', line 978

def clear_on_death
  !@persist_on_death
end

#commandnil

Retrieves the command associated with the spell.

Returns:

  • (nil)

    Always returns nil.



1042
# File 'documented/common/spell.rb', line 1042

def command;       nil;                              end

#costString

Retrieves the mana cost of the spell.

Returns:

  • (String)

    The mana cost formula.



988
# File 'documented/common/spell.rb', line 988

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

#durationString

for backwards compatiblity Retrieves the duration of the spell.

Returns:

  • (String)

    The duration formula.



985
# File 'documented/common/spell.rb', line 985

def duration;      self.time_per_formula;            end

#elementalCSString

Retrieves the elemental critical strength of the spell.

Returns:

  • (String)

    The elemental critical strength formula.



1012
# File 'documented/common/spell.rb', line 1012

def elementalCS;   self.elemental_cs_formula;        end

#elementalTDString

Retrieves the elemental total damage of the spell.

Returns:

  • (String)

    The elemental total damage formula.



1024
# File 'documented/common/spell.rb', line 1024

def elementalTD;   self.elemental_td_formula;        end

#force_cast(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String?

Forces the casting of the spell, ignoring certain checks.

Parameters:

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

    The target of the spell.

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

    Additional options for casting.

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

    Regex to match results of interest.

  • force_stance (Boolean, nil) (defaults to: nil)

    Whether to force a specific stance.

Returns:

  • (String, nil)

    The result of the cast or nil if unsuccessful.



855
856
857
858
859
860
861
862
# File 'documented/common/spell.rb', line 855

def force_cast(target = nil, arg_options = nil, results_of_interest = nil, force_stance: 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, force_stance: force_stance)
end

#force_channel(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String?

Forces the channeling of the spell, ignoring certain checks.

Parameters:

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

    The target of the spell.

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

    Additional options for channeling.

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

    Regex to match results of interest.

  • force_stance (Boolean, nil) (defaults to: nil)

    Whether to force a specific stance.

Returns:

  • (String, nil)

    The result of the channel or nil if unsuccessful.



870
871
872
873
874
875
876
877
# File 'documented/common/spell.rb', line 870

def force_channel(target = nil, arg_options = nil, results_of_interest = nil, force_stance: 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, force_stance: force_stance)
end

#force_evoke(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String?

Forces the evocation of the spell, ignoring certain checks.

Parameters:

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

    The target of the spell.

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

    Additional options for evocation.

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

    Regex to match results of interest.

  • force_stance (Boolean, nil) (defaults to: nil)

    Whether to force a specific stance.

Returns:

  • (String, nil)

    The result of the evoke or nil if unsuccessful.



885
886
887
888
889
890
891
892
# File 'documented/common/spell.rb', line 885

def force_evoke(target = nil, arg_options = nil, results_of_interest = nil, force_stance: 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, force_stance: force_stance)
end

#force_incant(arg_options = nil, results_of_interest = nil, force_stance: nil) ⇒ String?

Forces the incantation of the spell, ignoring certain checks.

Parameters:

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

    Additional options for incantation.

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

    Regex to match results of interest.

  • force_stance (Boolean, nil) (defaults to: nil)

    Whether to force a specific stance.

Returns:

  • (String, nil)

    The result of the incantation or nil if unsuccessful.



899
900
901
902
903
904
905
906
# File 'documented/common/spell.rb', line 899

def force_incant(arg_options = nil, results_of_interest = nil, force_stance: 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, force_stance: force_stance)
end

#incant=(val) ⇒ Object

Sets whether the spell requires an incantation.

Parameters:

  • val (Boolean)

    True if incantation is required, false otherwise.



556
557
558
# File 'documented/common/spell.rb', line 556

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

#incant?Boolean

Checks if the spell requires an incantation.

Returns:

  • (Boolean)

    True if the spell requires an incantation, false otherwise.



550
551
552
# File 'documented/common/spell.rb', line 550

def incant?
  !@no_incant
end

#known?Boolean

Checks if the spell is known by the caster.

Returns:

  • (Boolean)

    True if the spell is known, false otherwise.



464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
# File 'documented/common/spell.rb', line 464

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

Retrieves the mana cost of the spell.

Returns:

  • (String)

    The mana cost formula.



991
# File 'documented/common/spell.rb', line 991

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

#max_duration(options = {}) ⇒ Float

Retrieves the maximum duration of the spell.

Parameters:

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

    Options for the check, including caster and target.

Returns:

  • (Float)

    The maximum duration in minutes.



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'documented/common/spell.rb', line 569

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

Retrieves the mental critical strength of the spell.

Returns:

  • (String)

    The mental critical strength formula.



1015
# File 'documented/common/spell.rb', line 1015

def mentalCS;      self.mental_cs_formula;           end

#mentalTDString

Retrieves the mental total damage of the spell.

Returns:

  • (String)

    The mental total damage formula.



1027
# File 'documented/common/spell.rb', line 1027

def mentalTD;      self.mental_td_formula;           end

#minsleftFloat

Retrieves the remaining time in minutes.

Returns:

  • (Float)

    The time left in minutes.



359
360
361
# File 'documented/common/spell.rb', line 359

def minsleft
  self.timeleft
end

#multicastable?(options = {}) ⇒ Boolean

Checks if the spell can be multicast.

Parameters:

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

    Options for the check, including caster and target.

Returns:

  • (Boolean)

    True if the spell is multicastable, false otherwise.



438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'documented/common/spell.rb', line 438

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

Retrieves the physical attack strength of the spell.

Returns:

  • (String)

    The physical attack strength formula.



1003
# File 'documented/common/spell.rb', line 1003

def physicalAS;    self.physical_as_formula;         end

#physicalDSString

Retrieves the physical defense strength of the spell.

Returns:

  • (String)

    The physical defense strength formula.



1009
# File 'documented/common/spell.rb', line 1009

def physicalDS;    self.physical_ds_formula;         end

#putdownObject

Deactivates the spell and resets its duration.



597
598
599
600
# File 'documented/common/spell.rb', line 597

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

#putup(options = {}) ⇒ Object

Activates the spell and sets its duration.

Parameters:

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

    Options for the activation, including caster and target.



587
588
589
590
591
592
593
594
# File 'documented/common/spell.rb', line 587

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 can be refreshed.

Parameters:

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

    Options for the check, including caster and target.

Returns:

  • (Boolean)

    True if the spell is refreshable, false otherwise.



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'documented/common/spell.rb', line 411

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

#remainingString

Retrieves the remaining time formatted as a time string.

Returns:

  • (String)

    The remaining time as a formatted string.



604
605
606
# File 'documented/common/spell.rb', line 604

def remaining
  self.timeleft.as_time
end

#secsleftFloat

Retrieves the remaining time in seconds.

Returns:

  • (Float)

    The time left in seconds.



365
366
367
# File 'documented/common/spell.rb', line 365

def secsleft
  self.timeleft * 60
end

#selfonlyBoolean

Checks if the spell is self-only.

Returns:

  • (Boolean)

    True if the spell is self-only, false otherwise.



1048
# File 'documented/common/spell.rb', line 1048

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

#sorcererCSString

Retrieves the sorcerer critical strength of the spell.

Returns:

  • (String)

    The sorcerer critical strength formula.



1021
# File 'documented/common/spell.rb', line 1021

def sorcererCS;    self.sorcerer_cs_formula;         end

#sorcererTDString

Retrieves the sorcerer total damage of the spell.

Returns:

  • (String)

    The sorcerer total damage formula.



1033
# File 'documented/common/spell.rb', line 1033

def sorcererTD;    self.sorcerer_td_formula;         end

#spiritCostString

Retrieves the spirit cost of the spell.

Returns:

  • (String)

    The spirit cost formula.



994
# File 'documented/common/spell.rb', line 994

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

#spiritCSString

Retrieves the spirit critical strength of the spell.

Returns:

  • (String)

    The spirit critical strength formula.



1018
# File 'documented/common/spell.rb', line 1018

def spiritCS;      self.spirit_cs_formula;           end

#spiritTDString

Retrieves the spirit total damage of the spell.

Returns:

  • (String)

    The spirit total damage formula.



1030
# File 'documented/common/spell.rb', line 1030

def spiritTD;      self.spirit_td_formula;           end

#stackable?(options = {}) ⇒ Boolean

Checks if the spell can be stacked.

Parameters:

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

    Options for the check, including caster and target.

Returns:

  • (Boolean)

    True if the spell is stackable, false otherwise.



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'documented/common/spell.rb', line 384

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 spell is stackable.

Returns:

  • (Boolean)

    True if the spell is stackable, false otherwise.



1039
# File 'documented/common/spell.rb', line 1039

def stacks;        self.stackable?                   end

#staminaCostString

Retrieves the stamina cost of the spell.

Returns:

  • (String)

    The stamina cost formula.



997
# File 'documented/common/spell.rb', line 997

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

#time_per(options = {}) ⇒ Float

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

Parameters:

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

    Options for the calculation.

Returns:

  • (Float)

    The calculated time in seconds.



323
324
325
326
327
328
329
330
331
332
# File 'documented/common/spell.rb', line 323

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 spell based on various factors.

Parameters:

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

    Options for the calculation, including caster and target.

Returns:

  • (String)

    The formula for time calculation.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'documented/common/spell.rb', line 268

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

Retrieves the remaining time for the spell.

Returns:

  • (Float)

    The time left in minutes.



343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'documented/common/spell.rb', line 343

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

Sets the remaining time for the spell.

Parameters:

  • val (Float)

    The time left in minutes.



336
337
338
339
# File 'documented/common/spell.rb', line 336

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

#to_sString

Returns the name of the spell as a string.

Returns:

  • (String)

    The name of the spell.



562
563
564
# File 'documented/common/spell.rb', line 562

def to_s
  @name.to_s
end