Skip to content

Commit

Permalink
Merge pull request #6871 from WinterSolstice8/cleanup_weaponskills_more
Browse files Browse the repository at this point in the history
[lua] Move pdif calculations out of weaponskills.lua
  • Loading branch information
zach2good authored Feb 1, 2025
2 parents 26aaccf + b9e39ef commit 4c0b4b2
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 183 deletions.
64 changes: 44 additions & 20 deletions scripts/globals/combat/physical_utilities.lua
Original file line number Diff line number Diff line change
Expand Up @@ -379,35 +379,47 @@ end

-- WARNING: This function is used in src/utils/battleutils.cpp "GetDamageRatio" function.
-- If you update this parameters, update them there aswell.
---@param actor CBaseEntity
---@param target CBaseEntity
---@param weaponType xi.skill
---@param wsAttackMod number
---@param isCritical boolean
---@param applyLevelCorrection boolean
---@param tpIgnoresDefense boolean
---@param tpFactor number
---@param isWeaponskill boolean
---@param weaponSlot xi.slot
xi.combat.physical.calculateMeleePDIF = function(actor, target, weaponType, wsAttackMod, isCritical, applyLevelCorrection, tpIgnoresDefense, tpFactor, isWeaponskill, weaponSlot)
local pDif = 0

----------------------------------------
-- Step 1: Attack / Defense Ratio
----------------------------------------
local baseRatio = 0
local actorAttack = math.max(1, math.floor(actor:getStat(xi.mod.ATT, weaponSlot) * wsAttackMod))
local actorAttack = 0
local targetDefense = math.max(1, target:getStat(xi.mod.DEF))
local flourishBonus = 1.0

-- Actor Weaponskill Specific Attack modifiers.
if isWeaponskill then
if actor:hasStatusEffect(xi.effect.BUILDING_FLOURISH) then
local flourishEffect = actor:getStatusEffect(xi.effect.BUILDING_FLOURISH)
local flourishEffect = actor:getStatusEffect(xi.effect.BUILDING_FLOURISH)

if flourishEffect:getPower() >= 2 then -- 2 or more Finishing Moves used.
actorAttack = math.floor(actorAttack * (125 + flourishEffect:getSubPower()) / 100)
end
if flourishEffect and flourishEffect:getPower() >= 2 then -- 2 or more Finishing Moves used.
local meritCount = flourishEffect:getSubPower()

flourishBonus = 1.25 + 0.01 * meritCount -- +1% attack bonus per merit -- TODO: do the merits apply even when FMs are < 2?
end
end

-- Target Defense Modifiers.
local ignoreDefenseFactor = 1
-- TODO: it is unknown if ws attack mod and flourish bonus are additive or multiplicative
actorAttack = math.max(1, math.floor(actor:getStat(xi.mod.ATT, weaponSlot) * wsAttackMod * flourishBonus))

-- Target Defense Modifiers.
if tpIgnoresDefense then
ignoreDefenseFactor = 1 - tpFactor
end
local ignoreDefenseFactor = 1 - tpFactor

targetDefense = math.floor(targetDefense * ignoreDefenseFactor)
targetDefense = math.floor(targetDefense * ignoreDefenseFactor)
end

-- Actor Attack / Target Defense ratio
baseRatio = actorAttack / targetDefense
Expand Down Expand Up @@ -493,29 +505,41 @@ xi.combat.physical.calculateMeleePDIF = function(actor, target, weaponType, wsAt
return pDif
end

---@param actor CBaseEntity
---@param target CBaseEntity
---@param weaponType xi.skill
---@param wsAttackMod number
---@param isCritical boolean
---@param applyLevelCorrection boolean
---@param tpIgnoresDefense boolean
---@param tpFactor number
---@param isWeaponskill boolean
xi.combat.physical.calculateRangedPDIF = function(actor, target, weaponType, wsAttackMod, isCritical, applyLevelCorrection, tpIgnoresDefense, tpFactor, isWeaponskill)
local pDif = 0

----------------------------------------
-- Step 1: Attack / Defense Ratio
----------------------------------------
local baseRatio = 0
local actorAttack = math.max(1, math.floor(actor:getStat(xi.mod.RATT) * wsAttackMod))
local actorAttack = 0
local targetDefense = math.max(1, target:getStat(xi.mod.DEF))
local flourishBonus = 1.0

-- Actor Weaponskill Specific Ranged Attack modifiers.
-- Actor Weaponskill Specific Attack modifiers.
-- TODO: verify this actually works on ranged WS
if isWeaponskill then
-- TODO: verify this actually works on ranged WS.
-- This is a real concern now that RNG/DNC and COR/DNC can actually get level 50 subs through master levels.
if actor:hasStatusEffect(xi.effect.BUILDING_FLOURISH) then
local flourishEffect = actor:getStatusEffect(xi.effect.BUILDING_FLOURISH)
local flourishEffect = actor:getStatusEffect(xi.effect.BUILDING_FLOURISH)

if flourishEffect:getPower() >= 2 then -- 2 or more Finishing Moves used.
actorAttack = math.floor(actorAttack * (125 + flourishEffect:getSubPower()) / 100)
end
if flourishEffect and flourishEffect:getPower() >= 2 then -- 2 or more Finishing Moves used.
local meritCount = flourishEffect:getSubPower()

flourishBonus = 1.25 + 0.01 * meritCount -- +1% attack bonus per merit -- TODO: do the merits apply even when FMs are < 2?
end
end

-- TODO: it is unknown if ws attack mod and flourish bonus are additive or multiplicative
actorAttack = math.max(1, math.floor(actor:getStat(xi.mod.RATT) * wsAttackMod * flourishBonus))

-- Target Defense Modifiers.
local ignoreDefenseFactor = 1

Expand Down
176 changes: 29 additions & 147 deletions scripts/globals/weaponskills.lua
Original file line number Diff line number Diff line change
Expand Up @@ -217,57 +217,6 @@ local function getMultiAttacks(attacker, target, wsParams, firstHit, offHand)
return numHits
end

local function cRangedRatio(attacker, defender, params, ignoredDef, tp)
local atkMultiplier = xi.weaponskills.fTP(tp, params.atkVaries)
local cratio = attacker:getRATT() / (defender:getStat(xi.mod.DEF) - ignoredDef)
local levelCorrection = 0

if attacker:getMainLvl() < defender:getMainLvl() then
levelCorrection = (defender:getMainLvl() - attacker:getMainLvl()) / 40
end

cratio = (cratio - levelCorrection) * atkMultiplier

-- adding cap check base on weapon https://www.bg-wiki.com/ffxi/PDIF info
local damageLimitPlus = attacker:getMod(xi.mod.DAMAGE_LIMIT) / 100
local damageLimitPercent = (100 + attacker:getMod(xi.mod.DAMAGE_LIMITP)) / 100
local weaponType = attacker:getWeaponSkillType(xi.slot.RANGED)
local cRatioCap = 3.25 -- Archery

if weaponType == xi.skill.MARKSMANSHIP then
cRatioCap = 3.5
end

cRatioCap = (cRatioCap + damageLimitPlus) * damageLimitPercent -- Damage Limit calc

cratio = utils.clamp(cratio, 0, cRatioCap)

-- max
local pdifmax = cratio

if cratio < 0.9 then
pdifmax = cratio * 10 / 9
elseif cratio < 1.1 then
pdifmax = 1
end

-- min
local pdifmin = 0

if cratio < 0.9 then
pdifmin = cratio
elseif cratio < 1.1 then
pdifmin = 1
else
pdifmin = cratio * 20 / 19 - 3 / 19
end

local pdif = { pdifmin, pdifmax }
local pdifcrit = { pdifmin * 1.25, pdifmax * 1.25 }

return pdif, pdifcrit
end

local function getRangedHitRate(attacker, target, bonus)
local acc = attacker:getRACC()
local eva = target:getEVA()
Expand Down Expand Up @@ -307,8 +256,13 @@ end

-- Function to calculate if a hit in a WS misses, criticals, and the respective damage done
local function getSingleHitDamage(attacker, target, dmg, ftp, wsParams, calcParams)
local criticalHit = false
local hitDamage = 0
local criticalHit = false
local hitDamage = 0
local atkMultiplier = xi.weaponskills.fTP(calcParams.tpUsed, wsParams.atkVaries)
local ignoreDefMultiplier = xi.weaponskills.fTP(calcParams.tpUsed, wsParams.ignoredDefense)
local applyLevelCorrection = xi.combat.levelCorrection.isLevelCorrectedZone(attacker)
local ignoresDefense = (wsParams.ignoredDefense ~= nil) -- if the table exists, it ignores defense

-- local pdif = 0 Reminder for Future Implementation!

-- priority order of checks
Expand Down Expand Up @@ -362,9 +316,12 @@ local function getSingleHitDamage(attacker, target, dmg, ftp, wsParams, calcPara

if criticalHit then
calcParams.criticalHit = true
calcParams.pdif = xi.weaponskills.generatePdif(calcParams.ccritratio[1], calcParams.ccritratio[2], true)
end

if calcParams.attackType == xi.attackType.PHYSICAL then
calcParams.pdif = xi.combat.physical.calculateMeleePDIF(attacker, target, calcParams.attackInfo.weaponType, atkMultiplier, criticalHit, applyLevelCorrection, ignoresDefense, ignoreDefMultiplier, true, calcParams.attackInfo.slot)
else
calcParams.pdif = xi.weaponskills.generatePdif(calcParams.cratio[1], calcParams.cratio[2], true)
calcParams.pdif = xi.combat.physical.calculateRangedPDIF(attacker, target, calcParams.skillType, atkMultiplier, criticalHit, applyLevelCorrection, ignoresDefense, ignoreDefMultiplier, true)
end

hitDamage = (dmg + consumeManaBonus(attacker)) * ftp * calcParams.pdif
Expand Down Expand Up @@ -688,6 +645,17 @@ xi.weaponskills.calculateRawWSDmg = function(attacker, target, wsID, tp, action,
mainhandMultiHitsDone = mainhandMultiHitsDone + 1
end

local originalSlot = calcParams.attackInfo.slot

-- Update params for accuracy cap/pdif purposes
if calcParams.extraOffhandHit then
if offhandSkill ~= xi.skill.HAND_TO_HAND then
calcParams.attackInfo.slot = xi.slot.SUB
end

calcParams.attackInfo.weaponType = offhandSkill
end

-- Do the extra hit for our offhand if applicable
if calcParams.extraOffhandHit and hitsDone < 8 and finaldmg < targetHp then
calcParams.hitsLanded = 0
Expand Down Expand Up @@ -749,6 +717,9 @@ xi.weaponskills.calculateRawWSDmg = function(attacker, target, wsID, tp, action,

calcParams.extraHitsLanded = calcParams.mainHitsLanded + calcParams.offhandHitsLanded

-- Reset slot info (A listener eventually uses this, and the change to SLOT_SUB on DW will be unexpected)
calcParams.attackInfo.slot = originalSlot

-- Factor in "all hits" bonus damage mods
-- TODO: does this apply to every hit of a multi hit WS as it's coming in to account for potentially excess damage here?
local bonusdmg = 0
Expand Down Expand Up @@ -777,10 +748,6 @@ end
-- Sets up the necessary calcParams for a melee WS before passing it to calculateRawWSDmg. When the raw
-- damage is returned, handles reductions based on target resistances and passes off to xi.weaponskills.takeWeaponskillDamage.
xi.weaponskills.doPhysicalWeaponskill = function(attacker, target, wsID, wsParams, tp, action, primaryMsg, taChar)
-- Determine cratio and ccritratio
local ignoredDef = xi.weaponskills.calculatedIgnoredDef(tp, target:getStat(xi.mod.DEF), wsParams.ignoredDefense)
local cratio, ccritratio = xi.weaponskills.cMeleeRatio(attacker, target, wsParams, ignoredDef, tp)

-- Set up conditions and wsParams used for calculating weaponskill damage
local gorgetBeltFTP, gorgetBeltAcc = xi.weaponskills.handleWSGorgetBelt(attacker)
local attack =
Expand All @@ -796,8 +763,6 @@ xi.weaponskills.doPhysicalWeaponskill = function(attacker, target, wsID, wsParam
calcParams.attackInfo = attack
calcParams.weaponDamage = xi.weaponskills.getMeleeDmg(attacker, attack.weaponType, wsParams.kick)
calcParams.fSTR = xi.weaponskills.fSTR(attacker:getStat(xi.mod.STR), target:getStat(xi.mod.VIT), attacker:getWeaponDmgRank())
calcParams.cratio = cratio
calcParams.ccritratio = ccritratio
calcParams.accStat = attacker:getACC()
calcParams.melee = true
calcParams.mustMiss = target:hasStatusEffect(xi.effect.PERFECT_DODGE) or (target:hasStatusEffect(xi.effect.ALL_MISS) and not wsParams.hitsHigh)
Expand All @@ -812,6 +777,7 @@ xi.weaponskills.doPhysicalWeaponskill = function(attacker, target, wsID, wsParam
calcParams.hybridHit = wsParams.hybridWS
calcParams.flourishEffect = attacker:getStatusEffect(xi.effect.BUILDING_FLOURISH)
calcParams.bonusTP = wsParams.bonusTP or 0
calcParams.tpUsed = tp
calcParams.attackType = xi.attackType.PHYSICAL

local isJump = wsParams.isJump or false
Expand Down Expand Up @@ -860,10 +826,6 @@ end
-- Sets up the necessary calcParams for a ranged WS before passing it to calculateRawWSDmg. When the raw
-- damage is returned, handles reductions based on target resistances and passes off to xi.weaponskills.takeWeaponskillDamage.
xi.weaponskills.doRangedWeaponskill = function(attacker, target, wsID, wsParams, tp, action, primaryMsg)
-- Determine cratio and ccritratio
local ignoredDef = xi.weaponskills.calculatedIgnoredDef(tp, target:getStat(xi.mod.DEF), wsParams.ignoredDefense)
local cratio, ccritratio = cRangedRatio(attacker, target, wsParams, ignoredDef, tp)

-- Set up conditions and params used for calculating weaponskill damage
local gorgetBeltFTP, gorgetBeltAcc = xi.weaponskills.handleWSGorgetBelt(attacker)
local attack =
Expand All @@ -877,11 +839,10 @@ xi.weaponskills.doRangedWeaponskill = function(attacker, target, wsID, wsParams,
local calcParams =
{
wsID = wsID,
attackInfo = attack,
weaponDamage = { attacker:getRangedDmg() },
skillType = attacker:getWeaponSkillType(xi.slot.RANGED),
fSTR = xi.weaponskills.fSTR2(attacker:getStat(xi.mod.STR), target:getStat(xi.mod.VIT), attacker:getRangedDmgRank()),
cratio = cratio,
ccritratio = ccritratio,
accStat = attacker:getRACC(),
melee = false,
mustMiss = false,
Expand All @@ -892,6 +853,7 @@ xi.weaponskills.doRangedWeaponskill = function(attacker, target, wsID, wsParams,
forcedFirstCrit = false,
extraOffhandHit = false,
flourishEffect = false,
tpUsed = tp,
bonusTP = wsParams.bonusTP or 0,
bonusfTP = gorgetBeltFTP or 0,
bonusAcc = (gorgetBeltAcc or 0) + attacker:getMod(xi.mod.WSACC),
Expand Down Expand Up @@ -1252,76 +1214,6 @@ xi.weaponskills.calculatedIgnoredDef = function(tp, def, ignoredDefenseTable)
return 0
end

local function getMeleePDifRange(correctedRatio)
-- pDifMax
local pDifMax = 3
if correctedRatio < 0.5 then
pDifMax = correctedRatio + 0.5
elseif correctedRatio < 0.7 then
pDifMax = 1
elseif correctedRatio < 1.2 then
pDifMax = correctedRatio + 0.3
elseif correctedRatio < 1.5 then
pDifMax = correctedRatio * 0.25 + correctedRatio
elseif correctedRatio < 2.625 then
pDifMax = correctedRatio + 0.375
end

-- pDifMin
local pDifMin = correctedRatio - 0.375
if correctedRatio < 0.38 then
pDifMin = 0
elseif correctedRatio < 1.25 then
pDifMin = correctedRatio * 1176 / 1024 - 448 / 1024
elseif correctedRatio < 1.51 then
pDifMin = 1
elseif correctedRatio < 2.44 then
pDifMin = correctedRatio * 1176 / 1024 - 775 / 1024
end

return { pDifMin, pDifMax }
end

-- Given the raw ratio value (atk/def) and levels, returns the cRatio (min then max)
xi.weaponskills.cMeleeRatio = function(attacker, defender, params, ignoredDef, tp)
local weaponType = attacker:getWeaponSkillType(xi.slot.MAIN)
local flourishEffect = attacker:getStatusEffect(xi.effect.BUILDING_FLOURISH)

if flourishEffect ~= nil and flourishEffect:getPower() >= 2 then -- 2 or more Finishing Moves used.
attacker:addMod(xi.mod.ATTP, 25 + flourishEffect:getSubPower())
end

local atkMultiplier = xi.weaponskills.fTP(tp, params.atkVaries)
local cratio = attacker:getStat(xi.mod.ATT) * atkMultiplier / (defender:getStat(xi.mod.DEF) - ignoredDef)

-- cratio = utils.clamp(cratio, 0, 2.25)
if flourishEffect ~= nil and flourishEffect:getPower() >= 2 then -- 2 or more Finishing Moves used.
attacker:delMod(xi.mod.ATTP, 25 + flourishEffect:getSubPower())
end

local levelCorrection = 0
if attacker:getMainLvl() < defender:getMainLvl() then
levelCorrection = 0.05 * (defender:getMainLvl() - attacker:getMainLvl())
end

cratio = math.max(0, cratio - levelCorrection)
local damageLimitPlus = attacker:getMod(xi.mod.DAMAGE_LIMIT) / 100
local damageLimitPercent = (100 + attacker:getMod(xi.mod.DAMAGE_LIMITP)) / 100
local cratioCap = (xi.combat.physical.pDifWeaponCapTable[weaponType][1] + damageLimitPlus) * damageLimitPercent -- Added damage limit bonuses

cratio = utils.clamp(cratio, 0, cratioCap)
local pdif = getMeleePDifRange(cratio)

cratio = cratio + 1
cratio = utils.clamp(cratio, 0, cratioCap)
local unadjustedPDifCrit = getMeleePDifRange(cratio)

local critbonus = utils.clamp(attacker:getMod(xi.mod.CRIT_DMG_INCREASE) - defender:getMod(xi.mod.CRIT_DEF_BONUS), 0, 100)
local pdifcrit = { unadjustedPDifCrit[1] * (100 + critbonus) / 100, unadjustedPDifCrit[2] * (100 + critbonus) / 100 }

return pdif, pdifcrit
end

-- Given the attacker's str and the mob's vit, fSTR is calculated (for melee WS)
xi.weaponskills.fSTR = function(atkStr, defVit, weaponRank)
local dSTR = atkStr - defVit
Expand All @@ -1335,16 +1227,6 @@ xi.weaponskills.fSTR = function(atkStr, defVit, weaponRank)
return fSTR
end

xi.weaponskills.generatePdif = function(cratiomin, cratiomax, melee)
local pdif = math.random(cratiomin * 1000, cratiomax * 1000) / 1000

if melee then
pdif = pdif * (math.random(100, 105) / 100)
end

return pdif
end

xi.weaponskills.handleWSGorgetBelt = function(attacker)
local ftpBonus = 0
local accBonus = 0
Expand Down
2 changes: 1 addition & 1 deletion src/map/entities/battleentity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ uint16 CBattleEntity::RATT(uint8 skill, uint16 bonusSkill)
uint16 baseSkill = skill == SKILL_FISHING ? 0 : GetSkill(skill);
int32 RATT = 8 + baseSkill + bonusSkill + m_modStat[Mod::RATT] + battleutils::GetRangedAttackBonuses(this) + STR();
// use max to prevent any underflow
return std::max(0, RATT + (RATT * m_modStat[Mod::RATTP] / 100) + std::min<int16>((RATT * m_modStat[Mod::FOOD_RATTP] / 100), m_modStat[Mod::FOOD_RATT_CAP]));
return std::max(1, RATT + (RATT * m_modStat[Mod::RATTP] / 100) + std::min<int16>((RATT * m_modStat[Mod::FOOD_RATTP] / 100), m_modStat[Mod::FOOD_RATT_CAP]));
}

uint16 CBattleEntity::RACC(uint8 skill, uint16 bonusSkill)
Expand Down
Loading

0 comments on commit 4c0b4b2

Please sign in to comment.