68701d7d42
release / release (push) Successful in 3s
Add CoAClassSpecData.lua (copied from coa-db/data) and wire GetPlayerCoARole() into TankMode so CoA tokens resolve TANK/HEALER/DAMAGER from their active spec instead of defaulting to DAMAGER. - CoAClassSpecData.lua: defines CoAClassSpec keyed by UnitClass token, loaded in .toc before Core.xml/Modules.xml - GetPlayerCoARole(): pcall-guarded helper that calls SpecializationUtil.GetActiveSpecialization() (1-based index) to look up the current spec in CoAClassSpec[token].specs; TANK > HEALER > DAMAGER precedence when a spec carries multiple roles; returns nil for vanilla classes so the existing IsTank/IsHealer path is unchanged - mod:Update(): try GetPlayerCoARole() first; fall through to vanilla logic only when nil (vanilla class or unresolvable spec) - Re-evaluation on spec change already covered: PLAYER_TALENT_UPDATE fires on both spec and talent changes, driving mod:Update() luac -p: TankMode.lua OK, CoAClassSpecData.lua OK
428 lines
12 KiB
Lua
428 lines
12 KiB
Lua
--[[
|
|
-- Kui_Nameplates
|
|
-- By Kesava at curse.com
|
|
-- All rights reserved
|
|
-- Backported by: Kader at https://github.com/bkader
|
|
]]
|
|
local addon = LibStub("AceAddon-3.0"):GetAddon("KuiNameplates")
|
|
local mod = addon:NewModule("TankMode", addon.Prototype, "AceEvent-3.0")
|
|
local L = LibStub("AceLocale-3.0"):GetLocale("KuiNameplates")
|
|
|
|
-- `class` is resolved in mod:OnEnable() rather than at file scope: on the CoA
|
|
-- client this file can be parsed before PLAYER_LOGIN, when UnitClass("player")
|
|
-- returns nil and would otherwise leave `class` permanently nil.
|
|
local class, tankmode = nil, nil
|
|
|
|
local profile_tankmode
|
|
|
|
mod.uiName = L["Threat"]
|
|
|
|
-------------------------------------------------------- threat bracket stuff --
|
|
local function ShowThreatBrackets(frame, ...)
|
|
if not frame.threatBrackets then
|
|
return
|
|
end
|
|
if ... == false then
|
|
frame.threatBrackets:Hide()
|
|
else
|
|
frame.threatBrackets:SetVertexColor(...)
|
|
frame.threatBrackets:Show()
|
|
end
|
|
end
|
|
do
|
|
local brackets = {
|
|
{"BOTTOMLEFT", nil, "TOPLEFT"},
|
|
{"BOTTOMRIGHT", nil, "TOPRIGHT"},
|
|
{"TOPLEFT", nil, "BOTTOMLEFT"},
|
|
{"TOPRIGHT", nil, "BOTTOMRIGHT"}
|
|
}
|
|
|
|
-- pixel positions
|
|
local leftmost = 0.28125
|
|
local bottommost = 0
|
|
local default_size = 18
|
|
local ratio = 2
|
|
|
|
local size, x_offset, y_offset
|
|
|
|
function mod:UpdateThreatBracketScaling()
|
|
size = default_size * self.db.profile.brackets.scale
|
|
x_offset = (size * ratio) * leftmost
|
|
y_offset = floor((size * bottommost) - 2)
|
|
end
|
|
|
|
function mod:CreateThreatBrackets(frame)
|
|
local tb = CreateFrame("Frame", nil, frame.health)
|
|
tb:SetFrameLevel(1) -- same as castbar/healthbar
|
|
tb:Hide()
|
|
|
|
for k, v in ipairs(brackets) do
|
|
local b = tb:CreateTexture(nil, "ARTWORK", nil, -1)
|
|
b:SetTexture("Interface\\AddOns\\Kui_Nameplates\\Media\\threat-bracket")
|
|
tb[k] = b
|
|
end
|
|
|
|
tb.SetVertexColor = function(self, ...)
|
|
for k, b in ipairs(self) do
|
|
b:SetVertexColor(...)
|
|
end
|
|
end
|
|
|
|
frame.threatBrackets = tb
|
|
|
|
self:UpdateThreatBrackets(frame)
|
|
end
|
|
function mod:UpdateThreatBrackets(frame)
|
|
-- apply scaling + positions to threat brackets on given frame
|
|
if not frame.threatBrackets then
|
|
return
|
|
end
|
|
for k, v in ipairs(brackets) do
|
|
local b = frame.threatBrackets[k]
|
|
b:SetSize(size * ratio, size)
|
|
|
|
if k % 2 == 0 then
|
|
v[4] = x_offset - 1
|
|
else
|
|
v[4] = -x_offset
|
|
end
|
|
|
|
if k <= 2 then
|
|
v[5] = -y_offset
|
|
else
|
|
v[5] = y_offset - .5
|
|
end
|
|
|
|
if k == 2 then
|
|
b:SetTexCoord(1, 0, 0, 1)
|
|
elseif k == 3 then
|
|
b:SetTexCoord(0, 1, 1, 0)
|
|
elseif k == 4 then
|
|
b:SetTexCoord(1, 0, 1, 0)
|
|
end
|
|
|
|
v[2] = frame.health
|
|
b:SetPoint(unpack(v))
|
|
end
|
|
end
|
|
end
|
|
--------------------------------------------------------- CoA role detection --
|
|
-- Returns "TANK", "HEALER", or "DAMAGER" for the player's current spec when
|
|
-- the player is a CoA custom class. Returns nil if the class is not in
|
|
-- CoAClassSpec (vanilla path) or if the active spec cannot be resolved (caller
|
|
-- falls back to existing logic).
|
|
--
|
|
-- API rationale: SpecializationUtil.GetActiveSpecialization() is the
|
|
-- confirmed per-spec API on the CoA/Ascension 3.3.5a client. It returns
|
|
-- a 1-based integer matching the in-game tab order, which is the same order
|
|
-- as CoAClassSpec[token].specs. This is the same API used by coa-clique
|
|
-- (Clique.lua:700) and coa-weakauras (WeakAuras.lua:1608) on this client.
|
|
-- We guard with pcall so a missing or broken API degrades silently to nil.
|
|
local function GetPlayerCoARole()
|
|
if not CoAClassSpec then return nil end
|
|
|
|
local token = select(2, UnitClass("player"))
|
|
if not token then return nil end
|
|
|
|
local classData = CoAClassSpec[token]
|
|
if not classData then return nil end -- vanilla class; use vanilla path
|
|
|
|
-- Resolve the active spec index via SpecializationUtil.
|
|
local specIndex
|
|
local ok, result = pcall(function()
|
|
if SpecializationUtil and SpecializationUtil.GetActiveSpecialization then
|
|
return SpecializationUtil.GetActiveSpecialization()
|
|
end
|
|
return nil
|
|
end)
|
|
if ok and result and result >= 1 then
|
|
specIndex = result
|
|
end
|
|
|
|
local specData
|
|
if specIndex and classData.specs[specIndex] then
|
|
specData = classData.specs[specIndex]
|
|
end
|
|
|
|
if not specData then return nil end
|
|
|
|
-- Map CoAClassSpec roles → tank-mode role.
|
|
-- TANK and HEALER take precedence over DAMAGER roles when both present.
|
|
local roles = specData.roles
|
|
if not roles then return nil end
|
|
|
|
local isTank, isHealer = false, false
|
|
for _, r in ipairs(roles) do
|
|
if r == "TANK" then isTank = true end
|
|
if r == "HEALER" then isHealer = true end
|
|
end
|
|
|
|
if isTank then return "TANK" end
|
|
if isHealer then return "HEALER" end
|
|
return "DAMAGER"
|
|
end
|
|
|
|
--------------------------------------------------------- tank mode functions --
|
|
do
|
|
local function getTalentpointsSpent(spellID)
|
|
local spellName = GetSpellInfo(spellID)
|
|
for tabIndex = 1, GetNumTalentTabs() do
|
|
for talentID = 1, GetNumTalents(tabIndex) do
|
|
local name, _, _, _, spent = GetTalentInfo(tabIndex, talentID)
|
|
if (name == spellName) then
|
|
return spent
|
|
end
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
|
|
local function IsDeathKnightTank()
|
|
-- idea taken from addon 'ElitistJerks'
|
|
local tankTalents =
|
|
(getTalentpointsSpent(16271) >= 5 and 1 or 0) + -- Anticipation
|
|
(getTalentpointsSpent(49042) >= 5 and 1 or 0) + -- Toughness
|
|
(getTalentpointsSpent(55225) >= 5 and 1 or 0) -- Blade Barrier
|
|
return tankTalents >= 2
|
|
end
|
|
|
|
local function IsDruidTank()
|
|
-- idea taken from addon 'ElitistJerks'
|
|
local tankTalents =
|
|
(getTalentpointsSpent(57881) >= 2 and 1 or 0) + -- Natural Reaction
|
|
(getTalentpointsSpent(16929) >= 3 and 1 or 0) + -- Thick Hide
|
|
(getTalentpointsSpent(61336) >= 1 and 1 or 0) + -- Survival Instincts
|
|
(getTalentpointsSpent(57877) >= 3 and 1 or 0) -- Protector of the Pack
|
|
return tankTalents >= 3
|
|
end
|
|
|
|
-- NOTE: only vanilla 3.3.5 classes are matched below. CoA custom classes
|
|
-- will fall through to DAMAGER in mod:Update(). If a CoA-specific tank /
|
|
-- healer class table becomes available we should consult it here instead
|
|
-- of (or in addition to) the hard-coded class strings.
|
|
local function IsTank()
|
|
return (class == "WARRIOR" and select(3, GetTalentTabInfo(3)) >= 51) or
|
|
(class == "DEATHKNIGHT" and IsDeathKnightTank()) or
|
|
(class == "PALADIN" and select(3, GetTalentTabInfo(2)) >= 51) or
|
|
(class == "DRUID" and select(3, GetTalentTabInfo(2)) >= 51 and IsDruidTank())
|
|
end
|
|
|
|
-- See IsTank() note: CoA custom healer classes fall through to DAMAGER.
|
|
local function IsHealer()
|
|
return (class == "PALADIN" and select(3, GetTalentTabInfo(1)) >= 51) or
|
|
(class == "SHAMAN" and select(3, GetTalentTabInfo(3)) >= 51) or
|
|
(class == "DRUID" and select(3, GetTalentTabInfo(3)) >= 51) or
|
|
(class == "PRIEST" and select(3, GetTalentTabInfo(3)) < 51)
|
|
end
|
|
|
|
function mod:Update()
|
|
if profile_tankmode.enabled == 1 then
|
|
-- smart - judge by spec
|
|
local role
|
|
|
|
-- For CoA custom classes, derive role from the active spec via
|
|
-- CoAClassSpec. GetPlayerCoARole() returns nil when the class is
|
|
-- vanilla (not in CoAClassSpec) or when the spec cannot be resolved,
|
|
-- so vanilla classes always fall through to the IsTank/IsHealer path.
|
|
local coaRole = GetPlayerCoARole()
|
|
if coaRole then
|
|
role = coaRole
|
|
elseif class == "WARRIOR" and GetShapeshiftForm() ~= 2 then
|
|
-- no tank for gladiator stance
|
|
role = nil
|
|
elseif IsTank() then
|
|
role = "TANK"
|
|
elseif IsHealer() then
|
|
role = "HEALER"
|
|
else
|
|
role = "DAMAGER"
|
|
end
|
|
|
|
if role == "TANK" then
|
|
tankmode = true
|
|
else
|
|
tankmode = false
|
|
end
|
|
else
|
|
tankmode = (profile_tankmode.enabled == 3)
|
|
end
|
|
end
|
|
end
|
|
|
|
function mod:Toggle()
|
|
if profile_tankmode.enabled == 1 then
|
|
-- smart tank mode, listen for spec changes
|
|
self:RegisterEvent("PLAYER_TALENT_UPDATE", "Update")
|
|
|
|
-- on a warrior, watch for gladiator stance
|
|
if class == "WARRIOR" then
|
|
self:RegisterEvent("UPDATE_SHAPESHIFT_FORM", "Update")
|
|
end
|
|
else
|
|
self:UnregisterEvent("PLAYER_TALENT_UPDATE")
|
|
self:UnregisterEvent("UPDATE_SHAPESHIFT_FORM")
|
|
end
|
|
|
|
self:Update()
|
|
end
|
|
|
|
function mod:ThreatUpdate(frame)
|
|
frame.hasThreat = true
|
|
-- we are holding threat if the default glow is red
|
|
frame.holdingThreat = frame.glow.r > .9 and (frame.glow.g + frame.glow.b) < .1
|
|
|
|
if not frame.targetGlow or not frame.target then
|
|
if tankmode then
|
|
-- set glow to tank colour unless this is the current target
|
|
frame:SetGlowColour(unpack(profile_tankmode.glowcolour))
|
|
else
|
|
-- not in tank mode; set glow to default ui's colour
|
|
frame:SetGlowColour(frame.glow.r, frame.glow.g, frame.glow.b)
|
|
end
|
|
end
|
|
|
|
if tankmode then
|
|
-- also change health bar colour in tank mode
|
|
if frame.holdingThreat then
|
|
frame:SetHealthColour(10, unpack(profile_tankmode.barcolour))
|
|
ShowThreatBrackets(frame, unpack(profile_tankmode.barcolour))
|
|
else
|
|
-- losing/gaining threat
|
|
frame:SetHealthColour(10, unpack(profile_tankmode.midcolour))
|
|
ShowThreatBrackets(frame, unpack(profile_tankmode.midcolour))
|
|
end
|
|
else
|
|
-- not in tank mode; use default glow colour for brackets, too
|
|
ShowThreatBrackets(frame, frame.glow.r, frame.glow.g, frame.glow.b)
|
|
end
|
|
end
|
|
function mod:ThreatClear(frame)
|
|
frame:SetHealthColour(false)
|
|
ShowThreatBrackets(frame, false)
|
|
end
|
|
-------------------------------------------------------------------- messages --
|
|
function mod:PostCreate(msg, f)
|
|
self:CreateThreatBrackets(f)
|
|
end
|
|
function mod:PostHide(msg, f)
|
|
ShowThreatBrackets(f, false)
|
|
end
|
|
---------------------------------------------------- Post db change functions --
|
|
mod:AddConfigChanged("enabled", function() mod:Toggle() end)
|
|
mod:AddConfigChanged(
|
|
{"brackets", "scale"},
|
|
function()
|
|
mod:UpdateThreatBracketScaling()
|
|
end,
|
|
function(f)
|
|
mod:UpdateThreatBrackets(f)
|
|
end
|
|
)
|
|
-------------------------------------------------------------------- Register --
|
|
function mod:GetOptions()
|
|
return {
|
|
tankmode = {
|
|
type = "group",
|
|
name = L["Tank mode"],
|
|
inline = true,
|
|
order = 10,
|
|
disabled = function(info)
|
|
return mod.db.profile.tankmode.enabled == 2
|
|
end,
|
|
args = {
|
|
enabled = {
|
|
type = "select",
|
|
name = L["Enable tank mode"],
|
|
desc = L['Change the colour of a plate\'s health bar and border when you have threat on its unit.\n\nSelecting "Smart" (default) will automatically enable or disable tank mode based on your current specialisation\'s role.'],
|
|
values = {"Smart", "Disabled", "Enabled"},
|
|
order = 0,
|
|
width = "double",
|
|
disabled = false
|
|
},
|
|
barcolour = {
|
|
type = "color",
|
|
name = L["Bar colour"],
|
|
desc = L["The bar colour to use when you have threat"],
|
|
order = 10,
|
|
width = "half"
|
|
},
|
|
midcolour = {
|
|
type = "color",
|
|
name = L["Transitional colour"],
|
|
desc = L["The bar colour to use when you are losing or gaining threat."],
|
|
order = 20,
|
|
width = "half"
|
|
},
|
|
glowcolour = {
|
|
type = "color",
|
|
name = L["Glow colour"],
|
|
desc = L["The glow (border) colour to use when you have threat"],
|
|
hasAlpha = true,
|
|
order = 30,
|
|
width = "half"
|
|
}
|
|
}
|
|
},
|
|
brackets = {
|
|
type = "group",
|
|
name = L["Threat brackets"],
|
|
inline = true,
|
|
order = 20,
|
|
disabled = function(info)
|
|
return not mod.db.profile.brackets.enable_brackets
|
|
end,
|
|
args = {
|
|
enable_brackets = {
|
|
type = "toggle",
|
|
name = L["Show threat brackets"],
|
|
desc = L["Show threat brackets when you have threat on a nameplate. Kind of like target arrows, but for threat. In tank mode they will inherit the bar colour set above. Otherwise they will use the default glow colour."],
|
|
order = 10,
|
|
disabled = false
|
|
},
|
|
scale = {
|
|
type = "range",
|
|
name = L["Threat bracket scale"],
|
|
desc = L["The scale of the threat bracket textures"],
|
|
order = 20,
|
|
min = 0.1,
|
|
softMin = 0.5,
|
|
softMax = 2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
function mod:configChangedListener()
|
|
profile_tankmode = self.db.profile.tankmode
|
|
end
|
|
|
|
function mod:OnInitialize()
|
|
self.db = addon.db:RegisterNamespace(self.moduleName, {profile = {
|
|
tankmode = {
|
|
enabled = 1,
|
|
barcolour = {.2, .9, .1},
|
|
midcolour = {1, .5, 0},
|
|
glowcolour = {1, 0, 0, 1}
|
|
},
|
|
brackets = {
|
|
enable_brackets = true,
|
|
scale = 1
|
|
}
|
|
}})
|
|
|
|
addon:InitModuleOptions(self)
|
|
self:UpdateThreatBracketScaling()
|
|
self:SetEnabledState(true)
|
|
end
|
|
|
|
function mod:OnEnable()
|
|
class = select(2, UnitClass("player"))
|
|
|
|
if self.db.profile.brackets.enable_brackets then
|
|
self:RegisterMessage("KuiNameplates_PostCreate", "PostCreate")
|
|
self:RegisterMessage("KuiNameplates_PostHide", "PostHide")
|
|
end
|
|
|
|
self:Toggle()
|
|
end |