2024-10-02 17:25:31 +02:00
-----------------------------------
-- Table defining diferent status effect properties.
-----------------------------------
2025-01-07 11:07:16 +01:00
-- 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.
-----------------------------------
2024-10-02 17:25:31 +02:00
xi = xi or { }
2025-09-19 13:37:44 +02:00
xi.data = xi.data or { }
xi.data . statusEffect = xi.data . statusEffect or { }
2024-10-02 17:25:31 +02:00
-----------------------------------
2025-01-07 11:07:16 +01:00
-- Table column names.
local column =
{
2026-01-20 17:45:06 +01:00
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 ,
2025-01-07 11:07:16 +01:00
}
2024-10-02 17:25:31 +02:00
-- Table associating an status effect with their corresponding immunobreak, MEVA and resistance modifiers and immunities.
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.dataTable =
2024-10-02 17:25:31 +02:00
{
2026-01-20 17:45:06 +01:00
[ 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 } ,
2026-04-23 21:57:06 +02:00
[ 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 } ,
2026-01-20 17:45:06 +01:00
[ 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 , 0 , 0 , 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 } ,
2026-01-22 18:52:27 -07:00
[ 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 } ,
2026-01-20 17:45:06 +01:00
[ 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 , 0 , xi.mod . GRAVITY_MEVA , xi.mod . GRAVITY_IMMUNOBREAK } ,
2024-10-02 17:25:31 +02:00
}
-----------------------------------
-- Helper functions to easily fetch table data.
-----------------------------------
2026-01-20 17:45:06 +01:00
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
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.getNullificatingEffect = function ( effectId )
2025-01-07 11:07:16 +01:00
-- Sanitize fed value
2025-02-19 16:18:45 +01:00
local effectToCheck = utils.defaultIfNil ( effectId , 0 )
2025-01-07 11:07:16 +01:00
-- Fetch effect ID from table if entry exists.
2025-09-19 13:37:44 +02:00
if xi.data . statusEffect.dataTable [ effectToCheck ] then
2025-12-31 19:27:56 +01:00
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 ]
2025-01-07 11:07:16 +01:00
end
return 0
end
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.getEffectToRemove = function ( effectId )
2025-01-07 11:07:16 +01:00
-- Sanitize fed value
2025-02-19 16:18:45 +01:00
local effectToCheck = utils.defaultIfNil ( effectId , 0 )
2025-01-07 11:07:16 +01:00
-- Fetch effect ID from table if entry exists.
2025-09-19 13:37:44 +02:00
if xi.data . statusEffect.dataTable [ effectToCheck ] then
return xi.data . statusEffect.dataTable [ effectToCheck ] [ column.EFFECT_NULLIFIES ]
2025-01-07 11:07:16 +01:00
end
return 0
end
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.getAssociatedElement = function ( effectId , actionElement )
2024-10-02 17:25:31 +02:00
-- Sanitize fed values
2025-02-19 16:18:45 +01:00
local effectToCheck = utils.defaultIfNil ( effectId , 0 )
local elementToCheck = utils.defaultIfNil ( actionElement , 0 )
2024-10-02 17:25:31 +02:00
2025-01-07 11:07:16 +01:00
-- Sleep exception.
if effectToCheck == xi.effect . SLEEP_I then
return elementToCheck
end
2024-10-18 10:42:23 +02:00
2025-01-07 11:07:16 +01:00
-- Fetch element from table if entry exists.
2025-09-19 13:37:44 +02:00
if xi.data . statusEffect.dataTable [ effectToCheck ] then
return xi.data . statusEffect.dataTable [ effectToCheck ] [ column.EFFECT_ELEMENT ]
2024-10-18 10:42:23 +02:00
end
2024-10-02 17:25:31 +02:00
2025-01-07 11:07:16 +01:00
-- Assume the effect "element" is the same as the action element.
return elementToCheck
end
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.getAssociatedImmunity = function ( effectId , actionElement )
2025-01-07 11:07:16 +01:00
-- Sanitize fed values
2025-02-19 16:18:45 +01:00
local effectToCheck = utils.defaultIfNil ( effectId , 0 )
local elementToCheck = utils.defaultIfNil ( actionElement , 0 )
2025-01-07 11:07:16 +01:00
2024-10-02 17:25:31 +02:00
-- Sleep exception.
if
2025-01-07 11:07:16 +01:00
effectToCheck == xi.effect . SLEEP_I and
2024-10-02 17:25:31 +02:00
elementToCheck == xi.element . LIGHT
then
2025-01-07 11:07:16 +01:00
return xi.immunity . LIGHT_SLEEP
end
-- Fetch immunity from table if entry exists.
2025-09-19 13:37:44 +02:00
if xi.data . statusEffect.dataTable [ effectToCheck ] then
return xi.data . statusEffect.dataTable [ effectToCheck ] [ column.EFFECT_IMMUNITY ]
2024-10-02 17:25:31 +02:00
end
2025-01-07 11:07:16 +01:00
return 0
2024-10-02 17:25:31 +02:00
end
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.getAssociatedResistTraitModifier = function ( effectId )
2024-10-02 17:25:31 +02:00
-- Sanitize fed value
2025-09-11 01:59:08 +02:00
local effectToCheck = utils.defaultIfNil ( effectId , 0 )
2024-10-02 17:25:31 +02:00
2024-10-18 10:42:23 +02:00
-- Fetch modifier ID from table if entry exists.
2025-09-19 13:37:44 +02:00
if xi.data . statusEffect.dataTable [ effectToCheck ] then
return xi.data . statusEffect.dataTable [ effectToCheck ] [ column.MOD_RESIST_TRAIT ]
2025-01-07 11:07:16 +01:00
end
2024-10-18 10:42:23 +02:00
2025-01-07 11:07:16 +01:00
return 0
end
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.getAssociatedResistanceRankModifier = function ( effectId , actionElement )
2025-01-07 11:07:16 +01:00
-- Sanitize fed value
2025-02-19 16:18:45 +01:00
local effectToCheck = utils.defaultIfNil ( effectId , 0 )
2025-09-11 01:59:08 +02:00
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
2025-01-07 11:07:16 +01:00
-- Fetch modifier ID from table if entry exists.
2025-09-19 13:37:44 +02:00
if xi.data . statusEffect.dataTable [ effectToCheck ] then
return xi.data . statusEffect.dataTable [ effectToCheck ] [ column.MOD_RESIST_RANK ]
2024-10-18 10:42:23 +02:00
end
2024-10-02 17:25:31 +02:00
2025-01-07 11:07:16 +01:00
return 0
2024-10-02 17:25:31 +02:00
end
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.getAssociatedMagicEvasionModifier = function ( effectId )
2024-10-02 17:25:31 +02:00
-- Sanitize fed value
2025-09-11 01:59:08 +02:00
local effectToCheck = utils.defaultIfNil ( effectId , 0 )
2024-10-02 17:25:31 +02:00
2024-10-18 10:42:23 +02:00
-- Fetch modifier ID from table if entry exists.
2025-09-19 13:37:44 +02:00
if xi.data . statusEffect.dataTable [ effectToCheck ] then
return xi.data . statusEffect.dataTable [ effectToCheck ] [ column.MOD_MAGIC_EVASION ]
2024-10-18 10:42:23 +02:00
end
2024-10-02 17:25:31 +02:00
2025-01-07 11:07:16 +01:00
return 0
2024-10-02 17:25:31 +02:00
end
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.getAssociatedImmunobreakModifier = function ( effectId )
2024-10-02 17:25:31 +02:00
-- Sanitize fed value
2025-09-11 01:59:08 +02:00
local effectToCheck = utils.defaultIfNil ( effectId , 0 )
2024-10-02 17:25:31 +02:00
2024-10-18 10:42:23 +02:00
-- Fetch modifier ID from table if entry exists.
2025-09-19 13:37:44 +02:00
if xi.data . statusEffect.dataTable [ effectToCheck ] then
return xi.data . statusEffect.dataTable [ effectToCheck ] [ column.MOD_IMMUNOBREAK ]
2024-10-18 10:42:23 +02:00
end
2024-10-02 17:25:31 +02:00
2025-01-07 11:07:16 +01:00
return 0
2024-10-02 17:25:31 +02:00
end
2024-10-02 20:02:37 +02:00
-----------------------------------
-- Helper functions to check target effect nullification.
-----------------------------------
2026-01-20 17:45:06 +01:00
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
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.isTargetImmune = function ( target , effectId , actionElement )
2024-10-03 01:42:47 +02:00
if not target : isMob ( ) then
2024-10-02 20:02:37 +02:00
return false
end
2025-09-19 13:37:44 +02:00
local immunityId = xi.data . statusEffect.getAssociatedImmunity ( effectId , actionElement )
2024-10-02 20:02:37 +02:00
if
immunityId > 0 and
target : hasImmunity ( immunityId )
then
return true
end
return false
end
2025-09-19 13:37:44 +02:00
xi.data . statusEffect.isTargetResistant = function ( actor , target , effectId )
local modifierId = xi.data . statusEffect.getAssociatedResistTraitModifier ( effectId )
2024-10-02 20:02:37 +02:00
if modifierId == 0 then
return false
end
2025-01-07 11:07:16 +01:00
local resistancePower = target : getMod ( modifierId ) + target : getMod ( xi.mod . STATUSRES ) + 5
if resistancePower <= 5 then
2024-10-02 20:02:37 +02:00
return false
end
2025-01-07 11:07:16 +01:00
-- TODO: Investigate if this happens always or not. Ex: Terror.
2024-10-02 20:02:37 +02:00
if actor : isNM ( ) then
resistancePower = math.floor ( resistancePower / 2 )
end
2025-01-07 11:07:16 +01:00
if math.random ( 1 , 100 ) <= resistancePower then
return true
end
return false
end
2025-12-31 19:27:56 +01:00
xi.data . statusEffect.isEffectNullified = function ( target , effectId , effectTier )
-- Check effects that always block current effect from being applied.
2025-09-19 13:37:44 +02:00
local nullificatingEffect = xi.data . statusEffect.getNullificatingEffect ( effectId )
2025-01-07 11:07:16 +01:00
if
2025-01-09 21:41:58 +01:00
nullificatingEffect > 0 and
target : hasStatusEffect ( nullificatingEffect )
2025-01-07 11:07:16 +01:00
then
2024-10-02 20:02:37 +02:00
return true
end
2026-02-02 15:30:29 +01:00
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
2025-12-31 19:27:56 +01:00
-- 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
2026-02-02 15:30:29 +01:00
if target : getStatusEffect ( tierNullificatingEffect ) : getTier ( ) >= effectTier then
2025-12-31 19:27:56 +01:00
return true
end
end
2024-10-02 20:02:37 +02:00
return false
end