From e1161aae96852d7f325a544bfb04770da7112e40 Mon Sep 17 00:00:00 2001 From: Leonel Togniolli Date: Tue, 9 Jun 2026 22:48:47 -0300 Subject: [PATCH 1/2] Add support for tamed beast modifiers on Companion gems Rare tamed beasts carry randomly rolled monster modifiers, returned by the character API as "tamedBeastProperties". These were previously discarded on import, so companions were modeled only as their base monster. - Import: parse tamedBeastProperties into a per-gem modifier list, resolving mod ids from "[ModId |Display]" tokens with a display-line fallback for nameplate-only lines; user enable/disable choices survive re-import - Data: new generated Data/TamedBeastMods.lua (export script iterates the Monster-domain mod pool, naming entries from KeywordPopups/ArchnemesisMods and resolving effects through SkillStatMap); mods with a positive spawn weight are flagged rollable and offered in the UI - Calcs: enabled modifiers apply to the companion minion's modDB, gated on the active skill being a Companion so stale lists never leak - UI: "Beast Modifiers" section in the socket group editor with per-row selection dropdown, enable checkbox, remove button and calc-comparison tooltips, mirroring the gem slot layout - Persistence: modifiers saved as TamedBeastMod child elements of Gem; old builds are unaffected --- spec/System/TestTamedBeastMods_spec.lua | 254 +++ src/Classes/ImportTab.lua | 41 + src/Classes/SkillsTab.lua | 250 +++ src/Data/TamedBeastMods.lua | 2180 +++++++++++++++++++++++ src/Export/Scripts/tamedBeastMods.lua | 177 ++ src/Modules/CalcPerform.lua | 14 + src/Modules/Data.lua | 34 + 7 files changed, 2950 insertions(+) create mode 100644 spec/System/TestTamedBeastMods_spec.lua create mode 100644 src/Data/TamedBeastMods.lua create mode 100644 src/Export/Scripts/tamedBeastMods.lua diff --git a/spec/System/TestTamedBeastMods_spec.lua b/spec/System/TestTamedBeastMods_spec.lua new file mode 100644 index 0000000000..501c5d6faf --- /dev/null +++ b/spec/System/TestTamedBeastMods_spec.lua @@ -0,0 +1,254 @@ +describe("TestTamedBeastMods", function() + before_each(function() + newBuild() + end) + + local sampleProperties = { + { + name = "Monster Modifiers:\n{0}", + values = { + { "[MonsterFlaskRemovalAura1|Siphons Flask Charges]\n[MonsterLifeRegenerationRatePercentage1|Regenerates Life]\nPeriodically unleashes [Cold|Ice]\n[MonsterAdditionalProjectiles1|Additional Projectiles]", 0 }, + }, + displayMode = 3, + }, + } + + describe("ImportTab.ParseTamedBeastProperties", function() + it("parses mod-id tokens and resolves display-only lines", function() + local list = build.importTab:ParseTamedBeastProperties(sampleProperties) + + assert.are.equals(4, #list) + assert.are.equals("MonsterFlaskRemovalAura1", list[1].modId) + assert.are.equals("Siphons Flask Charges", list[1].display) + assert.are.equals("MonsterLifeRegenerationRatePercentage1", list[2].modId) + -- No leading [ModId|...] token; resolved by display line lookup + assert.are.equals("Periodically unleashes Ice", list[3].display) + assert.is_not_nil(list[3].modId) + assert.are.equals("MonsterAdditionalProjectiles1", list[4].modId) + for _, entry in ipairs(list) do + assert.True(entry.enabled) + end + end) + + it("keeps unresolvable lines with display text only", function() + local list = build.importTab:ParseTamedBeastProperties({ + { values = { { "[MonsterMadeUpModDoesNotExist1|Made Up Mod]", 0 } } }, + }) + + assert.are.equals(1, #list) + assert.is_nil(list[1].modId) + assert.are.equals("Made Up Mod", list[1].display) + end) + + it("returns nil for absent or empty input", function() + assert.is_nil(build.importTab:ParseTamedBeastProperties(nil)) + assert.is_nil(build.importTab:ParseTamedBeastProperties({ })) + end) + end) + + describe("SkillsTab persistence", function() + it("saves tamed beast mods as Gem child elements", function() + build.skillsTab.skillSets[1].socketGroupList = { { + enabled = true, + gemList = { { + nameSpec = "Companion: Mighty Silverfist", + level = 20, quality = 0, enabled = true, enableGlobal1 = true, enableGlobal2 = true, + count = 1, corrupted = false, corruptLevel = 0, + tamedBeastModList = { + { modId = "MonsterDamageGainedAsCold1", enabled = true }, + { display = "Periodically unleashes Ice", enabled = false }, + }, + } }, + } } + + local xml = { } + build.skillsTab:Save(xml) + + local gemNode + for _, skillSetNode in ipairs(xml) do + if skillSetNode.elem == "SkillSet" then + for _, skillNode in ipairs(skillSetNode) do + if skillNode.elem == "Skill" then + gemNode = skillNode[1] + end + end + end + end + assert.is_not_nil(gemNode) + assert.are.equals("Gem", gemNode.elem) + + local beastModNodes = { } + for _, child in ipairs(gemNode) do + if child.elem == "TamedBeastMod" then + table.insert(beastModNodes, child) + end + end + assert.are.equals(2, #beastModNodes) + assert.are.equals("MonsterDamageGainedAsCold1", beastModNodes[1].attrib.modId) + assert.are.equals("true", beastModNodes[1].attrib.enabled) + assert.is_nil(beastModNodes[2].attrib.modId) + assert.are.equals("Periodically unleashes Ice", beastModNodes[2].attrib.display) + assert.are.equals("false", beastModNodes[2].attrib.enabled) + end) + + it("loads TamedBeastMod elements back onto the gem instance", function() + local node = { elem = "Skill", attrib = { enabled = "true" }, + { elem = "Gem", attrib = { nameSpec = "Companion: Mighty Silverfist", level = "20", quality = "0", enabled = "true" }, + { elem = "TamedBeastMod", attrib = { modId = "MonsterDamageGainedAsCold1", enabled = "true" } }, + { elem = "TamedBeastMod", attrib = { display = "Periodically unleashes Ice", enabled = "false" } }, + }, + } + + build.skillsTab:LoadSkill(node, 1) + + local socketGroupList = build.skillsTab.skillSets[1].socketGroupList + local gemInstance = socketGroupList[#socketGroupList].gemList[1] + assert.is_not_nil(gemInstance.tamedBeastModList) + assert.are.equals(2, #gemInstance.tamedBeastModList) + assert.are.equals("MonsterDamageGainedAsCold1", gemInstance.tamedBeastModList[1].modId) + assert.True(gemInstance.tamedBeastModList[1].enabled) + assert.is_nil(gemInstance.tamedBeastModList[2].modId) + assert.are.equals("Periodically unleashes Ice", gemInstance.tamedBeastModList[2].display) + assert.False(gemInstance.tamedBeastModList[2].enabled) + end) + + it("loads legacy gems without beast mods as nil", function() + local node = { elem = "Skill", attrib = { enabled = "true" }, + { elem = "Gem", attrib = { nameSpec = "Fireball", level = "20", quality = "0", enabled = "true" } }, + } + + build.skillsTab:LoadSkill(node, 1) + + local socketGroupList = build.skillsTab.skillSets[1].socketGroupList + assert.is_nil(socketGroupList[#socketGroupList].gemList[1].tamedBeastModList) + end) + end) + + describe("Calculation wiring", function() + local beastId = "Metadata/Monsters/Quadrilla/QuadrillaBossMinion1" -- Mighty Silverfist + + local function buildCompanionGroup(tamedBeastModList) + table.insert(build.beastList, beastId) + local gemInstance = { + nameSpec = "Companion: Mighty Silverfist", + gemId = "Metadata/Items/Gems/SkillGemSummonBeast", + level = 20, quality = 0, enabled = true, enableGlobal1 = true, enableGlobal2 = true, + count = 1, corrupted = false, corruptLevel = 0, + skillMinion = beastId, + skillMinionCalcs = beastId, + tamedBeastModList = tamedBeastModList, + } + local group = { label = "", enabled = true, gemList = { gemInstance } } + table.insert(build.skillsTab.socketGroupList, group) + build.skillsTab:ProcessSocketGroup(group) + build.mainSocketGroup = #build.skillsTab.socketGroupList + build.buildFlag = true + runCallback("OnFrame") + return gemInstance + end + + it("applies enabled beast mods to the companion minion", function() + buildCompanionGroup({ { modId = "MonsterDamageGainedAsCold1", enabled = true } }) + + local minion = build.calcsTab.mainEnv.minion + assert.is_not_nil(minion) + assert.are.equals(40, minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) + end) + + it("skips disabled and unresolved beast mods", function() + local gemInstance = buildCompanionGroup({ + { modId = "MonsterDamageGainedAsCold1", enabled = false }, + { display = "Periodically unleashes Ice", enabled = true }, + }) + + local minion = build.calcsTab.mainEnv.minion + assert.is_not_nil(minion) + assert.are.equals(0, minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) + + gemInstance.tamedBeastModList[1].enabled = true + build.buildFlag = true + runCallback("OnFrame") + assert.are.equals(40, build.calcsTab.mainEnv.minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) + end) + + it("does not affect companions without beast mods", function() + buildCompanionGroup(nil) + + local minion = build.calcsTab.mainEnv.minion + assert.is_not_nil(minion) + assert.are.equals(0, minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) + end) + + it("does not apply a stale beast mod list when the gem is not a Companion", function() + build.skillsTab:PasteSocketGroup("Skeletal Sniper 20/0 1") + runCallback("OnFrame") + local srcInstance = build.skillsTab.socketGroupList[1].gemList[1] + srcInstance.tamedBeastModList = { { modId = "MonsterDamageGainedAsCold1", enabled = true } } + build.buildFlag = true + runCallback("OnFrame") + + local minion = build.calcsTab.mainEnv.minion + assert.is_not_nil(minion) + assert.are.equals(0, minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) + end) + + it("applies entries beyond the fourth and creates UI rows for them", function() + buildCompanionGroup({ + { modId = "MonsterIncreasedSpeedAura1", enabled = false }, + { modId = "MonsterLifeRegenerationRatePercentage1", enabled = false }, + { modId = "MonsterAdditionalProjectiles1", enabled = false }, + { modId = "MonsterFlaskRemovalAura1", enabled = false }, + { modId = "MonsterDamageGainedAsCold1", enabled = true }, + }) + + assert.are.equals(40, build.calcsTab.mainEnv.minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) + + local skillsTab = build.skillsTab + skillsTab:SetDisplayGroup(skillsTab.socketGroupList[#skillsTab.socketGroupList]) + skillsTab:UpdateBeastModSlots() + assert.is_not_nil(skillsTab.beastModSlots[6]) + local slot5 = skillsTab.beastModSlots[5] + assert.are.equals("MonsterDamageGainedAsCold1", slot5.select.list[slot5.select.selIndex].modId) + end) + end) + + describe("Re-import preservation", function() + local function companionPayload() + return { + level = 12, + equipment = { }, + skills = { { + support = false, + typeLine = "Companion: Mighty Silverfist", + properties = { { name = "Level", values = { { "17", 0 } } } }, + tamedBeastProperties = { { + name = "Monster Modifiers:\n{0}", + values = { { "[MonsterDamageGainedAsCold1|Extra Cold Damage]\n[MonsterIncreasedSpeedAura1|Haste Aura]", 0 } }, + displayMode = 3, + } }, + } }, + } + end + + it("keeps user enable choices for surviving mods when re-importing", function() + build.importTab.controls.charImportItemsClearSkills.state = true + build.importTab.controls.charImportItemsClearItems.state = false + build.importTab:ImportItemsAndSkills(companionPayload()) + runCallback("OnFrame") + + local gem = build.skillsTab.socketGroupList[1].gemList[1] + assert.are.equals(2, #gem.tamedBeastModList) + assert.True(gem.tamedBeastModList[1].enabled) + gem.tamedBeastModList[1].enabled = false + + build.importTab:ImportItemsAndSkills(companionPayload()) + runCallback("OnFrame") + + gem = build.skillsTab.socketGroupList[1].gemList[1] + assert.are.equals(2, #gem.tamedBeastModList) + assert.are.equals("MonsterDamageGainedAsCold1", gem.tamedBeastModList[1].modId) + assert.False(gem.tamedBeastModList[1].enabled) + assert.True(gem.tamedBeastModList[2].enabled) + end) + end) +end) diff --git a/src/Classes/ImportTab.lua b/src/Classes/ImportTab.lua index 0dcb9c59d9..b1c56ae8cf 100644 --- a/src/Classes/ImportTab.lua +++ b/src/Classes/ImportTab.lua @@ -859,6 +859,7 @@ local function snapshotSocketGroupReimportState(socketGroup, isMainGroup) skillMinionSkillCalcs = gem.skillMinionSkillCalcs, skillMinionSkillStatSetIndexLookup = gem.skillMinionSkillStatSetIndexLookup and copyTable(gem.skillMinionSkillStatSetIndexLookup), skillMinionSkillStatSetIndexLookupCalcs = gem.skillMinionSkillStatSetIndexLookupCalcs and copyTable(gem.skillMinionSkillStatSetIndexLookupCalcs), + tamedBeastModList = gem.tamedBeastModList and copyTable(gem.tamedBeastModList), enableGlobal1 = gem.enableGlobal1, enableGlobal2 = gem.enableGlobal2, } @@ -894,6 +895,23 @@ local function applyGemReimportState(gem, state) gem.skillMinionSkillCalcs = state.skillMinionSkillCalcs gem.skillMinionSkillStatSetIndexLookup = state.skillMinionSkillStatSetIndexLookup and copyTable(state.skillMinionSkillStatSetIndexLookup) gem.skillMinionSkillStatSetIndexLookupCalcs = state.skillMinionSkillStatSetIndexLookupCalcs and copyTable(state.skillMinionSkillStatSetIndexLookupCalcs) + if state.tamedBeastModList then + if gem.tamedBeastModList then + local preservedEnabled = { } + for _, entry in ipairs(state.tamedBeastModList) do + if entry.modId and entry.enabled ~= nil then + preservedEnabled[entry.modId] = entry.enabled + end + end + for _, entry in ipairs(gem.tamedBeastModList) do + if entry.modId and preservedEnabled[entry.modId] ~= nil then + entry.enabled = preservedEnabled[entry.modId] + end + end + else + gem.tamedBeastModList = copyTable(state.tamedBeastModList) + end + end gem.enableGlobal1 = state.enableGlobal1 gem.enableGlobal2 = state.enableGlobal2 end @@ -914,6 +932,28 @@ local function applySocketGroupReimportState(socketGroup, state) end end +-- Parses the "tamedBeastProperties" field on rare tamed beast Companion gems into a list of +-- { modId, display, enabled } entries. Each newline-separated line is one rolled monster mod; +-- most lead with a "[ModId|Display]" token, the rest are matched by display line. +function ImportTabClass:ParseTamedBeastProperties(tamedBeastProperties) + local data = self.build.data + local list = { } + for _, property in ipairs(tamedBeastProperties or { }) do + local text = property.values and property.values[1] and property.values[1][1] + if type(text) == "string" then + for line in text:gmatch("[^\n]+") do + local display = escapeGGGString(line) + local modId = line:match("^%[([%w_]+)|[^%]]*%]$") + if not (modId and data.tamedBeastMods[modId]) then + modId = data.tamedBeastModsByDisplay[data.normaliseBeastModLine(display)] + end + t_insert(list, { modId = modId, display = display, enabled = true }) + end + end + end + return list[1] and list or nil +end + function ImportTabClass:ImportItemsAndSkills(charData) local charItemData = charData.equipment if self.controls.charImportItemsClearItems.state then @@ -1034,6 +1074,7 @@ function ImportTabClass:ImportItemsAndSkills(charData) break end end + gemInstance.tamedBeastModList = self:ParseTamedBeastProperties(skillData.tamedBeastProperties) end gemInstance.nameSpec = self.build.data.gems[gemId].name diff --git a/src/Classes/SkillsTab.lua b/src/Classes/SkillsTab.lua index 3c43bfffe3..cbb0fb2537 100644 --- a/src/Classes/SkillsTab.lua +++ b/src/Classes/SkillsTab.lua @@ -282,6 +282,27 @@ will automatically apply to the skill.]] self.controls.gemCorruptHeader = new("LabelControl", {"BOTTOMLEFT", self.gemSlots[1].corruptLevel, "TOPLEFT"}, {0, -2, 0, 16}, "^7Corrupt:") self.controls.gemEnableHeader = new("LabelControl", {"BOTTOMLEFT", self.gemSlots[1].enabled, "TOPLEFT"}, {-16, -2, 0, 16}, "^7Enabled:") self.controls.gemCountHeader = new("LabelControl", {"BOTTOMLEFT", self.gemSlots[1].count, "TOPLEFT"}, {18, -2, 0, 16}, "^7Count:") + + -- Tamed beast (companion) modifiers + self.anchorBeastMods = new("Control", nil, {0, 0, 0, 0}) + self.anchorBeastMods:SetAnchor("TOPLEFT", self.anchorGemSlots, "TOPLEFT", 0, function() + local y = 0 + for i = 1, (self.displayGroup and #self.displayGroup.gemList or 0) + 1 do + local slot = self.gemSlots[i] + y = y + ((slot and (slot.enableGlobal1:IsShown() or slot.enableGlobal2:IsShown())) and 46 or 22) + end + return y + 14 + end) + self.beastModSlots = { } + self:CreateBeastModSlot(1) + self.controls.beastModHeader = new("LabelControl", {"BOTTOMLEFT", self.beastModSlots[1].select, "TOPLEFT"}, {0, -2, 0, 16}, "^7Beast Modifiers:") + self.controls.beastModHeader.shown = function() + return self:GetDisplayedBeastGem() ~= nil + end + self.controls.beastModEnableHeader = new("LabelControl", {"BOTTOMLEFT", self.beastModSlots[1].enabled, "TOPLEFT"}, {-16, -2, 0, 16}, "^7Enabled:") + self.controls.beastModEnableHeader.shown = function() + return self:GetDisplayedBeastGem() ~= nil + end end) function SkillsTabClass:GetCorruptIndex(gemInstance) @@ -386,6 +407,13 @@ function SkillsTabClass:LoadSkill(node, skillSetId) for _, map in ipairs(child) do gemInstance.skillMinionSkillStatSetIndexLookupCalcs[child.attrib.grantedEffect][tonumber(map.attrib.skillIndex)] = tonumber(map.attrib.statSetIndex) end + elseif child.elem == "TamedBeastMod" then + gemInstance.tamedBeastModList = gemInstance.tamedBeastModList or { } + t_insert(gemInstance.tamedBeastModList, { + modId = child.attrib.modId, + display = child.attrib.display, + enabled = child.attrib.enabled == "true", + }) end end @@ -537,6 +565,15 @@ function SkillsTabClass:Save(xml) t_insert(gemInfo, minionSkillStatSetIndexLookupCalcs) end end + if gemInstance.tamedBeastModList then + for _, beastMod in ipairs(gemInstance.tamedBeastModList) do + t_insert(gemInfo, { elem = "TamedBeastMod", attrib = { + modId = beastMod.modId, + display = beastMod.display, + enabled = tostring(beastMod.enabled), + } } ) + end + end t_insert(node, gemInfo) end t_insert(child, node) @@ -603,6 +640,7 @@ function SkillsTabClass:Draw(viewPort, inputEvents) end self:UpdateGemSlots() + self:UpdateBeastModSlots() self:DrawControls(viewPort) end @@ -787,6 +825,13 @@ function SkillsTabClass:CreateGemSlot(index) gemInstance.gemId = gemId gemInstance.skillId = nil self:ProcessSocketGroup(self.displayGroup) + -- Beast mods only exist on Companion gems; clear them when the gem becomes anything else + if gemInstance.tamedBeastModList then + local grantedEffect = gemInstance.gemData and gemInstance.gemData.grantedEffect + if not (grantedEffect and grantedEffect.minionList and grantedEffect.name:match("^Companion")) then + gemInstance.tamedBeastModList = nil + end + end -- New gems need to be constrained by ProcessGemLevel gemInstance.level = self:ProcessGemLevel(gemInstance.gemData) gemInstance.naturalMaxLevel = gemInstance.level @@ -1095,6 +1140,211 @@ function SkillsTabClass:CreateGemSlot(index) self.controls["gemSlot"..index.."EnableGlobal2"] = slot.enableGlobal2 end +-- Returns the displayed group's Companion gem, if any (only companions carry tamed beast mods) +function SkillsTabClass:GetDisplayedBeastGem() + if not self.displayGroup then + return + end + for _, gemInstance in ipairs(self.displayGroup.gemList) do + local grantedEffect = gemInstance.gemData and gemInstance.gemData.grantedEffect + if grantedEffect and grantedEffect.minionList and grantedEffect.name:match("^Companion") then + return gemInstance + end + end +end + +function SkillsTabClass:GetBeastModDropList() + if not self.beastModDropList then + local sorted = { } + local nameCount = { } + for modId, beastMod in pairs(self.build.data.tamedBeastMods) do + -- Non-rollable mods (script-applied, player-minion variants, placeholders) stay + -- resolvable on import but are not offered for selection + if beastMod.rollable then + t_insert(sorted, { modId = modId, beastMod = beastMod }) + nameCount[beastMod.name] = (nameCount[beastMod.name] or 0) + 1 + end + end + table.sort(sorted, function(a, b) + if a.beastMod.name ~= b.beastMod.name then + return a.beastMod.name < b.beastMod.name + end + return (a.beastMod.tier or 0) < (b.beastMod.tier or 0) + end) + local list = { { label = "" } } + for _, entry in ipairs(sorted) do + local label = entry.beastMod.name + if entry.beastMod.tier and nameCount[entry.beastMod.name] > 1 then + label = label .. " (Tier " .. entry.beastMod.tier .. ")" + end + t_insert(list, { label = label, modId = entry.modId, beastMod = entry.beastMod }) + end + self.beastModDropList = list + end + return self.beastModDropList +end + +function SkillsTabClass:CreateBeastModSlot(index) + local slot = { } + self.beastModSlots[index] = slot + + local function getEntry() + local gemInstance = self:GetDisplayedBeastGem() + return gemInstance and gemInstance.tamedBeastModList and gemInstance.tamedBeastModList[index], gemInstance + end + + local function isRowShown() + local gemInstance = self:GetDisplayedBeastGem() + return gemInstance ~= nil and index <= (gemInstance.tamedBeastModList and #gemInstance.tamedBeastModList or 0) + 1 + end + + -- Remove modifier + slot.delete = new("ButtonControl", nil, {0, 0, 20, 20}, "x", function() + local entry, gemInstance = getEntry() + if entry then + t_remove(gemInstance.tamedBeastModList, index) + self:AddUndoState() + self.build.buildFlag = true + end + end) + if index == 1 then + slot.delete:SetAnchor("TOPLEFT", self.anchorBeastMods, "TOPLEFT", 0, 20) + else + slot.delete:SetAnchor("TOPLEFT", self.beastModSlots[index - 1].delete, "BOTTOMLEFT", 0, 2) + end + slot.delete.shown = isRowShown + slot.delete.enabled = function() + return getEntry() ~= nil + end + slot.delete.tooltipText = "Remove this modifier." + self.controls["beastModSlot"..index.."Delete"] = slot.delete + + -- Modifier selection + slot.select = new("DropDownControl", {"LEFT", slot.delete, "RIGHT"}, {2, 0, 300, 20}, self:GetBeastModDropList(), function(indexSel, value) + local entry, gemInstance = getEntry() + if not gemInstance then + return + end + gemInstance.tamedBeastModList = gemInstance.tamedBeastModList or { } + if value.modId then + if entry then + entry.modId = value.modId + entry.display = nil + else + gemInstance.tamedBeastModList[index] = { modId = value.modId, enabled = true } + end + elseif entry then + t_remove(gemInstance.tamedBeastModList, index) + end + self:AddUndoState() + self.build.buildFlag = true + end) + slot.select.shown = isRowShown + slot.select.tooltipFunc = function(tooltip, mode, indexSel, value) + if tooltip:CheckForUpdate(self.build.outputRevision, value, self.displayGroup) then + if mode == "OUT" or not value or not value.beastMod then + return + end + for _, line in ipairs(value.beastMod.statDescriptions) do + if value.beastMod.modList[1] then + tooltip:AddLine(16, colorCodes.MAGIC..line) + else + local line = colorCodes.UNSUPPORTED..line + line = main.notSupportedModTooltips and (line .. main.notSupportedTooltipText) or line + tooltip:AddLine(16, line) + end + end + local entry, gemInstance = getEntry() + if not gemInstance then + return + end + local calcFunc, calcBase = self.build.calcsTab:GetMiscCalculator(self.build) + if calcFunc then + -- Trial-swap the entry for the compare, restoring both the entry and the + -- list reference so a hover can never leave state behind + local storedList = gemInstance.tamedBeastModList + gemInstance.tamedBeastModList = storedList or { } + local storedEntry = gemInstance.tamedBeastModList[index] + gemInstance.tamedBeastModList[index] = { modId = value.modId, enabled = true } + local output = calcFunc() + gemInstance.tamedBeastModList[index] = storedEntry + gemInstance.tamedBeastModList = storedList + tooltip:AddSeparator(10) + self.build:AddStatComparesToTooltip(tooltip, calcBase, output, "^7Selecting this modifier will give you:") + end + end + end + self.controls["beastModSlot"..index.."Select"] = slot.select + + -- Enable modifier + slot.enabled = new("CheckBoxControl", {"LEFT", slot.select, "RIGHT"}, {18, 0, 18}, nil, function(state) + local entry = getEntry() + if entry then + entry.enabled = state + self:AddUndoState() + self.build.buildFlag = true + end + end) + slot.enabled.shown = function() + return getEntry() ~= nil + end + slot.enabled.tooltipFunc = function(tooltip) + if tooltip:CheckForUpdate(self.build.outputRevision, self.displayGroup) then + local entry = getEntry() + if entry and entry.modId then + local calcFunc, calcBase = self.build.calcsTab:GetMiscCalculator(self.build) + if calcFunc then + entry.enabled = not entry.enabled + local output = calcFunc() + entry.enabled = not entry.enabled + self.build:AddStatComparesToTooltip(tooltip, calcBase, output, entry.enabled and "^7Disabling this modifier will give you:" or "^7Enabling this modifier will give you:") + end + end + end + end + self.controls["beastModSlot"..index.."Enable"] = slot.enabled + + -- Imported modifier the dropdown can't represent: unknown id, or known but not in the + -- selectable pool. Either way the entry still applies and can be toggled or removed. + slot.unresolved = new("LabelControl", {"LEFT", slot.enabled, "RIGHT"}, {8, 0, 0, 16}, function() + local entry = getEntry() + if not entry then + return "" + end + if not entry.modId then + return entry.display and ("^1Unrecognised: ^7"..entry.display) or "" + end + local beastMod = self.build.data.tamedBeastMods[entry.modId] + if not (beastMod and beastMod.rollable) then + return "^1Not selectable: ^7"..(beastMod and beastMod.name or entry.modId) + end + return "" + end) + self.controls["beastModSlot"..index.."Unresolved"] = slot.unresolved +end + +-- Update the beast mod slot controls to reflect the currently displayed socket group's companion +function SkillsTabClass:UpdateBeastModSlots() + local gemInstance = self:GetDisplayedBeastGem() + if not gemInstance then + return + end + -- Create slots on demand, like gem slots: one row per entry plus an empty row to add more + for index = 1, (gemInstance.tamedBeastModList and #gemInstance.tamedBeastModList or 0) + 1 do + if not self.beastModSlots[index] then + self:CreateBeastModSlot(index) + end + end + for index, slot in ipairs(self.beastModSlots) do + local entry = gemInstance.tamedBeastModList and gemInstance.tamedBeastModList[index] + slot.select.selIndex = 1 + if entry and entry.modId then + slot.select:SelByValue(entry.modId, "modId") + end + slot.enabled.state = entry and entry.enabled or false + end +end + -- Update the gem slot controls to reflect the currently displayed socket group function SkillsTabClass:UpdateGemSlots() if not self.displayGroup then diff --git a/src/Data/TamedBeastMods.lua b/src/Data/TamedBeastMods.lua new file mode 100644 index 0000000000..8448f01f6b --- /dev/null +++ b/src/Data/TamedBeastMods.lua @@ -0,0 +1,2180 @@ +-- This file is automatically generated, do not edit! +-- Path of Building +-- +-- Tamed Beast (Companion) Modifier Data +-- Monster data (c) Grinding Gear Games + +local mods, mod, flag = ... + +mods["MonsterDamageGainedAsFire1"] = { + name = "Extra Fire Damage", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster Gains 40% of damage as extra Fire damage.", + }, + modList = { + mod("DamageGainAsFire", "BASE", 40, 0, 0), -- MonsterDamageGainedAsFire1 [non_skill_base_all_damage_%_to_gain_as_fire = 40] + }, +} + +mods["PlayerMonsterDamageGainedAsFire1"] = { + name = "Extra Fire Damage", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("DamageGainAsFire", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsFire1 [non_skill_base_all_damage_%_to_gain_as_fire = 40] + }, +} + +mods["MonsterDamageGainedAsCold1"] = { + name = "Extra Cold Damage", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster Gains 40% of damage as extra Cold damage.", + }, + modList = { + mod("DamageGainAsCold", "BASE", 40, 0, 0), -- MonsterDamageGainedAsCold1 [non_skill_base_all_damage_%_to_gain_as_cold = 40] + }, +} + +mods["PlayerMonsterDamageGainedAsCold1"] = { + name = "Extra Cold Damage", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("DamageGainAsCold", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsCold1 [non_skill_base_all_damage_%_to_gain_as_cold = 40] + }, +} + +mods["MonsterDamageGainedAsLightning1"] = { + name = "Extra Lightning Damage", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster Gains 40% of damage as extra Lightning damage.", + }, + modList = { + mod("DamageGainAsLightning", "BASE", 40, 0, 0), -- MonsterDamageGainedAsLightning1 [non_skill_base_all_damage_%_to_gain_as_lightning = 40] + }, +} + +mods["PlayerMonsterDamageGainedAsLightning1"] = { + name = "Extra Lightning Damage", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("DamageGainAsLightning", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsLightning1 [non_skill_base_all_damage_%_to_gain_as_lightning = 40] + }, +} + +mods["MonsterIncreasedSpeed1"] = { + name = "Hasted", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has 30% increased Attack, Cast and Movement speed.", + }, + modList = { + mod("Speed", "INC", 30, 0, 0), -- MonsterIncreasedSpeed1 [attack_and_cast_speed_+% = 30] + mod("MovementSpeed", "INC", 30, 0, 0), -- MonsterIncreasedSpeed1 [base_movement_velocity_+% = 30] + }, +} + +mods["PlayerMonsterIncreasedSpeed1"] = { + name = "Hasted", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("Speed", "INC", 30, 0, 0), -- PlayerMonsterIncreasedSpeed1 [attack_and_cast_speed_+% = 30] + mod("MovementSpeed", "INC", 30, 0, 0), -- PlayerMonsterIncreasedSpeed1 [base_movement_velocity_+% = 30] + }, +} + +mods["MonsterCriticalStrikeChance1"] = { + name = "Extra Crits", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has 300% increased chance to Critically Hit.", + }, + modList = { + mod("CritChance", "INC", 300, 0, 0), -- MonsterCriticalStrikeChance1 [critical_strike_chance_+% = 300] + }, +} + +mods["PlayerMonsterCriticalStrikeChance1"] = { + name = "Extra Crits", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("CritChance", "INC", 300, 0, 0), -- PlayerMonsterCriticalStrikeChance1 [critical_strike_chance_+% = 300] + }, +} + +mods["MonsterStunDamageIncrease1"] = { + name = "Stuns", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has 100% increased Stun buildup.", + }, + modList = { + -- MonsterStunDamageIncrease1 [hit_damage_stun_multiplier_+% = 100] + }, +} + +mods["PlayerMonsterStunDamageIncrease1"] = { + name = "Stuns", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterStunDamageIncrease1 [hit_damage_stun_multiplier_+% = 100] + }, +} + +mods["MonsterExtraArmour1"] = { + name = "Armoured", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster gains extra Armour based off of their Strength.", + }, + modList = { + -- MonsterExtraArmour1 [monster_additional_strength_ratio_%_for_armour = 100] + }, +} + +mods["PlayerMonsterExtraArmour1"] = { + name = "Armoured", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterExtraArmour1 [monster_additional_strength_ratio_%_for_armour = 100] + }, +} + +mods["MonsterExtraEvasion1"] = { + name = "Evasive", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster gains extra Evasion based off of their Dexterity.", + }, + modList = { + -- MonsterExtraEvasion1 [monster_additional_dexterity_ratio_%_for_evasion = 100] + }, +} + +mods["PlayerMonsterExtraEvasion1"] = { + name = "Evasive", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterExtraEvasion1 [monster_additional_dexterity_ratio_%_for_evasion = 100] + }, +} + +mods["MonsterExtraEnergyShield1"] = { + name = "Extra Energy Shield", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster gains 25% of Maximum life as added Energy Shield.", + }, + modList = { + -- MonsterExtraEnergyShield1 [base_maximum_life_%_to_gain_as_total_energy_shield = 25] + }, +} + +mods["PlayerMonsterExtraEnergyShield1"] = { + name = "Extra Energy Shield", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterExtraEnergyShield1 [base_maximum_life_%_to_gain_as_total_energy_shield = 25] + }, +} + +mods["MonsterAlwaysPoison1"] = { + name = "Always Poisons", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("PoisonChance", "BASE", 100, 0, 0), -- MonsterAlwaysPoison1 [global_poison_on_hit = 1] + }, +} + +mods["PlayerMonsterAlwaysPoison1"] = { + name = "Always Poisons", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("PoisonChance", "BASE", 100, 0, 0), -- PlayerMonsterAlwaysPoison1 [global_poison_on_hit = 1] + }, +} + +mods["MonsterAlwaysBleed1"] = { + name = "Always Bleeds", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("BleedChance", "BASE", 100, 0, 0), -- MonsterAlwaysBleed1 [global_bleed_on_hit = 1] + }, +} + +mods["PlayerMonsterAlwaysBleed1"] = { + name = "Always Bleeds", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("BleedChance", "BASE", 100, 0, 0), -- PlayerMonsterAlwaysBleed1 [global_bleed_on_hit = 1] + }, +} + +mods["MonsterBurningGroundOnDeath1"] = { + name = "Periodically unleashes Fire", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterBurningGroundOnDeath1"] = { + name = "Burning Ground on Death", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterChilledGroundOnDeath1"] = { + name = "Periodically unleashes Ice", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterChilledGroundOnDeath1"] = { + name = "Periodically unleashes Ice", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterShockedGroundOnDeath1"] = { + name = "Periodically unleashes Lightning", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterShockedGroundOnDeath1"] = { + name = "Periodically unleashes Lightning", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterImmuneToStun1"] = { + name = "Increased Stun Threshold", + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster cannot be Stunned.", + }, + modList = { + mod("StunImmune", "FLAG", 1, 0, 0), -- MonsterImmuneToStun1 [base_cannot_be_stunned = 1] + }, +} + +mods["PlayerMonsterImmuneToStun1"] = { + name = "Increased Stun Threshold", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("StunImmune", "FLAG", 1, 0, 0), -- PlayerMonsterImmuneToStun1 [base_cannot_be_stunned = 1] + }, +} + +mods["MonsterStunResilience1"] = { + name = "Stun Resistant", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has 250% increased Stun Threshold.", + }, + modList = { + mod("StunThreshold", "INC", 250, 0, 0), -- MonsterStunResilience1 [stun_threshold_+% = 250] + }, +} + +mods["PlayerMonsterStunResilience1"] = { + name = "Stun Resistant", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("StunThreshold", "INC", 250, 0, 0), -- PlayerMonsterStunResilience1 [stun_threshold_+% = 250] + }, +} + +mods["MonsterFireResistance1"] = { + name = "Fire Resistant", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has +50% to Fire Resistance and +10% to Maximum Fire Resistance.", + }, + modList = { + mod("FireResist", "BASE", 50, 0, 0), -- MonsterFireResistance1 [base_fire_damage_resistance_% = 50] + mod("FireResistMax", "BASE", 10, 0, 0), -- MonsterFireResistance1 [base_maximum_fire_damage_resistance_% = 10] + }, +} + +mods["PlayerMonsterFireResistance1"] = { + name = "Fire Resistant", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("FireResist", "BASE", 50, 0, 0), -- PlayerMonsterFireResistance1 [base_fire_damage_resistance_% = 50] + mod("FireResistMax", "BASE", 10, 0, 0), -- PlayerMonsterFireResistance1 [base_maximum_fire_damage_resistance_% = 10] + }, +} + +mods["MonsterColdResistance1"] = { + name = "Cold Resistant", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has +50% to Cold Resistance and +10% to Maximum Cold Resistance.", + }, + modList = { + mod("ColdResist", "BASE", 50, 0, 0), -- MonsterColdResistance1 [base_cold_damage_resistance_% = 50] + mod("ColdResistMax", "BASE", 10, 0, 0), -- MonsterColdResistance1 [base_maximum_cold_damage_resistance_% = 10] + }, +} + +mods["PlayerMonsterColdResistance1"] = { + name = "Cold Resistant", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("ColdResist", "BASE", 50, 0, 0), -- PlayerMonsterColdResistance1 [base_cold_damage_resistance_% = 50] + mod("ColdResistMax", "BASE", 10, 0, 0), -- PlayerMonsterColdResistance1 [base_maximum_cold_damage_resistance_% = 10] + }, +} + +mods["MonsterLightningResistance1"] = { + name = "Lightning Resistant", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has +50% to Lightning Resistance and +10% to Maximum Lightning Resistance.", + }, + modList = { + mod("LightningResist", "BASE", 50, 0, 0), -- MonsterLightningResistance1 [base_lightning_damage_resistance_% = 50] + mod("LightningResistMax", "BASE", 10, 0, 0), -- MonsterLightningResistance1 [base_maximum_lightning_damage_resistance_% = 10] + }, +} + +mods["PlayerMonsterLightningResistance1"] = { + name = "Lightning Resistant", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("LightningResist", "BASE", 50, 0, 0), -- PlayerMonsterLightningResistance1 [base_lightning_damage_resistance_% = 50] + mod("LightningResistMax", "BASE", 10, 0, 0), -- PlayerMonsterLightningResistance1 [base_maximum_lightning_damage_resistance_% = 10] + }, +} + +mods["MonsterArmourPenetration1"] = { + name = "Breaks Armour", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster Breaks Armour equal to 1000% of Physical Damage dealt.", + }, + modList = { + mod("Condition:CanArmourBreak", "FLAG", 1000, 0, 0, { effectName = "ArmourBreak", effectType = "Buff", type = "GlobalEffect" }), -- MonsterArmourPenetration1 [armour_break_physical_damage_%_dealt_as_armour_break = 1000] + }, +} + +mods["PlayerMonsterArmourPenetration1"] = { + name = "Breaks Armour", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("Condition:CanArmourBreak", "FLAG", 1000, 0, 0, { effectName = "ArmourBreak", effectType = "Buff", type = "GlobalEffect" }), -- PlayerMonsterArmourPenetration1 [armour_break_physical_damage_%_dealt_as_armour_break = 1000] + }, +} + +mods["MonsterIncreasedAccuracy1"] = { + name = "Accurate", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has 200% increased Accuracy Rating.", + }, + modList = { + mod("Accuracy", "INC", 200, 0, 0), -- MonsterIncreasedAccuracy1 [accuracy_rating_+% = 200] + }, +} + +mods["PlayerMonsterIncreasedAccuracy1"] = { + name = "Accurate", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("Accuracy", "INC", 200, 0, 0), -- PlayerMonsterIncreasedAccuracy1 [accuracy_rating_+% = 200] + }, +} + +mods["MonsterDamageGainedAsChaos1"] = { + name = "Extra Chaos Damage", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster Gains 40% of damage as extra Chaos damage.", + }, + modList = { + mod("DamageGainAsChaos", "BASE", 40, 0, 0), -- MonsterDamageGainedAsChaos1 [non_skill_base_all_damage_%_to_gain_as_chaos = 40] + }, +} + +mods["PlayerMonsterDamageGainedAsChaos1"] = { + name = "Extra Chaos Damage", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("DamageGainAsChaos", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsChaos1 [non_skill_base_all_damage_%_to_gain_as_chaos = 40] + }, +} + +mods["MonsterLifeRegenerationRatePercentage1"] = { + name = "Regenerates Life", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster Regenerates 2% of Maximum Life per second.", + }, + modList = { + mod("LifeRegenPercent", "BASE", 2, 0, 0), -- MonsterLifeRegenerationRatePercentage1 [life_regeneration_rate_per_minute_% = 120] + }, +} + +mods["PlayerMonsterLifeRegenerationRatePercentage1"] = { + name = "Regenerates Life", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("LifeRegenPercent", "BASE", 2, 0, 0), -- PlayerMonsterLifeRegenerationRatePercentage1 [life_regeneration_rate_per_minute_% = 120] + }, +} + +mods["MonsterAdditionalProjectiles1"] = { + name = "Additional Projectiles", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster fires 4 additional Projectiles.", + }, + modList = { + mod("ProjectileCount", "BASE", 4, 0, 0), -- MonsterAdditionalProjectiles1 [number_of_additional_projectiles = 4] + }, +} + +mods["PlayerMonsterAdditionalProjectiles1"] = { + name = "Additional Projectiles", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("ProjectileCount", "BASE", 4, 0, 0), -- PlayerMonsterAdditionalProjectiles1 [number_of_additional_projectiles = 4] + }, +} + +mods["MonsterAreaOfEffect1"] = { + name = "Increased Area of Effect", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has 100% Increased Area of Effect.", + "100% more Area of Effect", + }, + modList = { + -- MonsterAreaOfEffect1 [rare_monster_mod_area_of_effect_+%_final = 100] + }, +} + +mods["PlayerMonsterAreaOfEffect1"] = { + name = "Increased Area of Effect", + type = "Suffix", + tier = 1, + statDescriptions = { + "100% more Area of Effect", + }, + modList = { + -- PlayerMonsterAreaOfEffect1 [rare_monster_mod_area_of_effect_+%_final = 100] + }, +} + +mods["MonsterIgniteChanceIncrease1"] = { + name = "All Damage Ignites", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("PhysicalCanIgnite", "FLAG", 1, 0, 0), -- MonsterIgniteChanceIncrease1 [all_damage_can_ignite = 1] + mod("EnemyIgniteChance", "BASE", 100, 0, 0), -- MonsterIgniteChanceIncrease1 [always_ignite = 1] + }, +} + +mods["PlayerMonsterIgniteChanceIncrease1"] = { + name = "All Damage Ignites", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("PhysicalCanIgnite", "FLAG", 1, 0, 0), -- PlayerMonsterIgniteChanceIncrease1 [all_damage_can_ignite = 1] + mod("EnemyIgniteChance", "BASE", 100, 0, 0), -- PlayerMonsterIgniteChanceIncrease1 [always_ignite = 1] + }, +} + +mods["MonsterFreezeDamageIncrease1"] = { + name = "All Damage Chills", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- MonsterFreezeDamageIncrease1 [all_damage_can_chill = 1] + -- MonsterFreezeDamageIncrease1 [chill_minimum_slow_% = 10] + }, +} + +mods["PlayerMonsterFreezeDamageIncrease1"] = { + name = "All Damage Chills", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterFreezeDamageIncrease1 [all_damage_can_chill = 1] + -- PlayerMonsterFreezeDamageIncrease1 [chill_minimum_slow_% = 10] + }, +} + +mods["MonsterShockChanceIncrease1"] = { + name = "All Damage Shocks", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("PhysicalCanShock", "FLAG", 1, 0, 0), -- MonsterShockChanceIncrease1 [all_damage_can_shock = 1] + mod("EnemyShockChance", "BASE", 100, 0, 0), -- MonsterShockChanceIncrease1 [always_shock = 1] + }, +} + +mods["PlayerMonsterShockChanceIncrease1"] = { + name = "All Damage Shocks", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("PhysicalCanShock", "FLAG", 1, 0, 0), -- PlayerMonsterShockChanceIncrease1 [all_damage_can_shock = 1] + mod("EnemyShockChance", "BASE", 100, 0, 0), -- PlayerMonsterShockChanceIncrease1 [always_shock = 1] + }, +} + +mods["MonsterBurningGroundTrail1"] = { + name = "Trail of Fire", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterBurningGroundTrail1"] = { + name = "Trail of Fire", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterChilledGroundTrail1"] = { + name = "Trail of Ice", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterChilledGroundTrail1"] = { + name = "Trail of Ice", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterShockedGroundTrail1"] = { + name = "Trail of Lightning", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster leaves a trail of Shocked Ground as they move.", + }, + modList = { + }, +} + +mods["PlayerMonsterShockedGroundTrail1"] = { + name = "Trail of Lightning", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterImmuneToSlow1"] = { + name = "Slow Resistant", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has 50% reduced Slowing Potency of Debuffs on them.", + "50% less Slowing Potency of Debuffs on me", + }, + modList = { + -- MonsterImmuneToSlow1 [monster_slow_potency_+%_final = -50] + }, +} + +mods["PlayerMonsterImmuneToSlow1"] = { + name = "Slow Resistant", + type = "Suffix", + tier = 1, + statDescriptions = { + "50% less Slowing Potency of Debuffs on me", + }, + modList = { + -- PlayerMonsterImmuneToSlow1 [monster_slow_potency_+%_final = -50] + }, +} + +mods["MonsterChaosResistance1"] = { + name = "Chaos Resistant", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Monster has +50% to Chaos Resistance.", + }, + modList = { + mod("ChaosResist", "BASE", 50, 0, 0), -- MonsterChaosResistance1 [base_chaos_damage_resistance_% = 50] + }, +} + +mods["PlayerMonsterChaosResistance1"] = { + name = "Chaos Resistant", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("ChaosResist", "BASE", 50, 0, 0), -- PlayerMonsterChaosResistance1 [base_chaos_damage_resistance_% = 50] + }, +} + +mods["MonsterCannotLeech1"] = { + name = "of Congealment", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- MonsterCannotLeech1 [life_leeched_from_-permyriad = 6000] + -- MonsterCannotLeech1 [mana_leeched_from_-permyriad = 4000] + -- MonsterCannotLeech1 [energy_shield_leeched_from_-permyriad = 6000] + }, +} + +mods["PlayerMonsterCannotLeech1"] = { + name = "of Congealment", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterCannotLeech1 [life_leeched_from_-permyriad = 6000] + -- PlayerMonsterCannotLeech1 [mana_leeched_from_-permyriad = 4000] + -- PlayerMonsterCannotLeech1 [energy_shield_leeched_from_-permyriad = 6000] + }, +} + +mods["MonsterIsHexproof1"] = { + name = "of Hexproof", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Hexproof", + }, + modList = { + -- MonsterIsHexproof1 [hexproof = 1] + }, +} + +mods["PlayerMonsterIsHexproof1"] = { + name = "of Hexproof", + type = "Suffix", + tier = 1, + statDescriptions = { + "Hexproof", + }, + modList = { + -- PlayerMonsterIsHexproof1 [hexproof = 1] + }, +} + +mods["MonsterAdditionalChains1"] = { + name = "of Chaining", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("ChainCountMax", "BASE", 2, 0, 0), -- MonsterAdditionalChains1 [number_of_chains = 2] + -- MonsterAdditionalChains1 [projectile_chain_from_terrain_chance_% = 50] + }, +} + +mods["PlayerMonsterAdditionalChains1"] = { + name = "of Chaining", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("ChainCountMax", "BASE", 2, 0, 0), -- PlayerMonsterAdditionalChains1 [number_of_chains = 2] + -- PlayerMonsterAdditionalChains1 [projectile_chain_from_terrain_chance_% = 50] + }, +} + +mods["MonsterProjectilesGainDamage1"] = { + name = "of Far Shot", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- MonsterProjectilesGainDamage1 [projectile_damage_+%_max_as_distance_travelled_increases = 60] + }, +} + +mods["PlayerMonsterProjectilesGainDamage1"] = { + name = "of Far Shot", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterProjectilesGainDamage1 [projectile_damage_+%_max_as_distance_travelled_increases = 60] + }, +} + +mods["MonsterModReducedCritMulti1"] = { + name = "Crit Resistant", + rollable = true, + type = "Suffix", + tier = 1, + statDescriptions = { + "Hits against this Monster have 80% reduced Critical Damage Bonus.", + }, + modList = { + mod("SelfCritMultiplier", "INC", -80, 0, 0), -- MonsterModReducedCritMulti1 [base_self_critical_strike_multiplier_-% = 80] + }, +} + +mods["PlayerMonsterModReducedCritMulti1"] = { + name = "Crit Resistant", + type = "Suffix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("SelfCritMultiplier", "INC", -80, 0, 0), -- PlayerMonsterModReducedCritMulti1 [base_self_critical_strike_multiplier_-% = 80] + }, +} + +mods["MonsterLastGasp1"] = { + name = "TBD", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- MonsterLastGasp1 [retaliation_godmode_ghost_duration_ms = 10000] + -- MonsterLastGasp1 [corpse_cannot_be_destroyed = 1] + -- MonsterLastGasp1 [cannot_be_dominated = 1] + }, +} + +mods["PlayerMonsterLastGasp1"] = { + name = "TBD", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterLastGasp1 [retaliation_godmode_ghost_duration_ms = 10000] + -- PlayerMonsterLastGasp1 [corpse_cannot_be_destroyed = 1] + -- PlayerMonsterLastGasp1 [cannot_be_dominated = 1] + }, +} + +mods["MonsterFlameBeacons1"] = { + name = "Periodic Fire Explosions", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterFlameBeacons1"] = { + name = "Periodic Fire Explosions", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterFrostBeacons1"] = { + name = "Periodic Cold Explosions", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterFrostBeacons1"] = { + name = "Periodic Cold Explosions", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterLightningBeacons1"] = { + name = "Periodic Lightning Explosions", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterLightningBeacons1"] = { + name = "Periodic Lightning Explosions", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterStrongerMinions1"] = { + name = "Powerful Minions", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster's Pack Minions have 25% increased Damage and 50% increased Life.", + }, + modList = { + }, +} + +mods["PlayerMonsterStrongerMinions1"] = { + name = "Powerful Minions", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterPhysicalDamageAura1"] = { + name = "Extra Physical Damage Aura", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates an Aura that grants 40% increased Physical Damage to Allies within 5 metres.", + }, + modList = { + mod("PhysicalDamage", "INC", 40, 0, 0), -- MonsterPhysicalDamageAura1 [physical_damage_+% = 40] + }, +} + +mods["PlayerMonsterPhysicalDamageAura1"] = { + name = "Extra Physical Damage Aura", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("PhysicalDamage", "INC", 40, 0, 0), -- PlayerMonsterPhysicalDamageAura1 [physical_damage_+% = 40] + }, +} + +mods["MonsterIncreasedSpeedAura1"] = { + name = "Haste Aura", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates an Aura that grants 20% increased Attack and Cast speed and 10% increased Movement speed to Allies within 5 metres.", + }, + modList = { + mod("Speed", "INC", 25, 0, 0), -- MonsterIncreasedSpeedAura1 [attack_and_cast_speed_+% = 25] + mod("MovementSpeed", "INC", 25, 0, 0), -- MonsterIncreasedSpeedAura1 [base_movement_velocity_+% = 25] + }, +} + +mods["PlayerMonsterIncreasedSpeedAura1"] = { + name = "Haste Aura", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("Speed", "INC", 25, 0, 0), -- PlayerMonsterIncreasedSpeedAura1 [attack_and_cast_speed_+% = 25] + mod("MovementSpeed", "INC", 25, 0, 0), -- PlayerMonsterIncreasedSpeedAura1 [base_movement_velocity_+% = 25] + }, +} + +mods["PlayerMonsterIncreasedSpeedAuraMinion1"] = { + name = "Haste Aura", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("Speed", "INC", 20, 0, 0), -- PlayerMonsterIncreasedSpeedAuraMinion1 [attack_and_cast_speed_+% = 20] + mod("MovementSpeed", "INC", 10, 0, 0), -- PlayerMonsterIncreasedSpeedAuraMinion1 [base_movement_velocity_+% = 10] + }, +} + +mods["MonsterEnergyShieldAura1"] = { + name = "Energy Shield Aura", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates an Aura that grants 20% of Maximum life as added Energy Shield to Allies within 5 metres.", + }, + modList = { + -- MonsterEnergyShieldAura1 [base_maximum_life_%_to_gain_as_total_energy_shield = 30] + }, +} + +mods["PlayerMonsterEnergyShieldAura1"] = { + name = "Energy Shield Aura", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterEnergyShieldAura1 [base_maximum_life_%_to_gain_as_total_energy_shield = 30] + }, +} + +mods["PlayerMonsterEnergyShieldAuraMinion1"] = { + name = "Energy Shield Aura", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterEnergyShieldAuraMinion1 [base_maximum_life_%_to_gain_as_total_energy_shield = 20] + }, +} + +mods["MonsterResistanceAura1"] = { + name = "Elemental Resistance Aura", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates an Aura that grants +35% to all Elemental Resistances to Allies within 5 metres.", + }, + modList = { + mod("ElementalResist", "BASE", 35, 0, 0), -- MonsterResistanceAura1 [base_resist_all_elements_% = 35] + }, +} + +mods["PlayerMonsterResistanceAura1"] = { + name = "Elemental Resistance Aura", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + mod("ElementalResist", "BASE", 35, 0, 0), -- PlayerMonsterResistanceAura1 [base_resist_all_elements_% = 35] + }, +} + +mods["MonsterTemporalAura1"] = { + name = "Temporal Bubble", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates an Aura that Debuffs Enemies within 3.2 metres; Slowing by 25%, making effects expire 40% slower and reducing Cooldown Recovery Rate by 60%.", + }, + modList = { + -- MonsterTemporalAura1 [action_speed_-% = 25] + -- MonsterTemporalAura1 [debuff_time_passed_+% = -40] + mod("CooldownRecovery", "INC", -60, 0, 0), -- MonsterTemporalAura1 [base_cooldown_speed_+% = -60] + -- MonsterTemporalAura1 [cannot_be_damaged_by_things_outside_radius = 0] + }, +} + +mods["PlayerMonsterTemporalAura1"] = { + name = "Temporal Bubble", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterTemporalAura1 [action_speed_-% = 25] + -- PlayerMonsterTemporalAura1 [debuff_time_passed_+% = -40] + mod("CooldownRecovery", "INC", -60, 0, 0), -- PlayerMonsterTemporalAura1 [base_cooldown_speed_+% = -60] + -- PlayerMonsterTemporalAura1 [cannot_be_damaged_by_things_outside_radius = 0] + }, +} + +mods["PlayerMonsterTemporalAuraMinion1"] = { + name = "Temporal Bubble", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterTemporalAuraMinion1 [action_speed_-% = 12] + -- PlayerMonsterTemporalAuraMinion1 [debuff_time_passed_+% = -40] + mod("CooldownRecovery", "INC", -20, 0, 0), -- PlayerMonsterTemporalAuraMinion1 [base_cooldown_speed_+% = -20] + -- PlayerMonsterTemporalAuraMinion1 [cannot_be_damaged_by_things_outside_radius = 0] + }, +} + +mods["MonsterHinderAura1"] = { + name = "Hinder Aura", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates an Aura that Hinders enemies within 3.6 metres.", + }, + modList = { + }, +} + +mods["PlayerMonsterHinderAura1"] = { + name = "Hinder Aura", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterPreventRecoveryAura1"] = { + name = "Prevents Recovery Above 50%", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates an Aura that Debuffs enemies within 4.2 metres, causing their Life and Energy Shield to not be able to recover past 50%.", + }, + modList = { + -- MonsterPreventRecoveryAura1 [cannot_recover_life_or_energy_shield_above_% = 50] + }, +} + +mods["PlayerMonsterPreventRecoveryAura1"] = { + name = "Prevents Recovery Above 50%", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterImmuneAura1"] = { + name = "Periodic Invulnerability Aura", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster releases a nova that makes Allies invulnerable for 5 seconds while the monster is alive within 5 metres every 12 seconds.", + }, + modList = { + -- MonsterImmuneAura1 [monster_allies_cannot_take_damage_pulse_owner = 1] + }, +} + +mods["PlayerMonsterImmuneAura1"] = { + name = "Periodic Invulnerability Aura", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterImmuneAura1 [monster_allies_cannot_take_damage_pulse_owner = 1] + }, +} + +mods["MonsterImmuneAura2"] = { + name = "Empowered Periodic Invulnerability Aura", + rollable = true, + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster releases a nova that makes Allies invulnerable for 5 seconds while the monster is alive within 5 metres every 12 seconds.", + }, + modList = { + -- MonsterImmuneAura2 [monster_allies_cannot_take_damage_pulse_owner = 1] + }, +} + +mods["PlayerMonsterImmuneAura2"] = { + name = "Empowered Periodic Invulnerability Aura", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + -- PlayerMonsterImmuneAura2 [monster_allies_cannot_take_damage_pulse_owner = 1] + }, +} + +mods["MonsterImmuneAuraMinion2"] = { + name = "Increased Life", + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster has 50% increased Life.", + }, + modList = { + -- MonsterImmuneAuraMinion2 [base_maximum_life = 0] + -- MonsterImmuneAuraMinion2 [maximum_life_+% = 50] + }, +} + +mods["MonsterManaSiphonAura1"] = { + name = "Siphons Mana and Deals Lightning Damage", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates a circular effect that drains Mana and deals Lightning Damage over time to enemies near the edge of the circle.", + }, + modList = { + }, +} + +mods["PlayerMonsterManaSiphonAura1"] = { + name = "Siphons Mana and Deals Lightning Damage", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterManaSiphonAura2"] = { + name = "Siphons Mana and Deals Lightning Damage", + rollable = true, + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster creates a circular effect that drains Mana and deals Lightning Damage over time to enemies near the edge of the circle. Additionally, Monster will periodically create separate circles that drain Mana and deal Lightning Damage over time to enemies standing in them.", + }, + modList = { + }, +} + +mods["PlayerMonsterManaSiphonAura2"] = { + name = "Siphons Mana and Deals Lightning Damage", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterHealingNova1"] = { + name = "Heals Allies and Suppresses Foe Recovery", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster releases a nova that reduces Enemy Life and Energy Shield Recovery Rate by 60% and causes Allies to Regenerate 5.5% of Maximum Life per second for 4 seconds within 5 metres every 8 seconds.", + }, + modList = { + }, +} + +mods["PlayerMonsterHealingNova1"] = { + name = "Heals Allies and Suppresses Foe Recovery", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterFlaskRemovalAura1"] = { + name = "Siphons Flask Charges", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates an Aura that removes 3 Flask and Charm charges from enemies every 3 seconds within 3.6 metres.", + }, + modList = { + -- MonsterFlaskRemovalAura1 [generate_x_charges_for_any_flask_per_minute = -3] + }, +} + +mods["PlayerMonsterFlaskRemovalAura1"] = { + name = "Siphons Flask Charges", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterFlaskRemovalAura1 [generate_x_charges_for_any_flask_per_minute = -3] + }, +} + +mods["MonsterRevivesMinions1"] = { + name = "Reviving Minions", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster periodically revives Pack Minions.", + }, + modList = { + }, +} + +mods["PlayerMonsterRevivesMinions1"] = { + name = "Reviving Minions", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterRevivesMinions2"] = { + name = "Empowered Reviving Minions", + rollable = true, + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster periodically revives Pack Minions with increased Life and Damage.", + }, + modList = { + }, +} + +mods["PlayerMonsterRevivesMinions2"] = { + name = "Empowered Reviving Minions", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterMinionsTakeLifeInstead1"] = { + name = "Damage Taken From Minions First", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "50% of damage taken from Monster is taken from Monster's Pack Minions instead", + }, + modList = { + -- MonsterMinionsTakeLifeInstead1 [damage_removed_from_pack_minions_before_life_or_es_% = 50] + }, +} + +mods["PlayerMonsterMinionsTakeLifeInstead1"] = { + name = "Damage Taken From Minions First", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterMinionsTakeLifeInstead1 [damage_removed_from_pack_minions_before_life_or_es_% = 50] + }, +} + +mods["MonsterShroudWalker1"] = { + name = "Shroud Walker", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster periodically teleports to an enemy they can see, creating a Smoke Cloud where they leave and where they teleport to.", + }, + modList = { + }, +} + +mods["PlayerMonsterShroudWalker1"] = { + name = "Shroud Walker", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterShroudWalker2"] = { + name = "Shroud Walker", + rollable = true, + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster periodically teleports to an enemy they can see, creating a Smoke Cloud where they leave and where they teleport to.", + }, + modList = { + }, +} + +mods["PlayerMonsterShroudWalker2"] = { + name = "Shroud Walker", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterPeriodicEnrage1"] = { + name = "Periodically Enrages", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster periodically Enrages; gaining 30% increased Damage, 25% increased Skill and Movement Speed and 33% less damage taken for 5 seconds every 10 seconds.", + }, + modList = { + }, +} + +mods["PlayerMonsterPeriodicEnrage1"] = { + name = "Periodically Enrages", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterPeriodicEnrage2"] = { + name = "Enraged", + rollable = true, + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster is Enraged; gaining 30% increased Damage, 25% increased Skill and Movement Speed and 33% less damage taken.", + }, + modList = { + }, +} + +mods["PlayerMonsterPeriodicEnrage2"] = { + name = "Enraged", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterPeriodicEnrageMinion2"] = { + name = "Enraged", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterCorpseExploder1"] = { + name = "Explodes Nearby Corpses", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterCorpseExploder1"] = { + name = "Explodes Nearby Corpses", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterLightningMirage1"] = { + name = "Lightning Mirage When Hit", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates a Mirage when Hit that moves towards enemies and explodes when it gets close enough, dealing Lightning Damage.", + }, + modList = { + }, +} + +mods["PlayerMonsterLightningMirage1"] = { + name = "Lightning Mirage When Hit", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterLightningMirage2"] = { + name = "Lightning Mirages When Hit", + rollable = true, + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster creates Mirages when Hit that move towards enemies and explode when they get close enough, dealing Lightning Damage.", + }, + modList = { + }, +} + +mods["PlayerMonsterLightningMirage2"] = { + name = "Lightning Mirages When Hit", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterMagmaBarrier1"] = { + name = "Magma Barrier", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates a 90% damage absorption barrier that explodes after taking a certain amount of damage, dealing Fire Damage", + }, + modList = { + }, +} + +mods["PlayerMonsterMagmaBarrier1"] = { + name = "Magma Barrier", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterFlamewaller1"] = { + name = "Conjures Flamewalls", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster creates circular walls of Fire that deal damage to enemies standing in them.", + }, + modList = { + }, +} + +mods["PlayerMonsterFlamewaller1"] = { + name = "Conjures Flamewalls", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterLightningStorms1"] = { + name = "Conjures Lightning Storms", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterLightningStorms1"] = { + name = "Conjures Lightning Storms", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterElementalWaller1"] = { + name = "Conjures Elemental Hazards", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterElementalWaller1"] = { + name = "Conjures Elemental Hazards", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterVolatilePlants1"] = { + name = "Volatile Plants", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster periodically creates Volatile Plants, releasing orbs that move towards enemies; exploding when they get close enough; dealing Chaos Damage.", + }, + modList = { + }, +} + +mods["PlayerMonsterVolatilePlants1"] = { + name = "Volatile Plants", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterVolatilePlants2"] = { + name = "Empowering Volatile Plants", + rollable = true, + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster periodically creates Powerful Volatile Plants, releasing orbs that move towards enemies; exploding when they get close enough; dealing Chaos Damage.", + }, + modList = { + }, +} + +mods["PlayerMonsterVolatilePlants2"] = { + name = "Empowering Volatile Plants", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterVolatileRocks1"] = { + name = "Volatile Crag", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster periodically creates Volatile Crag that moves towards enemies; exploding when they get close enough; dealing Fire Damage.", + }, + modList = { + }, +} + +mods["PlayerMonsterVolatileRocks1"] = { + name = "Volatile Crag", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterVolatileRocks2"] = { + name = "Empowering Volatile Crag", + rollable = true, + type = "Prefix", + tier = 2, + statDescriptions = { + "Monster periodically creates Powerful Volatile Crag that moves towards enemies; exploding when they get close enough; dealing Fire Damage.", + }, + modList = { + }, +} + +mods["PlayerMonsterVolatileRocks2"] = { + name = "Empowering Volatile Crag", + type = "Prefix", + tier = 2, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterProximalTangibility1"] = { + name = "Proximal Tangibility", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster cannot be damaged by enemies any further than 3 metres from them.", + }, + modList = { + -- MonsterProximalTangibility1 [ignore_cannot_be_damaged_by_enemies = 0] + }, +} + +mods["PlayerMonsterProximalTangibility1"] = { + name = "Proximal Tangibility", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterProximalTangibility1 [ignore_cannot_be_damaged_by_enemies = 0] + }, +} + +mods["MonsterBombardier1"] = { + name = "Bombardier", + type = "Prefix", + tier = 1, + statDescriptions = { + "Monster periodically unleashes barrages of Fire projectiles.", + }, + modList = { + }, +} + +mods["PlayerMonsterBombardier1"] = { + name = "Periodically unleashes Fire", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterSoulEater1"] = { + name = "Soul Eater", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- MonsterSoulEater1 [grant_actor_scale_+%_to_aura_owner_on_death = 0] + -- MonsterSoulEater1 [grant_attack_speed_+%_to_aura_owner_on_death = 1] + -- MonsterSoulEater1 [grant_damage_reduction_%_to_aura_owner_on_death = -1] + -- MonsterSoulEater1 [soul_is_consumed_on_death = 1] + }, +} + +mods["PlayerMonsterSoulEater1"] = { + name = "Soul Eater", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterSoulEater1 [grant_actor_scale_+%_to_aura_owner_on_death = 0] + -- PlayerMonsterSoulEater1 [grant_attack_speed_+%_to_aura_owner_on_death = 1] + -- PlayerMonsterSoulEater1 [grant_damage_reduction_%_to_aura_owner_on_death = -1] + -- PlayerMonsterSoulEater1 [soul_is_consumed_on_death = 1] + }, +} + +mods["MonsterAbyssalCrystalMineWall1"] = { + name = "Crystalline Barrier", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssalCrystalMineWall1"] = { + name = "Crystalline Barrier", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssVolatileRocks1"] = { + name = "Volatile Souls", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssVolatileRocks1"] = { + name = "Volatile Souls", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssSiphonAura1"] = { + name = "Soul Siphoner", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssSiphonAura1"] = { + name = "Soul Siphoner", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssLastGasp1"] = { + name = "Kurgal's Last Gasp", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssLastGasp1"] = { + name = "Kurgal's Last Gasp", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssLightlessFaction1"] = { + name = "Amanamu's Void", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssLightlessFaction1"] = { + name = "Amanamu's Void", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssLeechAura"] = { + name = "Lifestealer Aura", + type = "Prefix", + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssApparitionMirage1"] = { + name = "Unstable Revenants", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssImmuneAura1"] = { + name = "Undying Will", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- MonsterAbyssImmuneAura1 [ignore_cannot_be_damaged_by_enemies = 1] + }, +} + +mods["PlayerMonsterAbyssImmuneAura1"] = { + name = "Undying Will", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- PlayerMonsterAbyssImmuneAura1 [ignore_cannot_be_damaged_by_enemies = 0] + }, +} + +mods["MonsterAbyssFactionRunes"] = { + name = "Lithomantic Runes", + type = "Prefix", + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssFactionRunes"] = { + name = "Lithomantic Runes", + type = "Prefix", + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssMeteor"] = { + name = "Meteoric Demise", + type = "Prefix", + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssMeteor"] = { + name = "Meteoric Demise", + type = "Prefix", + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssApparitionBeamcaster"] = { + name = "Kulemak's Desecration", + type = "Prefix", + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssApparitionBeamcaster"] = { + name = "Kulemak's Desecration", + type = "Prefix", + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssPitSplitting"] = { + name = "Ulaman's Legion", + type = "Prefix", + statDescriptions = { + }, + modList = { + -- MonsterAbyssPitSplitting [grant_actor_scale_+%_to_aura_owner_on_death = 0] + -- MonsterAbyssPitSplitting [grant_attack_speed_+%_to_aura_owner_on_death = 0] + -- MonsterAbyssPitSplitting [grant_damage_reduction_%_to_aura_owner_on_death = 0] + -- MonsterAbyssPitSplitting [soul_is_consumed_on_death = 0] + }, +} + +mods["PlayerMonsterAbyssPitSplitting"] = { + name = "Ulaman's Legion", + type = "Prefix", + statDescriptions = { + }, + modList = { + -- PlayerMonsterAbyssPitSplitting [grant_actor_scale_+%_to_aura_owner_on_death = 0] + -- PlayerMonsterAbyssPitSplitting [grant_attack_speed_+%_to_aura_owner_on_death = 0] + -- PlayerMonsterAbyssPitSplitting [grant_damage_reduction_%_to_aura_owner_on_death = 0] + -- PlayerMonsterAbyssPitSplitting [soul_is_consumed_on_death = 0] + }, +} + +mods["MonsterAbyssPustuleGround1"] = { + name = "Bubonic Trail", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssPustuleGround1"] = { + name = "Bubonic Trail", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssGeyserWalls1"] = { + name = "Soulflame Geysers", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssGeyserWalls1"] = { + name = "Soulflame Geysers", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssShadeWalker1"] = { + name = "Shade Walker", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssShadeWalker1"] = { + name = "Shade Walker", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterAbyssSoulcano1"] = { + name = "Eruption of Souls", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["PlayerMonsterAbyssSoulcano1"] = { + name = "Eruption of Souls", + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + }, +} + +mods["MonsterProximalTangibilityHidden1"] = { + name = "Ethereal", + rollable = true, + type = "Prefix", + tier = 1, + statDescriptions = { + }, + modList = { + -- MonsterProximalTangibilityHidden1 [ignore_cannot_be_damaged_by_enemies = 0] + }, +} diff --git a/src/Export/Scripts/tamedBeastMods.lua b/src/Export/Scripts/tamedBeastMods.lua new file mode 100644 index 0000000000..9b27ac27fe --- /dev/null +++ b/src/Export/Scripts/tamedBeastMods.lua @@ -0,0 +1,177 @@ +local function makeSkillMod(modName, modType, modVal, flags, keywordFlags, ...) + return { + name = modName, + type = modType, + value = modVal, + flags = flags or 0, + keywordFlags = keywordFlags or 0, + ... + } +end +local function makeFlagMod(modName, ...) + return makeSkillMod(modName, "FLAG", true, 0, 0, ...) +end +local function makeSkillDataMod(dataKey, dataValue, ...) + return makeSkillMod("SkillData", "LIST", { key = dataKey, value = dataValue }, 0, 0, ...) +end +dofile("../Data/Global.lua") +local skillStatMap = LoadModule("../Data/SkillStatMap.lua", makeSkillMod, makeFlagMod, makeSkillDataMod) + +if not loadStatFile then + dofile("statdesc.lua") +end +-- Tamed beasts are itemised captured monsters; their mods render with this description file +loadStatFile("monster_stat_descriptions.csd") + +local function tableToString(tbl, pre) + pre = pre or "" + local tableString = "{ " + local outNames = { } + for name in pairs(tbl) do + table.insert(outNames, name) + end + table.sort(outNames) + for _, name in ipairs(outNames) do + if type(tbl[name]) == "table" then + tableString = tableString .. tableToString(tbl[name], pre .. name .. ".") + else + if _ > 1 then + tableString = tableString .. ", " + end + tableString = tableString .. pre .. name .. " = " .. (type(tbl[name]) == "string" and '"' or '') .. tostring(tbl[name]) .. (type(tbl[name]) == "string" and '"' or '') + end + end + return tableString .. " }" +end + +local function escapeString(text) + return text:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub("%s*[\r\n]+%s*", " ") +end + +-- GGG keyword markup is stripped with the shared escapeGGGString (Modules/Common.lua, +-- loaded by the export tool) — the same function the importer applies to incoming lines, +-- so stored display lines always match imported ones verbatim. + +local MONSTER_DOMAIN = 3 -- see modDomains in enums.lua +local GEN_PREFIX = 1 +local GEN_SUFFIX = 2 + +-- Player-facing display names for monster mods; this is the same keyword popup +-- system the character API uses for its "[ModId|Display]" markup. The popup +-- Description is the full explanation shown in-game (good tooltip text). +local popupNames, popupDescriptions = { }, { } +for popup in dat("KeywordPopups"):Rows() do + if popup.Name and popup.Name ~= "" then + popupNames[popup.Id] = popup.Name + end + if popup.Description and popup.Description ~= "" then + popupDescriptions[popup.Id] = popup.Description + end +end +-- Nameplate lines for rare monster mods without keyword popups (e.g. "Periodically +-- unleashes [Cold|Ice]"); the table name is a PoE1 leftover GGG reuses in PoE2 +local archNames = { } +for archMod in dat("ArchnemesisMods"):Rows() do + if archMod.Id and archMod.Name and archMod.Name ~= "" then + archNames[archMod.Id.Id] = archMod.Name + end +end + +local out = io.open("../Data/TamedBeastMods.lua", "w") +out:write('-- This file is automatically generated, do not edit!\n') +out:write('-- Path of Building\n') +out:write('--\n') +out:write('-- Tamed Beast (Companion) Modifier Data\n') +out:write('-- Monster data (c) Grinding Gear Games\n') +out:write('\n') +out:write('local mods, mod, flag = ...\n') + +local exported, unmappedStats = 0, { } +for modRow in dat("Mods"):Rows() do + if modRow.Domain == MONSTER_DOMAIN and (modRow.GenerationType == GEN_PREFIX or modRow.GenerationType == GEN_SUFFIX) and not modRow.Id:match("Royale") then + -- Render description lines at min roll; min == max keeps the text free of "(min-max)" ranges + -- so the importer can match display lines verbatim + local stats = { } + for i = 1, 6 do + if modRow["Stat"..i] then + stats[modRow["Stat"..i].Id] = { min = modRow["Stat"..i.."Value"][1], max = modRow["Stat"..i.."Value"][1] } + end + end + if modRow.Type then + stats.Type = modRow.Type + end + local descLines = describeStats(stats) + local popupName = popupNames[modRow.Id] + local archName = archNames[modRow.Id] and escapeGGGString(archNames[modRow.Id]) + local popupDescription = popupDescriptions[modRow.Id] and escapeGGGString(popupDescriptions[modRow.Id]) + if popupName or archName or descLines[1] or modRow.Name ~= "" then + local name = popupName or archName or (modRow.Name ~= "" and modRow.Name) or descLines[1] + -- A mod can only be rolled (and thus appear on a captured beast) if some monster + -- tag carries a positive spawn weight; script-applied mods (abyss etc.) and the + -- "PlayerMonster*" twins are all-zero. Unnamed placeholders are not selectable. + local rollable = false + for _, weight in ipairs(modRow.SpawnWeight) do + if weight > 0 then + rollable = true + break + end + end + out:write('\nmods["', modRow.Id, '"] = {\n') + out:write('\tname = "', escapeString(name), '",\n') + if rollable and name ~= "TBD" then + out:write('\trollable = true,\n') + end + out:write('\ttype = "', modRow.GenerationType == GEN_PREFIX and "Prefix" or "Suffix", '",\n') + local tier = tonumber(modRow.Id:match("(%d+)$")) + if tier then + out:write('\ttier = ', tier, ',\n') + end + out:write('\tstatDescriptions = {\n') + if archName and archName ~= name then + out:write('\t\t"', escapeString(archName), '",\n') + end + if popupDescription then + out:write('\t\t"', escapeString(popupDescription), '",\n') + end + for _, line in ipairs(descLines) do + out:write('\t\t"', escapeString(line), '",\n') + end + out:write('\t},\n') + out:write('\tmodList = {\n') + for i = 1, 6 do + if modRow["Stat"..i] then + local statId = modRow["Stat"..i].Id + local modStats = ' [' .. statId .. ' = ' .. modRow["Stat"..i.."Value"][1] .. ']' + if skillStatMap[statId] then + local newMod = skillStatMap[statId][1] + out:write('\t\tmod("', newMod.name, '", "', newMod.type, '", ', newMod.value and type(newMod.value) ~= "boolean" and tableToString(newMod.value) or (skillStatMap[statId].value or modRow["Stat"..i.."Value"][1] * (skillStatMap[statId].mult or 1) / (skillStatMap[statId].div or 1)), ', ', newMod.flags or 0, ', ', newMod.keywordFlags or 0) + for _, extra in ipairs(newMod) do + out:write(', ', tableToString(extra)) + end + out:write('), -- ', modRow.Id, modStats, '\n') + else + out:write('\t\t-- ', modRow.Id, modStats, '\n') + unmappedStats[statId] = (unmappedStats[statId] or 0) + 1 + end + end + end + out:write('\t},\n') + out:write('}\n') + exported = exported + 1 + end + end +end +out:close() + +print("Tamed beast mod data exported: " .. exported .. " mods.") +local unmappedList = { } +for statId, count in pairs(unmappedStats) do + table.insert(unmappedList, { statId = statId, count = count }) +end +table.sort(unmappedList, function(a, b) return a.count > b.count end) +if unmappedList[1] then + print(#unmappedList .. " stats had no SkillStatMap entry (emitted as comments):") + for _, entry in ipairs(unmappedList) do + print(" " .. entry.statId .. " (x" .. entry.count .. ")") + end +end diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 8dcf95a33d..17fd77c9c4 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1019,6 +1019,20 @@ function calcs.perform(env, skipEHP) for _, mod in ipairs(env.player.mainSkill.extraSkillModList) do env.minion.modDB:AddMod(mod) end + -- Rolled monster mods on rare tamed beast companions (imported from tamedBeastProperties). + local mainActiveEffect = env.player.mainSkill.activeEffect + local mainGrantedEffect = mainActiveEffect and mainActiveEffect.grantedEffect + local mainSrcInstance = mainActiveEffect and mainActiveEffect.srcInstance + if mainSrcInstance and mainSrcInstance.tamedBeastModList and mainGrantedEffect and mainGrantedEffect.minionList and mainGrantedEffect.name:match("^Companion") then + for _, beastMod in ipairs(mainSrcInstance.tamedBeastModList) do + local beastModData = beastMod.enabled and beastMod.modId and env.data.tamedBeastMods[beastMod.modId] + if beastModData then + for _, mod in ipairs(beastModData.modList) do + env.minion.modDB:AddMod(mod) + end + end + end + end if env.talismanModList then -- Adding mods provided by "Necromantic Talisman" env.minion.modDB:AddList(env.talismanModList) diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index 8fc98820d9..659c015cdc 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -996,6 +996,40 @@ for _, minion in pairs(data.minions) do mod.source = "Minion:"..minion.name end end +-- Load tamed beast (companion) monster mods +data.tamedBeastMods = { } +LoadModule("Data/TamedBeastMods", data.tamedBeastMods, makeSkillMod, makeFlagMod) +-- Normalises a beast mod display line for matching; nameplate lines are fixed labels +-- without roll values, so case folding and trimming suffice +function data.normaliseBeastModLine(line) + return (line:lower():gsub("^%s+", ""):gsub("%s+$", "")) +end +-- Secondary index for import lines that don't carry a "[ModId|...]" token; iterate ids in +-- sorted order so collisions deterministically resolve to the lowest id +data.tamedBeastModsByDisplay = { } +do + local sortedIds = { } + for id in pairs(data.tamedBeastMods) do + table.insert(sortedIds, id) + end + table.sort(sortedIds) + for _, id in ipairs(sortedIds) do + local beastMod = data.tamedBeastMods[id] + for _, mod in ipairs(beastMod.modList) do + mod.source = "Beast Mod:"..beastMod.name + end + for _, line in ipairs(beastMod.statDescriptions) do + local key = data.normaliseBeastModLine(line) + if not data.tamedBeastModsByDisplay[key] then + data.tamedBeastModsByDisplay[key] = id + end + end + local nameKey = data.normaliseBeastModLine(beastMod.name) + if not data.tamedBeastModsByDisplay[nameKey] then + data.tamedBeastModsByDisplay[nameKey] = id + end + end +end data.printMissingMinionSkills = function() local missing = { } for _, minion in pairs(data.minions) do From f2593c10320df3faf74008081548e433d5e849e8 Mon Sep 17 00:00:00 2001 From: Leonel Togniolli Date: Wed, 10 Jun 2026 14:06:50 -0300 Subject: [PATCH 2/2] Export companion mods by Player id with per-beast spawn validity Tamed beasts roll "Monster*" mods in the wild, but the itemised companion carries the standalone "PlayerMonster*" twin, which is what the character API reports. The exporter now enumerates ArchnemesisMods and emits only Player mods whose beast-side twin is a rollable prefix/suffix, resolving display text through the twin and replacing the rollable flag with the twin's spawn weight table. Mods whose spawn weights can never roll on the selected beast's tags (e.g. Haste Aura has fast_movement = 0) are ignored by calcs and flagged in red on the skill panel and the selection tooltip. Spectres.lua picks up "boss" monster tags for boss beasts so boss-gated mods (spawn weight boss = 0) validate correctly; the exporter change producing them ships separately. --- spec/System/TestTamedBeastMods_spec.lua | 113 +- src/Classes/SkillsTab.lua | 31 +- src/Data/Spectres.lua | 58 +- src/Data/TamedBeastMods.lua | 2097 ++++++----------------- src/Export/Scripts/tamedBeastMods.lua | 89 +- src/Modules/CalcPerform.lua | 3 +- src/Modules/Data.lua | 13 + 7 files changed, 712 insertions(+), 1692 deletions(-) diff --git a/spec/System/TestTamedBeastMods_spec.lua b/spec/System/TestTamedBeastMods_spec.lua index 501c5d6faf..5fe1888ea8 100644 --- a/spec/System/TestTamedBeastMods_spec.lua +++ b/spec/System/TestTamedBeastMods_spec.lua @@ -7,7 +7,7 @@ describe("TestTamedBeastMods", function() { name = "Monster Modifiers:\n{0}", values = { - { "[MonsterFlaskRemovalAura1|Siphons Flask Charges]\n[MonsterLifeRegenerationRatePercentage1|Regenerates Life]\nPeriodically unleashes [Cold|Ice]\n[MonsterAdditionalProjectiles1|Additional Projectiles]", 0 }, + { "[PlayerMonsterFlaskRemovalAura1|Siphons Flask Charges]\n[PlayerMonsterLifeRegenerationRatePercentage1|Regenerates Life]\nPeriodically unleashes [Cold|Ice]\n[PlayerMonsterAdditionalProjectiles1|Additional Projectiles]", 0 }, }, displayMode = 3, }, @@ -18,13 +18,13 @@ describe("TestTamedBeastMods", function() local list = build.importTab:ParseTamedBeastProperties(sampleProperties) assert.are.equals(4, #list) - assert.are.equals("MonsterFlaskRemovalAura1", list[1].modId) + assert.are.equals("PlayerMonsterFlaskRemovalAura1", list[1].modId) assert.are.equals("Siphons Flask Charges", list[1].display) - assert.are.equals("MonsterLifeRegenerationRatePercentage1", list[2].modId) + assert.are.equals("PlayerMonsterLifeRegenerationRatePercentage1", list[2].modId) -- No leading [ModId|...] token; resolved by display line lookup assert.are.equals("Periodically unleashes Ice", list[3].display) - assert.is_not_nil(list[3].modId) - assert.are.equals("MonsterAdditionalProjectiles1", list[4].modId) + assert.are.equals("PlayerMonsterChilledGroundOnDeath1", list[3].modId) + assert.are.equals("PlayerMonsterAdditionalProjectiles1", list[4].modId) for _, entry in ipairs(list) do assert.True(entry.enabled) end @@ -46,6 +46,63 @@ describe("TestTamedBeastMods", function() end) end) + describe("Exported data shape", function() + it("contains only Player-prefixed companion mods with spawn weights", function() + local count = 0 + for modId, beastMod in pairs(build.data.tamedBeastMods) do + count = count + 1 + assert.is_not_nil(modId:match("^PlayerMonster"), modId.." is not Player-prefixed") + assert.is_nil(beastMod.rollable, modId.." still carries the removed rollable flag") + assert.are.equals("string", type(beastMod.name)) + assert.True(beastMod.type == "Prefix" or beastMod.type == "Suffix") + assert.are.equals("table", type(beastMod.statDescriptions)) + assert.are.equals("table", type(beastMod.modList)) + assert.are.equals("table", type(beastMod.spawnWeights)) + assert.True(#beastMod.spawnWeights > 0, modId.." has no spawn weights") + for _, entry in ipairs(beastMod.spawnWeights) do + assert.are.equals("string", type(entry.tag)) + assert.are.equals("number", type(entry.weight)) + end + end + -- ~68 as of 0.5; loose band so balance patches don't break the suite + assert.True(count >= 50 and count <= 100, "unexpected mod count: "..count) + -- Script-applied twin (generation type 3) must not pass the prefix/suffix filter + assert.is_nil(build.data.tamedBeastMods["PlayerMonsterGlacialPrison1"]) + end) + + it("marks boss-only beasts via the object template is_boss flag", function() + local silverfist = build.data.minions["Metadata/Monsters/Quadrilla/QuadrillaBossMinion1"] + assert.is_not_nil(silverfist) + assert.True(isValueInArray(silverfist.monsterTags, "boss") ~= nil) + local wolf = build.data.minions["WolfMinion"] + assert.is_not_nil(wolf) + assert.is_nil(isValueInArray(wolf.monsterTags, "boss")) + end) + end) + + describe("data.beastModCanSpawn", function() + it("blocks zero-weight tag matches ahead of the default entry", function() + local hasteAura = build.data.tamedBeastMods["PlayerMonsterIncreasedSpeedAura1"] + assert.is_not_nil(hasteAura) + assert.False(build.data.beastModCanSpawn(hasteAura, { "fast_movement", "beast" })) + assert.True(build.data.beastModCanSpawn(hasteAura, { "beast" })) + end) + + it("requires positive-gate tags when the default weight is zero", function() + local alwaysBleeds = build.data.tamedBeastMods["PlayerMonsterAlwaysBleed1"] + assert.is_not_nil(alwaysBleeds) + assert.True(build.data.beastModCanSpawn(alwaysBleeds, { "physical_affinity" })) + assert.False(build.data.beastModCanSpawn(alwaysBleeds, { "beast" })) + assert.False(build.data.beastModCanSpawn(alwaysBleeds, nil)) + end) + + it("falls through to false when no entry matches and there is no default", function() + local beastMod = { spawnWeights = { { tag = "caster", weight = 1 } } } + assert.False(build.data.beastModCanSpawn(beastMod, { "beast" })) + assert.True(build.data.beastModCanSpawn(beastMod, { "caster" })) + end) + end) + describe("SkillsTab persistence", function() it("saves tamed beast mods as Gem child elements", function() build.skillsTab.skillSets[1].socketGroupList = { { @@ -55,7 +112,7 @@ describe("TestTamedBeastMods", function() level = 20, quality = 0, enabled = true, enableGlobal1 = true, enableGlobal2 = true, count = 1, corrupted = false, corruptLevel = 0, tamedBeastModList = { - { modId = "MonsterDamageGainedAsCold1", enabled = true }, + { modId = "PlayerMonsterDamageGainedAsCold1", enabled = true }, { display = "Periodically unleashes Ice", enabled = false }, }, } }, @@ -84,7 +141,7 @@ describe("TestTamedBeastMods", function() end end assert.are.equals(2, #beastModNodes) - assert.are.equals("MonsterDamageGainedAsCold1", beastModNodes[1].attrib.modId) + assert.are.equals("PlayerMonsterDamageGainedAsCold1", beastModNodes[1].attrib.modId) assert.are.equals("true", beastModNodes[1].attrib.enabled) assert.is_nil(beastModNodes[2].attrib.modId) assert.are.equals("Periodically unleashes Ice", beastModNodes[2].attrib.display) @@ -94,7 +151,7 @@ describe("TestTamedBeastMods", function() it("loads TamedBeastMod elements back onto the gem instance", function() local node = { elem = "Skill", attrib = { enabled = "true" }, { elem = "Gem", attrib = { nameSpec = "Companion: Mighty Silverfist", level = "20", quality = "0", enabled = "true" }, - { elem = "TamedBeastMod", attrib = { modId = "MonsterDamageGainedAsCold1", enabled = "true" } }, + { elem = "TamedBeastMod", attrib = { modId = "PlayerMonsterDamageGainedAsCold1", enabled = "true" } }, { elem = "TamedBeastMod", attrib = { display = "Periodically unleashes Ice", enabled = "false" } }, }, } @@ -105,7 +162,7 @@ describe("TestTamedBeastMods", function() local gemInstance = socketGroupList[#socketGroupList].gemList[1] assert.is_not_nil(gemInstance.tamedBeastModList) assert.are.equals(2, #gemInstance.tamedBeastModList) - assert.are.equals("MonsterDamageGainedAsCold1", gemInstance.tamedBeastModList[1].modId) + assert.are.equals("PlayerMonsterDamageGainedAsCold1", gemInstance.tamedBeastModList[1].modId) assert.True(gemInstance.tamedBeastModList[1].enabled) assert.is_nil(gemInstance.tamedBeastModList[2].modId) assert.are.equals("Periodically unleashes Ice", gemInstance.tamedBeastModList[2].display) @@ -125,7 +182,9 @@ describe("TestTamedBeastMods", function() end) describe("Calculation wiring", function() - local beastId = "Metadata/Monsters/Quadrilla/QuadrillaBossMinion1" -- Mighty Silverfist + -- Mighty Silverfist: monsterTags include both fast_movement and boss, so it can + -- never roll Haste Aura (fast_movement = 0) or Regenerates Life (boss = 0) + local beastId = "Metadata/Monsters/Quadrilla/QuadrillaBossMinion1" local function buildCompanionGroup(tamedBeastModList) table.insert(build.beastList, beastId) @@ -148,7 +207,7 @@ describe("TestTamedBeastMods", function() end it("applies enabled beast mods to the companion minion", function() - buildCompanionGroup({ { modId = "MonsterDamageGainedAsCold1", enabled = true } }) + buildCompanionGroup({ { modId = "PlayerMonsterDamageGainedAsCold1", enabled = true } }) local minion = build.calcsTab.mainEnv.minion assert.is_not_nil(minion) @@ -157,7 +216,7 @@ describe("TestTamedBeastMods", function() it("skips disabled and unresolved beast mods", function() local gemInstance = buildCompanionGroup({ - { modId = "MonsterDamageGainedAsCold1", enabled = false }, + { modId = "PlayerMonsterDamageGainedAsCold1", enabled = false }, { display = "Periodically unleashes Ice", enabled = true }, }) @@ -171,6 +230,18 @@ describe("TestTamedBeastMods", function() assert.are.equals(40, build.calcsTab.mainEnv.minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) end) + it("ignores enabled mods the selected beast can never roll", function() + buildCompanionGroup({ + { modId = "PlayerMonsterLifeRegenerationRatePercentage1", enabled = true }, -- boss = 0 + { modId = "PlayerMonsterDamageGainedAsCold1", enabled = true }, + }) + + local minion = build.calcsTab.mainEnv.minion + assert.is_not_nil(minion) + assert.are.equals(0, minion.modDB:Sum("BASE", nil, "LifeRegenPercent")) + assert.are.equals(40, minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) + end) + it("does not affect companions without beast mods", function() buildCompanionGroup(nil) @@ -183,7 +254,7 @@ describe("TestTamedBeastMods", function() build.skillsTab:PasteSocketGroup("Skeletal Sniper 20/0 1") runCallback("OnFrame") local srcInstance = build.skillsTab.socketGroupList[1].gemList[1] - srcInstance.tamedBeastModList = { { modId = "MonsterDamageGainedAsCold1", enabled = true } } + srcInstance.tamedBeastModList = { { modId = "PlayerMonsterDamageGainedAsCold1", enabled = true } } build.buildFlag = true runCallback("OnFrame") @@ -194,11 +265,11 @@ describe("TestTamedBeastMods", function() it("applies entries beyond the fourth and creates UI rows for them", function() buildCompanionGroup({ - { modId = "MonsterIncreasedSpeedAura1", enabled = false }, - { modId = "MonsterLifeRegenerationRatePercentage1", enabled = false }, - { modId = "MonsterAdditionalProjectiles1", enabled = false }, - { modId = "MonsterFlaskRemovalAura1", enabled = false }, - { modId = "MonsterDamageGainedAsCold1", enabled = true }, + { modId = "PlayerMonsterIncreasedSpeedAura1", enabled = false }, + { modId = "PlayerMonsterLifeRegenerationRatePercentage1", enabled = false }, + { modId = "PlayerMonsterAdditionalProjectiles1", enabled = false }, + { modId = "PlayerMonsterFlaskRemovalAura1", enabled = false }, + { modId = "PlayerMonsterDamageGainedAsCold1", enabled = true }, }) assert.are.equals(40, build.calcsTab.mainEnv.minion.modDB:Sum("BASE", nil, "DamageGainAsCold")) @@ -208,7 +279,7 @@ describe("TestTamedBeastMods", function() skillsTab:UpdateBeastModSlots() assert.is_not_nil(skillsTab.beastModSlots[6]) local slot5 = skillsTab.beastModSlots[5] - assert.are.equals("MonsterDamageGainedAsCold1", slot5.select.list[slot5.select.selIndex].modId) + assert.are.equals("PlayerMonsterDamageGainedAsCold1", slot5.select.list[slot5.select.selIndex].modId) end) end) @@ -223,7 +294,7 @@ describe("TestTamedBeastMods", function() properties = { { name = "Level", values = { { "17", 0 } } } }, tamedBeastProperties = { { name = "Monster Modifiers:\n{0}", - values = { { "[MonsterDamageGainedAsCold1|Extra Cold Damage]\n[MonsterIncreasedSpeedAura1|Haste Aura]", 0 } }, + values = { { "[PlayerMonsterDamageGainedAsCold1|Extra Cold Damage]\n[PlayerMonsterIncreasedSpeedAura1|Haste Aura]", 0 } }, displayMode = 3, } }, } }, @@ -246,7 +317,7 @@ describe("TestTamedBeastMods", function() gem = build.skillsTab.socketGroupList[1].gemList[1] assert.are.equals(2, #gem.tamedBeastModList) - assert.are.equals("MonsterDamageGainedAsCold1", gem.tamedBeastModList[1].modId) + assert.are.equals("PlayerMonsterDamageGainedAsCold1", gem.tamedBeastModList[1].modId) assert.False(gem.tamedBeastModList[1].enabled) assert.True(gem.tamedBeastModList[2].enabled) end) diff --git a/src/Classes/SkillsTab.lua b/src/Classes/SkillsTab.lua index cbb0fb2537..288df43bc5 100644 --- a/src/Classes/SkillsTab.lua +++ b/src/Classes/SkillsTab.lua @@ -1158,12 +1158,8 @@ function SkillsTabClass:GetBeastModDropList() local sorted = { } local nameCount = { } for modId, beastMod in pairs(self.build.data.tamedBeastMods) do - -- Non-rollable mods (script-applied, player-minion variants, placeholders) stay - -- resolvable on import but are not offered for selection - if beastMod.rollable then - t_insert(sorted, { modId = modId, beastMod = beastMod }) - nameCount[beastMod.name] = (nameCount[beastMod.name] or 0) + 1 - end + t_insert(sorted, { modId = modId, beastMod = beastMod }) + nameCount[beastMod.name] = (nameCount[beastMod.name] or 0) + 1 end table.sort(sorted, function(a, b) if a.beastMod.name ~= b.beastMod.name then @@ -1258,6 +1254,10 @@ function SkillsTabClass:CreateBeastModSlot(index) if not gemInstance then return end + local minion = gemInstance.skillMinion and self.build.data.minions[gemInstance.skillMinion] + if minion and not self.build.data.beastModCanSpawn(value.beastMod, minion.monsterTags) then + tooltip:AddLine(16, "^1Cannot naturally spawn on "..minion.name.." and will be ignored") + end local calcFunc, calcBase = self.build.calcsTab:GetMiscCalculator(self.build) if calcFunc then -- Trial-swap the entry for the compare, restoring both the entry and the @@ -1304,19 +1304,22 @@ function SkillsTabClass:CreateBeastModSlot(index) end self.controls["beastModSlot"..index.."Enable"] = slot.enabled - -- Imported modifier the dropdown can't represent: unknown id, or known but not in the - -- selectable pool. Either way the entry still applies and can be toggled or removed. + -- Per-row problem indicator: an imported entry the data doesn't know (stays toggleable + -- and removable, but applies nothing), or a known mod the selected beast's tags can + -- never roll (ignored by calcs, see CalcPerform) slot.unresolved = new("LabelControl", {"LEFT", slot.enabled, "RIGHT"}, {8, 0, 0, 16}, function() - local entry = getEntry() + local entry, gemInstance = getEntry() if not entry then return "" end - if not entry.modId then - return entry.display and ("^1Unrecognised: ^7"..entry.display) or "" + local beastMod = entry.modId and self.build.data.tamedBeastMods[entry.modId] + if not beastMod then + local display = entry.display or entry.modId + return display and ("^1Unrecognised: ^7"..display) or "" end - local beastMod = self.build.data.tamedBeastMods[entry.modId] - if not (beastMod and beastMod.rollable) then - return "^1Not selectable: ^7"..(beastMod and beastMod.name or entry.modId) + local minion = gemInstance.skillMinion and self.build.data.minions[gemInstance.skillMinion] + if minion and not self.build.data.beastModCanSpawn(beastMod, minion.monsterTags) then + return "^1Cannot spawn on ^7"..minion.name end return "" end) diff --git a/src/Data/Spectres.lua b/src/Data/Spectres.lua index d1ad258eab..48f0e1a4f5 100644 --- a/src/Data/Spectres.lua +++ b/src/Data/Spectres.lua @@ -20584,7 +20584,7 @@ minions["Metadata/Monsters/LeagueIncursionNew/Thaumaturge/VaalThaumaturgeSpear"] minions["Metadata/Monsters/LeagueIncursionNew/MiniBosses/SoulCoreQuadrillaBoss/SoulCoreQuadrillaMinion"] = { name = "Quadrilla Sergeant", - monsterTags = { "2HBluntStone_onhit_audio", "beast", "fast_movement", "humanoid", "incursion_unique_quadrilla", "not_dex", "not_int", "red_blood", "very_fast_movement", }, + monsterTags = { "2HBluntStone_onhit_audio", "beast", "fast_movement", "humanoid", "incursion_unique_quadrilla", "not_dex", "not_int", "red_blood", "very_fast_movement", "boss", }, life = 2.5, baseDamageIgnoresAttackSpeed = true, armour = 0.66, @@ -20626,7 +20626,7 @@ minions["Metadata/Monsters/LeagueIncursionNew/MiniBosses/SoulCoreQuadrillaBoss/S minions["Metadata/Monsters/LeagueIncursionNew/MiniBosses/IncursionChainedBeastBoss/ChainedBeastBossMinion_"] = { name = "Unchained Beast", - monsterTags = { "beast", "Claw_onhit_audio", "incursion_unique_chained_beast", "mammal_beast", "medium_movement", "not_dex", "not_int", "red_blood", }, + monsterTags = { "beast", "Claw_onhit_audio", "incursion_unique_chained_beast", "mammal_beast", "medium_movement", "not_dex", "not_int", "red_blood", "boss", }, life = 2.5, baseDamageIgnoresAttackSpeed = true, armour = 0.66, @@ -20740,7 +20740,7 @@ minions["Metadata/Monsters/LeagueExpeditionNew/Expedition2/HumanoidFaction/VaalF minions["Metadata/Monsters/CrowBell/CrowBellBossMinion1"] = { name = "The Crowbell", - monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "red_blood", }, + monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -20815,7 +20815,7 @@ minions["Metadata/Monsters/CrowBell/CrowBellBossMinion1"] = { minions["Metadata/Monsters/CrowBell/CrowBellBossMinion2"] = { name = "The Black Crow", - monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "red_blood", }, + monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -20890,7 +20890,7 @@ minions["Metadata/Monsters/CrowBell/CrowBellBossMinion2"] = { minions["Metadata/Monsters/MudBurrower/MudBurrowerHeadBossMinion1"] = { name = "The Devourer", - monsterTags = { "beast", "Beast_onhit_audio", "mammal_beast", "medium_movement", "not_dex", "not_int", "red_blood", }, + monsterTags = { "beast", "Beast_onhit_audio", "mammal_beast", "medium_movement", "not_dex", "not_int", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -20953,7 +20953,7 @@ minions["Metadata/Monsters/MudBurrower/MudBurrowerHeadBossMinion1"] = { minions["Metadata/Monsters/MudBurrower/MudBurrowerHeadBossMinion2"] = { name = "Gorian, the Moving Earth", - monsterTags = { "beast", "Beast_onhit_audio", "mammal_beast", "medium_movement", "not_dex", "not_int", "red_blood", }, + monsterTags = { "beast", "Beast_onhit_audio", "mammal_beast", "medium_movement", "not_dex", "not_int", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21016,7 +21016,7 @@ minions["Metadata/Monsters/MudBurrower/MudBurrowerHeadBossMinion2"] = { minions["Metadata/Monsters/ChimeraWetlandsBoss/ChimeraWetlandsBossMinion1"] = { name = "Xyclucian, the Chimera", - monsterTags = { "beast", "Claw_onhit_audio", "flying", "mammal_beast", "red_blood", "slow_movement", }, + monsterTags = { "beast", "Claw_onhit_audio", "flying", "mammal_beast", "red_blood", "slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21086,7 +21086,7 @@ minions["Metadata/Monsters/ChimeraWetlandsBoss/ChimeraWetlandsBossMinion1"] = { minions["Metadata/Monsters/ChimeraWetlandsBoss/ChimeraWetlandsBossMinion2"] = { name = "Xilozoma, the Maw-Beast", - monsterTags = { "beast", "Claw_onhit_audio", "flying", "mammal_beast", "red_blood", "slow_movement", }, + monsterTags = { "beast", "Claw_onhit_audio", "flying", "mammal_beast", "red_blood", "slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21156,7 +21156,7 @@ minions["Metadata/Monsters/ChimeraWetlandsBoss/ChimeraWetlandsBossMinion2"] = { minions["Metadata/Monsters/Ultimatum/ChimeraUltimatumBossMinion1"] = { name = "Uxmal, the Beastlord", - monsterTags = { "beast", "Claw_onhit_audio", "flying", "mammal_beast", "red_blood", "slow_movement", }, + monsterTags = { "beast", "Claw_onhit_audio", "flying", "mammal_beast", "red_blood", "slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21220,7 +21220,7 @@ minions["Metadata/Monsters/Ultimatum/ChimeraUltimatumBossMinion1"] = { minions["Metadata/Monsters/Ultimatum/ChimeraUltimatumBossMinion2"] = { name = "Gressor-Kul, the Apex", - monsterTags = { "beast", "Claw_onhit_audio", "flying", "mammal_beast", "red_blood", "slow_movement", }, + monsterTags = { "beast", "Claw_onhit_audio", "flying", "mammal_beast", "red_blood", "slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21284,7 +21284,7 @@ minions["Metadata/Monsters/Ultimatum/ChimeraUltimatumBossMinion2"] = { minions["Metadata/Monsters/Bird2/MutantBird2Minion1"] = { name = "Scourge of the Skies", - monsterTags = { "beast", "Beast_onhit_audio", "flying", "red_blood", "very_slow_movement", }, + monsterTags = { "beast", "Beast_onhit_audio", "flying", "red_blood", "very_slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21368,7 +21368,7 @@ minions["Metadata/Monsters/Bird2/MutantBird2Minion1"] = { minions["Metadata/Monsters/Bird2/MutantBird2Minion2"] = { name = "Chetza, the Feathered Plague", - monsterTags = { "beast", "Beast_onhit_audio", "flying", "red_blood", "very_slow_movement", }, + monsterTags = { "beast", "Beast_onhit_audio", "flying", "red_blood", "very_slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21452,7 +21452,7 @@ minions["Metadata/Monsters/Bird2/MutantBird2Minion2"] = { minions["Metadata/Monsters/HyenaMonster/RathbreakerBossMinion1"] = { name = "Rathbreaker", - monsterTags = { "2HSharpMetal_onhit_audio", "beast", "fast_movement", "mammal_beast", "melee", "physical_affinity", "red_blood", }, + monsterTags = { "2HSharpMetal_onhit_audio", "beast", "fast_movement", "mammal_beast", "melee", "physical_affinity", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21501,7 +21501,7 @@ minions["Metadata/Monsters/HyenaMonster/RathbreakerBossMinion1"] = { minions["Metadata/Monsters/HyenaMonster/RathbreakerBossMinion2"] = { name = "Caedron, the Hyena Lord", - monsterTags = { "2HSharpMetal_onhit_audio", "beast", "fast_movement", "mammal_beast", "melee", "physical_affinity", "red_blood", }, + monsterTags = { "2HSharpMetal_onhit_audio", "beast", "fast_movement", "mammal_beast", "melee", "physical_affinity", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21550,7 +21550,7 @@ minions["Metadata/Monsters/HyenaMonster/RathbreakerBossMinion2"] = { minions["Metadata/Monsters/Quadrilla/QuadrillaBossMinion1"] = { name = "Mighty Silverfist", - monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", }, + monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21595,7 +21595,7 @@ minions["Metadata/Monsters/Quadrilla/QuadrillaBossMinion1"] = { minions["Metadata/Monsters/Quadrilla/QuadrillaBossMinion2"] = { name = "Zekoa, the Headcrusher", - monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", }, + monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21640,7 +21640,7 @@ minions["Metadata/Monsters/Quadrilla/QuadrillaBossMinion2"] = { minions["Metadata/Monsters/Quadrilla/IcyQuadrillaBossMinion1"] = { name = "The Abominable Yeti", - monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", }, + monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21692,7 +21692,7 @@ minions["Metadata/Monsters/Quadrilla/IcyQuadrillaBossMinion1"] = { minions["Metadata/Monsters/Quadrilla/IcyQuadrillaBossMinion2"] = { name = "The Frostborn Fiend", - monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", }, + monsterTags = { "beast", "fast_movement", "mammal_beast", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21744,7 +21744,7 @@ minions["Metadata/Monsters/Quadrilla/IcyQuadrillaBossMinion2"] = { minions["Metadata/Monsters/GreatWhiteOne/GreatWhiteOneMinion1"] = { name = "Great White One", - monsterTags = { "beast", "fast_movement", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", }, + monsterTags = { "beast", "fast_movement", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21818,7 +21818,7 @@ minions["Metadata/Monsters/GreatWhiteOne/GreatWhiteOneMinion1"] = { minions["Metadata/Monsters/GreatWhiteOne/GreatWhiteOneMinion2"] = { name = "The Sandstrider", - monsterTags = { "beast", "fast_movement", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", }, + monsterTags = { "beast", "fast_movement", "MonsterBlunt_onhit_audio", "not_dex", "not_int", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21892,7 +21892,7 @@ minions["Metadata/Monsters/GreatWhiteOne/GreatWhiteOneMinion2"] = { minions["Metadata/Monsters/Goblins/Beast/ArenaBeastBossMinion1_"] = { name = "The Ravenous Fang", - monsterTags = { "beast", "Claw_onhit_audio", "mammal_beast", "medium_movement", "not_dex", "not_int", "red_blood", }, + monsterTags = { "beast", "Claw_onhit_audio", "mammal_beast", "medium_movement", "not_dex", "not_int", "red_blood", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -21961,7 +21961,7 @@ minions["Metadata/Monsters/Goblins/Beast/ArenaBeastBossMinion1_"] = { minions["Metadata/Monsters/ChaosGodOwlBoss/ChaosGodOwlBossMinion"] = { name = "Bahlak, the Sky Seer", - monsterTags = { "beast", "Beast_onhit_audio", "flying", "not_str", "red_blood", "slow_movement", }, + monsterTags = { "beast", "Beast_onhit_audio", "flying", "not_str", "red_blood", "slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -22025,7 +22025,7 @@ minions["Metadata/Monsters/ChaosGodOwlBoss/ChaosGodOwlBossMinion"] = { minions["Metadata/Monsters/ChaosGodOwlBoss/IcyOwlBossMinion1"] = { name = "Rakkar, the Frozen Talon", - monsterTags = { "beast", "Beast_onhit_audio", "flying", "not_str", "red_blood", "slow_movement", }, + monsterTags = { "beast", "Beast_onhit_audio", "flying", "not_str", "red_blood", "slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -22088,7 +22088,7 @@ minions["Metadata/Monsters/ChaosGodOwlBoss/IcyOwlBossMinion1"] = { minions["Metadata/Monsters/ChaosGodOwlBoss/IcyOwlBossMinion2"] = { name = "Thraeven, Wing of Winter", - monsterTags = { "beast", "Beast_onhit_audio", "flying", "not_str", "red_blood", "slow_movement", }, + monsterTags = { "beast", "Beast_onhit_audio", "flying", "not_str", "red_blood", "slow_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -22151,7 +22151,7 @@ minions["Metadata/Monsters/ChaosGodOwlBoss/IcyOwlBossMinion2"] = { minions["Metadata/Monsters/MarakethSanctumTrial/Boss/Shakari/ShakariMinion1_"] = { name = "Ashar, the Sand Mother", - monsterTags = { "beast", "fast_movement", "insect", "MonsterStab_onhit_audio", "not_dex", "not_int", "red_blood", "sanctum_monster", "very_fast_movement", }, + monsterTags = { "beast", "fast_movement", "insect", "MonsterStab_onhit_audio", "not_dex", "not_int", "red_blood", "sanctum_monster", "very_fast_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -22203,7 +22203,7 @@ minions["Metadata/Monsters/MarakethSanctumTrial/Boss/Shakari/ShakariMinion1_"] = minions["Metadata/Monsters/MarakethSanctumTrial/Boss/Shakari/ShakariMinion2"] = { name = "Karash, The Dune Dweller", - monsterTags = { "beast", "fast_movement", "insect", "MonsterStab_onhit_audio", "not_dex", "not_int", "red_blood", "sanctum_monster", "very_fast_movement", }, + monsterTags = { "beast", "fast_movement", "insect", "MonsterStab_onhit_audio", "not_dex", "not_int", "red_blood", "sanctum_monster", "very_fast_movement", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -22255,7 +22255,7 @@ minions["Metadata/Monsters/MarakethSanctumTrial/Boss/Shakari/ShakariMinion2"] = minions["Metadata/Monsters/Goblins/Beast/FireBeastBoss/FireBeastBossMinion1"] = { name = "Vornas, the Fell Flame", - monsterTags = { "beast", "Claw_onhit_audio", "fast_movement", "fire", "mammal_beast", "not_dex", "not_int", }, + monsterTags = { "beast", "Claw_onhit_audio", "fast_movement", "fire", "mammal_beast", "not_dex", "not_int", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -22314,7 +22314,7 @@ minions["Metadata/Monsters/Goblins/Beast/FireBeastBoss/FireBeastBossMinion1"] = minions["Metadata/Monsters/Goblins/Beast/FireBeastBoss/FireBeastBossMinion2"] = { name = "Morvak, the Infernal", - monsterTags = { "beast", "Claw_onhit_audio", "fast_movement", "fire", "mammal_beast", "not_dex", "not_int", }, + monsterTags = { "beast", "Claw_onhit_audio", "fast_movement", "fire", "mammal_beast", "not_dex", "not_int", "boss", }, extraFlags = { recommendedBeast = true, }, @@ -22373,7 +22373,7 @@ minions["Metadata/Monsters/Goblins/Beast/FireBeastBoss/FireBeastBossMinion2"] = minions["Metadata/Monsters/MarakethSanctumTrial/Boss/Shakari/ShakariDuoMinion"] = { name = "Akthi, the Final Sting", - monsterTags = { "beast", "fast_movement", "insect", "MonsterStab_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", }, + monsterTags = { "beast", "fast_movement", "insect", "MonsterStab_onhit_audio", "not_dex", "not_int", "red_blood", "very_fast_movement", "boss", }, extraFlags = { recommendedBeast = true, }, diff --git a/src/Data/TamedBeastMods.lua b/src/Data/TamedBeastMods.lua index 8448f01f6b..09ca89f35c 100644 --- a/src/Data/TamedBeastMods.lua +++ b/src/Data/TamedBeastMods.lua @@ -6,40 +6,19 @@ local mods, mod, flag = ... -mods["MonsterDamageGainedAsFire1"] = { - name = "Extra Fire Damage", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster Gains 40% of damage as extra Fire damage.", - }, - modList = { - mod("DamageGainAsFire", "BASE", 40, 0, 0), -- MonsterDamageGainedAsFire1 [non_skill_base_all_damage_%_to_gain_as_fire = 40] - }, -} - mods["PlayerMonsterDamageGainedAsFire1"] = { name = "Extra Fire Damage", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - mod("DamageGainAsFire", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsFire1 [non_skill_base_all_damage_%_to_gain_as_fire = 40] + spawnWeights = { + { tag = "fire_affinity", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterDamageGainedAsCold1"] = { - name = "Extra Cold Damage", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster Gains 40% of damage as extra Cold damage.", + "Monster Gains 40% of damage as extra Fire damage.", }, modList = { - mod("DamageGainAsCold", "BASE", 40, 0, 0), -- MonsterDamageGainedAsCold1 [non_skill_base_all_damage_%_to_gain_as_cold = 40] + mod("DamageGainAsFire", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsFire1 [non_skill_base_all_damage_%_to_gain_as_fire = 40] }, } @@ -47,23 +26,15 @@ mods["PlayerMonsterDamageGainedAsCold1"] = { name = "Extra Cold Damage", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - mod("DamageGainAsCold", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsCold1 [non_skill_base_all_damage_%_to_gain_as_cold = 40] + spawnWeights = { + { tag = "cold_affinity", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterDamageGainedAsLightning1"] = { - name = "Extra Lightning Damage", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster Gains 40% of damage as extra Lightning damage.", + "Monster Gains 40% of damage as extra Cold damage.", }, modList = { - mod("DamageGainAsLightning", "BASE", 40, 0, 0), -- MonsterDamageGainedAsLightning1 [non_skill_base_all_damage_%_to_gain_as_lightning = 40] + mod("DamageGainAsCold", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsCold1 [non_skill_base_all_damage_%_to_gain_as_cold = 40] }, } @@ -71,24 +42,15 @@ mods["PlayerMonsterDamageGainedAsLightning1"] = { name = "Extra Lightning Damage", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - mod("DamageGainAsLightning", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsLightning1 [non_skill_base_all_damage_%_to_gain_as_lightning = 40] + spawnWeights = { + { tag = "lightning_affinity", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterIncreasedSpeed1"] = { - name = "Hasted", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster has 30% increased Attack, Cast and Movement speed.", + "Monster Gains 40% of damage as extra Lightning damage.", }, modList = { - mod("Speed", "INC", 30, 0, 0), -- MonsterIncreasedSpeed1 [attack_and_cast_speed_+% = 30] - mod("MovementSpeed", "INC", 30, 0, 0), -- MonsterIncreasedSpeed1 [base_movement_velocity_+% = 30] + mod("DamageGainAsLightning", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsLightning1 [non_skill_base_all_damage_%_to_gain_as_lightning = 40] }, } @@ -96,7 +58,12 @@ mods["PlayerMonsterIncreasedSpeed1"] = { name = "Hasted", type = "Suffix", tier = 1, + spawnWeights = { + { tag = "fast_movement", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster has 30% increased Attack, Cast and Movement speed.", }, modList = { mod("Speed", "INC", 30, 0, 0), -- PlayerMonsterIncreasedSpeed1 [attack_and_cast_speed_+% = 30] @@ -104,40 +71,18 @@ mods["PlayerMonsterIncreasedSpeed1"] = { }, } -mods["MonsterCriticalStrikeChance1"] = { - name = "Extra Crits", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster has 300% increased chance to Critically Hit.", - }, - modList = { - mod("CritChance", "INC", 300, 0, 0), -- MonsterCriticalStrikeChance1 [critical_strike_chance_+% = 300] - }, -} - mods["PlayerMonsterCriticalStrikeChance1"] = { name = "Extra Crits", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - mod("CritChance", "INC", 300, 0, 0), -- PlayerMonsterCriticalStrikeChance1 [critical_strike_chance_+% = 300] + spawnWeights = { + { tag = "default", weight = 1 }, }, -} - -mods["MonsterStunDamageIncrease1"] = { - name = "Stuns", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster has 100% increased Stun buildup.", + "Monster has 300% increased chance to Critically Hit.", }, modList = { - -- MonsterStunDamageIncrease1 [hit_damage_stun_multiplier_+% = 100] + mod("CritChance", "INC", 300, 0, 0), -- PlayerMonsterCriticalStrikeChance1 [critical_strike_chance_+% = 300] }, } @@ -145,23 +90,15 @@ mods["PlayerMonsterStunDamageIncrease1"] = { name = "Stuns", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterStunDamageIncrease1 [hit_damage_stun_multiplier_+% = 100] + spawnWeights = { + { tag = "melee", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterExtraArmour1"] = { - name = "Armoured", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster gains extra Armour based off of their Strength.", + "Monster has 100% increased Stun buildup.", }, modList = { - -- MonsterExtraArmour1 [monster_additional_strength_ratio_%_for_armour = 100] + -- PlayerMonsterStunDamageIncrease1 [hit_damage_stun_multiplier_+% = 100] }, } @@ -169,23 +106,15 @@ mods["PlayerMonsterExtraArmour1"] = { name = "Armoured", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterExtraArmour1 [monster_additional_strength_ratio_%_for_armour = 100] + spawnWeights = { + { tag = "armour", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterExtraEvasion1"] = { - name = "Evasive", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster gains extra Evasion based off of their Dexterity.", + "Monster gains extra Armour based off of their Strength.", }, modList = { - -- MonsterExtraEvasion1 [monster_additional_dexterity_ratio_%_for_evasion = 100] + -- PlayerMonsterExtraArmour1 [monster_additional_strength_ratio_%_for_armour = 100] }, } @@ -193,23 +122,15 @@ mods["PlayerMonsterExtraEvasion1"] = { name = "Evasive", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterExtraEvasion1 [monster_additional_dexterity_ratio_%_for_evasion = 100] + spawnWeights = { + { tag = "evasion", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterExtraEnergyShield1"] = { - name = "Extra Energy Shield", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster gains 25% of Maximum life as added Energy Shield.", + "Monster gains extra Evasion based off of their Dexterity.", }, modList = { - -- MonsterExtraEnergyShield1 [base_maximum_life_%_to_gain_as_total_energy_shield = 25] + -- PlayerMonsterExtraEvasion1 [monster_additional_dexterity_ratio_%_for_evasion = 100] }, } @@ -217,22 +138,15 @@ mods["PlayerMonsterExtraEnergyShield1"] = { name = "Extra Energy Shield", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterExtraEnergyShield1 [base_maximum_life_%_to_gain_as_total_energy_shield = 25] + spawnWeights = { + { tag = "energy_shield", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterAlwaysPoison1"] = { - name = "Always Poisons", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { + "Monster gains 25% of Maximum life as added Energy Shield.", }, modList = { - mod("PoisonChance", "BASE", 100, 0, 0), -- MonsterAlwaysPoison1 [global_poison_on_hit = 1] + -- PlayerMonsterExtraEnergyShield1 [base_maximum_life_%_to_gain_as_total_energy_shield = 25] }, } @@ -240,22 +154,15 @@ mods["PlayerMonsterAlwaysPoison1"] = { name = "Always Poisons", type = "Suffix", tier = 1, - statDescriptions = { - }, - modList = { - mod("PoisonChance", "BASE", 100, 0, 0), -- PlayerMonsterAlwaysPoison1 [global_poison_on_hit = 1] + spawnWeights = { + { tag = "physical_affinity", weight = 1 }, + { tag = "chaos_affinity", weight = 1 }, + { tag = "default", weight = 0 }, }, -} - -mods["MonsterAlwaysBleed1"] = { - name = "Always Bleeds", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { }, modList = { - mod("BleedChance", "BASE", 100, 0, 0), -- MonsterAlwaysBleed1 [global_bleed_on_hit = 1] + mod("PoisonChance", "BASE", 100, 0, 0), -- PlayerMonsterAlwaysPoison1 [global_poison_on_hit = 1] }, } @@ -263,39 +170,27 @@ mods["PlayerMonsterAlwaysBleed1"] = { name = "Always Bleeds", type = "Suffix", tier = 1, - statDescriptions = { + spawnWeights = { + { tag = "physical_affinity", weight = 1 }, + { tag = "default", weight = 0 }, }, - modList = { - mod("BleedChance", "BASE", 100, 0, 0), -- PlayerMonsterAlwaysBleed1 [global_bleed_on_hit = 1] - }, -} - -mods["MonsterBurningGroundOnDeath1"] = { - name = "Periodically unleashes Fire", - rollable = true, - type = "Prefix", - tier = 1, statDescriptions = { }, modList = { + mod("BleedChance", "BASE", 100, 0, 0), -- PlayerMonsterAlwaysBleed1 [global_bleed_on_hit = 1] }, } mods["PlayerMonsterBurningGroundOnDeath1"] = { - name = "Burning Ground on Death", + name = "Periodically unleashes Fire", type = "Prefix", tier = 1, - statDescriptions = { - }, - modList = { + spawnWeights = { + { tag = "sanctum_monster", weight = 0 }, + { tag = "titan_boss", weight = 0 }, + { tag = "fire_affinity", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterChilledGroundOnDeath1"] = { - name = "Periodically unleashes Ice", - rollable = true, - type = "Prefix", - tier = 1, statDescriptions = { }, modList = { @@ -306,17 +201,11 @@ mods["PlayerMonsterChilledGroundOnDeath1"] = { name = "Periodically unleashes Ice", type = "Prefix", tier = 1, - statDescriptions = { - }, - modList = { + spawnWeights = { + { tag = "titan_boss", weight = 0 }, + { tag = "cold_affinity", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterShockedGroundOnDeath1"] = { - name = "Periodically unleashes Lightning", - rollable = true, - type = "Prefix", - tier = 1, statDescriptions = { }, modList = { @@ -327,45 +216,14 @@ mods["PlayerMonsterShockedGroundOnDeath1"] = { name = "Periodically unleashes Lightning", type = "Prefix", tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterImmuneToStun1"] = { - name = "Increased Stun Threshold", - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster cannot be Stunned.", - }, - modList = { - mod("StunImmune", "FLAG", 1, 0, 0), -- MonsterImmuneToStun1 [base_cannot_be_stunned = 1] - }, -} - -mods["PlayerMonsterImmuneToStun1"] = { - name = "Increased Stun Threshold", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("StunImmune", "FLAG", 1, 0, 0), -- PlayerMonsterImmuneToStun1 [base_cannot_be_stunned = 1] + spawnWeights = { + { tag = "titan_boss", weight = 0 }, + { tag = "lightning_affinity", weight = 1 }, + { tag = "default", weight = 1 }, }, -} - -mods["MonsterStunResilience1"] = { - name = "Stun Resistant", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster has 250% increased Stun Threshold.", }, modList = { - mod("StunThreshold", "INC", 250, 0, 0), -- MonsterStunResilience1 [stun_threshold_+% = 250] }, } @@ -373,24 +231,14 @@ mods["PlayerMonsterStunResilience1"] = { name = "Stun Resistant", type = "Suffix", tier = 1, - statDescriptions = { + spawnWeights = { + { tag = "default", weight = 1 }, }, - modList = { - mod("StunThreshold", "INC", 250, 0, 0), -- PlayerMonsterStunResilience1 [stun_threshold_+% = 250] - }, -} - -mods["MonsterFireResistance1"] = { - name = "Fire Resistant", - rollable = true, - type = "Suffix", - tier = 1, statDescriptions = { - "Monster has +50% to Fire Resistance and +10% to Maximum Fire Resistance.", + "Monster has 250% increased Stun Threshold.", }, modList = { - mod("FireResist", "BASE", 50, 0, 0), -- MonsterFireResistance1 [base_fire_damage_resistance_% = 50] - mod("FireResistMax", "BASE", 10, 0, 0), -- MonsterFireResistance1 [base_maximum_fire_damage_resistance_% = 10] + mod("StunThreshold", "INC", 250, 0, 0), -- PlayerMonsterStunResilience1 [stun_threshold_+% = 250] }, } @@ -398,7 +246,12 @@ mods["PlayerMonsterFireResistance1"] = { name = "Fire Resistant", type = "Suffix", tier = 1, + spawnWeights = { + { tag = "fire_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster has +50% to Fire Resistance and +10% to Maximum Fire Resistance.", }, modList = { mod("FireResist", "BASE", 50, 0, 0), -- PlayerMonsterFireResistance1 [base_fire_damage_resistance_% = 50] @@ -406,25 +259,16 @@ mods["PlayerMonsterFireResistance1"] = { }, } -mods["MonsterColdResistance1"] = { - name = "Cold Resistant", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster has +50% to Cold Resistance and +10% to Maximum Cold Resistance.", - }, - modList = { - mod("ColdResist", "BASE", 50, 0, 0), -- MonsterColdResistance1 [base_cold_damage_resistance_% = 50] - mod("ColdResistMax", "BASE", 10, 0, 0), -- MonsterColdResistance1 [base_maximum_cold_damage_resistance_% = 10] - }, -} - mods["PlayerMonsterColdResistance1"] = { name = "Cold Resistant", type = "Suffix", tier = 1, + spawnWeights = { + { tag = "cold_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster has +50% to Cold Resistance and +10% to Maximum Cold Resistance.", }, modList = { mod("ColdResist", "BASE", 50, 0, 0), -- PlayerMonsterColdResistance1 [base_cold_damage_resistance_% = 50] @@ -432,25 +276,16 @@ mods["PlayerMonsterColdResistance1"] = { }, } -mods["MonsterLightningResistance1"] = { - name = "Lightning Resistant", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster has +50% to Lightning Resistance and +10% to Maximum Lightning Resistance.", - }, - modList = { - mod("LightningResist", "BASE", 50, 0, 0), -- MonsterLightningResistance1 [base_lightning_damage_resistance_% = 50] - mod("LightningResistMax", "BASE", 10, 0, 0), -- MonsterLightningResistance1 [base_maximum_lightning_damage_resistance_% = 10] - }, -} - mods["PlayerMonsterLightningResistance1"] = { name = "Lightning Resistant", type = "Suffix", tier = 1, + spawnWeights = { + { tag = "lightning_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster has +50% to Lightning Resistance and +10% to Maximum Lightning Resistance.", }, modList = { mod("LightningResist", "BASE", 50, 0, 0), -- PlayerMonsterLightningResistance1 [base_lightning_damage_resistance_% = 50] @@ -458,137 +293,81 @@ mods["PlayerMonsterLightningResistance1"] = { }, } -mods["MonsterArmourPenetration1"] = { - name = "Breaks Armour", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster Breaks Armour equal to 1000% of Physical Damage dealt.", - }, - modList = { - mod("Condition:CanArmourBreak", "FLAG", 1000, 0, 0, { effectName = "ArmourBreak", effectType = "Buff", type = "GlobalEffect" }), -- MonsterArmourPenetration1 [armour_break_physical_damage_%_dealt_as_armour_break = 1000] - }, -} - mods["PlayerMonsterArmourPenetration1"] = { name = "Breaks Armour", type = "Suffix", tier = 1, + spawnWeights = { + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster Breaks Armour equal to 1000% of Physical Damage dealt.", }, modList = { mod("Condition:CanArmourBreak", "FLAG", 1000, 0, 0, { effectName = "ArmourBreak", effectType = "Buff", type = "GlobalEffect" }), -- PlayerMonsterArmourPenetration1 [armour_break_physical_damage_%_dealt_as_armour_break = 1000] }, } -mods["MonsterIncreasedAccuracy1"] = { +mods["PlayerMonsterIncreasedAccuracy1"] = { name = "Accurate", - rollable = true, type = "Suffix", tier = 1, + spawnWeights = { + { tag = "default", weight = 1 }, + }, statDescriptions = { "Monster has 200% increased Accuracy Rating.", }, modList = { - mod("Accuracy", "INC", 200, 0, 0), -- MonsterIncreasedAccuracy1 [accuracy_rating_+% = 200] + mod("Accuracy", "INC", 200, 0, 0), -- PlayerMonsterIncreasedAccuracy1 [accuracy_rating_+% = 200] }, } -mods["PlayerMonsterIncreasedAccuracy1"] = { - name = "Accurate", +mods["PlayerMonsterDamageGainedAsChaos1"] = { + name = "Extra Chaos Damage", type = "Suffix", tier = 1, + spawnWeights = { + { tag = "chaos_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster Gains 40% of damage as extra Chaos damage.", }, modList = { - mod("Accuracy", "INC", 200, 0, 0), -- PlayerMonsterIncreasedAccuracy1 [accuracy_rating_+% = 200] + mod("DamageGainAsChaos", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsChaos1 [non_skill_base_all_damage_%_to_gain_as_chaos = 40] }, } -mods["MonsterDamageGainedAsChaos1"] = { - name = "Extra Chaos Damage", - rollable = true, +mods["PlayerMonsterLifeRegenerationRatePercentage1"] = { + name = "Regenerates Life", type = "Suffix", tier = 1, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { - "Monster Gains 40% of damage as extra Chaos damage.", + "Monster Regenerates 2% of Maximum Life per second.", }, modList = { - mod("DamageGainAsChaos", "BASE", 40, 0, 0), -- MonsterDamageGainedAsChaos1 [non_skill_base_all_damage_%_to_gain_as_chaos = 40] + mod("LifeRegenPercent", "BASE", 2, 0, 0), -- PlayerMonsterLifeRegenerationRatePercentage1 [life_regeneration_rate_per_minute_% = 120] }, } -mods["PlayerMonsterDamageGainedAsChaos1"] = { - name = "Extra Chaos Damage", +mods["PlayerMonsterAdditionalProjectiles1"] = { + name = "Additional Projectiles", type = "Suffix", tier = 1, + spawnWeights = { + { tag = "allows_additional_projectiles", weight = 1 }, + { tag = "default", weight = 0 }, + }, statDescriptions = { + "Monster fires 4 additional Projectiles.", }, modList = { - mod("DamageGainAsChaos", "BASE", 40, 0, 0), -- PlayerMonsterDamageGainedAsChaos1 [non_skill_base_all_damage_%_to_gain_as_chaos = 40] - }, -} - -mods["MonsterLifeRegenerationRatePercentage1"] = { - name = "Regenerates Life", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster Regenerates 2% of Maximum Life per second.", - }, - modList = { - mod("LifeRegenPercent", "BASE", 2, 0, 0), -- MonsterLifeRegenerationRatePercentage1 [life_regeneration_rate_per_minute_% = 120] - }, -} - -mods["PlayerMonsterLifeRegenerationRatePercentage1"] = { - name = "Regenerates Life", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("LifeRegenPercent", "BASE", 2, 0, 0), -- PlayerMonsterLifeRegenerationRatePercentage1 [life_regeneration_rate_per_minute_% = 120] - }, -} - -mods["MonsterAdditionalProjectiles1"] = { - name = "Additional Projectiles", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster fires 4 additional Projectiles.", - }, - modList = { - mod("ProjectileCount", "BASE", 4, 0, 0), -- MonsterAdditionalProjectiles1 [number_of_additional_projectiles = 4] - }, -} - -mods["PlayerMonsterAdditionalProjectiles1"] = { - name = "Additional Projectiles", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("ProjectileCount", "BASE", 4, 0, 0), -- PlayerMonsterAdditionalProjectiles1 [number_of_additional_projectiles = 4] - }, -} - -mods["MonsterAreaOfEffect1"] = { - name = "Increased Area of Effect", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster has 100% Increased Area of Effect.", - "100% more Area of Effect", - }, - modList = { - -- MonsterAreaOfEffect1 [rare_monster_mod_area_of_effect_+%_final = 100] + mod("ProjectileCount", "BASE", 4, 0, 0), -- PlayerMonsterAdditionalProjectiles1 [number_of_additional_projectiles = 4] }, } @@ -596,1585 +375,723 @@ mods["PlayerMonsterAreaOfEffect1"] = { name = "Increased Area of Effect", type = "Suffix", tier = 1, - statDescriptions = { - "100% more Area of Effect", - }, - modList = { - -- PlayerMonsterAreaOfEffect1 [rare_monster_mod_area_of_effect_+%_final = 100] - }, -} - -mods["MonsterIgniteChanceIncrease1"] = { - name = "All Damage Ignites", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("PhysicalCanIgnite", "FLAG", 1, 0, 0), -- MonsterIgniteChanceIncrease1 [all_damage_can_ignite = 1] - mod("EnemyIgniteChance", "BASE", 100, 0, 0), -- MonsterIgniteChanceIncrease1 [always_ignite = 1] - }, -} - -mods["PlayerMonsterIgniteChanceIncrease1"] = { - name = "All Damage Ignites", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("PhysicalCanIgnite", "FLAG", 1, 0, 0), -- PlayerMonsterIgniteChanceIncrease1 [all_damage_can_ignite = 1] - mod("EnemyIgniteChance", "BASE", 100, 0, 0), -- PlayerMonsterIgniteChanceIncrease1 [always_ignite = 1] - }, -} - -mods["MonsterFreezeDamageIncrease1"] = { - name = "All Damage Chills", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - -- MonsterFreezeDamageIncrease1 [all_damage_can_chill = 1] - -- MonsterFreezeDamageIncrease1 [chill_minimum_slow_% = 10] - }, -} - -mods["PlayerMonsterFreezeDamageIncrease1"] = { - name = "All Damage Chills", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterFreezeDamageIncrease1 [all_damage_can_chill = 1] - -- PlayerMonsterFreezeDamageIncrease1 [chill_minimum_slow_% = 10] - }, -} - -mods["MonsterShockChanceIncrease1"] = { - name = "All Damage Shocks", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("PhysicalCanShock", "FLAG", 1, 0, 0), -- MonsterShockChanceIncrease1 [all_damage_can_shock = 1] - mod("EnemyShockChance", "BASE", 100, 0, 0), -- MonsterShockChanceIncrease1 [always_shock = 1] - }, -} - -mods["PlayerMonsterShockChanceIncrease1"] = { - name = "All Damage Shocks", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("PhysicalCanShock", "FLAG", 1, 0, 0), -- PlayerMonsterShockChanceIncrease1 [all_damage_can_shock = 1] - mod("EnemyShockChance", "BASE", 100, 0, 0), -- PlayerMonsterShockChanceIncrease1 [always_shock = 1] - }, -} - -mods["MonsterBurningGroundTrail1"] = { - name = "Trail of Fire", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterBurningGroundTrail1"] = { - name = "Trail of Fire", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterChilledGroundTrail1"] = { - name = "Trail of Ice", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterChilledGroundTrail1"] = { - name = "Trail of Ice", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterShockedGroundTrail1"] = { - name = "Trail of Lightning", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster leaves a trail of Shocked Ground as they move.", - }, - modList = { - }, -} - -mods["PlayerMonsterShockedGroundTrail1"] = { - name = "Trail of Lightning", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterImmuneToSlow1"] = { - name = "Slow Resistant", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster has 50% reduced Slowing Potency of Debuffs on them.", - "50% less Slowing Potency of Debuffs on me", - }, - modList = { - -- MonsterImmuneToSlow1 [monster_slow_potency_+%_final = -50] - }, -} - -mods["PlayerMonsterImmuneToSlow1"] = { - name = "Slow Resistant", - type = "Suffix", - tier = 1, - statDescriptions = { - "50% less Slowing Potency of Debuffs on me", - }, - modList = { - -- PlayerMonsterImmuneToSlow1 [monster_slow_potency_+%_final = -50] - }, -} - -mods["MonsterChaosResistance1"] = { - name = "Chaos Resistant", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Monster has +50% to Chaos Resistance.", - }, - modList = { - mod("ChaosResist", "BASE", 50, 0, 0), -- MonsterChaosResistance1 [base_chaos_damage_resistance_% = 50] - }, -} - -mods["PlayerMonsterChaosResistance1"] = { - name = "Chaos Resistant", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("ChaosResist", "BASE", 50, 0, 0), -- PlayerMonsterChaosResistance1 [base_chaos_damage_resistance_% = 50] - }, -} - -mods["MonsterCannotLeech1"] = { - name = "of Congealment", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - -- MonsterCannotLeech1 [life_leeched_from_-permyriad = 6000] - -- MonsterCannotLeech1 [mana_leeched_from_-permyriad = 4000] - -- MonsterCannotLeech1 [energy_shield_leeched_from_-permyriad = 6000] - }, -} - -mods["PlayerMonsterCannotLeech1"] = { - name = "of Congealment", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterCannotLeech1 [life_leeched_from_-permyriad = 6000] - -- PlayerMonsterCannotLeech1 [mana_leeched_from_-permyriad = 4000] - -- PlayerMonsterCannotLeech1 [energy_shield_leeched_from_-permyriad = 6000] - }, -} - -mods["MonsterIsHexproof1"] = { - name = "of Hexproof", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Hexproof", + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "allows_inc_aoe", weight = 1 }, + { tag = "default", weight = 0 }, }, - modList = { - -- MonsterIsHexproof1 [hexproof = 1] - }, -} - -mods["PlayerMonsterIsHexproof1"] = { - name = "of Hexproof", - type = "Suffix", - tier = 1, statDescriptions = { - "Hexproof", - }, - modList = { - -- PlayerMonsterIsHexproof1 [hexproof = 1] - }, -} - -mods["MonsterAdditionalChains1"] = { - name = "of Chaining", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("ChainCountMax", "BASE", 2, 0, 0), -- MonsterAdditionalChains1 [number_of_chains = 2] - -- MonsterAdditionalChains1 [projectile_chain_from_terrain_chance_% = 50] - }, -} - -mods["PlayerMonsterAdditionalChains1"] = { - name = "of Chaining", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("ChainCountMax", "BASE", 2, 0, 0), -- PlayerMonsterAdditionalChains1 [number_of_chains = 2] - -- PlayerMonsterAdditionalChains1 [projectile_chain_from_terrain_chance_% = 50] - }, -} - -mods["MonsterProjectilesGainDamage1"] = { - name = "of Far Shot", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - -- MonsterProjectilesGainDamage1 [projectile_damage_+%_max_as_distance_travelled_increases = 60] - }, -} - -mods["PlayerMonsterProjectilesGainDamage1"] = { - name = "of Far Shot", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterProjectilesGainDamage1 [projectile_damage_+%_max_as_distance_travelled_increases = 60] - }, -} - -mods["MonsterModReducedCritMulti1"] = { - name = "Crit Resistant", - rollable = true, - type = "Suffix", - tier = 1, - statDescriptions = { - "Hits against this Monster have 80% reduced Critical Damage Bonus.", - }, - modList = { - mod("SelfCritMultiplier", "INC", -80, 0, 0), -- MonsterModReducedCritMulti1 [base_self_critical_strike_multiplier_-% = 80] - }, -} - -mods["PlayerMonsterModReducedCritMulti1"] = { - name = "Crit Resistant", - type = "Suffix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("SelfCritMultiplier", "INC", -80, 0, 0), -- PlayerMonsterModReducedCritMulti1 [base_self_critical_strike_multiplier_-% = 80] - }, -} - -mods["MonsterLastGasp1"] = { - name = "TBD", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- MonsterLastGasp1 [retaliation_godmode_ghost_duration_ms = 10000] - -- MonsterLastGasp1 [corpse_cannot_be_destroyed = 1] - -- MonsterLastGasp1 [cannot_be_dominated = 1] - }, -} - -mods["PlayerMonsterLastGasp1"] = { - name = "TBD", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterLastGasp1 [retaliation_godmode_ghost_duration_ms = 10000] - -- PlayerMonsterLastGasp1 [corpse_cannot_be_destroyed = 1] - -- PlayerMonsterLastGasp1 [cannot_be_dominated = 1] - }, -} - -mods["MonsterFlameBeacons1"] = { - name = "Periodic Fire Explosions", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterFlameBeacons1"] = { - name = "Periodic Fire Explosions", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterFrostBeacons1"] = { - name = "Periodic Cold Explosions", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterFrostBeacons1"] = { - name = "Periodic Cold Explosions", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterLightningBeacons1"] = { - name = "Periodic Lightning Explosions", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterLightningBeacons1"] = { - name = "Periodic Lightning Explosions", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterStrongerMinions1"] = { - name = "Powerful Minions", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster's Pack Minions have 25% increased Damage and 50% increased Life.", - }, - modList = { - }, -} - -mods["PlayerMonsterStrongerMinions1"] = { - name = "Powerful Minions", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterPhysicalDamageAura1"] = { - name = "Extra Physical Damage Aura", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates an Aura that grants 40% increased Physical Damage to Allies within 5 metres.", - }, - modList = { - mod("PhysicalDamage", "INC", 40, 0, 0), -- MonsterPhysicalDamageAura1 [physical_damage_+% = 40] - }, -} - -mods["PlayerMonsterPhysicalDamageAura1"] = { - name = "Extra Physical Damage Aura", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("PhysicalDamage", "INC", 40, 0, 0), -- PlayerMonsterPhysicalDamageAura1 [physical_damage_+% = 40] - }, -} - -mods["MonsterIncreasedSpeedAura1"] = { - name = "Haste Aura", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates an Aura that grants 20% increased Attack and Cast speed and 10% increased Movement speed to Allies within 5 metres.", - }, - modList = { - mod("Speed", "INC", 25, 0, 0), -- MonsterIncreasedSpeedAura1 [attack_and_cast_speed_+% = 25] - mod("MovementSpeed", "INC", 25, 0, 0), -- MonsterIncreasedSpeedAura1 [base_movement_velocity_+% = 25] - }, -} - -mods["PlayerMonsterIncreasedSpeedAura1"] = { - name = "Haste Aura", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("Speed", "INC", 25, 0, 0), -- PlayerMonsterIncreasedSpeedAura1 [attack_and_cast_speed_+% = 25] - mod("MovementSpeed", "INC", 25, 0, 0), -- PlayerMonsterIncreasedSpeedAura1 [base_movement_velocity_+% = 25] - }, -} - -mods["PlayerMonsterIncreasedSpeedAuraMinion1"] = { - name = "Haste Aura", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("Speed", "INC", 20, 0, 0), -- PlayerMonsterIncreasedSpeedAuraMinion1 [attack_and_cast_speed_+% = 20] - mod("MovementSpeed", "INC", 10, 0, 0), -- PlayerMonsterIncreasedSpeedAuraMinion1 [base_movement_velocity_+% = 10] - }, -} - -mods["MonsterEnergyShieldAura1"] = { - name = "Energy Shield Aura", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates an Aura that grants 20% of Maximum life as added Energy Shield to Allies within 5 metres.", - }, - modList = { - -- MonsterEnergyShieldAura1 [base_maximum_life_%_to_gain_as_total_energy_shield = 30] - }, -} - -mods["PlayerMonsterEnergyShieldAura1"] = { - name = "Energy Shield Aura", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterEnergyShieldAura1 [base_maximum_life_%_to_gain_as_total_energy_shield = 30] - }, -} - -mods["PlayerMonsterEnergyShieldAuraMinion1"] = { - name = "Energy Shield Aura", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterEnergyShieldAuraMinion1 [base_maximum_life_%_to_gain_as_total_energy_shield = 20] - }, -} - -mods["MonsterResistanceAura1"] = { - name = "Elemental Resistance Aura", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates an Aura that grants +35% to all Elemental Resistances to Allies within 5 metres.", - }, - modList = { - mod("ElementalResist", "BASE", 35, 0, 0), -- MonsterResistanceAura1 [base_resist_all_elements_% = 35] - }, -} - -mods["PlayerMonsterResistanceAura1"] = { - name = "Elemental Resistance Aura", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - mod("ElementalResist", "BASE", 35, 0, 0), -- PlayerMonsterResistanceAura1 [base_resist_all_elements_% = 35] - }, -} - -mods["MonsterTemporalAura1"] = { - name = "Temporal Bubble", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates an Aura that Debuffs Enemies within 3.2 metres; Slowing by 25%, making effects expire 40% slower and reducing Cooldown Recovery Rate by 60%.", - }, - modList = { - -- MonsterTemporalAura1 [action_speed_-% = 25] - -- MonsterTemporalAura1 [debuff_time_passed_+% = -40] - mod("CooldownRecovery", "INC", -60, 0, 0), -- MonsterTemporalAura1 [base_cooldown_speed_+% = -60] - -- MonsterTemporalAura1 [cannot_be_damaged_by_things_outside_radius = 0] - }, -} - -mods["PlayerMonsterTemporalAura1"] = { - name = "Temporal Bubble", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterTemporalAura1 [action_speed_-% = 25] - -- PlayerMonsterTemporalAura1 [debuff_time_passed_+% = -40] - mod("CooldownRecovery", "INC", -60, 0, 0), -- PlayerMonsterTemporalAura1 [base_cooldown_speed_+% = -60] - -- PlayerMonsterTemporalAura1 [cannot_be_damaged_by_things_outside_radius = 0] - }, -} - -mods["PlayerMonsterTemporalAuraMinion1"] = { - name = "Temporal Bubble", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterTemporalAuraMinion1 [action_speed_-% = 12] - -- PlayerMonsterTemporalAuraMinion1 [debuff_time_passed_+% = -40] - mod("CooldownRecovery", "INC", -20, 0, 0), -- PlayerMonsterTemporalAuraMinion1 [base_cooldown_speed_+% = -20] - -- PlayerMonsterTemporalAuraMinion1 [cannot_be_damaged_by_things_outside_radius = 0] - }, -} - -mods["MonsterHinderAura1"] = { - name = "Hinder Aura", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates an Aura that Hinders enemies within 3.6 metres.", - }, - modList = { - }, -} - -mods["PlayerMonsterHinderAura1"] = { - name = "Hinder Aura", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterPreventRecoveryAura1"] = { - name = "Prevents Recovery Above 50%", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates an Aura that Debuffs enemies within 4.2 metres, causing their Life and Energy Shield to not be able to recover past 50%.", - }, - modList = { - -- MonsterPreventRecoveryAura1 [cannot_recover_life_or_energy_shield_above_% = 50] - }, -} - -mods["PlayerMonsterPreventRecoveryAura1"] = { - name = "Prevents Recovery Above 50%", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterImmuneAura1"] = { - name = "Periodic Invulnerability Aura", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster releases a nova that makes Allies invulnerable for 5 seconds while the monster is alive within 5 metres every 12 seconds.", - }, - modList = { - -- MonsterImmuneAura1 [monster_allies_cannot_take_damage_pulse_owner = 1] - }, -} - -mods["PlayerMonsterImmuneAura1"] = { - name = "Periodic Invulnerability Aura", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterImmuneAura1 [monster_allies_cannot_take_damage_pulse_owner = 1] - }, -} - -mods["MonsterImmuneAura2"] = { - name = "Empowered Periodic Invulnerability Aura", - rollable = true, - type = "Prefix", - tier = 2, - statDescriptions = { - "Monster releases a nova that makes Allies invulnerable for 5 seconds while the monster is alive within 5 metres every 12 seconds.", - }, - modList = { - -- MonsterImmuneAura2 [monster_allies_cannot_take_damage_pulse_owner = 1] - }, -} - -mods["PlayerMonsterImmuneAura2"] = { - name = "Empowered Periodic Invulnerability Aura", - type = "Prefix", - tier = 2, - statDescriptions = { - }, - modList = { - -- PlayerMonsterImmuneAura2 [monster_allies_cannot_take_damage_pulse_owner = 1] - }, -} - -mods["MonsterImmuneAuraMinion2"] = { - name = "Increased Life", - type = "Prefix", - tier = 2, - statDescriptions = { - "Monster has 50% increased Life.", - }, - modList = { - -- MonsterImmuneAuraMinion2 [base_maximum_life = 0] - -- MonsterImmuneAuraMinion2 [maximum_life_+% = 50] - }, -} - -mods["MonsterManaSiphonAura1"] = { - name = "Siphons Mana and Deals Lightning Damage", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates a circular effect that drains Mana and deals Lightning Damage over time to enemies near the edge of the circle.", - }, - modList = { - }, -} - -mods["PlayerMonsterManaSiphonAura1"] = { - name = "Siphons Mana and Deals Lightning Damage", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterManaSiphonAura2"] = { - name = "Siphons Mana and Deals Lightning Damage", - rollable = true, - type = "Prefix", - tier = 2, - statDescriptions = { - "Monster creates a circular effect that drains Mana and deals Lightning Damage over time to enemies near the edge of the circle. Additionally, Monster will periodically create separate circles that drain Mana and deal Lightning Damage over time to enemies standing in them.", - }, - modList = { - }, -} - -mods["PlayerMonsterManaSiphonAura2"] = { - name = "Siphons Mana and Deals Lightning Damage", - type = "Prefix", - tier = 2, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterHealingNova1"] = { - name = "Heals Allies and Suppresses Foe Recovery", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster releases a nova that reduces Enemy Life and Energy Shield Recovery Rate by 60% and causes Allies to Regenerate 5.5% of Maximum Life per second for 4 seconds within 5 metres every 8 seconds.", - }, - modList = { - }, -} - -mods["PlayerMonsterHealingNova1"] = { - name = "Heals Allies and Suppresses Foe Recovery", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterFlaskRemovalAura1"] = { - name = "Siphons Flask Charges", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates an Aura that removes 3 Flask and Charm charges from enemies every 3 seconds within 3.6 metres.", - }, - modList = { - -- MonsterFlaskRemovalAura1 [generate_x_charges_for_any_flask_per_minute = -3] - }, -} - -mods["PlayerMonsterFlaskRemovalAura1"] = { - name = "Siphons Flask Charges", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterFlaskRemovalAura1 [generate_x_charges_for_any_flask_per_minute = -3] - }, -} - -mods["MonsterRevivesMinions1"] = { - name = "Reviving Minions", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster periodically revives Pack Minions.", - }, - modList = { - }, -} - -mods["PlayerMonsterRevivesMinions1"] = { - name = "Reviving Minions", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterRevivesMinions2"] = { - name = "Empowered Reviving Minions", - rollable = true, - type = "Prefix", - tier = 2, - statDescriptions = { - "Monster periodically revives Pack Minions with increased Life and Damage.", - }, - modList = { - }, -} - -mods["PlayerMonsterRevivesMinions2"] = { - name = "Empowered Reviving Minions", - type = "Prefix", - tier = 2, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterMinionsTakeLifeInstead1"] = { - name = "Damage Taken From Minions First", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "50% of damage taken from Monster is taken from Monster's Pack Minions instead", - }, - modList = { - -- MonsterMinionsTakeLifeInstead1 [damage_removed_from_pack_minions_before_life_or_es_% = 50] - }, -} - -mods["PlayerMonsterMinionsTakeLifeInstead1"] = { - name = "Damage Taken From Minions First", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - -- PlayerMonsterMinionsTakeLifeInstead1 [damage_removed_from_pack_minions_before_life_or_es_% = 50] - }, -} - -mods["MonsterShroudWalker1"] = { - name = "Shroud Walker", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster periodically teleports to an enemy they can see, creating a Smoke Cloud where they leave and where they teleport to.", - }, - modList = { - }, -} - -mods["PlayerMonsterShroudWalker1"] = { - name = "Shroud Walker", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterShroudWalker2"] = { - name = "Shroud Walker", - rollable = true, - type = "Prefix", - tier = 2, - statDescriptions = { - "Monster periodically teleports to an enemy they can see, creating a Smoke Cloud where they leave and where they teleport to.", - }, - modList = { - }, -} - -mods["PlayerMonsterShroudWalker2"] = { - name = "Shroud Walker", - type = "Prefix", - tier = 2, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterPeriodicEnrage1"] = { - name = "Periodically Enrages", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster periodically Enrages; gaining 30% increased Damage, 25% increased Skill and Movement Speed and 33% less damage taken for 5 seconds every 10 seconds.", - }, - modList = { - }, -} - -mods["PlayerMonsterPeriodicEnrage1"] = { - name = "Periodically Enrages", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterPeriodicEnrage2"] = { - name = "Enraged", - rollable = true, - type = "Prefix", - tier = 2, - statDescriptions = { - "Monster is Enraged; gaining 30% increased Damage, 25% increased Skill and Movement Speed and 33% less damage taken.", - }, - modList = { - }, -} - -mods["PlayerMonsterPeriodicEnrage2"] = { - name = "Enraged", - type = "Prefix", - tier = 2, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterPeriodicEnrageMinion2"] = { - name = "Enraged", - type = "Prefix", - tier = 2, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterCorpseExploder1"] = { - name = "Explodes Nearby Corpses", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterCorpseExploder1"] = { - name = "Explodes Nearby Corpses", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterLightningMirage1"] = { - name = "Lightning Mirage When Hit", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates a Mirage when Hit that moves towards enemies and explodes when it gets close enough, dealing Lightning Damage.", - }, - modList = { - }, -} - -mods["PlayerMonsterLightningMirage1"] = { - name = "Lightning Mirage When Hit", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterLightningMirage2"] = { - name = "Lightning Mirages When Hit", - rollable = true, - type = "Prefix", - tier = 2, - statDescriptions = { - "Monster creates Mirages when Hit that move towards enemies and explode when they get close enough, dealing Lightning Damage.", - }, - modList = { - }, -} - -mods["PlayerMonsterLightningMirage2"] = { - name = "Lightning Mirages When Hit", - type = "Prefix", - tier = 2, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterMagmaBarrier1"] = { - name = "Magma Barrier", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates a 90% damage absorption barrier that explodes after taking a certain amount of damage, dealing Fire Damage", - }, - modList = { - }, -} - -mods["PlayerMonsterMagmaBarrier1"] = { - name = "Magma Barrier", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterFlamewaller1"] = { - name = "Conjures Flamewalls", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster creates circular walls of Fire that deal damage to enemies standing in them.", - }, - modList = { - }, -} - -mods["PlayerMonsterFlamewaller1"] = { - name = "Conjures Flamewalls", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterLightningStorms1"] = { - name = "Conjures Lightning Storms", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterLightningStorms1"] = { - name = "Conjures Lightning Storms", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterElementalWaller1"] = { - name = "Conjures Elemental Hazards", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["PlayerMonsterElementalWaller1"] = { - name = "Conjures Elemental Hazards", - type = "Prefix", - tier = 1, - statDescriptions = { - }, - modList = { - }, -} - -mods["MonsterVolatilePlants1"] = { - name = "Volatile Plants", - rollable = true, - type = "Prefix", - tier = 1, - statDescriptions = { - "Monster periodically creates Volatile Plants, releasing orbs that move towards enemies; exploding when they get close enough; dealing Chaos Damage.", + "Monster has 100% Increased Area of Effect.", + "100% more Area of Effect", }, modList = { + -- PlayerMonsterAreaOfEffect1 [rare_monster_mod_area_of_effect_+%_final = 100] }, } -mods["PlayerMonsterVolatilePlants1"] = { - name = "Volatile Plants", - type = "Prefix", +mods["PlayerMonsterIgniteChanceIncrease1"] = { + name = "All Damage Ignites", + type = "Suffix", tier = 1, + spawnWeights = { + { tag = "fire_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { }, modList = { + mod("PhysicalCanIgnite", "FLAG", 1, 0, 0), -- PlayerMonsterIgniteChanceIncrease1 [all_damage_can_ignite = 1] + mod("EnemyIgniteChance", "BASE", 100, 0, 0), -- PlayerMonsterIgniteChanceIncrease1 [always_ignite = 1] }, } -mods["MonsterVolatilePlants2"] = { - name = "Empowering Volatile Plants", - rollable = true, - type = "Prefix", - tier = 2, +mods["PlayerMonsterFreezeDamageIncrease1"] = { + name = "All Damage Chills", + type = "Suffix", + tier = 1, + spawnWeights = { + { tag = "cold_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { - "Monster periodically creates Powerful Volatile Plants, releasing orbs that move towards enemies; exploding when they get close enough; dealing Chaos Damage.", }, modList = { + -- PlayerMonsterFreezeDamageIncrease1 [all_damage_can_chill = 1] + -- PlayerMonsterFreezeDamageIncrease1 [chill_minimum_slow_% = 10] }, } -mods["PlayerMonsterVolatilePlants2"] = { - name = "Empowering Volatile Plants", - type = "Prefix", - tier = 2, +mods["PlayerMonsterShockChanceIncrease1"] = { + name = "All Damage Shocks", + type = "Suffix", + tier = 1, + spawnWeights = { + { tag = "lightning_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { }, modList = { + mod("PhysicalCanShock", "FLAG", 1, 0, 0), -- PlayerMonsterShockChanceIncrease1 [all_damage_can_shock = 1] + mod("EnemyShockChance", "BASE", 100, 0, 0), -- PlayerMonsterShockChanceIncrease1 [always_shock = 1] }, } -mods["MonsterVolatileRocks1"] = { - name = "Volatile Crag", - rollable = true, - type = "Prefix", +mods["PlayerMonsterBurningGroundTrail1"] = { + name = "Trail of Fire", + type = "Suffix", tier = 1, + spawnWeights = { + { tag = "sanctum_monster", weight = 0 }, + { tag = "immobile", weight = 0 }, + { tag = "ranged", weight = 0 }, + { tag = "fire_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { - "Monster periodically creates Volatile Crag that moves towards enemies; exploding when they get close enough; dealing Fire Damage.", }, modList = { }, } -mods["PlayerMonsterVolatileRocks1"] = { - name = "Volatile Crag", - type = "Prefix", +mods["PlayerMonsterChilledGroundTrail1"] = { + name = "Trail of Ice", + type = "Suffix", tier = 1, + spawnWeights = { + { tag = "immobile", weight = 0 }, + { tag = "ranged", weight = 0 }, + { tag = "cold_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { }, modList = { }, } -mods["MonsterVolatileRocks2"] = { - name = "Empowering Volatile Crag", - rollable = true, - type = "Prefix", - tier = 2, +mods["PlayerMonsterShockedGroundTrail1"] = { + name = "Trail of Lightning", + type = "Suffix", + tier = 1, + spawnWeights = { + { tag = "immobile", weight = 0 }, + { tag = "ranged", weight = 0 }, + { tag = "lightning_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { - "Monster periodically creates Powerful Volatile Crag that moves towards enemies; exploding when they get close enough; dealing Fire Damage.", + "Monster leaves a trail of Shocked Ground as they move.", }, modList = { }, } -mods["PlayerMonsterVolatileRocks2"] = { - name = "Empowering Volatile Crag", - type = "Prefix", - tier = 2, +mods["PlayerMonsterImmuneToSlow1"] = { + name = "Slow Resistant", + type = "Suffix", + tier = 1, + spawnWeights = { + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster has 50% reduced Slowing Potency of Debuffs on them.", + "50% less Slowing Potency of Debuffs on me", }, modList = { + -- PlayerMonsterImmuneToSlow1 [monster_slow_potency_+%_final = -50] }, } -mods["MonsterProximalTangibility1"] = { - name = "Proximal Tangibility", - rollable = true, - type = "Prefix", +mods["PlayerMonsterModReducedCritMulti1"] = { + name = "Crit Resistant", + type = "Suffix", tier = 1, + spawnWeights = { + { tag = "default", weight = 1 }, + }, statDescriptions = { - "Monster cannot be damaged by enemies any further than 3 metres from them.", + "Hits against this Monster have 80% reduced Critical Damage Bonus.", }, modList = { - -- MonsterProximalTangibility1 [ignore_cannot_be_damaged_by_enemies = 0] + mod("SelfCritMultiplier", "INC", -80, 0, 0), -- PlayerMonsterModReducedCritMulti1 [base_self_critical_strike_multiplier_-% = 80] }, } -mods["PlayerMonsterProximalTangibility1"] = { - name = "Proximal Tangibility", - type = "Prefix", +mods["PlayerMonsterChaosResistance1"] = { + name = "Chaos Resistant", + type = "Suffix", tier = 1, + spawnWeights = { + { tag = "chaos_affinity", weight = 1 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster has +50% to Chaos Resistance.", }, modList = { - -- PlayerMonsterProximalTangibility1 [ignore_cannot_be_damaged_by_enemies = 0] + mod("ChaosResist", "BASE", 50, 0, 0), -- PlayerMonsterChaosResistance1 [base_chaos_damage_resistance_% = 50] }, } -mods["MonsterBombardier1"] = { - name = "Bombardier", +mods["PlayerMonsterFlameBeacons1"] = { + name = "Periodic Fire Explosions", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { - "Monster periodically unleashes barrages of Fire projectiles.", }, modList = { }, } -mods["PlayerMonsterBombardier1"] = { - name = "Periodically unleashes Fire", +mods["PlayerMonsterFrostBeacons1"] = { + name = "Periodic Cold Explosions", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { }, modList = { }, } -mods["MonsterSoulEater1"] = { - name = "Soul Eater", +mods["PlayerMonsterLightningBeacons1"] = { + name = "Periodic Lightning Explosions", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { }, modList = { - -- MonsterSoulEater1 [grant_actor_scale_+%_to_aura_owner_on_death = 0] - -- MonsterSoulEater1 [grant_attack_speed_+%_to_aura_owner_on_death = 1] - -- MonsterSoulEater1 [grant_damage_reduction_%_to_aura_owner_on_death = -1] - -- MonsterSoulEater1 [soul_is_consumed_on_death = 1] }, } -mods["PlayerMonsterSoulEater1"] = { - name = "Soul Eater", +mods["PlayerMonsterStrongerMinions1"] = { + name = "Powerful Minions", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster's Pack Minions have 25% increased Damage and 50% increased Life.", }, modList = { - -- PlayerMonsterSoulEater1 [grant_actor_scale_+%_to_aura_owner_on_death = 0] - -- PlayerMonsterSoulEater1 [grant_attack_speed_+%_to_aura_owner_on_death = 1] - -- PlayerMonsterSoulEater1 [grant_damage_reduction_%_to_aura_owner_on_death = -1] - -- PlayerMonsterSoulEater1 [soul_is_consumed_on_death = 1] }, } -mods["MonsterAbyssalCrystalMineWall1"] = { - name = "Crystalline Barrier", +mods["PlayerMonsterPhysicalDamageAura1"] = { + name = "Extra Physical Damage Aura", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates an Aura that grants 40% increased Physical Damage to Allies within 5 metres.", }, modList = { + mod("PhysicalDamage", "INC", 40, 0, 0), -- PlayerMonsterPhysicalDamageAura1 [physical_damage_+% = 40] }, } -mods["PlayerMonsterAbyssalCrystalMineWall1"] = { - name = "Crystalline Barrier", +mods["PlayerMonsterIncreasedSpeedAura1"] = { + name = "Haste Aura", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "fast_movement", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates an Aura that grants 20% increased Attack and Cast speed and 10% increased Movement speed to Allies within 5 metres.", }, modList = { + mod("Speed", "INC", 25, 0, 0), -- PlayerMonsterIncreasedSpeedAura1 [attack_and_cast_speed_+% = 25] + mod("MovementSpeed", "INC", 25, 0, 0), -- PlayerMonsterIncreasedSpeedAura1 [base_movement_velocity_+% = 25] }, } -mods["MonsterAbyssVolatileRocks1"] = { - name = "Volatile Souls", +mods["PlayerMonsterEnergyShieldAura1"] = { + name = "Energy Shield Aura", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates an Aura that grants 20% of Maximum life as added Energy Shield to Allies within 5 metres.", }, modList = { + -- PlayerMonsterEnergyShieldAura1 [base_maximum_life_%_to_gain_as_total_energy_shield = 30] }, } -mods["PlayerMonsterAbyssVolatileRocks1"] = { - name = "Volatile Souls", +mods["PlayerMonsterResistanceAura1"] = { + name = "Elemental Resistance Aura", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates an Aura that grants +35% to all Elemental Resistances to Allies within 5 metres.", }, modList = { + mod("ElementalResist", "BASE", 35, 0, 0), -- PlayerMonsterResistanceAura1 [base_resist_all_elements_% = 35] }, } -mods["MonsterAbyssSiphonAura1"] = { - name = "Soul Siphoner", +mods["PlayerMonsterTemporalAura1"] = { + name = "Temporal Bubble", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates an Aura that Debuffs Enemies within 3.2 metres; Slowing by 25%, making effects expire 40% slower and reducing Cooldown Recovery Rate by 60%.", }, modList = { + -- PlayerMonsterTemporalAura1 [action_speed_-% = 25] + -- PlayerMonsterTemporalAura1 [debuff_time_passed_+% = -40] + mod("CooldownRecovery", "INC", -60, 0, 0), -- PlayerMonsterTemporalAura1 [base_cooldown_speed_+% = -60] + -- PlayerMonsterTemporalAura1 [cannot_be_damaged_by_things_outside_radius = 0] }, } -mods["PlayerMonsterAbyssSiphonAura1"] = { - name = "Soul Siphoner", +mods["PlayerMonsterHinderAura1"] = { + name = "Hinder Aura", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates an Aura that Hinders enemies within 3.6 metres.", }, modList = { }, } -mods["MonsterAbyssLastGasp1"] = { - name = "Kurgal's Last Gasp", +mods["PlayerMonsterPreventRecoveryAura1"] = { + name = "Prevents Recovery Above 50%", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates an Aura that Debuffs enemies within 4.2 metres, causing their Life and Energy Shield to not be able to recover past 50%.", }, modList = { }, } -mods["PlayerMonsterAbyssLastGasp1"] = { - name = "Kurgal's Last Gasp", +mods["PlayerMonsterImmuneAura1"] = { + name = "Periodic Invulnerability Aura", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster releases a nova that makes Allies invulnerable for 5 seconds while the monster is alive within 5 metres every 12 seconds.", }, modList = { + -- PlayerMonsterImmuneAura1 [monster_allies_cannot_take_damage_pulse_owner = 1] }, } -mods["MonsterAbyssLightlessFaction1"] = { - name = "Amanamu's Void", +mods["PlayerMonsterImmuneAura2"] = { + name = "Empowered Periodic Invulnerability Aura", type = "Prefix", - tier = 1, + tier = 2, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster releases a nova that makes Allies invulnerable for 5 seconds while the monster is alive within 5 metres every 12 seconds.", }, modList = { + -- PlayerMonsterImmuneAura2 [monster_allies_cannot_take_damage_pulse_owner = 1] }, } -mods["PlayerMonsterAbyssLightlessFaction1"] = { - name = "Amanamu's Void", +mods["PlayerMonsterManaSiphonAura1"] = { + name = "Siphons Mana and Deals Lightning Damage", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "ranged", weight = 0 }, + { tag = "sanctum_monster", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates a circular effect that drains Mana and deals Lightning Damage over time to enemies near the edge of the circle.", }, modList = { }, } -mods["MonsterAbyssLeechAura"] = { - name = "Lifestealer Aura", +mods["PlayerMonsterManaSiphonAura2"] = { + name = "Siphons Mana and Deals Lightning Damage", type = "Prefix", + tier = 2, + spawnWeights = { + { tag = "ranged", weight = 0 }, + { tag = "sanctum_monster", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates a circular effect that drains Mana and deals Lightning Damage over time to enemies near the edge of the circle. Additionally, Monster will periodically create separate circles that drain Mana and deal Lightning Damage over time to enemies standing in them.", }, modList = { }, } -mods["MonsterAbyssApparitionMirage1"] = { - name = "Unstable Revenants", +mods["PlayerMonsterHealingNova1"] = { + name = "Heals Allies and Suppresses Foe Recovery", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster releases a nova that reduces Enemy Life and Energy Shield Recovery Rate by 60% and causes Allies to Regenerate 5.5% of Maximum Life per second for 4 seconds within 5 metres every 8 seconds.", }, modList = { }, } -mods["MonsterAbyssImmuneAura1"] = { - name = "Undying Will", +mods["PlayerMonsterFlaskRemovalAura1"] = { + name = "Siphons Flask Charges", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates an Aura that removes 3 Flask and Charm charges from enemies every 3 seconds within 3.6 metres.", }, modList = { - -- MonsterAbyssImmuneAura1 [ignore_cannot_be_damaged_by_enemies = 1] + -- PlayerMonsterFlaskRemovalAura1 [generate_x_charges_for_any_flask_per_minute = -3] }, } -mods["PlayerMonsterAbyssImmuneAura1"] = { - name = "Undying Will", +mods["PlayerMonsterRevivesMinions1"] = { + name = "Reviving Minions", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "quest_null_monster_mods", weight = 0 }, + { tag = "boss", weight = 0 }, + { tag = "no_minion_revival", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically revives Pack Minions.", }, modList = { - -- PlayerMonsterAbyssImmuneAura1 [ignore_cannot_be_damaged_by_enemies = 0] }, } -mods["MonsterAbyssFactionRunes"] = { - name = "Lithomantic Runes", +mods["PlayerMonsterRevivesMinions2"] = { + name = "Empowered Reviving Minions", type = "Prefix", + tier = 2, + spawnWeights = { + { tag = "quest_null_monster_mods", weight = 0 }, + { tag = "boss", weight = 0 }, + { tag = "no_minion_revival", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically revives Pack Minions with increased Life and Damage.", }, modList = { }, } -mods["PlayerMonsterAbyssFactionRunes"] = { - name = "Lithomantic Runes", +mods["PlayerMonsterMinionsTakeLifeInstead1"] = { + name = "Damage Taken From Minions First", type = "Prefix", + tier = 1, + spawnWeights = { + { tag = "boss", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "50% of damage taken from Monster is taken from Monster's Pack Minions instead", }, modList = { + -- PlayerMonsterMinionsTakeLifeInstead1 [damage_removed_from_pack_minions_before_life_or_es_% = 50] }, } -mods["MonsterAbyssMeteor"] = { - name = "Meteoric Demise", +mods["PlayerMonsterShroudWalker1"] = { + name = "Shroud Walker", type = "Prefix", + tier = 1, + spawnWeights = { + { tag = "sanctum_monster", weight = 0 }, + { tag = "immobile", weight = 0 }, + { tag = "no_shroud_walker", weight = 0 }, + { tag = "boss", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically teleports to an enemy they can see, creating a Smoke Cloud where they leave and where they teleport to.", }, modList = { }, } -mods["PlayerMonsterAbyssMeteor"] = { - name = "Meteoric Demise", +mods["PlayerMonsterShroudWalker2"] = { + name = "Shroud Walker", type = "Prefix", + tier = 2, + spawnWeights = { + { tag = "sanctum_monster", weight = 0 }, + { tag = "immobile", weight = 0 }, + { tag = "no_shroud_walker", weight = 0 }, + { tag = "boss", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically teleports to an enemy they can see, creating a Smoke Cloud where they leave and where they teleport to.", }, modList = { }, } -mods["MonsterAbyssApparitionBeamcaster"] = { - name = "Kulemak's Desecration", +mods["PlayerMonsterPeriodicEnrage1"] = { + name = "Periodically Enrages", type = "Prefix", + tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically Enrages; gaining 30% increased Damage, 25% increased Skill and Movement Speed and 33% less damage taken for 5 seconds every 10 seconds.", }, modList = { }, } -mods["PlayerMonsterAbyssApparitionBeamcaster"] = { - name = "Kulemak's Desecration", +mods["PlayerMonsterPeriodicEnrage2"] = { + name = "Enraged", type = "Prefix", + tier = 2, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster is Enraged; gaining 30% increased Damage, 25% increased Skill and Movement Speed and 33% less damage taken.", }, modList = { }, } -mods["MonsterAbyssPitSplitting"] = { - name = "Ulaman's Legion", +mods["PlayerMonsterCorpseExploder1"] = { + name = "Explodes Nearby Corpses", type = "Prefix", + tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { }, modList = { - -- MonsterAbyssPitSplitting [grant_actor_scale_+%_to_aura_owner_on_death = 0] - -- MonsterAbyssPitSplitting [grant_attack_speed_+%_to_aura_owner_on_death = 0] - -- MonsterAbyssPitSplitting [grant_damage_reduction_%_to_aura_owner_on_death = 0] - -- MonsterAbyssPitSplitting [soul_is_consumed_on_death = 0] }, } -mods["PlayerMonsterAbyssPitSplitting"] = { - name = "Ulaman's Legion", +mods["PlayerMonsterLightningMirage1"] = { + name = "Lightning Mirage When Hit", type = "Prefix", + tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates a Mirage when Hit that moves towards enemies and explodes when it gets close enough, dealing Lightning Damage.", }, modList = { - -- PlayerMonsterAbyssPitSplitting [grant_actor_scale_+%_to_aura_owner_on_death = 0] - -- PlayerMonsterAbyssPitSplitting [grant_attack_speed_+%_to_aura_owner_on_death = 0] - -- PlayerMonsterAbyssPitSplitting [grant_damage_reduction_%_to_aura_owner_on_death = 0] - -- PlayerMonsterAbyssPitSplitting [soul_is_consumed_on_death = 0] }, } -mods["MonsterAbyssPustuleGround1"] = { - name = "Bubonic Trail", +mods["PlayerMonsterLightningMirage2"] = { + name = "Lightning Mirages When Hit", type = "Prefix", - tier = 1, + tier = 2, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates Mirages when Hit that move towards enemies and explode when they get close enough, dealing Lightning Damage.", }, modList = { }, } -mods["PlayerMonsterAbyssPustuleGround1"] = { - name = "Bubonic Trail", +mods["PlayerMonsterMagmaBarrier1"] = { + name = "Magma Barrier", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "sanctum_monster", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates a 90% damage absorption barrier that explodes after taking a certain amount of damage, dealing Fire Damage", }, modList = { }, } -mods["MonsterAbyssGeyserWalls1"] = { - name = "Soulflame Geysers", +mods["PlayerMonsterFlamewaller1"] = { + name = "Conjures Flamewalls", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "sanctum_monster", weight = 0 }, + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster creates circular walls of Fire that deal damage to enemies standing in them.", }, modList = { }, } -mods["PlayerMonsterAbyssGeyserWalls1"] = { - name = "Soulflame Geysers", +mods["PlayerMonsterLightningStorms1"] = { + name = "Conjures Lightning Storms", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { }, modList = { }, } -mods["MonsterAbyssShadeWalker1"] = { - name = "Shade Walker", +mods["PlayerMonsterVolatilePlants1"] = { + name = "Volatile Plants", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically creates Volatile Plants, releasing orbs that move towards enemies; exploding when they get close enough; dealing Chaos Damage.", }, modList = { }, } -mods["PlayerMonsterAbyssShadeWalker1"] = { - name = "Shade Walker", +mods["PlayerMonsterVolatilePlants2"] = { + name = "Empowering Volatile Plants", type = "Prefix", - tier = 1, + tier = 2, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically creates Powerful Volatile Plants, releasing orbs that move towards enemies; exploding when they get close enough; dealing Chaos Damage.", }, modList = { }, } -mods["MonsterAbyssSoulcano1"] = { - name = "Eruption of Souls", +mods["PlayerMonsterVolatileRocks1"] = { + name = "Volatile Crag", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically creates Volatile Crag that moves towards enemies; exploding when they get close enough; dealing Fire Damage.", }, modList = { }, } -mods["PlayerMonsterAbyssSoulcano1"] = { - name = "Eruption of Souls", +mods["PlayerMonsterVolatileRocks2"] = { + name = "Empowering Volatile Crag", type = "Prefix", - tier = 1, + tier = 2, + spawnWeights = { + { tag = "magic", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster periodically creates Powerful Volatile Crag that moves towards enemies; exploding when they get close enough; dealing Fire Damage.", }, modList = { }, } -mods["MonsterProximalTangibilityHidden1"] = { - name = "Ethereal", - rollable = true, +mods["PlayerMonsterProximalTangibility1"] = { + name = "Proximal Tangibility", type = "Prefix", tier = 1, + spawnWeights = { + { tag = "no_proximity_shield", weight = 0 }, + { tag = "boss", weight = 0 }, + { tag = "default", weight = 1 }, + }, statDescriptions = { + "Monster cannot be damaged by enemies any further than 3 metres from them.", }, modList = { - -- MonsterProximalTangibilityHidden1 [ignore_cannot_be_damaged_by_enemies = 0] + -- PlayerMonsterProximalTangibility1 [ignore_cannot_be_damaged_by_enemies = 0] }, } diff --git a/src/Export/Scripts/tamedBeastMods.lua b/src/Export/Scripts/tamedBeastMods.lua index 9b27ac27fe..debf7770fe 100644 --- a/src/Export/Scripts/tamedBeastMods.lua +++ b/src/Export/Scripts/tamedBeastMods.lua @@ -86,46 +86,61 @@ out:write('-- Monster data (c) Grinding Gear Games\n') out:write('\n') out:write('local mods, mod, flag = ...\n') -local exported, unmappedStats = 0, { } -for modRow in dat("Mods"):Rows() do - if modRow.Domain == MONSTER_DOMAIN and (modRow.GenerationType == GEN_PREFIX or modRow.GenerationType == GEN_SUFFIX) and not modRow.Id:match("Royale") then - -- Render description lines at min roll; min == max keeps the text free of "(min-max)" ranges - -- so the importer can match display lines verbatim - local stats = { } - for i = 1, 6 do - if modRow["Stat"..i] then - stats[modRow["Stat"..i].Id] = { min = modRow["Stat"..i.."Value"][1], max = modRow["Stat"..i.."Value"][1] } - end - end - if modRow.Type then - stats.Type = modRow.Type - end - local descLines = describeStats(stats) - local popupName = popupNames[modRow.Id] - local archName = archNames[modRow.Id] and escapeGGGString(archNames[modRow.Id]) - local popupDescription = popupDescriptions[modRow.Id] and escapeGGGString(popupDescriptions[modRow.Id]) - if popupName or archName or descLines[1] or modRow.Name ~= "" then - local name = popupName or archName or (modRow.Name ~= "" and modRow.Name) or descLines[1] - -- A mod can only be rolled (and thus appear on a captured beast) if some monster - -- tag carries a positive spawn weight; script-applied mods (abyss etc.) and the - -- "PlayerMonster*" twins are all-zero. Unnamed placeholders are not selectable. - local rollable = false - for _, weight in ipairs(modRow.SpawnWeight) do +-- Tamed beasts roll their mods in the wild as "Monster*" mods (the ones with real +-- spawn weights); the itemised companion carries the standalone "PlayerMonster*" +-- twin, which is what the character API reports on import, so Player ids are the +-- only ids exported. Display text (keyword popups, nameplate lines) is keyed by +-- the Monster ids, so name and descriptions resolve through the twin; stats come +-- from the Player row itself, which is authoritative for the companion. +local exported, unmappedStats, seen = 0, { }, { } +for archMod in dat("ArchnemesisMods"):Rows() do + local playerRow = archMod.Id + local id = playerRow and playerRow.Id + if id and id:match("^Player") and not seen[id] then + seen[id] = true + local twinId = id:gsub("^Player", "", 1) + local twinRow = dat("Mods"):GetRow("Id", twinId) + -- The mod must exist on the beast side as a rollable prefix/suffix: that is + -- what can appear on a captured beast. + local rollable = false + if twinRow and twinRow.Domain == MONSTER_DOMAIN and (twinRow.GenerationType == GEN_PREFIX or twinRow.GenerationType == GEN_SUFFIX) then + for _, weight in ipairs(twinRow.SpawnWeight) do if weight > 0 then rollable = true break end end - out:write('\nmods["', modRow.Id, '"] = {\n') - out:write('\tname = "', escapeString(name), '",\n') - if rollable and name ~= "TBD" then - out:write('\trollable = true,\n') + end + if rollable then + local stats = { } + for i = 1, 6 do + if playerRow["Stat"..i] then + stats[playerRow["Stat"..i].Id] = { min = playerRow["Stat"..i.."Value"][1], max = playerRow["Stat"..i.."Value"][1] } + end + end + if playerRow.Type then + stats.Type = playerRow.Type end - out:write('\ttype = "', modRow.GenerationType == GEN_PREFIX and "Prefix" or "Suffix", '",\n') - local tier = tonumber(modRow.Id:match("(%d+)$")) + local descLines = describeStats(stats) + local popupName = popupNames[twinId] + local archName = archNames[twinId] and escapeGGGString(archNames[twinId]) + local popupDescription = popupDescriptions[twinId] and escapeGGGString(popupDescriptions[twinId]) + local name = popupName or archName or (twinRow.Name ~= "" and twinRow.Name) or descLines[1] or twinId + out:write('\nmods["', id, '"] = {\n') + out:write('\tname = "', escapeString(name), '",\n') + out:write('\ttype = "', twinRow.GenerationType == GEN_PREFIX and "Prefix" or "Suffix", '",\n') + local tier = tonumber(id:match("(%d+)$")) if tier then out:write('\ttier = ', tier, ',\n') end + if #twinRow.SpawnTags ~= #twinRow.SpawnWeight then + printf("Warning: %s has %d spawn tags but %d weights", twinId, #twinRow.SpawnTags, #twinRow.SpawnWeight) + end + out:write('\tspawnWeights = {\n') + for i, tagRow in ipairs(twinRow.SpawnTags) do + out:write('\t\t{ tag = "', tagRow.Id, '", weight = ', twinRow.SpawnWeight[i] or 0, ' },\n') + end + out:write('\t},\n') out:write('\tstatDescriptions = {\n') if archName and archName ~= name then out:write('\t\t"', escapeString(archName), '",\n') @@ -139,18 +154,18 @@ for modRow in dat("Mods"):Rows() do out:write('\t},\n') out:write('\tmodList = {\n') for i = 1, 6 do - if modRow["Stat"..i] then - local statId = modRow["Stat"..i].Id - local modStats = ' [' .. statId .. ' = ' .. modRow["Stat"..i.."Value"][1] .. ']' + if playerRow["Stat"..i] then + local statId = playerRow["Stat"..i].Id + local modStats = ' [' .. statId .. ' = ' .. playerRow["Stat"..i.."Value"][1] .. ']' if skillStatMap[statId] then local newMod = skillStatMap[statId][1] - out:write('\t\tmod("', newMod.name, '", "', newMod.type, '", ', newMod.value and type(newMod.value) ~= "boolean" and tableToString(newMod.value) or (skillStatMap[statId].value or modRow["Stat"..i.."Value"][1] * (skillStatMap[statId].mult or 1) / (skillStatMap[statId].div or 1)), ', ', newMod.flags or 0, ', ', newMod.keywordFlags or 0) + out:write('\t\tmod("', newMod.name, '", "', newMod.type, '", ', newMod.value and type(newMod.value) ~= "boolean" and tableToString(newMod.value) or (skillStatMap[statId].value or playerRow["Stat"..i.."Value"][1] * (skillStatMap[statId].mult or 1) / (skillStatMap[statId].div or 1)), ', ', newMod.flags or 0, ', ', newMod.keywordFlags or 0) for _, extra in ipairs(newMod) do out:write(', ', tableToString(extra)) end - out:write('), -- ', modRow.Id, modStats, '\n') + out:write('), -- ', id, modStats, '\n') else - out:write('\t\t-- ', modRow.Id, modStats, '\n') + out:write('\t\t-- ', id, modStats, '\n') unmappedStats[statId] = (unmappedStats[statId] or 0) + 1 end end diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 17fd77c9c4..defdcb0539 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1026,7 +1026,8 @@ function calcs.perform(env, skipEHP) if mainSrcInstance and mainSrcInstance.tamedBeastModList and mainGrantedEffect and mainGrantedEffect.minionList and mainGrantedEffect.name:match("^Companion") then for _, beastMod in ipairs(mainSrcInstance.tamedBeastModList) do local beastModData = beastMod.enabled and beastMod.modId and env.data.tamedBeastMods[beastMod.modId] - if beastModData then + -- A mod the beast's tags can never roll is ignoreed + if beastModData and data.beastModCanSpawn(beastModData, env.minion.minionData.monsterTags) then for _, mod in ipairs(beastModData.modList) do env.minion.modDB:AddMod(mod) end diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index 659c015cdc..0daf439a2e 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -1004,6 +1004,19 @@ LoadModule("Data/TamedBeastMods", data.tamedBeastMods, makeSkillMod, makeFlagMod function data.normaliseBeastModLine(line) return (line:lower():gsub("^%s+", ""):gsub("%s+$", "")) end + +function data.beastModCanSpawn(beastMod, monsterTags) + local tagSet = { } + for _, tag in ipairs(monsterTags or { }) do + tagSet[tag] = true + end + for _, entry in ipairs(beastMod.spawnWeights) do + if entry.tag == "default" or tagSet[entry.tag] then + return entry.weight > 0 + end + end + return false +end -- Secondary index for import lines that don't carry a "[ModId|...]" token; iterate ids in -- sorted order so collisions deterministically resolve to the lowest id data.tamedBeastModsByDisplay = { }