Skip to main content

Overview

The Spell class manages spell information, tracking, and casting in Lich. It provides methods to query spell data, check active spells, cast spells with proper preparation and stance management, and track spell durations. Spells are loaded from effect-list.xml and include information about:
  • Mana, spirit, and stamina costs
  • Spell durations and formulas
  • Active spell tracking with timers
  • Type classification (attack, defense, utility)
  • Availability (self-cast, group, all)

Quick Start

# Look up a spell by number or name
spell = Spell[101]  # Spirit Warding I
spell = Spell['Spirit Warding']

# Cast a spell
Spell[101].cast

# Check if a spell is active
if Spell[101].active?
  echo "Spirit Warding is up"
end

# Get all active spells
Spell.active.each do |spell|
  echo "#{spell.name}: #{spell.timeleft.round(1)} minutes left"
end

Instance Attributes

num
Integer
Spell number (e.g. 101, 202, 1711)
name
String
Spell name (e.g. “Spirit Warding I”, “Major Sanctuary”)
type
String
Spell type: “attack”, “defense”, “utility”, etc.
circle
String
Spell circle number as string (“1” for Minor Spirit, “2” for Major Spirit, etc.)
active
Boolean
Whether the spell is currently active on you
availability
String
Who can receive the spell: “self-cast”, “group”, or “all”
stance
Boolean
Whether this spell requires offensive stance to cast
channel
Boolean
Whether this spell uses CHANNEL instead of CAST

Class Methods - Lookup

Spell[val]

Looks up a spell by number, name, or pattern.
val
Integer | String | Regexp
required
Spell number, name, or pattern to match
# By number
spell = Spell[101]

# By exact name
spell = Spell['Spirit Warding I']

# By partial name
spell = Spell['Spirit Warding']

# By pattern in messages
spell = Spell[/warding/i]
return
Spell
Spell object, or nil if not found

Spell.list

Returns all loaded spells.
Spell.list.each do |spell|
  echo "#{spell.num}: #{spell.name}"
end
return
Array<Spell>
Array of all Spell objects

Spell.active

Returns all currently active spells.
Spell.active.each do |spell|
  mins = spell.timeleft.round(1)
  echo "#{spell.name}: #{mins}m remaining"
end
return
Array<Spell>
Array of active Spell objects

Spell.active?(val)

Checks if a specific spell is active.
val
Integer | String
required
Spell number or name
if Spell.active?(101)
  echo "Spirit Warding I is active"
end

if Spell.active?('Major Sanctuary')
  echo "In sanctuary"
end
return
Boolean
True if the spell is active

Instance Methods - Information

active?

Checks if this spell is currently active.
spell = Spell[101]
if spell.active?
  echo "#{spell.name} is up"
end
return
Boolean
True if spell is active with time remaining

known?

Checks if you know this spell (have trained enough ranks).
spell = Spell[120]
if spell.known?
  echo "You know #{spell.name}"
else
  echo "You don't know this spell yet"
end
return
Boolean
True if you have the ranks to cast this spell

available?(options = )

Checks if the spell is available to cast on the target.
options
Hash
Optional hash with :target and :caster keys
# Can you cast on yourself?
if spell.available?
  spell.cast
end

# Can you cast on someone else?
if spell.available?(target: 'Player')
  spell.cast('Player')
end
return
Boolean
True if spell can be cast with given parameters

timeleft

Returns remaining duration in minutes.
spell = Spell[101]
mins = spell.timeleft
echo "#{mins.round(1)} minutes remaining"
return
Float
Minutes remaining, or 0.0 if not active

secsleft

Returns remaining duration in seconds.
secs = Spell[101].secsleft
echo "#{secs.round} seconds remaining"
return
Float
Seconds remaining

circle_name

Returns the name of the spell circle.
spell = Spell[101]
echo spell.circle_name  # => "Minor Spirit"
return
String
Name of the spell circle

Instance Methods - Costs

mana_cost(options = )

Calculates the mana cost for casting this spell.
options
Hash
Options including :target, :caster, :multicast
spell = Spell[430]
cost = spell.mana_cost
echo "Mana cost: #{cost}"

# With multicast
cost = spell.mana_cost(multicast: 3)
echo "Multicast x3 cost: #{cost}"
return
Integer
Mana cost in points

spirit_cost(options = )

Calculates the spirit cost (for Paladin spells).
cost = Spell[1611].spirit_cost
echo "Spirit cost: #{cost}"
return
Integer
Spirit cost in points

stamina_cost(options = )

Calculates the stamina cost (for Monk spells with Mental Acuity).
cost = Spell[1209].stamina_cost
echo "Stamina cost: #{cost}"
return
Integer
Stamina cost in points

affordable?(options = )

Checks if you have enough resources to cast this spell.
spell = Spell[430]
if spell.affordable?
  spell.cast('target')
else
  echo "Not enough mana"
end
return
Boolean
True if you can afford to cast the spell

Instance Methods - Casting

cast(target = nil, results_of_interest = nil, arg_options = nil, force_stance: nil)

Casts the spell with automatic preparation, stance management, and error handling.
target
String | GameObj | Integer
Target for the spell (GameObj, ID, or name). Use “target” for current target.
results_of_interest
Regexp
Additional patterns to watch for in results
arg_options
String
Additional casting options (e.g. “at door”, “channel”, “evoke”)
force_stance
Boolean
Override stance behavior (true = force offensive, false = never change)
# Simple cast
Spell[101].cast

# Cast at target
Spell[410].cast('target')

# Cast at specific NPC
goblin = GameObj['goblin']
Spell[410].cast(goblin)

# Cast with options
Spell[405].cast('target', nil, 'evoke')

# Channel instead of cast
Spell[1700].cast(nil, nil, 'channel')
return
String | Boolean
Result text from the cast, or false on failure

force_cast(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil)

Forces CAST command (never uses INCANT).
Spell[101].force_cast

force_channel(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil)

Forces CHANNEL command.
Spell[1700].force_channel

force_evoke(target = nil, arg_options = nil, results_of_interest = nil, force_stance: nil)

Forces EVOKE command.
Spell[405].force_evoke('target')

force_incant(arg_options = nil, results_of_interest = nil, force_stance: nil)

Forces INCANT command.
Spell[901].force_incant

Instance Methods - Duration Management

putup(options = )

Manually activates the spell with calculated duration.
options
Hash
Options for duration calculation
# Activate spell with standard duration
Spell[101].putup

# With caster/target options
Spell[120].putup(caster: 'Healer', target: 'self')

putdown

Manually deactivates the spell and sets timeleft to 0.
Spell[101].putdown

stackable?(options = )

Checks if the spell duration stacks when recast.
if Spell[101].stackable?
  echo "Can stack this spell"
end
return
Boolean
True if spell stacks

refreshable?(options = )

Checks if the spell duration refreshes when recast.
if Spell[120].refreshable?
  echo "Can refresh this spell"
end
return
Boolean
True if spell refreshes

Class Methods - Utilities

Spell.load(filename = nil)

Loads spell data from XML file.
filename
String
Path to effect-list.xml (defaults to DATA_DIR)
# Load from default location
Spell.load

# Load from custom file
Spell.load('/path/to/custom-effects.xml')
return
Boolean
True on success, false on failure

Spell.lock_cast / Spell.unlock_cast

Acquires/releases the casting lock to prevent interference between scripts.
Spell.lock_cast
begin
  Spell[101].cast
  Spell[102].cast
ensure
  Spell.unlock_cast
end

Spell.after_stance

Gets or sets the stance to return to after casting.
# Set default return stance
Spell.after_stance = 'guarded'

# Check current setting
echo Spell.after_stance

Examples

Check and refresh buffs

buffs = [101, 102, 103, 120]  # Spirit Warding I-II, Spirit Defense, Lesser Shroud

buffs.each do |num|
  spell = Spell[num]
  
  if spell.known?
    if !spell.active? || spell.timeleft < 5
      if spell.affordable?
        spell.cast
        waitcastrt?
      end
    end
  end
end

Cast attack spell on targets

attack_spell = Spell[410]  # Elemental Wave

if attack_spell.known? && attack_spell.affordable?
  GameObj.targets.each do |target|
    next if target.status == 'dead'
    
    attack_spell.cast(target)
    waitcastrt?
    waitrt?
  end
end

Monitor active spells

loop do
  echo "\n=== Active Spells ==="
  
  Spell.active.sort_by(&:num).each do |spell|
    mins = spell.timeleft.round(1)
    
    if mins < 5
      echo "#{spell.name}: #{mins}m (LOW)"
    else
      echo "#{spell.name}: #{mins}m"
    end
  end
  
  sleep 30
end

Group casting with cost checks

group_spells = [120, 140, 219]  # Lesser Shroud, Wall of Force, Spell Shield

group_spells.each do |num|
  spell = Spell[num]
  
  next unless spell.known?
  next unless spell.available?(target: 'Groupmate')
  next unless spell.affordable?
  
  spell.cast('Groupmate')
  waitcastrt?
  waitrt?
end

Build docs developers (and LLMs) love