From 41978b47e1991699cc20183337fa460fe351b242 Mon Sep 17 00:00:00 2001 From: Skold <113406182+Skold177@users.noreply.github.com> Date: Sat, 13 Jun 2026 21:47:04 -0400 Subject: [PATCH] Implement Retail Accurate Automaton Skill Caps Co-authored-by: Xaver-DaRed --- scripts/globals/pets/automaton.lua | 73 +++++++++++++++++-- src/map/utils/puppetutils.cpp | 110 ++++++++++++++--------------- 2 files changed, 119 insertions(+), 64 deletions(-) diff --git a/scripts/globals/pets/automaton.lua b/scripts/globals/pets/automaton.lua index 9a28782bc5a..1e44a12d480 100644 --- a/scripts/globals/pets/automaton.lua +++ b/scripts/globals/pets/automaton.lua @@ -423,6 +423,67 @@ xi.pets.automaton.frameStats = }, } +xi.pets.automaton.skillCaps = +{ + -- Base frame ranks, an unlisted skill means no skill unless combined with a head that grants it. + frames = + { + [xi.automaton.frame.HARLEQUIN] = + { + [xi.skill.AUTOMATON_MELEE ] = xi.skillRank.B_MINUS, + [xi.skill.AUTOMATON_RANGED] = xi.skillRank.B_MINUS, + [xi.skill.AUTOMATON_MAGIC ] = xi.skillRank.B_MINUS, + }, + + [xi.automaton.frame.VALOREDGE] = + { + [xi.skill.AUTOMATON_MELEE] = xi.skillRank.B_PLUS, + }, + + [xi.automaton.frame.SHARPSHOT] = + { + [xi.skill.AUTOMATON_MELEE ] = xi.skillRank.C_PLUS, + [xi.skill.AUTOMATON_RANGED] = xi.skillRank.B_PLUS, + }, + + [xi.automaton.frame.STORMWAKER] = + { + [xi.skill.AUTOMATON_MELEE] = xi.skillRank.C, + [xi.skill.AUTOMATON_MAGIC] = xi.skillRank.B_PLUS, + }, + }, + + -- Head Bonuses. For skill rank ENUMs, lower is better. Example : xi.skillRank.B_PLUS(3) -2 = xi.skillRank.A_PLUS(1) + -- If this bonus is applied to a frame that does not have that skill innately, becomes xi.skillRank.F. + heads = + { + [xi.automaton.head.VALOREDGE] = + { + [xi.skill.AUTOMATON_MELEE] = -2, + }, + + [xi.automaton.head.SHARPSHOT] = + { + [xi.skill.AUTOMATON_RANGED] = -2, + }, + + [xi.automaton.head.STORMWAKER] = + { + [xi.skill.AUTOMATON_MAGIC] = -2, + }, + + [xi.automaton.head.SOULSOOTHER] = + { + [xi.skill.AUTOMATON_MAGIC] = -2, + }, + + [xi.automaton.head.SPIRITREAVER] = + { + [xi.skill.AUTOMATON_MAGIC] = -2, + }, + }, +} + xi.pets.automaton.frameMods = { ----------------------------------- @@ -432,7 +493,7 @@ xi.pets.automaton.frameMods = { mods = { - { xi.mod.DMG, -625 }, + { xi.mod.DMG, -625 }, }, }, @@ -460,9 +521,9 @@ xi.pets.automaton.frameMods = { mods = { - { xi.mod.PIERCE_SDT, 8750 }, - { xi.mod.DMGBREATH, -1250 }, - { xi.mod.DMGMAGIC, -1250 }, + { xi.mod.PIERCE_SDT, 8750 }, + { xi.mod.DMGBREATH, -1250 }, + { xi.mod.DMGMAGIC, -1250 }, }, }, @@ -473,8 +534,8 @@ xi.pets.automaton.frameMods = { mods = { - { xi.mod.DMGBREATH, -2500 }, - { xi.mod.DMGMAGIC, -2500 }, + { xi.mod.DMGBREATH, -2500 }, + { xi.mod.DMGMAGIC, -2500 }, }, }, } diff --git a/src/map/utils/puppetutils.cpp b/src/map/utils/puppetutils.cpp index 325e7e52f42..5945b14dfd6 100644 --- a/src/map/utils/puppetutils.cpp +++ b/src/map/utils/puppetutils.cpp @@ -466,76 +466,70 @@ auto getSkillCap(const CCharEntity* PChar, const SKILLTYPE skill, const uint8 le return 0; } - int8 rank = 0; if (skill < SKILL_AUTOMATON_MELEE || skill > SKILL_AUTOMATON_MAGIC) { return 0; } - switch (PChar->getAutomatonFrame()) + + const auto frame = static_cast(PChar->getAutomatonFrame()); + const auto head = static_cast(PChar->getAutomatonHead()); + const auto skillKey = static_cast(skill); + + const auto maybeSkillCaps = lua["xi"]["pets"]["automaton"]["skillCaps"].get>(); + if (!maybeSkillCaps) { - default: // case Harlequin: - rank = 5; - break; - case AutomatonFrame::Valoredge: - if (skill == SKILL_AUTOMATON_MELEE) - { - rank = 2; - } - break; - case AutomatonFrame::Sharpshot: - if (skill == SKILL_AUTOMATON_MELEE) - { - rank = 6; - } - else if (skill == SKILL_AUTOMATON_RANGED) - { - rank = 3; - } - break; - case AutomatonFrame::Stormwaker: - if (skill == SKILL_AUTOMATON_MELEE) - { - rank = 7; - } - else if (skill == SKILL_AUTOMATON_MAGIC) - { - rank = 3; - } - break; + ShowError("puppetutils::getSkillCap() - Missing xi.pets.automaton.skillCaps"); + return 0; } - switch (PChar->getAutomatonHead()) + const auto& skillCaps = *maybeSkillCaps; + + const auto maybeFrames = skillCaps["frames"].get>(); + if (!maybeFrames) { - case AutomatonHead::Valoredge: - if (skill == SKILL_AUTOMATON_MELEE) - { - rank -= 1; - } - break; - case AutomatonHead::Sharpshot: - if (skill == SKILL_AUTOMATON_RANGED) - { - rank -= 1; - } - break; - case AutomatonHead::Stormwaker: - if (skill == SKILL_AUTOMATON_MELEE || skill == SKILL_AUTOMATON_MAGIC) - { - rank -= 1; - } - break; - case AutomatonHead::Soulsoother: - case AutomatonHead::Spiritreaver: - if (skill == SKILL_AUTOMATON_MAGIC) + ShowError("puppetutils::getSkillCap() - Missing xi.pets.automaton.skillCaps.frames"); + return 0; + } + + const auto& frames = *maybeFrames; + + const auto maybeFrameCaps = frames[frame].get>(); + if (!maybeFrameCaps) + { + ShowErrorFmt("puppetutils::getSkillCap() - Missing automaton skill caps for frame {}", static_cast(frame)); + return 0; + } + + const auto& frameCaps = *maybeFrameCaps; + + // Grab the skill cap for the frame, then apply the bonus from the head if applicable. + int8 rank = 0; + + const auto maybeFrameRank = frameCaps[skillKey].get>(); + if (maybeFrameRank) + { + rank = *maybeFrameRank; + } + + const auto maybeHeads = skillCaps["heads"].get>(); + if (maybeHeads) + { + const auto& heads = *maybeHeads; + + const auto maybeHeadCaps = heads[head].get>(); + if (maybeHeadCaps) + { + const auto& headCaps = *maybeHeadCaps; + + const auto maybeHeadRank = headCaps[skillKey].get>(); + if (maybeHeadRank) { - rank -= 2; + rank += *maybeHeadRank; } - break; - default: - break; + } } - // only happens if a head gives bonus to a rank of 0 - making it G or F rank + // Handle automaton frames with no native skill being combined with heads that give a bonus to that rank. if (rank < 0) { rank = 13 + rank;