Puppeteer/core/Roles.lua
2025-09-08 17:16:48 -07:00

346 lines
11 KiB
Lua

PTUtil.SetEnvironment(Puppeteer)
local _G = getfenv(0)
local util = PTUtil
local colorize = util.Colorize
local GetColoredRoleText = util.GetColoredRoleText
local SplitString = util.SplitString
AssignedRoles = nil
LFTAutoRoleFrame = CreateFrame("Frame", "PT_LFTAutoRoleFrame")
function SetLFTAutoRoleEnabled(enabled)
if not util.IsTurtleWow() then
return
end
if not enabled then
LFTAutoRoleFrame:SetScript("OnEvent", nil)
LFTAutoRoleFrame:SetScript("OnUpdate", nil)
LFTAutoRoleFrame:UnregisterAllEvents()
return
end
if LFTAutoRoleFrame:GetScript("OnEvent") ~= nil then -- Already enabled
return
end
local roleMap = {
["t"] = "Tank",
["h"] = "Healer",
["d"] = "Damage"
}
local offerCompleteTime = -1 -- The time the offer is completed, used to verify rolecheck info is valid
local alreadyAssigned = {} -- The Tank & Healer names
local scanDPSTime = -1
local AutoRoleFrame_OnUpdate = function()
if scanDPSTime > GetTime() then
return
end
for _, unit in ipairs(PartyUnits) do
local name = UnitName(unit)
if name ~= "Unknown" and not util.ArrayContains(alreadyAssigned, name) then
print("Assuming "..name.." is Damage")
SetAssignedRole(name, "Damage")
end
end
UpdateUnitFrameGroups()
LFTAutoRoleFrame:SetScript("OnUpdate", nil)
end
LFTAutoRoleFrame:RegisterEvent("CHAT_MSG_ADDON")
LFTAutoRoleFrame:SetScript("OnEvent", function()
if arg1 == (LFT_ADDON_PREFIX or "TW_LFG") then
-- After an offer is complete, it is immediately followed by rolecheck info for the tank and healer
if strfind(arg2, "S2C_ROLECHECK_INFO") then
if GetNumPartyMembers() == 4 then
local params = util.SplitString(arg2, ";")
local member = params[2]
params = util.SplitString(params[3], ":") -- get confirmed roles
if offerCompleteTime + 0.5 > GetTime() and table.getn(params) == 1 then
local role = roleMap[params[1]]
table.insert(alreadyAssigned, member)
SetAssignedRole(member, role) -- Tank & Healer is sent after the offer is complete
print("Assigning "..member.." to "..role)
if table.getn(alreadyAssigned) == 2 then -- Set rest as Damage after Tank & Healer is sent
-- Delay scanning party members because their names might not be loaded yet
scanDPSTime = GetTime() + 1.5
LFTAutoRoleFrame:SetScript("OnUpdate", AutoRoleFrame_OnUpdate)
end
UpdateUnitFrameGroups()
end
end
elseif strfind(arg2, "S2C_OFFER_COMPLETE") then
alreadyAssigned = {}
offerCompleteTime = GetTime()
end
end
end)
end
function GetAssignedRole(name)
if not AssignedRoles or not AssignedRoles[name] then
return
end
AssignedRoles[name]["lastSeen"] = time()
return AssignedRoles[name]["role"]
end
function GetUnitAssignedRole(unit)
if not UnitIsPlayer(unit) then
return
end
return GetAssignedRole(UnitName(unit))
end
function SetAssignedRole(name, role)
if role == nil or role == "No Role" then
AssignedRoles[name] = nil
return
end
AssignedRoles[name] = {
["role"] = role,
["lastSeen"] = time()
}
end
-- Returns true if role assignment failed
function SetUnitAssignedRole(unit, role)
if not UnitIsPlayer(unit) then
return true
end
SetAssignedRole(UnitName(unit), role)
end
function PruneAssignedRoles()
local currentTime = time()
for name, data in pairs(AssignedRoles) do
if not data["lastSeen"] or data["lastSeen"] < currentTime - (24 * 60 * 60) then
AssignedRoles[name] = nil
--print("Pruned "..name.."'s role")
end
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)
if not TalentCountRoleMap[class] then
SetRoleAndUpdate(name, "Damage")
return
end
PlayerTalentData[name] = {class = class, trees = {}}
requestTalents(name)
end
RoleAssignInfo = {}
RoleDropdown = PTGuiLib.Get("dropdown", UIParent)
function InitRoleDropdown()
local initFunc = function(self)
self.checked = (GetAssignedRole(RoleAssignInfo.Name) or "No Role") == self.role
end
local genRole = function(role)
return {
text = GetColoredRoleText(role),
role = role,
initFunc = initFunc,
func = function(info)
SetAssignedRole(RoleAssignInfo.Name, info.role)
UpdateUnitFrameGroups()
end
}
end
local massRoleFunc = function(info)
if not RoleAssignInfo.FrameGroup then
return
end
for _, ui in pairs(RoleAssignInfo.FrameGroup.uis) do
if (not ui:GetRole() or not info.role) and UnitIsPlayer(ui:GetUnit()) then
SetAssignedRole(UnitName(ui:GetUnit()), info.role)
end
end
UpdateUnitFrameGroups()
RoleDropdown:SetToggleState(false)
end
local genMassRole = function(role)
return {
text = GetColoredRoleText(role),
role = role,
notCheckable = true,
func = massRoleFunc
}
end
local options = {
{
initFunc = function(self)
self.text = colorize(RoleAssignInfo.Name.."'s Role", RoleAssignInfo.ClassColor)
end,
notCheckable = true,
disabled = true,
textHeight = 12
},
genRole("Tank"),
genRole("Healer"),
genRole("Damage"),
genRole("No Role"),
{
notCheckable = true,
disabled = true
}, {
text = "Set Unassigned As",
tooltipTitle = "Set Unassigned As",
tooltipText = "Mass-set the roles of unassigned players. Only applies to players contained in this UI group.",
notCheckable = true,
textHeight = 11,
children = {
genMassRole("Tank"),
genMassRole("Healer"),
genMassRole("Damage")
}
}, {
text = "Clear Roles",
tooltipTitle = "Clear Roles",
tooltipText = "Clear all players' roles. Only applies to players contained in this UI group.",
notCheckable = true,
textHeight = 11,
func = massRoleFunc
}
}
if PTGlobalOptions.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