From 87d27f4cc975eb0ee31727ecfa0f3b3fa6825c55 Mon Sep 17 00:00:00 2001 From: OldManAlpha <60587722+OldManAlpha@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:02:32 -0700 Subject: [PATCH] Add experimental auto role detection --- Puppeteer.lua | 2 + PuppeteerSettings.lua | 3 + core/ActionBindings.lua | 1 + core/Roles.lua | 142 +++++++++++++++++++++++++++++++++++++++- gui/Settings.lua | 20 ++++-- 5 files changed, 161 insertions(+), 7 deletions(-) diff --git a/Puppeteer.lua b/Puppeteer.lua index 7075c4d..ef91d63 100644 --- a/Puppeteer.lua +++ b/Puppeteer.lua @@ -354,6 +354,8 @@ function OnAddonLoaded() end end) end + + InitRoleDropdown() SetLFTAutoRoleEnabled(PTOptions.LFTAutoRole) diff --git a/PuppeteerSettings.lua b/PuppeteerSettings.lua index e3d4b86..7efe524 100644 --- a/PuppeteerSettings.lua +++ b/PuppeteerSettings.lua @@ -133,6 +133,9 @@ function SetDefaults() ["Medium"] = 10, -- <=2 min ["Long"] = 60 * 2 -- >2 min }, + ["Experiments"] = { + ["AutoRole"] = false + }, ["CastWhen"] = "Mouse Up", -- Mouse Up, Mouse Down ["CastWhenKey"] = "Key Up", -- Key Up, Key Down ["AutoResurrect"] = Puppeteer.ResurrectionSpells[util.GetClass("player")] ~= nil, diff --git a/core/ActionBindings.lua b/core/ActionBindings.lua index 33528e5..74e6b45 100644 --- a/core/ActionBindings.lua +++ b/core/ActionBindings.lua @@ -81,6 +81,7 @@ RegisterActionBind({ return end RoleAssignInfo.Name = UnitName(unit) + RoleAssignInfo.Class = util.GetClass(unit) RoleAssignInfo.ClassColor = util.GetClassColor(util.GetClass(unit), true) RoleAssignInfo.FrameGroup = unitFrame.owningGroup local frame = unitFrame:GetRootContainer() diff --git a/core/Roles.lua b/core/Roles.lua index 67b610b..e3f6555 100644 --- a/core/Roles.lua +++ b/core/Roles.lua @@ -3,6 +3,7 @@ local _G = getfenv(0) local util = PTUtil local colorize = util.Colorize local GetColoredRoleText = util.GetColoredRoleText +local SplitString = util.SplitString AssignedRoles = nil @@ -120,15 +121,117 @@ function PruneAssignedRoles() end end +function SetRoleAndUpdate(name, role) + SetAssignedRole(name, role) + UpdateUnitFrameGroups() +end + function SetUnitRoleAndUpdate(unit, role) if not SetUnitAssignedRole(unit, role) then UpdateUnitFrameGroups() end end +-- Players will be considered as the role in the index if they have the highest talent points in said index. +-- Clases not listed have only DPS specs and are not bothered to be scanned. +TalentCountRoleMap = { + PRIEST = { + "Healer", "Healer", "Damage" + }, + PALADIN = { + "Healer", "Tank", "Damage" + }, + WARRIOR = { + "Damage", "Damage", "Tank" + }, + SHAMAN = { + "Damage", "Damage", "Healer" + }, + DRUID = { -- Spec #2(Feral) will be swapped to Tank if Thick Hide talent is found + "Damage", "Damage", "Healer" + } +} + +local PlayerTalentData = {} +local talentScanner = CreateFrame("Frame", "PTTalentScanner") +talentScanner:RegisterEvent("CHAT_MSG_ADDON") +talentScanner:SetScript("OnEvent", function() + if arg1 == "TW_CHAT_MSG_WHISPER" then + local message = arg2 + local sender = arg4 + + if not PlayerTalentData[sender] then + return + end + + if string.find(message, "INSTalentTabInfo;", 1, true) then + -- This is sent right before receiving info for individual talents in the tree + local split = SplitString(message, ';') + local index = tonumber(split[2]) + local pointsSpent = tonumber(split[4]) + + PlayerTalentData[sender].trees[index] = {points = pointsSpent, talents = {}} + elseif string.find(message, "INSTalentInfo;", 1, true) then + local split = SplitString(message, ';') + + local tree = tonumber(split[2]) + local tier = tonumber(split[5]) + local column = tonumber(split[6]) + local currRank = tonumber(split[7]) + + local cache = PlayerTalentData[sender] + local talents = cache.trees[tree].talents + talents[tier.."-"..column] = currRank + elseif string.find(message, "INSTalentEND;", 1, true) then + local data = PlayerTalentData[sender] + local trees = data.trees + local mostPoints = 0 + local mostIndex = 1 + for i = 1, 3 do + if trees[i].points > mostPoints then + mostPoints = trees[i].points + mostIndex = i + end + end + local class = data.class + -- Check for Druid Thick Hide talent, set as tank if they have it + if class == "DRUID" and (trees[2].talents["2-3"] or 0) > 0 then + SetRoleAndUpdate(sender, "Tank") + else + SetRoleAndUpdate(sender, mostPoints > 0 and TalentCountRoleMap[class][mostIndex] or "Damage") + end + PlayerTalentData[sender] = nil + end + end +end) + +local function requestTalents(name) + SendAddonMessage("TW_CHAT_MSG_WHISPER<"..name..">", "INSShowTalents", "GUILD") +end + +function AutoRole(unit) + local class = util.GetClass(unit) + if not TalentCountRoleMap[class] then + SetUnitRoleAndUpdate(unit, "Damage") + return + end + if not UnitIsConnected(unit) then -- Can't request offline player's talents + return + end + PlayerTalentData[UnitName(unit)] = {class = class, trees = {}} + requestTalents(UnitName(unit)) +end + +function AutoRoleByNameClass(name, class) + PlayerTalentData[name] = {class = class, trees = {}} + requestTalents(name) +end + RoleAssignInfo = {} -do +RoleDropdown = PTGuiLib.Get("dropdown", UIParent) + +function InitRoleDropdown() local initFunc = function(self) self.checked = (GetAssignedRole(RoleAssignInfo.Name) or "No Role") == self.role end @@ -165,7 +268,6 @@ do } end - RoleDropdown = PTGuiLib.Get("dropdown", UIParent) local options = { { initFunc = function(self) @@ -178,7 +280,7 @@ do genRole("Tank"), genRole("Healer"), genRole("Damage"), - genRole("No Role"), + genRole("No Role"), { notCheckable = true, disabled = true @@ -202,5 +304,39 @@ do func = massRoleFunc } } + if PTOptions.Experiments.AutoRole then + table.insert(options, 6, { + text = colorize("Auto Detect", 1, 0.6, 0), + func = function() + if not RoleAssignInfo.FrameGroup then + return + end + AutoRoleByNameClass(RoleAssignInfo.Name, RoleAssignInfo.Class) + end + }) + local lastMassRole = 0 + table.insert(options, 9, { + text = "Auto Detect Unassigned", + tooltipTitle = "Auto Detect Unassigned", + tooltipText = "Automatically detect the roles of unassigned players. Only applies to players contained in this UI group.", + notCheckable = true, + textHeight = 11, + func = function() + if not RoleAssignInfo.FrameGroup then + return + end + if lastMassRole + 6 > GetTime() then + DEFAULT_CHAT_FRAME:AddMessage("Please wait a moment before requesting roles again") + return + end + lastMassRole = GetTime() + for _, ui in pairs(RoleAssignInfo.FrameGroup.uis) do + if UnitIsPlayer(ui:GetUnit()) and not GetUnitAssignedRole(ui:GetUnit()) then + AutoRole(ui:GetUnit()) + end + end + end + }) + end RoleDropdown:SetOptions(options) end \ No newline at end of file diff --git a/gui/Settings.lua b/gui/Settings.lua index 9b357f1..6fe9f4c 100644 --- a/gui/Settings.lua +++ b/gui/Settings.lua @@ -436,7 +436,7 @@ function CreateTab_Options_SpellsTooltip(panel) layout:offset(0, 10) factory:dropdown("Anchor", "Where the tooltip should be anchored", "SpellsTooltip.Anchor", {"Top Left", "Top Right", "Bottom Left", "Bottom Right"}) - factory:checkbox("Show Item Count", {"Show the amount of your bound items", colorize("Warning: This causes lag!", 1, 0.2, 0.2)}, + factory:checkbox("Show Item Count", {"Show the amount of your bound items", colorize("Warning: This causes lag!", 1, 0.4, 0.4)}, "SpellsTooltip.ShowItemCount") end @@ -477,22 +477,26 @@ function CreateTab_Options_Other(panel) "UseHealPredictions", function() Puppeteer.UpdateAllIncomingHealing() end) factory:checkbox("(TWoW) LFT Auto Role", {"Automatically assign roles when joining LFT groups", - "This functionality was created for 1.17.2 and may break in future updates"}, "LFTAutoRole", + "This functionality was tested for 1.18.0 and may break in future updates"}, "LFTAutoRole", function() Puppeteer.SetLFTAutoRoleEnabled(PTOptions.LFTAutoRole) end) end function CreateTab_Options_Advanced(panel) local container = panel:CreateTab("Advanced") - local layout = NewLabeledColumnLayout(container, {150, 220, 300}, -20, 10) + local layout = NewLabeledColumnLayout(container, {150, 220, 300}, 0, 10) local factory = NewComponentFactory(container, layout) container.factory = factory local TEXT_WIDTH = 370 + local scriptsLabel = CreateLabel(container, "Load & Postload Scripts") + :SetPoint("TOP", container, "TOP", 0, -10) + :SetFontSize(14) + local loadScriptInfo = CreateLabel(container, "The Load Script runs after profiles are initialized, but before UIs are created, ".. "making it good for editing profile attributes. GetProfile and CreateProfile are defined locals.") :SetWidth(TEXT_WIDTH) - :SetPoint("TOP", container, "TOP", 0, -10) + :SetPoint("TOP", scriptsLabel, "BOTTOM", 0, -10) local loadScriptButton = PTGuiLib.Get("button", container) :SetPoint("TOP", loadScriptInfo, "BOTTOM", 0, -5) :SetSize(150, 20) @@ -547,6 +551,14 @@ function CreateTab_Options_Advanced(panel) :OnClick(function() ReloadUI() end) + + local experimentsLabel = CreateLabel(container, "Experiments") + :SetPoint("TOP", reloadButton, "BOTTOM", 0, -20) + :SetFontSize(14) + layout:offset(0, -260) + factory:checkbox("(TWoW) Auto Role", {"If enabled, the Role Action menu shows auto role detection options", + colorize("Using this functionality WILL cause errors and other unexpected behavior", 1, 0.4, 0.4)}, "Experiments.AutoRole", + Puppeteer.InitRoleDropdown) end function CreateTab_Options_Mods(panel)