landsandboat/scripts/utils/combat_utils.lua

188 lines
5.5 KiB
Lua

---@class utils
utils = utils or {}
-- A mechanic that will occasionaly reduce shadows consumed by 1 when hit by an AOE skill.
---@nodiscard
---@param actor CBaseEntity
---@param shadowsToRemove integer
---@return integer
function utils.attemptShadowMitigation(actor, shadowsToRemove)
-- TODO: Currently unknown what conditions/skills/stats might affect proc rate. Ninjutsu, AGI, EVA skills might come to mind.
-- TODO: Does this work with Blink or only Copy Images?
-- TODO: Do mobs utilize this mechanic for their shadows?
-- Note: This seems to work with NIN subjob as well.
local procChance = 60
if
(actor:getMainJob() == xi.job.NIN or actor:getSubJob() == xi.job.NIN) and
math.random(1, 100) <= procChance
then
shadowsToRemove = math.max(1, shadowsToRemove - 1)
end
return shadowsToRemove
end
-- Calculate shadow consumption/damage absorbtion.
---@param actor CBaseEntity
---@param damage integer
---@param shadowsToRemove integer?
---@return integer
function utils.takeShadows(actor, damage, shadowsToRemove)
shadowsToRemove = shadowsToRemove or 1
-- Check for Utsusemi first, then Blink.
local shadowPower = actor:getMod(xi.mod.UTSUSEMI)
local shadowType = xi.mod.UTSUSEMI
if shadowPower == 0 then
shadowPower = actor:getMod(xi.mod.BLINK)
shadowType = xi.mod.BLINK
end
-- No shadows, return full damage
if shadowPower == 0 then
return damage
end
local shadowsRemaining = shadowPower
local shadowsUsed = 0
-- Handle Blink shadow removal
if shadowType == xi.mod.BLINK then
for _ = 1, shadowsToRemove do
if shadowsRemaining > 0 and math.random(1, 100) <= 80 then
shadowsRemaining = shadowsRemaining - 1
shadowsUsed = shadowsUsed + 1
end
end
if shadowsUsed >= shadowsToRemove then
damage = 0
else
damage = damage * ((shadowsToRemove - shadowsUsed) / shadowsToRemove)
end
else
-- Handle Utsusemi removal
if shadowPower >= shadowsToRemove then
shadowsRemaining = shadowPower - shadowsToRemove
damage = 0
-- Update remaining Copy Image icon
if shadowsRemaining > 0 then
local effect = actor:getStatusEffect(xi.effect.COPY_IMAGE)
if effect then
local iconMap =
{
[1] = xi.effect.COPY_IMAGE,
[2] = xi.effect.COPY_IMAGE_2,
[3] = xi.effect.COPY_IMAGE_3,
-- Note: 4+ use the same icon.
}
effect:setIcon(iconMap[shadowsRemaining] or xi.effect.COPY_IMAGE_4)
end
end
else
-- Partial shadow consumption, take damage.
damage = damage * ((shadowsToRemove - shadowPower) / shadowsToRemove)
shadowsRemaining = 0
end
end
actor:setMod(shadowType, shadowsRemaining)
if shadowsRemaining <= 0 then
actor:delStatusEffect(xi.effect.COPY_IMAGE)
actor:delStatusEffect(xi.effect.BLINK)
end
return damage
end
-- Calculates Phalanx damage reduction.
---@nodiscard
---@param actor CBaseEntity
---@param damage integer
---@return integer
function utils.handlePhalanx(actor, damage)
if damage <= 0 then
return damage
end
return utils.clamp(damage - actor:getMod(xi.mod.PHALANX), 0, 99999)
end
-- Returns reduced magic damage from RUN buff, 'One for All'
---@nodiscard
---@param actor CBaseEntity
---@param damage integer
---@return integer
function utils.handleOneForAll(actor, damage)
if damage <= 0 then
return damage
end
local oneForAllEffect = actor:getStatusEffect(xi.effect.ONE_FOR_ALL)
if not oneForAllEffect then
return damage
end
return utils.clamp(damage - oneForAllEffect:getPower(), 0, 99999)
end
-- Calculates Stoneskin damage reduction.
---@nodiscard
---@param actor CBaseEntity
---@param damage integer
---@return integer
function utils.handleStoneskin(actor, damage)
if damage <= 0 then
return damage
end
local stoneskinRemaining = actor:getMod(xi.mod.STONESKIN)
if stoneskinRemaining <= 0 then
return damage
end
-- Absorb all damage
if stoneskinRemaining > damage then
actor:delMod(xi.mod.STONESKIN, damage)
return 0
-- Wear off if mitigated damage exceeds stoneskin.
else
actor:delStatusEffect(xi.effect.STONESKIN)
actor:setMod(xi.mod.STONESKIN, 0)
return damage - stoneskinRemaining
end
end
-- Handles Automaton attachment "Analyzer" which decreases damage from successive special attacks.
function utils.handleAutomatonAutoAnalyzer(actor, skill, damage)
local analyzerMod = actor:getMod(xi.mod.AUTO_ANALYZER)
if analyzerMod > 0 then
local skillID = skill:getID()
local previousSkillID = actor:getLocalVar('analyzer_skillID')
local analyzerHits = actor:getLocalVar('analyzer_hits') or 0
if previousSkillID == skillID and analyzerHits < analyzerMod then
-- Analyzer reduces damage by 40%
damage = math.floor(damage * 0.6)
analyzerHits = analyzerHits + 1
else
-- New skill or exceeded limit
actor:setLocalVar('analyzer_skillID', skillID)
analyzerHits = 0
end
actor:setLocalVar('analyzer_hits', analyzerHits)
end
return damage
end