From d4f73598a5241fe193f7ecf1d16a871da76dd171 Mon Sep 17 00:00:00 2001 From: Critical <48370698+CriticalXI@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:31:45 -0700 Subject: [PATCH] [lua, sql, cpp][module] Samurai Era Audit --- modules/abyssea/lua/job_adjustments.lua | 111 ++++++++++++++++++++++++ modules/abyssea/sql/job_adjustments.sql | 28 ++++++ scripts/actions/abilities/shikikoyo.lua | 4 +- scripts/effects/hasso.lua | 5 ++ scripts/effects/seigan.lua | 5 ++ scripts/enum/mod.lua | 2 + scripts/globals/job_utils/samurai.lua | 13 +-- src/map/attack.cpp | 2 +- src/map/entities/battleentity.cpp | 2 +- src/map/modifier.h | 4 +- src/map/utils/battleutils.cpp | 4 +- 11 files changed, 163 insertions(+), 17 deletions(-) diff --git a/modules/abyssea/lua/job_adjustments.lua b/modules/abyssea/lua/job_adjustments.lua index dd4d143ac11..bebcd69c3e9 100644 --- a/modules/abyssea/lua/job_adjustments.lua +++ b/modules/abyssea/lua/job_adjustments.lua @@ -205,4 +205,115 @@ m:addOverride('xi.job_utils.beastmaster.useKillerInstinct', function(player, tar return xi.effect.KILLER_INSTINCT end) +----------------------------------- +-- Samurai +----------------------------------- + +-- Warding Circle: Revert duration from 3 minutes to 1 minute +-- Source: https://www.bg-wiki.com/ffxi/Version_Update_(02/13/2012) +m:addOverride('xi.job_utils.samurai.useWardingCircle', function(player, target, ability) + local duration = 60 + player:getMod(xi.mod.WARDING_CIRCLE_DURATION) + local power = 15 + + if player:getMainJob() ~= xi.job.SAM then + power = 5 + end + + power = power + player:getMod(xi.mod.WARDING_CIRCLE_POTENCY) + + -- Handle simplified message for other party members. + if player:getID() ~= target:getID() then + ability:setMsg(xi.msg.basic.FORTIFIED_DEMONS) + end + + target:addStatusEffect(xi.effect.WARDING_CIRCLE, power, 0, duration) + + return xi.effect.WARDING_CIRCLE +end) + +-- Blade Bash: Apply merit recast reduction, remove extra plague duration from merits +m:addOverride('xi.job_utils.samurai.useBladeBash', function(player, target, ability, action) + local recastReduction = player:getMerit(xi.merit.BLADE_BASH) - 150 + action:setRecast(action:getRecast() - recastReduction) + + -- Damage + -- TODO: Verify damage formula and DRK interaction + local jobLevel = utils.getActiveJobLevel(player, xi.job.DRK) + local damage = math.floor((jobLevel + 11) / 4 + player:getMod(xi.mod.WEAPON_BASH)) + damage = utils.handleStoneskin(target, damage) + target:takeDamage(damage, player, xi.attackType.PHYSICAL, xi.damageType.BLUNT) + target:updateEnmityFromDamage(player, damage) + + -- Stun + if + not xi.data.statusEffect.isTargetImmune(target, xi.effect.STUN, xi.element.THUNDER) and + not xi.data.statusEffect.isTargetResistant(player, target, xi.effect.STUN) and + not xi.data.statusEffect.isEffectNullified(target, xi.effect.STUN, 0) + then + local resistanceRate = xi.combat.magicHitRate.calculateResistRate(player, target, 0, 0, xi.skillRank.A_PLUS, xi.element.THUNDER, xi.mod.INT, xi.effect.STUN, 0) + if xi.data.statusEffect.isResistRateSuccessfull(xi.effect.STUN, resistanceRate, 0) then + target:addStatusEffect(xi.effect.STUN, 1, 0, 6 * resistanceRate) + end + end + + -- Plague + if + not xi.data.statusEffect.isTargetImmune(target, xi.effect.PLAGUE, xi.element.FIRE) and + not xi.data.statusEffect.isTargetResistant(player, target, xi.effect.PLAGUE) and + not xi.data.statusEffect.isEffectNullified(target, xi.effect.PLAGUE, 0) + then + local resistanceRate = xi.combat.magicHitRate.calculateResistRate(player, target, 0, 0, xi.skillRank.A_PLUS, xi.element.FIRE, xi.mod.INT, xi.effect.PLAGUE, 0) + if xi.data.statusEffect.isResistRateSuccessfull(xi.effect.PLAGUE, resistanceRate, 0) then + target:addStatusEffect(xi.effect.PLAGUE, 5, 0, 15 * resistanceRate) + end + end + + -- Animation + local animationTable = + { + -- [weapon type] = animation ID + [xi.skill.GREAT_SWORD ] = 201, + [xi.skill.GREAT_KATANA] = 201, + [xi.skill.GREAT_AXE ] = 202, + [xi.skill.SCYTHE ] = 202, + [xi.skill.STAFF ] = 202, + [xi.skill.POLEARM ] = 203, + } + + local animation = animationTable[player:getWeaponSkillType(xi.slot.MAIN)] or 0 + action:setAnimation(target:getID(), animation) + + ability:setMsg(xi.msg.basic.JA_DAMAGE) + + return damage +end) + +-- Shikikoyo: Apply merit recast reduction, remove extra TP sharing from merits +m:addOverride('xi.job_utils.samurai.useShikikoyo', function(player, target, ability, action) + local recastReduction = player:getMerit(xi.merit.SHIKIKOYO) - 150 + action:setRecast(action:getRecast() - recastReduction) + + local pTP = player:getTP() - 1000 + pTP = utils.clamp(pTP, 0, 3000 - target:getTP()) + + player:setTP(1000) + target:setTP(target:getTP() + pTP) + + return pTP +end) + +-- Hasso: Remove Zanshin bonus +m:addOverride('xi.effects.hasso.onEffectGain', function(target, effect) + effect:addMod(xi.mod.TWOHAND_STR, effect:getPower()) + effect:addMod(xi.mod.TWOHAND_HASTE_ABILITY, 1000) + effect:addMod(xi.mod.TWOHAND_ACC, 10) +end) + +-- Seigan: Remove Zanshin-based counter bonus +m:addOverride('xi.effects.seigan.onEffectGain', function(target, effect) + local jpValue = target:getJobPointLevel(xi.jp.SEIGAN_EFFECT) + + effect:addMod(xi.mod.DEF, jpValue * 3) +end) + return m diff --git a/modules/abyssea/sql/job_adjustments.sql b/modules/abyssea/sql/job_adjustments.sql index 13070550224..cc6bb302b0f 100644 --- a/modules/abyssea/sql/job_adjustments.sql +++ b/modules/abyssea/sql/job_adjustments.sql @@ -124,3 +124,31 @@ UPDATE abilities SET recastTime = 900 WHERE name = 'killer_instinct'; -- Killer Instinct merit: Revert value to 150 seconds per level UPDATE merits SET value = 150 WHERE name = 'killer_instinct'; + +------------------------------------ +-- Samurai +-- Source: https://www.bg-wiki.com/ffxi/Version_Update_(02/13/2012) +------------------------------------ + +-- Warding Circle: Revert recast from 5 to 10 minutes +UPDATE abilities SET recastTime = 600 WHERE name = 'warding_circle'; + +-- Warding Circle merit: Revert value to 20 seconds per level +UPDATE merits SET value = 20 WHERE name = 'warding_circle_recast'; + +-- Sekkanoki: Adjust level requirement from 40 to 60 +-- Source: https://www.bg-wiki.com/ffxi/Version_Update_(06/21/2010) +UPDATE abilities SET level = 60 WHERE name = 'sekkanoki'; + +-- Blade Bash: Revert recast from 5 to 15 minutes +-- Source: https://www.bg-wiki.com/ffxi/Version_Update_(05/15/2012) +UPDATE abilities SET recastTime = 900 WHERE name = 'blade_bash'; + +-- Blade Bash merit: Revert value to 150 seconds per level +UPDATE merits SET value = 150 WHERE name = 'blade_bash'; + +-- Shikikoyo: Revert recast from 5 to 15 minutes +UPDATE abilities SET recastTime = 900 WHERE name = 'shikikoyo'; + +-- Shikikoyo merit: Revert value to 150 seconds per level +UPDATE merits SET value = 150 WHERE name = 'shikikoyo'; diff --git a/scripts/actions/abilities/shikikoyo.lua b/scripts/actions/abilities/shikikoyo.lua index 81ceaf033b8..978021e3895 100644 --- a/scripts/actions/abilities/shikikoyo.lua +++ b/scripts/actions/abilities/shikikoyo.lua @@ -13,8 +13,8 @@ abilityObject.onAbilityCheck = function(player, target, ability) return xi.job_utils.samurai.checkShikikoyo(player, target, ability) end -abilityObject.onUseAbility = function(player, target, ability) - return xi.job_utils.samurai.useShikikoyo(player, target, ability) +abilityObject.onUseAbility = function(player, target, ability, action) + return xi.job_utils.samurai.useShikikoyo(player, target, ability, action) end return abilityObject diff --git a/scripts/effects/hasso.lua b/scripts/effects/hasso.lua index eed20edae18..3e2ea7bff9d 100644 --- a/scripts/effects/hasso.lua +++ b/scripts/effects/hasso.lua @@ -10,6 +10,11 @@ effectObject.onEffectGain = function(target, effect) effect:addMod(xi.mod.TWOHAND_STR, effect:getPower()) effect:addMod(xi.mod.TWOHAND_HASTE_ABILITY, 1000) effect:addMod(xi.mod.TWOHAND_ACC, 10) + + -- SAM main job bonus: Hasso occasionally triggers Zanshin after landing normal attacks + if target:getMainJob() == xi.job.SAM then + effect:addMod(xi.mod.HASSO_ZANSHIN_BONUS, 1) + end end effectObject.onEffectTick = function(target, effect) diff --git a/scripts/effects/seigan.lua b/scripts/effects/seigan.lua index d0f385a5580..b67bb12918c 100644 --- a/scripts/effects/seigan.lua +++ b/scripts/effects/seigan.lua @@ -9,6 +9,11 @@ effectObject.onEffectGain = function(target, effect) local jpValue = target:getJobPointLevel(xi.jp.SEIGAN_EFFECT) effect:addMod(xi.mod.DEF, jpValue * 3) + + -- SAM main job bonus: Seigan gives counter chance at 25% of Zanshin rate + if target:getMainJob() == xi.job.SAM then + effect:addMod(xi.mod.SEIGAN_COUNTER_BONUS, 1) + end end effectObject.onEffectTick = function(target, effect) diff --git a/scripts/enum/mod.lua b/scripts/enum/mod.lua index b3a4431b1ff..c0c1d4403a3 100644 --- a/scripts/enum/mod.lua +++ b/scripts/enum/mod.lua @@ -555,6 +555,8 @@ xi.mod = SENGIKORI_SC_DMG_DEBUFF = 1088, -- % Increase to closing skillchain damage. Applied to defender. SENGIKORI_MB_DMG_DEBUFF = 1089, -- % Increase to magic burst damage. Applied to defender. SENGIKORI_BONUS = 1090, -- additive % increase to Sengikori + HASSO_ZANSHIN_BONUS = 1187, -- Enables Hasso to occasionally trigger Zanshin after landing normal attacks + SEIGAN_COUNTER_BONUS = 1188, -- Enables Seigan counter bonus based on Zanshin rate -- Ninja ENHANCES_SANGE = 1091, -- 1 = +1 attack for Daken during Sange per Sange merit (i.e. 20 with 5 merits = +100 attack during Sange) diff --git a/scripts/globals/job_utils/samurai.lua b/scripts/globals/job_utils/samurai.lua index dc830ef0b24..4e71961ef4f 100644 --- a/scripts/globals/job_utils/samurai.lua +++ b/scripts/globals/job_utils/samurai.lua @@ -219,14 +219,6 @@ xi.job_utils.samurai.useKonzenIttai = function(player, target, ability, action) end xi.job_utils.samurai.useBladeBash = function(player, target, ability, action) - -- Damage - -- TODO: Verify damage formula and DRK interaction - local jobLevel = utils.getActiveJobLevel(player, xi.job.DRK) - local damage = math.floor((jobLevel + 11) / 4 + player:getMod(xi.mod.WEAPON_BASH)) - damage = utils.handleStoneskin(target, damage) - target:takeDamage(damage, player, xi.attackType.PHYSICAL, xi.damageType.BLUNT) - target:updateEnmityFromDamage(player, damage) - -- Stun if not xi.data.statusEffect.isTargetImmune(target, xi.effect.STUN, xi.element.THUNDER) and @@ -269,10 +261,11 @@ xi.job_utils.samurai.useBladeBash = function(player, target, ability, action) ability:setMsg(xi.msg.basic.JA_DAMAGE) - return damage + -- Blade Bash does not deal damage + return 0 end -xi.job_utils.samurai.useShikikoyo = function(player, target, ability) +xi.job_utils.samurai.useShikikoyo = function(player, target, ability, action) local pTP = (player:getTP() - 1000) * (1 + (player:getMerit(xi.merit.SHIKIKOYO) - 12) / 100) pTP = utils.clamp(pTP, 0, 3000 - target:getTP()) diff --git a/src/map/attack.cpp b/src/map/attack.cpp index 8149e21e612..809ff408913 100644 --- a/src/map/attack.cpp +++ b/src/map/attack.cpp @@ -460,7 +460,7 @@ bool CAttack::CheckCounter() uint16 seiganChance = 0; - if (m_victim->objtype == TYPE_PC && m_victim->GetMJob() == JOB_SAM) + if (m_victim->objtype == TYPE_PC && m_victim->getMod(Mod::SEIGAN_COUNTER_BONUS) > 0) { // counter check (rate AND your hit rate makes it land, else its just a regular hit) // having seigan active gives chance to counter at 25% of the zanshin proc rate diff --git a/src/map/entities/battleentity.cpp b/src/map/entities/battleentity.cpp index 0cc0c0d986e..d262d5a0a03 100644 --- a/src/map/entities/battleentity.cpp +++ b/src/map/entities/battleentity.cpp @@ -3121,7 +3121,7 @@ bool CBattleEntity::OnAttack(CAttackState& state, action_t& action) const bool missedOrCountered = actionResult.resolution != ActionResolution::Hit || actionResult.spikesEffect == ActionReactKind::Counter; const bool normalZanshinProc = missedOrCountered && xirand::GetRandomNumber(100) < zanshinChance; - const bool isSamWithHasso = GetMJob() == JOB_SAM && this->StatusEffectContainer->HasStatusEffect(EFFECT_HASSO); + const bool isSamWithHasso = this->getMod(Mod::HASSO_ZANSHIN_BONUS) > 0 && this->StatusEffectContainer->HasStatusEffect(EFFECT_HASSO); const bool hassoZanshinProc = isSamWithHasso && xirand::GetRandomNumber(100) < zanshinChance / 4; if (normalZanshinProc || hassoZanshinProc) diff --git a/src/map/modifier.h b/src/map/modifier.h index 4f4cb18da9b..f4075ad4200 100644 --- a/src/map/modifier.h +++ b/src/map/modifier.h @@ -611,6 +611,8 @@ enum class Mod SENGIKORI_SC_DMG_DEBUFF = 1088, // % Increase to closing skillchain damage. Applied to defender. SENGIKORI_MB_DMG_DEBUFF = 1089, // % Increase to magic burst damage. Applied to defender. SENGIKORI_BONUS = 1090, // additive % increase to Sengikori + HASSO_ZANSHIN_BONUS = 1187, // Enables Hasso to occasionally trigger Zanshin after landing normal attacks + SEIGAN_COUNTER_BONUS = 1188, // Enables Seigan counter bonus based on Zanshin rate // Ninja UTSUSEMI = 307, // Everyone's favorite --tracks shadows. @@ -1141,7 +1143,7 @@ enum class Mod // The spares take care of finding the next ID to use so long as we don't forget to list IDs that have been freed up by refactoring. // 570 through 825 used by WS DMG mods these are not spares. // - // SPARE IDs: 1188 and onward + // SPARE IDs: 1189 and onward }; // temporary workaround for using enum class as unordered_map key until compilers support it diff --git a/src/map/utils/battleutils.cpp b/src/map/utils/battleutils.cpp index 5a1f06af803..e5e94f57e1a 100644 --- a/src/map/utils/battleutils.cpp +++ b/src/map/utils/battleutils.cpp @@ -3073,8 +3073,8 @@ uint8 CheckMultiHits(CBattleEntity* PEntity, CItemWeapon* PWeapon) num += 1; } - // hasso occasionally triggers Zanshin after landing a normal attack, only active while Samurai is set as Main - if (PEntity->GetMJob() == JOB_SAM) + // Hasso Zanshin bonus: requires HASSO_ZANSHIN_BONUS mod (applied by Hasso effect when SAM is main job) + if (PEntity->getMod(Mod::HASSO_ZANSHIN_BONUS) > 0) { if (PEntity->StatusEffectContainer->HasStatusEffect(EFFECT_HASSO)) {