624 lines
17 KiB
Lua
624 lines
17 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"):NewAddon("KuiNameplates", "AceEvent-3.0", "AceTimer-3.0")
|
|
addon.version = GetAddOnMetadata("Kui_Nameplates", "Version")
|
|
addon.website = GetAddOnMetadata("Kui_Nameplates", "X-Website")
|
|
_G.KuiNameplates = addon
|
|
|
|
local kui = LibStub("Kui-1.0")
|
|
local LSM = LibStub("LibSharedMedia-3.0")
|
|
local L = LibStub("AceLocale-3.0"):GetLocale("KuiNameplates")
|
|
|
|
local group_update
|
|
local GROUP_UPDATE_INTERVAL = 1
|
|
local group_update_elapsed
|
|
|
|
addon.font = ""
|
|
addon.uiscale = nil
|
|
|
|
addon.frameList = {}
|
|
addon.numFrames = 0
|
|
|
|
-- sizes of frame elements
|
|
-- some populated by UpdateSizesTable & ScaleFontSizes
|
|
addon.sizes = {
|
|
frame = {
|
|
bgOffset = 8 -- inset of the frame glow
|
|
},
|
|
tex = {
|
|
targetGlowH = 7,
|
|
targetArrow = 33
|
|
},
|
|
font = {}
|
|
}
|
|
|
|
-- as these are scaled with the user option we need to store the default
|
|
addon.defaultFontSizes = {
|
|
large = 12,
|
|
spellname = 11,
|
|
name = 11,
|
|
level = 11,
|
|
health = 11,
|
|
small = 9
|
|
}
|
|
|
|
-- add latin-only fonts to LSM
|
|
LSM:Register(LSM.MediaType.FONT, "Yanone Kaffesatz Bold", kui.m.f.yanone)
|
|
LSM:Register(LSM.MediaType.FONT, "FrancoisOne", kui.m.f.francois)
|
|
local DEFAULT_FONT = "FrancoisOne"
|
|
|
|
-- add my status bar textures too..
|
|
LSM:Register(LSM.MediaType.STATUSBAR, "Kui status bar", kui.m.t.bar)
|
|
LSM:Register(LSM.MediaType.STATUSBAR, "Kui shaded bar", kui.m.t.oldbar)
|
|
local DEFAULT_BAR = "Kui status bar"
|
|
|
|
local locale = GetLocale()
|
|
local latin = (locale ~= "zhCN" and locale ~= "zhTW" and locale ~= "koKR" and locale ~= "ruRU")
|
|
|
|
-------------------------------------------------------------- Default config --
|
|
local defaults = {
|
|
profile = {
|
|
general = {
|
|
combataction_hostile = 1,
|
|
combataction_friendly = 1,
|
|
highlight = true, -- highlight plates on mouse-over
|
|
highlight_target = false,
|
|
fixaa = true, -- attempt to make plates appear sharper
|
|
compatibility = false,
|
|
bartexture = DEFAULT_BAR,
|
|
targetglow = true,
|
|
targetglowcolour = {.3, 0.7, 1, 1},
|
|
targetarrows = false,
|
|
hheight = 13,
|
|
thheight = 9,
|
|
width = 130,
|
|
twidth = 72,
|
|
glowshadow = true,
|
|
strata = "BACKGROUND",
|
|
lowhealthval = 20,
|
|
raidicon_size = 30,
|
|
raidicon_side = 3
|
|
},
|
|
fade = {
|
|
smooth = true, -- smoothy fade plates
|
|
fadespeed = .5, -- fade animation speed modifier
|
|
fademouse = false, -- fade in plates on mouse-over
|
|
fadeall = false, -- fade all plates by default
|
|
fadedalpha = .5, -- the alpha value to fade plates out to
|
|
rules = {
|
|
avoidhostilehp = false,
|
|
avoidfriendhp = false,
|
|
avoidcast = false,
|
|
avoidraidicon = true
|
|
}
|
|
},
|
|
text = {
|
|
level = false, -- display levels
|
|
nameanchorpoint = "TOP",
|
|
nameoffsetx = 0,
|
|
nameoffsety = 0,
|
|
levelanchorpoint = "BOTTOMLEFT",
|
|
leveloffsetx = 0,
|
|
leveloffsety = 2.5,
|
|
healthanchorpoint = "BOTTOMRIGHT",
|
|
healthoffsetx = 0,
|
|
healthoffsety = 2.5
|
|
},
|
|
hp = {
|
|
reactioncolours = {
|
|
hatedcol = {.7, 0.2, 0.1},
|
|
neutralcol = {1, 0.8, 0},
|
|
friendlycol = {.2, 0.6, 0.1},
|
|
tappedcol = {.5, 0.5, 0.5},
|
|
playercol = {.2, 0.5, 0.9}
|
|
},
|
|
bar = {
|
|
animation = 2
|
|
},
|
|
text = {
|
|
hp_text_disabled = false,
|
|
mouseover = false,
|
|
hp_friend_max = 5,
|
|
hp_friend_low = 4,
|
|
hp_hostile_max = 5,
|
|
hp_hostile_low = 3
|
|
}
|
|
},
|
|
fonts = {
|
|
options = {
|
|
font = (latin and DEFAULT_FONT or LSM:GetDefault(LSM.MediaType.FONT)),
|
|
fontscale = 1,
|
|
outline = true,
|
|
monochrome = false,
|
|
onesize = false,
|
|
noalpha = false
|
|
},
|
|
sizes = {
|
|
large = 12,
|
|
spellname = 11,
|
|
name = 11,
|
|
level = 11,
|
|
health = 11,
|
|
small = 9
|
|
}
|
|
}
|
|
}
|
|
}
|
|
------------------------------------------ GUID/name storage functions --
|
|
do
|
|
local knownGUIDs = {} -- GUIDs that we can relate to names (i.e. players)
|
|
local knownIndex = {}
|
|
|
|
-- loaded = visible frames that currently possess this key
|
|
local loadedGUIDs, loadedNames = {}, {}
|
|
|
|
function addon:StoreNameWithGUID(name, guid)
|
|
-- used to provide aggressive name -> guid matching
|
|
-- should only be used for players
|
|
if not name or not guid then
|
|
return
|
|
end
|
|
if knownGUIDs[name] then
|
|
return
|
|
end
|
|
knownGUIDs[name] = guid
|
|
tinsert(knownIndex, name)
|
|
|
|
-- purging index > 100 names
|
|
if #knownIndex > 100 then
|
|
knownGUIDs[tremove(knownIndex, 1)] = nil
|
|
end
|
|
end
|
|
|
|
function addon:GetGUID(f)
|
|
-- give this frame a guid if we think we already know it
|
|
if f.player and knownGUIDs[f.name.text] then
|
|
f.guid = knownGUIDs[f.name.text]
|
|
loadedGUIDs[f.guid] = f
|
|
|
|
addon:SendMessage("KuiNameplates_GUIDAssumed", f)
|
|
end
|
|
end
|
|
function addon:StoreGUID(f, unit, guid)
|
|
if not unit then
|
|
return
|
|
end
|
|
if not guid then
|
|
guid = UnitGUID(unit)
|
|
if not guid then
|
|
return
|
|
end
|
|
end
|
|
|
|
if f.guid and loadedGUIDs[f.guid] then
|
|
if f.guid ~= guid then
|
|
-- the currently stored guid is incorrect
|
|
loadedGUIDs[f.guid] = nil
|
|
else
|
|
return
|
|
end
|
|
end
|
|
|
|
f.guid = guid
|
|
loadedGUIDs[guid] = f
|
|
|
|
if UnitIsPlayer(unit) then
|
|
-- we can probably assume this unit has a unique name
|
|
-- nevertheless, overwrite this each time. just in case.
|
|
self:StoreNameWithGUID(f.name.text, guid)
|
|
elseif loadedNames[f.name.text] == f then
|
|
-- force the registered f for this name to change
|
|
loadedNames[f.name.text] = nil
|
|
end
|
|
|
|
--print('got GUID for: '..f.name.text.. '; '..f.guid)
|
|
addon:SendMessage("KuiNameplates_GUIDStored", f, unit)
|
|
end
|
|
function addon:StoreName(f)
|
|
if not f.name.text or f.guid then
|
|
return
|
|
end
|
|
if not loadedNames[f.name.text] then
|
|
loadedNames[f.name.text] = f
|
|
end
|
|
end
|
|
function addon:FrameHasName(f)
|
|
return loadedNames[f.name.text] == f
|
|
end
|
|
function addon:FrameHasGUID(f)
|
|
return loadedGUIDs[f.guid] == f
|
|
end
|
|
function addon:ClearName(f)
|
|
if self:FrameHasName(f) then
|
|
loadedNames[f.name.text] = nil
|
|
end
|
|
end
|
|
function addon:ClearGUID(f)
|
|
if self:FrameHasGUID(f) then
|
|
loadedGUIDs[f.guid] = nil
|
|
end
|
|
f.guid = nil
|
|
end
|
|
function addon:GetNameplate(guid, name)
|
|
local gf, nf = loadedGUIDs[guid], loadedNames[name]
|
|
|
|
if gf then
|
|
return gf
|
|
elseif nf then
|
|
return nf
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
-- return the given unit's nameplate
|
|
function addon:GetUnitPlate(unit)
|
|
return self:GetNameplate(UnitGUID(unit), GetUnitName(unit))
|
|
end
|
|
|
|
-- store an assumed unique name with its guid before it becomes visible
|
|
local function StoreUnit(unit)
|
|
if not unit then
|
|
return
|
|
end
|
|
if not UnitIsPlayer(unit) then
|
|
return
|
|
end
|
|
|
|
local guid = UnitGUID(unit)
|
|
if not guid then
|
|
return
|
|
end
|
|
if loadedGUIDs[guid] then
|
|
return
|
|
end
|
|
|
|
local name = GetUnitName(unit)
|
|
if not name or knownGUIDs[name] then
|
|
return
|
|
end
|
|
addon:StoreNameWithGUID(name, guid)
|
|
|
|
-- also send GUIDStored if the frame currently exists
|
|
local f = addon:GetNameplate(guid, name)
|
|
if f then
|
|
addon:StoreGUID(f, unit, guid)
|
|
else
|
|
-- equivalent to GUIDStored, but with no currently-visible frame
|
|
addon:SendMessage("KuiNameplates_UnitStored", unit, name, guid)
|
|
end
|
|
end
|
|
|
|
function addon:UPDATE_MOUSEOVER_UNIT(event)
|
|
StoreUnit("mouseover")
|
|
if UnitExists("mouseovertarget") then
|
|
StoreUnit("mouseovertarget")
|
|
end
|
|
end
|
|
function addon:PLAYER_TARGET_CHANGED(event)
|
|
StoreUnit("target")
|
|
if UnitExists("targettarget") then
|
|
StoreUnit("targettarget")
|
|
end
|
|
end
|
|
function addon:PLAYER_FOCUS_CHANGED(event)
|
|
StoreUnit("focus")
|
|
if UnitExists("focustarget") then
|
|
StoreUnit("focustarget")
|
|
end
|
|
end
|
|
|
|
local function GetGroupTypeAndCount()
|
|
local t, stop, start = "raid", GetNumRaidMembers(), 1
|
|
if stop == 0 then
|
|
t, stop, start = "party", GetNumPartyMembers(), 0
|
|
end
|
|
if stop == 0 then
|
|
t = nil
|
|
end
|
|
return t, stop, start
|
|
end
|
|
|
|
function addon:GroupUpdate()
|
|
group_update = nil
|
|
|
|
local t, stop, start = GetGroupTypeAndCount()
|
|
if not t then
|
|
return
|
|
end
|
|
|
|
for i = start, stop do
|
|
StoreUnit(t .. i)
|
|
end
|
|
end
|
|
end
|
|
function addon:QueueGroupUpdate()
|
|
group_update = true
|
|
group_update_elapsed = 0
|
|
end
|
|
------------------------------------------------------------ helper functions --
|
|
-- cycle all frames' fontstrings and reset the font
|
|
function addon:UpdateAllFonts()
|
|
for _, frame in pairs(addon.frameList) do
|
|
for _, fs in pairs(frame.kui.fontObjects) do
|
|
local _, size, flags = fs:GetFont()
|
|
fs:SetFont(addon.font, size, flags)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- given to fontstrings created with frame:CreateFontString (below)
|
|
local function SetFontSize(fs, size)
|
|
size = size or (addon.db.profile.fonts.options.onesize and "name" or fs.osize or fs.size)
|
|
|
|
if type(size) == "string" and fs.size and addon.sizes.font[size] then
|
|
-- if fontsize is a key of the font sizes table, store it so that
|
|
-- we can scale this font correctly
|
|
fs.size = size
|
|
size = addon.sizes.font[size]
|
|
end
|
|
|
|
local font, _, flags = fs:GetFont()
|
|
fs:SetFont(font, size, flags)
|
|
end
|
|
|
|
-- given to frames
|
|
local function CreateFontString(self, parent, obj)
|
|
-- store size as a key of addon.fontSizes so that it can be recalled & scaled
|
|
-- correctly. Used by SetFontSize.
|
|
local sizeKey
|
|
|
|
obj = obj or {}
|
|
obj.mono = addon.db.profile.fonts.options.monochrome
|
|
obj.outline = addon.db.profile.fonts.options.outline
|
|
obj.size = (addon.db.profile.fonts.options.onesize and "name") or obj.size or "name"
|
|
|
|
if type(obj.size) == "string" then
|
|
sizeKey = obj.size
|
|
obj.size = addon.sizes.font[sizeKey]
|
|
end
|
|
|
|
if not obj.font then
|
|
obj.font = addon.font
|
|
end
|
|
|
|
if obj.alpha and addon.db.profile.fonts.options.noalpha then
|
|
obj.alpha = nil
|
|
end
|
|
|
|
local fs = kui.CreateFontString(parent, obj)
|
|
fs.size = sizeKey
|
|
fs.SetFontSize = SetFontSize
|
|
fs:SetWordWrap(false)
|
|
|
|
tinsert(self.fontObjects, fs)
|
|
return fs
|
|
end
|
|
|
|
addon.CreateFontString = CreateFontString
|
|
----------------------------------------------------------- scaling functions --
|
|
-- scale font sizes with the fontscale option
|
|
function addon:ScaleFontSize(key)
|
|
local size
|
|
|
|
if self.db.profile.fonts.sizes and self.db.profile.fonts.sizes[key] then
|
|
size = self.db.profile.fonts.sizes[key]
|
|
else
|
|
size = self.defaultFontSizes[key]
|
|
end
|
|
|
|
self.sizes.font[key] = size * self.db.profile.fonts.options.fontscale
|
|
end
|
|
-- the same, for all registered sizes
|
|
function addon:ScaleFontSizes()
|
|
for key, _ in pairs(self.defaultFontSizes) do
|
|
self:ScaleFontSize(key)
|
|
end
|
|
end
|
|
-- modules should use this to add font sizes which scale correctly with the
|
|
-- fontscale option
|
|
-- keys must be unique
|
|
function addon:RegisterFontSize(key, size)
|
|
-- TODO should add an option to the interface
|
|
addon.defaultFontSizes[key] = size
|
|
self:ScaleFontSize(key)
|
|
end
|
|
-- once upon a time, equivalent logic was necessary for all frame sizes
|
|
function addon:RegisterSize(type, key, size)
|
|
error("deprecated function call: RegisterSize " .. (type or "nil") .. " " .. (key or "nil") .. " " .. (size or "nil"))
|
|
end
|
|
|
|
function addon:UpdateSizesTable()
|
|
-- populate sizes table with profile values
|
|
addon.sizes.frame.height = addon.db.profile.general.hheight
|
|
addon.sizes.frame.theight = addon.db.profile.general.thheight
|
|
addon.sizes.frame.width = addon.db.profile.general.width
|
|
addon.sizes.frame.twidth = addon.db.profile.general.twidth
|
|
addon.sizes.tex.raidicon = addon.db.profile.general.raidicon_size
|
|
addon.sizes.tex.healthOffsetX = addon.db.profile.text.healthoffsetx
|
|
addon.sizes.tex.healthOffsetY = addon.db.profile.text.healthoffsety
|
|
addon.sizes.tex.levelOffsetX = addon.db.profile.text.leveloffsetx
|
|
addon.sizes.tex.levelOffsetY = addon.db.profile.text.leveloffsety
|
|
addon.sizes.tex.nameOffsetX = addon.db.profile.text.nameoffsetx
|
|
addon.sizes.tex.nameOffsetY = addon.db.profile.text.nameoffsety
|
|
addon.sizes.tex.targetGlowW = addon.sizes.frame.width - 5
|
|
addon.sizes.tex.ttargetGlowW = addon.sizes.frame.twidth - 5
|
|
end
|
|
------------------------------------------- Listen for LibSharedMedia changes --
|
|
function addon:LSMMediaRegistered(msg, mediatype, key)
|
|
if mediatype == LSM.MediaType.FONT then
|
|
if key == self.db.profile.fonts.options.font then
|
|
self.font = LSM:Fetch(mediatype, key)
|
|
addon:UpdateAllFonts()
|
|
end
|
|
elseif mediatype == LSM.MediaType.STATUSBAR then
|
|
if key == self.db.profile.general.bartexture then
|
|
self.bartexture = LSM:Fetch(mediatype, key)
|
|
addon:UpdateAllFonts()
|
|
end
|
|
end
|
|
end
|
|
------------------------------------------------------------ main update loop --
|
|
do
|
|
local WorldFrame, tinsert, select = WorldFrame, tinsert, select
|
|
function addon:OnUpdate()
|
|
-- find new nameplates
|
|
local frames = select("#", WorldFrame:GetChildren())
|
|
if frames ~= self.numFrames then
|
|
local f
|
|
for i = 1, frames do
|
|
f = select(i, WorldFrame:GetChildren())
|
|
if self:IsNameplate(f) and not f.kui then
|
|
self:InitFrame(f)
|
|
tinsert(self.frameList, f)
|
|
end
|
|
end
|
|
self.numFrames = frames
|
|
end
|
|
-- process group update queue
|
|
if group_update then
|
|
group_update_elapsed = group_update_elapsed + .1
|
|
if group_update_elapsed > GROUP_UPDATE_INTERVAL then
|
|
self:GroupUpdate()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
------------------------------------------------------------------------ init --
|
|
function addon:OnInitialize()
|
|
self.db = LibStub("AceDB-3.0"):New("KuiNameplatesGDB", defaults)
|
|
self:FinalizeOptions()
|
|
|
|
self.db.RegisterCallback(self, "OnProfileChanged", "ProfileChanged")
|
|
LSM.RegisterCallback(self, "LibSharedMedia_Registered", "LSMMediaRegistered")
|
|
|
|
-- we treat these like built in elements rather than having them rely
|
|
-- on messages
|
|
addon.Castbar = addon:GetModule("Castbar")
|
|
addon.TankModule = addon:GetModule("TankMode")
|
|
|
|
if IsAddOnLoaded("PlateBuffs") then
|
|
StaticPopup_Show("KUINAMEPLATES_INCOMPATIBLE")
|
|
end
|
|
end
|
|
---------------------------------------------------------------------- enable --
|
|
function addon:OnEnable()
|
|
-- force enable threat on nameplates - this is a hidden CVar
|
|
SetCVar("threatWarning", 3)
|
|
|
|
-- get font and status bar texture from LSM
|
|
self.font = LSM:Fetch(LSM.MediaType.FONT, self.db.profile.fonts.options.font)
|
|
self.bartexture = LSM:Fetch(LSM.MediaType.STATUSBAR, self.db.profile.general.bartexture)
|
|
|
|
-- handle deleted or invalid files
|
|
if not self.font then
|
|
self.font = LSM:Fetch(LSM.MediaType.FONT, DEFAULT_FONT)
|
|
end
|
|
if not self.bartexture then
|
|
self.bartexture = LSM:Fetch(LSM.MediaType.STATUSBAR, DEFAULT_BAR)
|
|
end
|
|
|
|
self.uiscale = UIParent:GetEffectiveScale()
|
|
|
|
self:UpdateSizesTable()
|
|
self:ScaleFontSizes()
|
|
|
|
-------------------------------------- Health bar smooth update functions --
|
|
if self.db.profile.hp.bar.animation == 2 then
|
|
local f, smoothing, GetFramerate, min, max, abs = CreateFrame("Frame"), {}, GetFramerate, math.min, math.max, math.abs
|
|
|
|
function self.SetValueSmooth(self, value)
|
|
local _, maxv = self:GetMinMaxValues()
|
|
|
|
if value == self:GetValue() or (self.prevMax and self.prevMax ~= maxv) then
|
|
-- finished smoothing/max health updated
|
|
smoothing[self] = nil
|
|
self:OrigSetValue(value)
|
|
else
|
|
smoothing[self] = value
|
|
end
|
|
|
|
self.prevMax = maxv
|
|
end
|
|
|
|
f:SetScript("OnUpdate", function()
|
|
local limit = 30 / GetFramerate()
|
|
|
|
for bar, value in pairs(smoothing) do
|
|
local cur = bar:GetValue()
|
|
local new = cur + min((value - cur) / 3, max(value - cur, limit))
|
|
|
|
if new ~= new then
|
|
new = value
|
|
end
|
|
|
|
bar:OrigSetValue(new)
|
|
|
|
if cur == value or abs(new - value) < .005 then
|
|
bar:OrigSetValue(value)
|
|
smoothing[bar] = nil
|
|
end
|
|
end
|
|
end)
|
|
elseif self.db.profile.hp.bar.animation == 3 then
|
|
local select = select
|
|
local SetValueCutaway = function(self, value)
|
|
if value < self:GetValue() then
|
|
if not kui.frameIsFading(self.KuiFader) then
|
|
self.KuiFader:SetPoint("RIGHT", self, "LEFT", (self:GetValue() / select(2, self:GetMinMaxValues())) * self:GetWidth(), 0)
|
|
|
|
-- store original rightmost value
|
|
self.KuiFader.right = self:GetValue()
|
|
end
|
|
|
|
kui.frameFade(self.KuiFader, {mode = "OUT", timeToFade = 0.3})
|
|
end
|
|
|
|
if self.KuiFader.right and value > self.KuiFader.right then
|
|
-- stop animation if new value overlaps old end point
|
|
kui.frameFadeRemoveFrame(self.KuiFader)
|
|
self.KuiFader:SetAlpha(0)
|
|
end
|
|
|
|
self:orig_SetValue(value)
|
|
end
|
|
local SetColour = function(self, ...)
|
|
self:orig_SetStatusBarColor(...)
|
|
self.KuiFader:SetVertexColor(...)
|
|
end
|
|
|
|
function self.CutawayBar(bar)
|
|
bar.KuiFader = bar:CreateTexture(nil, "ARTWORK")
|
|
bar.KuiFader:SetTexture(kui.m.t.solid)
|
|
bar.KuiFader:SetVertexColor(bar:GetStatusBarColor())
|
|
bar.KuiFader:SetAlpha(0)
|
|
|
|
bar.KuiFader:SetPoint("TOP")
|
|
bar.KuiFader:SetPoint("BOTTOM")
|
|
bar.KuiFader:SetPoint("LEFT", bar:GetStatusBarTexture(), "RIGHT")
|
|
|
|
bar.orig_SetValue = bar.SetValue
|
|
bar.SetValue = SetValueCutaway
|
|
|
|
bar.orig_SetStatusBarColor = bar.SetStatusBarColor
|
|
bar.SetStatusBarColor = SetColour
|
|
end
|
|
end
|
|
|
|
self:configChangedListener()
|
|
|
|
self:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
self:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
|
|
self:RegisterEvent("PLAYER_TARGET_CHANGED")
|
|
self:RegisterEvent("PLAYER_FOCUS_CHANGED")
|
|
self:RegisterEvent("PLAYER_REGEN_ENABLED")
|
|
self:RegisterEvent("PLAYER_REGEN_DISABLED")
|
|
self:RegisterEvent("PARTY_MEMBERS_CHANGED", "QueueGroupUpdate")
|
|
self:RegisterEvent("RAID_ROSTER_UPDATE", "QueueGroupUpdate")
|
|
|
|
self:ScheduleRepeatingTimer("OnUpdate", 0.1)
|
|
end |