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