From e3ae51ec477de11e4e99a5d22fb91984d41f60b3 Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Thu, 23 Jan 2025 02:45:50 +0100 Subject: [PATCH] Cleanup enfeebling songs - Add MACC logic for soul voice - Remove unnecesary steps and columns - Correct effect of gear on potency, macc and such --- scripts/effects/elegy.lua | 3 +- scripts/effects/threnody.lua | 3 +- scripts/globals/combat/magic_hit_rate.lua | 31 +- scripts/globals/spells/enfeebling_song.lua | 446 ++++++++------------ scripts/globals/spells/enfeebling_spell.lua | 36 +- 5 files changed, 211 insertions(+), 308 deletions(-) diff --git a/scripts/effects/elegy.lua b/scripts/effects/elegy.lua index a5a5419fab5..a1d246d402b 100644 --- a/scripts/effects/elegy.lua +++ b/scripts/effects/elegy.lua @@ -5,14 +5,13 @@ local effectObject = {} effectObject.onEffectGain = function(target, effect) - target:addMod(xi.mod.HASTE_MAGIC, -effect:getPower()) + effect:addMod(xi.mod.HASTE_MAGIC, -effect:getPower()) end effectObject.onEffectTick = function(target, effect) end effectObject.onEffectLose = function(target, effect) - target:delMod(xi.mod.HASTE_MAGIC, -effect:getPower()) end return effectObject diff --git a/scripts/effects/threnody.lua b/scripts/effects/threnody.lua index 416035b4c7c..0c8e1e5e8cd 100644 --- a/scripts/effects/threnody.lua +++ b/scripts/effects/threnody.lua @@ -6,14 +6,13 @@ local effectObject = {} effectObject.onEffectGain = function(target, effect) - target:addMod(effect:getSubPower(), effect:getPower()) + effect:addMod(effect:getSubPower(), -effect:getPower()) end effectObject.onEffectTick = function(target, effect) end effectObject.onEffectLose = function(target, effect) - target:delMod(effect:getSubPower(), effect:getPower()) end return effectObject diff --git a/scripts/globals/combat/magic_hit_rate.lua b/scripts/globals/combat/magic_hit_rate.lua index c324454d203..c58cdcc73dd 100644 --- a/scripts/globals/combat/magic_hit_rate.lua +++ b/scripts/globals/combat/magic_hit_rate.lua @@ -333,8 +333,30 @@ local function magicAccuracyFromFoodMultiplier(actor) return magicAcc end +local function magicAccuracyFromSoulVoiceMultiplier(actor, skillType, effectId) + local effectTable = + set{ + xi.effect.SLEEP_I, -- Lullabies + xi.effect.NONE, -- Magic Finale + xi.effect.CHARM_I -- Maiden's Virellai + } + + if + effectTable[effectId] and + skillType == xi.skill.SINGING + then + if actor:hasStatusEffect(xi.effect.SOUL_VOICE) then + return 2 + elseif actor:hasStatusEffect(xi.effect.MARCATO) then + return 1.5 + end + end + + return 1 +end + -- Global function to calculate total magicc accuracy. -xi.combat.magicHitRate.calculateActorMagicAccuracy = function(actor, target, spellGroup, skillType, skillRank, actionElement, statUsed, bonusMacc) +xi.combat.magicHitRate.calculateActorMagicAccuracy = function(actor, target, spellGroup, skillType, skillRank, actionElement, statUsed, effectId, bonusMacc) local finalMagicAcc = 0 local magicAccBase = actor:getMod(xi.mod.MACC) + actor:getILvlMacc(xi.slot.MAIN) @@ -349,11 +371,12 @@ xi.combat.magicHitRate.calculateActorMagicAccuracy = function(actor, target, spe local magicAccWeather = magicAccuracyFromWeatherElement(actor, actionElement) -- Multipliers - local magicAccFoodFactor = magicAccuracyFromFoodMultiplier(actor) + local magicAccFoodFactor = magicAccuracyFromFoodMultiplier(actor) + local magicAccSoulVoiceFactor = magicAccuracyFromSoulVoiceMultiplier(actor, skillType, effectId) -- Add up food magic accuracy. finalMagicAcc = magicAccBase + magicAccSkill + magicAccElement + magicAccStatDiff + magicAccEffects + magicAccMerits + magicAccJobPoints + magicAccBurst + magicAccDay + magicAccWeather + bonusMacc - finalMagicAcc = math.floor(finalMagicAcc * magicAccFoodFactor) + finalMagicAcc = math.floor(finalMagicAcc * magicAccFoodFactor * magicAccSoulVoiceFactor) return finalMagicAcc end @@ -537,7 +560,7 @@ xi.combat.magicHitRate.calculateResistRate = function(actor, target, spellGroup, end -- Get Actor Magic Accuracy and target Magic Evasion - local magicAcc = xi.combat.magicHitRate.calculateActorMagicAccuracy(actor, target, spellGroup, skillType, skillRank, actionElement, statUsed, bonusMacc) + local magicAcc = xi.combat.magicHitRate.calculateActorMagicAccuracy(actor, target, spellGroup, skillType, skillRank, actionElement, statUsed, effectId, bonusMacc) local magicEva = xi.combat.magicHitRate.calculateTargetMagicEvasion(actor, target, actionElement, magicEvasionModifier, rankModifier) local magicHitRate = xi.combat.magicHitRate.calculateMagicHitRate(magicAcc, magicEva) local resistRate = xi.combat.magicHitRate.calculateResistanceFactor(actor, target, actionElement, magicHitRate, rankModifier) diff --git a/scripts/globals/spells/enfeebling_song.lua b/scripts/globals/spells/enfeebling_song.lua index f07a450e2d7..a39bcb58aee 100644 --- a/scripts/globals/spells/enfeebling_song.lua +++ b/scripts/globals/spells/enfeebling_song.lua @@ -13,349 +13,243 @@ xi = xi or {} xi.spells = xi.spells or {} xi.spells.enfeebling = xi.spells.enfeebling or {} ----------------------------------- - --- Soul voice affects different songs differently. -local svType = +local column = { - POWER = 1, - DURATION = 2, - ACCURACY = 3, + SONG_EFFECT = 1, + SONG_POWER_BASE = 2, + SONG_POWER_CAP = 3, + SONG_DURATION = 4, + SONG_MODIFIER = 5, } -local soulVoiceMultiplier = 2 -local marcatoMultiplier = 1.5 -local troubadourMultiplier = 2 -local gearBoostDurationMultiplier = 0.10 -local additionalClarionSeconds = 2 -local additionalTenutoSeconds = 2 local pTable = -{ -- |POWER | - -- 1 2 3 4 5 6 7 8 9 10 11 - -- [Spell ID ] = { Effect, Base, Mult, Cap, Duration, Resist, Augment, Modifier, SoulVoice, JobPoints, GearAcc}, - - -- Requiem (has job point effects) - -- https://www.bg-wiki.com/ffxi/Requiem - [xi.magic.spell.FOE_REQUIEM ] = { xi.effect.REQUIEM, 1, 3, 999, 63, 0.5, 0, xi.mod.REQUIEM_EFFECT, svType.POWER, xi.jp.REQUIEM_EFFECT, false }, - [xi.magic.spell.FOE_REQUIEM_II ] = { xi.effect.REQUIEM, 2, 3, 999, 79, 0.5, 0, xi.mod.REQUIEM_EFFECT, svType.POWER, xi.jp.REQUIEM_EFFECT, false }, - [xi.magic.spell.FOE_REQUIEM_III ] = { xi.effect.REQUIEM, 3, 3, 999, 95, 0.5, 0, xi.mod.REQUIEM_EFFECT, svType.POWER, xi.jp.REQUIEM_EFFECT, false }, - [xi.magic.spell.FOE_REQUIEM_IV ] = { xi.effect.REQUIEM, 4, 3, 999, 111, 0.5, 0, xi.mod.REQUIEM_EFFECT, svType.POWER, xi.jp.REQUIEM_EFFECT, false }, - [xi.magic.spell.FOE_REQUIEM_V ] = { xi.effect.REQUIEM, 5, 3, 999, 127, 0.5, 0, xi.mod.REQUIEM_EFFECT, svType.POWER, xi.jp.REQUIEM_EFFECT, false }, - [xi.magic.spell.FOE_REQUIEM_VI ] = { xi.effect.REQUIEM, 6, 3, 999, 143, 0.5, 0, xi.mod.REQUIEM_EFFECT, svType.POWER, xi.jp.REQUIEM_EFFECT, false }, - [xi.magic.spell.FOE_REQUIEM_VII ] = { xi.effect.REQUIEM, 8, 3, 999, 160, 0.5, 0, xi.mod.REQUIEM_EFFECT, svType.POWER, xi.jp.REQUIEM_EFFECT, false }, - - -- Lullaby (has job point effects) - -- https://www.bg-wiki.com/ffxi/Category:Lullaby - [xi.magic.spell.FOE_LULLABY ] = { xi.effect.SLEEP_I, 1, 0, 1, 30, 0.5, 0, xi.mod.LULLABY_EFFECT, svType.ACCURACY, xi.jp.LULLABY_DURATION, true }, - [xi.magic.spell.FOE_LULLABY_II ] = { xi.effect.SLEEP_I, 1, 0, 1, 60, 0.5, 0, xi.mod.LULLABY_EFFECT, svType.ACCURACY, xi.jp.LULLABY_DURATION, true }, - [xi.magic.spell.HORDE_LULLABY ] = { xi.effect.SLEEP_I, 1, 0, 1, 30, 0.5, 0, xi.mod.LULLABY_EFFECT, svType.ACCURACY, xi.jp.LULLABY_DURATION, true }, - [xi.magic.spell.HORDE_LULLABY_II ] = { xi.effect.SLEEP_I, 1, 0, 1, 60, 0.5, 0, xi.mod.LULLABY_EFFECT, svType.ACCURACY, xi.jp.LULLABY_DURATION, true }, - - -- Finale - -- https://www.bg-wiki.com/ffxi/Category:Finale - [xi.magic.spell.MAGIC_FINALE ] = { xi.effect.NONE, 1, 1, 1, 0, 0.9375, 0, xi.mod.FINALE_EFFECT, svType.ACCURACY, 0, true }, - - -- Elegy - -- https://www.bg-wiki.com/ffxi/Category:Elegy - [xi.magic.spell.BATTLEFIELD_ELEGY ] = { xi.effect.ELEGY, 2500, 249, 5000, 120, 0.5, 0, xi.mod.ELEGY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.CARNAGE_ELEGY ] = { xi.effect.ELEGY, 5000, 100, 5000, 180, 0.5, 0, xi.mod.ELEGY_EFFECT, svType.POWER, 0, false }, - - -- Threnody - -- https://www.bg-wiki.com/ffxi/Category:Threnody - [xi.magic.spell.FIRE_THRENODY ] = { xi.effect.THRENODY, 50, 5, 95, 60, 0.5, xi.mod.FIRE_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.ICE_THRENODY ] = { xi.effect.THRENODY, 50, 5, 95, 60, 0.5, xi.mod.ICE_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.WIND_THRENODY ] = { xi.effect.THRENODY, 50, 5, 95, 60, 0.5, xi.mod.WIND_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.EARTH_THRENODY ] = { xi.effect.THRENODY, 50, 5, 95, 60, 0.5, xi.mod.EARTH_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.LIGHTNING_THRENODY ] = { xi.effect.THRENODY, 50, 5, 95, 60, 0.5, xi.mod.THUNDER_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.WATER_THRENODY ] = { xi.effect.THRENODY, 50, 5, 95, 60, 0.5, xi.mod.WATER_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.LIGHT_THRENODY ] = { xi.effect.THRENODY, 50, 5, 95, 60, 0.5, xi.mod.LIGHT_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.DARK_THRENODY ] = { xi.effect.THRENODY, 50, 5, 95, 60, 0.5, xi.mod.DARK_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.FIRE_THRENODY_II ] = { xi.effect.THRENODY, 160, 5, 205, 90, 0.5, xi.mod.FIRE_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.ICE_THRENODY_II ] = { xi.effect.THRENODY, 160, 5, 205, 90, 0.5, xi.mod.ICE_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.WIND_THRENODY_II ] = { xi.effect.THRENODY, 160, 5, 205, 90, 0.5, xi.mod.WIND_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.EARTH_THRENODY_II ] = { xi.effect.THRENODY, 160, 5, 205, 90, 0.5, xi.mod.EARTH_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.LIGHTNING_THRENODY_II] = { xi.effect.THRENODY, 160, 5, 205, 90, 0.5, xi.mod.THUNDER_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.WATER_THRENODY_II ] = { xi.effect.THRENODY, 160, 5, 205, 90, 0.5, xi.mod.WATER_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.LIGHT_THRENODY_II ] = { xi.effect.THRENODY, 160, 5, 205, 90, 0.5, xi.mod.LIGHT_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - [xi.magic.spell.DARK_THRENODY_II ] = { xi.effect.THRENODY, 160, 5, 205, 90, 0.5, xi.mod.DARK_MEVA, xi.mod.THRENODY_EFFECT, svType.POWER, 0, false }, - - -- Virelai - [xi.magic.spell.MAIDENS_VIRELAI ] = { xi.effect.CHARM_I, 0, 0, 0, 30, 0.5, 0, xi.mod.VIRELAI_EFFECT, svType.ACCURACY, 0, true }, - - -- Nocturne - [xi.magic.spell.PINING_NOCTURNE ] = { xi.effect.NOCTURNE, 15, 1.5, 25, 120, 0.5, 0, 0, svType.POWER, 0, true }, +{ + -- [Spell ID ] = { Effect, Base, Cap, Dur, Modifier }, + -- Requiem: https://www.bg-wiki.com/ffxi/Category:Requiem + [xi.magic.spell.FOE_REQUIEM ] = { xi.effect.REQUIEM, 1, 300, 60, xi.mod.REQUIEM_EFFECT }, + [xi.magic.spell.FOE_REQUIEM_II ] = { xi.effect.REQUIEM, 2, 300, 60, xi.mod.REQUIEM_EFFECT }, + [xi.magic.spell.FOE_REQUIEM_III ] = { xi.effect.REQUIEM, 3, 300, 90, xi.mod.REQUIEM_EFFECT }, + [xi.magic.spell.FOE_REQUIEM_IV ] = { xi.effect.REQUIEM, 4, 300, 90, xi.mod.REQUIEM_EFFECT }, + [xi.magic.spell.FOE_REQUIEM_V ] = { xi.effect.REQUIEM, 5, 300, 90, xi.mod.REQUIEM_EFFECT }, + [xi.magic.spell.FOE_REQUIEM_VI ] = { xi.effect.REQUIEM, 6, 300, 120, xi.mod.REQUIEM_EFFECT }, + [xi.magic.spell.FOE_REQUIEM_VII ] = { xi.effect.REQUIEM, 8, 300, 120, xi.mod.REQUIEM_EFFECT }, + -- Lullaby: https://www.bg-wiki.com/ffxi/Category:Lullaby + [xi.magic.spell.FOE_LULLABY ] = { xi.effect.SLEEP_I, 1, 1, 30, xi.mod.LULLABY_EFFECT }, + [xi.magic.spell.FOE_LULLABY_II ] = { xi.effect.SLEEP_I, 1, 1, 60, xi.mod.LULLABY_EFFECT }, + [xi.magic.spell.HORDE_LULLABY ] = { xi.effect.SLEEP_I, 1, 1, 30, xi.mod.LULLABY_EFFECT }, + [xi.magic.spell.HORDE_LULLABY_II ] = { xi.effect.SLEEP_I, 1, 1, 60, xi.mod.LULLABY_EFFECT }, + -- Finale: https://www.bg-wiki.com/ffxi/Category:Finale + [xi.magic.spell.MAGIC_FINALE ] = { xi.effect.NONE, 1, 1, 0, xi.mod.FINALE_EFFECT }, + -- Elegy: https://www.bg-wiki.com/ffxi/Category:Elegy + [xi.magic.spell.BATTLEFIELD_ELEGY ] = { xi.effect.ELEGY, 2500, 5000, 120, xi.mod.ELEGY_EFFECT }, + [xi.magic.spell.CARNAGE_ELEGY ] = { xi.effect.ELEGY, 5000, 5000, 180, xi.mod.ELEGY_EFFECT }, + -- Threnody: https://www.bg-wiki.com/ffxi/Category:Threnody + [xi.magic.spell.FIRE_THRENODY ] = { xi.effect.THRENODY, 50, 95, 60, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.ICE_THRENODY ] = { xi.effect.THRENODY, 50, 95, 60, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.WIND_THRENODY ] = { xi.effect.THRENODY, 50, 95, 60, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.EARTH_THRENODY ] = { xi.effect.THRENODY, 50, 95, 60, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.LIGHTNING_THRENODY ] = { xi.effect.THRENODY, 50, 95, 60, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.WATER_THRENODY ] = { xi.effect.THRENODY, 50, 95, 60, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.LIGHT_THRENODY ] = { xi.effect.THRENODY, 50, 95, 60, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.DARK_THRENODY ] = { xi.effect.THRENODY, 50, 95, 60, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.FIRE_THRENODY_II ] = { xi.effect.THRENODY, 160, 205, 90, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.ICE_THRENODY_II ] = { xi.effect.THRENODY, 160, 205, 90, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.WIND_THRENODY_II ] = { xi.effect.THRENODY, 160, 205, 90, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.EARTH_THRENODY_II ] = { xi.effect.THRENODY, 160, 205, 90, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.LIGHTNING_THRENODY_II] = { xi.effect.THRENODY, 160, 205, 90, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.WATER_THRENODY_II ] = { xi.effect.THRENODY, 160, 205, 90, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.LIGHT_THRENODY_II ] = { xi.effect.THRENODY, 160, 205, 90, xi.mod.THRENODY_EFFECT }, + [xi.magic.spell.DARK_THRENODY_II ] = { xi.effect.THRENODY, 160, 205, 90, xi.mod.THRENODY_EFFECT }, + -- Virelai: https://www.bg-wiki.com/ffxi/Category:Virelai + [xi.magic.spell.MAIDENS_VIRELAI ] = { xi.effect.CHARM_I, 0, 0, 30, xi.mod.VIRELAI_EFFECT }, + -- Nocturne: https://www.bg-wiki.com/ffxi/Category:Nocturne + [xi.magic.spell.PINING_NOCTURNE ] = { xi.effect.NOCTURNE, 15, 25, 120, 0 }, } ------------------------------------ --- Casts an enfeebling song. --- TODO: https://github.com/LandSandBoat/server/pull/6677#issuecomment-2580655974 ------------------------------------ ----@param caster CBaseEntity ----@param target CBaseEntity ----@param spell CSpell ----@return xi.effect ------------------------------------ -xi.spells.enfeebling.useEnfeeblingSong = function(caster, target, spell) - local spellId = spell:getID() - local spellEffect = pTable[spellId][1] - local resistThreshold = pTable[spellId][6] - local augmentStat = pTable[spellId][7] - local songModifier = pTable[spellId][8] - local soulVoiceType = pTable[spellId][9] - local jobPointEffect = pTable[spellId][10] - local spellElement = spell:getElement() - local spellGroup = xi.magic.spellGroup.SONG - local skillType = xi.skill.SINGING - local spellStat = xi.mod.CHR - local bonusMagicAcc = 0 - local dotTick = 0 - - -- Check the amount of Song+ and All_Song+ gear. - local gearBoost = caster:getMod(songModifier) + caster:getMod(xi.mod.ALL_SONGS_EFFECT) - - ------------------------------ - -- STEP 1: Check spell nullification. - ------------------------------ - if xi.spells.enfeebling.handleEffectNullification(caster, target, spell, spellEffect) then - return spellEffect - end - - ------------------------------ - -- STEP 2: Check if spell resists. - -- Songs cannot Immunobreak. (https://www.bg-wiki.com/ffxi/Category:Enfeebling_Magic#Immunobreak) - -- TODO: Add BRD job point support to magicAccuracyFromJobPoints in magic_hit_rate.lua. (https://www.bg-wiki.com/ffxi/Bard#Job_Points) - -- TODO: Add Troubadour support to magicAccuracyFromMerits in magic_hit_rate.lua. (https://www.bg-wiki.com/ffxi/Troubadour) - ------------------------------ - -- Finale has innate +175 to magic accuracy. - -- https://www.bg-wiki.com/ffxi/Magic_Finale - if spellEffect == xi.effect.NONE then - bonusMagicAcc = 175 - end - - local resistRate = xi.combat.magicHitRate.calculateResistRate(caster, target, spellGroup, skillType, 0, spellElement, spellStat, spellEffect, bonusMagicAcc) - if resistRate < resistThreshold then - spell:setMsg(xi.msg.basic.MAGIC_RESIST) - return spellEffect - end - - ------------------------------ - -- STEP 3: Calculate power and duration. - ------------------------------ - local power = xi.spells.enfeebling.calculateSongPower(caster, spellId, gearBoost, jobPointEffect, spellEffect, soulVoiceType) or 0 - local duration = xi.spells.enfeebling.calculateSongDuration(caster, spellId, gearBoost, jobPointEffect, spellEffect, soulVoiceType) or 0 - - ------------------------------ - -- STEP 4: Special cases. - ------------------------------ - local finalValues = xi.spells.enfeebling.processSongExceptions(caster, target, spell, power, duration, dotTick, resistRate, spellEffect) - if finalValues.earlyQuit then - return finalValues.effect - end - - ------------------------------ - -- STEP 5: Attempt to apply the status effect. Check for magic burst. - ------------------------------ - if target:addStatusEffect(finalValues.effect, finalValues.power, finalValues.dotTick, finalValues.duration, 0, augmentStat) then - local _, skillchainCount = xi.magicburst.formMagicBurst(spellElement, target) - if skillchainCount > 0 then - spell:setMsg(xi.msg.basic.MAGIC_BURST_ENFEEB) - caster:triggerRoeEvent(xi.roeTrigger.MAGIC_BURST) - else - -- Lullaby has a different application message than the rest of the song debuffs. - if finalValues.effect == xi.effect.SLEEP_I then - spell:setMsg(xi.msg.basic.MAGIC_ENFEEB_IS) - else - spell:setMsg(xi.msg.basic.MAGIC_ENFEEB) - end - end - else - spell:setMsg(xi.msg.basic.MAGIC_NO_EFFECT) - end - - return finalValues.effect -end - ----------------------------------- -- Calculates song power. ----------------------------------- ----@param caster CBaseEntity ----@param spellId xi.magic.spell ----@param gearBoost integer the amount of Song+ and All Song from gear ----@param jobPointType integer TODO: This is not enum'd yet. ----@param spellEffect xi.effect ----@param soulVoiceType integer ----@return number ------------------------------------ -xi.spells.enfeebling.calculateSongPower = function(caster, spellId, gearBoost, jobPointType, spellEffect, soulVoiceType) - local power = pTable[spellId][2] - local powerBoostMult = pTable[spellId][3] - local powerCap = pTable[spellId][4] - - -- Boost from Song+ and All_Song+ gear. - power = power + gearBoost * powerBoostMult +xi.spells.enfeebling.calculateSongPower = function(caster, spellEffect, basePower, gearBoost) + local power = basePower - -- Requiem gets a power boost from job points of 3 HP / tick per level. - if spellEffect == xi.effect.REQUIEM and jobPointType ~= 0 then - power = math.floor(power + caster:getJobPointLevel(jobPointType) * 3) + if spellEffect == xi.effect.REQUIEM then + power = power + utils.clamp(gearBoost - 1, 0, 20) + caster:getJobPointLevel(xi.jp.REQUIEM_EFFECT) * 3 + elseif spellEffect == xi.effect.ELEGY then + power = power + gearBoost * 6375 / 256 -- Simplified numbers of: 25.5 * 10000/1024 + elseif spellEffect == xi.effect.THRENODY then + power = power + gearBoost * 5 + elseif spellEffect == xi.effect.NOCTURNE then + power = power + gearBoost * 1.5 end -- Apply Soul Voice or Marcato if appropriate. - if soulVoiceType == svType.POWER then + local effectTable = + set{ + xi.effect.ELEGY, + xi.effect.NOCTURNE, + xi.effect.REQUIEM, + xi.effect.THRENODY + } + + if effectTable[spellEffect] then if caster:hasStatusEffect(xi.effect.SOUL_VOICE) then - power = power * soulVoiceMultiplier + power = power * 2 elseif caster:hasStatusEffect(xi.effect.MARCATO) then - power = power * marcatoMultiplier + power = power * 1.5 end end - -- Cap power if necessary. - power = utils.clamp(power, 0, powerCap) - return power end ----------------------------------- -- Calculates song duration. ----------------------------------- ----@param caster CBaseEntity ----@param spellId xi.magic.spell ----@param gearBoost integer the amount of Song+ and All Song from gear ----@param jobPointType integer TODO: This is not enum'd yet. ----@param spellEffect xi.effect ----@param soulVoiceType integer ----@return integer ------------------------------------ -xi.spells.enfeebling.calculateSongDuration = function(caster, spellId, gearBoost, jobPointType, spellEffect, soulVoiceType) - local duration = pTable[spellId][5] - +xi.spells.enfeebling.calculateSongDuration = function(caster, spellEffect, baseDuration, gearBoost) -- Duration boost of 10% per level of Song+ and All_Song+ gear plus song duration gear. - duration = math.floor(duration * (1 + gearBoost * gearBoostDurationMultiplier)) + local duration = baseDuration - -- Job point gift duration bonus (5%) is captured by the SONG_DURATION_BONUS mod. - -- Virelai is not affected by song duration bonus or BRD gifts. - -- https://www.bg-wiki.com/ffxi/Category:Virelai - local songDurationBonus = caster:getMod(xi.mod.SONG_DURATION_BONUS) - if songDurationBonus > 0 and spellEffect ~= xi.effect.CHARM_I then - duration = math.floor(duration * (1 + songDurationBonus / 100)) + -- Lullaby gets a duration boost from job points of 1 second per level. + if spellEffect == xi.effect.SLEEP_I then + duration = duration + gearBoost * baseDuration / 10 + caster:getJobPointLevel(xi.jp.LULLABY_DURATION) end - -- Lullaby gets a duration boost from job points of 1 second per level. - if spellEffect == xi.effect.SLEEP_I and jobPointType ~= 0 then - duration = duration + caster:getJobPointLevel(xi.jp.LULLABY_DURATION) + -- Virelai is not affected by song duration bonus or BRD gifts. + if spellEffect ~= xi.effect.CHARM_I then + duration = math.floor(duration * (1 + caster:getMod(xi.mod.SONG_DURATION_BONUS) / 100)) + else + duration = duration + gearBoost * 3 end - -- Add duration from job points for Clarion Call and Tenuto. + -- Duration from status effects. if caster:hasStatusEffect(xi.effect.CLARION_CALL) then - duration = math.floor(duration + caster:getJobPointLevel(xi.jp.CLARION_CALL_EFFECT) * additionalClarionSeconds) + duration = duration + caster:getJobPointLevel(xi.jp.CLARION_CALL_EFFECT) * 2 end if caster:hasStatusEffect(xi.effect.TENUTO) then - duration = math.floor(duration + caster:getJobPointLevel(xi.jp.TENUTO_EFFECT) * additionalTenutoSeconds) - end - - -- Apply Soul Voice or Marcato if appropriate. - if soulVoiceType == svType.DURATION then - if caster:hasStatusEffect(xi.effect.SOUL_VOICE) then - duration = math.floor(duration * soulVoiceMultiplier) - - elseif caster:hasStatusEffect(xi.effect.MARCATO) then - duration = math.floor(duration + caster:getJobPointLevel(xi.jp.MARCATO_EFFECT)) - duration = math.floor(duration * marcatoMultiplier) - caster:delStatusEffect(xi.effect.MARCATO) - end + duration = duration + caster:getJobPointLevel(xi.jp.TENUTO_EFFECT) * 2 end - -- Troubadour does not affecte Virelai. - -- https://www.bg-wiki.com/ffxi/Category:Virelai if caster:hasStatusEffect(xi.effect.TROUBADOUR) and spellEffect ~= xi.effect.CHARM_I then - duration = math.floor(duration * troubadourMultiplier) + duration = math.floor(duration * 2) end return duration end ----------------------------------- --- Adjusts power, duration, etc. to account for unique song needs. ------------------------------------ ----@param caster CBaseEntity ----@param target CBaseEntity ----@param spell CSpell ----@param power number ----@param duration integer ----@param dotTick integer ----@param resistRate number ----@param spellEffect xi.effect ----@return table +-- Casts an enfeebling song. ----------------------------------- -xi.spells.enfeebling.processSongExceptions = function(caster, target, spell, power, duration, dotTick, resistRate, spellEffect) - local adjustedValues = - { - earlyQuit = false, - power = power, - duration = duration, - dotTick = dotTick, - effect = spellEffect, - } +xi.spells.enfeebling.useEnfeeblingSong = function(caster, target, spell) + local spellId = spell:getID() + local spellElement = spell:getElement() + local spellEffect = pTable[spellId][column.SONG_EFFECT] - -- Requiem is a DoT. - if spellEffect == xi.effect.REQUIEM then - adjustedValues.dotTick = 3 + ------------------------------ + -- STEP 1: Check spell nullification. + ------------------------------ + if xi.combat.statusEffect.isTargetImmune(target, spellEffect, spellElement) then + spell:setMsg(xi.msg.basic.MAGIC_COMPLETE_RESIST) + return spellEffect + end - -- Threnody applies a negative effect. - elseif spellEffect == xi.effect.THRENODY then - adjustedValues.power = power * -1 + -- Check trait nullification trigger. + if xi.combat.statusEffect.isTargetResistant(caster, target, spellEffect) then + spell:setModifier(xi.msg.actionModifier.RESIST) + spell:setMsg(xi.msg.basic.MAGIC_RESIST) + return spellEffect + end + + -- Target already has an status effect that nullifies current. + if xi.combat.statusEffect.isEffectNullified(target, spellEffect) then + spell:setMsg(xi.msg.basic.MAGIC_NO_EFFECT) + return spellEffect + end - -- Lullaby should only have a max of 1 power. - elseif spellEffect == xi.effect.SLEEP_I then - adjustedValues.power = utils.clamp(power, 1, 1) + ------------------------------ + -- STEP 2: Check if spell resists. + ------------------------------ + -- Check the amount of Song+ and All_Song+ gear. + local gearBoost = caster:getMod(pTable[spellId][column.SONG_MODIFIER]) + caster:getMod(xi.mod.ALL_SONGS_EFFECT) - -- Elegy has a hard cap of 50%. - elseif spellEffect == xi.effect.ELEGY then - adjustedValues.power = utils.clamp(power, 0, 5000) + -- Finale has innate +175 to magic accuracy. + local bonusMagicAcc = 0 + if spellEffect == xi.effect.NONE then + bonusMagicAcc = 175 + gearBoost * 5 + end + + local resistRate = xi.combat.magicHitRate.calculateResistRate(caster, target, xi.magic.spellGroup.SONG, xi.skill.SINGING, 0, spellElement, xi.mod.CHR, spellEffect, bonusMagicAcc) + if + resistRate <= 0.25 or + (spellEffect == xi.effect.CHARM_I and + target:isMob() and + target:getMobMod(xi.mobMod.CHARMABLE) <= 0) + then + spell:setMsg(xi.msg.basic.MAGIC_RESIST) + return spellEffect + end + + ------------------------------ + -- STEP 3: Calculate power, tick, duration and subEffect. + ------------------------------ + local power = xi.spells.enfeebling.calculateSongPower(caster, spellEffect, pTable[spellId][column.SONG_POWER_BASE], gearBoost) or 0 + local tick = spellEffect == xi.effect.REQUIEM and 3 or 0 + local duration = xi.spells.enfeebling.calculateSongDuration(caster, spellEffect, pTable[spellId][column.SONG_DURATION], gearBoost) or 0 + local subEffect = spellEffect == xi.effect.THRENODY and xi.combat.element.getElementalMEVAModifier(spellElement) or 0 + + -- FClamp and floor. + power = math.floor(utils.clamp(power, 0, pTable[spellId][column.SONG_POWER_CAP])) + duration = math.floor(duration * resistRate) + ------------------------------ + -- STEP 4: Special cases. + ------------------------------ -- Finale doesn't apply a debuff. Quit early. - elseif spellEffect == xi.effect.NONE then + if spellEffect == xi.effect.NONE then -- TODO: This is actually message 342 which doesn't exist currently. The wording is identical. spell:setMsg(xi.msg.basic.MAGIC_ERASE) - -- If nothing was dispelled then the spell had no effect. local dispelledEffect = target:dispelStatusEffect() if dispelledEffect == xi.effect.NONE then spell:setMsg(xi.msg.basic.MAGIC_NO_EFFECT) end - adjustedValues.effect = dispelledEffect - adjustedValues.earlyQuit = true - return adjustedValues + return dispelledEffect -- Virelai applies a charm. Quit early. - -- Shadow removal is taken care of by Maiden Virelai's onMagicCastingCheck function. - -- https://www.bg-wiki.com/ffxi/Category:Virelai elseif spellEffect == xi.effect.CHARM_I then - if caster:isMob() then - -- Mobs charm players by first adding the charm status effect to the player. - -- Note the duration of the status effect determines the charm length. - target:addStatusEffect(xi.effect.CHARM_I, 0, 0, duration * resistRate) - -- The charm function below simply changes the player AI - caster:charm(target) - spell:setMsg(xi.msg.basic.MAGIC_ENFEEB_IS) - elseif - caster:isPC() and - target:isMob() and - target:getMobMod(xi.mobMod.CHARMABLE) > 0 - then - -- Players typically charm mobs by using job_utils.beastmaster however that uses BST assumptions - -- Therefore instead we call charm directly with a duration based on resist of the spell - caster:charm(target, math.floor(duration * resistRate)) + target:addStatusEffect(xi.effect.CHARM_I, 0, 0, duration) + caster:charm(target) + if caster:isPC() then spell:setMsg(xi.msg.basic.MAGIC_ENFEEB) else - spell:setMsg(xi.msg.basic.MAGIC_RESIST) + spell:setMsg(xi.msg.basic.MAGIC_ENFEEB_IS) end - adjustedValues.earlyQuit = true - return adjustedValues + return spellEffect + end + + ------------------------------ + -- STEP 5: Attempt to apply the status effect. Check for magic burst. + ------------------------------ + if target:addStatusEffect(spellEffect, power, tick, duration, 0, subEffect) then + local _, skillchainCount = xi.magicburst.formMagicBurst(spellElement, target) + if skillchainCount > 0 then + spell:setMsg(xi.msg.basic.MAGIC_BURST_ENFEEB) + caster:triggerRoeEvent(xi.roeTrigger.MAGIC_BURST) + else + -- Lullaby has a different application message than the rest of the song debuffs. + if spellEffect == xi.effect.SLEEP_I then + spell:setMsg(xi.msg.basic.MAGIC_ENFEEB_IS) + else + spell:setMsg(xi.msg.basic.MAGIC_ENFEEB) + end + end + else + spell:setMsg(xi.msg.basic.MAGIC_NO_EFFECT) end - return adjustedValues + return spellEffect end diff --git a/scripts/globals/spells/enfeebling_spell.lua b/scripts/globals/spells/enfeebling_spell.lua index e4dc6db9590..1a294ca487e 100644 --- a/scripts/globals/spells/enfeebling_spell.lua +++ b/scripts/globals/spells/enfeebling_spell.lua @@ -330,41 +330,30 @@ xi.spells.enfeebling.calculateDuration = function(caster, target, spellId, spell return math.floor(duration) end -xi.spells.enfeebling.handleEffectNullification = function(caster, target, spell, spellEffect) - -- Determine if target mob is completely immune to a status effect. - if xi.combat.statusEffect.isTargetImmune(target, spellEffect, spell:getElement()) then - spell:setMsg(xi.msg.basic.MAGIC_COMPLETE_RESIST) +-- Main function, called by spell scripts +xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) + local spellId = spell:getID() + local spellElement = spell:getElement() + local spellEffect = pTable[spellId][column.EFFECT_ID] - return true + ------------------------------ + -- STEP 1: Check spell nullification. + ------------------------------ + if xi.combat.statusEffect.isTargetImmune(target, spellEffect, spellElement) then + spell:setMsg(xi.msg.basic.MAGIC_COMPLETE_RESIST) + return spellEffect end -- Check trait nullification trigger. if xi.combat.statusEffect.isTargetResistant(caster, target, spellEffect) then spell:setModifier(xi.msg.actionModifier.RESIST) spell:setMsg(xi.msg.basic.MAGIC_RESIST) - - return true + return spellEffect end -- Target already has an status effect that nullifies current. if xi.combat.statusEffect.isEffectNullified(target, spellEffect) then spell:setMsg(xi.msg.basic.MAGIC_NO_EFFECT) - - return true - end - - return false -end - --- Main function, called by spell scripts -xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) - local spellId = spell:getID() - local spellEffect = pTable[spellId][column.EFFECT_ID] - - ------------------------------ - -- STEP 1: Check spell nullification. - ------------------------------ - if xi.spells.enfeebling.handleEffectNullification(caster, target, spell, spellEffect) then return spellEffect end @@ -372,7 +361,6 @@ xi.spells.enfeebling.useEnfeeblingSpell = function(caster, target, spell) -- STEP 2: Calculate resist tiers. ------------------------------ local skillType = spell:getSkillType() - local spellElement = spell:getElement() local spellGroup = spell:getSpellGroup() local statUsed = pTable[spellId][column.STAT_USED] local resistStages = pTable[spellId][column.RESIST_STAGES]