Files
coa-kui-nameplates/Kui_Nameplates/Core/Core.lua
T

629 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
-- CoA: StaticPopup_Show silently fails when called from OnInitialize (ADDON_LOADED,
-- before PLAYER_LOGIN). Defer via C_Timer.After(0, …) so the popup fires after
-- the StaticPopup system is ready. See PORTING.md §StaticPopup_Show during PLAYER_LOGIN.
C_Timer.After(0, function()
StaticPopup_Show("KUINAMEPLATES_INCOMPATIBLE")
end)
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