Skip to main content
Combat calculations in LandSandBoat are split across several files in scripts/globals/combat/ and the top-level globals. The C++ core drives the attack loop and calls into Lua for damage, effects, and hit logic.

Combat module overview

scripts/globals/combat/
  physical_utilities.lua      # fSTR, WSC, fTP, pDIF, multi-attack
  physical_hit_rate.lua       # hit/miss calculation, anticipate
  magic_hit_rate.lua          # magic accuracy, resist rates
  magic_aoe.lua               # AoE splash damage
  damage_multipliers.lua      # day/weather/element multipliers
  damage_additions.lua        # bonus damage sources
  ranged_utilities.lua        # ranged attack calculations
  tp.lua                      # TP gain calculations
  counter.lua                 # counter-attack logic
  ability_aoe.lua             # ability AoE handling
  action_additional_effect_damage.lua   # additional effect (damage) proc
  action_additional_effect_status.lua   # additional effect (status) proc
  action_mobskill_status_effect.lua     # mob skill status effect proc
  entity_behavior.lua         # shared behavior helpers
  treasure_hunter.lua         # treasure hunter tier logic

Physical damage formula

From physical_utilities.lua:
Damage per hit = floor((D + fSTR + WSC) * fTP) * pDIF
  • D — base weapon damage
  • fSTR — STR-derived bonus (level-dependent)
  • WSC — weapon skill coefficient (stat * alpha)
  • fTP — TP multiplier
  • pDIF — attack vs. defense ratio with caps per weapon type
xi.combat.physical.pDifWeaponCapTable defines the pre-ratio and final pDIF caps per weapon skill type:
xi.combat.physical.pDifWeaponCapTable =
{
    [xi.skill.HAND_TO_HAND] = { 3.875, 3.5  },
    [xi.skill.DAGGER       ] = { 3.625, 3.25 },
    [xi.skill.SWORD        ] = { 3.625, 3.25 },
    [xi.skill.GREAT_SWORD  ] = { 4.125, 3.75 },
    [xi.skill.SCYTHE       ] = { 4.375, 4    },
    -- ...
}

Weapon skills

scripts/globals/weaponskills.lua provides all shared weapon skill calculations. Individual weapon skill scripts in scripts/actions/weaponskills/ call these helpers.

Key functions

-- Multi-attack resolution (double/triple/quad attack)
-- Called internally by the WS calculation loop
local function getMultiAttacks(attacker, target, wsParams, firstHit, offHand)
    local doubleRate = attacker:getMod(xi.mod.DOUBLE_ATTACK)
                     + attacker:getMerit(xi.merit.DOUBLE_ATTACK_RATE)
    -- ...
end
A weapon skill script defines a wsParams table and calls the calculation:
-- scripts/actions/weaponskills/fast_blade.lua (actual source)
---@type TWeaponSkill
local weaponskillObject = {}

weaponskillObject.onUseWeaponSkill = function(player, target, wsID, tp, primary, action, taChar)
    local params = {}
    params.numHits = 2
    params.ftpMod  = { 1.0, 1.5, 2.0 }  -- TP multiplier at 100/200/300 TP
    params.str_wsc = 0.2
    params.dex_wsc = 0.2

    if xi.settings.main.USE_ADOULIN_WEAPON_SKILL_CHANGES then
        params.str_wsc = 0.4
        params.dex_wsc = 0.4
    end

    local damage, criticalHit, tpHits, extraHits =
        xi.weaponskills.doPhysicalWeaponskill(player, target, wsID, params, tp, action, primary, taChar)

    return tpHits, extraHits, criticalHit, damage
end

return weaponskillObject

Shadow absorption

Shadow (Utsusemi) and Blink absorption is handled automatically in weaponskills.lua. Utsusemi is checked first; if it has shadows it absorbs and reduces by one. Blink has an 80% chance to absorb if Utsusemi is already down:
local function shadowAbsorb(target)
    local targetShadows = target:getMod(xi.mod.UTSUSEMI)
    local shadowType    = xi.mod.UTSUSEMI

    if targetShadows == 0 then
        if math.random(1, 100) <= 80 then
            targetShadows = target:getMod(xi.mod.BLINK)
            shadowType    = xi.mod.BLINK
        end
    end
    -- ...
end

Mob skills

scripts/globals/mobskills.lua provides calculation routines for monster TP moves. Individual mob skill scripts live in scripts/actions/mobskills/.

Enums

-- Shadow behavior constants
xi.mobskills.shadowBehavior =
{
    IGNORE_SHADOWS = 0,
    NUMSHADOWS_1   = 1,
    NUMSHADOWS_2   = 2,
    NUMSHADOWS_3   = 3,
    NUMSHADOWS_4   = 4,
    WIPE_SHADOWS   = 999,
}

-- Physical TP bonus types
xi.mobskills.physicalTpBonus =
{
    NO_EFFECT   = 0,
    ACC_VARIES  = 1,
    ATK_VARIES  = 2,
    DMG_VARIES  = 3,
    CRIT_VARIES = 4,
}

-- Magical TP bonus types
xi.mobskills.magicalTpBonus =
{
    NO_EFFECT  = 0,
    MACC_BONUS = 1,
    MAB_BONUS  = 2,
    DMG_BONUS  = 3,
}

-- Drain types
xi.mobskills.drainType =
{
    HP = 0,
    MP = 1,
    TP = 2,
}

Mob skill script structure

-- scripts/actions/mobskills/some_mob_skill.lua (simplified example)
require('scripts/globals/mobskills')

local mobskillObject = {}
mobskillObject.id   = 123
mobskillObject.name = 'Some Mob Skill'

mobskillObject.onMobSkillCheck = function(target, mob, skill)
    return 0  -- 0 = skill can execute
end

mobskillObject.onMobWeaponskill = function(target, mob, skill)
    local numhits = 2
    local dmgmod  = 1
    local info    = xi.mobskills.mobPhysicalMove(
        mob, target, skill, numhits, dmgmod,
        xi.mobskills.physicalTpBonus.DMG_VARIES,
        1.0, 1.5, 2.0
    )
    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

Skillchains

Skillchain properties are defined in physical_utilities.lua as a table mapping xi.skillchainType values to their elemental resonance arrays:
-- [Skillchain type            ] = { Fire, Ice, Wind, Earth, Thunder, Water, Light, Dark }
[xi.skillchainType.FUSION      ] = { 1, 0, 0, 0, 0, 0, 1, 0 },  -- Lv2: Fire & Light
[xi.skillchainType.DISTORTION  ] = { 0, 1, 0, 0, 0, 1, 0, 0 },  -- Lv2: Ice & Water
[xi.skillchainType.FRAGMENTATION] = { 0, 0, 1, 0, 1, 0, 0, 0 }, -- Lv2: Wind & Thunder
[xi.skillchainType.GRAVITATION ] = { 0, 0, 0, 1, 0, 0, 0, 1 },  -- Lv2: Earth & Dark
[xi.skillchainType.LIGHT       ] = { 1, 0, 1, 0, 1, 0, 1, 0 },  -- Lv3
[xi.skillchainType.DARKNESS    ] = { 0, 1, 0, 1, 0, 1, 0, 1 },  -- Lv3

Magic hit rate

scripts/globals/combat/magic_hit_rate.lua exposes:
-- Calculate magic accuracy vs. target's magic evasion
xi.combat.magicHitRate.calculateResistRate(
    actor, target, bonusMacc, skillType, skillId, element, jobPoints, tier, extraMacc
)
This is called by spell scripts, blue magic, and mob magical skills.

Build docs developers (and LLMs) love