Skip to main content

Spell script structure

Each spell has a Lua file in scripts/actions/spells/ organized by spell school. Spells expose standard handler functions.
-- scripts/actions/spells/black/blizzard.lua (actual source)
---@type TSpell
local spellObject = {}

spellObject.onMagicCastingCheck = function(caster, target, spell)
    return 0  -- 0 means the spell can be cast
end

spellObject.onSpellCast = function(caster, target, spell)
    return xi.spells.damage.useDamageSpell(caster, target, spell)
end

return spellObject

Spell handler reference

HandlerSignatureWhen called
onMagicCastingCheck(caster, target, spell)Validates whether the spell can be cast; return 0 to allow, non-zero to deny
onSpellCast(caster, target, spell)Main spell execution; return the damage dealt

Damage spell calculations

scripts/globals/spells/damage_spell.lua contains xi.spells.damage, used for all direct-damage magic (Black, White, Dark, Ninjutsu). Damage parameters are stored in a table indexed by spell ID:
-- Structure: [spellId] = { StatUsed, bonusMacc, npcPower, npcMultiplier,
--                          pcPower, inflexionPoint, M0, M50, M100, M200, M300, M400, M500 }
[xi.magic.spell.FIRE   ] = { xi.mod.INT,  0,   35, 1,   55,  46, 1.4, 1, 0, 0, 0, 0, 0 },
[xi.magic.spell.FIRE_II] = { xi.mod.INT, 10,  133, 1,  160, 155, 2.4, 1.7, 1, 0, 0, 0, 0 },
[xi.magic.spell.FIRE_III]= { xi.mod.INT, 20,  295, 1.5, 290, 320, 3.1, 2.7, 1.85, 1, 0, 0, 0 },
-- ...
Call xi.spells.damage.useDamageSpell(caster, target, spell) to run the full pipeline including:
  • Base power lookup from the parameter table
  • Stat modifier (INT/MND) application
  • Magic accuracy vs. magic evasion (resist rate from magic_hit_rate.lua)
  • Day/weather bonus (damage_multipliers.lua)
  • Magic burst multiplier (magicburst.lua)
  • Stoneskin, Phalanx, One For All absorption
  • Final HP adjustment

Cure spells

Cure calculations live in scripts/globals/magic.lua:
-- Get the caster's raw cure power
getCurePower(caster, isBlueMagic)
-- power = floor(MND/2) + floor(VIT/4) + HealingMagicSkill

-- Apply all potency modifiers to a base cure amount
getCureFinal(caster, spell, basecure, minCure, isBlueMagic)
-- Applies: CURE_POTENCY, CURE_POTENCY_II, day/weather, RAPTURE, DIVINE_SEAL

Enfeebling and enhancing spells

Helper modules in scripts/globals/spells/ cover other spell families:
FileCovers
enfeebling_spell.luaSlow, Paralyze, Blind, etc.
enhancing_spell.luaProtect, Shell, Haste, etc.
enfeebling_song.luaBard enfeebling songs
enhancing_song.luaBard enhancing songs
enhancing_ninjutsu.luaNinjutsu enhancing buffs
enhancing_teleport.luaTeleport/Warp spells
absorb_spell.luaAbsorb-TP, Absorb-STR, etc.

Blue magic

scripts/globals/bluemagic.lua provides xi.spells.blue with calculation routines for Blue Mage spells. Blue magic spells can be physical or magical and reuse the mob skill calculation pipeline.
-- TP modifier enum (currently unused in most spells)
xi.spells.blue.tpMod =
{
    NONE          = 0,
    CRITICAL      = 1,
    DAMAGE        = 2,
    ACC           = 3,
    ATTACK        = 4,
    DURATION      = 5,
    EFFECT_CHANCE = 6,
}
Blue magic physical spells use WSC alpha scaling based on the caster’s level:
-- alpha calculation (level-dependent WSC multiplier)
-- level <= 60: ceil(100 - level/6) / 100
-- level <= 75: ceil(100 - (level-40)/2) / 100
-- level  > 75: 0.83
Individual blue magic spell scripts live in scripts/actions/spells/blue/ and call into xi.spells.blue helpers.

Job abilities

scripts/globals/ability.lua provides xi.ability.adjustDamage(), which applies universal modifiers to job ability damage:
xi.ability.adjustDamage = function(dmg, attacker, skill, target, skilltype, skillparam, shadowbehav)
    -- Handles: perfect dodge, all_miss, shadows, anticipate, third_eye
    -- Returns adjusted damage
end
Individual job ability scripts live in scripts/actions/abilities/ and return a table with:
local abilityObject = {}

abilityObject.onAbilityCheck = function(player, target, ability)
    -- Return 0 to allow, non-zero message ID to deny
    return 0
end

abilityObject.onUseAbility = function(player, target, ability)
    -- Execute ability effect
end

return abilityObject

Ninjutsu

Ninjutsu tools are checked and consumed automatically by the C++ core. Ninjutsu spell scripts follow the same structure as regular spells but may reference scripts/globals/spells/enhancing_ninjutsu.lua for buff ninjutsu.

Spell object API

The spell object passed to all spell handlers is a CLuaSpell (bound in src/map/lua/lua_spell.cpp).
spell:getID()         -- spell ID constant
spell:getElement()    -- elemental constant (xi.element.FIRE, etc.)
spell:getSkillType()  -- magic skill type
spell:getTP()         -- TP cost
spell:getMP()         -- MP cost
spell:getTargetID()   -- target entity ID
spell:isMagicBurst()  -- boolean
spell:setTotalDamage(dmg)
spell:setCasterAnimationPacket(anim)

Build docs developers (and LLMs) love