From 716edd3d20d201ef9e003fddde84542e682358a9 Mon Sep 17 00:00:00 2001 From: Critical Date: Sun, 5 Jul 2026 17:03:07 -0600 Subject: [PATCH] [lua,sql,cpp][module] Summoner era modules and adjustments --- modules/abyssea/sql/job_adjustments.sql | 8 + .../pets/abyssea_avatar_bloodpacts.lua | 297 ++++++++++++++++++ .../lua/actions/abilities/pets/hastega.lua | 37 +++ modules/era/lua/effects/level_restriction.lua | 26 ++ .../era/lua/globals/job_utils/summoner.lua | 55 ++++ modules/era/lua/globals/pets.lua | 22 ++ modules/rov/sql/job_adjustments.sql | 33 ++ settings/default/main.lua | 1 + src/map/utils/battleutils.cpp | 2 +- 9 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 modules/era/lua/actions/abilities/pets/abyssea_avatar_bloodpacts.lua create mode 100644 modules/era/lua/actions/abilities/pets/hastega.lua create mode 100644 modules/era/lua/effects/level_restriction.lua create mode 100644 modules/era/lua/globals/job_utils/summoner.lua create mode 100644 modules/era/lua/globals/pets.lua diff --git a/modules/abyssea/sql/job_adjustments.sql b/modules/abyssea/sql/job_adjustments.sql index 6e3a314111d..314af3faa40 100644 --- a/modules/abyssea/sql/job_adjustments.sql +++ b/modules/abyssea/sql/job_adjustments.sql @@ -251,6 +251,14 @@ UPDATE abilities SET recastTime = 900 WHERE name = 'deep_breathing'; -- Deep Breathing merit: Revert value to 150 seconds per level UPDATE merits SET value = 150 WHERE name = 'deep_breathing'; +----------------------------------- +-- Summoner +----------------------------------- + +-- Summoning Magic Casting Time Merit: Repurposed to Spirit MP cost merit. Revert merit value from 5 to 1 per level. +-- Source: https://forum.square-enix.com/ffxi/threads/22099-March-27-2012-%28JST%29-Version-Update +UPDATE merits SET value = 1 WHERE name = 'summoning_magic_cast_time'; + ----------------------------------- -- Corsair ----------------------------------- diff --git a/modules/era/lua/actions/abilities/pets/abyssea_avatar_bloodpacts.lua b/modules/era/lua/actions/abilities/pets/abyssea_avatar_bloodpacts.lua new file mode 100644 index 00000000000..9d4de6f0664 --- /dev/null +++ b/modules/era/lua/actions/abilities/pets/abyssea_avatar_bloodpacts.lua @@ -0,0 +1,297 @@ +----------------------------------- +-- Reverts blood pacts to no longer have a duration based on summoning magic. +-- Source: https://www.bg-wiki.com/ffxi/Version_Update_(09/08/2010) +----------------------------------- +require('modules/module_utils') +----------------------------------- + +local moduleName = 'abyssea_avatar_bloodpacts' + +if xi.module.isContentEnabled('ABYSSEA') then + return { name = moduleName } +end + +local m = Module:new(moduleName) + +----------------------------------- +-- Shining Ruby +----------------------------------- +m:addOverride('xi.actions.abilities.pets.shining_ruby.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 180 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + target:delStatusEffect(xi.effect.SHINING_RUBY) + target:addStatusEffect(xi.effect.SHINING_RUBY, { power = 1, duration = duration, origin = pet }) + + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.SKILL_GAIN_EFFECT_2) + else + petskill:setMsg(xi.msg.basic.JA_GAIN_EFFECT) + end + + return xi.effect.SHINING_RUBY +end) + +----------------------------------- +-- Hastega +----------------------------------- +m:addOverride('xi.actions.abilities.pets.hastega.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 180 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + -- Reverts Garuda's Hastega to use 102/1024 or 9.96% + local typeEffect = xi.effect.HASTE + if target:addStatusEffect(typeEffect, { power = 996, duration = duration, origin = pet }) then + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.SKILL_GAIN_EFFECT_2) + else + petskill:setMsg(xi.msg.basic.JA_GAIN_EFFECT) + end + else + petskill:setMsg(xi.msg.basic.JA_NO_EFFECT_2) + return + end + + return typeEffect +end) + +----------------------------------- +-- Crimson Howl +----------------------------------- +m:addOverride('xi.actions.abilities.pets.crimson_howl.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 60 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + local typeEffect = xi.effect.WARCRY + if target:addStatusEffect(typeEffect, { power = 9, duration = duration, origin = pet }) then + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.SKILL_GAIN_EFFECT_2) + else + petskill:setMsg(xi.msg.basic.JA_GAIN_EFFECT) + end + else + petskill:setMsg(xi.msg.basic.JA_NO_EFFECT_2) + return + end + + return typeEffect +end) + +----------------------------------- +-- Frost Armor +----------------------------------- +m:addOverride('xi.actions.abilities.pets.frost_armor.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 180 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + local typeEffect = xi.effect.ICE_SPIKES + target:delStatusEffect(typeEffect) + + if target:addStatusEffect(typeEffect, { power = 15, duration = duration, origin = pet }) then + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.SKILL_GAIN_EFFECT_2) + else + petskill:setMsg(xi.msg.basic.JA_GAIN_EFFECT) + end + else + petskill:setMsg(xi.msg.basic.JA_NO_EFFECT_2) + return + end + + return typeEffect +end) + +----------------------------------- +-- Rolling Thunder +----------------------------------- +m:addOverride('xi.actions.abilities.pets.rolling_thunder.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 120 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + local magicskill = xi.data.skillLevel.getSkillCap(target:getMainLvl(), xi.skillRank.A_PLUS) + local potency = 3 + 6 * magicskill / 100 + + if magicskill > 200 then + potency = 5 + 5 * magicskill / 100 + end + + xi.mobskills.mobBuffMove(target, xi.effect.ENTHUNDER, potency, 0, duration) + + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.JA_RECEIVES_EFFECT_2) + else + petskill:setMsg(xi.msg.basic.JA_RECEIVES_EFFECT) + end + + return xi.effect.ENTHUNDER +end) + +----------------------------------- +-- Lightning Armor +----------------------------------- +m:addOverride('xi.actions.abilities.pets.lightning_armor.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 180 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + local typeEffect = xi.effect.SHOCK_SPIKES + target:delStatusEffect(typeEffect) + + if target:addStatusEffect(typeEffect, { power = 15, duration = duration, origin = pet }) then + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.SKILL_GAIN_EFFECT_2) + else + petskill:setMsg(xi.msg.basic.JA_GAIN_EFFECT) + end + else + petskill:setMsg(xi.msg.basic.JA_NO_EFFECT_2) + return + end + + return typeEffect +end) + +----------------------------------- +-- Ecliptic Growl +----------------------------------- +m:addOverride('xi.actions.abilities.pets.ecliptic_growl.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 180 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + local moonCycle = getVanadielMoonCycle() + + local cycleBuffs = + { + [xi.moonCycle.NEW_MOON] = 1, + [xi.moonCycle.LESSER_WAXING_CRESCENT] = 2, + [xi.moonCycle.GREATER_WAXING_CRESCENT] = 3, + [xi.moonCycle.FIRST_QUARTER] = 4, + [xi.moonCycle.LESSER_WAXING_GIBBOUS] = 5, + [xi.moonCycle.GREATER_WAXING_GIBBOUS] = 6, + [xi.moonCycle.FULL_MOON] = 7, + [xi.moonCycle.GREATER_WANING_GIBBOUS] = 6, + [xi.moonCycle.LESSER_WANING_GIBBOUS] = 5, + [xi.moonCycle.THIRD_QUARTER] = 4, + [xi.moonCycle.GREATER_WANING_CRESCENT] = 3, + [xi.moonCycle.LESSER_WANING_CRESCENT] = 2, + } + + local buffValue = cycleBuffs[moonCycle] + + target:delStatusEffect(xi.effect.STR_BOOST) + target:delStatusEffect(xi.effect.DEX_BOOST) + target:delStatusEffect(xi.effect.VIT_BOOST) + target:delStatusEffect(xi.effect.AGI_BOOST) + target:delStatusEffect(xi.effect.MND_BOOST) + target:delStatusEffect(xi.effect.CHR_BOOST) + + target:addStatusEffect(xi.effect.STR_BOOST, { power = buffValue, duration = duration, origin = pet }) + target:addStatusEffect(xi.effect.DEX_BOOST, { power = buffValue, duration = duration, origin = pet }) + target:addStatusEffect(xi.effect.VIT_BOOST, { power = buffValue, duration = duration, origin = pet }) + target:addStatusEffect(xi.effect.AGI_BOOST, { power = 8 - buffValue, duration = duration, origin = pet }) + target:addStatusEffect(xi.effect.INT_BOOST, { power = 8 - buffValue, duration = duration, origin = pet }) + target:addStatusEffect(xi.effect.MND_BOOST, { power = 8 - buffValue, duration = duration, origin = pet }) + target:addStatusEffect(xi.effect.CHR_BOOST, { power = 8 - buffValue, duration = duration, origin = pet }) + + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.STATUS_BOOST) + else + petskill:setMsg(xi.msg.basic.STATUS_BOOST_2) + end + + return 0 +end) + +----------------------------------- +-- Ecliptic Howl +----------------------------------- +m:addOverride('xi.actions.abilities.pets.ecliptic_howl.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 180 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + local moonCycle = getVanadielMoonCycle() + + local cycleBuffs = + { + [xi.moonCycle.NEW_MOON] = 1, + [xi.moonCycle.LESSER_WAXING_CRESCENT] = 5, + [xi.moonCycle.GREATER_WAXING_CRESCENT] = 9, + [xi.moonCycle.FIRST_QUARTER] = 13, + [xi.moonCycle.LESSER_WAXING_GIBBOUS] = 17, + [xi.moonCycle.GREATER_WAXING_GIBBOUS] = 21, + [xi.moonCycle.FULL_MOON] = 25, + [xi.moonCycle.GREATER_WANING_GIBBOUS] = 21, + [xi.moonCycle.LESSER_WANING_GIBBOUS] = 17, + [xi.moonCycle.THIRD_QUARTER] = 13, + [xi.moonCycle.GREATER_WANING_CRESCENT] = 9, + [xi.moonCycle.LESSER_WANING_CRESCENT] = 5, + } + + local buffValue = cycleBuffs[moonCycle] + + target:delStatusEffect(xi.effect.ACCURACY_BOOST) + target:delStatusEffect(xi.effect.EVASION_BOOST) + target:addStatusEffect(xi.effect.ACCURACY_BOOST, { power = buffValue, duration = duration, origin = pet }) + target:addStatusEffect(xi.effect.EVASION_BOOST, { power = 25 - buffValue, duration = duration, origin = pet }) + + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.ACC_EVA_BOOST) + else + petskill:setMsg(xi.msg.basic.ACC_EVA_BOOST_2) + end + + return 0 +end) + +----------------------------------- +-- Noctoshield +----------------------------------- +m:addOverride('xi.actions.abilities.pets.noctoshield.onPetAbility', function(target, pet, petskill, summoner, action) + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + local duration = 180 + + if target:addStatusEffect(xi.effect.PHALANX, { power = 13, duration = duration, origin = pet }) then + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.SKILL_GAIN_EFFECT_2) + else + petskill:setMsg(xi.msg.basic.JA_GAIN_EFFECT) + end + else + petskill:setMsg(xi.msg.basic.JA_NO_EFFECT_2) + return + end + + return xi.effect.PHALANX +end) + +----------------------------------- +-- Dream Shroud +----------------------------------- +m:addOverride('xi.actions.abilities.pets.dream_shroud.onPetAbility', function(target, pet, petskill, summoner, action) + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + local duration = 180 + local hour = VanadielHour() + local buffvalue = math.abs(12 - hour) + 1 + + target:delStatusEffect(xi.effect.MAGIC_ATK_BOOST) + target:delStatusEffect(xi.effect.MAGIC_DEF_BOOST) + target:addStatusEffect(xi.effect.MAGIC_ATK_BOOST, { power = buffvalue, duration = duration, origin = pet }) + target:addStatusEffect(xi.effect.MAGIC_DEF_BOOST, { power = 14 - buffvalue, duration = duration, origin = pet }) + + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.JA_RECEIVES_MAB_MDB) + else + petskill:setMsg(xi.msg.basic.JA_RECEIVES_MAB_MDB_2) + end + + return 0 +end) + +return m diff --git a/modules/era/lua/actions/abilities/pets/hastega.lua b/modules/era/lua/actions/abilities/pets/hastega.lua new file mode 100644 index 00000000000..4cba3efeb8e --- /dev/null +++ b/modules/era/lua/actions/abilities/pets/hastega.lua @@ -0,0 +1,37 @@ +----------------------------------- +-- Reverts Hastega to no longer have a duration based on summoning magic and reduces haste power +-- Source: https://www.bg-wiki.com/ffxi/Version_Update_(04/08/2009) +----------------------------------- +require('modules/module_utils') +----------------------------------- + +local moduleName = 'hastega' + +if xi.module.isContentEnabled('WOTG') then + return { name = moduleName } +end + +local m = Module:new(moduleName) + +m:addOverride('xi.actions.abilities.pets.hastega.onPetAbility', function(target, pet, petskill, summoner, action) + local duration = 180 + + xi.job_utils.summoner.onUseBloodPact(target, petskill, summoner, action) + + -- Reverts Garuda's Hastega to use 102/1024 or 9.96% + local typeEffect = xi.effect.HASTE + if target:addStatusEffect(typeEffect, { power = 996, duration = duration, origin = pet }) then + if target:getID() == action:getPrimaryTargetID() then + petskill:setMsg(xi.msg.basic.SKILL_GAIN_EFFECT_2) + else + petskill:setMsg(xi.msg.basic.JA_GAIN_EFFECT) + end + else + petskill:setMsg(xi.msg.basic.JA_NO_EFFECT_2) + return + end + + return typeEffect +end) + +return m diff --git a/modules/era/lua/effects/level_restriction.lua b/modules/era/lua/effects/level_restriction.lua new file mode 100644 index 00000000000..e9427216fb4 --- /dev/null +++ b/modules/era/lua/effects/level_restriction.lua @@ -0,0 +1,26 @@ +----------------------------------- +-- Module: Elemental Spirit Perpetuation Cost (level restriction) +-- Reverts perpetuation cost which is recalculated on level restriction if a summoned elemental spirit is present. +-- Source: https://forum.square-enix.com/ffxi/threads/22099-March-27-2012-%28JST%29-Version-Update +----------------------------------- +require('modules/module_utils') +----------------------------------- +local moduleName = 'era_effect_level_restriction' + +if xi.module.isContentEnabled('ABYSSEA') then + return { name = moduleName } +end + +local m = Module:new(moduleName) + +m:addOverride('xi.effects.level_restriction.onEffectGain', function(target, effect) + super(target, effect) + xi.job_utils.summoner.applySpiritPerpetuationCost(target) +end) + +m:addOverride('xi.effects.level_restriction.onEffectLose', function(target, effect) + super(target, effect) + xi.job_utils.summoner.applySpiritPerpetuationCost(target) +end) + +return m diff --git a/modules/era/lua/globals/job_utils/summoner.lua b/modules/era/lua/globals/job_utils/summoner.lua new file mode 100644 index 00000000000..6056e3fa2ae --- /dev/null +++ b/modules/era/lua/globals/job_utils/summoner.lua @@ -0,0 +1,55 @@ +----------------------------------- +-- Module: Summoner spirit perpetuation cost helpers +----------------------------------- +local moduleName = 'era_job_utils_summoner' + +xi.job_utils.summoner = xi.job_utils.summoner or {} + +-- Perpetuation cost breakpoints for elemental spirits, in ascending level order. +-- Source: https://forum.square-enix.com/ffxi/threads/22099-March-27-2012-%28JST%29-Version-Update +local spiritPerpTresholds = +{ + { level = 5, cost = 2 }, + { level = 9, cost = 3 }, + { level = 14, cost = 4 }, + { level = 18, cost = 5 }, + { level = 23, cost = 6 }, + { level = 25, cost = 7 }, + { level = 27, cost = 8 }, + { level = 32, cost = 9 }, + { level = 36, cost = 10 }, + { level = 40, cost = 11 }, + { level = 45, cost = 12 }, + { level = 49, cost = 13 }, + { level = 54, cost = 14 }, + { level = 58, cost = 15 }, + { level = 63, cost = 16 }, + { level = 67, cost = 17 }, + { level = 72, cost = 18 }, +} + +local function getSpiritPerpetuationCost(level) + for _, threshold in ipairs(spiritPerpTresholds) do + if level < threshold.level then + return threshold.cost + end + end + + return spiritPerpTresholds[#spiritPerpTresholds].cost +end + +xi.job_utils.summoner.applySpiritPerpetuationCost = function(caster) + local pet = caster:getPet() + if + pet and + pet:getPetID() >= xi.petId.FIRE_SPIRIT and + pet:getPetID() <= xi.petId.DARK_SPIRIT + then + local baseCost = getSpiritPerpetuationCost(pet:getMainLvl()) + local meritReduction = caster:getMerit(xi.merit.SUMMONING_MAGIC_CAST_TIME) + + caster:setMod(xi.mod.AVATAR_PERPETUATION, math.max(0, baseCost - meritReduction)) + end +end + +return { name = moduleName } diff --git a/modules/era/lua/globals/pets.lua b/modules/era/lua/globals/pets.lua new file mode 100644 index 00000000000..ef3bce669e0 --- /dev/null +++ b/modules/era/lua/globals/pets.lua @@ -0,0 +1,22 @@ +----------------------------------- +-- Module: Elemental Spirit Perpetuation Cost +-- Reverts perpetuation cost which is calculated on pet spawn and set with a AVATAR_PERPETUATION mod on the player. +-- Source: https://forum.square-enix.com/ffxi/threads/22099-March-27-2012-%28JST%29-Version-Update +----------------------------------- +require('modules/module_utils') +----------------------------------- +local moduleName = 'era_globals_pets' + +if xi.module.isContentEnabled('ABYSSEA') then + return { name = moduleName } +end + +local m = Module:new(moduleName) + +m:addOverride('xi.pet.spawnPet', function(caster, petID, state, target) + super(caster, petID, state, target) + + xi.job_utils.summoner.applySpiritPerpetuationCost(caster) +end) + +return m diff --git a/modules/rov/sql/job_adjustments.sql b/modules/rov/sql/job_adjustments.sql index ee8ccd31f40..f857e2241f5 100644 --- a/modules/rov/sql/job_adjustments.sql +++ b/modules/rov/sql/job_adjustments.sql @@ -123,3 +123,36 @@ UPDATE abilities SET recastId = 158 WHERE name = 'spirit_jump'; -- High Jump / Soul Jump: Revert to share a cooldown UPDATE abilities SET recastId = 159 WHERE name = 'high_jump'; UPDATE abilities SET recastId = 159 WHERE name = 'soul_jump'; + +------------------------------------ +-- Summoner +------------------------------------ + +-- Bloodpact: Ward AoE skills: Revert radius from 14 to 10 +-- TODO: Could use more verification on exact range value +-- Source: https://forum.square-enix.com/ffxi/threads/46531-Mar-26-2015-%28JST%29-Version-Update +UPDATE pet_skills SET pet_skill_radius = 10 WHERE pet_skill_name IN ( + 'shining_ruby', + 'glittering_ruby', + 'healing_ruby_ii', + 'soothing_ruby', + 'ecliptic_growl', + 'ecliptic_howl', + 'heavenward_howl', + 'crimson_howl', + 'inferno_howl', + 'earthen_ward', + 'spring_water', + 'soothing_current', + 'whispering_wind', + 'hastega', + 'aerial_armor', + 'fleet_wind', + 'hastega_ii', + 'frost_armor', + 'crystal_blessing', + 'rolling_thunder', + 'lightning_armor', + 'noctoshield', + 'dream_shroud' +); diff --git a/settings/default/main.lua b/settings/default/main.lua index 84f47de500b..b3cad35e22a 100644 --- a/settings/default/main.lua +++ b/settings/default/main.lua @@ -156,6 +156,7 @@ xi.settings.main = USE_ADOULIN_WEAPON_SKILL_CHANGES = true, -- true/false. Change to toggle new Adoulin weapon skill damage calculations DISABLE_PARTY_EXP_PENALTY = false, -- true/false. ENABLE_IMMUNOBREAK = true, -- true/false. Allow/Disallow immunobreaks to happen. + ENABLE_SMN_MAGIC_CAST_TIME_MERIT = true, -- true/false. If false, the Summoning Magic Casting Time merit has no effect on cast time (pre-2012 behavior). -- TRUSTS ENABLE_TRUST_CASTING = 1, diff --git a/src/map/utils/battleutils.cpp b/src/map/utils/battleutils.cpp index e625d711059..8f0b1d9f38f 100644 --- a/src/map/utils/battleutils.cpp +++ b/src/map/utils/battleutils.cpp @@ -5493,7 +5493,7 @@ timer::duration CalculateSpellCastTime(CBattleEntity* PEntity, CMagicState* PMag { auto amount = 1000ms * PEntity->getMod(Mod::SUMMONING_MAGIC_CAST); - if (PEntity->objtype == TYPE_PC) + if (PEntity->objtype == TYPE_PC && settings::get("main.ENABLE_SMN_MAGIC_CAST_TIME_MERIT")) { auto* PChar = static_cast(PEntity); amount += std::chrono::floor(base * 0.01 * PChar->PMeritPoints->GetMeritValue(MERIT_SUMMONING_MAGIC_CAST_TIME, PChar));