Puppeteer/Puppeteer.lua
2025-06-28 15:45:13 -07:00

1759 lines
62 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

SLASH_PUPPETEER1 = "/puppeteer"
SLASH_PUPPETEER2 = "/pt"
SLASH_PUPPETEER3 = "/hm"
SlashCmdList["PUPPETEER"] = function(args)
if args == "reset" then
for _, group in pairs(Puppeteer.UnitFrameGroups) do
local gc = group:GetContainer()
gc:ClearAllPoints()
gc:SetPoint(PTUtil.GetCenterScreenPoint(gc:GetWidth(), gc:GetHeight()))
end
PuppeteerSettings.HM_SettingsContainer:ClearAllPoints()
PuppeteerSettings.HM_SettingsContainer:SetPoint("CENTER", 0, 0)
DEFAULT_CHAT_FRAME:AddMessage("Reset all frame positions.")
elseif args == "check" then
Puppeteer.CheckGroup()
elseif args == "update" then
for _, ui in pairs(Puppeteer.AllUnitFrames) do
ui:SizeElements()
ui:UpdateAll()
end
for _, group in pairs(Puppeteer.UnitFrameGroups) do
group:ApplyProfile()
group:UpdateUIPositions()
end
elseif args == "testui" then
PTOptions.TestUI = not PTOptions.TestUI
Puppeteer.TestUI = PTOptions.TestUI
if PTOptions.TestUI then
for _, ui in pairs(Puppeteer.AllUnitFrames) do
ui.fakeStats = ui.GenerateFakeStats()
ui:Show()
end
end
Puppeteer.CheckGroup()
if not PTOptions.TestUI and PTUnitProxy then
for _, type in ipairs(PTUnitProxy.CustomUnitTypes) do
PTUnitProxy.UpdateUnitTypeFrames(type)
end
end
DEFAULT_CHAT_FRAME:AddMessage("UI Testing is now "..(not PTOptions.TestUI and
PTUtil.Colorize("off", 1, 0.6, 0.6) or PTUtil.Colorize("on", 0.6, 1, 0.6))..".")
elseif args == "toggle" then
PTOptions.Hidden = not PTOptions.Hidden
Puppeteer.CheckGroup()
DEFAULT_CHAT_FRAME:AddMessage("The Puppeteer UI is now "..(PTOptions.Hidden and
PTUtil.Colorize("hidden", 1, 0.6, 0.6) or PTUtil.Colorize("shown", 0.6, 1, 0.6))..".")
elseif args == "show" then
PTOptions.Hidden = false
Puppeteer.CheckGroup()
DEFAULT_CHAT_FRAME:AddMessage("The Puppeteer UI is now "..(PTOptions.Hidden and
PTUtil.Colorize("hidden", 1, 0.6, 0.6) or PTUtil.Colorize("shown", 0.6, 1, 0.6))..".")
elseif args == "hide" then
PTOptions.Hidden = true
Puppeteer.CheckGroup()
DEFAULT_CHAT_FRAME:AddMessage("The Puppeteer UI is now "..(PTOptions.Hidden and
PTUtil.Colorize("hidden", 1, 0.6, 0.6) or PTUtil.Colorize("shown", 0.6, 1, 0.6))..".")
elseif args == "silent" then
PTOnLoadInfoDisabled = not PTOnLoadInfoDisabled
DEFAULT_CHAT_FRAME:AddMessage("Load message is now "..(PTOnLoadInfoDisabled and
PTUtil.Colorize("off", 1, 0.6, 0.6) or PTUtil.Colorize("on", 0.6, 1, 0.6))..".")
elseif args == "help" or args == "?" then
DEFAULT_CHAT_FRAME:AddMessage(PTUtil.Colorize("/pt", 0, 0.8, 0).." -- Opens the addon configuration")
DEFAULT_CHAT_FRAME:AddMessage(PTUtil.Colorize("/pt reset", 0, 0.8, 0).." -- Resets all heal frame positions")
DEFAULT_CHAT_FRAME:AddMessage(PTUtil.Colorize("/pt testui", 0, 0.8, 0)..
" -- Toggles fake players to see how the UI would look")
DEFAULT_CHAT_FRAME:AddMessage(PTUtil.Colorize("/pt toggle", 0, 0.8, 0).." -- Shows/hides the UI")
DEFAULT_CHAT_FRAME:AddMessage(PTUtil.Colorize("/pt show", 0, 0.8, 0).." -- Shows the UI")
DEFAULT_CHAT_FRAME:AddMessage(PTUtil.Colorize("/pt hide", 0, 0.8, 0).." -- Hides the UI")
DEFAULT_CHAT_FRAME:AddMessage(PTUtil.Colorize("/pt silent", 0, 0.8, 0).." -- Turns off/on message when addon loads")
elseif args == "" then
local container = PuppeteerSettings.HM_SettingsContainer
if container then
if container:IsVisible() then
container:Hide()
else
container:Show()
end
else
DEFAULT_CHAT_FRAME:AddMessage("HM_SettingsContainer frame not found.")
end
else
DEFAULT_CHAT_FRAME:AddMessage("Unknown subcommand. See usage with /pt help")
end
end
PuppeteerLib = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0")
Puppeteer = {}
local _G = getfenv(0)
setmetatable(Puppeteer, {__index = getfenv(1)})
setfenv(1, Puppeteer)
VERSION = GetAddOnMetadata("Puppeteer", "version")
TestUI = false
Banzai = AceLibrary("Banzai-1.0")
HealComm = AceLibrary("HealComm-1.0")
GuidRoster = PTGuidRoster -- Will be nil if SuperWoW isn't present
local util = PTUtil
local colorize = util.Colorize
local GetKeyModifier = util.GetKeyModifier
local GetClass = util.GetClass
local GetPowerType = util.GetPowerType
local GetColoredRoleText = util.GetColoredRoleText
local UseItem = util.UseItem
local GetItemCount = util.GetItemCount
PartyUnits = util.PartyUnits
PetUnits = util.PetUnits
TargetUnits = util.TargetUnits
RaidUnits = util.RaidUnits
RaidPetUnits = util.RaidPetUnits
AllUnits = util.AllUnits
AllUnitsSet = util.AllUnitsSet
AllCustomUnits = util.CustomUnits
AllCustomUnitsSet = util.CustomUnitsSet
if PTUnitProxy then
PTUnitProxy.ImportFunctions(Puppeteer)
end
-- TODO: Actually use this
UIGroupInfo = {}
UIGroupInfo["Party"] = {
units = PartyUnits,
environment = "party",
enableCondition = function()
return true
end}
UIGroupInfo["Pets"] = {
units = PetUnits,
environment = "party",
enableCondition = function()
return true
end
}
UIGroupInfo["Raid"] = {
units = RaidUnits,
environment = "raid",
enableCondition = function()
return true
end
}
UIGroupInfo["Raid Pets"] = {
units = RaidPetUnits,
environment = "raid",
enableCondition = function()
return true
end
}
UIGroupInfo["Target"] = {
units = TargetUnits,
environment = "all",
enableCondition = function()
return true
end
}
-- Relic of previous versions, may be removed
PreviousHealth = {} --This is used to determine if the player gained or lost health, used in the scrolling combat text functions
for _, unit in ipairs(AllUnits) do
PreviousHealth[unit] = -1
end
ReadableButtonMap = {
["LeftButton"] = "Left",
["MiddleButton"] = "Middle",
["RightButton"] = "Right",
["Button4"] = "Button 4",
["Button5"] = "Button 5"
}
ResurrectionSpells = {
["PRIEST"] = "Resurrection",
["PALADIN"] = "Redemption",
["SHAMAN"] = "Ancestral Spirit",
["DRUID"] = "Rebirth"
}
local ptBarsPath = util.GetAssetsPath().."textures\\bars\\"
BarStyles = {
["Blizzard"] = "Interface\\TargetingFrame\\UI-StatusBar",
["Blizzard Smooth"] = ptBarsPath.."Blizzard-Smooth",
["Blizzard Raid"] = ptBarsPath.."Blizzard-Raid",
["Blizzard Raid Sideless"] = ptBarsPath.."Blizzard-Raid-Sideless",
["HealersMate"] = ptBarsPath.."HealersMate",
["HealersMate Borderless"] = ptBarsPath.."HealersMate-Borderless",
["HealersMate Shineless"] = ptBarsPath.."HealersMate-Shineless",
["HealersMate Shineless Borderless"] = ptBarsPath.."HealersMate-Shineless-Borderless"
}
GameTooltip = CreateFrame("GameTooltip", "PTGameTooltip", UIParent, "GameTooltipTemplate")
CurrentlyHeldButton = nil
SpellsTooltip = CreateFrame("GameTooltip", "PTSpellsTooltip", UIParent, "GameTooltipTemplate")
SpellsTooltipOwner = nil
SpellsTooltipPowerBar = nil
do
local manaBar = CreateFrame("StatusBar", "PTSpellsTooltipManaBar", SpellsTooltip)
SpellsTooltipPowerBar = manaBar
manaBar:SetStatusBarTexture(BarStyles["HealersMate"])
manaBar:SetMinMaxValues(0, 1)
manaBar:SetWidth(100)
manaBar:SetHeight(12)
manaBar:SetPoint("TOPRIGHT", SpellsTooltip, "TOPRIGHT", -10, -12)
local bg = manaBar:CreateTexture(nil, "BACKGROUND")
manaBar.background = bg
bg:SetAllPoints(true)
bg:SetTexture(0.3, 0.3, 0.3, 0.8)
local text = manaBar:CreateFontString(nil, "ARTWORK", "GameFontNormal")
manaBar.text = text
text:SetWidth(manaBar:GetWidth())
text:SetHeight(manaBar:GetHeight())
text:SetPoint("CENTER", manaBar, "CENTER")
text:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE")
text:SetShadowOffset(0, 0)
text:SetJustifyH("CENTER")
text:SetJustifyV("CENTER")
end
-- An unmapped array of all unit frames
AllUnitFrames = {}
-- A map of units to an array of unit frames associated with the unit
PTUnitFrames = {}
-- Key: Unit frame group name | Value: The group
UnitFrameGroups = {}
CustomUnitGUIDMap = PTUnitProxy and PTUnitProxy.CustomUnitGUIDMap or {}
GUIDCustomUnitMap = PTUnitProxy and PTUnitProxy.GUIDCustomUnitMap or {}
CurrentlyInRaid = false
AssignedRoles = nil
-- Returns the array of unit frames of the unit
function GetUnitFrames(unit)
return PTUnitFrames[unit]
end
-- A temporary dummy function while the addon initializes. See below for the real iterator.
function UnitFrames(unit)
return function() end
end
local function OpenUnitFramesIterator()
-- UnitFrames function definition.
-- Returns an iterator for the unit frames of the unit.
-- These iterators have a serious problem in that they do not support concurrent iteration.
if util.IsSuperWowPresent() then
local EMPTY_UIS = {}
local PTUnitFrames = PTUnitFrames
local GuidUnitMap = PTGuidRoster.GuidUnitMap
local iterTable = {} -- The table reused for iteration over GUID units
local uis
local i = 0
local len = 0
local iterFunc = function()
i = i + 1
if i <= len then
return uis[i]
end
end
function UnitFrames(unit)
if i < len then
print("Collision: "..i.."/"..len)
end
if GuidUnitMap[unit] then -- If a GUID is provided, ALL UIs associated with that GUID will be iterated
uis = iterTable
for i = 1, table.getn(uis) do
uis[i] = nil
end
table.setn(uis, 0)
for _, unit in pairs(GuidUnitMap[unit]) do
for _, frame in ipairs(PTUnitFrames[unit]) do
table.insert(uis, frame)
end
end
else
uis = PTUnitFrames[unit] or EMPTY_UIS
end
len = table.getn(uis)
i = 0
return iterFunc
end
else -- Optimized version for vanilla
local PTUnitFrames = PTUnitFrames
local uis
local i = 0
local len = 0
local iterFunc = function()
i = i + 1
if i <= len then
return uis[i]
end
end
function UnitFrames(unit)
i = 0
uis = PTUnitFrames[unit]
len = table.getn(uis)
return iterFunc
end
end
end
--This is just to respond to events "EventHandlerFrame" never appears on the screen
local EventHandlerFrame = CreateFrame("Frame", "PTEventHandlerFrame", UIParent)
EventHandlerFrame:RegisterEvent("ADDON_LOADED"); -- This triggers once for every addon that was loaded after this addon
EventHandlerFrame:RegisterEvent("PLAYER_LOGOUT"); -- Fired when about to log out
EventHandlerFrame:RegisterEvent("PLAYER_QUITING"); -- Fired when a player has the quit option on screen
EventHandlerFrame:RegisterEvent("UNIT_HEALTH") --“UNIT_HEALTH” fires when a units health changes
EventHandlerFrame:RegisterEvent("UNIT_MAXHEALTH")
EventHandlerFrame:RegisterEvent("UNIT_AURA") -- Register for the "UNIT_AURA" event to update buffs and debuffs
EventHandlerFrame:RegisterEvent("PLAYER_ENTERING_WORLD") -- Fired when the player enters the world, reloads the UI, or zones between map instances. Basically, it triggers whenever a loading screen appears2. This includes logging in, respawning at a graveyard, entering/leaving an instance, and other situations where a loading screen is presented.
EventHandlerFrame:RegisterEvent("PARTY_MEMBERS_CHANGED") -- Fired when someone joins or leaves the group
EventHandlerFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
EventHandlerFrame:RegisterEvent("RAID_ROSTER_UPDATE")
EventHandlerFrame:RegisterEvent("UNIT_PET")
EventHandlerFrame:RegisterEvent("PLAYER_PET_CHANGED")
EventHandlerFrame:RegisterEvent("SPELLS_CHANGED")
EventHandlerFrame:RegisterEvent("RAID_TARGET_UPDATE")
EventHandlerFrame:RegisterEvent("UNIT_MANA")
EventHandlerFrame:RegisterEvent("UNIT_DISPLAYPOWER")
EventHandlerFrame:RegisterEvent("UNIT_RAGE")
EventHandlerFrame:RegisterEvent("UNIT_ENERGY")
EventHandlerFrame:RegisterEvent("UNIT_FOCUS")
EventHandlerFrame:RegisterEvent("UNIT_MAXMANA")
local lastModifier = "None"
EventHandlerFrame:SetScript("OnUpdate", function()
local modifier = GetKeyModifier()
if lastModifier ~= modifier then
lastModifier = modifier
if SpellsTooltip:IsVisible() then
ReapplySpellsTooltip()
end
end
end)
function Debug(msg)
DEFAULT_CHAT_FRAME:AddMessage(msg)
end
function GetSpells()
return PTSpells["Friendly"]
end
function GetHostileSpells()
return PTSpells["Hostile"]
end
function UpdateUnitFrameGroups()
for _, group in pairs(UnitFrameGroups) do
group:UpdateUIPositions()
end
end
local ScanningTooltip = CreateFrame("GameTooltip", "PTScanningTooltip", nil, "GameTooltipTemplate");
ScanningTooltip:SetOwner(WorldFrame, "ANCHOR_NONE");
-- Allow tooltip SetX() methods to dynamically add new lines based on these
ScanningTooltip:AddFontStrings(
ScanningTooltip:CreateFontString( "$parentTextLeft1", nil, "GameTooltipText" ),
ScanningTooltip:CreateFontString( "$parentTextRight1", nil, "GameTooltipText" ) );
-- Thanks ChatGPT
function ExtractSpellRank(spellname)
-- Find the starting position of "Rank "
local start_pos = string.find(spellname, "Rank ")
-- Check if "Rank " was found
if start_pos then
-- Adjust start_pos to point to the first digit
--start_pos = start_pos + 5 -- Move past "Rank "
-- Find the ending parenthesis
local end_pos = string.find(spellname, ")", start_pos)
-- Extract the number substring
if end_pos then
local number_str = string.sub(spellname, start_pos, end_pos - 1)
--local number = tonumber(number_str) -- Convert to a number
return number_str
end
end
return nil
end
-- Thanks again ChatGPT
local tooltipResources = {"Mana", "Rage", "Energy"}
function ExtractResourceCost(costText)
-- First extract resource type
local resource
for _, r in ipairs(tooltipResources) do
if string.find(costText, r) then
resource = string.lower(r)
break
end
end
-- No resource found, this spell is probably free
if not resource then
return 0
end
-- Find the position where non-digit characters start
local num_end = string.find(costText, "%D")
-- If a non-digit character is found, extract the number
if num_end then
-- Extract the number substring from the start to the position before the non-digit character
local number_str = string.sub(costText, 1, num_end - 1)
-- Convert the substring to a number
local number = tonumber(number_str)
-- Print the result
return number, resource
else
-- If no non-digit character is found, the entire string is a number
local number = tonumber(costText)
return number, resource
end
end
function GetSpellID(spellname)
local id = 1;
local matchingSpells = {}
local spellRank = ExtractSpellRank(spellname)
if spellRank ~= nil then
spellname = string.gsub(spellname, "%b()", "")
end
for i = 1, GetNumSpellTabs() do
local _, _, _, numSpells = GetSpellTabInfo(i);
for j = 1, numSpells do
local spellName, rank, realID = GetSpellName(id, "spell");
if spellName == spellname then
if rank == spellRank then -- If the rank is specified, then we can check if this is the right spell
return id
else
table.insert(matchingSpells, id)
end
end
id = id + 1;
end
end
return matchingSpells[table.getn(matchingSpells)]
end
-- Returns the numerical cost and the resource name; "unknown" if the spell is unknown; 0 if the spell is free
function GetResourceCost(spellName)
ScanningTooltip:SetOwner(UIParent, "ANCHOR_NONE");
local spellID, bookType
if GetSpellSlotTypeIdForName then -- Nampower 2.6.0 function
spellID, bookType = GetSpellSlotTypeIdForName(spellName)
if bookType == "unknown" then
return "unknown"
end
if bookType ~= "spell" then
return 0
end
else
spellID = GetSpellID(spellName)
end
if not spellID then
return "unknown"
end
ScanningTooltip:SetSpell(spellID, "spell")
local leftText = getglobal("PTScanningTooltipTextLeft"..2)
if leftText:GetText() then
return ExtractResourceCost(leftText:GetText())
end
return 0
end
-- Returns the aura's name and its school type
function GetAuraInfo(unit, type, index)
-- Make these texts blank since they don't clear otherwise
local leftText = getglobal("PTScanningTooltipTextLeft1")
leftText:SetText("")
local rightText = getglobal("PTScanningTooltipTextRight1")
rightText:SetText("")
if type == "Buff" then
ScanningTooltip:SetUnitBuff(unit, index)
else
ScanningTooltip:SetUnitDebuff(unit, index)
end
return leftText:GetText() or "", rightText:GetText() or ""
end
function ApplySpellsTooltip(attachTo, unit, owner)
if not PTOptions.SpellsTooltip.Enabled then
return
end
local spellList = {}
local modifier = GetKeyModifier()
local settings = PuppeteerSettings
local spells = UnitCanAttack("player", unit) and GetHostileSpells() or GetSpells()
local deadFriend = util.IsDeadFriend(unit)
local selfClass = GetClass("player")
local canResurrect = PTOptions.AutoResurrect and deadFriend and ResurrectionSpells[selfClass]
-- Holy Champion Texture: Interface\\Icons\\Spell_Holy_ProclaimChampion_02
local canReviveChampion = canResurrect and GetSpellID("Revive Champion") and
PTUnit.Get(unit):HasBuffIDOrName(45568, "Holy Champion") and UnitAffectingCombat("player")
for _, btn in ipairs(settings.CustomButtonOrder) do
if canResurrect then -- Show all spells (except special binds) as the resurrection spell
local kv = {}
local readableButton = settings.CustomButtonNames[btn] or ReadableButtonMap[btn]
kv[readableButton] = canReviveChampion and "Revive Champion" or ResurrectionSpells[selfClass]
if SpecialBinds[string.upper(spells[modifier][btn] or "")] then
kv[readableButton] = spells[modifier][btn]
end
table.insert(spellList, kv)
else
if spells[modifier][btn] or (settings.ShowEmptySpells and not settings.IgnoredEmptySpells[btn]) then
local kv = {}
local readableButton = settings.CustomButtonNames[btn] or ReadableButtonMap[btn]
kv[readableButton] = spells[modifier][btn] or "Unbound"
table.insert(spellList, kv)
end
end
end
ShowSpellsTooltip(attachTo, spellList, owner)
end
function IsValidMacro(name)
return GetMacroIndexByName(name) ~= 0
end
function RunMacro(name, target)
if not IsValidMacro(name) then
return
end
if target then
_G.PT_MacroTarget = target
end
local _, _, body = GetMacroInfo(GetMacroIndexByName(name))
local commands = util.SplitString(body, "\n")
for i = 1, table.getn(commands) do
ChatFrameEditBox:SetText(commands[i])
ChatEdit_SendText(ChatFrameEditBox)
end
if target then
_G.PT_MacroTarget = nil
end
end
local ITEM_PREFIX = "Item: "
local MACRO_PREFIX = "Macro: "
local lowToHighColors = {
{1, 0, 0},
{1, 0.9, 0},
{0.35, 1, 0.35}
}
local tooltipPowerColors = {
["mana"] = {0.5, 0.7, 1}, -- Not the accurate color, but more readable
["rage"] = {1, 0, 0},
["energy"] = {1, 1, 0}
}
local tooltipAnchorMap = {["Top Left"] = "ANCHOR_LEFT", ["Top Right"] = "ANCHOR_RIGHT",
["Bottom Left"] = "ANCHOR_BOTTOMLEFT", ["Bottom Right"] = "ANCHOR_BOTTOMRIGHT"}
function ShowSpellsTooltip(attachTo, spells, owner)
SpellsTooltipOwner = owner
SpellsTooltip:SetOwner(attachTo, tooltipAnchorMap[PTOptions.SpellsTooltip.Anchor],
PTOptions.SpellsTooltip.OffsetX, PTOptions.SpellsTooltip.OffsetY)
local options = PTOptions.SpellsTooltip
local currentPower = UnitMana("player")
local maxPower = UnitManaMax("player")
local powerType = GetPowerType("player")
local powerColor = tooltipPowerColors[powerType]
local powerText = ""
local showPowerBar = options.ShowPowerBar
if options.ShowPowerAs == "Power" then
powerText = tostring(currentPower)
elseif options.ShowPowerAs == "Power/Max Power" then
powerText = currentPower.."/"..maxPower
elseif options.ShowPowerAs == "Power %" then
powerText = util.RoundNumber((currentPower / maxPower) * 100).."%"
end
if showPowerBar then
local color = util.InterpolateColors(lowToHighColors, (currentPower / maxPower))
powerText = colorize(powerText, color)
SpellsTooltipPowerBar:SetStatusBarColor(powerColor[1], powerColor[2], powerColor[3])
SpellsTooltipPowerBar:SetValue(currentPower / maxPower)
SpellsTooltipPowerBar.text:SetText(powerText)
else
powerText = colorize(powerText, powerColor)
end
local modifier = util.GetKeyModifierTypeByID(1 + (options.AbbreviatedKeys and 2 or 0) + (options.ColoredKeys and 1 or 0))
SpellsTooltip:AddDoubleLine(modifier, showPowerBar and " " or powerText, 1, 1, 1)
for _, kv in ipairs(spells) do
for button, spell in pairs(kv) do
local leftText = colorize(button, 1, 1, 0.5)
local rightText
if spell == "Unbound" then
leftText = colorize(button, 0.6, 0.6, 0.6)
rightText = colorize("Unbound", 0.6, 0.6, 0.6)
elseif util.StartsWith(spell, ITEM_PREFIX) then
local item = string.sub(spell, string.len(ITEM_PREFIX) + 1)
local itemCount = GetItemCount(item)
local castsColor = {0.6, 1, 0.6}
if itemCount == 0 then
castsColor = {1, 0.5, 0.5}
elseif itemCount == 1 then
castsColor = {1, 1, 0}
end
rightText = colorize(item, 1, 1, 1)..colorize(" ("..itemCount..")", castsColor)
elseif util.StartsWith(spell, MACRO_PREFIX) then
local macro = string.sub(spell, string.len(MACRO_PREFIX) + 1)
if IsValidMacro(macro) then
rightText = colorize(macro, 1, 0.6, 1)
else
rightText = colorize(macro.." (Invalid Macro)", 1, 0.4, 0.4)
end
elseif SpecialBinds[string.upper(spell)] then
rightText = spell
else -- There is a bound spell
local cost, resource = GetResourceCost(spell)
if cost == "unknown" then
leftText = colorize(button, 1, 0.4, 0.4)
rightText = colorize(spell.." (Unknown)", 1, 0.4, 0.4)
elseif cost == 0 then -- The spell is free, so no fancy text
rightText = spell
else
local resourceColor = tooltipPowerColors[resource]
local casts = math.floor(currentPower / cost)
if resource ~= powerType then -- A druid can't cast a spell that requires a different power type
casts = 0
end
local r, g, b = 0.6, 1, 0.6
if casts == 0 then
r, g, b = 1, 0.5, 0.5
elseif casts <= options.CriticalCastsLevel then
r, g, b = 1, 1, 0
end
local costText
if powerType == "mana" and resource == powerType then
if options.ShowManaCost then
costText = cost
end
if options.ShowManaPercentCost then
costText = (costText and (costText.." ") or "")..util.RoundNumber((cost / maxPower) * 100, 1).."%"
end
else
costText = cost
end
rightText = spell
if casts == 0 then
rightText = colorize(util.StripColors(rightText), 0.5, 0.5, 0.5)
end
if costText then
rightText = rightText.." "..colorize(costText, resourceColor)
end
if casts <= options.HideCastsAbove then
rightText = rightText..colorize(" ("..casts..")", r, g, b)
end
end
end
-- Gray out spells that are not held down
if CurrentlyHeldButton and button ~= CurrentlyHeldButton then
leftText = colorize(util.StripColors(leftText), 0.3, 0.3, 0.3)
rightText = colorize(util.StripColors(rightText), 0.3, 0.3, 0.3)
end
SpellsTooltip:AddDoubleLine(leftText, rightText)
end
end
--local leftTexts = {spellsTooltipTextLeft1, spellsTooltipTextLeft2, spellsTooltipTextLeft3,
-- spellsTooltipTextLeft4, spellsTooltipTextLeft5, spellsTooltipTextLeft6}
--spellsTooltipTextLeft1:SetFont("Fonts\\FRIZQT__.TTF", 12, "GameFontNormal")
--spellsTooltipTextRight1:SetFont("Fonts\\FRIZQT__.TTF", 12, "GameFontNormal")
SpellsTooltip:Show()
end
function HideSpellsTooltip()
SpellsTooltip:Hide()
SpellsTooltipOwner = nil
end
function ReapplySpellsTooltip()
if SpellsTooltipOwner ~= nil then
local prevOwner = SpellsTooltipOwner
HideSpellsTooltip()
prevOwner:GetScript("OnEnter")()
end
end
function UpdateAllIncomingHealing()
if PTHealPredict then
for _, ui in ipairs(AllUnitFrames) do
if PTOptions.UseHealPredictions then
local _, guid = UnitExists(ui:GetUnit())
ui:SetIncomingHealing(PTHealPredict.GetIncomingHealing(guid))
else
ui:SetIncomingHealing(0)
end
end
else
for _, ui in ipairs(AllUnitFrames) do
if PTOptions.UseHealPredictions then
ui:UpdateIncomingHealing()
else
ui:SetIncomingHealing(0)
end
end
end
end
function UpdateAllOutlines()
for _, ui in ipairs(AllUnitFrames) do
ui:UpdateOutline()
end
end
function CreateUnitFrameGroup(groupName, environment, units, petGroup, profile, sortByRole)
if UnitFrameGroups[groupName] then
error("[Puppeteer] Tried to create a unit frame group using existing name! \""..groupName.."\"")
return
end
local uiGroup = PTUnitFrameGroup:New(groupName, environment, units, petGroup, profile, sortByRole)
for _, unit in ipairs(units) do
local ui = PTUnitFrame:New(unit, AllCustomUnitsSet[unit] ~= nil)
if not PTUnitFrames[unit] then
PTUnitFrames[unit] = {}
end
table.insert(PTUnitFrames[unit], ui)
table.insert(AllUnitFrames, ui)
uiGroup:AddUI(ui)
if unit ~= "target" then
ui:Hide()
end
end
UnitFrameGroups[groupName] = uiGroup
return uiGroup
end
local function initUnitFrames()
local getSelectedProfile = PuppeteerSettings.GetSelectedProfile
CreateUnitFrameGroup("Party", "party", PartyUnits, false, getSelectedProfile("Party"))
CreateUnitFrameGroup("Pets", "party", PetUnits, true, getSelectedProfile("Pets"))
CreateUnitFrameGroup("Raid", "raid", RaidUnits, false, getSelectedProfile("Raid"))
CreateUnitFrameGroup("Raid Pets", "raid", RaidPetUnits, true, getSelectedProfile("Raid Pets"))
CreateUnitFrameGroup("Target", "all", TargetUnits, false, getSelectedProfile("Target"), false)
if util.IsSuperWowPresent() then
CreateUnitFrameGroup("Focus", "all", PTUnitProxy.CustomUnitsMap["focus"], false, getSelectedProfile("Focus"), false)
end
UnitFrameGroups["Target"].ShowCondition = function(self)
local friendly = not UnitCanAttack("player", "target")
return (PTOptions.AlwaysShowTargetFrame or (UnitExists("target") and
(friendly and PTOptions.ShowTargets.Friendly) or (not friendly and PTOptions.ShowTargets.Hostile)))
and not PTOptions.Hidden
end
OpenUnitFramesIterator()
end
function EventAddonLoaded()
local freshInstall = false
if PTSpells == nil then
freshInstall = true
local PTSpells = {}
PTSpells["Friendly"] = {}
PTSpells["Hostile"] = {}
setglobal("PTSpells", PTSpells)
end
for _, spells in pairs(PTSpells) do
for _, modifier in ipairs(util.GetKeyModifiers()) do
if not spells[modifier] then
spells[modifier] = {}
end
end
end
if util.IsSuperWowPresent() then
-- In case other addons override unit functions, we want to make sure we're using their functions
PTUnitProxy.CreateUnitProxies()
-- Do it again after all addons have loaded
local frame = CreateFrame("Frame")
local reapply = GetTime() + 0.1
frame:SetScript("OnUpdate", function()
if GetTime() > reapply then
PTUnitProxy.CreateUnitProxies()
frame:SetScript("OnUpdate", nil)
end
end)
end
if not _G.PTRoleCache then
_G.PTRoleCache = {}
end
if not _G.PTRoleCache[GetRealmName()] then
_G.PTRoleCache[GetRealmName()] = {}
end
AssignedRoles = _G.PTRoleCache[GetRealmName()]
PruneAssignedRoles()
if util.IsSuperWowPresent() then
PTUnit.UpdateGuidCaches()
local customUnitUpdater = CreateFrame("Frame", "PTCustomUnitUpdater")
local nextUpdate = GetTime() + 0.25
-- Older versions of SuperWoW had an issue where units that aren't part of normal units wouldn't receive events,
-- so updates are done manually
local needsManualUpdates = util.SuperWoWFeatureLevel < util.SuperWoW_v1_4
customUnitUpdater:SetScript("OnUpdate", function()
if GetTime() > nextUpdate then
nextUpdate = GetTime() + 0.25
for unit, guid in pairs(CustomUnitGUIDMap) do
if needsManualUpdates or not UnitExists(guid) then
PTUnit.Get(unit):UpdateAuras()
for ui in UnitFrames(unit) do
ui:UpdateHealth()
ui:UpdatePower()
ui:UpdateAuras()
ui:UpdateIncomingHealing()
end
end
end
end
end)
else
PTUnit.CreateCaches()
end
PuppeteerSettings.UpdateTrackedDebuffTypes()
PTProfileManager.InitializeDefaultProfiles()
PuppeteerSettings.SetDefaults()
do
if PTOptions.Scripts.OnLoad then
local scriptString = "local GetProfile = PTProfileManager.GetProfile "..
"local CreateProfile = PTProfileManager.CreateProfile "..PTOptions.Scripts.OnLoad
local script = loadstring(scriptString)
local ok, result = pcall(script)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage(colorize("[Puppeteer] ", 1, 0.4, 0.4)..colorize("ERROR: ", 1, 0.2, 0.2)
..colorize("The Load Script produced an error! If this causes Puppeteer to fail to load, "..
"you will need to manually edit the script in your game files.", 1, 0.4, 0.4))
DEFAULT_CHAT_FRAME:AddMessage(colorize("OnLoad Script Error: "..tostring(result), 1, 0, 0))
end
end
end
PuppeteerSettings.InitSettings()
if PTHealPredict then
PTHealPredict.OnLoad()
PTHealPredict.HookUpdates(function(guid, incomingHealing, incomingDirectHealing)
if not PTOptions.UseHealPredictions then
return
end
local units = GuidRoster.GetUnits(guid)
if not units then
return
end
for _, unit in ipairs(units) do
for ui in UnitFrames(unit) do
ui:SetIncomingHealing(incomingHealing, incomingDirectHealing)
end
end
end)
else
local roster = AceLibrary("RosterLib-2.0")
PuppeteerLib:RegisterEvent("HealComm_Healupdate", function(name)
if not PTOptions.UseHealPredictions then
return
end
local unit = roster:GetUnitIDFromName(name)
if unit then
for ui in UnitFrames(unit) do
ui:UpdateIncomingHealing()
end
end
if UnitName("target") == name then
for ui in UnitFrames("target") do
ui:UpdateIncomingHealing()
end
end
end)
PuppeteerLib:RegisterEvent("HealComm_Ressupdate", function(name)
local unit = roster:GetUnitIDFromName(name)
if unit then
for ui in UnitFrames(unit) do
ui:UpdateHealth()
end
end
if UnitName("target") == name then
for ui in UnitFrames("target") do
ui:UpdateHealth()
end
end
end)
end
SetLFTAutoRoleEnabled(PTOptions.LFTAutoRole)
TestUI = PTOptions.TestUI
if TestUI then
DEFAULT_CHAT_FRAME:AddMessage(colorize("[Puppeteer] UI Testing is enabled. Use /pt testui to disable.", 1, 0.6, 0.6))
end
initUnitFrames()
StartDistanceScanner()
PuppeteerLib:RegisterEvent("Banzai_UnitGainedAggro", function(unit)
if PTGuidRoster then
unit = PTGuidRoster.GetUnitGuid(unit)
end
for ui in UnitFrames(unit) do
ui:UpdateOutline()
end
end)
PuppeteerLib:RegisterEvent("Banzai_UnitLostAggro", function(unit)
if PTGuidRoster then
unit = PTGuidRoster.GetUnitGuid(unit)
end
for ui in UnitFrames(unit) do
ui:UpdateOutline()
end
end)
if PTOnLoadInfoDisabled == nil then
PTOnLoadInfoDisabled = false
end
do
local INFO_SEND_TIME = GetTime() + 0.5
local infoFrame = CreateFrame("Frame")
infoFrame:SetScript("OnUpdate", function()
if GetTime() < INFO_SEND_TIME then
return
end
infoFrame:SetScript("OnUpdate", nil)
if not PTOnLoadInfoDisabled then
DEFAULT_CHAT_FRAME:AddMessage(colorize("[Puppeteer] Use ", 0.5, 1, 0.5)..colorize("/pt help", 0, 1, 0)
..colorize(" to see commands.", 0.5, 1, 0.5))
end
if not util.IsSuperWowPresent() and util.IsNampowerPresent() then
DEFAULT_CHAT_FRAME:AddMessage(colorize("[Puppeteer] ", 1, 0.4, 0.4)..colorize("WARNING: ", 1, 0.2, 0.2)
..colorize("You are using Nampower without SuperWoW, which will cause heal predictions to be wildly inaccurate "..
"for you and your raid members! It is highly recommended to install SuperWoW.", 1, 0.4, 0.4))
end
if util.IsSuperWowPresent() and not HealComm:IsEventRegistered("UNIT_CASTEVENT") then
DEFAULT_CHAT_FRAME:AddMessage(colorize("[Puppeteer] ", 1, 0.4, 0.4)..colorize("WARNING: ", 1, 0.2, 0.2)
..colorize("You have another addon that uses a HealComm version that is incompatible with SuperWoW! "..
"This will cause wildly inaccurate heal predictions to be shown to your raid members. It is "..
"recommended to either unload the offending addon or copy Puppeteer's HealComm "..
"into the other addon.", 1, 0.4, 0.4))
end
end)
end
-- Create default bindings for new characters
if freshInstall then
local class = GetClass("player")
local spells = GetSpells()
local hostileSpells = GetHostileSpells()
if class == "PRIEST" then
spells["None"]["LeftButton"] = "Power Word: Shield"
spells["None"]["MiddleButton"] = "Renew"
spells["None"]["RightButton"] = "Lesser Heal"
spells["Shift"]["LeftButton"] = "Target"
spells["Shift"]["RightButton"] = "Context"
spells["Control"]["RightButton"] = "Dispel Magic"
hostileSpells["None"]["RightButton"] = "Dispel Magic"
elseif class == "DRUID" then
spells["None"]["LeftButton"] = "Rejuvenation"
spells["None"]["RightButton"] = "Healing Touch"
spells["Shift"]["LeftButton"] = "Target"
spells["Shift"]["MiddleButton"] = "Role"
spells["Shift"]["RightButton"] = "Context"
spells["Control"]["RightButton"] = "Remove Curse"
elseif class == "PALADIN" then
spells["None"]["LeftButton"] = "Flash of Light"
spells["None"]["RightButton"] = "Holy Light"
spells["Shift"]["LeftButton"] = "Target"
spells["Shift"]["MiddleButton"] = "Role"
spells["Shift"]["RightButton"] = "Context"
spells["Control"]["RightButton"] = "Cleanse"
elseif class == "SHAMAN" then
spells["None"]["LeftButton"] = "Healing Wave"
spells["None"]["RightButton"] = "Lesser Healing Wave"
spells["Shift"]["LeftButton"] = "Target"
spells["Shift"]["MiddleButton"] = "Role"
spells["Shift"]["RightButton"] = "Context"
spells["Control"]["RightButton"] = "Cure Disease"
else
-- Non-healer classes can use this addon like traditional raid frames
spells["None"]["LeftButton"] = "Target"
spells["None"]["MiddleButton"] = "Role"
spells["None"]["RightButton"] = "Context"
end
hostileSpells["None"]["LeftButton"] = "Target"
end
do
if PTOptions.Scripts.OnPostLoad then
local scriptString = PTOptions.Scripts.OnPostLoad
local script = loadstring(scriptString)
local ok, result = pcall(script)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage(colorize("[Puppeteer] ", 1, 0.4, 0.4)..colorize("ERROR: ", 1, 0.2, 0.2)
..colorize("The Postload Script produced an error! If this causes Puppeteer to fail to operate, "..
"you may need to manually edit the script in your game files.", 1, 0.4, 0.4))
DEFAULT_CHAT_FRAME:AddMessage(colorize("OnPostLoad Script Error: "..tostring(result), 1, 0, 0))
end
end
end
end
function CheckPartyFramesEnabled()
local shouldBeDisabled = (CurrentlyInRaid and PTOptions.DisablePartyFrames.InRaid) or
(not CurrentlyInRaid and PTOptions.DisablePartyFrames.InParty)
SetPartyFramesEnabled(not shouldBeDisabled)
end
function SetPartyFramesEnabled(enabled)
if enabled then
for i = 1, MAX_PARTY_MEMBERS do
local frame = getglobal("PartyMemberFrame"..i)
if frame and frame.PTRealShow then
frame.Show = frame.PTRealShow
frame.PTRealShow = nil
if UnitExists("party"..i) then
frame:Show()
end
local prevThis = _G.this
_G.this = frame
PartyMemberFrame_OnLoad()
_G.this = prevThis
end
end
else
for i = 1, MAX_PARTY_MEMBERS do
local frame = getglobal("PartyMemberFrame"..i)
if frame and not frame.PTRealShow then
frame:UnregisterAllEvents()
frame.PTRealShow = frame.Show
frame.Show = function() end
frame:Hide()
end
end
end
end
LFTAutoRoleFrame = CreateFrame("Frame", "PT_LFTAutoRoleFrame")
function SetLFTAutoRoleEnabled(enabled)
if not LFT_ADDON_PREFIX then -- Must not be Turtle WoW, or LFT has changed
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 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, LFT_ADDON_FIELD_DELIMITER)
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
local roleTarget
local roleTargetClassColor
local roleTargetGroup
local function setUnassignedRoles(role)
if not roleTargetGroup then
return
end
for _, ui in pairs(roleTargetGroup.uis) do
if not ui:GetRole() and UnitIsPlayer(ui:GetUnit()) then
SetAssignedRole(UnitName(ui:GetUnit()), role)
end
end
UpdateUnitFrameGroups()
ToggleDropDownMenu(1, nil, _G["PTRoleDropdown"])
end
local function applyTargetRole(role)
SetAssignedRole(roleTarget, role)
UpdateUnitFrameGroups()
end
do
local roleDropdown = CreateFrame("Frame", "PTRoleDropdown", UIParent, "UIDropDownMenuTemplate")
local options = {
{
["text"] = "",
["arg1"] = "Assign Role",
["notCheckable"] = true,
["disabled"] = true
}, {
["text"] = GetColoredRoleText("Tank"),
["arg1"] = "Tank",
["func"] = applyTargetRole
}, {
["text"] = GetColoredRoleText("Healer"),
["arg1"] = "Healer",
["func"] = applyTargetRole
}, {
["text"] = GetColoredRoleText("Damage"),
["arg1"] = "Damage",
["func"] = applyTargetRole
}, {
["text"] = GetColoredRoleText("No Role"),
["arg1"] = "No Role",
["func"] = applyTargetRole
}, {
["text"] = "",
["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,
["hasArrow"] = true,
["suboptions"] = {
{
["text"] = GetColoredRoleText("Tank"),
["arg1"] = "Tank",
["notCheckable"] = true,
["func"] = setUnassignedRoles
}, {
["text"] = GetColoredRoleText("Healer"),
["arg1"] = "Healer",
["notCheckable"] = true,
["func"] = setUnassignedRoles
}, {
["text"] = GetColoredRoleText("Damage"),
["arg1"] = "Damage",
["notCheckable"] = true,
["func"] = setUnassignedRoles
}
}
}, {
["text"] = "Clear Roles",
["arg1"] = "Clear Roles",
["tooltipTitle"] = "Clear Roles",
["tooltipText"] = "Clear all players' roles. Only applies to players contained in this UI group.",
["notCheckable"] = true,
["func"] = function()
if not roleTargetGroup then
return
end
for _, ui in pairs(roleTargetGroup.uis) do
if ui:GetRole() and UnitIsPlayer(ui:GetUnit()) then
SetAssignedRole(UnitName(ui:GetUnit()), nil)
end
end
UpdateUnitFrameGroups()
ToggleDropDownMenu(1, nil, _G["PTRoleDropdown"])
end
}
}
UIDropDownMenu_Initialize(roleDropdown, function(level)
level = level or 1
if level == 1 then
for _, option in ipairs(options) do
option.checked = (GetAssignedRole(roleTarget) or "No Role") == option.arg1
if option.arg1 == "Assign Role" and roleTarget then
option.text = colorize("Assign Role: ", 1, 0.5, 1)..colorize(roleTarget, roleTargetClassColor)
end
UIDropDownMenu_AddButton(option)
end
elseif level == 2 then
local suboptions
for _, option in ipairs(options) do
if option.text == UIDROPDOWNMENU_MENU_VALUE then
suboptions = option.suboptions
end
end
for _, option in ipairs(suboptions) do
UIDropDownMenu_AddButton(option, level)
end
end
end, "MENU")
end
local function setUnitRoleAndUpdate(unit, role)
if not SetUnitAssignedRole(unit, role) then
UpdateUnitFrameGroups()
end
end
SpecialBinds = {
["target"] = function(unit)
TargetUnit(unit)
end,
["assist"] = function(unit)
AssistUnit(unit)
end,
["follow"] = function(unit)
FollowUnit(unit)
end,
["context"] = function(unit, ui)
-- Resolve focus to a proper unit if possible
if AllCustomUnitsSet[unit] then
unit = PTUnitProxy.ResolveCustomUnit(unit)
if not unit then
return
end
end
local dropdown
local specialContexts = {
["player"] = _G["PlayerFrameDropDown"],
["target"] = _G["TargetFrameDropDown"],
["pet"] = _G["PetFrameDropDown"]
}
if specialContexts[unit] then
dropdown = specialContexts[unit]
elseif util.StartsWith(unit, "raid") and not util.StartsWith(unit, "raidpet") then
FriendsDropDown.displayMode = "MENU"
FriendsDropDown.initialize = function()
UnitPopup_ShowMenu(_G[UIDROPDOWNMENU_OPEN_MENU], "PARTY", unit, nil, string.sub(unit, 5))
end
dropdown = FriendsDropDown
elseif util.StartsWith(unit, "party") and not util.StartsWith(unit, "partypet") then
dropdown = _G["PartyMemberFrame"..string.sub(unit, 6).."DropDown"]
end
if dropdown then
local frame = ui:GetRootContainer()
ToggleDropDownMenu(1, nil, dropdown, frame:GetName(), frame:GetWidth(), 0)
end
end,
["Role: Tank"] = function(unit)
setUnitRoleAndUpdate(unit, "Tank")
end,
["Role: Healer"] = function(unit)
setUnitRoleAndUpdate(unit, "Healer")
end,
["Role: Damage"] = function(unit)
setUnitRoleAndUpdate(unit, "Damage")
end,
["Role: None"] = function(unit)
setUnitRoleAndUpdate(unit, nil)
end,
["Role"] = function(unit, ui)
if not UnitIsPlayer(unit) then
return
end
roleTarget = UnitName(unit)
roleTargetClassColor = util.GetClassColor(util.GetClass(unit), true)
roleTargetGroup = ui.owningGroup
local frame = ui:GetRootContainer()
local dropdown = _G["PTRoleDropdown"]
if dropdown:IsShown() then
ToggleDropDownMenu(1, nil, dropdown)
end
ToggleDropDownMenu(1, nil, dropdown, frame:GetName(), frame:GetWidth(), frame:GetHeight())
PlaySound("igMainMenuOpen")
end,
["Focus"] = function(unit)
if not util.IsSuperWowPresent() then
DEFAULT_CHAT_FRAME:AddMessage(colorize("You need SuperWoW to focus targets.", 1, 0.5, 0.5))
return
end
PT_ToggleFocusUnit(unit)
end,
["Promote Focus"] = function(unit)
if not util.IsSuperWowPresent() then
DEFAULT_CHAT_FRAME:AddMessage(colorize("You need SuperWoW to focus targets.", 1, 0.5, 0.5))
return
end
PT_PromoteFocus(unit)
end,
["Demote Focus"] = function(unit)
if not util.IsSuperWowPresent() then
DEFAULT_CHAT_FRAME:AddMessage(colorize("You need SuperWoW to focus targets.", 1, 0.5, 0.5))
return
end
PT_UnfocusUnit(unit)
PT_FocusUnit(unit)
end
}
-- Create aliases for special binds
SpecialBinds["Set Role"] = SpecialBinds["Role"]
-- Make all the special binds upper case
do
local upperSpecialBinds = {}
for name, func in pairs(SpecialBinds) do
upperSpecialBinds[string.upper(name)] = func
end
SpecialBinds = upperSpecialBinds
end
function _G.PT_ToggleFocusUnit(unit)
if PTUnitProxy.IsUnitUnitType(unit, "focus") then
if not PTUnitProxy.CustomUnitsSetMap["focus"][unit] then
return -- Do not toggle focus if user is clicking on a UI that isn't the focus UI
end
PT_UnfocusUnit(unit)
else
PT_FocusUnit(unit)
end
end
function _G.PT_FocusUnit(unit)
local guid = PTGuidRoster.ResolveUnitGuid(unit)
if not guid or PTUnitProxy.IsGuidUnitType(guid, "focus") then
return
end
PTUnitProxy.SetGuidUnitType(guid, "focus")
PlaySound("GAMETARGETHOSTILEUNIT")
end
function _G.PT_UnfocusUnit(unit)
local guid = PTGuidRoster.ResolveUnitGuid(unit)
if not guid then
return
end
local focusUnit = PTUnitProxy.GetCurrentUnitOfType(guid, "focus")
if not focusUnit then
return
end
PTUnitProxy.SetCustomUnitGuid(focusUnit, nil)
PlaySound("INTERFACESOUND_LOSTTARGETUNIT")
end
function _G.PT_PromoteFocus(unit)
local guid = PTGuidRoster.ResolveUnitGuid(unit)
if not guid then
return
end
PTUnitProxy.PromoteGuidUnitType(guid, "focus")
end
function CycleFocus(onlyAttackable)
PTUnitProxy.CycleUnitType("focus", onlyAttackable)
end
local Sound_Disabled = function() end
function ClickHandler(buttonType, unit, ui)
local currentTargetEnemy = UnitCanAttack("player", "target")
local spells = UnitCanAttack("player", unit) and GetHostileSpells() or GetSpells()
local spell = spells[GetKeyModifier()][buttonType]
if not UnitIsConnected(unit) or not UnitIsVisible(unit) then
if spell and SpecialBinds[string.upper(spell)] then
SpecialBinds[string.upper(spell)](unit, ui)
end
return
end
if PTOptions.AutoResurrect and util.IsDeadFriend(unit) then
if spell and SpecialBinds[string.upper(spell)] then
SpecialBinds[string.upper(spell)](unit, ui)
return
end
if PTUnit.Get(unit):HasBuffIDOrName(45568, "Holy Champion") and GetSpellID("Revive Champion")
and UnitAffectingCombat("player") then
spell = "Revive Champion"
else
spell = ResurrectionSpells[GetClass("player")] or spell
end
end
if spell == nil then
return
end
if SpecialBinds[string.upper(spell)] then
SpecialBinds[string.upper(spell)](unit, ui)
return
end
local isItem = util.StartsWith(spell, ITEM_PREFIX)
local isMacro = util.StartsWith(spell, MACRO_PREFIX)
local isNonSpell = isItem or isMacro
-- Not a special bind
if util.IsSuperWowPresent() and not isNonSpell then -- No target changing shenanigans required with SuperWoW
if PTOptions.AutoTarget and not UnitIsUnit("target", unit) then
TargetUnit(unit)
end
CastSpellByName(spell, unit)
else
local currentTarget = UnitName("target")
local targetChanged = false
-- Check if target is not already targeted
if not UnitIsUnit("target", unit) then
-- Set target as target
local Sound_Enabled = PlaySound
_G.PlaySound = Sound_Disabled
TargetUnit(unit)
_G.PlaySound = Sound_Enabled
targetChanged = true
end
if isItem then
UseItem(string.sub(spell, string.len(ITEM_PREFIX) + 1))
elseif isMacro then
RunMacro(string.sub(spell, string.len(MACRO_PREFIX) + 1), unit)
else
CastSpellByName(spell)
end
--Put Target of player back to whatever it was before casting spell
if targetChanged and not PTOptions.AutoTarget then
if currentTarget == nil then
--Player wasn't targeting anything before casting spell
local Sound_Enabled = PlaySound
_G.PlaySound = Sound_Disabled
ClearTarget()
_G.PlaySound = Sound_Enabled
else
--Set Target back to whatever it was before casting the spell
if currentTargetEnemy then
TargetLastEnemy() -- to make sure if there was more than one mob with that name near you the same one get retargeted
else
TargetLastTarget()
--TargetByName(currentTarget)
end
end
end
end
end
-- Reevaluates what UI frames should be shown
function CheckGroup()
if GetNumRaidMembers() > 0 then
if not CurrentlyInRaid then
CurrentlyInRaid = true
SetPartyFramesEnabled(not PTOptions.DisablePartyFrames.InRaid)
end
else
if CurrentlyInRaid then
CurrentlyInRaid = false
SetPartyFramesEnabled(not PTOptions.DisablePartyFrames.InParty)
end
end
local superwow = util.IsSuperWowPresent()
if superwow then
GuidRoster.ResetRoster()
GuidRoster.PopulateRoster()
PTUnit.UpdateGuidCaches()
end
for _, unit in ipairs(util.AllRealUnits) do
local exists, guid = UnitExists(unit)
if unit ~= "target" then
if exists then
for ui in UnitFrames(unit) do
ui:Show()
end
else
for ui in UnitFrames(unit) do
ui:Hide()
end
end
end
end
for _, group in pairs(UnitFrameGroups) do
group:EvaluateShown()
end
if not superwow then -- If SuperWoW isn't present, the units may have shifted and thus require a full scan
PTUnit.UpdateAllUnits()
end
for _, ui in pairs(AllUnitFrames) do
if ui:IsShown() then
ui:UpdateRange()
ui:UpdateAuras()
ui:UpdateIncomingHealing()
ui:UpdateOutline()
end
end
if superwow then
PTHealPredict.SetRelevantGUIDs(GuidRoster.GetTrackedGuids())
end
RunTrackingScan()
end
function CheckTarget()
local exists, guid = UnitExists("target")
if exists then
local friendly = not UnitCanAttack("player", "target")
if (friendly and PTOptions.ShowTargets.Friendly) or (not friendly and PTOptions.ShowTargets.Hostile) then
for ui in UnitFrames("target") do
ui.lastHealthPercent = (ui:GetCurrentHealth() / ui:GetMaxHealth()) * 100
ui:UpdateRange()
ui:UpdateSight()
ui:UpdateRole()
ui:UpdateIncomingHealing()
end
end
else
for ui in UnitFrames("target") do
ui.lastHealthPercent = (ui:GetCurrentHealth() / ui:GetMaxHealth()) * 100
ui:UpdateAll()
ui:UpdateRole()
ui:UpdateIncomingHealing()
end
end
UnitFrameGroups["Target"]:EvaluateShown()
end
function IsRelevantUnit(unit)
--return not string.find(unit, "0x")
return AllUnitsSet[unit] ~= nil or GUIDCustomUnitMap[unit]
end
function EventHandler()
if event == "ADDON_LOADED" then
if arg1 ~= "Puppeteer" then
return
end
EventAddonLoaded()
EventHandlerFrame:UnregisterEvent("ADDON_LOADED")
elseif event == "PLAYER_ENTERING_WORLD" then
CheckGroup()
if PTOptions.DisablePartyFrames.InParty then
SetPartyFramesEnabled(false)
end
elseif event == "PLAYER_LOGOUT" or event == "PLAYER_QUITING" then
elseif event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
local unit = arg1
if not IsRelevantUnit(unit) then
return
end
for ui in UnitFrames(unit) do
ui:UpdateHealth()
end
elseif event == "UNIT_MANA" or event == "UNIT_RAGE" or event == "UNIT_ENERGY" or
event == "UNIT_FOCUS" or event == "UNIT_MAXMANA" or event == "UNIT_DISPLAYPOWER" then
local unit = arg1
if not IsRelevantUnit(unit) then
return
end
for ui in UnitFrames(unit) do
ui:UpdatePower()
end
if unit == "player" then
ReapplySpellsTooltip()
end
elseif event == "UNIT_AURA" then
local unit = arg1
if not IsRelevantUnit(unit) then
return
end
PTUnit.Get(unit):UpdateAuras()
for ui in UnitFrames(unit) do
ui:UpdateAuras()
ui:UpdateHealth() -- Update health because there may be an aura that changes health bar color
end
elseif event == "PARTY_MEMBERS_CHANGED" or event == "RAID_ROSTER_UPDATE" then
CheckGroup()
elseif event == "UNIT_PET" or event == "PLAYER_PET_CHANGED" then
local unit = arg1
if IsRelevantUnit(unit) then
CheckGroup()
end
elseif event == "PLAYER_TARGET_CHANGED" then
for _, ui in ipairs(AllUnitFrames) do
ui:EvaluateTarget()
end
local exists, guid = UnitExists("target")
if guid then
PTUnit.UpdateGuidCaches()
end
PTUnit.Get("target"):UpdateAll()
if util.IsSuperWowPresent() then
GuidRoster.SetUnitGuid("target", guid)
PTHealPredict.SetRelevantGUIDs(GuidRoster.GetTrackedGuids())
end
if exists then
EvaluateTracking("target", true)
end
if PTOptions.Hidden then
return
end
CheckTarget()
elseif event == "SPELLS_CHANGED" then
PuppeteerSettings.UpdateTrackedDebuffTypes()
elseif event == "RAID_TARGET_UPDATE" then
for _, ui in ipairs(AllUnitFrames) do
ui:UpdateRaidMark()
end
end
end
EventHandlerFrame:SetScript("OnEvent", EventHandler)
function print(msg)
if not PTOptions or not PTOptions["Debug"] then
return
end
local window
local i = 1
while not window do
local name = GetChatWindowInfo(i)
if not name then
break
end
if name == "Debug" then
window = getglobal("ChatFrame"..i)
break
end
i = i + 1
end
if window then
window:AddMessage(tostring(msg))
end
end