From be44d7ef518e7b37b437d311a20d9707118b0fa1 Mon Sep 17 00:00:00 2001 From: Can Uysal Date: Fri, 14 Aug 2026 23:52:43 +0300 Subject: [PATCH] Fix spectre buffs only applying from the selected spectre Raise Spectre now instantiates every spectre in the spectre list so their buff, aura and curse skills are applied, instead of only the ones from the spectre currently selected on the skill. The selected spectre is still the only one used for minion DPS and defence calculations. --- spec/System/TestSpectreBuffs_spec.lua | 40 ++++++++++++++ src/Modules/CalcActiveSkill.lua | 56 ++++++++++++++----- src/Modules/CalcPerform.lua | 79 +++++++++++++++++++++++---- 3 files changed, 149 insertions(+), 26 deletions(-) create mode 100644 spec/System/TestSpectreBuffs_spec.lua diff --git a/spec/System/TestSpectreBuffs_spec.lua b/spec/System/TestSpectreBuffs_spec.lua new file mode 100644 index 0000000000..f75a3d5255 --- /dev/null +++ b/spec/System/TestSpectreBuffs_spec.lua @@ -0,0 +1,40 @@ +describe("TestSpectreBuffs", function() + before_each(function() + newBuild() + end) + + teardown(function() + -- newBuild() takes care of resetting everything in setup() + end) + + it("applies buff skills from every spectre in the spectre list", function() + build.spectreList = { + "Metadata/Monsters/LeagueAzmeri/SpecialCorpses/DemonBossHigh", -- Perfect Blood Demon + "Metadata/Monsters/LeagueAzmeri/SpecialCorpses/TigerHigh", -- Perfect Forest Tiger (Haste) + "Metadata/Monsters/LeagueAzmeri/SpecialCorpses/TurtleHigh", -- Perfect Guardian Turtle (Determination) + } + build.skillsTab:PasteSocketGroup("Raise Spectre 20/0 1") + build.calcsTab:BuildOutput() + + local env = build.calcsTab.mainEnv + -- The main minion is the Blood Demon, so the Tiger's and Turtle's auras only apply if + -- the other spectres in the list are processed as well + assert.is_true(env.modDB.conditions["AffectedByHaste"] == true) + assert.is_true(env.modDB.conditions["AffectedByDetermination"] == true) + assert.is_true(env.minion.modDB.conditions["AffectedByHaste"] == true) + assert.is_true(env.minion.modDB.conditions["AffectedByDetermination"] == true) + -- The Tiger and Turtle are beasts, so the condition applies even with the Demon selected + assert.is_true(env.modDB.conditions["HaveBeastSpectre"] == true) + end) + + it("applies buff skills from the selected spectre only once", function() + build.spectreList = { + "Metadata/Monsters/LeagueAzmeri/SpecialCorpses/TurtleHigh", -- Perfect Guardian Turtle (Determination) + } + build.skillsTab:PasteSocketGroup("Raise Spectre 20/0 1") + build.calcsTab:BuildOutput() + + local env = build.calcsTab.mainEnv + assert.is_true(env.modDB.conditions["AffectedByDetermination"] == true) + end) +end) diff --git a/src/Modules/CalcActiveSkill.lua b/src/Modules/CalcActiveSkill.lua index 4821e7a2da..4f51240eca 100644 --- a/src/Modules/CalcActiveSkill.lua +++ b/src/Modules/CalcActiveSkill.lua @@ -681,23 +681,17 @@ function calcs.buildActiveSkillModList(env, activeSkill) end activeSkill.minionList = minionList if minionList[1] and not activeSkill.actor.minionData then - local minionType - if env.mode == "CALCS" and activeSkill == env.player.mainSkill then - local index = isValueInArray(minionList, activeEffect.srcInstance.skillMinionCalcs) or 1 - minionType = minionList[index] - activeEffect.srcInstance.skillMinionCalcs = minionType - else - local index = isValueInArray(minionList, activeEffect.srcInstance.skillMinion) or 1 - minionType = minionList[index] - activeEffect.srcInstance.skillMinion = minionType - end - if minionType then + local function instantiateMinion(minionType) + if not minionType then + return + end local minion = { } - activeSkill.minion = minion - skillFlags.haveMinion = true minion.type = minionType minion.minionData = env.data.minions[minionType] - minion.hostile = minion.minionData and minion.minionData.hostile or false + if not minion.minionData then + return + end + minion.hostile = minion.minionData.hostile or false if minion.hostile then minion.parent = env.enemy minion.enemy = env.player @@ -782,6 +776,40 @@ function calcs.buildActiveSkillModList(env, activeSkill) minion.weaponData1 = copyTable(minion.weaponData1) minion.weaponData1.AttackRate = env.player.weaponData1.AttackRate end + return minion + end + local minionType + if env.mode == "CALCS" and activeSkill == env.player.mainSkill then + local index = isValueInArray(minionList, activeEffect.srcInstance.skillMinionCalcs) or 1 + minionType = minionList[index] + activeEffect.srcInstance.skillMinionCalcs = minionType + else + local index = isValueInArray(minionList, activeEffect.srcInstance.skillMinion) or 1 + minionType = minionList[index] + activeEffect.srcInstance.skillMinion = minionType + end + activeSkill.spectreListMinions = nil + if minionType then + local minion = instantiateMinion(minionType) + if minion then + activeSkill.minion = minion + skillFlags.haveMinion = true + if isSpectre then + activeSkill.spectreListMinions = { minion } + end + end + end + -- Instantiate the other spectres in the spectre list so their buff skills can be applied + if isSpectre then + activeSkill.spectreListMinions = activeSkill.spectreListMinions or { } + for _, spectreType in ipairs(minionList) do + if not activeSkill.minion or spectreType ~= activeSkill.minion.type then + local extraMinion = instantiateMinion(spectreType) + if extraMinion then + t_insert(activeSkill.spectreListMinions, extraMinion) + end + end + end end elseif activeEffect.srcInstance and not (activeEffect.gemData and activeEffect.gemData.secondaryGrantedEffect) then activeEffect.srcInstance.skillMinionCalcs = nil diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 95e90c3825..0bef2a4993 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1259,13 +1259,52 @@ function calcs.perform(env, skipEHP) modLib.mergeKeystones(env, env.modDB) -- Build minion skills + local function initMinionSkills(activeSkill, minion, isPrimary) + minion.modDB = new("ModDB"):ModDB() + minion.modDB.actor = minion + if isPrimary then + calcs.createMinionSkills(env, activeSkill) + activeSkill.skillPartName = activeSkill.minion.mainSkill.activeEffect.grantedEffect.name + return + end + -- Build the skills of an extra spectre from the spectre list on a temporary copy of the + -- active skill, so the main minion and the stored skill selection are left untouched + local tempSkill = { + activeEffect = activeSkill.activeEffect, + effectList = activeSkill.effectList, + supportList = activeSkill.supportList, + actor = activeSkill.actor, + socketGroup = activeSkill.socketGroup, + baseSkillModList = activeSkill.baseSkillModList, + skillModList = activeSkill.skillModList, + skillCfg = activeSkill.skillCfg, + skillData = activeSkill.skillData, + skillFlags = activeSkill.skillFlags, + skillTypes = activeSkill.skillTypes, + minionSkillTypes = activeSkill.minionSkillTypes, + buffList = activeSkill.buffList, + minion = minion, + } + local srcInstance = activeSkill.activeEffect and activeSkill.activeEffect.srcInstance + local savedMinionSkill = srcInstance and srcInstance.skillMinionSkill + local savedMinionSkillCalcs = srcInstance and srcInstance.skillMinionSkillCalcs + calcs.createMinionSkills(env, tempSkill) + if srcInstance then + srcInstance.skillMinionSkill = savedMinionSkill + srcInstance.skillMinionSkillCalcs = savedMinionSkillCalcs + end + end for _, activeSkill in ipairs(env.player.activeSkillList) do activeSkill.skillModList = new("ModList"):ModList(activeSkill.baseSkillModList) if activeSkill.minion then - activeSkill.minion.modDB = new("ModDB"):ModDB() - activeSkill.minion.modDB.actor = activeSkill.minion - calcs.createMinionSkills(env, activeSkill) - activeSkill.skillPartName = activeSkill.minion.mainSkill.activeEffect.grantedEffect.name + initMinionSkills(activeSkill, activeSkill.minion, true) + end + if activeSkill.spectreListMinions then + for _, spectreMinion in ipairs(activeSkill.spectreListMinions) do + if spectreMinion ~= activeSkill.minion then + initMinionSkills(activeSkill, spectreMinion, false) + end + end end end @@ -2161,7 +2200,13 @@ function calcs.perform(env, skipEHP) local skillId = activeSkill.activeEffect.grantedEffect.id if skillId and skillId:match("^RaiseSpectre") then hasActiveSpectreSkill = true - if activeSkill.minion and activeSkill.minion.type then + if activeSkill.spectreListMinions then + for _, spectreMinion in ipairs(activeSkill.spectreListMinions) do + if spectreMinion.type then + t_insert(activeSpectreList, spectreMinion.type) + end + end + elseif activeSkill.minion and activeSkill.minion.type then t_insert(activeSpectreList, activeSkill.minion.type) end end @@ -2719,9 +2764,19 @@ function calcs.perform(env, skipEHP) end end end + local minionsToProcess = { } if activeSkill.minion and activeSkill.minion.activeSkillList then - local castingMinion = activeSkill.minion - for _, activeMinionSkill in ipairs(activeSkill.minion.activeSkillList) do + t_insert(minionsToProcess, activeSkill.minion) + end + if activeSkill.spectreListMinions then + for _, spectreMinion in ipairs(activeSkill.spectreListMinions) do + if spectreMinion ~= activeSkill.minion and spectreMinion.activeSkillList then + t_insert(minionsToProcess, spectreMinion) + end + end + end + for _, castingMinion in ipairs(minionsToProcess) do + for _, activeMinionSkill in ipairs(castingMinion.activeSkillList) do local function setSpectreSource(modList, sourceSkill) if activeSkill.skillFlags.spectre then local source = "Spectre:" @@ -2767,7 +2822,7 @@ function calcs.perform(env, skipEHP) if envMinionCheck then env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true else - activeSkill.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true + castingMinion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true end local srcList = new("ModList"):ModList() local inc = modStore:Sum("INC", skillCfg, "BuffEffect", (env.minion == castingMinion) and "BuffEffectOnSelf" or nil) @@ -2784,7 +2839,7 @@ function calcs.perform(env, skipEHP) if env.mode_buffs and activeMinionSkill.skillData.enable then -- Check for extra modifiers to apply to aura skills local extraAuraModList = { } - for _, value in ipairs(activeSkill.minion.modDB:List(skillCfg, "ExtraAuraEffect")) do + for _, value in ipairs(castingMinion.modDB:List(skillCfg, "ExtraAuraEffect")) do local add = true for _, mod in ipairs(extraAuraModList) do if modLib.compareModParams(mod, value.mod) then @@ -2797,7 +2852,7 @@ function calcs.perform(env, skipEHP) t_insert(extraAuraModList, copyTable(value.mod, true)) end end - if not (activeSkill.minion.modDB:Flag(nil, "SelfAurasCannotAffectAllies") or activeSkill.minion.modDB:Flag(nil, "SelfAurasOnlyAffectYou") or activeSkill.minion.modDB:Flag(nil, "SelfAuraSkillsCannotAffectAllies") or skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget")) then + if not (castingMinion.modDB:Flag(nil, "SelfAurasCannotAffectAllies") or castingMinion.modDB:Flag(nil, "SelfAurasOnlyAffectYou") or castingMinion.modDB:Flag(nil, "SelfAuraSkillsCannotAffectAllies") or skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget")) then if not modDB:Flag(nil, "AlliesAurasCannotAffectSelf") and not modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] then local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "BuffEffectOnPlayer", "AuraBuffEffect") + modDB:Sum("INC", skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf") local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "AuraBuffEffect") * modDB:More(skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf") @@ -2816,7 +2871,7 @@ function calcs.perform(env, skipEHP) mergeBuff(srcList, buffs, buff.name) end end - if env.minion and not env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] and (env.minion ~= activeSkill.minion or not activeSkill.skillData.auraCannotAffectSelf) then + if env.minion and not env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] and (env.minion ~= castingMinion or not activeSkill.skillData.auraCannotAffectSelf) then local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect") + env.minion.modDB:Sum("INC", skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf") local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect") * env.minion.modDB:More(skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf") local mult = (1 + inc / 100) * more @@ -2920,7 +2975,7 @@ function calcs.perform(env, skipEHP) end end enemyDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true - if env.minion and env.minion == activeSkill.minion then + if env.minion and env.minion == castingMinion then env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true end if buff.type == "Debuff" then