landsandboat/scripts/data/status_effect_tables.lua
ThrisStraizo 4c6d16541a Add Gravity resistance rank
Co-Authored-By: Xaver-DaRed <Xaver-DaRed@users.noreply.github.com>
2026-07-20 16:19:27 +02:00

309 lines
19 KiB
Lua

-----------------------------------
-- Table defining diferent status effect properties.
-----------------------------------
-- Info
-- Resistance: https://wiki-ffo-jp.translate.goog/html/1801.html?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=sc
-- Effects: https://wiki-ffo-jp.translate.goog/html/1720.html?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=sc
-- Resist: https://wiki-ffo-jp.translate.goog/html/795.html?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=sc
-- Immunobreak: https://wiki-ffo-jp.translate.goog/html/27204.html?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=sc
-- NOTE: I have yet to find a case where the effect "associated element" isnt determined by the element of the action that causes it.
-- Example: Siren's elegy is wind while spell elegies are earth. Same effect, diferent elements.
-- Unlike sleep, elegy effect doesnt have an associated "effect resistance rank" and uses "element resistance rank" instead.
-----------------------------------
xi = xi or {}
xi.data = xi.data or {}
xi.data.statusEffect = xi.data.statusEffect or {}
-----------------------------------
-- Table column names.
local column =
{
ALLOWED_RESIST_STATE = 1, -- The amount of times an status effect can trigger a resist before it will fail. A value higher than 2 (x >= 3) implies it will NEVER fully resist.
EFFECT_NULLIFIED_ALWAYS = 2, -- [effect] is nullified by { effect }. In other words, [effect] cant be applied because { effect } is active.
EFFECT_NULLIFIED_BY_TIER = 3,
EFFECT_NULLIFIES = 4, -- TODO: IMPLEMENT. [effect] nullifies { effect }.
EFFECT_ELEMENT = 5, -- Players and most effects dont have "effect resistance ranks", so they always use the effect "associated element" "resistance rank".
EFFECT_IMMUNITY = 6, -- Detected by "Completely resists" message. Cant immunobreak/resistance-hack it.
MOD_RESIST_TRAIT = 7, -- Detected by "Resist!" message. Cant immunobreak/resistance-hack it if triggered.
MOD_RESIST_RANK = 8, -- TODO: IMPLEMENT. For mobs, status effects can either: Use an specific status effect ressistance rank OR use their associated element resistance rank.
MOD_MAGIC_EVASION = 9,
MOD_IMMUNOBREAK = 10,
}
-- Table associating an status effect with their corresponding immunobreak, MEVA and resistance modifiers and immunities.
xi.data.statusEffect.dataTable =
{
[xi.effect.ADDLE ] = { 2, 0, 0, xi.effect.NOCTURNE, xi.element.FIRE, xi.immunity.ADDLE, xi.mod.SLOWRES, 0, 0, xi.mod.ADDLE_IMMUNOBREAK }, -- Addle cant be immunobroken?
[xi.effect.AMNESIA ] = { 2, 0, 0, 0, xi.element.FIRE, 0, xi.mod.AMNESIARES, 0, xi.mod.AMNESIA_MEVA, 0 },
[xi.effect.ATTACK_DOWN ] = { 3, 0, 0, 0, xi.element.WATER, 0, 0, 0, 0, 0 },
[xi.effect.BIND ] = { 2, 0, 0, 0, xi.element.ICE, xi.immunity.BIND, xi.mod.BINDRES, xi.mod.BIND_RES_RANK, xi.mod.BIND_MEVA, xi.mod.BIND_IMMUNOBREAK },
[xi.effect.BIO ] = { 4, 0, xi.effect.DIA, 0, xi.element.DARK, 0, 0, 0, 0, 0 },
[xi.effect.BLINDNESS ] = { 1, 0, 0, 0, xi.element.DARK, xi.immunity.BLIND, xi.mod.BLINDRES, xi.mod.BLIND_RES_RANK, xi.mod.BLIND_MEVA, xi.mod.BLIND_IMMUNOBREAK },
[xi.effect.BURN ] = { 2, xi.effect.DROWN, 0, xi.effect.FROST, xi.element.FIRE, 0, 0, 0, 0, 0 },
[xi.effect.CHARM_I ] = { 1, 0, 0, 0, xi.element.LIGHT, 0, xi.mod.CHARMRES, 0, xi.mod.CHARM_MEVA, 0 }, -- TODO: charm should be moved from a mob property to a regular immunity
[xi.effect.CHOKE ] = { 2, xi.effect.FROST, 0, xi.effect.RASP, xi.element.WIND, 0, 0, 0, 0, 0 },
[xi.effect.CURSE_I ] = { 2, 0, 0, 0, xi.element.DARK, 0, xi.mod.CURSERES, 0, xi.mod.CURSE_MEVA, 0 },
[xi.effect.DEFENSE_DOWN ] = { 3, 0, 0, 0, xi.element.WIND, 0, 0, 0, 0, 0 },
[xi.effect.DIA ] = { 4, 0, xi.effect.BIO, 0, xi.element.LIGHT, 0, 0, 0, 0, 0 },
[xi.effect.DROWN ] = { 2, xi.effect.SHOCK, 0, xi.effect.BURN, xi.element.WATER, 0, 0, 0, 0, 0 },
[xi.effect.ELEGY ] = { 1, 0, 0, 0, xi.element.EARTH, xi.immunity.ELEGY, xi.mod.SLOWRES, 0, 0, 0 },
[xi.effect.EVASION_DOWN ] = { 3, 0, 0, 0, xi.element.ICE, 0, 0, 0, 0, 0 },
[xi.effect.FLASH ] = { 1, 0, 0, 0, xi.element.LIGHT, xi.immunity.BLIND, xi.mod.BLINDRES, xi.mod.BLIND_RES_RANK, xi.mod.BLIND_MEVA, xi.mod.BLIND_IMMUNOBREAK },
[xi.effect.FROST ] = { 2, xi.effect.BURN, 0, xi.effect.CHOKE, xi.element.ICE, 0, 0, 0, 0, 0 },
[xi.effect.HASTE ] = { 4, 0, xi.effect.SLOW, 0, xi.element.NONE, 0, 0, 0, 0, 0 },
[xi.effect.HELIX ] = { 3, 0, 0, 0, xi.element.NONE, 0, 0, 0, 0, 0 },
[xi.effect.INHIBIT_TP ] = { 1, 0, 0, 0, xi.element.DARK, 0, 0, 0, 0, 0 },
[xi.effect.INUNDATION ] = { 2, 0, 0, 0, xi.element.LIGHT, 0, 0, 0, 0, 0 },
[xi.effect.MAGIC_EVASION_DOWN ] = { 3, 0, 0, 0, xi.element.DARK, 0, 0, 0, 0, 0 },
[xi.effect.NOCTURNE ] = { 1, xi.effect.ADDLE, 0, 0, xi.element.FIRE, xi.immunity.ADDLE, xi.mod.SLOWRES, 0, 0, 0 },
[xi.effect.NONE ] = { 1, 0, 0, 0, xi.element.DARK, xi.immunity.DISPEL, 0, 0, 0, 0 },
[xi.effect.PARALYSIS ] = { 1, 0, 0, 0, xi.element.ICE, xi.immunity.PARALYZE, xi.mod.PARALYZERES, xi.mod.PARALYZE_RES_RANK, xi.mod.PARALYZE_MEVA, xi.mod.PARALYZE_IMMUNOBREAK },
[xi.effect.PETRIFICATION ] = { 2, 0, 0, 0, xi.element.EARTH, xi.immunity.PETRIFY, xi.mod.PETRIFYRES, 0, xi.mod.PETRIFY_MEVA, xi.mod.PETRIFY_IMMUNOBREAK },
[xi.effect.PLAGUE ] = { 2, 0, 0, 0, xi.element.FIRE, xi.immunity.PLAGUE, xi.mod.VIRUSRES, 0, xi.mod.VIRUS_MEVA, 0 },
[xi.effect.POISON ] = { 1, 0, 0, 0, xi.element.WATER, xi.immunity.POISON, xi.mod.POISONRES, xi.mod.POISON_RES_RANK, xi.mod.POISON_MEVA, xi.mod.POISON_IMMUNOBREAK },
[xi.effect.RASP ] = { 2, xi.effect.CHOKE, 0, xi.effect.SHOCK, xi.element.EARTH, 0, 0, 0, 0, 0 },
[xi.effect.REQUIEM ] = { 1, 0, 0, 0, xi.element.LIGHT, 0, 0, 0, 0, 0 },
[xi.effect.SHOCK ] = { 2, xi.effect.RASP, 0, xi.effect.DROWN, xi.element.THUNDER, 0, 0, 0, 0, 0 },
[xi.effect.SILENCE ] = { 1, 0, 0, 0, xi.element.WIND, xi.immunity.SILENCE, xi.mod.SILENCERES, xi.mod.SILENCE_RES_RANK, xi.mod.SILENCE_MEVA, xi.mod.SILENCE_IMMUNOBREAK },
[xi.effect.SLEEP_I ] = { 1, 0, 0, 0, xi.element.DARK, xi.immunity.DARK_SLEEP, xi.mod.SLEEPRES, xi.mod.DARK_SLEEP_RES_RANK, xi.mod.SLEEP_MEVA, xi.mod.SLEEP_IMMUNOBREAK },
[xi.effect.SLOW ] = { 1, 0, xi.effect.HASTE, 0, xi.element.EARTH, xi.immunity.SLOW, xi.mod.SLOWRES, xi.mod.SLOW_RES_RANK, xi.mod.SLOW_MEVA, xi.mod.SLOW_IMMUNOBREAK },
[xi.effect.STUN ] = { 2, xi.effect.PETRIFICATION, 0, 0, xi.element.THUNDER, xi.immunity.STUN, xi.mod.STUNRES, xi.mod.STUN_RES_RANK, xi.mod.STUN_MEVA, 0 },
[xi.effect.TERROR ] = { 2, 0, 0, 0, 0, xi.immunity.TERROR, 0, 0, 0, 0 }, -- TODO: implement Resist Terror https://www.bg-wiki.com/ffxi/Resist_Terror, TODO: sometimes Terror has an element, but sometimes it does not. It seems it may only be player based effects?
[xi.effect.THRENODY ] = { 1, 0, 0, 0, xi.element.NONE, 0, 0, 0, 0, 0 },
[xi.effect.WEIGHT ] = { 1, 0, 0, 0, xi.element.WIND, xi.immunity.GRAVITY, xi.mod.GRAVITYRES, xi.mod.GRAVITY_RES_RANK, xi.mod.GRAVITY_MEVA, xi.mod.GRAVITY_IMMUNOBREAK },
}
-----------------------------------
-- Helper functions to easily fetch table data.
-----------------------------------
xi.data.statusEffect.getLastAllowedResistanceState = function(effectId)
-- Sanitize fed value
local effectToCheck = utils.defaultIfNil(effectId, 0)
-- Fetch effect ID from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.ALLOWED_RESIST_STATE]
end
return 0
end
xi.data.statusEffect.getNullificatingEffect = function(effectId)
-- Sanitize fed value
local effectToCheck = utils.defaultIfNil(effectId, 0)
-- Fetch effect ID from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.EFFECT_NULLIFIED_ALWAYS]
end
return 0
end
xi.data.statusEffect.getNullificatingEffectByTier = function(effectId)
-- Sanitize fed value
local effectToCheck = utils.defaultIfNil(effectId, 0)
-- Fetch effect ID from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.EFFECT_NULLIFIED_BY_TIER]
end
return 0
end
xi.data.statusEffect.getEffectToRemove = function(effectId)
-- Sanitize fed value
local effectToCheck = utils.defaultIfNil(effectId, 0)
-- Fetch effect ID from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.EFFECT_NULLIFIES]
end
return 0
end
xi.data.statusEffect.getAssociatedElement = function(effectId, actionElement)
-- Sanitize fed values
local effectToCheck = utils.defaultIfNil(effectId, 0)
local elementToCheck = utils.defaultIfNil(actionElement, 0)
-- Sleep exception.
if effectToCheck == xi.effect.SLEEP_I then
return elementToCheck
end
-- Fetch element from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.EFFECT_ELEMENT]
end
-- Assume the effect "element" is the same as the action element.
return elementToCheck
end
xi.data.statusEffect.getAssociatedImmunity = function(effectId, actionElement)
-- Sanitize fed values
local effectToCheck = utils.defaultIfNil(effectId, 0)
local elementToCheck = utils.defaultIfNil(actionElement, 0)
-- Sleep exception.
if
effectToCheck == xi.effect.SLEEP_I and
elementToCheck == xi.element.LIGHT
then
return xi.immunity.LIGHT_SLEEP
end
-- Fetch immunity from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.EFFECT_IMMUNITY]
end
return 0
end
xi.data.statusEffect.getAssociatedResistTraitModifier = function(effectId)
-- Sanitize fed value
local effectToCheck = utils.defaultIfNil(effectId, 0)
-- Fetch modifier ID from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.MOD_RESIST_TRAIT]
end
return 0
end
xi.data.statusEffect.getAssociatedResistanceRankModifier = function(effectId, actionElement)
-- Sanitize fed value
local effectToCheck = utils.defaultIfNil(effectId, 0)
local elementToCheck = utils.defaultIfNil(actionElement, 0)
-- Sleep exception.
if
effectToCheck == xi.effect.SLEEP_I and
elementToCheck == xi.element.LIGHT
then
return xi.mod.LIGHT_SLEEP_RES_RANK
end
-- Fetch modifier ID from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.MOD_RESIST_RANK]
end
return 0
end
xi.data.statusEffect.getAssociatedMagicEvasionModifier = function(effectId)
-- Sanitize fed value
local effectToCheck = utils.defaultIfNil(effectId, 0)
-- Fetch modifier ID from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.MOD_MAGIC_EVASION]
end
return 0
end
xi.data.statusEffect.getAssociatedImmunobreakModifier = function(effectId)
-- Sanitize fed value
local effectToCheck = utils.defaultIfNil(effectId, 0)
-- Fetch modifier ID from table if entry exists.
if xi.data.statusEffect.dataTable[effectToCheck] then
return xi.data.statusEffect.dataTable[effectToCheck][column.MOD_IMMUNOBREAK]
end
return 0
end
-----------------------------------
-- Helper functions to check target effect nullification.
-----------------------------------
xi.data.statusEffect.isResistRateSuccessfull = function(effectId, resistRate, resistRateBypass)
local allowedResistState = resistRateBypass > 0 and resistRateBypass or xi.data.statusEffect.getLastAllowedResistanceState(effectId)
if resistRate >= 1 / 2 ^ allowedResistState then
return true
end
return false
end
xi.data.statusEffect.isTargetImmune = function(target, effectId, actionElement)
if not target:isMob() then
return false
end
local immunityId = xi.data.statusEffect.getAssociatedImmunity(effectId, actionElement)
if
immunityId > 0 and
target:hasImmunity(immunityId)
then
return true
end
return false
end
xi.data.statusEffect.isTargetResistant = function(actor, target, effectId)
local modifierId = xi.data.statusEffect.getAssociatedResistTraitModifier(effectId)
if modifierId == 0 then
return false
end
local resistancePower = target:getMod(modifierId) + target:getMod(xi.mod.STATUSRES) + 5
if resistancePower <= 5 then
return false
end
-- TODO: Investigate if this happens always or not. Ex: Terror.
if actor:isNM() then
resistancePower = math.floor(resistancePower / 2)
end
if math.randomInt(1, 100) <= resistancePower then
return true
end
return false
end
xi.data.statusEffect.isEffectNullified = function(target, effectId, effectTier)
-- Check effects that always block current effect from being applied.
local nullificatingEffect = xi.data.statusEffect.getNullificatingEffect(effectId)
if
nullificatingEffect > 0 and
target:hasStatusEffect(nullificatingEffect)
then
return true
end
if effectTier == 0 then
return false
end
-- Check if current effect is in place with a higher tier.
if target:hasStatusEffect(effectId) then
if target:getStatusEffect(effectId):getTier() >= effectTier then
return true
end
end
-- Check effects that can block current effect from being applied, based on tier.
local tierNullificatingEffect = xi.data.statusEffect.getNullificatingEffectByTier(effectId)
if
tierNullificatingEffect > 0 and
target:hasStatusEffect(tierNullificatingEffect)
then
if target:getStatusEffect(tierNullificatingEffect):getTier() >= effectTier then
return true
end
end
return false
end