From f34b49e12acc1d68da97401a73ac45da0753e0b3 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 5 Aug 2026 04:02:21 +1000 Subject: [PATCH] Add support for Replica Maata's Teaching Adds support for the base attack time override and make sure it doesn't interact incorrectly with minions that use your items --- spec/System/TestMinionAttackTime_spec.lua | 79 +++++++++++++++++++++++ src/Data/ModCache.lua | 3 + src/Data/Uniques/mace.lua | 13 ++++ src/Export/Uniques/mace.lua | 12 ++++ src/Modules/CalcActiveSkill.lua | 13 ++++ src/Modules/ModParser.lua | 1 + 6 files changed, 121 insertions(+) create mode 100644 spec/System/TestMinionAttackTime_spec.lua diff --git a/spec/System/TestMinionAttackTime_spec.lua b/spec/System/TestMinionAttackTime_spec.lua new file mode 100644 index 0000000000..0f9448275e --- /dev/null +++ b/spec/System/TestMinionAttackTime_spec.lua @@ -0,0 +1,79 @@ +describe("TestMinionAttackTime", function() + local replicaMaatasTeaching = [[Replica Maata's Teaching + Karui Sceptre + Implicits: 1 + 26% increased Elemental Damage + +(30-40) to Intelligence + (8-16)% increased Attack Speed + Minions have (15-30)% increased Movement Speed + Non-Spectre Minions' Base Attack time is equal to + the Attack time of your Main Hand Weapon]] + + before_each(function() + newBuild() + end) + + teardown(function() + -- newBuild() takes care of resetting everything in setup() + end) + + local function equipReplicaMaatasTeaching() + build.itemsTab:CreateDisplayItemFromRaw(replicaMaatasTeaching) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + end + + it("uses the parent main-hand attack time and rescales ordinary minion base damage", function() + build.skillsTab:PasteSocketGroup("Summon Skeletons of Archers 20/0 1") + runCallback("OnFrame") + + local minionData = data.minions.RaisedSkeletonArcher + local baseAverageDamage = build.calcsTab.mainOutput.Minion.AverageDamage + assert.is_true(baseAverageDamage > 0) + + equipReplicaMaatasTeaching() + + local env = build.calcsTab.mainEnv + local parentAttackTime = 1 / env.player.weaponData1.AttackRate + local expectedDamageRatio = parentAttackTime / minionData.attackTime + local actualDamageRatio = build.calcsTab.mainOutput.Minion.AverageDamage / baseAverageDamage + + assert.are.near(env.player.weaponData1.AttackRate, env.minion.weaponData1.AttackRate, 10 ^ -9) + assert.are.near(expectedDamageRatio, actualDamageRatio, 0.001) + assert.is_true(build.calcsTab.mainOutput.Minion.AverageDamage < baseAverageDamage) + end) + + it("does not rescale base damage for minions whose base damage ignores attack speed", function() + local minionData = data.minions.RaisedSkeletonArcher + local originalFlag = minionData.baseDamageIgnoresAttackSpeed + minionData.baseDamageIgnoresAttackSpeed = true + + build.skillsTab:PasteSocketGroup("Summon Skeletons of Archers 20/0 1") + runCallback("OnFrame") + local baseAverageDamage = build.calcsTab.mainOutput.Minion.AverageDamage + + equipReplicaMaatasTeaching() + local overriddenAverageDamage = build.calcsTab.mainOutput.Minion.AverageDamage + local minionAttackRate = build.calcsTab.mainEnv.minion.weaponData1.AttackRate + local parentAttackRate = build.calcsTab.mainEnv.player.weaponData1.AttackRate + + minionData.baseDamageIgnoresAttackSpeed = originalFlag + + assert.are.equals(baseAverageDamage, overriddenAverageDamage) + assert.are.near(parentAttackRate, minionAttackRate, 10 ^ -9) + end) + + it("does not change spectre base attack time", function() + build.spectreList = { "Metadata/Monsters/BloodChieftain/MonkeyChiefBloodEnrage" } + build.skillsTab:PasteSocketGroup("Raise Spectre 20/0 1") + runCallback("OnFrame") + + local baseAttackRate = build.calcsTab.mainEnv.minion.weaponData1.AttackRate + local baseAverageDamage = build.calcsTab.mainOutput.Minion.AverageDamage + + equipReplicaMaatasTeaching() + + assert.are.equals(baseAttackRate, build.calcsTab.mainEnv.minion.weaponData1.AttackRate) + assert.are.equals(baseAverageDamage, build.calcsTab.mainOutput.Minion.AverageDamage) + end) +end) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index 81bd59d929..4c0f497060 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -10222,6 +10222,7 @@ c["Minions have 20% increased Movement Speed"]={{[1]={flags=0,keywordFlags=0,nam c["Minions have 20% increased maximum Life"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Life",type="INC",value=20}}}},nil} c["Minions have 20% more Maximum Life"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Life",type="MORE",value=20}}}},nil} c["Minions have 20% reduced maximum Life"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Life",type="INC",value=-20}}}},nil} +c["Minions have 23% increased Movement Speed"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="MovementSpeed",type="INC",value=23}}}},nil} c["Minions have 25% chance to gain Unholy Might for 4 seconds on Kill"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={[1]={type="Condition",var="KilledRecently"},flags=0,keywordFlags=0,name="Condition:UnholyMight",type="FLAG",value=true}}}},nil} c["Minions have 25% increased Critical Strike Chance"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="CritChance",type="INC",value=25}}}},nil} c["Minions have 25% increased Movement Speed"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="MovementSpeed",type="INC",value=25}}}},nil} @@ -10425,6 +10426,8 @@ c["Non-Damaging Elemental Ailments you inflict spread to nearby enemies within 2 c["Non-Damaging Elemental Ailments you inflict spread to nearby enemies within 2 metres Non-Damaging Elemental Ailments you inflict have 100% more Effect"]={nil,"Non-Damaging Elemental Ailments you inflict spread to nearby enemies within 2 metres Non-Damaging Elemental Ailments you inflict have 100% more Effect "} c["Non-Exerted Attacks deal no Damage"]={nil,"Non-Exerted Attacks deal no Damage "} c["Non-Instant Warcries ignore their Cooldown when Used"]={{[1]={[1]={neg=true,skillType=64,type="SkillType"},flags=0,keywordFlags=4,name="CooldownRecovery",type="OVERRIDE",value=0}},nil} +c["Non-Spectre Minions' Base Attack time is equal to"]={nil,"Non-Spectre Minions' Base Attack time is equal to "} +c["Non-Spectre Minions' Base Attack time is equal to the Attack time of your Main Hand Weapon"]={{[1]={flags=0,keywordFlags=0,name="NonSpectreMinionsUseParentMainHandAttackTime",type="FLAG",value=true}},nil} c["Non-Travel Attack Skills Repeat an additional Time"]={{[1]={[1]={neg=true,skillType=79,type="SkillType"},[2]={type="Condition",varList={[1]="averageRepeat",[2]="alwaysFinalRepeat"}},flags=0,keywordFlags=65536,name="RepeatCount",type="BASE",value=1}},nil} c["Non-Unique Jewels cause Small and Notable Passive Skills in a Large Radius to"]={nil,"Non-Unique Jewels cause Small and Notable Passive Skills in a Large Radius to "} c["Non-Unique Utility Flasks you Use apply to Linked Targets"]={{[1]={flags=0,keywordFlags=0,name="ExtraLinkEffect",type="LIST",value={mod={[1]={effectType="Global",type="GlobalEffect",unscalable=true},flags=0,keywordFlags=0,name="ParentNonUniqueFlasksAppliedToYou",type="FLAG",value=true}}}},nil} diff --git a/src/Data/Uniques/mace.lua b/src/Data/Uniques/mace.lua index fa2280793e..10f5666f4c 100644 --- a/src/Data/Uniques/mace.lua +++ b/src/Data/Uniques/mace.lua @@ -521,6 +521,19 @@ Raised Zombies Cover Enemies in Ash on Hit Raised Zombies take (15-30)% of their Maximum Life per second as Fire Damage Raised Zombies have Avatar of Fire ]],[[ +Replica Maata's Teaching +Karui Sceptre +Variant: Pre 3.25.0 +Variant: Current +Requires Level 56, 96 Str, 96 Int +Implicits: 1 +26% increased Elemental Damage ++(30-40) to Intelligence +(8-16)% increased Attack Speed +Minions have (15-30)% increased Movement Speed +Non-Spectre Minions' Base Attack time is equal to +the Attack time of your Main Hand Weapon +]],[[ Maata's Teaching Karui Sceptre Variant: Pre 3.25.0 diff --git a/src/Export/Uniques/mace.lua b/src/Export/Uniques/mace.lua index 873507c726..290a3921ef 100644 --- a/src/Export/Uniques/mace.lua +++ b/src/Export/Uniques/mace.lua @@ -513,6 +513,18 @@ ZombiesCoverInAshOnHitUnique__1 ZombiesHaveAvatarOfFireUnique__1 ZombiesTakeFireDamagePerSecondUnique__1_ ]],[[ +Replica Maata's Teaching +Karui Sceptre +Variant: Pre 3.25.0 +Variant: Current +Requires Level 56, 96 Str, 96 Int +Implicits: 1 +ElementalDamagePercentImplicitSceptreNew17 +IntelligenceUnique__33 +LocalIncreasedAttackSpeedUnique__46 +MinionMovementSpeedUnique_2 +MinionsUseMainHandBaseAttackDurationUnique__1 +]],[[ Maata's Teaching Karui Sceptre Variant: Pre 3.25.0 diff --git a/src/Modules/CalcActiveSkill.lua b/src/Modules/CalcActiveSkill.lua index 543bc6c616..ffb3031790 100644 --- a/src/Modules/CalcActiveSkill.lua +++ b/src/Modules/CalcActiveSkill.lua @@ -708,6 +708,13 @@ function calcs.buildActiveSkillModList(env, activeSkill) minion.lifeTable = (minion.minionData.lifeScaling == "AltLife1" and env.data.monsterLifeTable2) or (minion.minionData.lifeScaling == "AltLife2" and env.data.monsterLifeTable3) or (isSpectre and env.data.monsterLifeTable) or env.data.monsterAllyLifeTable end local attackTime = minion.minionData.attackTime + local useParentMainHandAttackTime = not isSpectre + and skillModList:Flag(activeSkill.skillCfg, "NonSpectreMinionsUseParentMainHandAttackTime") + and env.player.weaponData1.AttackRate + and env.player.weaponData1.AttackRate > 0 + if useParentMainHandAttackTime then + attackTime = 1 / env.player.weaponData1.AttackRate + end local damageTable = (isSpectre or minion.minionData.hostile) and env.data.monsterDamageTable or env.data.monsterAllyDamageTable local damage = damageTable[minion.level] * minion.minionData.damage if not minion.minionData.baseDamageIgnoresAttackSpeed then -- minions with this flag do not factor attack time into their base damage @@ -766,6 +773,12 @@ function calcs.buildActiveSkillModList(env, activeSkill) end end end + if useParentMainHandAttackTime then + -- The weapon data may be shared with the player, an item set, or another actor. + -- Copy it before replacing only the minion's base attack rate. + minion.weaponData1 = copyTable(minion.weaponData1) + minion.weaponData1.AttackRate = 1 / attackTime + end end elseif activeEffect.srcInstance and not (activeEffect.gemData and activeEffect.gemData.secondaryGrantedEffect) then activeEffect.srcInstance.skillMinionCalcs = nil diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index ca197b1008..66af4c4675 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -4466,6 +4466,7 @@ local specialModList = { ["each summoned phantasm grants you phantasmal might"] = { flag("Condition:PhantasmalMight") }, ["minions have (%d+)%% increased critical strike chance per maximum power charge you have"] = function(num) return { mod("MinionModifier", "LIST", { mod = mod("CritChance", "INC", num, { type = "Multiplier", actor = "parent", var = "PowerChargeMax" }) }) } end, ["minions' base attack critical strike chance is equal to the critical strike chance of your main hand weapon"] = { mod("MinionModifier", "LIST", { mod = flag("AttackCritIsEqualToParentMainHand", nil, ModFlag.Attack) }) }, + ["non%-spectre minions' base attack time is equal to the attack time of your main hand weapon"] = { flag("NonSpectreMinionsUseParentMainHandAttackTime") }, ["minions can hear the whispers for 5 seconds after they deal a critical strike"] = { mod("ExtraSkillMod", "LIST", { mod = mod("Speed", "INC", 50, { type = "GlobalEffect", effectType = "Global", unscalable = true }, { type = "Condition", neg = true, var = "NeverCrit" } ) }), mod("ExtraSkillMod", "LIST", { mod = mod("Damage", "INC", 50, { type = "GlobalEffect", effectType = "Global", unscalable = true }, { type = "Condition", neg = true, var = "NeverCrit" } ) }),