From 643a78b459b08a8edb6b8cddd7e59121e9f15fb3 Mon Sep 17 00:00:00 2001 From: Kyle Anderson Date: Mon, 24 Aug 2026 00:10:06 +0700 Subject: [PATCH] Add Minion Rage configuration support --- spec/System/TestMinionRage_spec.lua | 217 ++++++++++++++++++++++++++++ src/Classes/ConfigTab.lua | 6 + src/Data/SkillStatMap.lua | 7 + src/Modules/CalcPerform.lua | 4 + src/Modules/ConfigOptions.lua | 2 + 5 files changed, 236 insertions(+) create mode 100644 spec/System/TestMinionRage_spec.lua diff --git a/spec/System/TestMinionRage_spec.lua b/spec/System/TestMinionRage_spec.lua new file mode 100644 index 0000000000..d7b5951fdc --- /dev/null +++ b/spec/System/TestMinionRage_spec.lua @@ -0,0 +1,217 @@ +describe("Minion Rage", function() + before_each(function() + newBuild() + end) + + local function setupMinionSkill(gemId, supportGemId, quality) + local gemList = { + { + gemId = gemId, + level = 20, + quality = quality or 0, + enabled = true, + count = 1, + enableGlobal1 = true, + enableGlobal2 = true, + }, + } + + if supportGemId then + table.insert(gemList, { + gemId = supportGemId, + level = 1, + quality = 0, + enabled = true, + count = 1, + enableGlobal1 = true, + enableGlobal2 = true, + }) + end + + local socketGroup = { + enabled = true, + gemList = gemList, + } + + table.insert(build.skillsTab.socketGroupList, socketGroup) + build.skillsTab:ProcessSocketGroup(socketGroup) + + local groupIndex = #build.skillsTab.socketGroupList + build.mainSocketGroup = groupIndex + build.calcsTab.input.skill_number = groupIndex + socketGroup.mainActiveSkill = 1 + socketGroup.mainActiveSkillCalcs = 1 + + build.buildFlag = true + build.modFlag = true + runCallback("OnFrame") + build.calcsTab:BuildOutput() + runCallback("OnFrame") + end + + local function setMinionRage(rage) + build.configTab.input.multiplierMinionRage = rage + build.configTab:BuildModList() + + build.modFlag = true + build.buildFlag = true + runCallback("OnFrame") + build.calcsTab:BuildOutput() + runCallback("OnFrame") + + return build.calcsTab.mainOutput.Minion, build.calcsTab.mainEnv.minion + end + + it("hides Minion Rage for an unsupported non-Reaver minion", function() + setupMinionSkill("Metadata/Items/Gems/SkillGemSkeletalBrute") + + local control = build.configTab.varControls.multiplierMinionRage + assert.is_not_nil(control) + assert.is_false(control.shown()) + end) + + it("shows Minion Rage for a minion supported by Rage III", function() + setupMinionSkill( + "Metadata/Items/Gems/SkillGemSkeletalBrute", + "Metadata/Items/Gems/SkillGemRageSupportThree" + ) + + local control = build.configTab.varControls.multiplierMinionRage + assert.is_true(control.shown()) + end) + + it("shows Minion Rage for Skeletal Reavers without Rage support", function() + setupMinionSkill("Metadata/Items/Gems/SkillGemSkeletalReaver") + + local control = build.configTab.varControls.multiplierMinionRage + assert.is_true(control.shown()) + end) + + it("clamps configured Minion Rage to Maximum Rage", function() + setupMinionSkill("Metadata/Items/Gems/SkillGemSkeletalReaver") + + local output = setMinionRage(40) + + assert.are.equals(30, output.Rage) + assert.are.equals(30, output.MaximumRage) + end) + + it("grants Skeletal Reavers 3% increased Attack Speed per Rage", function() + setupMinionSkill("Metadata/Items/Gems/SkillGemSkeletalReaver") + + local output, minion = setMinionRage(20) + local speedIncrease = minion.mainSkill.skillModList:Sum( + "INC", + minion.mainSkill.skillCfg, + "Speed" + ) + + assert.are.equals(20, output.RageEffect) + assert.are.equals(60, speedIncrease) + end) + + it("applies Skeletal Reaver quality to Rage effect", function() + setupMinionSkill( + "Metadata/Items/Gems/SkillGemSkeletalReaver", + nil, + 20 + ) + + local output, minion = setMinionRage(20) + local speedIncrease = minion.mainSkill.skillModList:Sum( + "INC", + minion.mainSkill.skillCfg, + "Speed" + ) + + assert.are.equals(24, output.RageEffect) + assert.are.equals(72, speedIncrease) + end) + + it("applies Rage III Attack Speed only below Maximum Rage", function() + setupMinionSkill( + "Metadata/Items/Gems/SkillGemSkeletalBrute", + "Metadata/Items/Gems/SkillGemRageSupportThree" + ) + + local outputAt29, minionAt29 = setMinionRage(29) + local speedAt29 = minionAt29.mainSkill.skillModList:Sum( + "INC", + minionAt29.mainSkill.skillCfg, + "Speed" + ) + + local outputAt30, minionAt30 = setMinionRage(30) + local speedAt30 = minionAt30.mainSkill.skillModList:Sum( + "INC", + minionAt30.mainSkill.skillCfg, + "Speed" + ) + + assert.are.equals(29, outputAt29.Rage) + assert.are.equals(15, speedAt29) + assert.are.equals(30, outputAt30.Rage) + assert.are.equals(0, speedAt30) + end) + + it("applies Rage as more Attack Damage to minions", function() + setupMinionSkill( + "Metadata/Items/Gems/SkillGemSkeletalBrute", + "Metadata/Items/Gems/SkillGemRageSupportThree" + ) + + local _, minionAtZero = setMinionRage(0) + local damageAtZero = minionAtZero.mainSkill.skillModList:Sum( + "MORE", + minionAtZero.mainSkill.skillCfg, + "Damage" + ) + + local outputAt20, minionAt20 = setMinionRage(20) + local damageAt20 = minionAt20.mainSkill.skillModList:Sum( + "MORE", + minionAt20.mainSkill.skillCfg, + "Damage" + ) + + assert.are.equals(20, outputAt20.RageEffect) + assert.are.equals(damageAtZero + 20, damageAt20) + end) + + it("uses Maximum Rage from a weapon copied by Manifest Weapon", function() + build.itemsTab:CreateDisplayItemFromRaw([[ + New Item + Rabid Talisman + Implicits: 1 + +10 to Maximum Rage + ]]) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + + setupMinionSkill( + "Metadata/Items/Gems/SkillGemManifestWeapon", + "Metadata/Items/Gems/SkillGemRageSupportThree" + ) + + local outputAt30, minionAt30 = setMinionRage(30) + local speedAt30 = minionAt30.mainSkill.skillModList:Sum( + "INC", + minionAt30.mainSkill.skillCfg, + "Speed" + ) + + assert.are.equals(40, outputAt30.MaximumRage) + assert.are.equals(30, outputAt30.Rage) + + local outputAt40, minionAt40 = setMinionRage(40) + local speedAt40 = minionAt40.mainSkill.skillModList:Sum( + "INC", + minionAt40.mainSkill.skillCfg, + "Speed" + ) + + assert.are.equals(40, outputAt40.MaximumRage) + assert.are.equals(40, outputAt40.Rage) + assert.are.equals(15, speedAt30 - speedAt40) + end) +end) \ No newline at end of file diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index 83c649b476..728c8352e9 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -458,6 +458,12 @@ local ConfigTabClass = newClass("ConfigTab", "UndoHandler", "ControlHost", "Cont end end)) end + if varData.ifMinionFlag then + t_insert(shownFuncs, listOrSingleIfOption(varData.ifMinionFlag, function(ifOption) + local minion = self.build.calcsTab.mainEnv.minion + return minion and minion.modDB and minion.modDB:Flag(nil, ifOption) + end)) + end if varData.ifMod then t_insert(shownFuncs, listOrSingleIfOption(varData.ifMod, function(ifOption) if implyCond(varData) then diff --git a/src/Data/SkillStatMap.lua b/src/Data/SkillStatMap.lua index 6e5decaed8..bad4c7bdc3 100644 --- a/src/Data/SkillStatMap.lua +++ b/src/Data/SkillStatMap.lua @@ -2941,6 +2941,7 @@ return { }, ["gain_x_rage_on_melee_hit"] = { flag("Condition:CanGainRage", { type = "GlobalEffect", effectType = "Buff", effectName = "Rage" } ), + mod("MinionModifier", "LIST", { mod = flag("Condition:CanGainRage") }), }, ["gain_x%_of_maximum_rage_on_melee_hit"] = { flag("Condition:CanGainRage", { type = "GlobalEffect", effectType = "Buff", effectName = "Rage" } ), @@ -2982,6 +2983,12 @@ return { ["skeletal_graft_buff_effect_magnitude"] = { mod("UmbralWellBuffValue", "BASE", nil), }, +["attack_speed_+%_per_rage"] = { + mod("MinionModifier", "LIST", { mod = mod("Speed", "INC", nil, ModFlag.Attack, 0, { type = "Multiplier", var = "RageEffect" }) }), +}, +["minion_rage_effect_+%"] = { + mod("MinionModifier", "LIST", { mod = mod("RageEffect", "INC", nil) }), +}, ["physical_damage_reduction_rating_+%"] = { mod("Armour", "INC", nil), }, diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index dbc4bb9767..7f163d6b68 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1073,6 +1073,10 @@ local function initMinionModDB(env, activeSkill) minion.modDB:NewMod("LightningResist", "BASE", minion.minionData.lightningResist, "Base") minion.modDB:NewMod("ChaosResist", "BASE", minion.minionData.chaosResist, "Base") end + minion.modDB:NewMod("MaximumRage", "BASE", data.gameConstants["BaseMaximumRage"], "Base") + if activeSkill.activeEffect.grantedEffect.id == "SummonSkeletalReaversPlayer" then + minion.modDB:NewMod("Condition:CanGainRage", "FLAG", true, "Skeletal Reaver") + end minion.modDB:NewMod("ProjectileCount", "BASE", 1, "Base") minion.modDB:NewMod("PhysicalHeavyStunBuildup", "MORE", data.monsterConstants["physical_hit_damage_stun_multiplier_+%_final_from_ot"], "Physical Damage") minion.modDB:NewMod("EnemyHeavyStunBuildup", "MORE", data.monsterConstants["melee_hit_damage_stun_multiplier_+%_final_from_ot"], "Melee Damage", ModFlag.Melee) diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index 0437732526..c896992b32 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -964,6 +964,8 @@ Huge sets the radius to 11. { var = "multiplierRage", type = "count", label = "^xFF9922Rage:", ifFlag = "Condition:CanGainRage", tooltip = "Base Maximum ^xFF9922Rage ^7is 30, and inherently grants 1% More Attack Damage per 1 ^xFF9922Rage^7\nYou lose 5 ^xFF9922Rage ^7every second if you have not been Hit or gained ^xFF9922Rage ^7in the last 4 seconds.", apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:RageStack", "BASE", val, "Config", { type = "IgnoreCond" }, { type = "Condition", var = "Combat" }, { type = "Condition", var = "CanGainRage" }) end }, + { var = "multiplierMinionRage", type = "count", label = "Minion ^xFF9922Rage:", ifMinionFlag = "Condition:CanGainRage", tooltip = "Sets the Minion's current Rage.\nRage is capped at Minion's Maximum Rage.", apply = function(val, modList, enemyModList) modList:NewMod("MinionModifier", "LIST", { mod = modLib.createMod("Multiplier:RageStack", "BASE", val, "Config", { type = "IgnoreCond" }, { type = "Condition", var = "Combat" }, { type = "Condition", var = "CanGainRage" }) }, "Config") + end }, { var = "multiplierCombo", type = "count", label = "Combo:", ifMult = "ComboStacks", tooltip = "Some skills and effects require a certain Combo count to use.\nCombo is built by successfully Striking Enemies.", apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:ComboStacks", "BASE", val, "Config", { type = "IgnoreCond" }, { type = "Condition", var = "Combat" }) end },