landsandboat/scripts/globals/automaton.lua
Skold 984b000675 Globalize Attachment Mods, Add Volt Gun, Cleanup Automaton.lua
Globalizes the attachment modifiers, implements volt gun and cleans up automaton lua.

Co-Authored-By: WinterSolstice8 <60417494+WinterSolstice8@users.noreply.github.com>
2026-06-18 09:32:41 +02:00

612 lines
30 KiB
Lua

-----------------------------------
-- Automaton Global
-----------------------------------
xi = xi or {}
xi.automaton = xi.automaton or {}
xi.automaton.abilities =
{
CHIMERA_RIPPER = 1940,
STRING_CLIPPER = 1941,
ARCUBALLISTA = 1942,
SLAPSTICK = 1943,
SHIELD_BASH = 1944,
PROVOKE = 1945,
SHOCK_ABSORBER = 1946,
FLASHBULB = 1947,
MANA_CONVERTER = 1948,
RANGED_ATTACK = 1949,
ERASER = 2021,
REACTIVE_SHIELD = 2031,
CANNIBAL_BLADE = 2065,
DAZE = 2066,
KNOCKOUT = 2067,
ECONOMIZER = 2068,
REPLICATOR = 2132,
BONE_CRUSHER = 2299,
ARMOR_PIERCER = 2300,
MAGIC_MORTAR = 2301,
STRING_SHREDDER = 2743,
ARMOR_SHATTERER = 2744,
HEAT_CAPACITOR = 2745,
BARRAGE_TURBINE = 2746,
DISRUPTOR = 2747,
REGULATOR = 3485,
}
-- [FRAME][HEAD] = Model ID
local automatonModels =
{
[xi.automaton.frame.HARLEQUIN] =
{
[xi.automaton.head.HARLEQUIN ] = 0x07B9,
[xi.automaton.head.VALOREDGE ] = 0x07BA,
[xi.automaton.head.SHARPSHOT ] = 0x07BC,
[xi.automaton.head.STORMWAKER ] = 0x07BB,
[xi.automaton.head.SOULSOOTHER ] = 0x07D3,
[xi.automaton.head.SPIRITREAVER] = 0x07D7,
},
[xi.automaton.frame.VALOREDGE] =
{
[xi.automaton.head.HARLEQUIN ] = 0x07BE,
[xi.automaton.head.VALOREDGE ] = 0x07BF,
[xi.automaton.head.SHARPSHOT ] = 0x07C1,
[xi.automaton.head.STORMWAKER ] = 0x07C0,
[xi.automaton.head.SOULSOOTHER ] = 0x07D4,
[xi.automaton.head.SPIRITREAVER] = 0x07D8,
},
[xi.automaton.frame.SHARPSHOT] =
{
[xi.automaton.head.HARLEQUIN ] = 0x07C3,
[xi.automaton.head.VALOREDGE ] = 0x07C4,
[xi.automaton.head.SHARPSHOT ] = 0x07C6,
[xi.automaton.head.STORMWAKER ] = 0x07C5,
[xi.automaton.head.SOULSOOTHER ] = 0x07D5,
[xi.automaton.head.SPIRITREAVER] = 0x07D9,
},
[xi.automaton.frame.STORMWAKER] =
{
[xi.automaton.head.HARLEQUIN ] = 0x07C8,
[xi.automaton.head.VALOREDGE ] = 0x07C9,
[xi.automaton.head.SHARPSHOT ] = 0x07CB,
[xi.automaton.head.STORMWAKER ] = 0x07CA,
[xi.automaton.head.SOULSOOTHER ] = 0x07D6,
[xi.automaton.head.SPIRITREAVER] = 0x07DA,
},
}
local maneuverList =
{
['dark_maneuver' ] = { xi.effect.DARK_MANEUVER, xi.element.DARK, nil },
['earth_maneuver' ] = { xi.effect.EARTH_MANEUVER, xi.element.EARTH, xi.mod.VIT },
['fire_maneuver' ] = { xi.effect.FIRE_MANEUVER, xi.element.FIRE, xi.mod.STR },
['ice_maneuver' ] = { xi.effect.ICE_MANEUVER, xi.element.ICE, xi.mod.INT },
['light_maneuver' ] = { xi.effect.LIGHT_MANEUVER, xi.element.LIGHT, xi.mod.CHR },
['thunder_maneuver'] = { xi.effect.THUNDER_MANEUVER, xi.element.THUNDER, xi.mod.DEX },
['water_maneuver' ] = { xi.effect.WATER_MANEUVER, xi.element.WATER, xi.mod.MND },
['wind_maneuver' ] = { xi.effect.WIND_MANEUVER, xi.element.WIND, xi.mod.AGI },
}
-- Table of attachment modifiers.
-- Modifiers are keyed by number of maneuvers active. 0 / 1 / 2 / 3
-- Regen & Refresh values are calculated separately in their own function and left nil on purpose.
xi.automaton.attachmentModifiers =
{
-- Attachment Modifier Maneuver Values Optic Fiber Bonus
['accelerator' ] = { { xi.mod.EVA, { 5, 10, 15, 20 }, true }, },
['accelerator_ii' ] = { { xi.mod.EVA, { 10, 15, 20, 25 }, true }, },
['accelerator_iii' ] = { { xi.mod.EVA, { 20, 30, 40, 50 }, true }, },
['accelerator_iv' ] = { { xi.mod.EVA, { 30, 45, 60, 80 }, true }, },
['analyzer' ] = { { xi.mod.AUTO_ANALYZER, { 1, 2, 4, 6 }, false }, },
['amplifier' ] = { { xi.mod.MAGIC_BURST_BONUS_UNCAPPED, { 10, 20, 35, 50 }, true },
{ xi.mod.ELEMENTAL_CELERITY, { 25, 25, 25, 25 }, true }, },
['amplifier_ii' ] = { { xi.mod.MAGIC_BURST_BONUS_UNCAPPED, { 20, 30, 50, 70 }, true },
{ xi.mod.ELEMENTAL_CELERITY, { 25, 25, 25, 25 }, true }, },
['arcanic_cell' ] = { { xi.mod.OCCULT_ACUMEN, { 10, 20, 35, 50 }, true }, },
['arcanic_cell_ii' ] = { { xi.mod.OCCULT_ACUMEN, { 20, 40, 70, 100 }, true }, },
['arcanoclutch' ] = { { xi.mod.MAGIC_DAMAGE, { 20, 40, 60, 80 }, true }, },
['arcanoclutch_ii' ] = { { xi.mod.MAGIC_DAMAGE, { 40, 60, 80, 120 }, true }, },
['armor_plate' ] = { { xi.mod.DMGPHYS, { -500, -700, -1000, -1500 }, true }, },
['armor_plate_ii' ] = { { xi.mod.DMGPHYS, { -1000, -1500, -2000, -2500 }, true }, },
['armor_plate_iii' ] = { { xi.mod.DMGPHYS, { -1500, -2000, -2500, -3000 }, true }, },
['armor_plate_iv' ] = { { xi.mod.DMGPHYS, { -2000, -2500, -3000, -4000 }, true }, },
['auto-repair_kit' ] = { { xi.mod.REGEN, { nil, nil, nil, nil }, true }, },
['auto-repair_kit_ii' ] = { { xi.mod.REGEN, { nil, nil, nil, nil }, true }, },
['auto-repair_kit_iii'] = { { xi.mod.REGEN, { nil, nil, nil, nil }, true }, },
['auto-repair_kit_iv' ] = { { xi.mod.REGEN, { nil, nil, nil, nil }, true }, },
['barrier_module' ] = { { xi.mod.SHIELDBLOCKRATE, { 0, 5, 10, 15 }, true },
{ xi.mod.AUTO_SHIELD_BASH_DELAY, { 0, 5, 10, 15 }, false }, },
['barrier_module_ii' ] = { { xi.mod.SHIELDBLOCKRATE, { 0, 10, 20, 30 }, true },
{ xi.mod.AUTO_SHIELD_BASH_DELAY, { 0, 5, 10, 15 }, false }, },
['coiler' ] = { { xi.mod.DOUBLE_ATTACK, { 3, 10, 20, 30 }, true }, },
['coiler_ii' ] = { { xi.mod.DOUBLE_ATTACK, { 10, 15, 25, 35 }, true }, },
['damage_gauge' ] = { { xi.mod.AUTO_HEALING_THRESHOLD, { 50, 60, 70, 80 }, false },
{ xi.mod.AUTO_HEALING_DELAY, { 3, 3, 3, 3 }, false }, },
['damage_gauge_ii' ] = { { xi.mod.AUTO_HEALING_THRESHOLD, { 60, 70, 80, 90 }, false },
{ xi.mod.AUTO_HEALING_DELAY, { 3, 3, 3, 3 }, false }, },
['drum_magazine' ] = { { xi.mod.AUTO_RANGED_DELAY, { 3, 6, 9, 15 }, true }, },
['dynamo' ] = { { xi.mod.CRITHITRATE, { 3, 5, 7, 9 }, true }, },
['dynamo_ii' ] = { { xi.mod.CRITHITRATE, { 5, 10, 15, 20 }, true }, },
['dynamo_iii' ] = { { xi.mod.CRITHITRATE, { 10, 15, 25, 35 }, true }, },
['flame_holder' ] = { { xi.mod.WEAPONSKILL_DAMAGE_BASE, { 125, 200, 275, 350 }, true }, },
['equalizer' ] = { { xi.mod.AUTO_EQUALIZER, { 10, 25, 50, 75 }, true }, },
['galvanizer' ] = { { xi.mod.COUNTER, { 10, 20, 35, 50 }, true }, },
['hammermill' ] = { { xi.mod.SHIELD_BASH, { 15, 25, 50, 100 }, true },
{ xi.mod.AUTO_SHIELD_BASH_SLOW, { 0, 12, 19, 25 }, true }, },
['heatsink' ] = { { xi.mod.BURDEN_DECAY, { 2, 4, 5, 6 }, false }, },
['ice_maker' ] = { { xi.mod.AUTO_MAB_COEFFICIENT, { 0, 50, 75, 100 }, true }, },
['inhibitor' ] = { { xi.mod.STORETP, { 5, 15, 25, 40 }, true }, },
['inhibitor_ii' ] = { { xi.mod.STORETP, { 10, 25, 40, 65 }, true }, },
['loudspeaker' ] = { { xi.mod.MATT, { 5, 10, 15, 20 }, true }, },
['loudspeaker_ii' ] = { { xi.mod.MATT, { 10, 15, 20, 25 }, true }, },
['loudspeaker_iii' ] = { { xi.mod.MATT, { 20, 30, 40 , 50 }, true }, },
['loudspeaker_iv' ] = { { xi.mod.MATT, { 30, 40, 50, 60 }, true }, },
['loudspeaker_v' ] = { { xi.mod.MATT, { 40, 50, 60, 70 }, true }, },
['magniplug' ] = { { xi.mod.MAIN_DMG_RATING, { 5, 15, 30, 45 }, true },
{ xi.mod.RANGED_DMG_RATING, { 5, 15, 30, 45 }, true }, },
['magniplug_ii' ] = { { xi.mod.MAIN_DMG_RATING, { 10, 20, 35, 50 }, true },
{ xi.mod.RANGED_DMG_RATING, { 10, 20, 35, 50 }, true }, },
['mana_booster' ] = { { xi.mod.FASTCAST, { 20, 30, 45, 60 }, false }, },
['mana_channeler' ] = { { xi.mod.MATT, { 10, 15, 25, 35 }, true },
{ xi.mod.AUTO_MAGIC_COOLDOWN, { 3, 6, 9, 12 }, true }, },
['mana_channeler_ii' ] = { { xi.mod.MATT, { 20, 30, 40, 50 }, true },
{ xi.mod.AUTO_MAGIC_COOLDOWN, { 6, 12, 18, 24 }, true }, },
['mana_conserver' ] = { { xi.mod.CONSERVE_MP, { 15, 30, 45, 60 }, true }, },
['mana_jammer' ] = { { xi.mod.MDEF, { 10, 20, 30, 40 }, true }, },
['mana_jammer_ii' ] = { { xi.mod.MDEF, { 20, 30, 40, 50 }, true }, },
['mana_jammer_iii' ] = { { xi.mod.MDEF, { 30, 40, 50, 60 }, true }, },
['mana_jammer_iv' ] = { { xi.mod.MDEF, { 40, 50, 60, 70 }, true }, },
['mana_tank' ] = { { xi.mod.REFRESH, { nil, nil, nil, nil }, true }, },
['mana_tank_ii' ] = { { xi.mod.REFRESH, { nil, nil, nil, nil }, true }, },
['mana_tank_iii' ] = { { xi.mod.REFRESH, { nil, nil, nil, nil }, true }, },
['mana_tank_iv' ] = { { xi.mod.REFRESH, { nil, nil, nil, nil }, true }, },
['optic_fiber' ] = { { xi.mod.AUTO_PERFORMANCE_BOOST, { 10, 20, 25, 30 }, false }, },
['optic_fiber_ii' ] = { { xi.mod.AUTO_PERFORMANCE_BOOST, { 15, 30, 37, 45 }, false }, },
['percolator' ] = { { xi.mod.COMBAT_SKILLUP_RATE, { 5, 10, 15, 20 }, true }, },
['power_cooler' ] = { { xi.mod.MP_COST_REDUCTION, { 10, 20, 35, 50 }, true }, },
['repeater' ] = { { xi.mod.DOUBLE_SHOT_RATE, { 10, 15, 35, 65 }, true }, },
['resister' ] = { { xi.mod.STATUSRES, { 5, 10, 20, 30 }, true }, },
['resister_ii' ] = { { xi.mod.STATUSRES, { 10, 20, 40, 60 }, true }, },
['scanner' ] = { { xi.mod.AUTO_SCAN_RESISTS, { 0, 1, 1, 1 }, false }, },
['schurzen' ] = { { xi.mod.AUTO_SCHURZEN, { 0, 1, 1, 1 }, false }, },
['scope' ] = { { xi.mod.RACC, { 10, 20, 30, 40 }, true }, },
['scope_ii' ] = { { xi.mod.RACC, { 20, 30, 40, 50 }, true }, },
['scope_iii' ] = { { xi.mod.RACC, { 30, 40, 55, 70 }, true }, },
['scope_iv' ] = { { xi.mod.RACC, { 40, 50, 65, 80 }, true }, },
['speedloader' ] = { { xi.mod.SKILLCHAINBONUS, { 20, 30, 40, 60 }, true }, },
['speedloader_ii' ] = { { xi.mod.SKILLCHAINBONUS, { 35, 45, 60, 80 }, true }, },
['smoke_screen' ] = { { xi.mod.EVA, { 20, 40, 80, 160 }, true },
{ xi.mod.ACC, { -20, -40, -80, -160 }, true },
{ xi.mod.RACC, { -20, -40, -80, -160 }, true }, },
['stabilizer' ] = { { xi.mod.ACC, { 5, 10, 15, 20 }, true }, },
['stabilizer_ii' ] = { { xi.mod.ACC, { 10, 15, 20, 25 }, true }, },
['stabilizer_iii' ] = { { xi.mod.ACC, { 20, 30, 40, 50 }, true }, },
['stabilizer_iv' ] = { { xi.mod.ACC, { 30, 40, 55, 70 }, true }, },
['stabilizer_v' ] = { { xi.mod.ACC, { 40, 50, 65, 80 }, true }, },
['stealth_screen' ] = { { xi.mod.ENMITY, { -10, -20, -30, -40 }, true }, },
['stealth_screen_ii' ] = { { xi.mod.ENMITY, { -15, -25, -35, -45 }, true }, },
['steam_jacket' ] = { { xi.mod.AUTO_STEAM_JACKET_REDUCTION, { 30, 45, 60, 80 }, true }, },
['strobe' ] = { { xi.mod.ENMITY, { 10, 25, 40, 60 }, true }, },
['strobe_ii' ] = { { xi.mod.ENMITY, { 20, 40, 65, 100 }, true }, },
['tactical_processor' ] = { { xi.mod.AUTO_DECISION_DELAY, { 50, 70, 85, 115 }, false }, },
['tension_spring' ] = { { xi.mod.ATTP, { 3, 6, 9, 12 }, true },
{ xi.mod.RATTP, { 3, 6, 9, 12 }, true }, },
['tension_spring_ii' ] = { { xi.mod.ATTP, { 6, 9, 12, 15 }, true },
{ xi.mod.RATTP, { 6, 9, 12, 15 }, true }, },
['tension_spring_iii' ] = { { xi.mod.ATTP, { 12, 15, 18, 21 }, true },
{ xi.mod.RATTP, { 12, 15, 18, 21 }, true }, },
['tension_spring_iv' ] = { { xi.mod.ATTP, { 15, 18, 21, 24 }, true },
{ xi.mod.RATTP, { 15, 18, 21, 24 }, true }, },
['tension_spring_v' ] = { { xi.mod.ATTP, { 18, 21, 24, 27 }, true },
{ xi.mod.RATTP, { 18, 21, 24, 27 }, true }, },
['tranquilizer' ] = { { xi.mod.MACC, { 10, 30, 40, 50 }, true }, },
['tranquilizer_ii' ] = { { xi.mod.MACC, { 20, 40, 55, 70 }, true }, },
['tranquilizer_iii' ] = { { xi.mod.MACC, { 30, 50, 70, 80 }, true }, },
['tranquilizer_iv' ] = { { xi.mod.MACC, { 40, 60, 80, 110 }, true }, },
['truesights' ] = { { xi.mod.AUTO_RANGED_DAMAGEP, { 5, 15, 30, 45 }, true }, },
['turbo_charger' ] = { { xi.mod.HASTE_MAGIC, { 500, 1500, 2000, 2500 }, true }, },
['turbo_charger_ii' ] = { { xi.mod.HASTE_MAGIC, { 700, 1700, 2800, 4375 }, true }, },
['vivi-valve' ] = { { xi.mod.CURE_POTENCY, { 5, 15, 30, 45 }, true }, },
['vivi-valve_ii' ] = { { xi.mod.CURE_POTENCY, { 10, 20, 35, 50 }, true }, },
['volt_gun' ] = { { xi.mod.VOLT_GUN_POTENCY, { 0, 20, 40, 100 }, false }, },
}
-----------------------------------
-- Attuner
-----------------------------------
---@param actor CBaseEntity
---@param target CBaseEntity
---@return number
xi.automaton.handleAttuner = function(actor, target)
if not actor:isAutomaton() then
return 0
end
if not actor:hasAttachmentSet(xi.item.ATTUNER_ATTACHMENT) then
return 0
end
if actor:getMainLvl() >= target:getMainLvl() then
return 0
end
local master = actor:getMaster()
if not master then
return 0
end
-- We have reason to believe BG wiki is wrong about the attuner values
-- so we are using these for the moment; JP wiki and dev posts imply that it's not as simple as level + 1 and higher gets massive def ignore.
local numFireManeuvers = math.min(master:countEffect(xi.effect.FIRE_MANEUVER), 3)
if numFireManeuvers > 0 and master:hasStatusEffect(xi.effect.OVERDRIVE) then
numFireManeuvers = 3
end
local attunerEffect =
{
[0] = 0.05,
[1] = 0.10,
[2] = 0.15,
[3] = 0.20
}
return attunerEffect[numFireManeuvers] or 0
end
-----------------------------------
-- Auto Repair Kit
-----------------------------------
-- Repair Kit HP boost is calculated per frame using the attachment's hpBoost and the frame divisor.
-- Example: Repair Kit IV on Harlequin is 4 / 20 = 0.2, or a 20% HP boost.
xi.automaton.repairKit =
{
frameDivisors =
{
[xi.automaton.frame.HARLEQUIN ] = 20,
[xi.automaton.frame.VALOREDGE ] = 24,
[xi.automaton.frame.SHARPSHOT ] = 18,
[xi.automaton.frame.STORMWAKER] = 16,
},
data =
{
['auto-repair_kit' ] = { id = 193, hpBoost = 1, regenBase = { 0, 1, 2, 3 }, regenMultiplier = { 0, 0.125, 0.225, 0.375 } },
['auto-repair_kit_ii' ] = { id = 196, hpBoost = 2, regenBase = { 0, 3, 6, 9 }, regenMultiplier = { 0, 0.600, 1.200, 1.800 } },
['auto-repair_kit_iii'] = { id = 202, hpBoost = 3, regenBase = { 0, 9, 12, 15 }, regenMultiplier = { 0, 1.800, 2.400, 3.000 } },
['auto-repair_kit_iv' ] = { id = 205, hpBoost = 4, regenBase = { 0, 15, 18, 21 }, regenMultiplier = { 0, 3.000, 3.600, 4.200 } },
},
}
local function getRegenModValue(pet, attachment, numManeuvers)
local repairKitData = xi.automaton.repairKit.data[attachment:getName()]
if not repairKitData then
return 0
end
return repairKitData.regenBase[numManeuvers + 1] + pet:getMaxHP() * repairKitData.regenMultiplier[numManeuvers + 1] / 100
end
-----------------------------------
-- Coiler
-----------------------------------
-- Returns the number of extra hits granted by the DOUBLE_ATTACK modifier based on the base number of hits.
xi.automaton.getExtraHits = function(automaton, numHits)
local doubleAttackRate = utils.clamp(automaton:getMod(xi.mod.DOUBLE_ATTACK), 0, 100)
local extraHits = 0
if doubleAttackRate > 0 then
for _ = 1, numHits do
if math.random(1, 100) <= doubleAttackRate then
extraHits = extraHits + 1
end
end
end
return extraHits
end
-----------------------------------
-- Equalizer
-----------------------------------
---@param actor CBaseEntity
---@param damage integer
---@return integer
xi.automaton.handleEqualizer = function(actor, damage)
local equalizerModifier = actor:getMod(xi.mod.AUTO_EQUALIZER)
local maxHP = actor:getMaxHP()
-- No Equalizer Equipped, return unmodified damage.
if equalizerModifier == 0 then
return damage
end
-- No Damage to reduce, return unmodified damage.
if damage <= 0 then
return damage
end
-- Equalizer damage reduction becomes more effective the higher the damage is in relation to the automatons max HP.
local reductionRate = (damage / maxHP) * (equalizerModifier / 100)
reductionRate = math.floor(reductionRate * 100) / 100
-- Damage reduction is capped at 90%.
reductionRate = math.min(reductionRate, 0.90)
return math.floor(damage * (1 - reductionRate))
end
-----------------------------------
-- Flame Holder
-----------------------------------
-- Applies the FTP multiplier for an Automaton Weapon Skill, factoring in the WEAPONSKILL_DAMAGE_BASE modifier from Flame Holder.
xi.automaton.applyFlameHolder = function(automaton, ftp)
local flameHolderFTP = automaton:getMod(xi.mod.WEAPONSKILL_DAMAGE_BASE) / 100
if flameHolderFTP > 0 then
ftp[1] = ftp[1] * flameHolderFTP
ftp[2] = ftp[2] * flameHolderFTP
ftp[3] = ftp[3] * flameHolderFTP
end
end
-----------------------------------
-- Mana Tank
-----------------------------------
-- Mana Tank MP boost is calculated per frame using the attachment's mpBoost and the frame divisor.
-- Example: Mana Tank IV on Harlequin is 4 / 20 = 0.2, or a 20% MP boost.
xi.automaton.manaTank =
{
frameDivisors =
{
[xi.automaton.frame.HARLEQUIN ] = 20,
[xi.automaton.frame.STORMWAKER] = 24,
},
data =
{
['mana_tank' ] = { id = 225, mpBoost = 1, refreshBase = { 0, 1, 2, 3 }, refreshMultiplier = { 0, 0.2, 0.4, 0.6 } },
['mana_tank_ii' ] = { id = 228, mpBoost = 2, refreshBase = { 0, 2, 3, 4 }, refreshMultiplier = { 0, 0.4, 0.6, 0.8 } },
['mana_tank_iii'] = { id = 233, mpBoost = 3, refreshBase = { 0, 3, 4, 5 }, refreshMultiplier = { 0, 0.6, 0.8, 1.0 } },
['mana_tank_iv' ] = { id = 235, mpBoost = 4, refreshBase = { 0, 4, 5, 6 }, refreshMultiplier = { 0, 0.8, 1.0, 1.2 } },
},
}
local function getRefreshModValue(pet, attachment, numManeuvers)
local manaTankData = xi.automaton.manaTank.data[attachment:getName()]
if not manaTankData then
return 0
end
return manaTankData.refreshBase[numManeuvers + 1] + pet:getMaxMP() * manaTankData.refreshMultiplier[numManeuvers + 1] / 100
end
-----------------------------------
-- Optic Fiber
-----------------------------------
local function isOpticFiber(attachmentName)
if string.find(attachmentName, 'optic_fiber') ~= nil then
return true
end
return false
end
-- Due to load order, we can't expect to determine Optic Fiber enhancements on change.
-- For maneuvers, calculate this based on the number of light maneuvers that are active.
local function calculatePerformanceBoost(pet)
local master = pet:getMaster()
local performanceBoost = 0
local numLightManeuvers = master and master:countEffect(xi.effect.LIGHT_MANEUVER) or 0
for _, attachmentName in pairs(pet:getAttachments()) do
if
isOpticFiber(attachmentName) and
xi.automaton.attachmentModifiers[attachmentName]
then
performanceBoost = performanceBoost + xi.automaton.attachmentModifiers[attachmentName][1][2][numLightManeuvers + 1]
end
end
return performanceBoost
end
-----------------------------------
-- Truesights
-----------------------------------
-- Return the base damage of an Automaton Ranged Attack, factoring in the AUTO_RANGED_DAMAGEP modifier.
xi.automaton.getRangedBaseDamage = function(automaton)
return automaton:getRangedDmg() * (1 + automaton:getMod(xi.mod.AUTO_RANGED_DAMAGEP) / 100)
end
-----------------------------------
-- Volt Gun
-----------------------------------
-- Calculates the potency of Volt Gun AE. TODO: Additional dINT testing.
xi.automaton.calculateVoltGunPotency = function(automaton, target)
local voltGunModifier = automaton:getMod(xi.mod.VOLT_GUN_POTENCY)
local skillLevel = math.max(automaton:getSkillLevel(xi.skill.AUTOMATON_MELEE), automaton:getSkillLevel(xi.skill.AUTOMATON_RANGED), automaton:getSkillLevel(xi.skill.AUTOMATON_MAGIC))
local basePotency = math.floor(skillLevel / 16)
local dINT = automaton:getStat(xi.mod.INT) - target:getStat(xi.mod.INT)
local power = math.min(math.max(math.floor(dINT / 2), -10), 30)
return math.max(math.floor((basePotency + power) * (1 + voltGunModifier / 100)), 1)
end
-----------------------------------
-- Animator Check
-----------------------------------
local function hasAnimatorEquipped(player)
if
player:getWeaponSubSkillType(xi.slot.RANGED) == 10 or
player:getWeaponSubSkillType(xi.slot.RANGED) == 11
then
return true
end
return false
end
-----------------------------------
-- Global functions to handle attachment modifiers.
-- NOTE: Core is 0-indexed for maneuvers, yet the table above is 1-indexed.
-- Maneuvers are updated in core before the appropriate function is called in Lua. This is why some of the functions below have offsets applied.
-----------------------------------
xi.automaton.onAttachmentEquip = function(pet, attachment)
xi.automaton.updateAttachmentModifier(pet, attachment, 0)
end
xi.automaton.onAttachmentUnequip = function(pet, attachment)
local attachmentName = attachment:getName()
local modTable = xi.automaton.attachmentModifiers[attachmentName]
for k, modList in ipairs(modTable) do
local previousMod = pet:getLocalVar(attachmentName .. k)
if modList[2][4] and modList[2][4] < 0 then
previousMod = previousMod * -1
end
if previousMod ~= 0 then
pet:delMod(modList[1], previousMod)
end
end
pet:clearLocalVarsWithPrefix(attachmentName)
end
xi.automaton.updateAttachmentModifier = function(pet, attachment, maneuvers)
local attachmentName = attachment:getName()
local modTable = xi.automaton.attachmentModifiers[attachmentName]
for attachmentModPos, modList in ipairs(modTable) do
local previousMod = pet:getLocalVar(attachmentName .. attachmentModPos)
local modValue = 0
-- Local Vars are uint. In all cases, a negative modifier carries for all number of maneuvers.
-- If the last entry is negative, restore this as a negative value.
if modList[2][4] and modList[2][4] < 0 then
previousMod = previousMod * -1
end
if modList[1] == xi.mod.REGEN then
modValue = getRegenModValue(pet, attachment, maneuvers)
elseif modList[1] == xi.mod.REFRESH then
modValue = getRefreshModValue(pet, attachment, maneuvers)
else
modValue = modList[2][maneuvers + 1]
end
if modList[3] then
modValue = math.floor(modValue * (1 + calculatePerformanceBoost(pet) / 100))
end
if modValue ~= previousMod then
if previousMod ~= 0 then
-- If the automaton was reset to a blank state (LEVEL_RESTRICTION) and the local variables were not cleared, this will under/overflow.
pet:delMod(modList[1], previousMod)
end
pet:addMod(modList[1], modValue)
pet:setLocalVar(attachmentName .. attachmentModPos, math.abs(modValue))
local master = pet:getMaster()
if master and isOpticFiber(attachmentName) then
master:updateAttachments()
end
end
end
end
-----------------------------------
-- Burden Calculation
-----------------------------------
local function getAddBurdenValue(player, maneuverInfo)
local compareMod = maneuverInfo[3]
if not compareMod then
return player:getMP() < player:getPet():getMP() and 15 or 10
else
return player:getStat(compareMod) < player:getPet():getStat(compareMod) and 20 or 15
end
end
-----------------------------------
-- Global functions to handle maneuvers
-----------------------------------
xi.automaton.onManeuverGain = function(pet, attachment, maneuvers)
xi.automaton.updateAttachmentModifier(pet, attachment, maneuvers)
end
xi.automaton.onManeuverLose = function(pet, attachment, maneuvers)
xi.automaton.updateAttachmentModifier(pet, attachment, maneuvers)
end
xi.automaton.onManeuverCheck = function(player, target, ability)
if
not player:hasStatusEffect(xi.effect.OVERLOAD) and
player:getPet() and
hasAnimatorEquipped(player)
then
return 0, 0
else
return 71, 0
end
end
xi.automaton.onUseManeuver = function(player, target, ability, action)
local maneuverInfo = maneuverList[ability:getName()]
local element = maneuverInfo[2] - 1
local burdenValue = getAddBurdenValue(player, maneuverInfo)
local overload = target:addBurden(element, burdenValue)
if
overload ~= 0 and
(player:getMod(xi.mod.PREVENT_OVERLOAD) > 0 or player:getPet():getMod(xi.mod.PREVENT_OVERLOAD) > 0) and
player:delStatusEffectSilent(xi.effect.WATER_MANEUVER)
then
overload = 0
end
action:messageID(player:getID(), xi.msg.basic.AUTO_OVERLOAD_CHANCE)
if overload ~= 0 then
target:removeAllManeuvers()
target:addStatusEffect(xi.effect.OVERLOAD, { duration = overload, origin = player })
action:messageID(player:getID(), xi.msg.basic.AUTO_OVERLOADED)
else
local puppetmasterLevel = target:getMainJob() == xi.job.PUP and target:getMainLvl() or target:getSubLvl()
local maneuverBonus = 1 + (puppetmasterLevel / 15) + target:getMod(xi.mod.MANEUVER_BONUS)
if target:getActiveManeuverCount() == 3 then
target:removeOldestManeuver()
end
local maneuverDuration = player:getPet():getLocalVar('MANEUVER_DURATION')
target:addStatusEffect(maneuverInfo[1], { power = maneuverBonus, duration = utils.clamp(maneuverDuration, 60, 300), origin = player })
end
return target:getOverloadChance(element)
end
-----------------------------------
---Retrieve model ID of the automaton for cutscene purposes
-----------------------------------
---@param player CBaseEntity
---@return integer
xi.automaton.getModelId = function(player)
local frame = player:getAutomatonFrame()
local head = player:getAutomatonHead()
local defaultModelId = automatonModels[xi.automaton.frame.HARLEQUIN][xi.automaton.head.HARLEQUIN]
if not frame or not head then
return defaultModelId
end
local frameTable = automatonModels[frame]
if not frameTable then
return defaultModelId
end
return frameTable[head] or defaultModelId
end