From 204953269e3eaf5fea035290b1206570e789614e Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Wed, 21 Jan 2026 03:03:08 +0100 Subject: [PATCH 1/2] AEs: Implement effect absorb --- .../action_additional_effect_status.lua | 85 ++++++++++--------- scripts/globals/spells/enfeebling_spell.lua | 2 +- .../Hazhalm_Testing_Grounds/mobs/Djigga.lua | 18 ++-- .../mobs/Djigga_Hildesvini.lua | 18 ++-- 4 files changed, 63 insertions(+), 60 deletions(-) diff --git a/scripts/globals/combat/action_additional_effect_status.lua b/scripts/globals/combat/action_additional_effect_status.lua index 117e33bd444..e9e6a49c5d7 100644 --- a/scripts/globals/combat/action_additional_effect_status.lua +++ b/scripts/globals/combat/action_additional_effect_status.lua @@ -10,25 +10,25 @@ 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.NONE ] = { xi.subEffect.DARKNESS_DAMAGE }, - [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 }, + [xi.effect.AMNESIA ] = { xi.subEffect.AMNESIA, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.ATTACK_DOWN ] = { xi.subEffect.ATTACK_DOWN, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.BIND ] = { xi.subEffect.DARKNESS_DAMAGE, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.BLINDNESS ] = { xi.subEffect.BLIND, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.CURSE_I ] = { xi.subEffect.CURSE, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.DEFENSE_DOWN ] = { xi.subEffect.DEFENSE_DOWN, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.EVASION_DOWN ] = { xi.subEffect.EVASION_DOWN, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.KO ] = { xi.subEffect.DEATH, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.NONE ] = { xi.subEffect.DARKNESS_DAMAGE, xi.msg.basic.ADD_EFFECT_DISPEL }, + [xi.effect.PARALYSIS ] = { xi.subEffect.PARALYSIS, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.PETRIFICATION] = { xi.subEffect.PETRIFY, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.PLAGUE ] = { xi.subEffect.PLAGUE, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.POISON ] = { xi.subEffect.POISON, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.SILENCE ] = { xi.subEffect.SILENCE, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.SLEEP_I ] = { xi.subEffect.SLEEP, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.SLOW ] = { xi.subEffect.SLOW, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.STUN ] = { xi.subEffect.STUN, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.TERROR ] = { xi.subEffect.PARALYSIS, xi.msg.basic.ADD_EFFECT_STATUS }, + [xi.effect.WEIGHT ] = { xi.subEffect.ATTACK_DOWN, xi.msg.basic.ADD_EFFECT_STATUS }, } ----------------------------------- @@ -38,30 +38,31 @@ local function validateParameters(fedData) local params = {} -- Chance. - params.chance = fedData.chance or 100 -- Default: Always proc. + 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 + 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 + 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 + params.resetEmnity = fedData.resetEmnity or false + params.absorbEffect = fedData.absorbEffect 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 + params.animation = fedData.animation or defaultsTable[params.effectId][1] + params.message = fedData.message or defaultsTable[params.effectId][2] return params end @@ -138,11 +139,17 @@ xi.combat.action.executeAdditionalDispel = function(actor, target, fedData) return 0, 0, 0 end - -- Early return: No effect fetched. - local dispelledEffect = target:dispelStatusEffect(xi.effectFlag.DISPELABLE) - if dispelledEffect == xi.effect.NONE then + -- Attampt to dispel or steal an status effect. + local dispelledEffect = 0 + if params.absorbEffect then + dispelledEffect = actor:stealStatusEffect(target, xi.effectFlag.DISPELABLE, true) + else + dispelledEffect = target:dispelStatusEffect(xi.effectFlag.DISPELABLE) + end + + if dispelledEffect == 0 then return 0, 0, 0 end - return params.animation, xi.msg.basic.ADD_EFFECT_DISPEL, dispelledEffect + return params.animation, params.message, dispelledEffect end diff --git a/scripts/globals/spells/enfeebling_spell.lua b/scripts/globals/spells/enfeebling_spell.lua index 02f10dffd69..d92cf08f209 100644 --- a/scripts/globals/spells/enfeebling_spell.lua +++ b/scripts/globals/spells/enfeebling_spell.lua @@ -383,7 +383,7 @@ xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) ------------------------------ -- STEP 3: Check if spell resists and Immunobreak. ------------------------------ - if xi.data.statusEffect.isResistRateSuccessfull(spellEffect, resistRate, 0) then + if not 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. diff --git a/scripts/zones/Hazhalm_Testing_Grounds/mobs/Djigga.lua b/scripts/zones/Hazhalm_Testing_Grounds/mobs/Djigga.lua index 5e4b37c8cad..8855ee8b55d 100644 --- a/scripts/zones/Hazhalm_Testing_Grounds/mobs/Djigga.lua +++ b/scripts/zones/Hazhalm_Testing_Grounds/mobs/Djigga.lua @@ -11,17 +11,15 @@ entity.onMobInitialize = function(mob) end entity.onAdditionalEffect = function(mob, target, damage) - if math.random(1, 100) <= 25 then - -- last argument is true to hide the "effect lost" message from the player - local result = mob:stealStatusEffect(target, xi.effectFlag.DISPELABLE, true) - if result == 0 then - return 0, 0, 0 - end + local pTable = + { + chance = 25, + absorbEffect = true, + animation = xi.subEffect.STATUS_DRAIN, + message = xi.msg.basic.NONE, + } - return xi.subEffect.STATUS_DRAIN, xi.msg.basic.NONE, result - end - - return 0, 0, 0 + return xi.combat.action.executeAdditionalDispel(mob, target, pTable) end return entity diff --git a/scripts/zones/Hazhalm_Testing_Grounds/mobs/Djigga_Hildesvini.lua b/scripts/zones/Hazhalm_Testing_Grounds/mobs/Djigga_Hildesvini.lua index bf1be1723d4..a289e72c048 100644 --- a/scripts/zones/Hazhalm_Testing_Grounds/mobs/Djigga_Hildesvini.lua +++ b/scripts/zones/Hazhalm_Testing_Grounds/mobs/Djigga_Hildesvini.lua @@ -17,17 +17,15 @@ entity.onMobInitialize = function(mob) end entity.onAdditionalEffect = function(mob, target, damage) - if math.random(1, 100) <= 25 then - -- last argument is true to hide the "effect lost" message from the player - local result = mob:stealStatusEffect(target, xi.effectFlag.DISPELABLE, true) - if result == 0 then - return 0, 0, 0 - end + local pTable = + { + chance = 25, + absorbEffect = true, + animation = xi.subEffect.STATUS_DRAIN, + message = xi.msg.basic.NONE, + } - return xi.subEffect.STATUS_DRAIN, xi.msg.basic.NONE, result - end - - return 0, 0, 0 + return xi.combat.action.executeAdditionalDispel(mob, target, pTable) end return entity From 15263bc4fbcae55eacdc0b96d4daf283a3283cbe Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Wed, 21 Jan 2026 05:23:18 +0100 Subject: [PATCH 2/2] Cleanup Immunobreak logic --- scripts/globals/spells/enfeebling_spell.lua | 108 ++++++++++++-------- 1 file changed, 65 insertions(+), 43 deletions(-) diff --git a/scripts/globals/spells/enfeebling_spell.lua b/scripts/globals/spells/enfeebling_spell.lua index d92cf08f209..786763b9029 100644 --- a/scripts/globals/spells/enfeebling_spell.lua +++ b/scripts/globals/spells/enfeebling_spell.lua @@ -132,6 +132,63 @@ local function getElementalDebuffPotency(caster, statUsed) return potency end +local function executeImmunobreak(caster, target, spell, effectId) + -- Early return: Immunobreak didn't exist in lvl 75 era. + if not xi.settings.main.ENABLE_IMMUNOBREAK then + return + end + + -- Early return: Only players can immunobreak. (NOTE: Any job can proc Immunobreaks.) + if not caster:isPC() then + return + end + + -- Early return: Only non-players can be immunobroken. + if not target:isMob() then + return + end + + -- Early return: Only Enfeebling magic can immunobreak. + if spell:getSkillType() ~= xi.skill.ENFEEBLING_MAGIC then + return + end + + -- Early return: This effect doesn't have an immunobreak associated modifier. + local immunobreakModId = xi.data.statusEffect.getAssociatedImmunobreakModifier(effectId) + if immunobreakModId == 0 then + return + end + + -- Fetch resistance rank modifier (Either effect-specific or elemental) + local resistanceRankModId = xi.data.statusEffect.getAssociatedResistanceRankModifier(effectId, spell:getElement()) + if resistanceRankModId == 0 then -- If it's an effect and this is 0, try with element. + resistanceRankModId = xi.data.element.getElementalResistanceRankModifier(spell:getElement()) + end + + -- Early return: Only mobs with a resistance rank of 6+ (x <= 30% EEM) can be immunobroken. + local baseResistanceRank = target:getMod(resistanceRankModId) + if baseResistanceRank < 6 then + return + end + + -- Early return: Resistance rank cannot be lowered (and wont trigger) bellow rank 4 (50% EEM) + local immunobreakValue = target:getMod(immunobreakModId) + local finalResistanceRank = baseResistanceRank - immunobreakValue + if finalResistanceRank <= 4 then + return + end + + -- Calculate Immunobreack chance. + local immunobreakChance = caster:getMerit(xi.merit.IMMUNOBREAK_CHANCE) + 20 / (immunobreakValue + 1) -- TODO: Add immunobreak gear? + if math.random(1, 100) > immunobreakChance then + return + end + + -- Apply immunobreak effect (lower resistance rank) and apply special message. + target:setMod(immunobreakModId, immunobreakValue + 1) -- TODO: Add equipment modifier (x2) here (Chironic Hose). + spell:setModifier(xi.msg.actionModifier.IMMUNOBREAK) +end + -- Calculate potency. xi.spells.enfeebling.calculatePotency = function(caster, target, spellId, spellEffect, skillType, statUsed) local potency = pTable[spellId][column.BASE_POTENCY] @@ -332,6 +389,7 @@ end xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) local spellId = spell:getID() local spellElement = spell:getElement() + local skillType = spell:getSkillType() local spellEffect = pTable[spellId][column.EFFECT_ID] local tier = pTable[spellId][column.TIER] @@ -359,12 +417,11 @@ xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) ------------------------------ -- STEP 2: Calculate resist tiers. ------------------------------ - local skillType = spell:getSkillType() - local spellGroup = spell:getSpellGroup() - local statUsed = pTable[spellId][column.STAT_USED] - 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) + local spellGroup = spell:getSpellGroup() + local statUsed = pTable[spellId][column.STAT_USED] + 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) if spellEffect ~= xi.effect.NONE then -- Stymie @@ -381,45 +438,10 @@ xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) end ------------------------------ - -- STEP 3: Check if spell resists and Immunobreak. + -- STEP 3: Check if spell resists. ------------------------------ if not 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. - local resistanceRankMod = xi.data.statusEffect.getAssociatedResistanceRankModifier(spellEffect, spellElement) - - if resistanceRankMod == 0 then -- If it's an effect and this is 0, try with element. - resistanceRankMod = xi.data.element.getElementalResistanceRankModifier(spellElement) - end - - -- Fetch resistance rank and apply possible modifiers to it. - local resistanceRank = target:getMod(resistanceRankMod) - - -- Attempt immunobreak. Fetch resistance rank modifier. - local immunobreakModifier = xi.data.statusEffect.getAssociatedImmunobreakModifier(spellEffect) - local immunobreakValue = target:getMod(immunobreakModifier) - - if - xi.settings.main.ENABLE_IMMUNOBREAK and -- Immunobreak didn't exist in lvl 75 era. - caster:isPC() and -- Only players can immunobreak. - caster:getMainJob() == xi.job.RDM and -- Only Red Mages can immunobreak. - target:isMob() and -- Only non-players can be immunobroken. - immunobreakModifier > 0 and -- Only certain effects can be immunobroken. - skillType == xi.skill.ENFEEBLING_MAGIC and -- Only Enfeebling magic can immunobreak. - resistanceRank >= 5 -- Only mobs with a resistance rank of 5+ (50% EEM) can be immunobroken. - then - local immunobreakChance = 20 / (1 + immunobreakValue) + caster:getMerit(xi.merit.IMMUNOBREAK_CHANCE) -- TODO: Add immunobreak gear? - - -- We successfully trigger Immunobreak. Change target modifier and set correct message. - if math.random(1, 100) <= immunobreakChance then - target:setMod(immunobreakModifier, immunobreakValue + 1) -- TODO: Add equipment modifier (x2) here. - - spell:setModifier(xi.msg.actionModifier.IMMUNOBREAK) - end - end - - -- We still resited. + executeImmunobreak(caster, target, spell, spellEffect) spell:setMsg(xi.msg.basic.MAGIC_RESIST) return spellEffect