From 35bd1c1f0e7fcfbff598e47378bc0557d25ac9bb Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Mon, 19 Jan 2026 23:26:23 +0100 Subject: [PATCH 1/4] Create physical SDT function and reorganize magical one --- scripts/actions/mobskills/gravitic_horn.lua | 2 +- scripts/globals/bluemagic.lua | 6 +-- .../combat/action_additional_effect.lua | 12 +----- scripts/globals/combat/damage_multipliers.lua | 41 +++++++++++++++---- scripts/globals/job_utils/dragoon.lua | 2 +- scripts/globals/job_utils/rune_fencer.lua | 2 +- scripts/globals/magic.lua | 2 +- scripts/globals/mobskills.lua | 4 +- scripts/globals/spells/absorb_spell.lua | 4 +- scripts/globals/spells/damage_spell.lua | 28 +------------ 10 files changed, 47 insertions(+), 56 deletions(-) diff --git a/scripts/actions/mobskills/gravitic_horn.lua b/scripts/actions/mobskills/gravitic_horn.lua index f1027d8438f..e62aaf9adce 100644 --- a/scripts/actions/mobskills/gravitic_horn.lua +++ b/scripts/actions/mobskills/gravitic_horn.lua @@ -24,7 +24,7 @@ mobskillObject.onMobWeaponSkill = function(target, mob, skill) damage = math.floor(damage * 0.95) end - damage = math.floor(damage * xi.spells.damage.calculateSDT(target, xi.element.WIND)) + damage = math.floor(damage * xi.combat.damage.magicalElementSDT(target, xi.element.WIND)) damage = xi.mobskills.mobFinalAdjustments(damage, mob, skill, target, xi.attackType.MAGICAL, xi.damageType.WIND, xi.mobskills.shadowBehavior.WIPE_SHADOWS) target:takeDamage(damage, mob, xi.attackType.MAGICAL, xi.damageType.WIND) diff --git a/scripts/globals/bluemagic.lua b/scripts/globals/bluemagic.lua index 8c415d255ad..55d352a8349 100644 --- a/scripts/globals/bluemagic.lua +++ b/scripts/globals/bluemagic.lua @@ -419,7 +419,7 @@ xi.spells.blue.useMagicalSpell = function(caster, target, spell, params) finalDamage = math.floor(finalDamage * xi.combat.magicHitRate.calculateResistRate(caster, target, spellGroup, skillType, 0, spellElement, params.attribute, 0, 0)) finalDamage = math.floor(finalDamage * xi.spells.damage.calculateElementalStaffBonus(caster, spellElement)) - finalDamage = math.floor(finalDamage * xi.spells.damage.calculateSDT(target, spellElement)) + finalDamage = math.floor(finalDamage * xi.combat.damage.magicalElementSDT(target, spellElement)) finalDamage = math.floor(finalDamage * xi.spells.damage.calculateDayAndWeather(caster, spellElement, false)) finalDamage = math.floor(finalDamage * xi.spells.damage.calculateMagicBonusDiff(caster, target, spellId, skillType, spellElement)) @@ -476,7 +476,7 @@ xi.spells.blue.useDrainSpell = function(caster, target, spell, params, damageCap finalDamage = math.floor(finalDamage * xi.combat.magicHitRate.calculateResistRate(caster, target, spellGroup, skillType, 0, spellElement, params.attribute, 0, 0)) finalDamage = math.floor(finalDamage * xi.spells.damage.calculateElementalStaffBonus(caster, spellElement)) - finalDamage = math.floor(finalDamage * xi.spells.damage.calculateSDT(target, spellElement)) + finalDamage = math.floor(finalDamage * xi.combat.damage.magicalElementSDT(target, spellElement)) finalDamage = math.floor(finalDamage * xi.spells.damage.calculateDayAndWeather(caster, spellElement, false)) finalDamage = math.floor(finalDamage * xi.spells.damage.calculateMagicBonusDiff(caster, target, spellId, skillType, spellElement)) @@ -564,7 +564,7 @@ xi.spells.blue.useBreathSpell = function(caster, target, spell, params) local elementalAffinityBonus = xi.spells.damage.calculateElementalAffinityBonus(caster, spellElement) local resistTier = xi.combat.magicHitRate.calculateResistRate(caster, target, spellFamily, xi.skill.BLUE_MAGIC, 0, spellElement, 0, 0, 0) local additionalResistTier = xi.spells.damage.calculateAdditionalResistTier(caster, target, spellElement) - local elementalSDT = xi.spells.damage.calculateSDT(target, spellElement) + local elementalSDT = xi.combat.damage.magicalElementSDT(target, spellElement) local dayAndWeather = xi.spells.damage.calculateDayAndWeather(caster, spellElement, false) local magicBonusDiff = xi.spells.damage.calculateMagicBonusDiff(caster, target, spellId, xi.skill.BLUE_MAGIC, spellElement) local skillTypeMultiplier = xi.spells.damage.calculateSkillTypeMultiplier(xi.skill.BLUE_MAGIC) diff --git a/scripts/globals/combat/action_additional_effect.lua b/scripts/globals/combat/action_additional_effect.lua index fb58486942b..2fa62d7ccc4 100644 --- a/scripts/globals/combat/action_additional_effect.lua +++ b/scripts/globals/combat/action_additional_effect.lua @@ -73,8 +73,8 @@ xi.combat.action.executeAdditionalDamage = function(actor, target, fedData) local multiplierAbsorption = xi.spells.damage.calculateAbsorption(target, params.magicalElement, params.isMagical) local multiplierNullification = xi.spells.damage.calculateNullification(target, params.magicalElement, isMagical, isBreath) local multiplierDamageTypeSDT = xi.spells.damage.calculateDamageAdjustment(target, isPhysical, isMagical, isRanged, isBreath) - local multiplierPhysicalElementSDT = 1 -- TODO: Create function for physical elements. - local multiplierMagicalElementSDT = xi.spells.damage.calculateSDT(target, params.magicalElement) + local multiplierPhysicalElementSDT = xi.combat.damage.physicalElementSDT(target, params.physicalElement) + local multiplierMagicalElementSDT = xi.combat.damage.magicalElementSDT(target, params.magicalElement) local multiplierElementalStaff = xi.spells.damage.calculateElementalStaffBonus(actor, params.magicalElement) local multiplierElementalAffinity = xi.spells.damage.calculateElementalAffinityBonus(actor, params.magicalElement) local multiplierDayWeather = xi.spells.damage.calculateDayAndWeather(actor, params.magicalElement, false) @@ -120,11 +120,3 @@ xi.combat.action.executeAdditionalDamage = function(actor, target, fedData) return 0, 0, 0 end end - -xi.combat.action.executeAdditionalEffect = function(actor, target, fedData) - return 0, 0, 0 -end - -xi.combat.action.executeAdditionalDispel = function(actor, target, fedData) - return 0, 0, 0 -end diff --git a/scripts/globals/combat/damage_multipliers.lua b/scripts/globals/combat/damage_multipliers.lua index 94708aef70f..0629492feca 100644 --- a/scripts/globals/combat/damage_multipliers.lua +++ b/scripts/globals/combat/damage_multipliers.lua @@ -4,17 +4,40 @@ xi.combat = xi.combat or {} xi.combat.damage = xi.combat.damage or {} ----------------------------------- ------------------------------------ --- Physical damage multipliers ------------------------------------ +xi.combat.damage.physicalElementSDT = function(target, physicalElement) + if + physicalElement < xi.damageType.PIERCING or + physicalElement > xi.damageType.HTH + then + return 1 + end ------------------------------------ --- Magical damage multipliers ------------------------------------ + local physicalElementSDTModifier = + { + [xi.damageType.PIERCING] = xi.mod.PIERCE_SDT, + [xi.damageType.SLASHING] = xi.mod.SLASH_SDT, + [xi.damageType.BLUNT ] = xi.mod.IMPACT_SDT, + [xi.damageType.HTH ] = xi.mod.HTH_SDT, + } + + local sdt = 1 + target:getMod(physicalElementSDTModifier[physicalElement]) / 10000 + + return utils.clamp(sdt, 0, 3) +end + +xi.combat.damage.magicalElementSDT = function(target, magicalElement) + if + magicalElement < xi.element.FIRE or + magicalElement > xi.element.DARK + then + return 1 + end + + local sdt = 1 + target:getMod(xi.data.element.getElementalSDTModifier(magicalElement)) / 10000 + + return utils.clamp(sdt, 0, 3) +end ------------------------------------ --- All damage multipliers ------------------------------------ xi.combat.damage.scarletDeliriumMultiplier = function(actor) -- Scarlet delirium are 2 different status effects. SCARLET_DELIRIUM_1 is the one that boosts power. if not actor:hasStatusEffect(xi.effect.SCARLET_DELIRIUM_1) then diff --git a/scripts/globals/job_utils/dragoon.lua b/scripts/globals/job_utils/dragoon.lua index f307c0e5d6e..4f1505e605b 100644 --- a/scripts/globals/job_utils/dragoon.lua +++ b/scripts/globals/job_utils/dragoon.lua @@ -692,7 +692,7 @@ xi.job_utils.dragoon.useDamageBreath = function(wyvern, target, skill, action, d -- Breath accuracy is directly affected by a wyvern's current HP, but no data exists. local resist = xi.combat.magicHitRate.calculateResistRate(wyvern, target, 0, 0, 0, element, 0, 0, bonusMacc) - local sdt = xi.spells.damage.calculateSDT(target, element) + local sdt = xi.combat.damage.magicalElementSDT(target, element) local absorb = xi.spells.damage.calculateAbsorption(target, element, true) local nullify = xi.spells.damage.calculateNullification(target, element, true, true) local magicBurst = 1 diff --git a/scripts/globals/job_utils/rune_fencer.lua b/scripts/globals/job_utils/rune_fencer.lua index d1a012ecdaa..e036c638030 100644 --- a/scripts/globals/job_utils/rune_fencer.lua +++ b/scripts/globals/job_utils/rune_fencer.lua @@ -520,7 +520,7 @@ local function getSwipeLungeDamageMultipliers(player, target, element, bonusMacc multipliers.eleStaffBonus = xi.spells.damage.calculateElementalStaffBonus(player, element) multipliers.eleAffinityBonus = xi.spells.damage.calculateElementalAffinityBonus(player, element) - multipliers.SDT = xi.spells.damage.calculateSDT(target, element) + multipliers.SDT = xi.combat.damage.magicalElementSDT(target, element) multipliers.resist = xi.combat.magicHitRate.calculateResistRate(player, target, 0, 0, 0, element, 0, 0, bonusMacc) multipliers.dayAndWeather = xi.spells.damage.calculateDayAndWeather(player, element, false) multipliers.magicBonusDiff = xi.spells.damage.calculateMagicBonusDiff(player, target, 0, 0, element) diff --git a/scripts/globals/magic.lua b/scripts/globals/magic.lua index 70df71968f0..33bd0822871 100644 --- a/scripts/globals/magic.lua +++ b/scripts/globals/magic.lua @@ -111,7 +111,7 @@ function addBonusesAbility(caster, ele, target, dmg, params) local affinityBonus = xi.spells.damage.calculateElementalStaffBonus(caster, ele) dmg = math.floor(dmg * affinityBonus) - local magicDefense = xi.spells.damage.calculateSDT(target, ele) + local magicDefense = xi.combat.damage.magicalElementSDT(target, ele) dmg = math.floor(dmg * magicDefense) local dayWeatherBonus = xi.spells.damage.calculateDayAndWeather(caster, ele, false) diff --git a/scripts/globals/mobskills.lua b/scripts/globals/mobskills.lua index 1754f1c2f18..d8f4a43a6ef 100644 --- a/scripts/globals/mobskills.lua +++ b/scripts/globals/mobskills.lua @@ -370,7 +370,7 @@ xi.mobskills.mobMagicalMove = function(actor, target, action, baseDamage, action end -- Multipliers. - local sdt = xi.spells.damage.calculateSDT(target, actionElement) + local sdt = xi.combat.damage.magicalElementSDT(target, actionElement) local resistRate = xi.combat.magicHitRate.calculateResistRate(actor, target, 0, 0, 0, actionElement, xi.mod.INT, 0, petAccBonus) local dayAndWeather = xi.spells.damage.calculateDayAndWeather(actor, actionElement, false) local magicBonusDiff = xi.spells.damage.calculateMagicBonusDiff(actor, target, 0, 0, actionElement) @@ -440,7 +440,7 @@ xi.mobskills.mobBreathMove = function(mob, target, skill, skillParams) mAccuracyBonus = xi.combat.physical.calculateTPfactor(skill:getTP(), mAccuracyBonusfTP) local systemBonus = 1 + utils.getEcosystemStrengthBonus(mob:getEcosystem(), target:getEcosystem()) / 4 - local elementalSDT = xi.spells.damage.calculateSDT(target, actionElement) + local elementalSDT = xi.combat.damage.magicalElementSDT(target, actionElement) local resistRate = xi.combat.magicHitRate.calculateResistRate(mob, target, 0, 0, xi.skillRank.A_PLUS, actionElement, resistStat, 0, mAccuracyBonus) local dayAndWeather = xi.spells.damage.calculateDayAndWeather(mob, actionElement, false) local absorb = xi.spells.damage.calculateAbsorption(target, actionElement, true) diff --git a/scripts/globals/spells/absorb_spell.lua b/scripts/globals/spells/absorb_spell.lua index 14e1e840ef4..9353d036619 100644 --- a/scripts/globals/spells/absorb_spell.lua +++ b/scripts/globals/spells/absorb_spell.lua @@ -127,7 +127,7 @@ xi.spells.absorb.doDrainingSpell = function(caster, target, spell) -- Multipliers. local resistTier = xi.combat.magicHitRate.calculateResistRate(caster, target, xi.magic.spellGroup.BLACK, xi.skill.DARK_MAGIC, 0, xi.element.DARK, xi.mod.INT, 0, 0) local additionalResistTier = xi.spells.damage.calculateAdditionalResistTier(caster, target, xi.element.DARK) - local sdt = xi.spells.damage.calculateSDT(target, xi.element.DARK) + local sdt = xi.combat.damage.magicalElementSDT(target, xi.element.DARK) local elementalStaffBonus = xi.spells.damage.calculateElementalStaffBonus(caster, xi.element.DARK) local elementalAffinityBonus = xi.spells.damage.calculateElementalAffinityBonus(caster, xi.element.DARK) local dayAndWeather = xi.spells.damage.calculateDayAndWeather(caster, xi.element.DARK, false) @@ -239,7 +239,7 @@ xi.spells.absorb.doAbsorbTPSpell = function(caster, target, spell) -- Multipliers. local resistTier = xi.combat.magicHitRate.calculateResistRate(caster, target, xi.magic.spellGroup.BLACK, xi.skill.DARK_MAGIC, 0, xi.element.DARK, xi.mod.INT, 0, 0) local additionalResistTier = xi.spells.damage.calculateAdditionalResistTier(caster, target, xi.element.DARK) - local sdt = xi.spells.damage.calculateSDT(target, xi.element.DARK) + local sdt = xi.combat.damage.magicalElementSDT(target, xi.element.DARK) local elementalStaffBonus = xi.spells.damage.calculateElementalStaffBonus(caster, xi.element.DARK) local dayAndWeather = xi.spells.damage.calculateDayAndWeather(caster, xi.element.DARK, false) local absorbMultiplier = 1 + caster:getMod(xi.mod.AUGMENTS_ABSORB) / 100 diff --git a/scripts/globals/spells/damage_spell.lua b/scripts/globals/spells/damage_spell.lua index c1c68c7fc69..c26a0dc4e0f 100644 --- a/scripts/globals/spells/damage_spell.lua +++ b/scripts/globals/spells/damage_spell.lua @@ -495,35 +495,11 @@ xi.spells.damage.calculateElementalAffinityBonus = function(caster, spellElement return affinityFactor end --- Elemental Specific Damage Taken (Elemental SDT) --- SDT (Species/Specific Damage Taken) is a stat/mod present in mobs and players that applies a % to specific damage types. --- Each of the 8 elements has an SDT modifier (Modifiers 54 to 61. Check script(globals/status.lua) --- Mob elemental modifiers are populated by the values set in "mob_resistances.sql" (The database). SDT columns. --- The value of the modifiers are base 10000. Positive numbers mean less damage taken. Negative mean more damage taken. --- Examples: --- A value of 5000 -> 50% MORE damage taken. --- A value of -5000 -> 50% LESS damage taken. --- A word on SDT as understood in some wikis, even if they are refering to resistance and not actual SDT --- SDT under 50% applies a flat 1/2 *, which was for a long time confused with an additional resist tier, which, in reality, its an independent multiplier. --- This is understandable, because in a way, it is effectively a whole tier, but recent testing with skillchains/magic bursts after resist was removed from them, proved this. --- SDT affects magic burst damage, but never in a "negative" way. --- https://www.bg-wiki.com/ffxi/Resist for some SDT info. --- *perhaps this simply means there is a cap/clamp limiting it there. -xi.spells.damage.calculateSDT = function(target, spellElement) - local sdt = 1 -- The variable we want to calculate - - if spellElement > xi.element.NONE then - sdt = 1 + target:getMod(xi.data.element.getElementalSDTModifier(spellElement)) / 10000 - end - - return utils.clamp(sdt, 0, 3) -end - xi.spells.damage.calculateAdditionalResistTier = function(caster, target, spellElement) local additionalResistTier = 1 if - not caster:hasStatusEffect(xi.effect.SUBTLE_SORCERY) and -- Subtle sorcery bypasses this tier. + not caster:hasStatusEffect(xi.effect.SUBTLE_SORCERY) and -- Subtle sorcery bypasses this tier. target:getMod(xi.data.element.getElementalResistanceRankModifier(spellElement)) >= 4 -- Forced only at and after rank 4 (50% EEM). then additionalResistTier = additionalResistTier / 2 @@ -1177,7 +1153,7 @@ xi.spells.damage.useDamageSpell = function(caster, target, spell) local elementalStaffBonus = xi.spells.damage.calculateElementalStaffBonus(caster, spellElement) local elementalAffinityBonus = xi.spells.damage.calculateElementalAffinityBonus(caster, spellElement) local additionalResistTier = xi.spells.damage.calculateAdditionalResistTier(caster, target, spellElement) - local sdt = xi.spells.damage.calculateSDT(target, spellElement) + local sdt = xi.combat.damage.magicalElementSDT(target, spellElement) local dayAndWeather = xi.spells.damage.calculateDayAndWeather(caster, spellElement, forceDayWeatherBonus) local magicBonusDiff = xi.spells.damage.calculateMagicBonusDiff(caster, target, spellId, skillType, spellElement) local criticalDamageMultiplier = xi.spells.damage.calculateMagicCriticalMultiplier(caster) From 75d6990b4dfca3ec2aba2d03b3dde7bf1d237e1a Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Mon, 19 Jan 2026 23:31:30 +0100 Subject: [PATCH 2/4] Minor action_ script consistency cleanup --- ...ua => action_additional_effect_damage.lua} | 32 +++++++++++-------- .../combat/action_mobskill_status_effect.lua | 27 +++++++++------- 2 files changed, 33 insertions(+), 26 deletions(-) rename scripts/globals/combat/{action_additional_effect.lua => action_additional_effect_damage.lua} (84%) diff --git a/scripts/globals/combat/action_additional_effect.lua b/scripts/globals/combat/action_additional_effect_damage.lua similarity index 84% rename from scripts/globals/combat/action_additional_effect.lua rename to scripts/globals/combat/action_additional_effect_damage.lua index 2fa62d7ccc4..d012b3b7f91 100644 --- a/scripts/globals/combat/action_additional_effect.lua +++ b/scripts/globals/combat/action_additional_effect_damage.lua @@ -1,27 +1,31 @@ ----------------------------------- --- Global file for additional effects. +-- Global file for additional effects (damage) +----------------------------------- +require('scripts/globals/combat/damage_multipliers') +require('scripts/globals/combat/magic_hit_rate') ----------------------------------- xi = xi or {} xi.combat = xi.combat or {} xi.combat.action = xi.combat.action or {} ----------------------------------- -local damageAnimationDefaults = +local defaultsTable = { - [xi.element.NONE ] = { xi.subEffect.LIGHT_DAMAGE }, -- Like Excalibur. - [xi.element.FIRE ] = { xi.subEffect.FIRE_DAMAGE }, - [xi.element.ICE ] = { xi.subEffect.ICE_DAMAGE }, - [xi.element.WIND ] = { xi.subEffect.WIND_DAMAGE }, - [xi.element.EARTH ] = { xi.subEffect.EARTH_DAMAGE }, - [xi.element.THUNDER] = { xi.subEffect.LIGHTNING_DAMAGE }, - [xi.element.WATER ] = { xi.subEffect.WATER_DAMAGE }, - [xi.element.LIGHT ] = { xi.subEffect.LIGHT_DAMAGE }, - [xi.element.DARK ] = { xi.subEffect.DARKNESS_DAMAGE }, + [xi.element.NONE ] = { xi.subEffect.LIGHT_DAMAGE }, -- Like Excalibur. + [xi.element.FIRE ] = { xi.subEffect.FIRE_DAMAGE }, + [xi.element.ICE ] = { xi.subEffect.ICE_DAMAGE }, + [xi.element.WIND ] = { xi.subEffect.WIND_DAMAGE }, + [xi.element.EARTH ] = { xi.subEffect.EARTH_DAMAGE }, + [xi.element.THUNDER] = { xi.subEffect.LIGHTNING_DAMAGE }, + [xi.element.WATER ] = { xi.subEffect.WATER_DAMAGE }, + [xi.element.LIGHT ] = { xi.subEffect.LIGHT_DAMAGE }, + [xi.element.DARK ] = { xi.subEffect.DARKNESS_DAMAGE }, } + ----------------------------------- -- Local functions to ensure defaults are set. ----------------------------------- -local function validateDamageParameters(fedData) +local function validateParameters(fedData) local params = {} -- Chance. @@ -42,7 +46,7 @@ local function validateDamageParameters(fedData) params.canResist = fedData.canResist or false -- Animations and messaging. - params.animation = fedData.animation or damageAnimationDefaults[params.magicalElement] + params.animation = fedData.animation or defaultsTable[params.magicalElement][1] params.messageDamage = fedData.messageDamage or xi.msg.basic.ADD_EFFECT_DMG params.messageHeal = fedData.messageHeal or xi.msg.basic.ADD_EFFECT_HEAL @@ -53,7 +57,7 @@ end -- Global functions called from "emtity.onAdditionalEffect()" ----------------------------------- xi.combat.action.executeAdditionalDamage = function(actor, target, fedData) - local params = validateDamageParameters(fedData) + local params = validateParameters(fedData) -- Early return: No proc. if math.random(1, 100) > params.chance then diff --git a/scripts/globals/combat/action_mobskill_status_effect.lua b/scripts/globals/combat/action_mobskill_status_effect.lua index 420531f17ba..8cde2fe998a 100644 --- a/scripts/globals/combat/action_mobskill_status_effect.lua +++ b/scripts/globals/combat/action_mobskill_status_effect.lua @@ -1,6 +1,9 @@ ----------------------------------- -- Global file for mobskills that apply status effects. ----------------------------------- +require('scripts/globals/combat/damage_multipliers') +require('scripts/globals/combat/magic_hit_rate') +----------------------------------- xi = xi or {} xi.combat = xi.combat or {} xi.combat.action = xi.combat.action or {} @@ -17,23 +20,23 @@ local step = APPLICATION_FAIL = 7, } -local function validateParameters(effectData) +local function validateParameters(fedData) local params = {} -- Status effect application parameters. - params.effectId = effectData.effectId or 0 - params.power = effectData.power or 0 - params.tick = effectData.tick or 0 - params.duration = effectData.duration or 0 - params.subType = effectData.subType or 0 - params.subPower = effectData.subPower or 0 - params.tier = effectData.tier or 0 + params.effectId = fedData.effectId or 0 + params.power = fedData.power or 0 + params.tick = fedData.tick or 0 + params.duration = fedData.duration or 0 + params.subType = fedData.subType or 0 + params.subPower = fedData.subPower or 0 + params.tier = fedData.tier or 0 -- Calculation parameters. - params.resist = effectData.resist or 0.125 -- The amount of resists this effect allows. Example: Sleep can only resist once before failing, so value = 1/2 (No 1/4 nor 1/8) - params.rank = effectData.rank or xi.skillRank.A_PLUS -- The skill rank used for macc. - params.stat = effectData.stat or xi.mod.INT -- Stat used for macc. - params.macc = effectData.macc or 0 -- Flat macc bonus addition. + params.resist = fedData.resist or 0.125 -- The amount of resists this effect allows. Example: Sleep can only resist once before failing, so value = 1/2 (No 1/4 nor 1/8) + params.rank = fedData.rank or xi.skillRank.A_PLUS -- The skill rank used for macc. + params.stat = fedData.stat or xi.mod.INT -- Stat used for macc. + params.macc = fedData.macc or 0 -- Flat macc bonus addition. return params end From fb20f9eb58f216359ca828344c9a099ad4f8ea0a Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Tue, 20 Jan 2026 17:45:06 +0100 Subject: [PATCH 3/4] Move resist states/levels out of enfeebling_spell script for global use --- scripts/data/status_effect_tables.lua | 108 +++++++---- .../combat/action_mobskill_status_effect.lua | 33 ++-- scripts/globals/spells/enfeebling_song.lua | 10 +- scripts/globals/spells/enfeebling_spell.lua | 180 +++++++++--------- 4 files changed, 181 insertions(+), 150 deletions(-) diff --git a/scripts/data/status_effect_tables.lua b/scripts/data/status_effect_tables.lua index b4b85036b80..b073348b38d 100644 --- a/scripts/data/status_effect_tables.lua +++ b/scripts/data/status_effect_tables.lua @@ -19,55 +19,76 @@ xi.data.statusEffect = xi.data.statusEffect or {} -- Table column names. local column = { - EFFECT_NULLIFIED_ALWAYS = 1, -- [effect] is nullified by { effect }. In other words, [effect] cant be applied because { effect } is active. - EFFECT_NULLIFIED_BY_TIER = 2, - EFFECT_NULLIFIES = 3, -- TODO: IMPLEMENT. [effect] nullifies { effect }. - EFFECT_ELEMENT = 4, -- Players and most effects dont have "effect resistance ranks", so they always use the effect "associated element" "resistance rank". - EFFECT_IMMUNITY = 5, -- Detected by "Completely resists" message. Cant immunobreak/resistance-hack it. - MOD_RESIST_TRAIT = 6, -- Detected by "Resist!" message. Cant immunobreak/resistance-hack it if triggered. - MOD_RESIST_RANK = 7, -- 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 = 8, - MOD_IMMUNOBREAK = 9, + 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 ] = { 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 ] = { 0, 0, 0, xi.element.FIRE, 0, xi.mod.AMNESIARES, 0, xi.mod.AMNESIA_MEVA, 0 }, - [xi.effect.ATTACK_DOWN ] = { 0, 0, 0, xi.element.WATER, 0, 0, 0, 0, 0 }, - [xi.effect.BIND ] = { 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 ] = { 0, xi.effect.DIA, 0, xi.element.DARK, 0, 0, 0, 0, 0 }, - [xi.effect.BLINDNESS ] = { 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 ] = { xi.effect.DROWN, 0, xi.effect.FROST, xi.element.FIRE, 0, 0, 0, 0, 0 }, - [xi.effect.CHARM_I ] = { 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 ] = { xi.effect.FROST, 0, xi.effect.RASP, xi.element.WIND, 0, 0, 0, 0, 0 }, - [xi.effect.CURSE_I ] = { 0, 0, 0, xi.element.DARK, 0, xi.mod.CURSERES, 0, xi.mod.CURSE_MEVA, 0 }, - [xi.effect.DEFENSE_DOWN ] = { 0, 0, 0, xi.element.WIND, 0, 0, 0, 0, 0 }, - [xi.effect.DIA ] = { 0, xi.effect.BIO, 0, xi.element.LIGHT, 0, 0, 0, 0, 0 }, - [xi.effect.DROWN ] = { xi.effect.SHOCK, 0, xi.effect.BURN, xi.element.WATER, 0, 0, 0, 0, 0 }, - [xi.effect.FLASH ] = { 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 ] = { xi.effect.BURN, 0, xi.effect.CHOKE, xi.element.ICE, 0, 0, 0, 0, 0 }, - [xi.effect.HASTE ] = { 0, xi.effect.SLOW, 0, xi.element.NONE, 0, 0, 0, 0, 0 }, - [xi.effect.NOCTURNE ] = { xi.effect.ADDLE, 0, 0, xi.element.FIRE, xi.immunity.ADDLE, xi.mod.SLOWRES, 0, 0, 0 }, - [xi.effect.NONE ] = { 0, 0, 0, xi.element.DARK, xi.immunity.DISPEL, 0, 0, 0, 0 }, - [xi.effect.PARALYSIS ] = { 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] = { 0, 0, 0, xi.element.EARTH, xi.immunity.PETRIFY, xi.mod.PETRIFYRES, 0, xi.mod.PETRIFY_MEVA, xi.mod.PETRIFY_IMMUNOBREAK }, - [xi.effect.PLAGUE ] = { 0, 0, 0, xi.element.FIRE, xi.immunity.PLAGUE, xi.mod.VIRUSRES, 0, xi.mod.VIRUS_MEVA, 0 }, - [xi.effect.POISON ] = { 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 ] = { xi.effect.CHOKE, 0, xi.effect.SHOCK, xi.element.EARTH, 0, 0, 0, 0, 0 }, - [xi.effect.SHOCK ] = { xi.effect.RASP, 0, xi.effect.DROWN, xi.element.THUNDER, 0, 0, 0, 0, 0 }, - [xi.effect.SILENCE ] = { 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 ] = { 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 ] = { 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 ] = { 0, 0, 0, xi.element.THUNDER, xi.immunity.STUN, xi.mod.STUNRES, 0, xi.mod.STUN_MEVA, 0 }, - [xi.effect.TERROR ] = { 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.WEIGHT ] = { 0, 0, 0, xi.element.WIND, xi.immunity.GRAVITY, xi.mod.GRAVITYRES, 0, xi.mod.GRAVITY_MEVA, xi.mod.GRAVITY_IMMUNOBREAK }, + [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 ] = { 1, 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, 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 }, + [xi.effect.STUN ] = { 2, xi.effect.PETRIFICATION, 0, 0, xi.element.THUNDER, xi.immunity.STUN, xi.mod.STUNRES, 0, 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, 0, 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) @@ -204,6 +225,15 @@ 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 diff --git a/scripts/globals/combat/action_mobskill_status_effect.lua b/scripts/globals/combat/action_mobskill_status_effect.lua index 8cde2fe998a..df7db8a6672 100644 --- a/scripts/globals/combat/action_mobskill_status_effect.lua +++ b/scripts/globals/combat/action_mobskill_status_effect.lua @@ -24,19 +24,20 @@ local function validateParameters(fedData) local params = {} -- Status effect application parameters. - params.effectId = fedData.effectId or 0 - params.power = fedData.power or 0 - params.tick = fedData.tick or 0 - params.duration = fedData.duration or 0 - params.subType = fedData.subType or 0 - params.subPower = fedData.subPower or 0 - params.tier = fedData.tier or 0 + params.effectId = fedData.effectId or 0 + params.power = fedData.power or 0 + params.tick = fedData.tick or 0 + params.duration = fedData.duration or 0 + params.subType = fedData.subType or 0 + params.subPower = fedData.subPower or 0 + params.tier = fedData.tier or 0 -- Calculation parameters. - params.resist = fedData.resist or 0.125 -- The amount of resists this effect allows. Example: Sleep can only resist once before failing, so value = 1/2 (No 1/4 nor 1/8) - params.rank = fedData.rank or xi.skillRank.A_PLUS -- The skill rank used for macc. - params.stat = fedData.stat or xi.mod.INT -- Stat used for macc. - params.macc = fedData.macc or 0 -- Flat macc bonus addition. + params.element = fedData.element or xi.data.statusEffect.getAssociatedElement(params.effectId, xi.element.NONE) + params.rank = fedData.rank or xi.skillRank.A_PLUS -- The skill rank used for macc. + params.stat = fedData.stat or xi.mod.INT -- Stat used for macc. + params.macc = fedData.macc or 0 -- Flat macc bonus addition. + params.resistRate = fedData.resistRate or 0 return params end @@ -62,10 +63,8 @@ xi.combat.action.executeMobskillStatusEffect = function(actor, target, skill, ef return handleReturn(skill, setMessage, xi.msg.basic.SKILL_NO_EFFECT, step.CANT_GAIN) end - local element = xi.data.statusEffect.getAssociatedElement(params.effectId, actor:getStatusEffectElement(params.effectId)) - -- Check immunity. - if xi.data.statusEffect.isTargetImmune(target, params.effectId, element) then + if xi.data.statusEffect.isTargetImmune(target, params.effectId, params.element) then return handleReturn(skill, setMessage, xi.msg.basic.SKILL_MISS, step.IMMUNE_CHECK) -- Check resist traits. @@ -78,13 +77,13 @@ xi.combat.action.executeMobskillStatusEffect = function(actor, target, skill, ef end -- Calculate resist state. - local resistRate = xi.combat.magicHitRate.calculateResistRate(actor, target, 0, 0, params.rank, element, params.stat, params.effectId, params.macc) - if resistRate < params.resist then + local resistanceRate = xi.combat.magicHitRate.calculateResistRate(actor, target, 0, 0, params.rank, params.element, params.stat, params.effectId, params.macc) + if not xi.data.statusEffect.isResistRateSuccessfull(params.effectId, resistanceRate, params.resistRate) then return handleReturn(skill, setMessage, xi.msg.basic.SKILL_MISS, step.RESIST_RATE_CHECK) end -- Calculate duration. - local totalDuration = math.floor(params.duration * resistRate) + local totalDuration = math.floor(params.duration * resistanceRate) -- Apply effect. if target:addStatusEffect(params.effectId, params.power, params.tick, totalDuration, params.subType, params.subPower, params.tier) then diff --git a/scripts/globals/spells/enfeebling_song.lua b/scripts/globals/spells/enfeebling_song.lua index 41a6665c9bc..246d09f4523 100644 --- a/scripts/globals/spells/enfeebling_song.lua +++ b/scripts/globals/spells/enfeebling_song.lua @@ -183,10 +183,14 @@ xi.spells.enfeebling.useEnfeeblingSong = function(caster, target, spell) end local resistRate = xi.combat.magicHitRate.calculateResistRate(caster, target, xi.magic.spellGroup.SONG, xi.skill.SINGING, 0, spellElement, xi.mod.CHR, spellEffect, bonusMagicAcc) + if not xi.data.statusEffect.isResistRateSuccessfull(spellEffect, resistRate, 0) then + spell:setMsg(xi.msg.basic.MAGIC_RESIST) + return spellEffect + end + if - resistRate <= 0.25 or - (spellEffect == xi.effect.CHARM_I and - target:isMob() and + spellEffect == xi.effect.CHARM_I and + (not target:isMob() or target:getMobMod(xi.mobMod.CHARMABLE) <= 0) then spell:setMsg(xi.msg.basic.MAGIC_RESIST) diff --git a/scripts/globals/spells/enfeebling_spell.lua b/scripts/globals/spells/enfeebling_spell.lua index 01766fdbbfc..02f10dffd69 100644 --- a/scripts/globals/spells/enfeebling_spell.lua +++ b/scripts/globals/spells/enfeebling_spell.lua @@ -12,106 +12,105 @@ xi.spells.enfeebling = xi.spells.enfeebling or {} ----------------------------------- local column = { - EFFECT_ID = 1, - EFFECT_TIER = 2, - STAT_USED = 3, - BASE_POTENCY = 4, - BASE_TICK = 5, - BASE_DURATION = 6, - RESIST_STAGES = 7, - MESSAGE_OFFSET = 8, - SABOTEUR = 9, - BONUS_MACC = 10, + EFFECT_ID = 1, + EFFECT_TIER = 2, + STAT_USED = 3, + BASE_POTENCY = 4, + BASE_TICK = 5, + BASE_DURATION = 6, + MESSAGE_OFFSET = 7, + SABOTEUR = 8, + BONUS_MACC = 9, } local pTable = { -- Black Magic - [xi.magic.spell.BIND ] = { xi.effect.BIND, 1, xi.mod.INT, 0, 0, 60, 2, 0, false, 0 }, - [xi.magic.spell.BINDGA ] = { xi.effect.BIND, 1, xi.mod.INT, 0, 0, 60, 2, 0, false, 0 }, - [xi.magic.spell.BLIND ] = { xi.effect.BLINDNESS, 1, xi.mod.INT, 0, 0, 180, 2, 0, true, 0 }, - [xi.magic.spell.BLIND_II ] = { xi.effect.BLINDNESS, 3, xi.mod.INT, 0, 0, 180, 2, 0, true, 0 }, - [xi.magic.spell.BLINDGA ] = { xi.effect.BLINDNESS, 2, xi.mod.INT, 0, 0, 180, 2, 0, true, 0 }, - [xi.magic.spell.BREAK ] = { xi.effect.PETRIFICATION, 1, xi.mod.INT, 1, 0, 30, 2, 0, false, 0 }, - [xi.magic.spell.BREAKGA ] = { xi.effect.PETRIFICATION, 2, xi.mod.INT, 1, 0, 30, 2, 0, false, 0 }, - [xi.magic.spell.BURN ] = { xi.effect.BURN, 1, xi.mod.INT, 0, 3, 90, 3, 1, true, 0 }, - [xi.magic.spell.CHOKE ] = { xi.effect.CHOKE, 1, xi.mod.INT, 0, 3, 90, 3, 1, true, 0 }, - [xi.magic.spell.CURSE ] = { xi.effect.CURSE_I, 1, xi.mod.INT, 50, 0, 300, 2, 0, false, 0 }, - [xi.magic.spell.DISPEL ] = { xi.effect.NONE, 1, xi.mod.INT, 0, 0, 0, 4, 0, false, 175 }, - [xi.magic.spell.DISPELGA ] = { xi.effect.NONE, 1, xi.mod.INT, 0, 0, 0, 4, 0, false, 0 }, - [xi.magic.spell.DISTRACT ] = { xi.effect.EVASION_DOWN, 1, xi.mod.MND, 0, 0, 120, 2, 0, true, 150 }, - [xi.magic.spell.DISTRACT_II ] = { xi.effect.EVASION_DOWN, 2, xi.mod.MND, 0, 0, 120, 2, 0, true, 150 }, - [xi.magic.spell.DISTRACT_III ] = { xi.effect.EVASION_DOWN, 3, xi.mod.MND, 0, 0, 120, 2, 0, true, 150 }, - [xi.magic.spell.DROWN ] = { xi.effect.DROWN, 1, xi.mod.INT, 0, 3, 90, 3, 1, true, 0 }, - [xi.magic.spell.FRAZZLE ] = { xi.effect.MAGIC_EVASION_DOWN, 1, xi.mod.MND, 0, 0, 120, 2, 0, true, 150 }, - [xi.magic.spell.FRAZZLE_II ] = { xi.effect.MAGIC_EVASION_DOWN, 2, xi.mod.MND, 0, 0, 120, 2, 0, true, 150 }, - [xi.magic.spell.FRAZZLE_III ] = { xi.effect.MAGIC_EVASION_DOWN, 3, xi.mod.MND, 0, 0, 120, 2, 0, true, 150 }, - [xi.magic.spell.FROST ] = { xi.effect.FROST, 1, xi.mod.INT, 0, 3, 90, 3, 1, true, 0 }, - [xi.magic.spell.GRAVITY ] = { xi.effect.WEIGHT, 1, xi.mod.INT, 26, 0, 120, 2, 0, true, 0 }, - [xi.magic.spell.GRAVITY_II ] = { xi.effect.WEIGHT, 2, xi.mod.INT, 32, 0, 180, 2, 0, true, 0 }, - [xi.magic.spell.GRAVIGA ] = { xi.effect.WEIGHT, 1, xi.mod.INT, 50, 0, 120, 2, 0, true, 0 }, - [xi.magic.spell.POISON ] = { xi.effect.POISON, 1, xi.mod.INT, 0, 3, 90, 2, 0, true, 0 }, - [xi.magic.spell.POISON_II ] = { xi.effect.POISON, 2, xi.mod.INT, 0, 3, 120, 2, 0, true, 30 }, - [xi.magic.spell.POISON_III ] = { xi.effect.POISON, 3, xi.mod.INT, 0, 3, 150, 2, 0, true, 0 }, - [xi.magic.spell.POISONGA ] = { xi.effect.POISON, 1, xi.mod.INT, 0, 3, 90, 2, 0, true, 0 }, - [xi.magic.spell.POISONGA_II ] = { xi.effect.POISON, 1, xi.mod.INT, 0, 3, 120, 2, 0, true, 0 }, - [xi.magic.spell.POISONGA_III ] = { xi.effect.POISON, 1, xi.mod.INT, 0, 3, 150, 2, 0, true, 0 }, - [xi.magic.spell.RASP ] = { xi.effect.RASP, 1, xi.mod.INT, 0, 3, 90, 3, 1, true, 0 }, - [xi.magic.spell.SHOCK ] = { xi.effect.SHOCK, 1, xi.mod.INT, 0, 3, 90, 3, 1, true, 0 }, - [xi.magic.spell.SLEEP ] = { xi.effect.SLEEP_I, 1, xi.mod.INT, 1, 0, 60, 2, 0, false, 0 }, - [xi.magic.spell.SLEEP_II ] = { xi.effect.SLEEP_I, 2, xi.mod.INT, 2, 0, 90, 2, 0, false, 0 }, - [xi.magic.spell.SLEEPGA ] = { xi.effect.SLEEP_I, 1, xi.mod.INT, 1, 0, 60, 2, 0, false, 0 }, - [xi.magic.spell.SLEEPGA_II ] = { xi.effect.SLEEP_I, 2, xi.mod.INT, 2, 0, 90, 2, 0, false, 0 }, - [xi.magic.spell.STUN ] = { xi.effect.STUN, 1, xi.mod.INT, 1, 0, 5, 4, 0, false, 200 }, - [xi.magic.spell.VIRUS ] = { xi.effect.PLAGUE, 1, xi.mod.INT, 5, 3, 60, 2, 0, false, 0 }, + [xi.magic.spell.BIND ] = { xi.effect.BIND, 1, xi.mod.INT, 0, 0, 60, 0, false, 0 }, + [xi.magic.spell.BINDGA ] = { xi.effect.BIND, 1, xi.mod.INT, 0, 0, 60, 0, false, 0 }, + [xi.magic.spell.BLIND ] = { xi.effect.BLINDNESS, 1, xi.mod.INT, 0, 0, 180, 0, true, 0 }, + [xi.magic.spell.BLIND_II ] = { xi.effect.BLINDNESS, 3, xi.mod.INT, 0, 0, 180, 0, true, 0 }, + [xi.magic.spell.BLINDGA ] = { xi.effect.BLINDNESS, 2, xi.mod.INT, 0, 0, 180, 0, true, 0 }, + [xi.magic.spell.BREAK ] = { xi.effect.PETRIFICATION, 1, xi.mod.INT, 1, 0, 30, 0, false, 0 }, + [xi.magic.spell.BREAKGA ] = { xi.effect.PETRIFICATION, 2, xi.mod.INT, 1, 0, 30, 0, false, 0 }, + [xi.magic.spell.BURN ] = { xi.effect.BURN, 1, xi.mod.INT, 0, 3, 90, 1, true, 0 }, + [xi.magic.spell.CHOKE ] = { xi.effect.CHOKE, 1, xi.mod.INT, 0, 3, 90, 1, true, 0 }, + [xi.magic.spell.CURSE ] = { xi.effect.CURSE_I, 1, xi.mod.INT, 50, 0, 300, 0, false, 0 }, + [xi.magic.spell.DISPEL ] = { xi.effect.NONE, 1, xi.mod.INT, 0, 0, 0, 0, false, 175 }, + [xi.magic.spell.DISPELGA ] = { xi.effect.NONE, 1, xi.mod.INT, 0, 0, 0, 0, false, 0 }, + [xi.magic.spell.DISTRACT ] = { xi.effect.EVASION_DOWN, 1, xi.mod.MND, 0, 0, 120, 0, true, 150 }, + [xi.magic.spell.DISTRACT_II ] = { xi.effect.EVASION_DOWN, 2, xi.mod.MND, 0, 0, 120, 0, true, 150 }, + [xi.magic.spell.DISTRACT_III ] = { xi.effect.EVASION_DOWN, 3, xi.mod.MND, 0, 0, 120, 0, true, 150 }, + [xi.magic.spell.DROWN ] = { xi.effect.DROWN, 1, xi.mod.INT, 0, 3, 90, 1, true, 0 }, + [xi.magic.spell.FRAZZLE ] = { xi.effect.MAGIC_EVASION_DOWN, 1, xi.mod.MND, 0, 0, 120, 0, true, 150 }, + [xi.magic.spell.FRAZZLE_II ] = { xi.effect.MAGIC_EVASION_DOWN, 2, xi.mod.MND, 0, 0, 120, 0, true, 150 }, + [xi.magic.spell.FRAZZLE_III ] = { xi.effect.MAGIC_EVASION_DOWN, 3, xi.mod.MND, 0, 0, 120, 0, true, 150 }, + [xi.magic.spell.FROST ] = { xi.effect.FROST, 1, xi.mod.INT, 0, 3, 90, 1, true, 0 }, + [xi.magic.spell.GRAVITY ] = { xi.effect.WEIGHT, 1, xi.mod.INT, 26, 0, 120, 0, true, 0 }, + [xi.magic.spell.GRAVITY_II ] = { xi.effect.WEIGHT, 2, xi.mod.INT, 32, 0, 180, 0, true, 0 }, + [xi.magic.spell.GRAVIGA ] = { xi.effect.WEIGHT, 1, xi.mod.INT, 50, 0, 120, 0, true, 0 }, + [xi.magic.spell.POISON ] = { xi.effect.POISON, 1, xi.mod.INT, 0, 3, 90, 0, true, 0 }, + [xi.magic.spell.POISON_II ] = { xi.effect.POISON, 2, xi.mod.INT, 0, 3, 120, 0, true, 30 }, + [xi.magic.spell.POISON_III ] = { xi.effect.POISON, 3, xi.mod.INT, 0, 3, 150, 0, true, 0 }, + [xi.magic.spell.POISONGA ] = { xi.effect.POISON, 1, xi.mod.INT, 0, 3, 90, 0, true, 0 }, + [xi.magic.spell.POISONGA_II ] = { xi.effect.POISON, 1, xi.mod.INT, 0, 3, 120, 0, true, 0 }, + [xi.magic.spell.POISONGA_III ] = { xi.effect.POISON, 1, xi.mod.INT, 0, 3, 150, 0, true, 0 }, + [xi.magic.spell.RASP ] = { xi.effect.RASP, 1, xi.mod.INT, 0, 3, 90, 1, true, 0 }, + [xi.magic.spell.SHOCK ] = { xi.effect.SHOCK, 1, xi.mod.INT, 0, 3, 90, 1, true, 0 }, + [xi.magic.spell.SLEEP ] = { xi.effect.SLEEP_I, 1, xi.mod.INT, 1, 0, 60, 0, false, 0 }, + [xi.magic.spell.SLEEP_II ] = { xi.effect.SLEEP_I, 2, xi.mod.INT, 2, 0, 90, 0, false, 0 }, + [xi.magic.spell.SLEEPGA ] = { xi.effect.SLEEP_I, 1, xi.mod.INT, 1, 0, 60, 0, false, 0 }, + [xi.magic.spell.SLEEPGA_II ] = { xi.effect.SLEEP_I, 2, xi.mod.INT, 2, 0, 90, 0, false, 0 }, + [xi.magic.spell.STUN ] = { xi.effect.STUN, 1, xi.mod.INT, 1, 0, 5, 0, false, 200 }, + [xi.magic.spell.VIRUS ] = { xi.effect.PLAGUE, 1, xi.mod.INT, 5, 3, 60, 0, false, 0 }, -- Black magic Helixes - [xi.magic.spell.GEOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.GEOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.HYDROHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.HYDROHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.ANEMOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.ANEMOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.PYROHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.PYROHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.CRYOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.CRYOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.IONOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.IONOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.NOCTOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.NOCTOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.LUMINOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, - [xi.magic.spell.LUMINOHELIX_II] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, 0, false, 0 }, + [xi.magic.spell.GEOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.GEOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.HYDROHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.HYDROHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.ANEMOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.ANEMOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.PYROHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.PYROHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.CRYOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.CRYOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.IONOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.IONOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.NOCTOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.NOCTOHELIX_II ] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.LUMINOHELIX ] = { xi.effect.HELIX, 1, xi.mod.INT, 0, 10, 30, 0, false, 0 }, + [xi.magic.spell.LUMINOHELIX_II] = { xi.effect.HELIX, 2, xi.mod.INT, 0, 10, 30, 0, false, 0 }, -- White Magic - [xi.magic.spell.ADDLE ] = { xi.effect.ADDLE, 1, xi.mod.MND, 30, 0, 180, 2, 0, true, 0 }, - [xi.magic.spell.FLASH ] = { xi.effect.FLASH, 1, xi.mod.MND, 0, 0, 12, 4, 0, true, 512 }, - [xi.magic.spell.INUNDATION ] = { xi.effect.INUNDATION, 1, xi.mod.MND, 1, 0, 300, 5, 0, false, 0 }, - [xi.magic.spell.PARALYZE ] = { xi.effect.PARALYSIS, 1, xi.mod.MND, 0, 0, 120, 2, 0, true, -10 }, - [xi.magic.spell.PARALYZE_II ] = { xi.effect.PARALYSIS, 3, xi.mod.MND, 0, 0, 120, 2, 0, true, 0 }, - [xi.magic.spell.PARALYGA ] = { xi.effect.PARALYSIS, 2, xi.mod.MND, 0, 0, 120, 2, 0, true, 0 }, - [xi.magic.spell.REPOSE ] = { xi.effect.SLEEP_I, 2, xi.mod.MND, 2, 0, 90, 2, 1, false, 0 }, - [xi.magic.spell.SILENCE ] = { xi.effect.SILENCE, 1, xi.mod.MND, 1, 0, 120, 2, 0, false, 0 }, - [xi.magic.spell.SILENCEGA ] = { xi.effect.SILENCE, 2, xi.mod.MND, 1, 0, 120, 2, 0, false, 0 }, - [xi.magic.spell.SLOW ] = { xi.effect.SLOW, 3, xi.mod.MND, 0, 0, 180, 2, 0, true, 10 }, - [xi.magic.spell.SLOW_II ] = { xi.effect.SLOW, 7, xi.mod.MND, 0, 0, 180, 2, 0, true, 10 }, - [xi.magic.spell.SLOWGA ] = { xi.effect.SLOW, 8, xi.mod.MND, 0, 0, 180, 2, 0, true, 0 }, + [xi.magic.spell.ADDLE ] = { xi.effect.ADDLE, 1, xi.mod.MND, 30, 0, 180, 0, true, 0 }, + [xi.magic.spell.FLASH ] = { xi.effect.FLASH, 1, xi.mod.MND, 0, 0, 12, 0, true, 512 }, + [xi.magic.spell.INUNDATION ] = { xi.effect.INUNDATION, 1, xi.mod.MND, 1, 0, 300, 0, false, 0 }, + [xi.magic.spell.PARALYZE ] = { xi.effect.PARALYSIS, 1, xi.mod.MND, 0, 0, 120, 0, true, -10 }, + [xi.magic.spell.PARALYZE_II ] = { xi.effect.PARALYSIS, 3, xi.mod.MND, 0, 0, 120, 0, true, 0 }, + [xi.magic.spell.PARALYGA ] = { xi.effect.PARALYSIS, 2, xi.mod.MND, 0, 0, 120, 0, true, 0 }, + [xi.magic.spell.REPOSE ] = { xi.effect.SLEEP_I, 2, xi.mod.MND, 2, 0, 90, 1, false, 0 }, + [xi.magic.spell.SILENCE ] = { xi.effect.SILENCE, 1, xi.mod.MND, 1, 0, 120, 0, false, 0 }, + [xi.magic.spell.SILENCEGA ] = { xi.effect.SILENCE, 2, xi.mod.MND, 1, 0, 120, 0, false, 0 }, + [xi.magic.spell.SLOW ] = { xi.effect.SLOW, 3, xi.mod.MND, 0, 0, 180, 0, true, 10 }, + [xi.magic.spell.SLOW_II ] = { xi.effect.SLOW, 7, xi.mod.MND, 0, 0, 180, 0, true, 10 }, + [xi.magic.spell.SLOWGA ] = { xi.effect.SLOW, 8, xi.mod.MND, 0, 0, 180, 0, true, 0 }, -- Ninjutsu - [xi.magic.spell.AISHA_ICHI ] = { xi.effect.ATTACK_DOWN, 1, xi.mod.INT, 15, 0, 120, 4, 1, false, 0 }, - [xi.magic.spell.DOKUMORI_ICHI ] = { xi.effect.POISON, 1, xi.mod.INT, 3, 3, 60, 2, 0, false, 0 }, - [xi.magic.spell.DOKUMORI_NI ] = { xi.effect.POISON, 2, xi.mod.INT, 10, 3, 120, 2, 0, false, 0 }, - [xi.magic.spell.DOKUMORI_SAN ] = { xi.effect.POISON, 3, xi.mod.INT, 20, 3, 360, 2, 0, false, 0 }, - [xi.magic.spell.HOJO_ICHI ] = { xi.effect.SLOW, 3, xi.mod.INT, 1465, 0, 180, 2, 0, false, 0 }, - [xi.magic.spell.HOJO_NI ] = { xi.effect.SLOW, 4, xi.mod.INT, 1953, 0, 300, 2, 0, false, 0 }, - [xi.magic.spell.HOJO_SAN ] = { xi.effect.SLOW, 7, xi.mod.INT, 2930, 0, 420, 2, 0, false, 0 }, - [xi.magic.spell.JUBAKU_ICHI ] = { xi.effect.PARALYSIS, 1, xi.mod.INT, 20, 0, 180, 2, 1, false, 0 }, - [xi.magic.spell.JUBAKU_NI ] = { xi.effect.PARALYSIS, 2, xi.mod.INT, 30, 0, 300, 2, 1, false, 0 }, - [xi.magic.spell.JUBAKU_SAN ] = { xi.effect.PARALYSIS, 3, xi.mod.INT, 35, 0, 420, 2, 1, false, 0 }, - [xi.magic.spell.KURAYAMI_ICHI ] = { xi.effect.BLINDNESS, 1, xi.mod.INT, 20, 0, 180, 2, 0, false, 0 }, - [xi.magic.spell.KURAYAMI_NI ] = { xi.effect.BLINDNESS, 2, xi.mod.INT, 30, 0, 300, 2, 0, false, 0 }, - [xi.magic.spell.KURAYAMI_SAN ] = { xi.effect.BLINDNESS, 3, xi.mod.INT, 40, 0, 420, 2, 0, false, 0 }, - [xi.magic.spell.YURIN_ICHI ] = { xi.effect.INHIBIT_TP, 1, xi.mod.INT, 10, 0, 180, 3, 1, false, 0 }, + [xi.magic.spell.AISHA_ICHI ] = { xi.effect.ATTACK_DOWN, 1, xi.mod.INT, 15, 0, 120, 1, false, 0 }, + [xi.magic.spell.DOKUMORI_ICHI ] = { xi.effect.POISON, 1, xi.mod.INT, 3, 3, 60, 0, false, 0 }, + [xi.magic.spell.DOKUMORI_NI ] = { xi.effect.POISON, 2, xi.mod.INT, 10, 3, 120, 0, false, 0 }, + [xi.magic.spell.DOKUMORI_SAN ] = { xi.effect.POISON, 3, xi.mod.INT, 20, 3, 360, 0, false, 0 }, + [xi.magic.spell.HOJO_ICHI ] = { xi.effect.SLOW, 3, xi.mod.INT, 1465, 0, 180, 0, false, 0 }, + [xi.magic.spell.HOJO_NI ] = { xi.effect.SLOW, 4, xi.mod.INT, 1953, 0, 300, 0, false, 0 }, + [xi.magic.spell.HOJO_SAN ] = { xi.effect.SLOW, 7, xi.mod.INT, 2930, 0, 420, 0, false, 0 }, + [xi.magic.spell.JUBAKU_ICHI ] = { xi.effect.PARALYSIS, 1, xi.mod.INT, 20, 0, 180, 1, false, 0 }, + [xi.magic.spell.JUBAKU_NI ] = { xi.effect.PARALYSIS, 2, xi.mod.INT, 30, 0, 300, 1, false, 0 }, + [xi.magic.spell.JUBAKU_SAN ] = { xi.effect.PARALYSIS, 3, xi.mod.INT, 35, 0, 420, 1, false, 0 }, + [xi.magic.spell.KURAYAMI_ICHI ] = { xi.effect.BLINDNESS, 1, xi.mod.INT, 20, 0, 180, 0, false, 0 }, + [xi.magic.spell.KURAYAMI_NI ] = { xi.effect.BLINDNESS, 2, xi.mod.INT, 30, 0, 300, 0, false, 0 }, + [xi.magic.spell.KURAYAMI_SAN ] = { xi.effect.BLINDNESS, 3, xi.mod.INT, 40, 0, 420, 0, false, 0 }, + [xi.magic.spell.YURIN_ICHI ] = { xi.effect.INHIBIT_TP, 1, xi.mod.INT, 10, 0, 180, 1, false, 0 }, } local function getElementalDebuffPotency(caster, statUsed) @@ -363,7 +362,6 @@ xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) local skillType = spell:getSkillType() local spellGroup = spell:getSpellGroup() local statUsed = pTable[spellId][column.STAT_USED] - local resistStages = pTable[spellId][column.RESIST_STAGES] local message = pTable[spellId][column.MESSAGE_OFFSET] local bonusMacc = pTable[spellId][column.BONUS_MACC] local resistRate = xi.combat.magicHitRate.calculateResistRate(caster, target, spellGroup, skillType, 0, spellElement, statUsed, spellEffect, bonusMacc) @@ -385,7 +383,7 @@ xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) ------------------------------ -- STEP 3: Check if spell resists and Immunobreak. ------------------------------ - if resistRate <= 1 / (2 ^ resistStages) then + if xi.data.statusEffect.isResistRateSuccessfull(spellEffect, resistRate, 0) then -- Decide which resistance rank modifier to use: -- 1: If an effect exists, check if said effect has a specialized resistance rank. -- 2: If an effect doesn't exist, or does but doesn't have a specialized resistance rank, default to action element. From e5c7ef2816b6fd7872fdb72f803cca4e9887d409 Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Tue, 20 Jan 2026 18:16:50 +0100 Subject: [PATCH 4/4] Additional effect system expansion for statuses Replaces all blindness AEs as an example --- .../action_additional_effect_status.lua | 114 ++++++++++++++++++ scripts/globals/mobs.lua | 15 --- .../zones/Batallia_Downs/mobs/Eyegouger.lua | 10 +- .../Jugner_Forest_[S]/mobs/Boll_Weevil.lua | 10 +- scripts/zones/Ordelles_Caves/mobs/Donggu.lua | 10 +- .../Outer_Horutoto_Ruins/mobs/Desmodont.lua | 10 +- .../Yuhtunga_Jungle/mobs/Voluptuous_Vilma.lua | 16 ++- 7 files changed, 164 insertions(+), 21 deletions(-) create mode 100644 scripts/globals/combat/action_additional_effect_status.lua diff --git a/scripts/globals/combat/action_additional_effect_status.lua b/scripts/globals/combat/action_additional_effect_status.lua new file mode 100644 index 00000000000..1a7a900e6c9 --- /dev/null +++ b/scripts/globals/combat/action_additional_effect_status.lua @@ -0,0 +1,114 @@ +----------------------------------- +-- Global file for additional effects (Status Effects) +----------------------------------- +require('scripts/globals/combat/magic_hit_rate') +----------------------------------- +xi = xi or {} +xi.combat = xi.combat or {} +xi.combat.action = xi.combat.action or {} +----------------------------------- + +local defaultsTable = +{ + [xi.effect.AMNESIA ] = { xi.subEffect.AMNESIA }, + [xi.effect.ATTACK_DOWN ] = { xi.subEffect.ATTACK_DOWN }, + [xi.effect.BIND ] = { xi.subEffect.DARKNESS_DAMAGE }, + [xi.effect.BLINDNESS ] = { xi.subEffect.BLIND }, + [xi.effect.CURSE_I ] = { xi.subEffect.CURSE }, + [xi.effect.DEFENSE_DOWN ] = { xi.subEffect.DEFENSE_DOWN }, + [xi.effect.EVASION_DOWN ] = { xi.subEffect.EVASION_DOWN }, + [xi.effect.KO ] = { xi.subEffect.DEATH }, + [xi.effect.PARALYSIS ] = { xi.subEffect.PARALYSIS }, + [xi.effect.PETRIFICATION] = { xi.subEffect.PETRIFY }, + [xi.effect.PLAGUE ] = { xi.subEffect.PLAGUE }, + [xi.effect.POISON ] = { xi.subEffect.POISON }, + [xi.effect.SILENCE ] = { xi.subEffect.SILENCE }, + [xi.effect.SLEEP_I ] = { xi.subEffect.SLEEP }, + [xi.effect.SLOW ] = { xi.subEffect.SLOW }, + [xi.effect.STUN ] = { xi.subEffect.STUN }, + [xi.effect.TERROR ] = { xi.subEffect.PARALYSIS }, + [xi.effect.WEIGHT ] = { xi.subEffect.ATTACK_DOWN }, +} + +----------------------------------- +-- Local functions to ensure defaults are set. +----------------------------------- +local function validateParameters(fedData) + local params = {} + + -- Chance. + params.chance = fedData.chance or 100 -- Default: Always proc. + + -- Status effect application parameters. + params.effectId = fedData.effectId or xi.effect.NONE + params.power = fedData.power or 0 + params.tick = fedData.tick or 0 + params.duration = fedData.duration or 120 + params.subType = fedData.subType or 0 + params.subPower = fedData.subPower or 0 + params.tier = fedData.tier or 0 + + -- Action properties. + params.element = fedData.element or xi.element.NONE + params.actorStat = fedData.actorStat or 0 + params.targetStat = fedData.targetStat or params.actorStat -- Currently unused. For future use. + params.macc = fedData.macc or 0 + params.resistRate = fedData.resistRate or 0 + + -- Optional behavior. + params.resetEmnity = fedData.resetEmnity or false + + -- Animations and messaging. + params.animation = fedData.animation or defaultsTable[params.effectId][1] + params.message = fedData.messageDamage or xi.msg.basic.ADD_EFFECT_STATUS + + return params +end + +----------------------------------- +-- Global functions called from "emtity.onAdditionalEffect()" +----------------------------------- +xi.combat.action.executeAdditionalStatus = function(actor, target, fedData) + local params = validateParameters(fedData) + + -- Early return: Incorrect effect ID. + if params.effectId == xi.effect.NONE then + return 0, 0, 0 + end + + -- Early return: No proc. + if math.random(1, 100) > params.chance then + return 0, 0, 0 + end + + -- Early return: Target is immune. + if xi.data.statusEffect.isTargetImmune(target, params.effectId, params.element) then + return 0, 0, 0 + end + + -- Early return: Target triggers resist trait. + if xi.data.statusEffect.isTargetResistant(actor, target, params.effectId) then + return 0, 0, 0 + end + + -- Early return: Target has an status effect that invalidates current (Outright incompatible or higher tier). + if xi.data.statusEffect.isEffectNullified(target, params.effectId, params.tier) then + return 0, 0, 0 + end + + -- Early return: Resist rate too high. + local resistanceRate = xi.combat.magicHitRate.calculateResistRate(actor, target, 0, 0, xi.skillRank.A_PLUS, params.element, params.actorStat, params.effectId, params.macc) + if not xi.data.statusEffect.isResistRateSuccessfull(params.effectId, resistanceRate, params.resistRate) then + return 0, 0, 0 + end + + -- Calculate duration. + local totalDuration = math.floor(params.duration * resistanceRate) + + -- Apply effect. + if target:addStatusEffect(params.effectId, params.power, params.tick, totalDuration, params.subType, params.subPower, params.tier) then + return params.animation, params.message, params.effectId + end + + return 0, 0, 0 +end diff --git a/scripts/globals/mobs.lua b/scripts/globals/mobs.lua index adb6ab5f035..c2bdd70130a 100644 --- a/scripts/globals/mobs.lua +++ b/scripts/globals/mobs.lua @@ -213,7 +213,6 @@ end xi.mob.additionalEffect = { - BLIND = 0, CURSE = 1, ENAERO = 2, ENBLIZZARD = 3, @@ -247,20 +246,6 @@ xi.mob.ae = xi.mob.additionalEffect local additionalEffects = { - [xi.mob.ae.BLIND] = - { - chance = 25, - ele = xi.element.DARK, - sub = xi.subEffect.BLIND, - msg = xi.msg.basic.ADD_EFFECT_STATUS, - applyEffect = true, - eff = xi.effect.BLINDNESS, - power = 20, - duration = 30, - minDuration = 1, - maxDuration = 45, - }, - [xi.mob.ae.CURSE] = { chance = 20, diff --git a/scripts/zones/Batallia_Downs/mobs/Eyegouger.lua b/scripts/zones/Batallia_Downs/mobs/Eyegouger.lua index 0be47766586..8192d6cc3e1 100644 --- a/scripts/zones/Batallia_Downs/mobs/Eyegouger.lua +++ b/scripts/zones/Batallia_Downs/mobs/Eyegouger.lua @@ -15,7 +15,15 @@ entity.onMobInitialize = function(mob) end entity.onAdditionalEffect = function(mob, target, damage) - return xi.mob.onAddEffect(mob, target, damage, xi.mob.ae.BLIND) + local pTable = + { + chance = 25, + effectId = xi.effect.BLINDNESS, + power = 20, + duration = 60, + } + + return xi.combat.action.executeAdditionalStatus(mob, target, pTable) end entity.onMobDeath = function(mob, player, optParams) diff --git a/scripts/zones/Jugner_Forest_[S]/mobs/Boll_Weevil.lua b/scripts/zones/Jugner_Forest_[S]/mobs/Boll_Weevil.lua index 78f091068fc..4b1f1dc78b0 100644 --- a/scripts/zones/Jugner_Forest_[S]/mobs/Boll_Weevil.lua +++ b/scripts/zones/Jugner_Forest_[S]/mobs/Boll_Weevil.lua @@ -10,7 +10,15 @@ entity.onMobInitialize = function(mob) end entity.onAdditionalEffect = function(mob, target, damage) - return xi.mob.onAddEffect(mob, target, damage, xi.mob.ae.BLIND) + local pTable = + { + chance = 25, + effectId = xi.effect.BLINDNESS, + power = 20, + duration = 60, + } + + return xi.combat.action.executeAdditionalStatus(mob, target, pTable) end entity.onMobDeath = function(mob, player, optParams) diff --git a/scripts/zones/Ordelles_Caves/mobs/Donggu.lua b/scripts/zones/Ordelles_Caves/mobs/Donggu.lua index b31d3d00818..61858ced9e7 100644 --- a/scripts/zones/Ordelles_Caves/mobs/Donggu.lua +++ b/scripts/zones/Ordelles_Caves/mobs/Donggu.lua @@ -23,7 +23,15 @@ entity.onMobInitialize = function(mob) end entity.onAdditionalEffect = function(mob, target, damage) - return xi.mob.onAddEffect(mob, target, damage, xi.mob.ae.BLIND) + local pTable = + { + chance = 25, + effectId = xi.effect.BLINDNESS, + power = 20, + duration = 60, + } + + return xi.combat.action.executeAdditionalStatus(mob, target, pTable) end entity.onMobDeath = function(mob, player, optParams) diff --git a/scripts/zones/Outer_Horutoto_Ruins/mobs/Desmodont.lua b/scripts/zones/Outer_Horutoto_Ruins/mobs/Desmodont.lua index ca05227d3fa..315d6af430b 100644 --- a/scripts/zones/Outer_Horutoto_Ruins/mobs/Desmodont.lua +++ b/scripts/zones/Outer_Horutoto_Ruins/mobs/Desmodont.lua @@ -22,7 +22,15 @@ entity.onMobInitialize = function(mob) end entity.onAdditionalEffect = function(mob, target, damage) - return xi.mob.onAddEffect(mob, target, damage, xi.mob.ae.BLIND) + local pTable = + { + chance = 25, + effectId = xi.effect.BLINDNESS, + power = 20, + duration = 60, + } + + return xi.combat.action.executeAdditionalStatus(mob, target, pTable) end entity.onMobDeath = function(mob, player, optParams) diff --git a/scripts/zones/Yuhtunga_Jungle/mobs/Voluptuous_Vilma.lua b/scripts/zones/Yuhtunga_Jungle/mobs/Voluptuous_Vilma.lua index 72be272db60..4912950439f 100644 --- a/scripts/zones/Yuhtunga_Jungle/mobs/Voluptuous_Vilma.lua +++ b/scripts/zones/Yuhtunga_Jungle/mobs/Voluptuous_Vilma.lua @@ -45,9 +45,21 @@ entity.onAdditionalEffect = function(mob, target, damage) [6] = xi.mob.ae.SLOW, [7] = xi.mob.ae.BIND, } - local random = math.random(1, #effects) - return xi.mob.onAddEffect(mob, target, damage, effects[random]) + local chosenEffect = effects[math.random(1, #effects)] + if chosenEffect == xi.mob.ae.BLIND then + local pTable = + { + chance = 25, + effectId = xi.effect.BLINDNESS, + power = 20, + duration = 60, + } + + return xi.combat.action.executeAdditionalStatus(mob, target, pTable) + end + + return xi.mob.onAddEffect(mob, target, damage, chosenEffect) end entity.onMobDespawn = function(mob)