Skip to main content

Mob script structure

Mob scripts live in the mobs/ subdirectory of a zone folder. A mob script file returns a local entity table with handler functions and configuration tables.
-- scripts/zones/East_Ronfaure/mobs/Bigmouth_Billy.lua
local ID = zones[xi.zone.EAST_RONFAURE]
---@type TMobEntity
local entity = {}

-- Lottery/placeholder list: { [phId] = nmId, ... }
entity.phList =
{
    [ID.mob.BIGMOUTH_BILLY - 2] = ID.mob.BIGMOUTH_BILLY,
    [ID.mob.BIGMOUTH_BILLY - 1] = ID.mob.BIGMOUTH_BILLY,
}

-- Possible NM spawn positions
entity.spawnPoints =
{
    { x = 453.625, y = -18.436, z = -127.048 },
    { x = 403.967, y = -36.822, z = -16.285  },
    -- ...
}

-- Called when the mob dies
entity.onMobDeath = function(mob, player, optParams)
    xi.hunts.checkHunt(mob, player, 151)
end

return entity

Mob handler reference

HandlerSignatureWhen called
onMobSpawn(mob)When the mob spawns
onMobDeath(mob, player, optParams)When the mob dies; player is the killer or last claimant
onMobDespawn(mob)When the mob despawns (after death timer)
onMobEngaged(mob, target)When the mob first enters combat
onMobDisengage(mob)When the mob loses its target
onMobFight(mob, target)Called repeatedly while the mob is in combat
onMobRoam(mob)Called while the mob is roaming
onMobRoamAction(mob)Called for roaming mob actions
onAdditionalEffect(attacker, target, attack, damage)Additional hit effect proc
onAdditionalEffectMiss(attacker, target, attack)Additional effect on a miss

Placeholder (PH) and lottery NM system

The PH system uses entity.phList and xi.mob.phOnDespawn to spawn lottery NMs when a placeholder mob dies.

phList format

-- Maps each placeholder mob ID to the NM it can spawn
entity.phList =
{
    [phMobId_1] = nmMobId,
    [phMobId_2] = nmMobId,
}

Hooking phOnDespawn

In the placeholder mob’s own script or in Zone.lua, call xi.mob.phOnDespawn from the placeholder’s onDespawn:
entity.onMobDespawn = function(mob)
    xi.mob.phOnDespawn(mob, ID.mob.SOME_NM, 10, 120, {
        -- chance    = 10 (10% pop chance)
        -- cooldown  = 120 (120-second NM respawn cooldown)
        -- immediate = false (wait for next PH pop time)
        -- dayOnly   = false
        -- nightOnly = false
    })
end

NM spawn point randomization

When entity.spawnPoints is defined, the framework calls xi.mob.updateNMSpawnPoint automatically on the NM’s pop to choose a random position from the table. You can also call it manually or supply an override table:
-- Use spawn points defined in the NM's entity.spawnPoints
xi.mob.updateNMSpawnPoint(mob)

-- Supply spawn points directly
xi.mob.updateNMSpawnPoint(mob, {
    { x = 100.0, y = -10.0, z = 200.0 },
    { x = 110.0, y = -10.0, z = 210.0 },
})

Global mob death hook

scripts/globals/mobs.lua defines xi.mob.onMobDeathEx, which is called by the core for every mob death:
xi.mob.onMobDeathEx = function(mob, player, isKiller, isWeaponSkillKill)
    -- extend here for server-wide death handling
end

Mob entity API

The mob object is a CBaseEntity (same as players and NPCs) with additional mob-specific methods:
mob:getID()                    -- entity ID
mob:getName()                  -- mob name string
mob:getZoneID()                -- zone ID
mob:getZoneName()              -- zone name string
mob:isSpawned()                -- true if currently alive/spawned
mob:isAlive()                  -- true if HP > 0
mob:spawn()                    -- force-spawn the mob
mob:getRespawnTime()           -- time until next respawn
mob:setSpawn(x, y, z, rot)     -- set spawn position
mob:getXPos() / :getYPos() / :getZPos()
mob:addEnmity(target, ce, ve)  -- add enmity toward target
mob:updateClaim(player)        -- claim mob to player
mob:setLocalVar(name, value)   -- per-mob local variable
mob:getLocalVar(name)          -- get per-mob local variable
mob:addListener(event, id, fn) -- attach event listener
mob:removeListener(id)         -- remove event listener
mob:getMod(modId)              -- get a modifier value
mob:setMod(modId, value)       -- set a modifier value
mob:hasStatusEffect(effectId)  -- check for status effect
mob:lookAt(pos)                -- face toward a position
mob:getHP() / :getHPP()        -- HP / HP percentage
mob:takeDamage(dmg, source, atkType, dmgType)

Mob skills

Mob skill scripts live in scripts/actions/mobskills/. They are linked to mobs through the SQL mob_skill_lists table rather than in the Lua file itself.
-- scripts/actions/mobskills/someskill.lua
require('scripts/globals/mobskills')

local mobskillObject = {}
mobskillObject.id   = 456
mobskillObject.name = 'Some Skill'

-- Return 0 to allow the skill, non-zero to cancel
mobskillObject.onMobSkillCheck = function(target, mob, skill)
    return 0
end

-- Execute the skill
mobskillObject.onMobWeaponskill = function(target, mob, skill)
    local numhits = 1
    local dmgmod  = 1.5
    local info = xi.mobskills.mobPhysicalMove(
        mob, target, skill, numhits, dmgmod,
        xi.mobskills.physicalTpBonus.NO_EFFECT
    )
    local dmg = xi.mobskills.mobFinalAdjustments(
        info.dmg, mob, skill, target,
        xi.attackType.PHYSICAL, xi.damageType.SLASHING,
        xi.mobskills.shadowBehavior.NUMSHADOWS_1
    )
    target:takeDamage(dmg, mob, xi.attackType.PHYSICAL, xi.damageType.SLASHING)
    return dmg
end

return mobskillObject

SQL relationship

Mob behavior is driven by two key SQL tables:
TablePurpose
mob_poolsDefines mob stats, drop rates, skill lists, and elemental resistances
mob_spawn_pointsDefines where and when mobs spawn in each zone
mob_skill_listsMaps mob pool IDs to the set of TP moves they can use
mob_droplistDrop table entries per mob pool
The Lua entity table in mobs/ supplements and overrides these SQL values for named mobs and NMs.
For placeholder NMs, the NM’s mob ID is always the last entry in the phList value (not a key). Placeholder IDs are the keys.

Build docs developers (and LLMs) love