From 5c98b052337cc670745a0db28669228bcafb4a72 Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Fri, 9 Jan 2026 19:40:56 +0100 Subject: [PATCH 1/7] Cleanup Meikyo Shisui effect script --- scripts/effects/meikyo_shisui.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/effects/meikyo_shisui.lua b/scripts/effects/meikyo_shisui.lua index 8b83db2286b..8651a2a37b1 100644 --- a/scripts/effects/meikyo_shisui.lua +++ b/scripts/effects/meikyo_shisui.lua @@ -7,16 +7,13 @@ local effectObject = {} effectObject.onEffectGain = function(target, effect) local jpValue = target:getJobPointLevel(xi.jp.MEIKYO_SHISUI_EFFECT) - target:addMod(xi.mod.SKILLCHAINDMG, 200 * jpValue) -- Base 10000 mod + effect:addMod(xi.mod.SKILLCHAINDMG, 200 * jpValue) -- Base 10000 mod end effectObject.onEffectTick = function(target, effect) end effectObject.onEffectLose = function(target, effect) - local jpValue = target:getJobPointLevel(xi.jp.MEIKYO_SHISUI_EFFECT) - - target:delMod(xi.mod.SKILLCHAINDMG, 200 * jpValue) -- Base 10000 mod end return effectObject From c950b8eccf7fe6c540f2f7ebdaef79a061518566 Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Fri, 9 Jan 2026 19:40:18 +0100 Subject: [PATCH 2/7] Add Meikyo check to `getSingleMeleeHitTPReturn()` And remove unneeded parameter --- scripts/globals/combat/tp.lua | 8 ++++++-- scripts/globals/job_utils/summoner.lua | 2 +- scripts/globals/mobskills.lua | 6 +++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/scripts/globals/combat/tp.lua b/scripts/globals/combat/tp.lua index 6811905d5bb..c133f59f10c 100644 --- a/scripts/globals/combat/tp.lua +++ b/scripts/globals/combat/tp.lua @@ -3,8 +3,12 @@ xi.combat = xi.combat or {} xi.combat.tp = xi.combat.tp or {} ----------------------------------- --- returns a single melee hit's TP return -xi.combat.tp.getSingleMeleeHitTPReturn = function(actor, target, isZanshin) +-- Returns attacker TP gain from a single melee hit from itself. +xi.combat.tp.getSingleMeleeHitTPReturn = function(actor, isZanshin) + if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then + return 0 + end + isZanshin = isZanshin or false -- optional input, defaults to false. local delay = actor:getBaseDelay() diff --git a/scripts/globals/job_utils/summoner.lua b/scripts/globals/job_utils/summoner.lua index f6ea09abc84..13c8c586432 100644 --- a/scripts/globals/job_utils/summoner.lua +++ b/scripts/globals/job_utils/summoner.lua @@ -235,7 +235,7 @@ end -- to be removed once damage is overhauled xi.job_utils.summoner.calculateTPReturn = function(avatar, target, damage, numHits) if damage ~= 0 and numHits > 0 then -- absorbed hits still give TP, though we can't know how many hits actually connected in the current avatar damage formulas - local tpReturn = xi.combat.tp.getSingleMeleeHitTPReturn(avatar, target) + local tpReturn = xi.combat.tp.getSingleMeleeHitTPReturn(avatar, false) tpReturn = tpReturn + 10 * (numHits - 1) -- extra hits give 10 TP each avatar:setTP(tpReturn) else diff --git a/scripts/globals/mobskills.lua b/scripts/globals/mobskills.lua index e8113fd9662..122eace67d5 100644 --- a/scripts/globals/mobskills.lua +++ b/scripts/globals/mobskills.lua @@ -298,7 +298,7 @@ xi.mobskills.mobPhysicalMove = function(mob, target, skill, numHits, accMod, ftp skill:setMsg(xi.msg.basic.SKILL_MISS) -- calculate tp return of mob skill and add if hit primary target elseif skill:getPrimaryTargetID() == target:getID() then - local tpReturn = xi.combat.tp.getSingleMeleeHitTPReturn(mob, target) + local tpReturn = xi.combat.tp.getSingleMeleeHitTPReturn(mob, false) tpReturn = tpReturn + 10 * (hitslanded - 1) -- extra hits give 10 TP each mob:addTP(tpReturn) end @@ -385,7 +385,7 @@ xi.mobskills.mobMagicalMove = function(actor, target, action, baseDamage, action -- magical mob skills are single hit so provide single Melee hit TP return if primary target -- TODO: This should probably be moved to AFTER all damage is calculated, since this is not the final step. if finalDamage > 0 and action:getPrimaryTargetID() == target:getID() then - local tpReturn = xi.combat.tp.getSingleMeleeHitTPReturn(actor, target) + local tpReturn = xi.combat.tp.getSingleMeleeHitTPReturn(actor, false) actor:addTP(tpReturn) end @@ -620,7 +620,7 @@ xi.mobskills.calculateSkillTPReturn = function(damage, mob, skill, target, attac local targetTPReturn = 0 if attackType == xi.attackType.BREATH then - mobTPReturn = xi.combat.tp.getSingleMeleeHitTPReturn(mob, target) + mobTPReturn = xi.combat.tp.getSingleMeleeHitTPReturn(mob, false) targetTPReturn = xi.combat.tp.calculateTPGainOnPhysicalDamage(damage, mob:getBaseDelay(), mob, target) -- TODO: Add TP return for MAGICAL, PHYSICAL, RANGED once added in future PRs. end From 0979394321fbd8d8b23770a48129840f2b1555ca Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Fri, 9 Jan 2026 19:44:39 +0100 Subject: [PATCH 3/7] Add Meikyo check to `getSingleWeaponTPReturn()` And minor cleanup --- scripts/globals/combat/tp.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/scripts/globals/combat/tp.lua b/scripts/globals/combat/tp.lua index c133f59f10c..2c758fe5a44 100644 --- a/scripts/globals/combat/tp.lua +++ b/scripts/globals/combat/tp.lua @@ -24,20 +24,23 @@ xi.combat.tp.getSingleMeleeHitTPReturn = function(actor, isZanshin) return math.floor(tpReturn * storeTPModifier) end --- returns a PC weapon slot's TP return for a single hit +-- Returns a PC weapon slot's TP return for a single hit. xi.combat.tp.getSingleWeaponTPReturn = function(actor, slot) - -- TODO: implement Zanshin check optionally? - if actor:isPC() then - local delay = actor:getBaseWeaponDelay(slot) - local attackOutput = xi.combat.tp.getModifiedDelayAndCanZanshin(actor, delay) - local tpReturn = xi.combat.tp.calculateTPReturn(actor, attackOutput.modifiedDelay) - local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 + if not actor:isPC() then + return 0 + end - return math.floor(tpReturn * storeTPModifier) + if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then + return 0 end - -- TODO: print error message for non-PC? - return 0 + -- TODO: implement Zanshin check optionally? + local delay = actor:getBaseWeaponDelay(slot) + local attackOutput = xi.combat.tp.getModifiedDelayAndCanZanshin(actor, delay) + local tpReturn = xi.combat.tp.calculateTPReturn(actor, attackOutput.modifiedDelay) + local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 + + return math.floor(tpReturn * storeTPModifier) end xi.combat.tp.getModifiedDelayAndCanZanshin = function(actor, delay) From 7f58a6af2c544815871df4cbd82cc2c83068c961 Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Fri, 9 Jan 2026 19:56:01 +0100 Subject: [PATCH 4/7] Add Meikyo check to `calculateSpellTP()` --- scripts/globals/combat/tp.lua | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/scripts/globals/combat/tp.lua b/scripts/globals/combat/tp.lua index 2c758fe5a44..6e8a61b7937 100644 --- a/scripts/globals/combat/tp.lua +++ b/scripts/globals/combat/tp.lua @@ -200,16 +200,23 @@ xi.combat.tp.calculateTPGainOnMagicalDamage = function(totalDamage, actor, targe return 0 end +-- USED IN CORE -- Calculate TP generated by spell for Occult Acumen trait xi.combat.tp.calculateSpellTP = function(actor, spell) - if - actor:isPC() and - utils.contains(spell:getSkillType(), { xi.skill.ELEMENTAL_MAGIC, xi.skill.DARK_MAGIC }) - then - local occultAcumenModifier = actor:getMod(xi.mod.OCCULT_ACUMEN) / 100 - local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 - return math.floor(spell:getMPCost() * occultAcumenModifier * storeTPModifier) + if not actor:isPC() then + return 0 end - return 0 + if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then + return 0 + end + + if not utils.contains(spell:getSkillType(), { xi.skill.ELEMENTAL_MAGIC, xi.skill.DARK_MAGIC }) then + return 0 + end + + local occultAcumenModifier = actor:getMod(xi.mod.OCCULT_ACUMEN) / 100 + local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 + + return math.floor(spell:getMPCost() * occultAcumenModifier * storeTPModifier) end From 801ae22bfc62de28b41f51b4e6107ba03601697b Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Fri, 9 Jan 2026 20:02:20 +0100 Subject: [PATCH 5/7] Add Meikyo check to `calculateTPGainOnMagicalDamage()` --- scripts/globals/bluemagic.lua | 4 +-- scripts/globals/combat/tp.lua | 48 +++++++++++++++++++++-------------- src/map/utils/battleutils.cpp | 2 +- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/scripts/globals/bluemagic.lua b/scripts/globals/bluemagic.lua index bdd06aac5c0..8c415d255ad 100644 --- a/scripts/globals/bluemagic.lua +++ b/scripts/globals/bluemagic.lua @@ -615,7 +615,7 @@ xi.spells.blue.useBreathSpell = function(caster, target, spell, params) -- Handle TP local tpHits = params.tphitslanded or 0 - local extraTPGained = xi.combat.tp.calculateTPGainOnMagicalDamage(dmg, caster, target) * math.max(tpHits - 1, 0) -- Calculate extra TP gained from multihits. takeSpellDamage accounts for one already. + local extraTPGained = xi.combat.tp.calculateTPGainOnMagicalDamage(caster, target, dmg) * math.max(tpHits - 1, 0) -- Calculate extra TP gained from multihits. takeSpellDamage accounts for one already. target:addTP(extraTPGained) -- Handle Afflatus Misery. @@ -633,7 +633,7 @@ xi.spells.blue.applySpellDamage = function(caster, target, spell, dmg, params, t local attackType = params.attackType or xi.attackType.NONE local damageType = params.damageType or xi.damageType.NONE local tpHits = params.tphitslanded or 0 - local extraTPGained = xi.combat.tp.calculateTPGainOnMagicalDamage(dmg, caster, target) * math.max(tpHits - 1, 0) -- Calculate extra TP gained from multihits. takeSpellDamage accounts for one already. + local extraTPGained = xi.combat.tp.calculateTPGainOnMagicalDamage(caster, target, dmg) * math.max(tpHits - 1, 0) -- Calculate extra TP gained from multihits. takeSpellDamage accounts for one already. -- handle MDT, One For All, Liement if attackType == xi.attackType.MAGICAL then diff --git a/scripts/globals/combat/tp.lua b/scripts/globals/combat/tp.lua index 6e8a61b7937..5f4e3b351a9 100644 --- a/scripts/globals/combat/tp.lua +++ b/scripts/globals/combat/tp.lua @@ -176,28 +176,38 @@ xi.combat.tp.calculateTPGainOnPhysicalDamage = function(totalDamage, delay, acto return 0 end -xi.combat.tp.calculateTPGainOnMagicalDamage = function(totalDamage, actor, target) - -- TODO: does dAGI penalty work against/for Trusts/Pets? Nothing is documented for this. Currently assuming mob only. - if totalDamage > 0 and target and actor then - local dAGI = actor:getStat(xi.mod.AGI) - target:getStat(xi.mod.AGI) - local inhibitTPModifier = (100 - target:getMod(xi.mod.INHIBIT_TP)) / 100 -- no known cap: https://www.bg-wiki.com/ffxi/Monster_TP_gain#Inhibit_TP - local dAGIModifier = utils.clamp(200 - (dAGI + 30) / 200, 0.5, 1) -- 50% reduction at +70 dAGI: https://www.bg-wiki.com/ffxi/Monster_TP_gain - local subtleBlowMerits = actor:getMerit(xi.merit.SUBTLE_BLOW_EFFECT) - local subtleBlowI = math.min(actor:getMod(xi.mod.SUBTLE_BLOW) + subtleBlowMerits, 50) -- cap of 50% https://www.bg-wiki.com/ffxi/Subtle_Blow - local tandemBlowBonus = xi.combat.tp.getTandemBlowBonus(actor) - local subtleBlowII = actor:getMod(xi.mod.SUBTLE_BLOW_II) + tandemBlowBonus -- no known cap - local subtleBlowModifier = math.max((100 - subtleBlowI + subtleBlowII) / 100, 0.25) -- combined cap of 75% reduction: https://www.bg-wiki.com/ffxi/Subtle_Blow - local storeTPModifier = 1 + target:getMod(xi.mod.STORETP) / 100 +-- USED IN CORE +-- Used exclusively for blue magic. +xi.combat.tp.calculateTPGainOnMagicalDamage = function(actor, target, totalDamage) + if not actor or not target then + return 0 + end - -- Similar caveats to above for physical damage, unknown where/how many floors but seems to be one. - if target:getObjType() == xi.objType.MOB then - return math.floor(100 * inhibitTPModifier * dAGIModifier * subtleBlowModifier * storeTPModifier) -- 100 sourced from testing & https://www.bg-wiki.com/ffxi/Monster_TP_gain#TP_gained_from_Magical_Damage - else - return math.floor(50 * inhibitTPModifier * subtleBlowModifier * storeTPModifier) -- 50 sourced from testing & https://www.bg-wiki.com/ffxi/Tactical_Points#Getting_hit_for_more_than_0_damage - end + if totalDamage <= 0 then + return 0 end - return 0 + if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then + return 0 + end + + -- TODO: does dAGI penalty work against/for Trusts/Pets? Nothing is documented for this. Currently assuming mob only. + local dAGI = actor:getStat(xi.mod.AGI) - target:getStat(xi.mod.AGI) + local inhibitTPModifier = (100 - target:getMod(xi.mod.INHIBIT_TP)) / 100 -- no known cap: https://www.bg-wiki.com/ffxi/Monster_TP_gain#Inhibit_TP + local dAGIModifier = utils.clamp(200 - (dAGI + 30) / 200, 0.5, 1) -- 50% reduction at +70 dAGI: https://www.bg-wiki.com/ffxi/Monster_TP_gain + local subtleBlowMerits = actor:getMerit(xi.merit.SUBTLE_BLOW_EFFECT) + local subtleBlowI = math.min(actor:getMod(xi.mod.SUBTLE_BLOW) + subtleBlowMerits, 50) -- cap of 50% https://www.bg-wiki.com/ffxi/Subtle_Blow + local tandemBlowBonus = xi.combat.tp.getTandemBlowBonus(actor) + local subtleBlowII = actor:getMod(xi.mod.SUBTLE_BLOW_II) + tandemBlowBonus -- no known cap + local subtleBlowModifier = math.max((100 - subtleBlowI + subtleBlowII) / 100, 0.25) -- combined cap of 75% reduction: https://www.bg-wiki.com/ffxi/Subtle_Blow + local storeTPModifier = 1 + target:getMod(xi.mod.STORETP) / 100 + + -- Similar caveats to above for physical damage, unknown where/how many floors but seems to be one. + if target:getObjType() == xi.objType.MOB then + return math.floor(100 * inhibitTPModifier * dAGIModifier * subtleBlowModifier * storeTPModifier) -- 100 sourced from testing & https://www.bg-wiki.com/ffxi/Monster_TP_gain#TP_gained_from_Magical_Damage + else + return math.floor(50 * inhibitTPModifier * subtleBlowModifier * storeTPModifier) -- 50 sourced from testing & https://www.bg-wiki.com/ffxi/Tactical_Points#Getting_hit_for_more_than_0_damage + end end -- USED IN CORE diff --git a/src/map/utils/battleutils.cpp b/src/map/utils/battleutils.cpp index 89e0c64ca05..5b82ee7d68c 100644 --- a/src/map/utils/battleutils.cpp +++ b/src/map/utils/battleutils.cpp @@ -2412,7 +2412,7 @@ void TakeSpellDamage(CBattleEntity* PDefender, CBattleEntity* PAttacker, CSpell* auto tpGainFunc = lua["xi"]["combat"]["tp"]["calculateTPGainOnMagicalDamage"]; if (tpGainFunc.valid()) { - PDefender->addTP(tpGainFunc(damage, PAttacker, PDefender)); + PDefender->addTP(tpGainFunc(PAttacker, PDefender, damage)); } } } From 1781017ac7c85cdd98dbff8b22f85841a63f0a23 Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Fri, 9 Jan 2026 20:07:57 +0100 Subject: [PATCH 6/7] Add Meikyo check to `calculateTPGainOnPhysicalDamage()` --- scripts/globals/combat/tp.lua | 70 +++++++++++++++++++---------------- scripts/globals/mobskills.lua | 2 +- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/scripts/globals/combat/tp.lua b/scripts/globals/combat/tp.lua index 5f4e3b351a9..205c6c9d9cb 100644 --- a/scripts/globals/combat/tp.lua +++ b/scripts/globals/combat/tp.lua @@ -141,39 +141,47 @@ xi.combat.tp.getTandemBlowBonus = function(actor) end -- TODO: does Ikishoten factor into this as a bonus to baseTPGain if it procs on the hit? Needs verification. -xi.combat.tp.calculateTPGainOnPhysicalDamage = function(totalDamage, delay, actor, target) - -- TODO: does dAGI penalty work against/for Trusts/Pets? Nothing is documented for this. Currently assuming mob only. - if totalDamage > 0 and target and actor then - local attackOutput = xi.combat.tp.getModifiedDelayAndCanZanshin(actor, delay) - local baseTPGain = xi.combat.tp.calculateTPReturn(target, attackOutput.modifiedDelay) - local dAGI = actor:getStat(xi.mod.AGI) - target:getStat(xi.mod.AGI) - local inhibitTPModifier = (100 - target:getMod(xi.mod.INHIBIT_TP)) / 100 -- no known cap: https://www.bg-wiki.com/ffxi/Monster_TP_gain#Inhibit_TP - local dAGIModifier = utils.clamp(200 - (dAGI + 30) / 200, 0.5, 1) -- 50% reduction at +70 dAGI: https://www.bg-wiki.com/ffxi/Monster_TP_gain - local subtleBlowMerits = actor:getMerit(xi.merit.SUBTLE_BLOW_EFFECT) - local subtleBlowI = math.min(actor:getMod(xi.mod.SUBTLE_BLOW) + subtleBlowMerits, 50) -- cap of 50% https://www.bg-wiki.com/ffxi/Subtle_Blow - local tandemBlowBonus = xi.combat.tp.getTandemBlowBonus(actor) - local subtleBlowII = actor:getMod(xi.mod.SUBTLE_BLOW_II) + tandemBlowBonus -- no known cap - local subtleBlowModifier = math.max((100 - subtleBlowI + subtleBlowII) / 100, 0.25) -- combined cap of 75% reduction: https://www.bg-wiki.com/ffxi/Subtle_Blow - local storeTPModifier = 1 + target:getMod(xi.mod.STORETP) / 100 - - -- TODO: unknown where/how many floor steps there are. Napkin math seems to be a single floor step, but given x/256 it's hard to tell - -- TODO: unknown if player pets (automaton/wyvern/avatars) are affected by dAGI - - -- mob vs mob (via charm) is observed to use the (base * 1/3) formula instead of (base + 30) - -- (base + 30) formula appears to be intentional by SE to make mobs 'more dangerous' when hit by players/pets - if - target:getObjType() == xi.objType.MOB and - actor:getObjType() ~= xi.objType.MOB - then - -- +30 sourced from http://wiki.ffo.jp/html/2621.html and tested in game - return math.floor((baseTPGain + 30) * inhibitTPModifier * dAGIModifier * subtleBlowModifier * storeTPModifier) - else - -- 1/3rd sourced from https://www.bg-wiki.com/ffxi/Tactical_Points and tested in game - return math.floor(baseTPGain * inhibitTPModifier * subtleBlowModifier * storeTPModifier * (1 / 3)) - end +xi.combat.tp.calculateTPGainOnPhysicalDamage = function(actor, target, totalDamage, delay) + if not actor or not target then + return 0 end - return 0 + if totalDamage <= 0 then + return 0 + end + + if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then + return 0 + end + + -- TODO: does dAGI penalty work against/for Trusts/Pets? Nothing is documented for this. Currently assuming mob only. + local attackOutput = xi.combat.tp.getModifiedDelayAndCanZanshin(actor, delay) + local baseTPGain = xi.combat.tp.calculateTPReturn(target, attackOutput.modifiedDelay) + local dAGI = actor:getStat(xi.mod.AGI) - target:getStat(xi.mod.AGI) + local inhibitTPModifier = (100 - target:getMod(xi.mod.INHIBIT_TP)) / 100 -- no known cap: https://www.bg-wiki.com/ffxi/Monster_TP_gain#Inhibit_TP + local dAGIModifier = utils.clamp(200 - (dAGI + 30) / 200, 0.5, 1) -- 50% reduction at +70 dAGI: https://www.bg-wiki.com/ffxi/Monster_TP_gain + local subtleBlowMerits = actor:getMerit(xi.merit.SUBTLE_BLOW_EFFECT) + local subtleBlowI = math.min(actor:getMod(xi.mod.SUBTLE_BLOW) + subtleBlowMerits, 50) -- cap of 50% https://www.bg-wiki.com/ffxi/Subtle_Blow + local tandemBlowBonus = xi.combat.tp.getTandemBlowBonus(actor) + local subtleBlowII = actor:getMod(xi.mod.SUBTLE_BLOW_II) + tandemBlowBonus -- no known cap + local subtleBlowModifier = math.max((100 - subtleBlowI + subtleBlowII) / 100, 0.25) -- combined cap of 75% reduction: https://www.bg-wiki.com/ffxi/Subtle_Blow + local storeTPModifier = 1 + target:getMod(xi.mod.STORETP) / 100 + + -- TODO: unknown where/how many floor steps there are. Napkin math seems to be a single floor step, but given x/256 it's hard to tell + -- TODO: unknown if player pets (automaton/wyvern/avatars) are affected by dAGI + + -- mob vs mob (via charm) is observed to use the (base * 1/3) formula instead of (base + 30) + -- (base + 30) formula appears to be intentional by SE to make mobs 'more dangerous' when hit by players/pets + if + target:getObjType() == xi.objType.MOB and + actor:getObjType() ~= xi.objType.MOB + then + -- +30 sourced from http://wiki.ffo.jp/html/2621.html and tested in game + return math.floor((baseTPGain + 30) * inhibitTPModifier * dAGIModifier * subtleBlowModifier * storeTPModifier) + else + -- 1/3rd sourced from https://www.bg-wiki.com/ffxi/Tactical_Points and tested in game + return math.floor(baseTPGain * inhibitTPModifier * subtleBlowModifier * storeTPModifier * (1 / 3)) + end end -- USED IN CORE diff --git a/scripts/globals/mobskills.lua b/scripts/globals/mobskills.lua index 122eace67d5..1754f1c2f18 100644 --- a/scripts/globals/mobskills.lua +++ b/scripts/globals/mobskills.lua @@ -621,7 +621,7 @@ xi.mobskills.calculateSkillTPReturn = function(damage, mob, skill, target, attac if attackType == xi.attackType.BREATH then mobTPReturn = xi.combat.tp.getSingleMeleeHitTPReturn(mob, false) - targetTPReturn = xi.combat.tp.calculateTPGainOnPhysicalDamage(damage, mob:getBaseDelay(), mob, target) + targetTPReturn = xi.combat.tp.calculateTPGainOnPhysicalDamage(mob, target, damage, mob:getBaseDelay()) -- TODO: Add TP return for MAGICAL, PHYSICAL, RANGED once added in future PRs. end From 5df5345fb1cf541089718067c99a89efeedc09f8 Mon Sep 17 00:00:00 2001 From: Xaver-DaRed Date: Fri, 9 Jan 2026 20:08:50 +0100 Subject: [PATCH 7/7] Reorder TP functions by "local/global" usage --- scripts/globals/combat/tp.lua | 180 ++++++++++++++++++---------------- 1 file changed, 96 insertions(+), 84 deletions(-) diff --git a/scripts/globals/combat/tp.lua b/scripts/globals/combat/tp.lua index 205c6c9d9cb..648279ce955 100644 --- a/scripts/globals/combat/tp.lua +++ b/scripts/globals/combat/tp.lua @@ -3,90 +3,9 @@ xi.combat = xi.combat or {} xi.combat.tp = xi.combat.tp or {} ----------------------------------- --- Returns attacker TP gain from a single melee hit from itself. -xi.combat.tp.getSingleMeleeHitTPReturn = function(actor, isZanshin) - if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then - return 0 - end - - isZanshin = isZanshin or false -- optional input, defaults to false. - - local delay = actor:getBaseDelay() - local attackOutput = xi.combat.tp.getModifiedDelayAndCanZanshin(actor, delay) - local tpReturn = xi.combat.tp.calculateTPReturn(actor, attackOutput.modifiedDelay) - - if isZanshin and attackOutput.canZanshin then - tpReturn = tpReturn + actor:getMerit(xi.merit.IKISHOTEN) -- https://www.bg-wiki.com/ffxi/Ikishoten - end - - local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 - - return math.floor(tpReturn * storeTPModifier) -end - --- Returns a PC weapon slot's TP return for a single hit. -xi.combat.tp.getSingleWeaponTPReturn = function(actor, slot) - if not actor:isPC() then - return 0 - end - - if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then - return 0 - end - - -- TODO: implement Zanshin check optionally? - local delay = actor:getBaseWeaponDelay(slot) - local attackOutput = xi.combat.tp.getModifiedDelayAndCanZanshin(actor, delay) - local tpReturn = xi.combat.tp.calculateTPReturn(actor, attackOutput.modifiedDelay) - local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 - - return math.floor(tpReturn * storeTPModifier) -end - -xi.combat.tp.getModifiedDelayAndCanZanshin = function(actor, delay) - local modifiedDelay = delay - local canZanshin = false - - -- DW/H2H delay is halved for the purposes of a single hit's TP return when applicable, see https://www.bg-wiki.com/ffxi/Tactical_Points - if actor:isDualWielding() then -- NOTE: this 'isDualWielding' may trip on non-PCs even if they are 'using h2h'. If this is rectified in core in the future this should fall through correctly. - modifiedDelay = (delay * (100 - actor:getMod(xi.mod.DUAL_WIELD)) / 100) / 2 - elseif actor:isUsingH2H() then - if actor:getObjType() == xi.objType.PC then -- handle h2h with > 1 swing only on PC - if - actor:getEquippedItem(xi.slot.SUB) ~= nil or -- equipped shield = one swing - actor:getSkillRank(xi.skill.HAND_TO_HAND) == 0 -- zero h2h rank skill = one swing - then - modifiedDelay = math.max((delay - actor:getMod(xi.mod.MARTIAL_ARTS)), 96) -- min delay of 96 total, https://www.bg-wiki.com/ffxi/Attack_Speed - canZanshin = true -- Zanshin can proc on an 'unarmed' swing -- https://www.bg-wiki.com/ffxi/Zanshin - else - modifiedDelay = math.max((delay - actor:getMod(xi.mod.MARTIAL_ARTS)) / 2, 48) -- min delay of 96 total so 96/2 per fist, https://www.bg-wiki.com/ffxi/Attack_Speed - end - else - -- TODO: handle the corner case where a PC-like entity is using h2h but is only hitting with one 'fist'. Perhaps they have a shield with no main weapon. - -- elseif actor:getAutoAttackHits() > 1 - modifiedDelay = math.max((delay - actor:getMod(xi.mod.MARTIAL_ARTS)) / 2, 48) - end - else -- single melee swing, either 1H or 2H - canZanshin = true -- https://www.bg-wiki.com/ffxi/Zanshin - end - - modifiedDelay = modifiedDelay * math.max((100 + actor:getMod(xi.mod.DELAYP)) / 100, 0.85) -- minimum cap of -15% https://www.bg-wiki.com/ffxi/Attack_Speed. Undocumented if 15% + Claymore Grip goes above 15%. - - return ({ canZanshin = canZanshin , modifiedDelay = math.floor(modifiedDelay) }) -end - --- returns a single melee hit's TP return -xi.combat.tp.getSingleRangedHitTPReturn = function(actor, target) - local delay = actor:getBaseRangedDelay() -- there do not appear to be any delay modifiers for ranged attacks, snapshot does not seem to effect this - - if delay > 0 then - local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 - - return math.floor(xi.combat.tp.calculateTPReturn(actor, delay) * storeTPModifier) - end - - return 0 -end +----------------------------------- +-- "Local" functions only used here. +----------------------------------- -- https://www.bg-wiki.com/ffxi/Tactical_Points -- Gainee is the target who is going to gain the TP. @@ -126,6 +45,38 @@ xi.combat.tp.calculateTPReturn = function(gainee, delay) return math.floor(tpReturn) end +xi.combat.tp.getModifiedDelayAndCanZanshin = function(actor, delay) + local modifiedDelay = delay + local canZanshin = false + + -- DW/H2H delay is halved for the purposes of a single hit's TP return when applicable, see https://www.bg-wiki.com/ffxi/Tactical_Points + if actor:isDualWielding() then -- NOTE: this 'isDualWielding' may trip on non-PCs even if they are 'using h2h'. If this is rectified in core in the future this should fall through correctly. + modifiedDelay = (delay * (100 - actor:getMod(xi.mod.DUAL_WIELD)) / 100) / 2 + elseif actor:isUsingH2H() then + if actor:getObjType() == xi.objType.PC then -- handle h2h with > 1 swing only on PC + if + actor:getEquippedItem(xi.slot.SUB) ~= nil or -- equipped shield = one swing + actor:getSkillRank(xi.skill.HAND_TO_HAND) == 0 -- zero h2h rank skill = one swing + then + modifiedDelay = math.max((delay - actor:getMod(xi.mod.MARTIAL_ARTS)), 96) -- min delay of 96 total, https://www.bg-wiki.com/ffxi/Attack_Speed + canZanshin = true -- Zanshin can proc on an 'unarmed' swing -- https://www.bg-wiki.com/ffxi/Zanshin + else + modifiedDelay = math.max((delay - actor:getMod(xi.mod.MARTIAL_ARTS)) / 2, 48) -- min delay of 96 total so 96/2 per fist, https://www.bg-wiki.com/ffxi/Attack_Speed + end + else + -- TODO: handle the corner case where a PC-like entity is using h2h but is only hitting with one 'fist'. Perhaps they have a shield with no main weapon. + -- elseif actor:getAutoAttackHits() > 1 + modifiedDelay = math.max((delay - actor:getMod(xi.mod.MARTIAL_ARTS)) / 2, 48) + end + else -- single melee swing, either 1H or 2H + canZanshin = true -- https://www.bg-wiki.com/ffxi/Zanshin + end + + modifiedDelay = modifiedDelay * math.max((100 + actor:getMod(xi.mod.DELAYP)) / 100, 0.85) -- minimum cap of -15% https://www.bg-wiki.com/ffxi/Attack_Speed. Undocumented if 15% + Claymore Grip goes above 15%. + + return ({ canZanshin = canZanshin , modifiedDelay = math.floor(modifiedDelay) }) +end + -- Bonus subtle blow II from Tandem Blow (BST trait) xi.combat.tp.getTandemBlowBonus = function(actor) local tandemBlowBonus = 0 @@ -140,6 +91,67 @@ xi.combat.tp.getTandemBlowBonus = function(actor) return tandemBlowBonus end +----------------------------------- +-- Global functions used elsewhere. +----------------------------------- + +-- Returns attacker TP gain from a single melee hit from itself. +xi.combat.tp.getSingleMeleeHitTPReturn = function(actor, isZanshin) + if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then + return 0 + end + + isZanshin = isZanshin or false -- optional input, defaults to false. + + local delay = actor:getBaseDelay() + local attackOutput = xi.combat.tp.getModifiedDelayAndCanZanshin(actor, delay) + local tpReturn = xi.combat.tp.calculateTPReturn(actor, attackOutput.modifiedDelay) + + if isZanshin and attackOutput.canZanshin then + tpReturn = tpReturn + actor:getMerit(xi.merit.IKISHOTEN) -- https://www.bg-wiki.com/ffxi/Ikishoten + end + + local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 + + return math.floor(tpReturn * storeTPModifier) +end + +-- Returns a PC weapon slot's TP return for a single hit. +xi.combat.tp.getSingleWeaponTPReturn = function(actor, slot) + if not actor:isPC() then + return 0 + end + + if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then + return 0 + end + + -- TODO: implement Zanshin check optionally? + local delay = actor:getBaseWeaponDelay(slot) + local attackOutput = xi.combat.tp.getModifiedDelayAndCanZanshin(actor, delay) + local tpReturn = xi.combat.tp.calculateTPReturn(actor, attackOutput.modifiedDelay) + local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 + + return math.floor(tpReturn * storeTPModifier) +end + +-- UNUSED. +-- Returns a single ranged hit's TP return +xi.combat.tp.getSingleRangedHitTPReturn = function(actor) + if actor:hasStatusEffect(xi.effect.MEIKYO_SHISUI) then + return 0 + end + + local delay = actor:getBaseRangedDelay() -- there do not appear to be any delay modifiers for ranged attacks, snapshot does not seem to effect this + if delay <= 0 then + return 0 + end + + local storeTPModifier = 1 + actor:getMod(xi.mod.STORETP) / 100 + + return math.floor(xi.combat.tp.calculateTPReturn(actor, delay) * storeTPModifier) +end + -- TODO: does Ikishoten factor into this as a bonus to baseTPGain if it procs on the hit? Needs verification. xi.combat.tp.calculateTPGainOnPhysicalDamage = function(actor, target, totalDamage, delay) if not actor or not target then