----------------------------------- -- 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.MUTE ] = { 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.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