diff --git a/scripts/globals/automaton.lua b/scripts/globals/automaton.lua index 874db106ce2..840c28b33db 100644 --- a/scripts/globals/automaton.lua +++ b/scripts/globals/automaton.lua @@ -112,14 +112,10 @@ local attachmentModifiers = ['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.HPP, { 5, 5, 5, 5 }, false }, - { xi.mod.REGEN, { nil, nil, nil, nil }, true }, }, - ['auto-repair_kit_ii'] = { { xi.mod.HPP, { 10, 10, 10, 10 }, false }, - { xi.mod.REGEN, { nil, nil, nil, nil }, true }, }, - ['auto-repair_kit_iii'] = { { xi.mod.HPP, { 15, 15, 15, 15 }, false }, - { xi.mod.REGEN, { nil, nil, nil, nil }, true }, }, - ['auto-repair_kit_iv'] = { { xi.mod.HPP, { 20, 20, 20, 20 }, false }, - { xi.mod.REGEN, { nil, nil, nil, nil }, 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 }, @@ -162,14 +158,10 @@ local attachmentModifiers = ['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.MPP, { 5, 5, 5, 5 }, false }, - { xi.mod.REFRESH, { nil, nil, nil, nil }, true }, }, - ['mana_tank_ii'] = { { xi.mod.MPP, { 10, 10, 10, 10 }, false }, - { xi.mod.REFRESH, { nil, nil, nil, nil }, true }, }, - ['mana_tank_iii'] = { { xi.mod.MPP, { 15, 15, 15, 15 }, false }, - { xi.mod.REFRESH, { nil, nil, nil, nil }, true }, }, - ['mana_tank_iv'] = { { xi.mod.MPP, { 20, 20, 20, 20 }, false }, - { xi.mod.REFRESH, { nil, nil, nil, nil }, 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 }, }, @@ -222,31 +214,64 @@ local attachmentModifiers = { xi.mod.ENSPELL_CHANCE, { 20, 35, 50, 65 }, false }, }, } --- Auto Repair Kits and Mana Tanks use a formula based on Max HP/MP in the form of --- + . This table represents those two variables. -local regenRefreshFormulas = +-- The HP Boost from Repair Kit I/II/III/IV is calculated on a per frame basis based on a divsor. Example : 4 (Repair Kit IV) / 20 (Harlequin) = .2 or a 20% HP Boost. +xi.automaton.repairKit = { - -- Attachment BaseValue Multiplier (%) - ['auto-repair_kit'] = { { 0, 1, 2, 3 }, { 0, 0.125, 0.225, 0.375 } }, - ['auto-repair_kit_ii'] = { { 0, 3, 6, 9 }, { 0, 0.6, 1.2, 1.8 } }, - ['auto-repair_kit_iii'] = { { 0, 9, 12, 15 }, { 0, 1.8, 2.4, 3.0 } }, - ['auto-repair_kit_iv'] = { { 0, 15, 18, 21 }, { 0, 3.0, 3.6, 4.2 } }, - ['mana_tank'] = { { 0, 1, 2, 3 }, { 0, 0.2, 0.4, 0.6 } }, - ['mana_tank_ii'] = { { 0, 2, 3, 4 }, { 0, 0.4, 0.6, 0.8 } }, - ['mana_tank_iii'] = { { 0, 3, 4, 5 }, { 0, 0.6, 0.8, 1.0 } }, - ['mana_tank_iv'] = { { 0, 4, 5, 6 }, { 0, 0.8, 1.0, 1.2 } }, + 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, attachmentName, numManeuvers) +-- The MP Boost from Mana Tank I/II/III/IV is calculated on a per frame basis based on a divisor. Example : 4 (Mana Tank IV) / 20 (Harlequin) = .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 getRegenModValue(pet, attachment, numManeuvers) local petMaxHP = pet:getMaxHP() + local repairKitData = xi.automaton.repairKit.data[attachment:getName()] + + if repairKitData then + return repairKitData.regenBase[numManeuvers + 1] + petMaxHP * (repairKitData.regenMultiplier[numManeuvers + 1] / 100) + end - return regenRefreshFormulas[attachmentName][1][numManeuvers + 1] + petMaxHP * (regenRefreshFormulas[attachmentName][2][numManeuvers + 1] / 100) + return 0 end -local function getRefreshModValue(pet, attachmentName, numManeuvers) +local function getRefreshModValue(pet, attachment, numManeuvers) local petMaxMP = pet:getMaxMP() + local manaTankData = xi.automaton.manaTank.data[attachment:getName()] + + if manaTankData then + return manaTankData.refreshBase[numManeuvers + 1] + petMaxMP * (manaTankData.refreshMultiplier[numManeuvers + 1] / 100) + end - return regenRefreshFormulas[attachmentName][1][numManeuvers + 1] + petMaxMP * (regenRefreshFormulas[attachmentName][2][numManeuvers + 1] / 100) + return 0 end local function isOpticFiber(attachmentName) @@ -351,9 +376,9 @@ xi.automaton.updateAttachmentModifier = function(pet, attachment, maneuvers) -- Get base modifier value if modList[1] == xi.mod.REGEN then - modValue = getRegenModValue(pet, attachmentName, maneuvers) + modValue = getRegenModValue(pet, attachment, maneuvers) elseif modList[1] == xi.mod.REFRESH then - modValue = getRefreshModValue(pet, attachmentName, maneuvers) + modValue = getRefreshModValue(pet, attachment, maneuvers) else modValue = modList[2][maneuvers + 1] end diff --git a/scripts/globals/pets/automaton.lua b/scripts/globals/pets/automaton.lua index 1e44a12d480..b1b8008443a 100644 --- a/scripts/globals/pets/automaton.lua +++ b/scripts/globals/pets/automaton.lua @@ -448,7 +448,7 @@ xi.pets.automaton.skillCaps = [xi.automaton.frame.STORMWAKER] = { - [xi.skill.AUTOMATON_MELEE] = xi.skillRank.C, + [xi.skill.AUTOMATON_MELEE] = xi.skillRank.C_PLUS, [xi.skill.AUTOMATON_MAGIC] = xi.skillRank.B_PLUS, }, }, diff --git a/src/map/enums/automaton.h b/src/map/enums/automaton.h index 6de3096fa4b..73a2996db18 100644 --- a/src/map/enums/automaton.h +++ b/src/map/enums/automaton.h @@ -43,6 +43,14 @@ enum class AutomatonHead : uint8 enum class AutomatonAttachment : uint8 { - OpticFiber = 198, - OpticFiberII = 206, + AutoRepairKit = 193, + AutoRepairKitII = 196, + OpticFiber = 198, + AutoRepairKitIII = 202, + AutoRepairKitIV = 205, + OpticFiberII = 206, + ManaTank = 225, + ManaTankII = 228, + ManaTankIII = 233, + ManaTankIV = 235, }; diff --git a/src/map/packets/c2s/0x102_extended_job.cpp b/src/map/packets/c2s/0x102_extended_job.cpp index e11b40d9b5b..5e324762bdf 100644 --- a/src/map/packets/c2s/0x102_extended_job.cpp +++ b/src/map/packets/c2s/0x102_extended_job.cpp @@ -223,12 +223,10 @@ void GP_CLI_COMMAND_EXTENDED_JOB::process(MapSession* PSession, CCharEntity* PCh if (pupData.Slots[static_cast(AutomatonSlot::Head)] != 0) { puppetutils::setHead(PChar, static_cast(pupData.Slots[static_cast(AutomatonSlot::Head)])); - petutils::CalculateAutomatonStats(PChar, PChar->PPet); } else if (pupData.Slots[static_cast(AutomatonSlot::Frame)] != 0) { puppetutils::setFrame(PChar, static_cast(pupData.Slots[static_cast(AutomatonSlot::Frame)])); - petutils::CalculateAutomatonStats(PChar, PChar->PPet); } else { @@ -243,6 +241,7 @@ void GP_CLI_COMMAND_EXTENDED_JOB::process(MapSession* PSession, CCharEntity* PCh } } + petutils::CalculateAutomatonStats(PChar, PChar->PPet); charutils::SendExtendedJobPackets(PChar); puppetutils::SaveAutomaton(PChar); } diff --git a/src/map/utils/petutils.cpp b/src/map/utils/petutils.cpp index 276e52caf0a..a4a1e0a9a1f 100644 --- a/src/map/utils/petutils.cpp +++ b/src/map/utils/petutils.cpp @@ -490,6 +490,163 @@ void LoadAutomatonStats(CCharEntity* PMaster, CPetEntity* PPet, Pet_t* petStats, tempHealth.maxmp = getLevelStat("maxMP"); tempHealth.mp = tempHealth.maxmp; + // Handle Auto-Repair Kits, HP boost provided is shown in the automaton equipment menu, which means it needs to be calculated here. + const bool hasAutoRepairKit = PMaster->hasAutomatonAttachment(static_cast(AutomatonAttachment::AutoRepairKit)); + const bool hasAutoRepairKitII = PMaster->hasAutomatonAttachment(static_cast(AutomatonAttachment::AutoRepairKitII)); + const bool hasAutoRepairKitIII = PMaster->hasAutomatonAttachment(static_cast(AutomatonAttachment::AutoRepairKitIII)); + const bool hasAutoRepairKitIV = PMaster->hasAutomatonAttachment(static_cast(AutomatonAttachment::AutoRepairKitIV)); + + if (hasAutoRepairKit || hasAutoRepairKitII || hasAutoRepairKitIII || hasAutoRepairKitIV) + { + const auto maybeRepairKit = lua["xi"]["automaton"]["repairKit"].get>(); + if (!maybeRepairKit) + { + ShowError("LoadAutomatonStats: Missing xi.automaton.repairKit"); + return; + } + + const auto& repairKit = *maybeRepairKit; + + const auto maybeRepairKitData = repairKit["data"].get>(); + if (!maybeRepairKitData) + { + ShowError("LoadAutomatonStats: Missing xi.automaton.repairKit.data"); + return; + } + + const auto& repairKitData = *maybeRepairKitData; + + uint8 repairKitTier = 0; + + for (const auto& repairKitDataEntry : repairKitData) + { + if (!repairKitDataEntry.second.is()) + { + ShowError("LoadAutomatonStats: Invalid xi.automaton.repairKit.data entry"); + return; + } + + const auto repairKitEntry = repairKitDataEntry.second.as(); + + const auto maybeId = repairKitEntry["id"].get>(); + if (!maybeId) + { + ShowError("LoadAutomatonStats: Missing id in xi.automaton.repairKit.data"); + return; + } + + const auto maybeHPBoost = repairKitEntry["hpBoost"].get>(); + if (!maybeHPBoost) + { + ShowErrorFmt("LoadAutomatonStats: Missing hpBoost for attachment {} in xi.automaton.repairKit.data", *maybeId); + return; + } + + if (PMaster->hasAutomatonAttachment(*maybeId)) + { + repairKitTier += *maybeHPBoost; + } + } + + const auto maybeFrameDivisors = repairKit["frameDivisors"].get>(); + if (!maybeFrameDivisors) + { + ShowError("LoadAutomatonStats: Missing xi.automaton.repairKit.frameDivisors"); + return; + } + + const auto& frameDivisors = *maybeFrameDivisors; + + const auto maybeDivisor = frameDivisors[frame].get>(); + if (!maybeDivisor || *maybeDivisor == 0) + { + ShowErrorFmt("LoadAutomatonStats: Missing Auto-Repair Kit divisor for frame {}", static_cast(frame)); + return; + } + + tempHealth.maxhp += tempHealth.maxhp * repairKitTier / *maybeDivisor; + tempHealth.hp = tempHealth.maxhp; + } + + // Handle Mana Tanks, MP boost provided is shown in the automaton equipment menu, which means it needs to be calculated here. + const bool hasManaTank = PMaster->hasAutomatonAttachment(static_cast(AutomatonAttachment::ManaTank)); + const bool hasManaTankII = PMaster->hasAutomatonAttachment(static_cast(AutomatonAttachment::ManaTankII)); + const bool hasManaTankIII = PMaster->hasAutomatonAttachment(static_cast(AutomatonAttachment::ManaTankIII)); + const bool hasManaTankIV = PMaster->hasAutomatonAttachment(static_cast(AutomatonAttachment::ManaTankIV)); + + // Only calculate if the automaton has a mana pool to boost, even if the attachment is equipped. + if ((hasManaTank || hasManaTankII || hasManaTankIII || hasManaTankIV) && tempHealth.maxmp > 0) + { + const auto maybeManaTank = lua["xi"]["automaton"]["manaTank"].get>(); + if (!maybeManaTank) + { + ShowError("LoadAutomatonStats: Missing xi.automaton.manaTank"); + return; + } + + const auto& manaTank = *maybeManaTank; + + const auto maybeManaTankData = manaTank["data"].get>(); + if (!maybeManaTankData) + { + ShowError("LoadAutomatonStats: Missing xi.automaton.manaTank.data"); + return; + } + + const auto& manaTankData = *maybeManaTankData; + + uint8 manaTankTier = 0; + + for (const auto& manaTankDataEntry : manaTankData) + { + if (!manaTankDataEntry.second.is()) + { + ShowError("LoadAutomatonStats: Invalid xi.automaton.manaTank.data entry"); + return; + } + + const auto manaTankEntry = manaTankDataEntry.second.as(); + + const auto maybeId = manaTankEntry["id"].get>(); + if (!maybeId) + { + ShowError("LoadAutomatonStats: Missing id in xi.automaton.manaTank.data"); + return; + } + + const auto maybeMPBoost = manaTankEntry["mpBoost"].get>(); + if (!maybeMPBoost) + { + ShowErrorFmt("LoadAutomatonStats: Missing mpBoost for attachment {} in xi.automaton.manaTank.data", *maybeId); + return; + } + + if (PMaster->hasAutomatonAttachment(*maybeId)) + { + manaTankTier += *maybeMPBoost; + } + } + + const auto maybeFrameDivisors = manaTank["frameDivisors"].get>(); + if (!maybeFrameDivisors) + { + ShowError("LoadAutomatonStats: Missing xi.automaton.manaTank.frameDivisors"); + return; + } + + const auto& frameDivisors = *maybeFrameDivisors; + + const auto maybeDivisor = frameDivisors[frame].get>(); + if (!maybeDivisor || *maybeDivisor == 0) + { + ShowErrorFmt("LoadAutomatonStats: Missing Mana Tank divisor for frame {}", static_cast(frame)); + return; + } + + tempHealth.maxmp += tempHealth.maxmp * manaTankTier / *maybeDivisor; + tempHealth.mp = tempHealth.maxmp; + } + tempStats = { getLevelStat("STR"), getLevelStat("DEX"),