From 5d1eb1ea05070f87022a73d120730d566152e3d4 Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Sun, 8 Jun 2025 13:21:43 +0200 Subject: [PATCH 01/10] Added necessary changes to support name and level offset --- api/config.lua | 4 + env/translations_deDE.lua | 9 +- env/translations_enUS.lua | 8 +- env/translations_esES.lua | 9 +- env/translations_frFR.lua | 9 +- env/translations_koKR.lua | 9 +- env/translations_ruRU.lua | 9 +- env/translations_zhCN.lua | 9 +- env/translations_zhTW.lua | 9 +- fonts/Francois.ttf | Bin 0 -> 60580 bytes modules/gui.lua | 10 +- modules/nameplates.lua | 26 +- modules/nameplates_B.lua | 1236 +++++++++++++++++++++++++++++++++++++ 13 files changed, 1323 insertions(+), 24 deletions(-) create mode 100644 fonts/Francois.ttf create mode 100644 modules/nameplates_B.lua diff --git a/api/config.lua b/api/config.lua index 371afbe13..e4fad0c9f 100644 --- a/api/config.lua +++ b/api/config.lua @@ -744,6 +744,10 @@ function pfUI:LoadConfig() pfUI:UpdateConfig("nameplates", nil, "enemyclassc", "1") pfUI:UpdateConfig("nameplates", nil, "friendclassc", "1") pfUI:UpdateConfig("nameplates", nil, "friendclassnamec", "0") + pfUI:UpdateConfig("nameplates", nil, "nameoffsetx", "0") + pfUI:UpdateConfig("nameplates", nil, "nameoffsety", "0") + pfUI:UpdateConfig("nameplates", nil, "leveloffsetx", "-3") + pfUI:UpdateConfig("nameplates", nil, "leveloffsety", "0") pfUI:UpdateConfig("nameplates", nil, "raidiconsize", "16") pfUI:UpdateConfig("nameplates", nil, "raidiconpos", "CENTER") pfUI:UpdateConfig("nameplates", nil, "raidiconoffx", "0") diff --git a/env/translations_deDE.lua b/env/translations_deDE.lua index 5b9890064..e2d9fa7be 100644 --- a/env/translations_deDE.lua +++ b/env/translations_deDE.lua @@ -511,8 +511,13 @@ pfUI_translation["deDE"] = { ["Name | Health Missing"] = nil, ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = nil, - ["Nameplates"] = nil, - ["Nameplate Width"] = nil, + ["Nameplates"] = nil, ["Nameplate Width"] = nil, + ["Name Position"] = "Name Position", + ["Name X-Offset"] = "Name X-Versatz", + ["Name Y-Offset"] = "Name Y-Versatz", + ["Level Position"] = "Level Position", + ["Level X-Offset"] = "Level X-Versatz", + ["Level Y-Offset"] = "Level Y-Versatz", ["Name (Short)"] = nil, ["Name (Short) | Health Missing"] = nil, ["Native"] = nil, diff --git a/env/translations_enUS.lua b/env/translations_enUS.lua index a78e533c6..3e9a0ede6 100644 --- a/env/translations_enUS.lua +++ b/env/translations_enUS.lua @@ -513,8 +513,12 @@ pfUI_translation["enUS"] = { ["Nameplate Border Size"] = nil, ["Nameplates"] = nil, ["Nameplate Width"] = nil, - ["Name (Short)"] = nil, - ["Name (Short) | Health Missing"] = nil, + ["Name Position"]=nil, + ["Name X-Offset"] = nil, + ["Name Y-Offset"] = nil, + ["Level Position"]=nil, + ["Level X-Offset"] = nil, + ["Level Y-Offset"] = nil, ["Native"] = nil, ["Network Down"] = nil, ["Network Latency"] = nil, diff --git a/env/translations_esES.lua b/env/translations_esES.lua index 7020699ed..e2d066124 100644 --- a/env/translations_esES.lua +++ b/env/translations_esES.lua @@ -511,8 +511,13 @@ pfUI_translation["esES"] = { ["Name | Health Missing"] = "Nombre | Falta de salud", ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = "Tamaño del borde de las placas de nombre", - ["Nameplates"] = "Placas identificativas", - ["Nameplate Width"] = "Ancho de las placas identificativas", + ["Nameplates"] = "Placas identificativas", ["Nameplate Width"] = "Ancho de las placas identificativas", + ["Name Position"] = "Posición del nombre", + ["Name X-Offset"] = "Desplazamiento X del nombre", + ["Name Y-Offset"] = "Desplazamiento Y del nombre", + ["Level Position"] = "Posición del nivel", + ["Level X-Offset"] = "Desplazamiento X del nivel", + ["Level Y-Offset"] = "Desplazamiento Y del nivel", ["Name (Short)"] = "Nombre (corto)", ["Name (Short) | Health Missing"] = "Nombre (corto) | Falta de salud", ["Native"] = "Nativo", diff --git a/env/translations_frFR.lua b/env/translations_frFR.lua index 9a6e68173..401f22e1f 100644 --- a/env/translations_frFR.lua +++ b/env/translations_frFR.lua @@ -511,8 +511,13 @@ pfUI_translation["frFR"] = { ["Name | Health Missing"] = "Nom | santé manquante", ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = "Taille de la bordure des barres de vie flottantes", - ["Nameplates"] = "Barres de vie flottantes", - ["Nameplate Width"] = "Largeur des barres de vie flottantes", + ["Nameplates"] = "Barres de vie flottantes", ["Nameplate Width"] = "Largeur des barres de vie flottantes", + ["Name Position"] = "Position du nom", + ["Name X-Offset"] = "Décalage X du nom", + ["Name Y-Offset"] = "Décalage Y du nom", + ["Level Position"] = "Position du niveau", + ["Level X-Offset"] = "Décalage X du niveau", + ["Level Y-Offset"] = "Décalage Y du niveau", ["Name (Short)"] = "Nom (Court)", ["Name (Short) | Health Missing"] = "Nom (court) | santé manquante", ["Native"] = "Native", diff --git a/env/translations_koKR.lua b/env/translations_koKR.lua index aa7fb7b82..c9826205b 100644 --- a/env/translations_koKR.lua +++ b/env/translations_koKR.lua @@ -511,8 +511,13 @@ pfUI_translation["koKR"] = { ["Name | Health Missing"] = nil, ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = nil, - ["Nameplates"] = "상바", - ["Nameplate Width"] = "상바 넓이", + ["Nameplates"] = "상바", ["Nameplate Width"] = "상바 넓이", + ["Name Position"] = "이름 위치", + ["Name X-Offset"] = "이름 X 오프셋", + ["Name Y-Offset"] = "이름 Y 오프셋", + ["Level Position"] = "레벨 위치", + ["Level X-Offset"] = "레벨 X 오프셋", + ["Level Y-Offset"] = "레벨 Y 오프셋", ["Name (Short)"] = nil, ["Name (Short) | Health Missing"] = nil, ["Native"] = nil, diff --git a/env/translations_ruRU.lua b/env/translations_ruRU.lua index 32bf08ccf..0f5834f43 100644 --- a/env/translations_ruRU.lua +++ b/env/translations_ruRU.lua @@ -511,8 +511,13 @@ pfUI_translation["ruRU"] = { ["Name | Health Missing"] = "Имя | Отсутствующее здоровье", ["Name (Linebreak) -Health Missing"] = "Имя (разрыв линии) -Отсутствует здоровье", ["Nameplate Border Size"] = "Размер границы индикатора здоровья", - ["Nameplates"] = "Индикаторы здоровья", - ["Nameplate Width"] = "Ширина индикатора здоровья", + ["Nameplates"] = "Индикаторы здоровья", ["Nameplate Width"] = "Ширина индикатора здоровья", + ["Name Position"] = "Позиция имени", + ["Name X-Offset"] = "Смещение имени по X", + ["Name Y-Offset"] = "Смещение имени по Y", + ["Level Position"] = "Позиция уровня", + ["Level X-Offset"] = "Смещение уровня по X", + ["Level Y-Offset"] = "Смещение уровня по Y", ["Name (Short)"] = "Короткое имя", ["Name (Short) | Health Missing"] = "Короткое имя | Отсутствующее здоровье", ["Native"] = "Родная", diff --git a/env/translations_zhCN.lua b/env/translations_zhCN.lua index 7da4d61ef..00c58893b 100644 --- a/env/translations_zhCN.lua +++ b/env/translations_zhCN.lua @@ -511,8 +511,13 @@ pfUI_translation["zhCN"] = { ["Name | Health Missing"] = "名字 | 失去的生命值", ["Name (Linebreak) -Health Missing"] = "名字 (换行符) -失去的生命值", ["Nameplate Border Size"] = "姓名板边框尺寸", - ["Nameplates"] = "姓名板", - ["Nameplate Width"] = "姓名板宽度", + ["Nameplates"] = "姓名板", ["Nameplate Width"] = "姓名板宽度", + ["Name Position"] = "名字位置", + ["Name X-Offset"] = "名字水平偏移", + ["Name Y-Offset"] = "名字垂直偏移", + ["Level Position"] = "等级位置", + ["Level X-Offset"] = "等级水平偏移", + ["Level Y-Offset"] = "等级垂直偏移", ["Name (Short)"] = "名字 (短)", ["Name (Short) | Health Missing"] = "名字 (短) | 失去的生命值", ["Native"] = "本地", diff --git a/env/translations_zhTW.lua b/env/translations_zhTW.lua index e7182e9a5..4145c74fc 100644 --- a/env/translations_zhTW.lua +++ b/env/translations_zhTW.lua @@ -511,8 +511,13 @@ pfUI_translation["zhTW"] = { ["Name | Health Missing"] = nil, ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = nil, - ["Nameplates"] = "姓名板", - ["Nameplate Width"] = "姓名板高度", + ["Nameplates"] = "姓名板", ["Nameplate Width"] = "姓名板高度", + ["Name Position"] = "名字位置", + ["Name X-Offset"] = "名字水平偏移", + ["Name Y-Offset"] = "名字垂直偏移", + ["Level Position"] = "等級位置", + ["Level X-Offset"] = "等級水平偏移", + ["Level Y-Offset"] = "等級垂直偏移", ["Name (Short)"] = "名字 (短)", ["Name (Short) | Health Missing"] = nil, ["Native"] = "本地", diff --git a/fonts/Francois.ttf b/fonts/Francois.ttf new file mode 100644 index 0000000000000000000000000000000000000000..98e0ffa8c47b00f6f1eeb11fdaf396bb54d223c1 GIT binary patch literal 60580 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 bzlZSTA}FP{J)InD-23O7a^H4j{|)~a{~H<4 literal 0 HcmV?d00001 diff --git a/modules/gui.lua b/modules/gui.lua index 4d39216d4..eb80aae42 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1560,7 +1560,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () CreateConfig(U["infight"], T["Enable Low Health Glow Effects On Screen Edges"], C.appearance.infight, "health", "checkbox") CreateConfig(U["infight"], T["Screen Edge Glow Intensity"], C.appearance.infight, "intensity", "dropdown", pfUI.gui.dropdowns.glowintensity) end) - + CreateGUIEntry(T["Settings"], T["Cooldown"], function() CreateConfig(U["buff"], T["Show Milliseconds When Timer Runs Out"], C.appearance.cd, "milliseconds", "checkbox") CreateConfig(nil, T["Cooldown Color (Less than 3 Sec)"], C.appearance.cd, "lowcolor", "color") @@ -2292,6 +2292,14 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () CreateConfig(U["nameplates"], T["Replace Totems With Icons"], C.nameplates, "totemicons", "checkbox") CreateConfig(U["nameplates"], T["Show Guild Name"], C.nameplates, "showguildname", "checkbox") + CreateConfig(nil, T["Name Position"], nil, nil, "header") + CreateConfig(U["nameplates"], T["Name X-Offset"], C.nameplates, "nameoffsetx") + CreateConfig(U["nameplates"], T["Name Y-Offset"], C.nameplates, "nameoffsety") + + CreateConfig(nil, T["Level Position"], nil, nil, "header") + CreateConfig(U["nameplates"], T["Level X-Offset"], C.nameplates, "leveloffsetx") + CreateConfig(U["nameplates"], T["Level Y-Offset"], C.nameplates, "leveloffsety") + CreateConfig(nil, T["Raid Icon"], nil, nil, "header") CreateConfig(U["nameplates"], T["Raid Icon Position"], C.nameplates, "raidiconpos", "dropdown", pfUI.gui.dropdowns.positions) CreateConfig(U["nameplates"], T["Raid Icon X-Offset"], C.nameplates, "raidiconoffx") diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 59785b17a..3ef1b96ff 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -395,7 +395,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.health.text:SetTextColor(1,1,1,1) nameplate.name = nameplate:CreateFontString(nil, "OVERLAY") - nameplate.name:SetPoint("TOP", nameplate, "TOP", 0, 0) nameplate.glow = nameplate:CreateTexture(nil, "BACKGROUND") nameplate.glow:SetPoint("CENTER", nameplate.health, "CENTER", 0, 0) @@ -406,7 +405,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.guild:SetPoint("BOTTOM", nameplate.health, "BOTTOM", 0, 0) nameplate.level = nameplate:CreateFontString(nil, "OVERLAY") - nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", -3, 0) nameplate.raidicon:SetParent(nameplate.health) nameplate.raidicon:SetDrawLayer("OVERLAY") @@ -515,19 +513,31 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () c.NOTHREAT.r, c.NOTHREAT.g, c.NOTHREAT.b, c.NOTHREAT.a = GetStringColor(C.nameplates.combatnothreat) c.STUN.r, c.STUN.g, c.STUN.b, c.STUN.a = GetStringColor(C.nameplates.combatstun) + -- Get name offset values from config + local nameOffsetX = tonumber(C.nameplates.nameoffsetx) or 0 + local nameOffsetY = tonumber(C.nameplates.nameoffsety) or 0 + local levelOffsetX = tonumber(C.nameplates.leveloffsetx) or 0 + local levelOffsetY = tonumber(C.nameplates.leveloffsety) or 0 + nameplate:SetWidth(plate_width) nameplate:SetHeight(plate_height) nameplate:SetPoint("TOP", parent, "TOP", 0, 0) - nameplate.name:SetFont(font, font_size, font_style) nameplate.health:SetOrientation(orientation) - nameplate.health:SetPoint("TOP", nameplate.name, "BOTTOM", 0, healthoffset) + nameplate.health:SetPoint("TOP", nameplate, "BOTTOM", 0, healthoffset) nameplate.health:SetStatusBarTexture(hptexture) nameplate.health:SetWidth(C.nameplates.width) nameplate.health:SetHeight(C.nameplates.heighthealth) nameplate.health.hlr, nameplate.health.hlg, nameplate.health.hlb, nameplate.health.hla = hlr, hlg, hlb, hla + nameplate.name:SetFont(font, font_size, font_style) + nameplate.name:SetPoint("TOP", nameplate.health, "TOP", nameOffsetX, nameOffsetY) + + DEFAULT_CHAT_FRAME:AddMessage("pfUI: Setting level offset to " .. levelOffsetX .. ", " .. levelOffsetY) + nameplate.level:SetFont(font, font_size, font_style) + nameplate.level:ClearAllPoints() + nameplate.level:SetPoint("TOP", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) CreateBackdrop(nameplate.health, default_border) nameplate.health.text:SetFont(font, font_size - 2, "OUTLINE") @@ -541,7 +551,8 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.raidicon:ClearAllPoints() nameplate.raidicon:SetPoint(C.nameplates.raidiconpos, nameplate.health, C.nameplates.raidiconpos, C.nameplates.raidiconoffx, C.nameplates.raidiconoffy) - nameplate.level:SetFont(font, font_size, font_style) + + nameplate.raidicon:SetWidth(C.nameplates.raidiconsize) nameplate.raidicon:SetHeight(C.nameplates.raidiconsize) @@ -679,8 +690,9 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () plate.guild:Hide() plate.totem:Show() elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then - plate.level:SetPoint("RIGHT", plate.name, "LEFT", -3, 0) + DEFAULT_CHAT_FRAME:AddMessage("pfUI Hideplate: Setting level offset to -5, 0") plate.name:SetParent(plate) + plate.level:SetParent(plate) plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2)) plate.level:Show() @@ -693,8 +705,8 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () end plate.totem:Hide() else - plate.level:SetPoint("RIGHT", plate.health, "LEFT", -5, 0) plate.name:SetParent(plate.health) + plate.level:SetParent(plate.health) plate.guild:SetPoint("BOTTOM", plate.health, "BOTTOM", 0, -(font_size + 4)) plate.level:Show() diff --git a/modules/nameplates_B.lua b/modules/nameplates_B.lua new file mode 100644 index 000000000..efb62c229 --- /dev/null +++ b/modules/nameplates_B.lua @@ -0,0 +1,1236 @@ +pfUI:RegisterModule("nameplates", "vanilla:tbc", function () + -- disable original castbars + pcall(SetCVar, "ShowVKeyCastbar", 0) + + local unitcolors = { + ["ENEMY_NPC"] = { .9, .2, .3, .8 }, + ["NEUTRAL_NPC"] = { 1, 1, .3, .8 }, + ["FRIENDLY_NPC"] = { .6, 1, 0, .8 }, + ["ENEMY_PLAYER"] = { .9, .2, .3, .8 }, + ["FRIENDLY_PLAYER"] = { .2, .6, 1, .8 } + } + + local combatstate = { + -- gets overwritten by user config + ["NOTHREAT"] = { r = .7, g = .7, b = .2, a = 1 }, + ["THREAT"] = { r = .7, g = .2, b = .2, a = 1 }, + ["CASTING"] = { r = .7, g = .2, b = .7, a = 1 }, + ["STUN"] = { r = .2, g = .7, b = .7, a = 1 }, + ["NONE"] = { r = .2, g = .2, b = .2, a = 1 }, + } + + local elitestrings = { + ["elite"] = "+", + ["rareelite"] = "R+", + ["rare"] = "R", + ["boss"] = "B" + } + + -- catch all nameplates + local childs, regions, plate + local initialized = 0 + local parentcount = 0 + local platecount = 0 + local registry = {} + local debuffdurations = C.appearance.cd.debuffs == "1" and true or nil + + -- cache default border color + local er, eg, eb, ea = GetStringColor(pfUI_config.appearance.border.color) + + local function GetCombatStateColor(guid) + local target = guid.."target" + local color = false + + if UnitAffectingCombat("player") and UnitAffectingCombat(guid) and not UnitCanAssist("player", guid) then + if C.nameplates.ccombatcasting == "1" and (UnitCastingInfo(guid) or UnitChannelInfo(guid)) then + color = combatstate.CASTING + elseif C.nameplates.ccombatthreat == "1" and UnitIsUnit(target, "player") then + color = combatstate.THREAT + elseif C.nameplates.ccombatnothreat == "1" and UnitExists(target) then + color = combatstate.NOTHREAT + elseif C.nameplates.ccombatstun == "1" and not UnitExists(target) and not UnitIsPlayer(guid) then + color = combatstate.STUN + end + end + + return color + end + + local function DoNothing() + return + end + + local function IsNamePlate(frame) + if frame:GetObjectType() ~= NAMEPLATE_FRAMETYPE then return nil end + regions = plate:GetRegions() + + if not regions then return nil end + if not regions.GetObjectType then return nil end + if not regions.GetTexture then return nil end + + if regions:GetObjectType() ~= "Texture" then return nil end + return regions:GetTexture() == "Interface\\Tooltips\\Nameplate-Border" or nil + end + + local function DisableObject(object) + if not object then return end + if not object.GetObjectType then return end + + local otype = object:GetObjectType() + + if otype == "Texture" then + object:SetTexture("") + object:SetTexCoord(0, 0, 0, 0) + elseif otype == "FontString" then + object:SetWidth(0.001) + elseif otype == "StatusBar" then + object:SetStatusBarTexture("") + end + end + + local function TotemPlate(name) + if C.nameplates.totemicons == "1" then + for totem, icon in pairs(L["totems"]) do + if string.find(name, totem) then return icon end + end + end + end + + local function HidePlate(unittype, name, fullhp, target) + -- keep some plates always visible according to config + if C.nameplates.fullhealth == "1" and not fullhp then return nil end + if C.nameplates.target == "1" and target then return nil end + + -- return true when something needs to be hidden + if C.nameplates.enemynpc == "1" and unittype == "ENEMY_NPC" then + return true + elseif C.nameplates.enemyplayer == "1" and unittype == "ENEMY_PLAYER" then + return true + elseif C.nameplates.neutralnpc == "1" and unittype == "NEUTRAL_NPC" then + return true + elseif C.nameplates.friendlynpc == "1" and unittype == "FRIENDLY_NPC" then + return true + elseif C.nameplates.friendlyplayer == "1" and unittype == "FRIENDLY_PLAYER" then + return true + elseif C.nameplates.critters == "1" and unittype == "NEUTRAL_NPC" then + for i, critter in pairs(L["critters"]) do + if string.lower(name) == string.lower(critter) then return true end + end + elseif C.nameplates.totems == "1" then + for totem in pairs(L["totems"]) do + if string.find(name, totem) then return true end + end + end + + -- nothing to hide + return nil + end + + local function abbrevname(t) + return string.sub(t,1,1)..". " + end + + local function GetNameString(name) + local abbrev = pfUI_config.unitframes.abbrevname == "1" or nil + local size = 20 + + -- first try to only abbreviate the first word + if abbrev and name and strlen(name) > size then + name = string.gsub(name, "^(%S+) ", abbrevname) + end + + -- abbreviate all if it still doesn't fit + if abbrev and name and strlen(name) > size then + name = string.gsub(name, "(%S+) ", abbrevname) + end + + return name + end + + + local function GetUnitType(red, green, blue) + if red > .9 and green < .2 and blue < .2 then + return "ENEMY_NPC" + elseif red > .9 and green > .9 and blue < .2 then + return "NEUTRAL_NPC" + elseif red < .2 and green < .2 and blue > 0.9 then + return "FRIENDLY_PLAYER" + elseif red < .2 and green > .9 and blue < .2 then + return "FRIENDLY_NPC" + end + end + + local filter, list, cache + local function DebuffFilterPopulate() + -- initialize variables + filter = C.nameplates["debuffs"]["filter"] + if filter == "none" then return end + list = C.nameplates["debuffs"][filter] + cache = {} + + -- populate list + for _, val in pairs({strsplit("#", list)}) do + cache[strlower(val)] = true + end + end + + local function DebuffFilter(effect) + if filter == "none" then return true end + if not cache then DebuffFilterPopulate() end + + if filter == "blacklist" and cache[strlower(effect)] then + return nil + elseif filter == "blacklist" then + return true + elseif filter == "whitelist" and cache[strlower(effect)] then + return true + elseif filter == "whitelist" then + return nil + end + end + + local function PlateCacheDebuffs(self, unitstr, verify) + if not self.debuffcache then self.debuffcache = {} end + + for id = 1, 16 do + local effect, _, texture, stacks, _, duration, timeleft + + if unitstr and C.nameplates.selfdebuff == "1" then + effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, id) + else + effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitDebuff(unitstr, id) + end + + if effect and timeleft and timeleft > 0 then + local start = GetTime() - ( (duration or 0) - ( timeleft or 0) ) + local stop = GetTime() + ( timeleft or 0 ) + self.debuffcache[id] = self.debuffcache[id] or {} + self.debuffcache[id].effect = effect + self.debuffcache[id].texture = texture + self.debuffcache[id].stacks = stacks + self.debuffcache[id].duration = duration or 0 + self.debuffcache[id].start = start + self.debuffcache[id].stop = stop + self.debuffcache[id].empty = nil + end + end + + self.verify = verify + end + + local function PlateUnitDebuff(self, id) + -- break on unknown data + if not self.debuffcache then return end + if not self.debuffcache[id] then return end + if not self.debuffcache[id].stop then return end + + -- break on timeout debuffs + if self.debuffcache[id].empty then return end + if self.debuffcache[id].stop < GetTime() then return end + + -- return cached debuff + local c = self.debuffcache[id] + return c.effect, c.rank, c.texture, c.stacks, c.dtype, c.duration, (c.stop - GetTime()) + end + + local function CreateDebuffIcon(plate, index) + plate.debuffs[index] = CreateFrame("Frame", plate.platename.."Debuff"..index, plate) + plate.debuffs[index]:Hide() + plate.debuffs[index]:SetFrameLevel(1) + + plate.debuffs[index].icon = plate.debuffs[index]:CreateTexture(nil, "BACKGROUND") + plate.debuffs[index].icon:SetTexture(.3,1,.8,1) + plate.debuffs[index].icon:SetAllPoints(plate.debuffs[index]) + + plate.debuffs[index].stacks = plate.debuffs[index]:CreateFontString(nil, "OVERLAY") + plate.debuffs[index].stacks:SetAllPoints(plate.debuffs[index]) + plate.debuffs[index].stacks:SetJustifyH("RIGHT") + plate.debuffs[index].stacks:SetJustifyV("BOTTOM") + plate.debuffs[index].stacks:SetTextColor(1,1,0) + + if pfUI.client <= 11200 then + -- create a fake animation frame on vanilla to improve performance + plate.debuffs[index].cd = CreateFrame("Frame", plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index]) + plate.debuffs[index].cd:SetScript("OnUpdate", CooldownFrame_OnUpdateModel) + plate.debuffs[index].cd.AdvanceTime = DoNothing + plate.debuffs[index].cd.SetSequence = DoNothing + plate.debuffs[index].cd.SetSequenceTime = DoNothing + else + -- use regular cooldown animation frames on burning crusade and later + plate.debuffs[index].cd = CreateFrame(COOLDOWN_FRAME_TYPE, plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate") + end + + plate.debuffs[index].cd.pfCooldownStyleAnimation = 0 + plate.debuffs[index].cd.pfCooldownType = "ALL" + end + + local function UpdateDebuffConfig(nameplate, i) + if not nameplate.debuffs[i] then return end + + -- update debuff positions + local width = tonumber(C.nameplates.width) + local debuffsize = tonumber(C.nameplates.debuffsize) + local debuffoffset = tonumber(C.nameplates.debuffoffset) + local limit = floor(width / debuffsize) + local font = C.nameplates.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default + local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size + local font_style = C.nameplates.name.fontstyle + + local aligna, alignb, offs, space + if C.nameplates.debuffs["position"] == "BOTTOM" then + aligna, alignb, offs, space = "TOPLEFT", "BOTTOMLEFT", -debuffoffset, -1 + else + aligna, alignb, offs, space = "BOTTOMLEFT", "TOPLEFT", debuffoffset, 1 + end + + nameplate.debuffs[i].stacks:SetFont(font, font_size, font_style) + nameplate.debuffs[i]:ClearAllPoints() + if i == 1 then + nameplate.debuffs[i]:SetPoint(aligna, nameplate.health, alignb, 0, offs) + elseif i <= limit then + nameplate.debuffs[i]:SetPoint("LEFT", nameplate.debuffs[i-1], "RIGHT", 1, 0) + elseif i > limit and limit > 0 then + nameplate.debuffs[i]:SetPoint(aligna, nameplate.debuffs[i-limit], alignb, 0, space) + end + + nameplate.debuffs[i]:SetWidth(tonumber(C.nameplates.debuffsize)) + nameplate.debuffs[i]:SetHeight(tonumber(C.nameplates.debuffsize)) + end + + -- create nameplate core + local nameplates = CreateFrame("Frame", "pfNameplates", UIParent) + nameplates:RegisterEvent("PLAYER_ENTERING_WORLD") + nameplates:RegisterEvent("PLAYER_TARGET_CHANGED") + nameplates:RegisterEvent("UNIT_COMBO_POINTS") + nameplates:RegisterEvent("PLAYER_COMBO_POINTS") + nameplates:RegisterEvent("UNIT_AURA") + + nameplates:SetScript("OnEvent", function() + if event == "PLAYER_ENTERING_WORLD" then + this:SetGameVariables() + else + this.eventcache = true + end + end) + + nameplates:SetScript("OnUpdate", function() + -- propagate events to all nameplates + if this.eventcache then + this.eventcache = nil + for plate in pairs(registry) do + plate.eventcache = true + end + end + + -- detect new nameplates + parentcount = WorldFrame:GetNumChildren() + if initialized < parentcount then + childs = { WorldFrame:GetChildren() } + for i = initialized + 1, parentcount do + plate = childs[i] + if IsNamePlate(plate) and not registry[plate] then + nameplates.OnCreate(plate) + registry[plate] = plate + end + end + + initialized = parentcount + end + end) + + -- combat tracker + nameplates.combat = CreateFrame("Frame") + nameplates.combat:RegisterEvent("PLAYER_ENTER_COMBAT") + nameplates.combat:RegisterEvent("PLAYER_LEAVE_COMBAT") + nameplates.combat:SetScript("OnEvent", function() + if event == "PLAYER_ENTER_COMBAT" then + this.inCombat = 1 + if PlayerFrame then PlayerFrame.inCombat = 1 end + elseif event == "PLAYER_LEAVE_COMBAT" then + this.inCombat = nil + if PlayerFrame then PlayerFrame.inCombat = nil end + end + end) + + nameplates.OnCreate = function(frame) + local parent = frame or this + platecount = platecount + 1 + platename = "pfNamePlate" .. platecount + + -- create pfUI nameplate overlay + local nameplate = CreateFrame("Button", platename, parent) + nameplate.platename = platename + nameplate:EnableMouse(0) + nameplate.parent = parent + nameplate.cache = {} + nameplate.UnitDebuff = PlateUnitDebuff + nameplate.CacheDebuffs = PlateCacheDebuffs + nameplate.original = {} + + -- create shortcuts for all known elements and disable them + nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren() + DisableObject(nameplate.original.healthbar) + DisableObject(nameplate.original.castbar) + + for i, object in pairs({parent:GetRegions()}) do + if NAMEPLATE_OBJECTORDER[i] and NAMEPLATE_OBJECTORDER[i] == "raidicon" then + nameplate[NAMEPLATE_OBJECTORDER[i]] = object + elseif NAMEPLATE_OBJECTORDER[i] then + nameplate.original[NAMEPLATE_OBJECTORDER[i]] = object + DisableObject(object) + else + DisableObject(object) + end + end + + HookScript(nameplate.original.healthbar, "OnValueChanged", nameplates.OnValueChanged) + + -- adjust sizes and scaling of the nameplate + nameplate:SetScale(UIParent:GetScale()) + + nameplate.health = CreateFrame("StatusBar", nil, nameplate) + nameplate.health:SetFrameLevel(4) -- keep above glow + nameplate.health.text = nameplate.health:CreateFontString(nil, "OVERLAY", "GameFontNormal") + nameplate.health.text:SetAllPoints() + nameplate.health.text:SetTextColor(1,1,1,1) + + nameplate.name = nameplate:CreateFontString(nil, "OVERLAY") + + nameplate.glow = nameplate:CreateTexture(nil, "BACKGROUND") + nameplate.glow:SetPoint("CENTER", nameplate.health, "CENTER", 0, 0) + nameplate.glow:SetTexture(pfUI.media["img:dot"]) + nameplate.glow:Hide() + + nameplate.guild = nameplate:CreateFontString(nil, "OVERLAY") + nameplate.guild:SetPoint("BOTTOM", nameplate.health, "BOTTOM", 0, 0) + + nameplate.level = nameplate:CreateFontString(nil, "OVERLAY") + + nameplate.raidicon:SetParent(nameplate.health) + nameplate.raidicon:SetDrawLayer("OVERLAY") + nameplate.raidicon:SetTexture(pfUI.media["img:raidicons"]) + + nameplate.totem = CreateFrame("Frame", nil, nameplate) + nameplate.totem:SetPoint("CENTER", nameplate, "CENTER", 0, 0) + nameplate.totem:SetHeight(32) + nameplate.totem:SetWidth(32) + nameplate.totem.icon = nameplate.totem:CreateTexture(nil, "OVERLAY") + nameplate.totem.icon:SetTexCoord(.078, .92, .079, .937) + nameplate.totem.icon:SetAllPoints() + CreateBackdrop(nameplate.totem) + + do -- debuffs + nameplate.debuffs = {} + CreateDebuffIcon(nameplate, 1) + end + + do -- combopoints + local combopoints = { } + for i = 1, 5 do + combopoints[i] = CreateFrame("Frame", nil, nameplate) + combopoints[i]:Hide() + combopoints[i]:SetFrameLevel(8) + combopoints[i].tex = combopoints[i]:CreateTexture("OVERLAY") + combopoints[i].tex:SetAllPoints() + + if i < 3 then + combopoints[i].tex:SetTexture(1, .3, .3, .75) + elseif i < 4 then + combopoints[i].tex:SetTexture(1, 1, .3, .75) + else + combopoints[i].tex:SetTexture(.3, 1, .3, .75) + end + end + nameplate.combopoints = combopoints + end + + do -- castbar + local castbar = CreateFrame("StatusBar", nil, nameplate.health) + castbar:Hide() + + castbar:SetScript("OnShow", function() + if C.nameplates.debuffs["position"] == "BOTTOM" then + nameplate.debuffs[1]:SetPoint("TOPLEFT", this, "BOTTOMLEFT", 0, -4) + end + end) + + castbar:SetScript("OnHide", function() + if C.nameplates.debuffs["position"] == "BOTTOM" then + nameplate.debuffs[1]:SetPoint("TOPLEFT", this:GetParent(), "BOTTOMLEFT", 0, -4) + end + end) + + castbar.text = castbar:CreateFontString("Status", "DIALOG", "GameFontNormal") + castbar.text:SetPoint("RIGHT", castbar, "LEFT", -4, 0) + castbar.text:SetNonSpaceWrap(false) + castbar.text:SetTextColor(1,1,1,.5) + + castbar.spell = castbar:CreateFontString("Status", "DIALOG", "GameFontNormal") + castbar.spell:SetPoint("CENTER", castbar, "CENTER") + castbar.spell:SetNonSpaceWrap(false) + castbar.spell:SetTextColor(1,1,1,1) + + castbar.icon = CreateFrame("Frame", nil, castbar) + castbar.icon.tex = castbar.icon:CreateTexture(nil, "BORDER") + castbar.icon.tex:SetAllPoints() + + nameplate.castbar = castbar + end + + parent.nameplate = nameplate + HookScript(parent, "OnShow", nameplates.OnShow) + HookScript(parent, "OnUpdate", nameplates.OnUpdate) + + nameplates.OnConfigChange(parent) + nameplates.OnShow(parent) + end + + nameplates.OnConfigChange = function(frame) + local parent = frame + local nameplate = frame.nameplate + + local font = C.nameplates.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default + local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size + local font_style = C.nameplates.name.fontstyle + local glowr, glowg, glowb, glowa = GetStringColor(C.nameplates.glowcolor) + local hlr, hlg, hlb, hla = GetStringColor(C.nameplates.highlightcolor) + local hptexture = pfUI.media[C.nameplates.healthtexture] + local rawborder, default_border = GetBorderSize("nameplates") + + local plate_width = C.nameplates.width + 50 + local plate_height = C.nameplates.heighthealth + font_size + 5 + local plate_height_cast = C.nameplates.heighthealth + font_size + 5 + C.nameplates.heightcast + 5 + local combo_size = 5 + + local width = tonumber(C.nameplates.width) + local debuffsize = tonumber(C.nameplates.debuffsize) + local healthoffset = tonumber(C.nameplates.health.offset) + local orientation = C.nameplates.verticalhealth == "1" and "VERTICAL" or "HORIZONTAL" + + local c = combatstate -- load combat state colors + c.CASTING.r, c.CASTING.g, c.CASTING.b, c.CASTING.a = GetStringColor(C.nameplates.combatcasting) + c.THREAT.r, c.THREAT.g, c.THREAT.b, c.THREAT.a = GetStringColor(C.nameplates.combatthreat) + c.NOTHREAT.r, c.NOTHREAT.g, c.NOTHREAT.b, c.NOTHREAT.a = GetStringColor(C.nameplates.combatnothreat) + c.STUN.r, c.STUN.g, c.STUN.b, c.STUN.a = GetStringColor(C.nameplates.combatstun) + + -- Get name offset values from config + local nameOffsetX = tonumber(C.nameplates.nameoffsetx) + local nameOffsetY = tonumber(C.nameplates.nameoffsety) + local levelOffsetX = tonumber(C.nameplates.leveloffsetx) + local levelOffsetY = tonumber(C.nameplates.leveloffsety) + + nameplate:SetWidth(plate_width) + nameplate:SetHeight(plate_height) + nameplate:SetPoint("TOP", parent, "TOP", 0, 0) + + + nameplate.health:SetOrientation(orientation) + nameplate.health:SetPoint("TOP", nameplate, "BOTTOM", 0, healthoffset) + nameplate.health:SetStatusBarTexture(hptexture) + nameplate.health:SetWidth(C.nameplates.width) + nameplate.health:SetHeight(C.nameplates.heighthealth) + nameplate.health.hlr, nameplate.health.hlg, nameplate.health.hlb, nameplate.health.hla = hlr, hlg, hlb, hla + + nameplate.name:SetFont(font, font_size, font_style) + nameplate.name:SetPoint("TOP", nameplate.health, "TOP", nameOffsetX, nameOffsetY) + + CreateBackdrop(nameplate.health, default_border) + + nameplate.health.text:SetFont(font, font_size - 2, "OUTLINE") + nameplate.health.text:SetJustifyH(C.nameplates.hptextpos) + + nameplate.guild:SetFont(font, font_size, font_style) + nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) + + + nameplate.glow:SetWidth(C.nameplates.width + 60) + nameplate.glow:SetHeight(C.nameplates.heighthealth + 30) + nameplate.glow:SetVertexColor(glowr, glowg, glowb, glowa) + + nameplate.raidicon:ClearAllPoints() + nameplate.raidicon:SetPoint(C.nameplates.raidiconpos, nameplate.health, C.nameplates.raidiconpos, C.nameplates.raidiconoffx, C.nameplates.raidiconoffy) + nameplate.level:SetFont(font, font_size, font_style) + DEFAULT_CHAT_FRAME:AddMessage("Level offset: "..tostring(levelOffsetX).." "..tostring(levelOffsetY)) + + nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) + + nameplate.raidicon:SetWidth(C.nameplates.raidiconsize) + nameplate.raidicon:SetHeight(C.nameplates.raidiconsize) + + for i=1,16 do + UpdateDebuffConfig(nameplate, i) + end + + for i=1,5 do + nameplate.combopoints[i]:SetWidth(combo_size) + nameplate.combopoints[i]:SetHeight(combo_size) + nameplate.combopoints[i]:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", -(i-1)*(combo_size+default_border*3), -default_border*3) + CreateBackdrop(nameplate.combopoints[i], default_border) + end + + nameplate.castbar:SetPoint("TOPLEFT", nameplate.health, "BOTTOMLEFT", 0, -default_border*3) + nameplate.castbar:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", 0, -default_border*3) + nameplate.castbar:SetHeight(C.nameplates.heightcast) + nameplate.castbar:SetStatusBarTexture(hptexture) + nameplate.castbar:SetStatusBarColor(.9,.8,0,1) + CreateBackdrop(nameplate.castbar, default_border) + + nameplate.castbar.text:SetFont(font, font_size, "OUTLINE") + nameplate.castbar.spell:SetFont(font, font_size, "OUTLINE") + nameplate.castbar.icon:SetPoint("BOTTOMLEFT", nameplate.castbar, "BOTTOMRIGHT", default_border*3, 0) + nameplate.castbar.icon:SetPoint("TOPLEFT", nameplate.health, "TOPRIGHT", default_border*3, 0) + nameplate.castbar.icon:SetWidth(C.nameplates.heightcast + default_border*3 + C.nameplates.heighthealth) + CreateBackdrop(nameplate.castbar.icon, default_border) + + nameplates:OnDataChanged(nameplate) + end + + nameplates.OnValueChanged = function(arg1) + nameplates:OnDataChanged(this:GetParent().nameplate) + end + + nameplates.OnDataChanged = function(self, plate) + local visible = plate:IsVisible() + local hp = plate.original.healthbar:GetValue() + local hpmin, hpmax = plate.original.healthbar:GetMinMaxValues() + local name = plate.original.name:GetText() + local level = plate.original.level:IsShown() and plate.original.level:GetObjectType() == "FontString" and tonumber(plate.original.level:GetText()) or "??" + local class, ulevel, elite, player, guild = GetUnitData(name, true) + local target = plate.istarget + local mouseover = UnitExists("mouseover") and plate.original.glow:IsShown() or nil + local unitstr = target and "target" or mouseover and "mouseover" or nil + local red, green, blue = plate.original.healthbar:GetStatusBarColor() + local unittype = GetUnitType(red, green, blue) or "ENEMY_NPC" + local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size + + -- use superwow unit guid as unitstr if possible + if superwow_active and not unitstr then + unitstr = plate.parent:GetName(1) + end + + -- ignore players with npc names if plate level is lower than player level + if ulevel and ulevel > (level == "??" and -1 or level) then player = nil end + + -- cache name and reset unittype on change + if plate.cache.name ~= name then + plate.cache.name = name + plate.cache.player = nil + end + + -- read and cache unittype + if plate.cache.player then + -- overwrite unittype from cache if existing + player = plate.cache.player == "PLAYER" and true or nil + elseif unitstr then + -- read unit type while unitstr is set + plate.cache.player = UnitIsPlayer(unitstr) and "PLAYER" or "NPC" + end + + if player and unittype == "ENEMY_NPC" then unittype = "ENEMY_PLAYER" end + elite = plate.original.levelicon:IsShown() and not player and "boss" or elite + if not class then plate.wait_for_scan = true end + + -- skip data updates on invisible frames + if not visible then return end + + -- target event sometimes fires too quickly, where nameplate identifiers are not + -- yet updated. So while being inside this event, we cannot trust the unitstr. + if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end + + -- remove unitstr on unit name mismatch + if unitstr and UnitName(unitstr) ~= name then unitstr = nil end + + -- use mobhealth values if addon is running + if (MobHealth3 or MobHealthFrame) and target and name == UnitName('target') and MobHealth_GetTargetCurHP() then + hp = MobHealth_GetTargetCurHP() > 0 and MobHealth_GetTargetCurHP() or hp + hpmax = MobHealth_GetTargetMaxHP() > 0 and MobHealth_GetTargetMaxHP() or hpmax + end + + -- always make sure to keep plate visible + plate:Show() + + if target and C.nameplates.targetglow == "1" then + plate.glow:Show() else plate.glow:Hide() + end + + -- target indicator + if superwow_active and C.nameplates.outcombatstate == "1" then + local guid = plate.parent:GetName(1) or "" + + -- determine color based on combat state + local color = GetCombatStateColor(guid) + if not color then color = combatstate.NONE end + + -- set border color + plate.health.backdrop:SetBackdropBorderColor(color.r, color.g, color.b, color.a) + elseif target and C.nameplates.targethighlight == "1" then + plate.health.backdrop:SetBackdropBorderColor(plate.health.hlr, plate.health.hlg, plate.health.hlb, plate.health.hla) + elseif C.nameplates.outfriendlynpc == "1" and unittype == "FRIENDLY_NPC" then + plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) + elseif C.nameplates.outfriendly == "1" and unittype == "FRIENDLY_PLAYER" then + plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) + elseif C.nameplates.outneutral == "1" and strfind(unittype, "NEUTRAL") then + plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) + elseif C.nameplates.outenemy == "1" and strfind(unittype, "ENEMY") then + plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) + else + plate.health.backdrop:SetBackdropBorderColor(er,eg,eb,ea) + end + + -- hide frames according to the configuration + local TotemIcon = TotemPlate(name) + + if TotemIcon then + -- create totem icon + plate.totem.icon:SetTexture("Interface\\Icons\\" .. TotemIcon) + + plate.glow:Hide() + plate.level:Hide() + plate.name:Hide() + plate.health:Hide() + plate.guild:Hide() + plate.totem:Show() + elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then + plate.level:SetPoint("RIGHT", plate.name, "LEFT", -3, 0) + plate.name:SetParent(plate) + plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2)) + + plate.level:Show() + plate.name:Show() + plate.health:Hide() + if guild and C.nameplates.showguildname == "1" then + plate.glow:SetPoint("CENTER", plate.name, "CENTER", 0, -(font_size / 2) - 2) + else + plate.glow:SetPoint("CENTER", plate.name, "CENTER", 0, 0) + end + plate.totem:Hide() + else + plate.level:SetPoint("RIGHT", plate.health, "LEFT", -5, 0) + plate.name:SetParent(plate.health) + plate.guild:SetPoint("BOTTOM", plate.health, "BOTTOM", 0, -(font_size + 4)) + + plate.level:Show() + plate.name:Show() + plate.health:Show() + plate.glow:SetPoint("CENTER", plate.health, "CENTER", 0, 0) + plate.totem:Hide() + end + + plate.name:SetText(GetNameString(name)) + plate.level:SetText(string.format("%s%s", level, (elitestrings[elite] or ""))) + + if guild and C.nameplates.showguildname == "1" then + plate.guild:SetText(guild) + if guild == GetGuildInfo("player") then + plate.guild:SetTextColor(0, 0.9, 0, 1) + else + plate.guild:SetTextColor(0.8, 0.8, 0.8, 1) + end + plate.guild:Show() + else + plate.guild:Hide() + end + + plate.health:SetMinMaxValues(hpmin, hpmax) + plate.health:SetValue(hp) + + if C.nameplates.showhp == "1" then + local rhp, rhpmax, estimated + if hpmax > 100 or (round(hpmax/100*hp) ~= hp) then + rhp, rhpmax = hp, hpmax + elseif pfUI.libhealth and pfUI.libhealth.enabled then + rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(name,level,tonumber(hp),tonumber(hpmax)) + end + + local setting = C.nameplates.hptextformat + local hasdata = ( estimated or hpmax > 100 or (round(hpmax/100*hp) ~= hp) ) + + if setting == "curperc" and hasdata then + plate.health.text:SetText(string.format("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100))) + elseif setting == "cur" and hasdata then + plate.health.text:SetText(string.format("%s", Abbreviate(rhp))) + elseif setting == "curmax" and hasdata then + plate.health.text:SetText(string.format("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax))) + elseif setting == "curmaxs" and hasdata then + plate.health.text:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax))) + elseif setting == "curmaxperc" and hasdata then + plate.health.text:SetText(string.format("%s - %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))) + elseif setting == "curmaxpercs" and hasdata then + plate.health.text:SetText(string.format("%s / %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))) + elseif setting == "deficit" then + plate.health.text:SetText(string.format("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp))) + else -- "percent" as fallback + plate.health.text:SetText(string.format("%s%%", ceil(hp/hpmax*100))) + end + else + plate.health.text:SetText() + end + + local r, g, b, a = unpack(unitcolors[unittype]) + + if unittype == "ENEMY_PLAYER" and C.nameplates["enemyclassc"] == "1" and class and RAID_CLASS_COLORS[class] then + r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1 + elseif unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassc"] == "1" and class and RAID_CLASS_COLORS[class] then + r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1 + end + + if superwow_active and unitstr and UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then + r, g, b, a = .5, .5, .5, .8 + end + + if superwow_active and C.nameplates.barcombatstate == "1" then + local guid = plate.parent:GetName(1) or "" + local color = GetCombatStateColor(guid) + + if color then + r, g, b, a = color.r, color.g, color.b, color.a + end + end + + if r ~= plate.cache.r or g ~= plate.cache.g or b ~= plate.cache.b then + plate.health:SetStatusBarColor(r, g, b, a) + plate.cache.r, plate.cache.g, plate.cache.b = r, g, b + end + + if r + g + b ~= plate.cache.namecolor and unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassnamec"] == "1" and class and RAID_CLASS_COLORS[class] then + plate.name:SetTextColor(r, g, b, a) + plate.cache.namecolor = r + g + b + end + + -- update combopoints + for i=1, 5 do plate.combopoints[i]:Hide() end + if target and C.nameplates.cpdisplay == "1" then + for i=1, GetComboPoints("target") do plate.combopoints[i]:Show() end + end + + -- update debuffs + local index = 1 + + if C.nameplates["showdebuffs"] == "1" then + local verify = string.format("%s:%s", (name or ""), (level or "")) + + -- update cached debuffs + if C.nameplates["guessdebuffs"] == "1" and unitstr then + plate:CacheDebuffs(unitstr, verify) + end + + -- update all debuff icons + for i = 1, 16 do + local effect, rank, texture, stacks, dtype, duration, timeleft + + if unitstr and C.nameplates.selfdebuff == "1" then + effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, i) + elseif unitstr then + effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitDebuff(unitstr, i) + elseif plate.verify == verify then + effect, rank, texture, stacks, dtype, duration, timeleft = plate:UnitDebuff(i) + end + + if effect and texture and DebuffFilter(effect) then + if not plate.debuffs[index] then + CreateDebuffIcon(plate, index) + UpdateDebuffConfig(plate, index) + end + + plate.debuffs[index]:Show() + plate.debuffs[index].icon:SetTexture(texture) + plate.debuffs[index].icon:SetTexCoord(.078, .92, .079, .937) + + if stacks and stacks > 1 and C.nameplates.debuffs["showstacks"] == "1" then + plate.debuffs[index].stacks:SetText(stacks) + plate.debuffs[index].stacks:Show() + else + plate.debuffs[index].stacks:Hide() + end + + if duration and timeleft and debuffdurations then + plate.debuffs[index].cd:SetAlpha(0) + plate.debuffs[index].cd:Show() + CooldownFrame_SetTimer(plate.debuffs[index].cd, GetTime() + timeleft - duration, duration, 1) + end + + index = index + 1 + end + end + end + + -- hide remaining debuffs + for i = index, 16 do + if plate.debuffs[i] then + plate.debuffs[i]:Hide() + end + end + end + + nameplates.OnShow = function(frame) + local frame = frame or this + local nameplate = frame.nameplate + + nameplates:OnDataChanged(nameplate) + end + + nameplates.OnUpdate = function(frame) + local update + local frame = frame or this + local nameplate = frame.nameplate + local original = nameplate.original + local name = original.name:GetText() + local target = UnitExists("target") and frame:GetAlpha() == 1 or nil + local mouseover = UnitExists("mouseover") and original.glow:IsShown() or nil + local namefightcolor = C.nameplates.namefightcolor == "1" + + -- trigger queued event update + if nameplate.eventcache then + nameplates:OnDataChanged(nameplate) + nameplate.eventcache = nil + end + + -- reset strata cache on target change + if nameplate.istarget ~= target then + nameplate.target_strata = nil + end + + -- keep target nameplate above others + if target and nameplate.target_strata ~= 1 then + nameplate:SetFrameStrata("LOW") + nameplate.target_strata = 1 + elseif not target and nameplate.target_strata ~= 0 then + nameplate:SetFrameStrata("BACKGROUND") + nameplate.target_strata = 0 + end + + -- cache target value + nameplate.istarget = target + + -- set non-target plate alpha + if target or not UnitExists("target") then + nameplate:SetAlpha(1) + else + frame:SetAlpha(.95) + nameplate:SetAlpha(tonumber(C.nameplates.notargalpha)) + end + + -- queue update on visual target update + if nameplate.cache.target ~= target then + nameplate.cache.target = target + update = true + end + + -- queue update on visual mouseover update + if nameplate.cache.mouseover ~= mouseover then + nameplate.cache.mouseover = mouseover + update = true + end + + -- trigger update when unit was found + if nameplate.wait_for_scan and GetUnitData(name, true) then + nameplate.wait_for_scan = nil + update = true + end + + -- trigger update when name color changed + local r, g, b = original.name:GetTextColor() + if r + g + b ~= nameplate.cache.namecolor then + nameplate.cache.namecolor = r + g + b + + if namefightcolor then + if r > .9 and g < .2 and b < .2 then + nameplate.name:SetTextColor(1,0.4,0.2,1) -- infight + else + nameplate.name:SetTextColor(r,g,b,1) + end + else + nameplate.name:SetTextColor(1,1,1,1) + end + update = true + end + + -- trigger update when level color changed + local r, g, b = original.level:GetTextColor() + r, g, b = r + .3, g + .3, b + .3 + if r + g + b ~= nameplate.cache.levelcolor then + nameplate.cache.levelcolor = r + g + b + nameplate.level:SetTextColor(r,g,b,1) + update = true + end + + -- scan for debuff timeouts + if nameplate.debuffcache then + for id, data in pairs(nameplate.debuffcache) do + if ( not data.stop or data.stop < GetTime() ) and not data.empty then + data.empty = true + update = true + end + end + end + + -- use timer based updates + if not nameplate.tick or nameplate.tick < GetTime() then + update = true + end + + -- run full updates if required + if update then + nameplates:OnDataChanged(nameplate) + nameplate.tick = GetTime() + .5 + end + + -- target zoom + local w, h = nameplate.health:GetWidth(), nameplate.health:GetHeight() + if target and C.nameplates.targetzoom == "1" then + local zoomval = tonumber(C.nameplates.targetzoomval)+1 + local wc = tonumber(C.nameplates.width)*zoomval + local hc = tonumber(C.nameplates.heighthealth)*(zoomval*.9) + local animation = false + + if wc >= w then + wc = w*1.05 + nameplate.health:SetWidth(wc) + nameplate.health.zoomTransition = true + animation = true + end + + if hc >= h then + hc = h*1.05 + nameplate.health:SetHeight(hc) + nameplate.health.zoomTransition = true + animation = true + end + + if animation == false and not nameplate.health.zoomed then + nameplate.health:SetWidth(wc) + nameplate.health:SetHeight(hc) + nameplate.health.zoomTransition = nil + nameplate.health.zoomed = true + end + elseif nameplate.health.zoomed or nameplate.health.zoomTransition then + local wc = tonumber(C.nameplates.width) + local hc = tonumber(C.nameplates.heighthealth) + local animation = false + + if wc <= w then + wc = w*.95 + nameplate.health:SetWidth(wc) + animation = true + end + + if hc <= h then + hc = h*0.95 + nameplate.health:SetHeight(hc) + animation = true + end + + if animation == false then + nameplate.health:SetWidth(wc) + nameplate.health:SetHeight(hc) + nameplate.health.zoomTransition = nil + nameplate.health.zoomed = nil + end + end + + -- castbar update + if C.nameplates["showcastbar"] == "1" and ( C.nameplates["targetcastbar"] == "0" or target ) then + local channel, cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill + + -- detect cast or channel bars + cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(target and "target" or name) + if not cast then channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(target and "target" or name) end + + -- read enemy casts from SuperWoW if enabled + if superwow_active then + cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(nameplate.parent:GetName(1)) + if not cast then channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(nameplate.parent:GetName(1)) end + end + + if not cast and not channel then + nameplate.castbar:Hide() + elseif cast or channel then + local effect = cast or channel + local duration = endTime - startTime + local max = duration / 1000 + local cur = GetTime() - startTime / 1000 + + -- invert castbar values while channeling + if channel then cur = max + startTime/1000 - GetTime() end + + nameplate.castbar:SetMinMaxValues(0, duration/1000) + nameplate.castbar:SetValue(cur) + nameplate.castbar.text:SetText(round(cur,1)) + if C.nameplates.spellname == "1" then + nameplate.castbar.spell:SetText(effect) + else + nameplate.castbar.spell:SetText("") + end + nameplate.castbar:Show() + + if texture then + nameplate.castbar.icon.tex:SetTexture(texture) + nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9) + end + end + else + nameplate.castbar:Hide() + end + end + + -- set nameplate game settings + nameplates.SetGameVariables = function() + -- update visibility (hostile) + if C.nameplates["showhostile"] == "1" then + _G.NAMEPLATES_ON = true + ShowNameplates() + else + _G.NAMEPLATES_ON = nil + HideNameplates() + end + + -- update visibility (hostile) + if C.nameplates["showfriendly"] == "1" then + _G.FRIENDNAMEPLATES_ON = true + ShowFriendNameplates() + else + _G.FRIENDNAMEPLATES_ON = nil + HideFriendNameplates() + end + end + + nameplates:SetGameVariables() + + nameplates.UpdateConfig = function() + -- update debuff filters + DebuffFilterPopulate() + + -- update nameplate visibility + nameplates:SetGameVariables() + + -- apply all config changes + for plate in pairs(registry) do + nameplates.OnConfigChange(plate) + end + end + + if pfUI.client <= 11200 then + -- handle vanilla only settings + -- due to the secured lua api, those settings can't be applied to TBC and later. + local hookOnConfigChange = nameplates.OnConfigChange + nameplates.OnConfigChange = function(self) + hookOnConfigChange(self) + + local parent = self + local nameplate = self.nameplate + local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and nameplate or parent + + -- disable all clicks for now + parent:EnableMouse(false) + nameplate:EnableMouse(false) + + -- adjust vertical offset + if C.nameplates["vertical_offset"] ~= "0" then + nameplate:SetPoint("TOP", parent, "TOP", 0, tonumber(C.nameplates["vertical_offset"])) + end + + -- replace clickhandler + if C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0" then + plate:SetScript("OnClick", function() parent:Click() end) + end + + -- enable mouselook on rightbutton down + if C.nameplates["rightclick"] == "1" then + plate:SetScript("OnMouseDown", nameplates.mouselook.OnMouseDown) + else + plate:SetScript("OnMouseDown", nil) + end + end + + local hookOnDataChanged = nameplates.OnDataChanged + nameplates.OnDataChanged = function(self, nameplate) + hookOnDataChanged(self, nameplate) + + -- make sure to keep mouse events disabled on parent nameplate + if (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") then + nameplate.parent:EnableMouse(false) + end + end + + local hookOnUpdate = nameplates.OnUpdate + nameplates.OnUpdate = function(self) + -- initialize shortcut variables + local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and this.nameplate or this + local clickable = C.nameplates["clickthrough"] ~= "1" and true or false + + -- disable all click events + if not clickable then + this:EnableMouse(false) + this.nameplate:EnableMouse(false) + else + plate:EnableMouse(clickable) + end + + if C.nameplates["overlap"] == "1" then + if this:GetWidth() > 1 then + -- set parent to 1 pixel to have them overlap each other + this:SetWidth(1) + this:SetHeight(1) + end + else + if not this.nameplate.dwidth then + -- cache initial sizing value for comparison + this.nameplate.dwidth = floor(this.nameplate:GetWidth() * UIParent:GetScale()) + end + + if floor(this:GetWidth()) ~= this.nameplate.dwidth then + -- align parent plate to the actual size + this:SetWidth(this.nameplate:GetWidth() * UIParent:GetScale()) + this:SetHeight(this.nameplate:GetHeight() * UIParent:GetScale()) + end + end + + -- disable click events while spell is targeting + local mouseEnabled = this.nameplate:IsMouseEnabled() + if C.nameplates["clickthrough"] == "0" and C.nameplates["overlap"] == "1" and SpellIsTargeting() == mouseEnabled then + this.nameplate:EnableMouse(not mouseEnabled) + end + + hookOnUpdate(self) + end + + -- enable mouselook on rightbutton down + nameplates.mouselook = CreateFrame("Frame", nil, UIParent) + nameplates.mouselook.time = nil + nameplates.mouselook.frame = nil + nameplates.mouselook.OnMouseDown = function() + if arg1 and arg1 == "RightButton" then + MouselookStart() + + -- start detection of the rightclick emulation + nameplates.mouselook.time = GetTime() + nameplates.mouselook.frame = this + nameplates.mouselook:Show() + end + end + + nameplates.mouselook:SetScript("OnUpdate", function() + -- break here if nothing to do + if not this.time or not this.frame then + this:Hide() + return + end + + -- if threshold is reached (0.5 second) no click action will follow + if not IsMouselooking() and this.time + tonumber(C.nameplates["clickthreshold"]) < GetTime() then + this:Hide() + return + end + + -- run a usual nameplate rightclick action + if not IsMouselooking() then + this.frame:Click("LeftButton") + if UnitCanAttack("player", "target") and not nameplates.combat.inCombat then AttackTarget() end + this:Hide() + return + end + end) + end + + pfUI.nameplates = nameplates +end) From 3a92e53aba7cb1ca559df4481a4b5465a15e6b9d Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Sun, 8 Jun 2025 13:27:17 +0200 Subject: [PATCH 02/10] Refactor code structure for improved readability and maintainability --- modules/nameplates.lua | 2 - modules/nameplates_B.lua | 1236 -------------------------------------- 2 files changed, 1238 deletions(-) delete mode 100644 modules/nameplates_B.lua diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 3ef1b96ff..993663d40 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -534,7 +534,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.name:SetFont(font, font_size, font_style) nameplate.name:SetPoint("TOP", nameplate.health, "TOP", nameOffsetX, nameOffsetY) - DEFAULT_CHAT_FRAME:AddMessage("pfUI: Setting level offset to " .. levelOffsetX .. ", " .. levelOffsetY) nameplate.level:SetFont(font, font_size, font_style) nameplate.level:ClearAllPoints() nameplate.level:SetPoint("TOP", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) @@ -690,7 +689,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () plate.guild:Hide() plate.totem:Show() elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then - DEFAULT_CHAT_FRAME:AddMessage("pfUI Hideplate: Setting level offset to -5, 0") plate.name:SetParent(plate) plate.level:SetParent(plate) plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2)) diff --git a/modules/nameplates_B.lua b/modules/nameplates_B.lua deleted file mode 100644 index efb62c229..000000000 --- a/modules/nameplates_B.lua +++ /dev/null @@ -1,1236 +0,0 @@ -pfUI:RegisterModule("nameplates", "vanilla:tbc", function () - -- disable original castbars - pcall(SetCVar, "ShowVKeyCastbar", 0) - - local unitcolors = { - ["ENEMY_NPC"] = { .9, .2, .3, .8 }, - ["NEUTRAL_NPC"] = { 1, 1, .3, .8 }, - ["FRIENDLY_NPC"] = { .6, 1, 0, .8 }, - ["ENEMY_PLAYER"] = { .9, .2, .3, .8 }, - ["FRIENDLY_PLAYER"] = { .2, .6, 1, .8 } - } - - local combatstate = { - -- gets overwritten by user config - ["NOTHREAT"] = { r = .7, g = .7, b = .2, a = 1 }, - ["THREAT"] = { r = .7, g = .2, b = .2, a = 1 }, - ["CASTING"] = { r = .7, g = .2, b = .7, a = 1 }, - ["STUN"] = { r = .2, g = .7, b = .7, a = 1 }, - ["NONE"] = { r = .2, g = .2, b = .2, a = 1 }, - } - - local elitestrings = { - ["elite"] = "+", - ["rareelite"] = "R+", - ["rare"] = "R", - ["boss"] = "B" - } - - -- catch all nameplates - local childs, regions, plate - local initialized = 0 - local parentcount = 0 - local platecount = 0 - local registry = {} - local debuffdurations = C.appearance.cd.debuffs == "1" and true or nil - - -- cache default border color - local er, eg, eb, ea = GetStringColor(pfUI_config.appearance.border.color) - - local function GetCombatStateColor(guid) - local target = guid.."target" - local color = false - - if UnitAffectingCombat("player") and UnitAffectingCombat(guid) and not UnitCanAssist("player", guid) then - if C.nameplates.ccombatcasting == "1" and (UnitCastingInfo(guid) or UnitChannelInfo(guid)) then - color = combatstate.CASTING - elseif C.nameplates.ccombatthreat == "1" and UnitIsUnit(target, "player") then - color = combatstate.THREAT - elseif C.nameplates.ccombatnothreat == "1" and UnitExists(target) then - color = combatstate.NOTHREAT - elseif C.nameplates.ccombatstun == "1" and not UnitExists(target) and not UnitIsPlayer(guid) then - color = combatstate.STUN - end - end - - return color - end - - local function DoNothing() - return - end - - local function IsNamePlate(frame) - if frame:GetObjectType() ~= NAMEPLATE_FRAMETYPE then return nil end - regions = plate:GetRegions() - - if not regions then return nil end - if not regions.GetObjectType then return nil end - if not regions.GetTexture then return nil end - - if regions:GetObjectType() ~= "Texture" then return nil end - return regions:GetTexture() == "Interface\\Tooltips\\Nameplate-Border" or nil - end - - local function DisableObject(object) - if not object then return end - if not object.GetObjectType then return end - - local otype = object:GetObjectType() - - if otype == "Texture" then - object:SetTexture("") - object:SetTexCoord(0, 0, 0, 0) - elseif otype == "FontString" then - object:SetWidth(0.001) - elseif otype == "StatusBar" then - object:SetStatusBarTexture("") - end - end - - local function TotemPlate(name) - if C.nameplates.totemicons == "1" then - for totem, icon in pairs(L["totems"]) do - if string.find(name, totem) then return icon end - end - end - end - - local function HidePlate(unittype, name, fullhp, target) - -- keep some plates always visible according to config - if C.nameplates.fullhealth == "1" and not fullhp then return nil end - if C.nameplates.target == "1" and target then return nil end - - -- return true when something needs to be hidden - if C.nameplates.enemynpc == "1" and unittype == "ENEMY_NPC" then - return true - elseif C.nameplates.enemyplayer == "1" and unittype == "ENEMY_PLAYER" then - return true - elseif C.nameplates.neutralnpc == "1" and unittype == "NEUTRAL_NPC" then - return true - elseif C.nameplates.friendlynpc == "1" and unittype == "FRIENDLY_NPC" then - return true - elseif C.nameplates.friendlyplayer == "1" and unittype == "FRIENDLY_PLAYER" then - return true - elseif C.nameplates.critters == "1" and unittype == "NEUTRAL_NPC" then - for i, critter in pairs(L["critters"]) do - if string.lower(name) == string.lower(critter) then return true end - end - elseif C.nameplates.totems == "1" then - for totem in pairs(L["totems"]) do - if string.find(name, totem) then return true end - end - end - - -- nothing to hide - return nil - end - - local function abbrevname(t) - return string.sub(t,1,1)..". " - end - - local function GetNameString(name) - local abbrev = pfUI_config.unitframes.abbrevname == "1" or nil - local size = 20 - - -- first try to only abbreviate the first word - if abbrev and name and strlen(name) > size then - name = string.gsub(name, "^(%S+) ", abbrevname) - end - - -- abbreviate all if it still doesn't fit - if abbrev and name and strlen(name) > size then - name = string.gsub(name, "(%S+) ", abbrevname) - end - - return name - end - - - local function GetUnitType(red, green, blue) - if red > .9 and green < .2 and blue < .2 then - return "ENEMY_NPC" - elseif red > .9 and green > .9 and blue < .2 then - return "NEUTRAL_NPC" - elseif red < .2 and green < .2 and blue > 0.9 then - return "FRIENDLY_PLAYER" - elseif red < .2 and green > .9 and blue < .2 then - return "FRIENDLY_NPC" - end - end - - local filter, list, cache - local function DebuffFilterPopulate() - -- initialize variables - filter = C.nameplates["debuffs"]["filter"] - if filter == "none" then return end - list = C.nameplates["debuffs"][filter] - cache = {} - - -- populate list - for _, val in pairs({strsplit("#", list)}) do - cache[strlower(val)] = true - end - end - - local function DebuffFilter(effect) - if filter == "none" then return true end - if not cache then DebuffFilterPopulate() end - - if filter == "blacklist" and cache[strlower(effect)] then - return nil - elseif filter == "blacklist" then - return true - elseif filter == "whitelist" and cache[strlower(effect)] then - return true - elseif filter == "whitelist" then - return nil - end - end - - local function PlateCacheDebuffs(self, unitstr, verify) - if not self.debuffcache then self.debuffcache = {} end - - for id = 1, 16 do - local effect, _, texture, stacks, _, duration, timeleft - - if unitstr and C.nameplates.selfdebuff == "1" then - effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, id) - else - effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitDebuff(unitstr, id) - end - - if effect and timeleft and timeleft > 0 then - local start = GetTime() - ( (duration or 0) - ( timeleft or 0) ) - local stop = GetTime() + ( timeleft or 0 ) - self.debuffcache[id] = self.debuffcache[id] or {} - self.debuffcache[id].effect = effect - self.debuffcache[id].texture = texture - self.debuffcache[id].stacks = stacks - self.debuffcache[id].duration = duration or 0 - self.debuffcache[id].start = start - self.debuffcache[id].stop = stop - self.debuffcache[id].empty = nil - end - end - - self.verify = verify - end - - local function PlateUnitDebuff(self, id) - -- break on unknown data - if not self.debuffcache then return end - if not self.debuffcache[id] then return end - if not self.debuffcache[id].stop then return end - - -- break on timeout debuffs - if self.debuffcache[id].empty then return end - if self.debuffcache[id].stop < GetTime() then return end - - -- return cached debuff - local c = self.debuffcache[id] - return c.effect, c.rank, c.texture, c.stacks, c.dtype, c.duration, (c.stop - GetTime()) - end - - local function CreateDebuffIcon(plate, index) - plate.debuffs[index] = CreateFrame("Frame", plate.platename.."Debuff"..index, plate) - plate.debuffs[index]:Hide() - plate.debuffs[index]:SetFrameLevel(1) - - plate.debuffs[index].icon = plate.debuffs[index]:CreateTexture(nil, "BACKGROUND") - plate.debuffs[index].icon:SetTexture(.3,1,.8,1) - plate.debuffs[index].icon:SetAllPoints(plate.debuffs[index]) - - plate.debuffs[index].stacks = plate.debuffs[index]:CreateFontString(nil, "OVERLAY") - plate.debuffs[index].stacks:SetAllPoints(plate.debuffs[index]) - plate.debuffs[index].stacks:SetJustifyH("RIGHT") - plate.debuffs[index].stacks:SetJustifyV("BOTTOM") - plate.debuffs[index].stacks:SetTextColor(1,1,0) - - if pfUI.client <= 11200 then - -- create a fake animation frame on vanilla to improve performance - plate.debuffs[index].cd = CreateFrame("Frame", plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index]) - plate.debuffs[index].cd:SetScript("OnUpdate", CooldownFrame_OnUpdateModel) - plate.debuffs[index].cd.AdvanceTime = DoNothing - plate.debuffs[index].cd.SetSequence = DoNothing - plate.debuffs[index].cd.SetSequenceTime = DoNothing - else - -- use regular cooldown animation frames on burning crusade and later - plate.debuffs[index].cd = CreateFrame(COOLDOWN_FRAME_TYPE, plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate") - end - - plate.debuffs[index].cd.pfCooldownStyleAnimation = 0 - plate.debuffs[index].cd.pfCooldownType = "ALL" - end - - local function UpdateDebuffConfig(nameplate, i) - if not nameplate.debuffs[i] then return end - - -- update debuff positions - local width = tonumber(C.nameplates.width) - local debuffsize = tonumber(C.nameplates.debuffsize) - local debuffoffset = tonumber(C.nameplates.debuffoffset) - local limit = floor(width / debuffsize) - local font = C.nameplates.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default - local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size - local font_style = C.nameplates.name.fontstyle - - local aligna, alignb, offs, space - if C.nameplates.debuffs["position"] == "BOTTOM" then - aligna, alignb, offs, space = "TOPLEFT", "BOTTOMLEFT", -debuffoffset, -1 - else - aligna, alignb, offs, space = "BOTTOMLEFT", "TOPLEFT", debuffoffset, 1 - end - - nameplate.debuffs[i].stacks:SetFont(font, font_size, font_style) - nameplate.debuffs[i]:ClearAllPoints() - if i == 1 then - nameplate.debuffs[i]:SetPoint(aligna, nameplate.health, alignb, 0, offs) - elseif i <= limit then - nameplate.debuffs[i]:SetPoint("LEFT", nameplate.debuffs[i-1], "RIGHT", 1, 0) - elseif i > limit and limit > 0 then - nameplate.debuffs[i]:SetPoint(aligna, nameplate.debuffs[i-limit], alignb, 0, space) - end - - nameplate.debuffs[i]:SetWidth(tonumber(C.nameplates.debuffsize)) - nameplate.debuffs[i]:SetHeight(tonumber(C.nameplates.debuffsize)) - end - - -- create nameplate core - local nameplates = CreateFrame("Frame", "pfNameplates", UIParent) - nameplates:RegisterEvent("PLAYER_ENTERING_WORLD") - nameplates:RegisterEvent("PLAYER_TARGET_CHANGED") - nameplates:RegisterEvent("UNIT_COMBO_POINTS") - nameplates:RegisterEvent("PLAYER_COMBO_POINTS") - nameplates:RegisterEvent("UNIT_AURA") - - nameplates:SetScript("OnEvent", function() - if event == "PLAYER_ENTERING_WORLD" then - this:SetGameVariables() - else - this.eventcache = true - end - end) - - nameplates:SetScript("OnUpdate", function() - -- propagate events to all nameplates - if this.eventcache then - this.eventcache = nil - for plate in pairs(registry) do - plate.eventcache = true - end - end - - -- detect new nameplates - parentcount = WorldFrame:GetNumChildren() - if initialized < parentcount then - childs = { WorldFrame:GetChildren() } - for i = initialized + 1, parentcount do - plate = childs[i] - if IsNamePlate(plate) and not registry[plate] then - nameplates.OnCreate(plate) - registry[plate] = plate - end - end - - initialized = parentcount - end - end) - - -- combat tracker - nameplates.combat = CreateFrame("Frame") - nameplates.combat:RegisterEvent("PLAYER_ENTER_COMBAT") - nameplates.combat:RegisterEvent("PLAYER_LEAVE_COMBAT") - nameplates.combat:SetScript("OnEvent", function() - if event == "PLAYER_ENTER_COMBAT" then - this.inCombat = 1 - if PlayerFrame then PlayerFrame.inCombat = 1 end - elseif event == "PLAYER_LEAVE_COMBAT" then - this.inCombat = nil - if PlayerFrame then PlayerFrame.inCombat = nil end - end - end) - - nameplates.OnCreate = function(frame) - local parent = frame or this - platecount = platecount + 1 - platename = "pfNamePlate" .. platecount - - -- create pfUI nameplate overlay - local nameplate = CreateFrame("Button", platename, parent) - nameplate.platename = platename - nameplate:EnableMouse(0) - nameplate.parent = parent - nameplate.cache = {} - nameplate.UnitDebuff = PlateUnitDebuff - nameplate.CacheDebuffs = PlateCacheDebuffs - nameplate.original = {} - - -- create shortcuts for all known elements and disable them - nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren() - DisableObject(nameplate.original.healthbar) - DisableObject(nameplate.original.castbar) - - for i, object in pairs({parent:GetRegions()}) do - if NAMEPLATE_OBJECTORDER[i] and NAMEPLATE_OBJECTORDER[i] == "raidicon" then - nameplate[NAMEPLATE_OBJECTORDER[i]] = object - elseif NAMEPLATE_OBJECTORDER[i] then - nameplate.original[NAMEPLATE_OBJECTORDER[i]] = object - DisableObject(object) - else - DisableObject(object) - end - end - - HookScript(nameplate.original.healthbar, "OnValueChanged", nameplates.OnValueChanged) - - -- adjust sizes and scaling of the nameplate - nameplate:SetScale(UIParent:GetScale()) - - nameplate.health = CreateFrame("StatusBar", nil, nameplate) - nameplate.health:SetFrameLevel(4) -- keep above glow - nameplate.health.text = nameplate.health:CreateFontString(nil, "OVERLAY", "GameFontNormal") - nameplate.health.text:SetAllPoints() - nameplate.health.text:SetTextColor(1,1,1,1) - - nameplate.name = nameplate:CreateFontString(nil, "OVERLAY") - - nameplate.glow = nameplate:CreateTexture(nil, "BACKGROUND") - nameplate.glow:SetPoint("CENTER", nameplate.health, "CENTER", 0, 0) - nameplate.glow:SetTexture(pfUI.media["img:dot"]) - nameplate.glow:Hide() - - nameplate.guild = nameplate:CreateFontString(nil, "OVERLAY") - nameplate.guild:SetPoint("BOTTOM", nameplate.health, "BOTTOM", 0, 0) - - nameplate.level = nameplate:CreateFontString(nil, "OVERLAY") - - nameplate.raidicon:SetParent(nameplate.health) - nameplate.raidicon:SetDrawLayer("OVERLAY") - nameplate.raidicon:SetTexture(pfUI.media["img:raidicons"]) - - nameplate.totem = CreateFrame("Frame", nil, nameplate) - nameplate.totem:SetPoint("CENTER", nameplate, "CENTER", 0, 0) - nameplate.totem:SetHeight(32) - nameplate.totem:SetWidth(32) - nameplate.totem.icon = nameplate.totem:CreateTexture(nil, "OVERLAY") - nameplate.totem.icon:SetTexCoord(.078, .92, .079, .937) - nameplate.totem.icon:SetAllPoints() - CreateBackdrop(nameplate.totem) - - do -- debuffs - nameplate.debuffs = {} - CreateDebuffIcon(nameplate, 1) - end - - do -- combopoints - local combopoints = { } - for i = 1, 5 do - combopoints[i] = CreateFrame("Frame", nil, nameplate) - combopoints[i]:Hide() - combopoints[i]:SetFrameLevel(8) - combopoints[i].tex = combopoints[i]:CreateTexture("OVERLAY") - combopoints[i].tex:SetAllPoints() - - if i < 3 then - combopoints[i].tex:SetTexture(1, .3, .3, .75) - elseif i < 4 then - combopoints[i].tex:SetTexture(1, 1, .3, .75) - else - combopoints[i].tex:SetTexture(.3, 1, .3, .75) - end - end - nameplate.combopoints = combopoints - end - - do -- castbar - local castbar = CreateFrame("StatusBar", nil, nameplate.health) - castbar:Hide() - - castbar:SetScript("OnShow", function() - if C.nameplates.debuffs["position"] == "BOTTOM" then - nameplate.debuffs[1]:SetPoint("TOPLEFT", this, "BOTTOMLEFT", 0, -4) - end - end) - - castbar:SetScript("OnHide", function() - if C.nameplates.debuffs["position"] == "BOTTOM" then - nameplate.debuffs[1]:SetPoint("TOPLEFT", this:GetParent(), "BOTTOMLEFT", 0, -4) - end - end) - - castbar.text = castbar:CreateFontString("Status", "DIALOG", "GameFontNormal") - castbar.text:SetPoint("RIGHT", castbar, "LEFT", -4, 0) - castbar.text:SetNonSpaceWrap(false) - castbar.text:SetTextColor(1,1,1,.5) - - castbar.spell = castbar:CreateFontString("Status", "DIALOG", "GameFontNormal") - castbar.spell:SetPoint("CENTER", castbar, "CENTER") - castbar.spell:SetNonSpaceWrap(false) - castbar.spell:SetTextColor(1,1,1,1) - - castbar.icon = CreateFrame("Frame", nil, castbar) - castbar.icon.tex = castbar.icon:CreateTexture(nil, "BORDER") - castbar.icon.tex:SetAllPoints() - - nameplate.castbar = castbar - end - - parent.nameplate = nameplate - HookScript(parent, "OnShow", nameplates.OnShow) - HookScript(parent, "OnUpdate", nameplates.OnUpdate) - - nameplates.OnConfigChange(parent) - nameplates.OnShow(parent) - end - - nameplates.OnConfigChange = function(frame) - local parent = frame - local nameplate = frame.nameplate - - local font = C.nameplates.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default - local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size - local font_style = C.nameplates.name.fontstyle - local glowr, glowg, glowb, glowa = GetStringColor(C.nameplates.glowcolor) - local hlr, hlg, hlb, hla = GetStringColor(C.nameplates.highlightcolor) - local hptexture = pfUI.media[C.nameplates.healthtexture] - local rawborder, default_border = GetBorderSize("nameplates") - - local plate_width = C.nameplates.width + 50 - local plate_height = C.nameplates.heighthealth + font_size + 5 - local plate_height_cast = C.nameplates.heighthealth + font_size + 5 + C.nameplates.heightcast + 5 - local combo_size = 5 - - local width = tonumber(C.nameplates.width) - local debuffsize = tonumber(C.nameplates.debuffsize) - local healthoffset = tonumber(C.nameplates.health.offset) - local orientation = C.nameplates.verticalhealth == "1" and "VERTICAL" or "HORIZONTAL" - - local c = combatstate -- load combat state colors - c.CASTING.r, c.CASTING.g, c.CASTING.b, c.CASTING.a = GetStringColor(C.nameplates.combatcasting) - c.THREAT.r, c.THREAT.g, c.THREAT.b, c.THREAT.a = GetStringColor(C.nameplates.combatthreat) - c.NOTHREAT.r, c.NOTHREAT.g, c.NOTHREAT.b, c.NOTHREAT.a = GetStringColor(C.nameplates.combatnothreat) - c.STUN.r, c.STUN.g, c.STUN.b, c.STUN.a = GetStringColor(C.nameplates.combatstun) - - -- Get name offset values from config - local nameOffsetX = tonumber(C.nameplates.nameoffsetx) - local nameOffsetY = tonumber(C.nameplates.nameoffsety) - local levelOffsetX = tonumber(C.nameplates.leveloffsetx) - local levelOffsetY = tonumber(C.nameplates.leveloffsety) - - nameplate:SetWidth(plate_width) - nameplate:SetHeight(plate_height) - nameplate:SetPoint("TOP", parent, "TOP", 0, 0) - - - nameplate.health:SetOrientation(orientation) - nameplate.health:SetPoint("TOP", nameplate, "BOTTOM", 0, healthoffset) - nameplate.health:SetStatusBarTexture(hptexture) - nameplate.health:SetWidth(C.nameplates.width) - nameplate.health:SetHeight(C.nameplates.heighthealth) - nameplate.health.hlr, nameplate.health.hlg, nameplate.health.hlb, nameplate.health.hla = hlr, hlg, hlb, hla - - nameplate.name:SetFont(font, font_size, font_style) - nameplate.name:SetPoint("TOP", nameplate.health, "TOP", nameOffsetX, nameOffsetY) - - CreateBackdrop(nameplate.health, default_border) - - nameplate.health.text:SetFont(font, font_size - 2, "OUTLINE") - nameplate.health.text:SetJustifyH(C.nameplates.hptextpos) - - nameplate.guild:SetFont(font, font_size, font_style) - nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) - - - nameplate.glow:SetWidth(C.nameplates.width + 60) - nameplate.glow:SetHeight(C.nameplates.heighthealth + 30) - nameplate.glow:SetVertexColor(glowr, glowg, glowb, glowa) - - nameplate.raidicon:ClearAllPoints() - nameplate.raidicon:SetPoint(C.nameplates.raidiconpos, nameplate.health, C.nameplates.raidiconpos, C.nameplates.raidiconoffx, C.nameplates.raidiconoffy) - nameplate.level:SetFont(font, font_size, font_style) - DEFAULT_CHAT_FRAME:AddMessage("Level offset: "..tostring(levelOffsetX).." "..tostring(levelOffsetY)) - - nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) - - nameplate.raidicon:SetWidth(C.nameplates.raidiconsize) - nameplate.raidicon:SetHeight(C.nameplates.raidiconsize) - - for i=1,16 do - UpdateDebuffConfig(nameplate, i) - end - - for i=1,5 do - nameplate.combopoints[i]:SetWidth(combo_size) - nameplate.combopoints[i]:SetHeight(combo_size) - nameplate.combopoints[i]:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", -(i-1)*(combo_size+default_border*3), -default_border*3) - CreateBackdrop(nameplate.combopoints[i], default_border) - end - - nameplate.castbar:SetPoint("TOPLEFT", nameplate.health, "BOTTOMLEFT", 0, -default_border*3) - nameplate.castbar:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", 0, -default_border*3) - nameplate.castbar:SetHeight(C.nameplates.heightcast) - nameplate.castbar:SetStatusBarTexture(hptexture) - nameplate.castbar:SetStatusBarColor(.9,.8,0,1) - CreateBackdrop(nameplate.castbar, default_border) - - nameplate.castbar.text:SetFont(font, font_size, "OUTLINE") - nameplate.castbar.spell:SetFont(font, font_size, "OUTLINE") - nameplate.castbar.icon:SetPoint("BOTTOMLEFT", nameplate.castbar, "BOTTOMRIGHT", default_border*3, 0) - nameplate.castbar.icon:SetPoint("TOPLEFT", nameplate.health, "TOPRIGHT", default_border*3, 0) - nameplate.castbar.icon:SetWidth(C.nameplates.heightcast + default_border*3 + C.nameplates.heighthealth) - CreateBackdrop(nameplate.castbar.icon, default_border) - - nameplates:OnDataChanged(nameplate) - end - - nameplates.OnValueChanged = function(arg1) - nameplates:OnDataChanged(this:GetParent().nameplate) - end - - nameplates.OnDataChanged = function(self, plate) - local visible = plate:IsVisible() - local hp = plate.original.healthbar:GetValue() - local hpmin, hpmax = plate.original.healthbar:GetMinMaxValues() - local name = plate.original.name:GetText() - local level = plate.original.level:IsShown() and plate.original.level:GetObjectType() == "FontString" and tonumber(plate.original.level:GetText()) or "??" - local class, ulevel, elite, player, guild = GetUnitData(name, true) - local target = plate.istarget - local mouseover = UnitExists("mouseover") and plate.original.glow:IsShown() or nil - local unitstr = target and "target" or mouseover and "mouseover" or nil - local red, green, blue = plate.original.healthbar:GetStatusBarColor() - local unittype = GetUnitType(red, green, blue) or "ENEMY_NPC" - local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size - - -- use superwow unit guid as unitstr if possible - if superwow_active and not unitstr then - unitstr = plate.parent:GetName(1) - end - - -- ignore players with npc names if plate level is lower than player level - if ulevel and ulevel > (level == "??" and -1 or level) then player = nil end - - -- cache name and reset unittype on change - if plate.cache.name ~= name then - plate.cache.name = name - plate.cache.player = nil - end - - -- read and cache unittype - if plate.cache.player then - -- overwrite unittype from cache if existing - player = plate.cache.player == "PLAYER" and true or nil - elseif unitstr then - -- read unit type while unitstr is set - plate.cache.player = UnitIsPlayer(unitstr) and "PLAYER" or "NPC" - end - - if player and unittype == "ENEMY_NPC" then unittype = "ENEMY_PLAYER" end - elite = plate.original.levelicon:IsShown() and not player and "boss" or elite - if not class then plate.wait_for_scan = true end - - -- skip data updates on invisible frames - if not visible then return end - - -- target event sometimes fires too quickly, where nameplate identifiers are not - -- yet updated. So while being inside this event, we cannot trust the unitstr. - if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end - - -- remove unitstr on unit name mismatch - if unitstr and UnitName(unitstr) ~= name then unitstr = nil end - - -- use mobhealth values if addon is running - if (MobHealth3 or MobHealthFrame) and target and name == UnitName('target') and MobHealth_GetTargetCurHP() then - hp = MobHealth_GetTargetCurHP() > 0 and MobHealth_GetTargetCurHP() or hp - hpmax = MobHealth_GetTargetMaxHP() > 0 and MobHealth_GetTargetMaxHP() or hpmax - end - - -- always make sure to keep plate visible - plate:Show() - - if target and C.nameplates.targetglow == "1" then - plate.glow:Show() else plate.glow:Hide() - end - - -- target indicator - if superwow_active and C.nameplates.outcombatstate == "1" then - local guid = plate.parent:GetName(1) or "" - - -- determine color based on combat state - local color = GetCombatStateColor(guid) - if not color then color = combatstate.NONE end - - -- set border color - plate.health.backdrop:SetBackdropBorderColor(color.r, color.g, color.b, color.a) - elseif target and C.nameplates.targethighlight == "1" then - plate.health.backdrop:SetBackdropBorderColor(plate.health.hlr, plate.health.hlg, plate.health.hlb, plate.health.hla) - elseif C.nameplates.outfriendlynpc == "1" and unittype == "FRIENDLY_NPC" then - plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) - elseif C.nameplates.outfriendly == "1" and unittype == "FRIENDLY_PLAYER" then - plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) - elseif C.nameplates.outneutral == "1" and strfind(unittype, "NEUTRAL") then - plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) - elseif C.nameplates.outenemy == "1" and strfind(unittype, "ENEMY") then - plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) - else - plate.health.backdrop:SetBackdropBorderColor(er,eg,eb,ea) - end - - -- hide frames according to the configuration - local TotemIcon = TotemPlate(name) - - if TotemIcon then - -- create totem icon - plate.totem.icon:SetTexture("Interface\\Icons\\" .. TotemIcon) - - plate.glow:Hide() - plate.level:Hide() - plate.name:Hide() - plate.health:Hide() - plate.guild:Hide() - plate.totem:Show() - elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then - plate.level:SetPoint("RIGHT", plate.name, "LEFT", -3, 0) - plate.name:SetParent(plate) - plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2)) - - plate.level:Show() - plate.name:Show() - plate.health:Hide() - if guild and C.nameplates.showguildname == "1" then - plate.glow:SetPoint("CENTER", plate.name, "CENTER", 0, -(font_size / 2) - 2) - else - plate.glow:SetPoint("CENTER", plate.name, "CENTER", 0, 0) - end - plate.totem:Hide() - else - plate.level:SetPoint("RIGHT", plate.health, "LEFT", -5, 0) - plate.name:SetParent(plate.health) - plate.guild:SetPoint("BOTTOM", plate.health, "BOTTOM", 0, -(font_size + 4)) - - plate.level:Show() - plate.name:Show() - plate.health:Show() - plate.glow:SetPoint("CENTER", plate.health, "CENTER", 0, 0) - plate.totem:Hide() - end - - plate.name:SetText(GetNameString(name)) - plate.level:SetText(string.format("%s%s", level, (elitestrings[elite] or ""))) - - if guild and C.nameplates.showguildname == "1" then - plate.guild:SetText(guild) - if guild == GetGuildInfo("player") then - plate.guild:SetTextColor(0, 0.9, 0, 1) - else - plate.guild:SetTextColor(0.8, 0.8, 0.8, 1) - end - plate.guild:Show() - else - plate.guild:Hide() - end - - plate.health:SetMinMaxValues(hpmin, hpmax) - plate.health:SetValue(hp) - - if C.nameplates.showhp == "1" then - local rhp, rhpmax, estimated - if hpmax > 100 or (round(hpmax/100*hp) ~= hp) then - rhp, rhpmax = hp, hpmax - elseif pfUI.libhealth and pfUI.libhealth.enabled then - rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(name,level,tonumber(hp),tonumber(hpmax)) - end - - local setting = C.nameplates.hptextformat - local hasdata = ( estimated or hpmax > 100 or (round(hpmax/100*hp) ~= hp) ) - - if setting == "curperc" and hasdata then - plate.health.text:SetText(string.format("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100))) - elseif setting == "cur" and hasdata then - plate.health.text:SetText(string.format("%s", Abbreviate(rhp))) - elseif setting == "curmax" and hasdata then - plate.health.text:SetText(string.format("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax))) - elseif setting == "curmaxs" and hasdata then - plate.health.text:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax))) - elseif setting == "curmaxperc" and hasdata then - plate.health.text:SetText(string.format("%s - %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))) - elseif setting == "curmaxpercs" and hasdata then - plate.health.text:SetText(string.format("%s / %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))) - elseif setting == "deficit" then - plate.health.text:SetText(string.format("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp))) - else -- "percent" as fallback - plate.health.text:SetText(string.format("%s%%", ceil(hp/hpmax*100))) - end - else - plate.health.text:SetText() - end - - local r, g, b, a = unpack(unitcolors[unittype]) - - if unittype == "ENEMY_PLAYER" and C.nameplates["enemyclassc"] == "1" and class and RAID_CLASS_COLORS[class] then - r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1 - elseif unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassc"] == "1" and class and RAID_CLASS_COLORS[class] then - r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1 - end - - if superwow_active and unitstr and UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then - r, g, b, a = .5, .5, .5, .8 - end - - if superwow_active and C.nameplates.barcombatstate == "1" then - local guid = plate.parent:GetName(1) or "" - local color = GetCombatStateColor(guid) - - if color then - r, g, b, a = color.r, color.g, color.b, color.a - end - end - - if r ~= plate.cache.r or g ~= plate.cache.g or b ~= plate.cache.b then - plate.health:SetStatusBarColor(r, g, b, a) - plate.cache.r, plate.cache.g, plate.cache.b = r, g, b - end - - if r + g + b ~= plate.cache.namecolor and unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassnamec"] == "1" and class and RAID_CLASS_COLORS[class] then - plate.name:SetTextColor(r, g, b, a) - plate.cache.namecolor = r + g + b - end - - -- update combopoints - for i=1, 5 do plate.combopoints[i]:Hide() end - if target and C.nameplates.cpdisplay == "1" then - for i=1, GetComboPoints("target") do plate.combopoints[i]:Show() end - end - - -- update debuffs - local index = 1 - - if C.nameplates["showdebuffs"] == "1" then - local verify = string.format("%s:%s", (name or ""), (level or "")) - - -- update cached debuffs - if C.nameplates["guessdebuffs"] == "1" and unitstr then - plate:CacheDebuffs(unitstr, verify) - end - - -- update all debuff icons - for i = 1, 16 do - local effect, rank, texture, stacks, dtype, duration, timeleft - - if unitstr and C.nameplates.selfdebuff == "1" then - effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, i) - elseif unitstr then - effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitDebuff(unitstr, i) - elseif plate.verify == verify then - effect, rank, texture, stacks, dtype, duration, timeleft = plate:UnitDebuff(i) - end - - if effect and texture and DebuffFilter(effect) then - if not plate.debuffs[index] then - CreateDebuffIcon(plate, index) - UpdateDebuffConfig(plate, index) - end - - plate.debuffs[index]:Show() - plate.debuffs[index].icon:SetTexture(texture) - plate.debuffs[index].icon:SetTexCoord(.078, .92, .079, .937) - - if stacks and stacks > 1 and C.nameplates.debuffs["showstacks"] == "1" then - plate.debuffs[index].stacks:SetText(stacks) - plate.debuffs[index].stacks:Show() - else - plate.debuffs[index].stacks:Hide() - end - - if duration and timeleft and debuffdurations then - plate.debuffs[index].cd:SetAlpha(0) - plate.debuffs[index].cd:Show() - CooldownFrame_SetTimer(plate.debuffs[index].cd, GetTime() + timeleft - duration, duration, 1) - end - - index = index + 1 - end - end - end - - -- hide remaining debuffs - for i = index, 16 do - if plate.debuffs[i] then - plate.debuffs[i]:Hide() - end - end - end - - nameplates.OnShow = function(frame) - local frame = frame or this - local nameplate = frame.nameplate - - nameplates:OnDataChanged(nameplate) - end - - nameplates.OnUpdate = function(frame) - local update - local frame = frame or this - local nameplate = frame.nameplate - local original = nameplate.original - local name = original.name:GetText() - local target = UnitExists("target") and frame:GetAlpha() == 1 or nil - local mouseover = UnitExists("mouseover") and original.glow:IsShown() or nil - local namefightcolor = C.nameplates.namefightcolor == "1" - - -- trigger queued event update - if nameplate.eventcache then - nameplates:OnDataChanged(nameplate) - nameplate.eventcache = nil - end - - -- reset strata cache on target change - if nameplate.istarget ~= target then - nameplate.target_strata = nil - end - - -- keep target nameplate above others - if target and nameplate.target_strata ~= 1 then - nameplate:SetFrameStrata("LOW") - nameplate.target_strata = 1 - elseif not target and nameplate.target_strata ~= 0 then - nameplate:SetFrameStrata("BACKGROUND") - nameplate.target_strata = 0 - end - - -- cache target value - nameplate.istarget = target - - -- set non-target plate alpha - if target or not UnitExists("target") then - nameplate:SetAlpha(1) - else - frame:SetAlpha(.95) - nameplate:SetAlpha(tonumber(C.nameplates.notargalpha)) - end - - -- queue update on visual target update - if nameplate.cache.target ~= target then - nameplate.cache.target = target - update = true - end - - -- queue update on visual mouseover update - if nameplate.cache.mouseover ~= mouseover then - nameplate.cache.mouseover = mouseover - update = true - end - - -- trigger update when unit was found - if nameplate.wait_for_scan and GetUnitData(name, true) then - nameplate.wait_for_scan = nil - update = true - end - - -- trigger update when name color changed - local r, g, b = original.name:GetTextColor() - if r + g + b ~= nameplate.cache.namecolor then - nameplate.cache.namecolor = r + g + b - - if namefightcolor then - if r > .9 and g < .2 and b < .2 then - nameplate.name:SetTextColor(1,0.4,0.2,1) -- infight - else - nameplate.name:SetTextColor(r,g,b,1) - end - else - nameplate.name:SetTextColor(1,1,1,1) - end - update = true - end - - -- trigger update when level color changed - local r, g, b = original.level:GetTextColor() - r, g, b = r + .3, g + .3, b + .3 - if r + g + b ~= nameplate.cache.levelcolor then - nameplate.cache.levelcolor = r + g + b - nameplate.level:SetTextColor(r,g,b,1) - update = true - end - - -- scan for debuff timeouts - if nameplate.debuffcache then - for id, data in pairs(nameplate.debuffcache) do - if ( not data.stop or data.stop < GetTime() ) and not data.empty then - data.empty = true - update = true - end - end - end - - -- use timer based updates - if not nameplate.tick or nameplate.tick < GetTime() then - update = true - end - - -- run full updates if required - if update then - nameplates:OnDataChanged(nameplate) - nameplate.tick = GetTime() + .5 - end - - -- target zoom - local w, h = nameplate.health:GetWidth(), nameplate.health:GetHeight() - if target and C.nameplates.targetzoom == "1" then - local zoomval = tonumber(C.nameplates.targetzoomval)+1 - local wc = tonumber(C.nameplates.width)*zoomval - local hc = tonumber(C.nameplates.heighthealth)*(zoomval*.9) - local animation = false - - if wc >= w then - wc = w*1.05 - nameplate.health:SetWidth(wc) - nameplate.health.zoomTransition = true - animation = true - end - - if hc >= h then - hc = h*1.05 - nameplate.health:SetHeight(hc) - nameplate.health.zoomTransition = true - animation = true - end - - if animation == false and not nameplate.health.zoomed then - nameplate.health:SetWidth(wc) - nameplate.health:SetHeight(hc) - nameplate.health.zoomTransition = nil - nameplate.health.zoomed = true - end - elseif nameplate.health.zoomed or nameplate.health.zoomTransition then - local wc = tonumber(C.nameplates.width) - local hc = tonumber(C.nameplates.heighthealth) - local animation = false - - if wc <= w then - wc = w*.95 - nameplate.health:SetWidth(wc) - animation = true - end - - if hc <= h then - hc = h*0.95 - nameplate.health:SetHeight(hc) - animation = true - end - - if animation == false then - nameplate.health:SetWidth(wc) - nameplate.health:SetHeight(hc) - nameplate.health.zoomTransition = nil - nameplate.health.zoomed = nil - end - end - - -- castbar update - if C.nameplates["showcastbar"] == "1" and ( C.nameplates["targetcastbar"] == "0" or target ) then - local channel, cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill - - -- detect cast or channel bars - cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(target and "target" or name) - if not cast then channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(target and "target" or name) end - - -- read enemy casts from SuperWoW if enabled - if superwow_active then - cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(nameplate.parent:GetName(1)) - if not cast then channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(nameplate.parent:GetName(1)) end - end - - if not cast and not channel then - nameplate.castbar:Hide() - elseif cast or channel then - local effect = cast or channel - local duration = endTime - startTime - local max = duration / 1000 - local cur = GetTime() - startTime / 1000 - - -- invert castbar values while channeling - if channel then cur = max + startTime/1000 - GetTime() end - - nameplate.castbar:SetMinMaxValues(0, duration/1000) - nameplate.castbar:SetValue(cur) - nameplate.castbar.text:SetText(round(cur,1)) - if C.nameplates.spellname == "1" then - nameplate.castbar.spell:SetText(effect) - else - nameplate.castbar.spell:SetText("") - end - nameplate.castbar:Show() - - if texture then - nameplate.castbar.icon.tex:SetTexture(texture) - nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9) - end - end - else - nameplate.castbar:Hide() - end - end - - -- set nameplate game settings - nameplates.SetGameVariables = function() - -- update visibility (hostile) - if C.nameplates["showhostile"] == "1" then - _G.NAMEPLATES_ON = true - ShowNameplates() - else - _G.NAMEPLATES_ON = nil - HideNameplates() - end - - -- update visibility (hostile) - if C.nameplates["showfriendly"] == "1" then - _G.FRIENDNAMEPLATES_ON = true - ShowFriendNameplates() - else - _G.FRIENDNAMEPLATES_ON = nil - HideFriendNameplates() - end - end - - nameplates:SetGameVariables() - - nameplates.UpdateConfig = function() - -- update debuff filters - DebuffFilterPopulate() - - -- update nameplate visibility - nameplates:SetGameVariables() - - -- apply all config changes - for plate in pairs(registry) do - nameplates.OnConfigChange(plate) - end - end - - if pfUI.client <= 11200 then - -- handle vanilla only settings - -- due to the secured lua api, those settings can't be applied to TBC and later. - local hookOnConfigChange = nameplates.OnConfigChange - nameplates.OnConfigChange = function(self) - hookOnConfigChange(self) - - local parent = self - local nameplate = self.nameplate - local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and nameplate or parent - - -- disable all clicks for now - parent:EnableMouse(false) - nameplate:EnableMouse(false) - - -- adjust vertical offset - if C.nameplates["vertical_offset"] ~= "0" then - nameplate:SetPoint("TOP", parent, "TOP", 0, tonumber(C.nameplates["vertical_offset"])) - end - - -- replace clickhandler - if C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0" then - plate:SetScript("OnClick", function() parent:Click() end) - end - - -- enable mouselook on rightbutton down - if C.nameplates["rightclick"] == "1" then - plate:SetScript("OnMouseDown", nameplates.mouselook.OnMouseDown) - else - plate:SetScript("OnMouseDown", nil) - end - end - - local hookOnDataChanged = nameplates.OnDataChanged - nameplates.OnDataChanged = function(self, nameplate) - hookOnDataChanged(self, nameplate) - - -- make sure to keep mouse events disabled on parent nameplate - if (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") then - nameplate.parent:EnableMouse(false) - end - end - - local hookOnUpdate = nameplates.OnUpdate - nameplates.OnUpdate = function(self) - -- initialize shortcut variables - local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and this.nameplate or this - local clickable = C.nameplates["clickthrough"] ~= "1" and true or false - - -- disable all click events - if not clickable then - this:EnableMouse(false) - this.nameplate:EnableMouse(false) - else - plate:EnableMouse(clickable) - end - - if C.nameplates["overlap"] == "1" then - if this:GetWidth() > 1 then - -- set parent to 1 pixel to have them overlap each other - this:SetWidth(1) - this:SetHeight(1) - end - else - if not this.nameplate.dwidth then - -- cache initial sizing value for comparison - this.nameplate.dwidth = floor(this.nameplate:GetWidth() * UIParent:GetScale()) - end - - if floor(this:GetWidth()) ~= this.nameplate.dwidth then - -- align parent plate to the actual size - this:SetWidth(this.nameplate:GetWidth() * UIParent:GetScale()) - this:SetHeight(this.nameplate:GetHeight() * UIParent:GetScale()) - end - end - - -- disable click events while spell is targeting - local mouseEnabled = this.nameplate:IsMouseEnabled() - if C.nameplates["clickthrough"] == "0" and C.nameplates["overlap"] == "1" and SpellIsTargeting() == mouseEnabled then - this.nameplate:EnableMouse(not mouseEnabled) - end - - hookOnUpdate(self) - end - - -- enable mouselook on rightbutton down - nameplates.mouselook = CreateFrame("Frame", nil, UIParent) - nameplates.mouselook.time = nil - nameplates.mouselook.frame = nil - nameplates.mouselook.OnMouseDown = function() - if arg1 and arg1 == "RightButton" then - MouselookStart() - - -- start detection of the rightclick emulation - nameplates.mouselook.time = GetTime() - nameplates.mouselook.frame = this - nameplates.mouselook:Show() - end - end - - nameplates.mouselook:SetScript("OnUpdate", function() - -- break here if nothing to do - if not this.time or not this.frame then - this:Hide() - return - end - - -- if threshold is reached (0.5 second) no click action will follow - if not IsMouselooking() and this.time + tonumber(C.nameplates["clickthreshold"]) < GetTime() then - this:Hide() - return - end - - -- run a usual nameplate rightclick action - if not IsMouselooking() then - this.frame:Click("LeftButton") - if UnitCanAttack("player", "target") and not nameplates.combat.inCombat then AttackTarget() end - this:Hide() - return - end - end) - end - - pfUI.nameplates = nameplates -end) From 5c4e3fe5e173244cca2d301333a96b20438a7df0 Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Wed, 11 Jun 2025 21:21:06 +0200 Subject: [PATCH 03/10] Remove unnecessary whitespace in GUI module for cleaner code --- modules/gui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gui.lua b/modules/gui.lua index eb80aae42..1035ab12c 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1560,7 +1560,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () CreateConfig(U["infight"], T["Enable Low Health Glow Effects On Screen Edges"], C.appearance.infight, "health", "checkbox") CreateConfig(U["infight"], T["Screen Edge Glow Intensity"], C.appearance.infight, "intensity", "dropdown", pfUI.gui.dropdowns.glowintensity) end) - + CreateGUIEntry(T["Settings"], T["Cooldown"], function() CreateConfig(U["buff"], T["Show Milliseconds When Timer Runs Out"], C.appearance.cd, "milliseconds", "checkbox") CreateConfig(nil, T["Cooldown Color (Less than 3 Sec)"], C.appearance.cd, "lowcolor", "color") From dbfe37c1e8ac32b1812c19f50145232f9872190f Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Wed, 11 Jun 2025 21:23:12 +0200 Subject: [PATCH 04/10] Remove unused font file 'Francois.ttf' --- fonts/Francois.ttf | Bin 60580 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fonts/Francois.ttf diff --git a/fonts/Francois.ttf b/fonts/Francois.ttf deleted file mode 100644 index 98e0ffa8c47b00f6f1eeb11fdaf396bb54d223c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60580 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 bzlZSTA}FP{J)InD-23O7a^H4j{|)~a{~H<4 From 8672418552edd115e14798d64cad797ded4890de Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Wed, 11 Jun 2025 21:24:12 +0200 Subject: [PATCH 05/10] Remove unnecessary blank lines in nameplates module for cleaner code --- modules/nameplates.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 993663d40..92cf518b0 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -550,8 +550,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.raidicon:ClearAllPoints() nameplate.raidicon:SetPoint(C.nameplates.raidiconpos, nameplate.health, C.nameplates.raidiconpos, C.nameplates.raidiconoffx, C.nameplates.raidiconoffy) - - nameplate.raidicon:SetWidth(C.nameplates.raidiconsize) nameplate.raidicon:SetHeight(C.nameplates.raidiconsize) From eceb26929dbf4a0e89ba6e4adea841765dcd2fbb Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Sun, 8 Jun 2025 13:21:43 +0200 Subject: [PATCH 06/10] Added necessary changes to support name and level offset --- api/config.lua | 4 + env/translations_deDE.lua | 9 +- env/translations_enUS.lua | 8 +- env/translations_esES.lua | 9 +- env/translations_frFR.lua | 9 +- env/translations_koKR.lua | 9 +- env/translations_ruRU.lua | 9 +- env/translations_zhCN.lua | 9 +- env/translations_zhTW.lua | 9 +- fonts/Francois.ttf | Bin 0 -> 60580 bytes modules/gui.lua | 10 +- modules/nameplates.lua | 26 +- modules/nameplates_B.lua | 1236 +++++++++++++++++++++++++++++++++++++ 13 files changed, 1323 insertions(+), 24 deletions(-) create mode 100644 fonts/Francois.ttf create mode 100644 modules/nameplates_B.lua diff --git a/api/config.lua b/api/config.lua index 6f38f1962..32d099a90 100644 --- a/api/config.lua +++ b/api/config.lua @@ -747,6 +747,10 @@ function pfUI:LoadConfig() pfUI:UpdateConfig("nameplates", nil, "enemyclassc", "1") pfUI:UpdateConfig("nameplates", nil, "friendclassc", "1") pfUI:UpdateConfig("nameplates", nil, "friendclassnamec", "0") + pfUI:UpdateConfig("nameplates", nil, "nameoffsetx", "0") + pfUI:UpdateConfig("nameplates", nil, "nameoffsety", "0") + pfUI:UpdateConfig("nameplates", nil, "leveloffsetx", "-3") + pfUI:UpdateConfig("nameplates", nil, "leveloffsety", "0") pfUI:UpdateConfig("nameplates", nil, "raidiconsize", "16") pfUI:UpdateConfig("nameplates", nil, "raidiconpos", "CENTER") pfUI:UpdateConfig("nameplates", nil, "raidiconoffx", "0") diff --git a/env/translations_deDE.lua b/env/translations_deDE.lua index 683f2732c..8488f4439 100644 --- a/env/translations_deDE.lua +++ b/env/translations_deDE.lua @@ -511,8 +511,13 @@ pfUI_translation["deDE"] = { ["Name | Health Missing"] = nil, ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = nil, - ["Nameplates"] = nil, - ["Nameplate Width"] = nil, + ["Nameplates"] = nil, ["Nameplate Width"] = nil, + ["Name Position"] = "Name Position", + ["Name X-Offset"] = "Name X-Versatz", + ["Name Y-Offset"] = "Name Y-Versatz", + ["Level Position"] = "Level Position", + ["Level X-Offset"] = "Level X-Versatz", + ["Level Y-Offset"] = "Level Y-Versatz", ["Name (Short)"] = nil, ["Name (Short) | Health Missing"] = nil, ["Native"] = nil, diff --git a/env/translations_enUS.lua b/env/translations_enUS.lua index b57c92f05..9adf29007 100644 --- a/env/translations_enUS.lua +++ b/env/translations_enUS.lua @@ -513,8 +513,12 @@ pfUI_translation["enUS"] = { ["Nameplate Border Size"] = nil, ["Nameplates"] = nil, ["Nameplate Width"] = nil, - ["Name (Short)"] = nil, - ["Name (Short) | Health Missing"] = nil, + ["Name Position"]=nil, + ["Name X-Offset"] = nil, + ["Name Y-Offset"] = nil, + ["Level Position"]=nil, + ["Level X-Offset"] = nil, + ["Level Y-Offset"] = nil, ["Native"] = nil, ["Network Down"] = nil, ["Network Latency"] = nil, diff --git a/env/translations_esES.lua b/env/translations_esES.lua index 334bd90bd..24e40d6fc 100644 --- a/env/translations_esES.lua +++ b/env/translations_esES.lua @@ -511,8 +511,13 @@ pfUI_translation["esES"] = { ["Name | Health Missing"] = "Nombre | Falta de salud", ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = "Tamaño del borde de las placas de nombre", - ["Nameplates"] = "Placas identificativas", - ["Nameplate Width"] = "Ancho de las placas identificativas", + ["Nameplates"] = "Placas identificativas", ["Nameplate Width"] = "Ancho de las placas identificativas", + ["Name Position"] = "Posición del nombre", + ["Name X-Offset"] = "Desplazamiento X del nombre", + ["Name Y-Offset"] = "Desplazamiento Y del nombre", + ["Level Position"] = "Posición del nivel", + ["Level X-Offset"] = "Desplazamiento X del nivel", + ["Level Y-Offset"] = "Desplazamiento Y del nivel", ["Name (Short)"] = "Nombre (corto)", ["Name (Short) | Health Missing"] = "Nombre (corto) | Falta de salud", ["Native"] = "Nativo", diff --git a/env/translations_frFR.lua b/env/translations_frFR.lua index f5e417bbd..335ffd64b 100644 --- a/env/translations_frFR.lua +++ b/env/translations_frFR.lua @@ -511,8 +511,13 @@ pfUI_translation["frFR"] = { ["Name | Health Missing"] = "Nom | santé manquante", ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = "Taille de la bordure des barres de vie flottantes", - ["Nameplates"] = "Barres de vie flottantes", - ["Nameplate Width"] = "Largeur des barres de vie flottantes", + ["Nameplates"] = "Barres de vie flottantes", ["Nameplate Width"] = "Largeur des barres de vie flottantes", + ["Name Position"] = "Position du nom", + ["Name X-Offset"] = "Décalage X du nom", + ["Name Y-Offset"] = "Décalage Y du nom", + ["Level Position"] = "Position du niveau", + ["Level X-Offset"] = "Décalage X du niveau", + ["Level Y-Offset"] = "Décalage Y du niveau", ["Name (Short)"] = "Nom (Court)", ["Name (Short) | Health Missing"] = "Nom (court) | santé manquante", ["Native"] = "Native", diff --git a/env/translations_koKR.lua b/env/translations_koKR.lua index d264126d5..97a72d88b 100644 --- a/env/translations_koKR.lua +++ b/env/translations_koKR.lua @@ -511,8 +511,13 @@ pfUI_translation["koKR"] = { ["Name | Health Missing"] = nil, ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = nil, - ["Nameplates"] = "상바", - ["Nameplate Width"] = "상바 넓이", + ["Nameplates"] = "상바", ["Nameplate Width"] = "상바 넓이", + ["Name Position"] = "이름 위치", + ["Name X-Offset"] = "이름 X 오프셋", + ["Name Y-Offset"] = "이름 Y 오프셋", + ["Level Position"] = "레벨 위치", + ["Level X-Offset"] = "레벨 X 오프셋", + ["Level Y-Offset"] = "레벨 Y 오프셋", ["Name (Short)"] = nil, ["Name (Short) | Health Missing"] = nil, ["Native"] = nil, diff --git a/env/translations_ruRU.lua b/env/translations_ruRU.lua index 5eca8fd1e..2232618cc 100644 --- a/env/translations_ruRU.lua +++ b/env/translations_ruRU.lua @@ -511,8 +511,13 @@ pfUI_translation["ruRU"] = { ["Name | Health Missing"] = "Имя | Отсутствующее здоровье", ["Name (Linebreak) -Health Missing"] = "Имя (разрыв линии) -Отсутствует здоровье", ["Nameplate Border Size"] = "Размер границы индикатора здоровья", - ["Nameplates"] = "Индикаторы здоровья", - ["Nameplate Width"] = "Ширина индикатора здоровья", + ["Nameplates"] = "Индикаторы здоровья", ["Nameplate Width"] = "Ширина индикатора здоровья", + ["Name Position"] = "Позиция имени", + ["Name X-Offset"] = "Смещение имени по X", + ["Name Y-Offset"] = "Смещение имени по Y", + ["Level Position"] = "Позиция уровня", + ["Level X-Offset"] = "Смещение уровня по X", + ["Level Y-Offset"] = "Смещение уровня по Y", ["Name (Short)"] = "Короткое имя", ["Name (Short) | Health Missing"] = "Короткое имя | Отсутствующее здоровье", ["Native"] = "Родная", diff --git a/env/translations_zhCN.lua b/env/translations_zhCN.lua index 765382cc9..2d34056dd 100644 --- a/env/translations_zhCN.lua +++ b/env/translations_zhCN.lua @@ -511,8 +511,13 @@ pfUI_translation["zhCN"] = { ["Name | Health Missing"] = "名字 | 失去的生命值", ["Name (Linebreak) -Health Missing"] = "名字 (换行符) -失去的生命值", ["Nameplate Border Size"] = "姓名板边框尺寸", - ["Nameplates"] = "姓名板", - ["Nameplate Width"] = "姓名板宽度", + ["Nameplates"] = "姓名板", ["Nameplate Width"] = "姓名板宽度", + ["Name Position"] = "名字位置", + ["Name X-Offset"] = "名字水平偏移", + ["Name Y-Offset"] = "名字垂直偏移", + ["Level Position"] = "等级位置", + ["Level X-Offset"] = "等级水平偏移", + ["Level Y-Offset"] = "等级垂直偏移", ["Name (Short)"] = "名字 (短)", ["Name (Short) | Health Missing"] = "名字 (短) | 失去的生命值", ["Native"] = "本地", diff --git a/env/translations_zhTW.lua b/env/translations_zhTW.lua index b563cc325..46f79ab33 100644 --- a/env/translations_zhTW.lua +++ b/env/translations_zhTW.lua @@ -511,8 +511,13 @@ pfUI_translation["zhTW"] = { ["Name | Health Missing"] = nil, ["Name (Linebreak) -Health Missing"] = nil, ["Nameplate Border Size"] = nil, - ["Nameplates"] = "姓名板", - ["Nameplate Width"] = "姓名板高度", + ["Nameplates"] = "姓名板", ["Nameplate Width"] = "姓名板高度", + ["Name Position"] = "名字位置", + ["Name X-Offset"] = "名字水平偏移", + ["Name Y-Offset"] = "名字垂直偏移", + ["Level Position"] = "等級位置", + ["Level X-Offset"] = "等級水平偏移", + ["Level Y-Offset"] = "等級垂直偏移", ["Name (Short)"] = "名字 (短)", ["Name (Short) | Health Missing"] = nil, ["Native"] = "本地", diff --git a/fonts/Francois.ttf b/fonts/Francois.ttf new file mode 100644 index 0000000000000000000000000000000000000000..98e0ffa8c47b00f6f1eeb11fdaf396bb54d223c1 GIT binary patch literal 60580 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 bzlZSTA}FP{J)InD-23O7a^H4j{|)~a{~H<4 literal 0 HcmV?d00001 diff --git a/modules/gui.lua b/modules/gui.lua index 1c7a46ed4..eb2e382e1 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1560,7 +1560,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () CreateConfig(U["infight"], T["Enable Low Health Glow Effects On Screen Edges"], C.appearance.infight, "health", "checkbox") CreateConfig(U["infight"], T["Screen Edge Glow Intensity"], C.appearance.infight, "intensity", "dropdown", pfUI.gui.dropdowns.glowintensity) end) - + CreateGUIEntry(T["Settings"], T["Cooldown"], function() CreateConfig(U["buff"], T["Show Milliseconds When Timer Runs Out"], C.appearance.cd, "milliseconds", "checkbox") CreateConfig(nil, T["Cooldown Color (Less than 3 Sec)"], C.appearance.cd, "lowcolor", "color") @@ -2295,6 +2295,14 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () CreateConfig(U["nameplates"], T["Replace Totems With Icons"], C.nameplates, "totemicons", "checkbox") CreateConfig(U["nameplates"], T["Show Guild Name"], C.nameplates, "showguildname", "checkbox") + CreateConfig(nil, T["Name Position"], nil, nil, "header") + CreateConfig(U["nameplates"], T["Name X-Offset"], C.nameplates, "nameoffsetx") + CreateConfig(U["nameplates"], T["Name Y-Offset"], C.nameplates, "nameoffsety") + + CreateConfig(nil, T["Level Position"], nil, nil, "header") + CreateConfig(U["nameplates"], T["Level X-Offset"], C.nameplates, "leveloffsetx") + CreateConfig(U["nameplates"], T["Level Y-Offset"], C.nameplates, "leveloffsety") + CreateConfig(nil, T["Raid Icon"], nil, nil, "header") CreateConfig(U["nameplates"], T["Raid Icon Position"], C.nameplates, "raidiconpos", "dropdown", pfUI.gui.dropdowns.positions) CreateConfig(U["nameplates"], T["Raid Icon X-Offset"], C.nameplates, "raidiconoffx") diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 59785b17a..3ef1b96ff 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -395,7 +395,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.health.text:SetTextColor(1,1,1,1) nameplate.name = nameplate:CreateFontString(nil, "OVERLAY") - nameplate.name:SetPoint("TOP", nameplate, "TOP", 0, 0) nameplate.glow = nameplate:CreateTexture(nil, "BACKGROUND") nameplate.glow:SetPoint("CENTER", nameplate.health, "CENTER", 0, 0) @@ -406,7 +405,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.guild:SetPoint("BOTTOM", nameplate.health, "BOTTOM", 0, 0) nameplate.level = nameplate:CreateFontString(nil, "OVERLAY") - nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", -3, 0) nameplate.raidicon:SetParent(nameplate.health) nameplate.raidicon:SetDrawLayer("OVERLAY") @@ -515,19 +513,31 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () c.NOTHREAT.r, c.NOTHREAT.g, c.NOTHREAT.b, c.NOTHREAT.a = GetStringColor(C.nameplates.combatnothreat) c.STUN.r, c.STUN.g, c.STUN.b, c.STUN.a = GetStringColor(C.nameplates.combatstun) + -- Get name offset values from config + local nameOffsetX = tonumber(C.nameplates.nameoffsetx) or 0 + local nameOffsetY = tonumber(C.nameplates.nameoffsety) or 0 + local levelOffsetX = tonumber(C.nameplates.leveloffsetx) or 0 + local levelOffsetY = tonumber(C.nameplates.leveloffsety) or 0 + nameplate:SetWidth(plate_width) nameplate:SetHeight(plate_height) nameplate:SetPoint("TOP", parent, "TOP", 0, 0) - nameplate.name:SetFont(font, font_size, font_style) nameplate.health:SetOrientation(orientation) - nameplate.health:SetPoint("TOP", nameplate.name, "BOTTOM", 0, healthoffset) + nameplate.health:SetPoint("TOP", nameplate, "BOTTOM", 0, healthoffset) nameplate.health:SetStatusBarTexture(hptexture) nameplate.health:SetWidth(C.nameplates.width) nameplate.health:SetHeight(C.nameplates.heighthealth) nameplate.health.hlr, nameplate.health.hlg, nameplate.health.hlb, nameplate.health.hla = hlr, hlg, hlb, hla + nameplate.name:SetFont(font, font_size, font_style) + nameplate.name:SetPoint("TOP", nameplate.health, "TOP", nameOffsetX, nameOffsetY) + + DEFAULT_CHAT_FRAME:AddMessage("pfUI: Setting level offset to " .. levelOffsetX .. ", " .. levelOffsetY) + nameplate.level:SetFont(font, font_size, font_style) + nameplate.level:ClearAllPoints() + nameplate.level:SetPoint("TOP", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) CreateBackdrop(nameplate.health, default_border) nameplate.health.text:SetFont(font, font_size - 2, "OUTLINE") @@ -541,7 +551,8 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.raidicon:ClearAllPoints() nameplate.raidicon:SetPoint(C.nameplates.raidiconpos, nameplate.health, C.nameplates.raidiconpos, C.nameplates.raidiconoffx, C.nameplates.raidiconoffy) - nameplate.level:SetFont(font, font_size, font_style) + + nameplate.raidicon:SetWidth(C.nameplates.raidiconsize) nameplate.raidicon:SetHeight(C.nameplates.raidiconsize) @@ -679,8 +690,9 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () plate.guild:Hide() plate.totem:Show() elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then - plate.level:SetPoint("RIGHT", plate.name, "LEFT", -3, 0) + DEFAULT_CHAT_FRAME:AddMessage("pfUI Hideplate: Setting level offset to -5, 0") plate.name:SetParent(plate) + plate.level:SetParent(plate) plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2)) plate.level:Show() @@ -693,8 +705,8 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () end plate.totem:Hide() else - plate.level:SetPoint("RIGHT", plate.health, "LEFT", -5, 0) plate.name:SetParent(plate.health) + plate.level:SetParent(plate.health) plate.guild:SetPoint("BOTTOM", plate.health, "BOTTOM", 0, -(font_size + 4)) plate.level:Show() diff --git a/modules/nameplates_B.lua b/modules/nameplates_B.lua new file mode 100644 index 000000000..efb62c229 --- /dev/null +++ b/modules/nameplates_B.lua @@ -0,0 +1,1236 @@ +pfUI:RegisterModule("nameplates", "vanilla:tbc", function () + -- disable original castbars + pcall(SetCVar, "ShowVKeyCastbar", 0) + + local unitcolors = { + ["ENEMY_NPC"] = { .9, .2, .3, .8 }, + ["NEUTRAL_NPC"] = { 1, 1, .3, .8 }, + ["FRIENDLY_NPC"] = { .6, 1, 0, .8 }, + ["ENEMY_PLAYER"] = { .9, .2, .3, .8 }, + ["FRIENDLY_PLAYER"] = { .2, .6, 1, .8 } + } + + local combatstate = { + -- gets overwritten by user config + ["NOTHREAT"] = { r = .7, g = .7, b = .2, a = 1 }, + ["THREAT"] = { r = .7, g = .2, b = .2, a = 1 }, + ["CASTING"] = { r = .7, g = .2, b = .7, a = 1 }, + ["STUN"] = { r = .2, g = .7, b = .7, a = 1 }, + ["NONE"] = { r = .2, g = .2, b = .2, a = 1 }, + } + + local elitestrings = { + ["elite"] = "+", + ["rareelite"] = "R+", + ["rare"] = "R", + ["boss"] = "B" + } + + -- catch all nameplates + local childs, regions, plate + local initialized = 0 + local parentcount = 0 + local platecount = 0 + local registry = {} + local debuffdurations = C.appearance.cd.debuffs == "1" and true or nil + + -- cache default border color + local er, eg, eb, ea = GetStringColor(pfUI_config.appearance.border.color) + + local function GetCombatStateColor(guid) + local target = guid.."target" + local color = false + + if UnitAffectingCombat("player") and UnitAffectingCombat(guid) and not UnitCanAssist("player", guid) then + if C.nameplates.ccombatcasting == "1" and (UnitCastingInfo(guid) or UnitChannelInfo(guid)) then + color = combatstate.CASTING + elseif C.nameplates.ccombatthreat == "1" and UnitIsUnit(target, "player") then + color = combatstate.THREAT + elseif C.nameplates.ccombatnothreat == "1" and UnitExists(target) then + color = combatstate.NOTHREAT + elseif C.nameplates.ccombatstun == "1" and not UnitExists(target) and not UnitIsPlayer(guid) then + color = combatstate.STUN + end + end + + return color + end + + local function DoNothing() + return + end + + local function IsNamePlate(frame) + if frame:GetObjectType() ~= NAMEPLATE_FRAMETYPE then return nil end + regions = plate:GetRegions() + + if not regions then return nil end + if not regions.GetObjectType then return nil end + if not regions.GetTexture then return nil end + + if regions:GetObjectType() ~= "Texture" then return nil end + return regions:GetTexture() == "Interface\\Tooltips\\Nameplate-Border" or nil + end + + local function DisableObject(object) + if not object then return end + if not object.GetObjectType then return end + + local otype = object:GetObjectType() + + if otype == "Texture" then + object:SetTexture("") + object:SetTexCoord(0, 0, 0, 0) + elseif otype == "FontString" then + object:SetWidth(0.001) + elseif otype == "StatusBar" then + object:SetStatusBarTexture("") + end + end + + local function TotemPlate(name) + if C.nameplates.totemicons == "1" then + for totem, icon in pairs(L["totems"]) do + if string.find(name, totem) then return icon end + end + end + end + + local function HidePlate(unittype, name, fullhp, target) + -- keep some plates always visible according to config + if C.nameplates.fullhealth == "1" and not fullhp then return nil end + if C.nameplates.target == "1" and target then return nil end + + -- return true when something needs to be hidden + if C.nameplates.enemynpc == "1" and unittype == "ENEMY_NPC" then + return true + elseif C.nameplates.enemyplayer == "1" and unittype == "ENEMY_PLAYER" then + return true + elseif C.nameplates.neutralnpc == "1" and unittype == "NEUTRAL_NPC" then + return true + elseif C.nameplates.friendlynpc == "1" and unittype == "FRIENDLY_NPC" then + return true + elseif C.nameplates.friendlyplayer == "1" and unittype == "FRIENDLY_PLAYER" then + return true + elseif C.nameplates.critters == "1" and unittype == "NEUTRAL_NPC" then + for i, critter in pairs(L["critters"]) do + if string.lower(name) == string.lower(critter) then return true end + end + elseif C.nameplates.totems == "1" then + for totem in pairs(L["totems"]) do + if string.find(name, totem) then return true end + end + end + + -- nothing to hide + return nil + end + + local function abbrevname(t) + return string.sub(t,1,1)..". " + end + + local function GetNameString(name) + local abbrev = pfUI_config.unitframes.abbrevname == "1" or nil + local size = 20 + + -- first try to only abbreviate the first word + if abbrev and name and strlen(name) > size then + name = string.gsub(name, "^(%S+) ", abbrevname) + end + + -- abbreviate all if it still doesn't fit + if abbrev and name and strlen(name) > size then + name = string.gsub(name, "(%S+) ", abbrevname) + end + + return name + end + + + local function GetUnitType(red, green, blue) + if red > .9 and green < .2 and blue < .2 then + return "ENEMY_NPC" + elseif red > .9 and green > .9 and blue < .2 then + return "NEUTRAL_NPC" + elseif red < .2 and green < .2 and blue > 0.9 then + return "FRIENDLY_PLAYER" + elseif red < .2 and green > .9 and blue < .2 then + return "FRIENDLY_NPC" + end + end + + local filter, list, cache + local function DebuffFilterPopulate() + -- initialize variables + filter = C.nameplates["debuffs"]["filter"] + if filter == "none" then return end + list = C.nameplates["debuffs"][filter] + cache = {} + + -- populate list + for _, val in pairs({strsplit("#", list)}) do + cache[strlower(val)] = true + end + end + + local function DebuffFilter(effect) + if filter == "none" then return true end + if not cache then DebuffFilterPopulate() end + + if filter == "blacklist" and cache[strlower(effect)] then + return nil + elseif filter == "blacklist" then + return true + elseif filter == "whitelist" and cache[strlower(effect)] then + return true + elseif filter == "whitelist" then + return nil + end + end + + local function PlateCacheDebuffs(self, unitstr, verify) + if not self.debuffcache then self.debuffcache = {} end + + for id = 1, 16 do + local effect, _, texture, stacks, _, duration, timeleft + + if unitstr and C.nameplates.selfdebuff == "1" then + effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, id) + else + effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitDebuff(unitstr, id) + end + + if effect and timeleft and timeleft > 0 then + local start = GetTime() - ( (duration or 0) - ( timeleft or 0) ) + local stop = GetTime() + ( timeleft or 0 ) + self.debuffcache[id] = self.debuffcache[id] or {} + self.debuffcache[id].effect = effect + self.debuffcache[id].texture = texture + self.debuffcache[id].stacks = stacks + self.debuffcache[id].duration = duration or 0 + self.debuffcache[id].start = start + self.debuffcache[id].stop = stop + self.debuffcache[id].empty = nil + end + end + + self.verify = verify + end + + local function PlateUnitDebuff(self, id) + -- break on unknown data + if not self.debuffcache then return end + if not self.debuffcache[id] then return end + if not self.debuffcache[id].stop then return end + + -- break on timeout debuffs + if self.debuffcache[id].empty then return end + if self.debuffcache[id].stop < GetTime() then return end + + -- return cached debuff + local c = self.debuffcache[id] + return c.effect, c.rank, c.texture, c.stacks, c.dtype, c.duration, (c.stop - GetTime()) + end + + local function CreateDebuffIcon(plate, index) + plate.debuffs[index] = CreateFrame("Frame", plate.platename.."Debuff"..index, plate) + plate.debuffs[index]:Hide() + plate.debuffs[index]:SetFrameLevel(1) + + plate.debuffs[index].icon = plate.debuffs[index]:CreateTexture(nil, "BACKGROUND") + plate.debuffs[index].icon:SetTexture(.3,1,.8,1) + plate.debuffs[index].icon:SetAllPoints(plate.debuffs[index]) + + plate.debuffs[index].stacks = plate.debuffs[index]:CreateFontString(nil, "OVERLAY") + plate.debuffs[index].stacks:SetAllPoints(plate.debuffs[index]) + plate.debuffs[index].stacks:SetJustifyH("RIGHT") + plate.debuffs[index].stacks:SetJustifyV("BOTTOM") + plate.debuffs[index].stacks:SetTextColor(1,1,0) + + if pfUI.client <= 11200 then + -- create a fake animation frame on vanilla to improve performance + plate.debuffs[index].cd = CreateFrame("Frame", plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index]) + plate.debuffs[index].cd:SetScript("OnUpdate", CooldownFrame_OnUpdateModel) + plate.debuffs[index].cd.AdvanceTime = DoNothing + plate.debuffs[index].cd.SetSequence = DoNothing + plate.debuffs[index].cd.SetSequenceTime = DoNothing + else + -- use regular cooldown animation frames on burning crusade and later + plate.debuffs[index].cd = CreateFrame(COOLDOWN_FRAME_TYPE, plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate") + end + + plate.debuffs[index].cd.pfCooldownStyleAnimation = 0 + plate.debuffs[index].cd.pfCooldownType = "ALL" + end + + local function UpdateDebuffConfig(nameplate, i) + if not nameplate.debuffs[i] then return end + + -- update debuff positions + local width = tonumber(C.nameplates.width) + local debuffsize = tonumber(C.nameplates.debuffsize) + local debuffoffset = tonumber(C.nameplates.debuffoffset) + local limit = floor(width / debuffsize) + local font = C.nameplates.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default + local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size + local font_style = C.nameplates.name.fontstyle + + local aligna, alignb, offs, space + if C.nameplates.debuffs["position"] == "BOTTOM" then + aligna, alignb, offs, space = "TOPLEFT", "BOTTOMLEFT", -debuffoffset, -1 + else + aligna, alignb, offs, space = "BOTTOMLEFT", "TOPLEFT", debuffoffset, 1 + end + + nameplate.debuffs[i].stacks:SetFont(font, font_size, font_style) + nameplate.debuffs[i]:ClearAllPoints() + if i == 1 then + nameplate.debuffs[i]:SetPoint(aligna, nameplate.health, alignb, 0, offs) + elseif i <= limit then + nameplate.debuffs[i]:SetPoint("LEFT", nameplate.debuffs[i-1], "RIGHT", 1, 0) + elseif i > limit and limit > 0 then + nameplate.debuffs[i]:SetPoint(aligna, nameplate.debuffs[i-limit], alignb, 0, space) + end + + nameplate.debuffs[i]:SetWidth(tonumber(C.nameplates.debuffsize)) + nameplate.debuffs[i]:SetHeight(tonumber(C.nameplates.debuffsize)) + end + + -- create nameplate core + local nameplates = CreateFrame("Frame", "pfNameplates", UIParent) + nameplates:RegisterEvent("PLAYER_ENTERING_WORLD") + nameplates:RegisterEvent("PLAYER_TARGET_CHANGED") + nameplates:RegisterEvent("UNIT_COMBO_POINTS") + nameplates:RegisterEvent("PLAYER_COMBO_POINTS") + nameplates:RegisterEvent("UNIT_AURA") + + nameplates:SetScript("OnEvent", function() + if event == "PLAYER_ENTERING_WORLD" then + this:SetGameVariables() + else + this.eventcache = true + end + end) + + nameplates:SetScript("OnUpdate", function() + -- propagate events to all nameplates + if this.eventcache then + this.eventcache = nil + for plate in pairs(registry) do + plate.eventcache = true + end + end + + -- detect new nameplates + parentcount = WorldFrame:GetNumChildren() + if initialized < parentcount then + childs = { WorldFrame:GetChildren() } + for i = initialized + 1, parentcount do + plate = childs[i] + if IsNamePlate(plate) and not registry[plate] then + nameplates.OnCreate(plate) + registry[plate] = plate + end + end + + initialized = parentcount + end + end) + + -- combat tracker + nameplates.combat = CreateFrame("Frame") + nameplates.combat:RegisterEvent("PLAYER_ENTER_COMBAT") + nameplates.combat:RegisterEvent("PLAYER_LEAVE_COMBAT") + nameplates.combat:SetScript("OnEvent", function() + if event == "PLAYER_ENTER_COMBAT" then + this.inCombat = 1 + if PlayerFrame then PlayerFrame.inCombat = 1 end + elseif event == "PLAYER_LEAVE_COMBAT" then + this.inCombat = nil + if PlayerFrame then PlayerFrame.inCombat = nil end + end + end) + + nameplates.OnCreate = function(frame) + local parent = frame or this + platecount = platecount + 1 + platename = "pfNamePlate" .. platecount + + -- create pfUI nameplate overlay + local nameplate = CreateFrame("Button", platename, parent) + nameplate.platename = platename + nameplate:EnableMouse(0) + nameplate.parent = parent + nameplate.cache = {} + nameplate.UnitDebuff = PlateUnitDebuff + nameplate.CacheDebuffs = PlateCacheDebuffs + nameplate.original = {} + + -- create shortcuts for all known elements and disable them + nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren() + DisableObject(nameplate.original.healthbar) + DisableObject(nameplate.original.castbar) + + for i, object in pairs({parent:GetRegions()}) do + if NAMEPLATE_OBJECTORDER[i] and NAMEPLATE_OBJECTORDER[i] == "raidicon" then + nameplate[NAMEPLATE_OBJECTORDER[i]] = object + elseif NAMEPLATE_OBJECTORDER[i] then + nameplate.original[NAMEPLATE_OBJECTORDER[i]] = object + DisableObject(object) + else + DisableObject(object) + end + end + + HookScript(nameplate.original.healthbar, "OnValueChanged", nameplates.OnValueChanged) + + -- adjust sizes and scaling of the nameplate + nameplate:SetScale(UIParent:GetScale()) + + nameplate.health = CreateFrame("StatusBar", nil, nameplate) + nameplate.health:SetFrameLevel(4) -- keep above glow + nameplate.health.text = nameplate.health:CreateFontString(nil, "OVERLAY", "GameFontNormal") + nameplate.health.text:SetAllPoints() + nameplate.health.text:SetTextColor(1,1,1,1) + + nameplate.name = nameplate:CreateFontString(nil, "OVERLAY") + + nameplate.glow = nameplate:CreateTexture(nil, "BACKGROUND") + nameplate.glow:SetPoint("CENTER", nameplate.health, "CENTER", 0, 0) + nameplate.glow:SetTexture(pfUI.media["img:dot"]) + nameplate.glow:Hide() + + nameplate.guild = nameplate:CreateFontString(nil, "OVERLAY") + nameplate.guild:SetPoint("BOTTOM", nameplate.health, "BOTTOM", 0, 0) + + nameplate.level = nameplate:CreateFontString(nil, "OVERLAY") + + nameplate.raidicon:SetParent(nameplate.health) + nameplate.raidicon:SetDrawLayer("OVERLAY") + nameplate.raidicon:SetTexture(pfUI.media["img:raidicons"]) + + nameplate.totem = CreateFrame("Frame", nil, nameplate) + nameplate.totem:SetPoint("CENTER", nameplate, "CENTER", 0, 0) + nameplate.totem:SetHeight(32) + nameplate.totem:SetWidth(32) + nameplate.totem.icon = nameplate.totem:CreateTexture(nil, "OVERLAY") + nameplate.totem.icon:SetTexCoord(.078, .92, .079, .937) + nameplate.totem.icon:SetAllPoints() + CreateBackdrop(nameplate.totem) + + do -- debuffs + nameplate.debuffs = {} + CreateDebuffIcon(nameplate, 1) + end + + do -- combopoints + local combopoints = { } + for i = 1, 5 do + combopoints[i] = CreateFrame("Frame", nil, nameplate) + combopoints[i]:Hide() + combopoints[i]:SetFrameLevel(8) + combopoints[i].tex = combopoints[i]:CreateTexture("OVERLAY") + combopoints[i].tex:SetAllPoints() + + if i < 3 then + combopoints[i].tex:SetTexture(1, .3, .3, .75) + elseif i < 4 then + combopoints[i].tex:SetTexture(1, 1, .3, .75) + else + combopoints[i].tex:SetTexture(.3, 1, .3, .75) + end + end + nameplate.combopoints = combopoints + end + + do -- castbar + local castbar = CreateFrame("StatusBar", nil, nameplate.health) + castbar:Hide() + + castbar:SetScript("OnShow", function() + if C.nameplates.debuffs["position"] == "BOTTOM" then + nameplate.debuffs[1]:SetPoint("TOPLEFT", this, "BOTTOMLEFT", 0, -4) + end + end) + + castbar:SetScript("OnHide", function() + if C.nameplates.debuffs["position"] == "BOTTOM" then + nameplate.debuffs[1]:SetPoint("TOPLEFT", this:GetParent(), "BOTTOMLEFT", 0, -4) + end + end) + + castbar.text = castbar:CreateFontString("Status", "DIALOG", "GameFontNormal") + castbar.text:SetPoint("RIGHT", castbar, "LEFT", -4, 0) + castbar.text:SetNonSpaceWrap(false) + castbar.text:SetTextColor(1,1,1,.5) + + castbar.spell = castbar:CreateFontString("Status", "DIALOG", "GameFontNormal") + castbar.spell:SetPoint("CENTER", castbar, "CENTER") + castbar.spell:SetNonSpaceWrap(false) + castbar.spell:SetTextColor(1,1,1,1) + + castbar.icon = CreateFrame("Frame", nil, castbar) + castbar.icon.tex = castbar.icon:CreateTexture(nil, "BORDER") + castbar.icon.tex:SetAllPoints() + + nameplate.castbar = castbar + end + + parent.nameplate = nameplate + HookScript(parent, "OnShow", nameplates.OnShow) + HookScript(parent, "OnUpdate", nameplates.OnUpdate) + + nameplates.OnConfigChange(parent) + nameplates.OnShow(parent) + end + + nameplates.OnConfigChange = function(frame) + local parent = frame + local nameplate = frame.nameplate + + local font = C.nameplates.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default + local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size + local font_style = C.nameplates.name.fontstyle + local glowr, glowg, glowb, glowa = GetStringColor(C.nameplates.glowcolor) + local hlr, hlg, hlb, hla = GetStringColor(C.nameplates.highlightcolor) + local hptexture = pfUI.media[C.nameplates.healthtexture] + local rawborder, default_border = GetBorderSize("nameplates") + + local plate_width = C.nameplates.width + 50 + local plate_height = C.nameplates.heighthealth + font_size + 5 + local plate_height_cast = C.nameplates.heighthealth + font_size + 5 + C.nameplates.heightcast + 5 + local combo_size = 5 + + local width = tonumber(C.nameplates.width) + local debuffsize = tonumber(C.nameplates.debuffsize) + local healthoffset = tonumber(C.nameplates.health.offset) + local orientation = C.nameplates.verticalhealth == "1" and "VERTICAL" or "HORIZONTAL" + + local c = combatstate -- load combat state colors + c.CASTING.r, c.CASTING.g, c.CASTING.b, c.CASTING.a = GetStringColor(C.nameplates.combatcasting) + c.THREAT.r, c.THREAT.g, c.THREAT.b, c.THREAT.a = GetStringColor(C.nameplates.combatthreat) + c.NOTHREAT.r, c.NOTHREAT.g, c.NOTHREAT.b, c.NOTHREAT.a = GetStringColor(C.nameplates.combatnothreat) + c.STUN.r, c.STUN.g, c.STUN.b, c.STUN.a = GetStringColor(C.nameplates.combatstun) + + -- Get name offset values from config + local nameOffsetX = tonumber(C.nameplates.nameoffsetx) + local nameOffsetY = tonumber(C.nameplates.nameoffsety) + local levelOffsetX = tonumber(C.nameplates.leveloffsetx) + local levelOffsetY = tonumber(C.nameplates.leveloffsety) + + nameplate:SetWidth(plate_width) + nameplate:SetHeight(plate_height) + nameplate:SetPoint("TOP", parent, "TOP", 0, 0) + + + nameplate.health:SetOrientation(orientation) + nameplate.health:SetPoint("TOP", nameplate, "BOTTOM", 0, healthoffset) + nameplate.health:SetStatusBarTexture(hptexture) + nameplate.health:SetWidth(C.nameplates.width) + nameplate.health:SetHeight(C.nameplates.heighthealth) + nameplate.health.hlr, nameplate.health.hlg, nameplate.health.hlb, nameplate.health.hla = hlr, hlg, hlb, hla + + nameplate.name:SetFont(font, font_size, font_style) + nameplate.name:SetPoint("TOP", nameplate.health, "TOP", nameOffsetX, nameOffsetY) + + CreateBackdrop(nameplate.health, default_border) + + nameplate.health.text:SetFont(font, font_size - 2, "OUTLINE") + nameplate.health.text:SetJustifyH(C.nameplates.hptextpos) + + nameplate.guild:SetFont(font, font_size, font_style) + nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) + + + nameplate.glow:SetWidth(C.nameplates.width + 60) + nameplate.glow:SetHeight(C.nameplates.heighthealth + 30) + nameplate.glow:SetVertexColor(glowr, glowg, glowb, glowa) + + nameplate.raidicon:ClearAllPoints() + nameplate.raidicon:SetPoint(C.nameplates.raidiconpos, nameplate.health, C.nameplates.raidiconpos, C.nameplates.raidiconoffx, C.nameplates.raidiconoffy) + nameplate.level:SetFont(font, font_size, font_style) + DEFAULT_CHAT_FRAME:AddMessage("Level offset: "..tostring(levelOffsetX).." "..tostring(levelOffsetY)) + + nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) + + nameplate.raidicon:SetWidth(C.nameplates.raidiconsize) + nameplate.raidicon:SetHeight(C.nameplates.raidiconsize) + + for i=1,16 do + UpdateDebuffConfig(nameplate, i) + end + + for i=1,5 do + nameplate.combopoints[i]:SetWidth(combo_size) + nameplate.combopoints[i]:SetHeight(combo_size) + nameplate.combopoints[i]:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", -(i-1)*(combo_size+default_border*3), -default_border*3) + CreateBackdrop(nameplate.combopoints[i], default_border) + end + + nameplate.castbar:SetPoint("TOPLEFT", nameplate.health, "BOTTOMLEFT", 0, -default_border*3) + nameplate.castbar:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", 0, -default_border*3) + nameplate.castbar:SetHeight(C.nameplates.heightcast) + nameplate.castbar:SetStatusBarTexture(hptexture) + nameplate.castbar:SetStatusBarColor(.9,.8,0,1) + CreateBackdrop(nameplate.castbar, default_border) + + nameplate.castbar.text:SetFont(font, font_size, "OUTLINE") + nameplate.castbar.spell:SetFont(font, font_size, "OUTLINE") + nameplate.castbar.icon:SetPoint("BOTTOMLEFT", nameplate.castbar, "BOTTOMRIGHT", default_border*3, 0) + nameplate.castbar.icon:SetPoint("TOPLEFT", nameplate.health, "TOPRIGHT", default_border*3, 0) + nameplate.castbar.icon:SetWidth(C.nameplates.heightcast + default_border*3 + C.nameplates.heighthealth) + CreateBackdrop(nameplate.castbar.icon, default_border) + + nameplates:OnDataChanged(nameplate) + end + + nameplates.OnValueChanged = function(arg1) + nameplates:OnDataChanged(this:GetParent().nameplate) + end + + nameplates.OnDataChanged = function(self, plate) + local visible = plate:IsVisible() + local hp = plate.original.healthbar:GetValue() + local hpmin, hpmax = plate.original.healthbar:GetMinMaxValues() + local name = plate.original.name:GetText() + local level = plate.original.level:IsShown() and plate.original.level:GetObjectType() == "FontString" and tonumber(plate.original.level:GetText()) or "??" + local class, ulevel, elite, player, guild = GetUnitData(name, true) + local target = plate.istarget + local mouseover = UnitExists("mouseover") and plate.original.glow:IsShown() or nil + local unitstr = target and "target" or mouseover and "mouseover" or nil + local red, green, blue = plate.original.healthbar:GetStatusBarColor() + local unittype = GetUnitType(red, green, blue) or "ENEMY_NPC" + local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size + + -- use superwow unit guid as unitstr if possible + if superwow_active and not unitstr then + unitstr = plate.parent:GetName(1) + end + + -- ignore players with npc names if plate level is lower than player level + if ulevel and ulevel > (level == "??" and -1 or level) then player = nil end + + -- cache name and reset unittype on change + if plate.cache.name ~= name then + plate.cache.name = name + plate.cache.player = nil + end + + -- read and cache unittype + if plate.cache.player then + -- overwrite unittype from cache if existing + player = plate.cache.player == "PLAYER" and true or nil + elseif unitstr then + -- read unit type while unitstr is set + plate.cache.player = UnitIsPlayer(unitstr) and "PLAYER" or "NPC" + end + + if player and unittype == "ENEMY_NPC" then unittype = "ENEMY_PLAYER" end + elite = plate.original.levelicon:IsShown() and not player and "boss" or elite + if not class then plate.wait_for_scan = true end + + -- skip data updates on invisible frames + if not visible then return end + + -- target event sometimes fires too quickly, where nameplate identifiers are not + -- yet updated. So while being inside this event, we cannot trust the unitstr. + if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end + + -- remove unitstr on unit name mismatch + if unitstr and UnitName(unitstr) ~= name then unitstr = nil end + + -- use mobhealth values if addon is running + if (MobHealth3 or MobHealthFrame) and target and name == UnitName('target') and MobHealth_GetTargetCurHP() then + hp = MobHealth_GetTargetCurHP() > 0 and MobHealth_GetTargetCurHP() or hp + hpmax = MobHealth_GetTargetMaxHP() > 0 and MobHealth_GetTargetMaxHP() or hpmax + end + + -- always make sure to keep plate visible + plate:Show() + + if target and C.nameplates.targetglow == "1" then + plate.glow:Show() else plate.glow:Hide() + end + + -- target indicator + if superwow_active and C.nameplates.outcombatstate == "1" then + local guid = plate.parent:GetName(1) or "" + + -- determine color based on combat state + local color = GetCombatStateColor(guid) + if not color then color = combatstate.NONE end + + -- set border color + plate.health.backdrop:SetBackdropBorderColor(color.r, color.g, color.b, color.a) + elseif target and C.nameplates.targethighlight == "1" then + plate.health.backdrop:SetBackdropBorderColor(plate.health.hlr, plate.health.hlg, plate.health.hlb, plate.health.hla) + elseif C.nameplates.outfriendlynpc == "1" and unittype == "FRIENDLY_NPC" then + plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) + elseif C.nameplates.outfriendly == "1" and unittype == "FRIENDLY_PLAYER" then + plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) + elseif C.nameplates.outneutral == "1" and strfind(unittype, "NEUTRAL") then + plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) + elseif C.nameplates.outenemy == "1" and strfind(unittype, "ENEMY") then + plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) + else + plate.health.backdrop:SetBackdropBorderColor(er,eg,eb,ea) + end + + -- hide frames according to the configuration + local TotemIcon = TotemPlate(name) + + if TotemIcon then + -- create totem icon + plate.totem.icon:SetTexture("Interface\\Icons\\" .. TotemIcon) + + plate.glow:Hide() + plate.level:Hide() + plate.name:Hide() + plate.health:Hide() + plate.guild:Hide() + plate.totem:Show() + elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then + plate.level:SetPoint("RIGHT", plate.name, "LEFT", -3, 0) + plate.name:SetParent(plate) + plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2)) + + plate.level:Show() + plate.name:Show() + plate.health:Hide() + if guild and C.nameplates.showguildname == "1" then + plate.glow:SetPoint("CENTER", plate.name, "CENTER", 0, -(font_size / 2) - 2) + else + plate.glow:SetPoint("CENTER", plate.name, "CENTER", 0, 0) + end + plate.totem:Hide() + else + plate.level:SetPoint("RIGHT", plate.health, "LEFT", -5, 0) + plate.name:SetParent(plate.health) + plate.guild:SetPoint("BOTTOM", plate.health, "BOTTOM", 0, -(font_size + 4)) + + plate.level:Show() + plate.name:Show() + plate.health:Show() + plate.glow:SetPoint("CENTER", plate.health, "CENTER", 0, 0) + plate.totem:Hide() + end + + plate.name:SetText(GetNameString(name)) + plate.level:SetText(string.format("%s%s", level, (elitestrings[elite] or ""))) + + if guild and C.nameplates.showguildname == "1" then + plate.guild:SetText(guild) + if guild == GetGuildInfo("player") then + plate.guild:SetTextColor(0, 0.9, 0, 1) + else + plate.guild:SetTextColor(0.8, 0.8, 0.8, 1) + end + plate.guild:Show() + else + plate.guild:Hide() + end + + plate.health:SetMinMaxValues(hpmin, hpmax) + plate.health:SetValue(hp) + + if C.nameplates.showhp == "1" then + local rhp, rhpmax, estimated + if hpmax > 100 or (round(hpmax/100*hp) ~= hp) then + rhp, rhpmax = hp, hpmax + elseif pfUI.libhealth and pfUI.libhealth.enabled then + rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(name,level,tonumber(hp),tonumber(hpmax)) + end + + local setting = C.nameplates.hptextformat + local hasdata = ( estimated or hpmax > 100 or (round(hpmax/100*hp) ~= hp) ) + + if setting == "curperc" and hasdata then + plate.health.text:SetText(string.format("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100))) + elseif setting == "cur" and hasdata then + plate.health.text:SetText(string.format("%s", Abbreviate(rhp))) + elseif setting == "curmax" and hasdata then + plate.health.text:SetText(string.format("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax))) + elseif setting == "curmaxs" and hasdata then + plate.health.text:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax))) + elseif setting == "curmaxperc" and hasdata then + plate.health.text:SetText(string.format("%s - %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))) + elseif setting == "curmaxpercs" and hasdata then + plate.health.text:SetText(string.format("%s / %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))) + elseif setting == "deficit" then + plate.health.text:SetText(string.format("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp))) + else -- "percent" as fallback + plate.health.text:SetText(string.format("%s%%", ceil(hp/hpmax*100))) + end + else + plate.health.text:SetText() + end + + local r, g, b, a = unpack(unitcolors[unittype]) + + if unittype == "ENEMY_PLAYER" and C.nameplates["enemyclassc"] == "1" and class and RAID_CLASS_COLORS[class] then + r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1 + elseif unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassc"] == "1" and class and RAID_CLASS_COLORS[class] then + r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1 + end + + if superwow_active and unitstr and UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then + r, g, b, a = .5, .5, .5, .8 + end + + if superwow_active and C.nameplates.barcombatstate == "1" then + local guid = plate.parent:GetName(1) or "" + local color = GetCombatStateColor(guid) + + if color then + r, g, b, a = color.r, color.g, color.b, color.a + end + end + + if r ~= plate.cache.r or g ~= plate.cache.g or b ~= plate.cache.b then + plate.health:SetStatusBarColor(r, g, b, a) + plate.cache.r, plate.cache.g, plate.cache.b = r, g, b + end + + if r + g + b ~= plate.cache.namecolor and unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassnamec"] == "1" and class and RAID_CLASS_COLORS[class] then + plate.name:SetTextColor(r, g, b, a) + plate.cache.namecolor = r + g + b + end + + -- update combopoints + for i=1, 5 do plate.combopoints[i]:Hide() end + if target and C.nameplates.cpdisplay == "1" then + for i=1, GetComboPoints("target") do plate.combopoints[i]:Show() end + end + + -- update debuffs + local index = 1 + + if C.nameplates["showdebuffs"] == "1" then + local verify = string.format("%s:%s", (name or ""), (level or "")) + + -- update cached debuffs + if C.nameplates["guessdebuffs"] == "1" and unitstr then + plate:CacheDebuffs(unitstr, verify) + end + + -- update all debuff icons + for i = 1, 16 do + local effect, rank, texture, stacks, dtype, duration, timeleft + + if unitstr and C.nameplates.selfdebuff == "1" then + effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, i) + elseif unitstr then + effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitDebuff(unitstr, i) + elseif plate.verify == verify then + effect, rank, texture, stacks, dtype, duration, timeleft = plate:UnitDebuff(i) + end + + if effect and texture and DebuffFilter(effect) then + if not plate.debuffs[index] then + CreateDebuffIcon(plate, index) + UpdateDebuffConfig(plate, index) + end + + plate.debuffs[index]:Show() + plate.debuffs[index].icon:SetTexture(texture) + plate.debuffs[index].icon:SetTexCoord(.078, .92, .079, .937) + + if stacks and stacks > 1 and C.nameplates.debuffs["showstacks"] == "1" then + plate.debuffs[index].stacks:SetText(stacks) + plate.debuffs[index].stacks:Show() + else + plate.debuffs[index].stacks:Hide() + end + + if duration and timeleft and debuffdurations then + plate.debuffs[index].cd:SetAlpha(0) + plate.debuffs[index].cd:Show() + CooldownFrame_SetTimer(plate.debuffs[index].cd, GetTime() + timeleft - duration, duration, 1) + end + + index = index + 1 + end + end + end + + -- hide remaining debuffs + for i = index, 16 do + if plate.debuffs[i] then + plate.debuffs[i]:Hide() + end + end + end + + nameplates.OnShow = function(frame) + local frame = frame or this + local nameplate = frame.nameplate + + nameplates:OnDataChanged(nameplate) + end + + nameplates.OnUpdate = function(frame) + local update + local frame = frame or this + local nameplate = frame.nameplate + local original = nameplate.original + local name = original.name:GetText() + local target = UnitExists("target") and frame:GetAlpha() == 1 or nil + local mouseover = UnitExists("mouseover") and original.glow:IsShown() or nil + local namefightcolor = C.nameplates.namefightcolor == "1" + + -- trigger queued event update + if nameplate.eventcache then + nameplates:OnDataChanged(nameplate) + nameplate.eventcache = nil + end + + -- reset strata cache on target change + if nameplate.istarget ~= target then + nameplate.target_strata = nil + end + + -- keep target nameplate above others + if target and nameplate.target_strata ~= 1 then + nameplate:SetFrameStrata("LOW") + nameplate.target_strata = 1 + elseif not target and nameplate.target_strata ~= 0 then + nameplate:SetFrameStrata("BACKGROUND") + nameplate.target_strata = 0 + end + + -- cache target value + nameplate.istarget = target + + -- set non-target plate alpha + if target or not UnitExists("target") then + nameplate:SetAlpha(1) + else + frame:SetAlpha(.95) + nameplate:SetAlpha(tonumber(C.nameplates.notargalpha)) + end + + -- queue update on visual target update + if nameplate.cache.target ~= target then + nameplate.cache.target = target + update = true + end + + -- queue update on visual mouseover update + if nameplate.cache.mouseover ~= mouseover then + nameplate.cache.mouseover = mouseover + update = true + end + + -- trigger update when unit was found + if nameplate.wait_for_scan and GetUnitData(name, true) then + nameplate.wait_for_scan = nil + update = true + end + + -- trigger update when name color changed + local r, g, b = original.name:GetTextColor() + if r + g + b ~= nameplate.cache.namecolor then + nameplate.cache.namecolor = r + g + b + + if namefightcolor then + if r > .9 and g < .2 and b < .2 then + nameplate.name:SetTextColor(1,0.4,0.2,1) -- infight + else + nameplate.name:SetTextColor(r,g,b,1) + end + else + nameplate.name:SetTextColor(1,1,1,1) + end + update = true + end + + -- trigger update when level color changed + local r, g, b = original.level:GetTextColor() + r, g, b = r + .3, g + .3, b + .3 + if r + g + b ~= nameplate.cache.levelcolor then + nameplate.cache.levelcolor = r + g + b + nameplate.level:SetTextColor(r,g,b,1) + update = true + end + + -- scan for debuff timeouts + if nameplate.debuffcache then + for id, data in pairs(nameplate.debuffcache) do + if ( not data.stop or data.stop < GetTime() ) and not data.empty then + data.empty = true + update = true + end + end + end + + -- use timer based updates + if not nameplate.tick or nameplate.tick < GetTime() then + update = true + end + + -- run full updates if required + if update then + nameplates:OnDataChanged(nameplate) + nameplate.tick = GetTime() + .5 + end + + -- target zoom + local w, h = nameplate.health:GetWidth(), nameplate.health:GetHeight() + if target and C.nameplates.targetzoom == "1" then + local zoomval = tonumber(C.nameplates.targetzoomval)+1 + local wc = tonumber(C.nameplates.width)*zoomval + local hc = tonumber(C.nameplates.heighthealth)*(zoomval*.9) + local animation = false + + if wc >= w then + wc = w*1.05 + nameplate.health:SetWidth(wc) + nameplate.health.zoomTransition = true + animation = true + end + + if hc >= h then + hc = h*1.05 + nameplate.health:SetHeight(hc) + nameplate.health.zoomTransition = true + animation = true + end + + if animation == false and not nameplate.health.zoomed then + nameplate.health:SetWidth(wc) + nameplate.health:SetHeight(hc) + nameplate.health.zoomTransition = nil + nameplate.health.zoomed = true + end + elseif nameplate.health.zoomed or nameplate.health.zoomTransition then + local wc = tonumber(C.nameplates.width) + local hc = tonumber(C.nameplates.heighthealth) + local animation = false + + if wc <= w then + wc = w*.95 + nameplate.health:SetWidth(wc) + animation = true + end + + if hc <= h then + hc = h*0.95 + nameplate.health:SetHeight(hc) + animation = true + end + + if animation == false then + nameplate.health:SetWidth(wc) + nameplate.health:SetHeight(hc) + nameplate.health.zoomTransition = nil + nameplate.health.zoomed = nil + end + end + + -- castbar update + if C.nameplates["showcastbar"] == "1" and ( C.nameplates["targetcastbar"] == "0" or target ) then + local channel, cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill + + -- detect cast or channel bars + cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(target and "target" or name) + if not cast then channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(target and "target" or name) end + + -- read enemy casts from SuperWoW if enabled + if superwow_active then + cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(nameplate.parent:GetName(1)) + if not cast then channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(nameplate.parent:GetName(1)) end + end + + if not cast and not channel then + nameplate.castbar:Hide() + elseif cast or channel then + local effect = cast or channel + local duration = endTime - startTime + local max = duration / 1000 + local cur = GetTime() - startTime / 1000 + + -- invert castbar values while channeling + if channel then cur = max + startTime/1000 - GetTime() end + + nameplate.castbar:SetMinMaxValues(0, duration/1000) + nameplate.castbar:SetValue(cur) + nameplate.castbar.text:SetText(round(cur,1)) + if C.nameplates.spellname == "1" then + nameplate.castbar.spell:SetText(effect) + else + nameplate.castbar.spell:SetText("") + end + nameplate.castbar:Show() + + if texture then + nameplate.castbar.icon.tex:SetTexture(texture) + nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9) + end + end + else + nameplate.castbar:Hide() + end + end + + -- set nameplate game settings + nameplates.SetGameVariables = function() + -- update visibility (hostile) + if C.nameplates["showhostile"] == "1" then + _G.NAMEPLATES_ON = true + ShowNameplates() + else + _G.NAMEPLATES_ON = nil + HideNameplates() + end + + -- update visibility (hostile) + if C.nameplates["showfriendly"] == "1" then + _G.FRIENDNAMEPLATES_ON = true + ShowFriendNameplates() + else + _G.FRIENDNAMEPLATES_ON = nil + HideFriendNameplates() + end + end + + nameplates:SetGameVariables() + + nameplates.UpdateConfig = function() + -- update debuff filters + DebuffFilterPopulate() + + -- update nameplate visibility + nameplates:SetGameVariables() + + -- apply all config changes + for plate in pairs(registry) do + nameplates.OnConfigChange(plate) + end + end + + if pfUI.client <= 11200 then + -- handle vanilla only settings + -- due to the secured lua api, those settings can't be applied to TBC and later. + local hookOnConfigChange = nameplates.OnConfigChange + nameplates.OnConfigChange = function(self) + hookOnConfigChange(self) + + local parent = self + local nameplate = self.nameplate + local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and nameplate or parent + + -- disable all clicks for now + parent:EnableMouse(false) + nameplate:EnableMouse(false) + + -- adjust vertical offset + if C.nameplates["vertical_offset"] ~= "0" then + nameplate:SetPoint("TOP", parent, "TOP", 0, tonumber(C.nameplates["vertical_offset"])) + end + + -- replace clickhandler + if C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0" then + plate:SetScript("OnClick", function() parent:Click() end) + end + + -- enable mouselook on rightbutton down + if C.nameplates["rightclick"] == "1" then + plate:SetScript("OnMouseDown", nameplates.mouselook.OnMouseDown) + else + plate:SetScript("OnMouseDown", nil) + end + end + + local hookOnDataChanged = nameplates.OnDataChanged + nameplates.OnDataChanged = function(self, nameplate) + hookOnDataChanged(self, nameplate) + + -- make sure to keep mouse events disabled on parent nameplate + if (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") then + nameplate.parent:EnableMouse(false) + end + end + + local hookOnUpdate = nameplates.OnUpdate + nameplates.OnUpdate = function(self) + -- initialize shortcut variables + local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and this.nameplate or this + local clickable = C.nameplates["clickthrough"] ~= "1" and true or false + + -- disable all click events + if not clickable then + this:EnableMouse(false) + this.nameplate:EnableMouse(false) + else + plate:EnableMouse(clickable) + end + + if C.nameplates["overlap"] == "1" then + if this:GetWidth() > 1 then + -- set parent to 1 pixel to have them overlap each other + this:SetWidth(1) + this:SetHeight(1) + end + else + if not this.nameplate.dwidth then + -- cache initial sizing value for comparison + this.nameplate.dwidth = floor(this.nameplate:GetWidth() * UIParent:GetScale()) + end + + if floor(this:GetWidth()) ~= this.nameplate.dwidth then + -- align parent plate to the actual size + this:SetWidth(this.nameplate:GetWidth() * UIParent:GetScale()) + this:SetHeight(this.nameplate:GetHeight() * UIParent:GetScale()) + end + end + + -- disable click events while spell is targeting + local mouseEnabled = this.nameplate:IsMouseEnabled() + if C.nameplates["clickthrough"] == "0" and C.nameplates["overlap"] == "1" and SpellIsTargeting() == mouseEnabled then + this.nameplate:EnableMouse(not mouseEnabled) + end + + hookOnUpdate(self) + end + + -- enable mouselook on rightbutton down + nameplates.mouselook = CreateFrame("Frame", nil, UIParent) + nameplates.mouselook.time = nil + nameplates.mouselook.frame = nil + nameplates.mouselook.OnMouseDown = function() + if arg1 and arg1 == "RightButton" then + MouselookStart() + + -- start detection of the rightclick emulation + nameplates.mouselook.time = GetTime() + nameplates.mouselook.frame = this + nameplates.mouselook:Show() + end + end + + nameplates.mouselook:SetScript("OnUpdate", function() + -- break here if nothing to do + if not this.time or not this.frame then + this:Hide() + return + end + + -- if threshold is reached (0.5 second) no click action will follow + if not IsMouselooking() and this.time + tonumber(C.nameplates["clickthreshold"]) < GetTime() then + this:Hide() + return + end + + -- run a usual nameplate rightclick action + if not IsMouselooking() then + this.frame:Click("LeftButton") + if UnitCanAttack("player", "target") and not nameplates.combat.inCombat then AttackTarget() end + this:Hide() + return + end + end) + end + + pfUI.nameplates = nameplates +end) From e0970a15a0c5f18aca1d3436c101102453439e72 Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Sun, 8 Jun 2025 13:27:17 +0200 Subject: [PATCH 07/10] Refactor code structure for improved readability and maintainability --- modules/nameplates.lua | 2 - modules/nameplates_B.lua | 1236 -------------------------------------- 2 files changed, 1238 deletions(-) delete mode 100644 modules/nameplates_B.lua diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 3ef1b96ff..993663d40 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -534,7 +534,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.name:SetFont(font, font_size, font_style) nameplate.name:SetPoint("TOP", nameplate.health, "TOP", nameOffsetX, nameOffsetY) - DEFAULT_CHAT_FRAME:AddMessage("pfUI: Setting level offset to " .. levelOffsetX .. ", " .. levelOffsetY) nameplate.level:SetFont(font, font_size, font_style) nameplate.level:ClearAllPoints() nameplate.level:SetPoint("TOP", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) @@ -690,7 +689,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () plate.guild:Hide() plate.totem:Show() elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then - DEFAULT_CHAT_FRAME:AddMessage("pfUI Hideplate: Setting level offset to -5, 0") plate.name:SetParent(plate) plate.level:SetParent(plate) plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2)) diff --git a/modules/nameplates_B.lua b/modules/nameplates_B.lua deleted file mode 100644 index efb62c229..000000000 --- a/modules/nameplates_B.lua +++ /dev/null @@ -1,1236 +0,0 @@ -pfUI:RegisterModule("nameplates", "vanilla:tbc", function () - -- disable original castbars - pcall(SetCVar, "ShowVKeyCastbar", 0) - - local unitcolors = { - ["ENEMY_NPC"] = { .9, .2, .3, .8 }, - ["NEUTRAL_NPC"] = { 1, 1, .3, .8 }, - ["FRIENDLY_NPC"] = { .6, 1, 0, .8 }, - ["ENEMY_PLAYER"] = { .9, .2, .3, .8 }, - ["FRIENDLY_PLAYER"] = { .2, .6, 1, .8 } - } - - local combatstate = { - -- gets overwritten by user config - ["NOTHREAT"] = { r = .7, g = .7, b = .2, a = 1 }, - ["THREAT"] = { r = .7, g = .2, b = .2, a = 1 }, - ["CASTING"] = { r = .7, g = .2, b = .7, a = 1 }, - ["STUN"] = { r = .2, g = .7, b = .7, a = 1 }, - ["NONE"] = { r = .2, g = .2, b = .2, a = 1 }, - } - - local elitestrings = { - ["elite"] = "+", - ["rareelite"] = "R+", - ["rare"] = "R", - ["boss"] = "B" - } - - -- catch all nameplates - local childs, regions, plate - local initialized = 0 - local parentcount = 0 - local platecount = 0 - local registry = {} - local debuffdurations = C.appearance.cd.debuffs == "1" and true or nil - - -- cache default border color - local er, eg, eb, ea = GetStringColor(pfUI_config.appearance.border.color) - - local function GetCombatStateColor(guid) - local target = guid.."target" - local color = false - - if UnitAffectingCombat("player") and UnitAffectingCombat(guid) and not UnitCanAssist("player", guid) then - if C.nameplates.ccombatcasting == "1" and (UnitCastingInfo(guid) or UnitChannelInfo(guid)) then - color = combatstate.CASTING - elseif C.nameplates.ccombatthreat == "1" and UnitIsUnit(target, "player") then - color = combatstate.THREAT - elseif C.nameplates.ccombatnothreat == "1" and UnitExists(target) then - color = combatstate.NOTHREAT - elseif C.nameplates.ccombatstun == "1" and not UnitExists(target) and not UnitIsPlayer(guid) then - color = combatstate.STUN - end - end - - return color - end - - local function DoNothing() - return - end - - local function IsNamePlate(frame) - if frame:GetObjectType() ~= NAMEPLATE_FRAMETYPE then return nil end - regions = plate:GetRegions() - - if not regions then return nil end - if not regions.GetObjectType then return nil end - if not regions.GetTexture then return nil end - - if regions:GetObjectType() ~= "Texture" then return nil end - return regions:GetTexture() == "Interface\\Tooltips\\Nameplate-Border" or nil - end - - local function DisableObject(object) - if not object then return end - if not object.GetObjectType then return end - - local otype = object:GetObjectType() - - if otype == "Texture" then - object:SetTexture("") - object:SetTexCoord(0, 0, 0, 0) - elseif otype == "FontString" then - object:SetWidth(0.001) - elseif otype == "StatusBar" then - object:SetStatusBarTexture("") - end - end - - local function TotemPlate(name) - if C.nameplates.totemicons == "1" then - for totem, icon in pairs(L["totems"]) do - if string.find(name, totem) then return icon end - end - end - end - - local function HidePlate(unittype, name, fullhp, target) - -- keep some plates always visible according to config - if C.nameplates.fullhealth == "1" and not fullhp then return nil end - if C.nameplates.target == "1" and target then return nil end - - -- return true when something needs to be hidden - if C.nameplates.enemynpc == "1" and unittype == "ENEMY_NPC" then - return true - elseif C.nameplates.enemyplayer == "1" and unittype == "ENEMY_PLAYER" then - return true - elseif C.nameplates.neutralnpc == "1" and unittype == "NEUTRAL_NPC" then - return true - elseif C.nameplates.friendlynpc == "1" and unittype == "FRIENDLY_NPC" then - return true - elseif C.nameplates.friendlyplayer == "1" and unittype == "FRIENDLY_PLAYER" then - return true - elseif C.nameplates.critters == "1" and unittype == "NEUTRAL_NPC" then - for i, critter in pairs(L["critters"]) do - if string.lower(name) == string.lower(critter) then return true end - end - elseif C.nameplates.totems == "1" then - for totem in pairs(L["totems"]) do - if string.find(name, totem) then return true end - end - end - - -- nothing to hide - return nil - end - - local function abbrevname(t) - return string.sub(t,1,1)..". " - end - - local function GetNameString(name) - local abbrev = pfUI_config.unitframes.abbrevname == "1" or nil - local size = 20 - - -- first try to only abbreviate the first word - if abbrev and name and strlen(name) > size then - name = string.gsub(name, "^(%S+) ", abbrevname) - end - - -- abbreviate all if it still doesn't fit - if abbrev and name and strlen(name) > size then - name = string.gsub(name, "(%S+) ", abbrevname) - end - - return name - end - - - local function GetUnitType(red, green, blue) - if red > .9 and green < .2 and blue < .2 then - return "ENEMY_NPC" - elseif red > .9 and green > .9 and blue < .2 then - return "NEUTRAL_NPC" - elseif red < .2 and green < .2 and blue > 0.9 then - return "FRIENDLY_PLAYER" - elseif red < .2 and green > .9 and blue < .2 then - return "FRIENDLY_NPC" - end - end - - local filter, list, cache - local function DebuffFilterPopulate() - -- initialize variables - filter = C.nameplates["debuffs"]["filter"] - if filter == "none" then return end - list = C.nameplates["debuffs"][filter] - cache = {} - - -- populate list - for _, val in pairs({strsplit("#", list)}) do - cache[strlower(val)] = true - end - end - - local function DebuffFilter(effect) - if filter == "none" then return true end - if not cache then DebuffFilterPopulate() end - - if filter == "blacklist" and cache[strlower(effect)] then - return nil - elseif filter == "blacklist" then - return true - elseif filter == "whitelist" and cache[strlower(effect)] then - return true - elseif filter == "whitelist" then - return nil - end - end - - local function PlateCacheDebuffs(self, unitstr, verify) - if not self.debuffcache then self.debuffcache = {} end - - for id = 1, 16 do - local effect, _, texture, stacks, _, duration, timeleft - - if unitstr and C.nameplates.selfdebuff == "1" then - effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, id) - else - effect, _, texture, stacks, _, duration, timeleft = libdebuff:UnitDebuff(unitstr, id) - end - - if effect and timeleft and timeleft > 0 then - local start = GetTime() - ( (duration or 0) - ( timeleft or 0) ) - local stop = GetTime() + ( timeleft or 0 ) - self.debuffcache[id] = self.debuffcache[id] or {} - self.debuffcache[id].effect = effect - self.debuffcache[id].texture = texture - self.debuffcache[id].stacks = stacks - self.debuffcache[id].duration = duration or 0 - self.debuffcache[id].start = start - self.debuffcache[id].stop = stop - self.debuffcache[id].empty = nil - end - end - - self.verify = verify - end - - local function PlateUnitDebuff(self, id) - -- break on unknown data - if not self.debuffcache then return end - if not self.debuffcache[id] then return end - if not self.debuffcache[id].stop then return end - - -- break on timeout debuffs - if self.debuffcache[id].empty then return end - if self.debuffcache[id].stop < GetTime() then return end - - -- return cached debuff - local c = self.debuffcache[id] - return c.effect, c.rank, c.texture, c.stacks, c.dtype, c.duration, (c.stop - GetTime()) - end - - local function CreateDebuffIcon(plate, index) - plate.debuffs[index] = CreateFrame("Frame", plate.platename.."Debuff"..index, plate) - plate.debuffs[index]:Hide() - plate.debuffs[index]:SetFrameLevel(1) - - plate.debuffs[index].icon = plate.debuffs[index]:CreateTexture(nil, "BACKGROUND") - plate.debuffs[index].icon:SetTexture(.3,1,.8,1) - plate.debuffs[index].icon:SetAllPoints(plate.debuffs[index]) - - plate.debuffs[index].stacks = plate.debuffs[index]:CreateFontString(nil, "OVERLAY") - plate.debuffs[index].stacks:SetAllPoints(plate.debuffs[index]) - plate.debuffs[index].stacks:SetJustifyH("RIGHT") - plate.debuffs[index].stacks:SetJustifyV("BOTTOM") - plate.debuffs[index].stacks:SetTextColor(1,1,0) - - if pfUI.client <= 11200 then - -- create a fake animation frame on vanilla to improve performance - plate.debuffs[index].cd = CreateFrame("Frame", plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index]) - plate.debuffs[index].cd:SetScript("OnUpdate", CooldownFrame_OnUpdateModel) - plate.debuffs[index].cd.AdvanceTime = DoNothing - plate.debuffs[index].cd.SetSequence = DoNothing - plate.debuffs[index].cd.SetSequenceTime = DoNothing - else - -- use regular cooldown animation frames on burning crusade and later - plate.debuffs[index].cd = CreateFrame(COOLDOWN_FRAME_TYPE, plate.platename.."Debuff"..index.."Cooldown", plate.debuffs[index], "CooldownFrameTemplate") - end - - plate.debuffs[index].cd.pfCooldownStyleAnimation = 0 - plate.debuffs[index].cd.pfCooldownType = "ALL" - end - - local function UpdateDebuffConfig(nameplate, i) - if not nameplate.debuffs[i] then return end - - -- update debuff positions - local width = tonumber(C.nameplates.width) - local debuffsize = tonumber(C.nameplates.debuffsize) - local debuffoffset = tonumber(C.nameplates.debuffoffset) - local limit = floor(width / debuffsize) - local font = C.nameplates.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default - local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size - local font_style = C.nameplates.name.fontstyle - - local aligna, alignb, offs, space - if C.nameplates.debuffs["position"] == "BOTTOM" then - aligna, alignb, offs, space = "TOPLEFT", "BOTTOMLEFT", -debuffoffset, -1 - else - aligna, alignb, offs, space = "BOTTOMLEFT", "TOPLEFT", debuffoffset, 1 - end - - nameplate.debuffs[i].stacks:SetFont(font, font_size, font_style) - nameplate.debuffs[i]:ClearAllPoints() - if i == 1 then - nameplate.debuffs[i]:SetPoint(aligna, nameplate.health, alignb, 0, offs) - elseif i <= limit then - nameplate.debuffs[i]:SetPoint("LEFT", nameplate.debuffs[i-1], "RIGHT", 1, 0) - elseif i > limit and limit > 0 then - nameplate.debuffs[i]:SetPoint(aligna, nameplate.debuffs[i-limit], alignb, 0, space) - end - - nameplate.debuffs[i]:SetWidth(tonumber(C.nameplates.debuffsize)) - nameplate.debuffs[i]:SetHeight(tonumber(C.nameplates.debuffsize)) - end - - -- create nameplate core - local nameplates = CreateFrame("Frame", "pfNameplates", UIParent) - nameplates:RegisterEvent("PLAYER_ENTERING_WORLD") - nameplates:RegisterEvent("PLAYER_TARGET_CHANGED") - nameplates:RegisterEvent("UNIT_COMBO_POINTS") - nameplates:RegisterEvent("PLAYER_COMBO_POINTS") - nameplates:RegisterEvent("UNIT_AURA") - - nameplates:SetScript("OnEvent", function() - if event == "PLAYER_ENTERING_WORLD" then - this:SetGameVariables() - else - this.eventcache = true - end - end) - - nameplates:SetScript("OnUpdate", function() - -- propagate events to all nameplates - if this.eventcache then - this.eventcache = nil - for plate in pairs(registry) do - plate.eventcache = true - end - end - - -- detect new nameplates - parentcount = WorldFrame:GetNumChildren() - if initialized < parentcount then - childs = { WorldFrame:GetChildren() } - for i = initialized + 1, parentcount do - plate = childs[i] - if IsNamePlate(plate) and not registry[plate] then - nameplates.OnCreate(plate) - registry[plate] = plate - end - end - - initialized = parentcount - end - end) - - -- combat tracker - nameplates.combat = CreateFrame("Frame") - nameplates.combat:RegisterEvent("PLAYER_ENTER_COMBAT") - nameplates.combat:RegisterEvent("PLAYER_LEAVE_COMBAT") - nameplates.combat:SetScript("OnEvent", function() - if event == "PLAYER_ENTER_COMBAT" then - this.inCombat = 1 - if PlayerFrame then PlayerFrame.inCombat = 1 end - elseif event == "PLAYER_LEAVE_COMBAT" then - this.inCombat = nil - if PlayerFrame then PlayerFrame.inCombat = nil end - end - end) - - nameplates.OnCreate = function(frame) - local parent = frame or this - platecount = platecount + 1 - platename = "pfNamePlate" .. platecount - - -- create pfUI nameplate overlay - local nameplate = CreateFrame("Button", platename, parent) - nameplate.platename = platename - nameplate:EnableMouse(0) - nameplate.parent = parent - nameplate.cache = {} - nameplate.UnitDebuff = PlateUnitDebuff - nameplate.CacheDebuffs = PlateCacheDebuffs - nameplate.original = {} - - -- create shortcuts for all known elements and disable them - nameplate.original.healthbar, nameplate.original.castbar = parent:GetChildren() - DisableObject(nameplate.original.healthbar) - DisableObject(nameplate.original.castbar) - - for i, object in pairs({parent:GetRegions()}) do - if NAMEPLATE_OBJECTORDER[i] and NAMEPLATE_OBJECTORDER[i] == "raidicon" then - nameplate[NAMEPLATE_OBJECTORDER[i]] = object - elseif NAMEPLATE_OBJECTORDER[i] then - nameplate.original[NAMEPLATE_OBJECTORDER[i]] = object - DisableObject(object) - else - DisableObject(object) - end - end - - HookScript(nameplate.original.healthbar, "OnValueChanged", nameplates.OnValueChanged) - - -- adjust sizes and scaling of the nameplate - nameplate:SetScale(UIParent:GetScale()) - - nameplate.health = CreateFrame("StatusBar", nil, nameplate) - nameplate.health:SetFrameLevel(4) -- keep above glow - nameplate.health.text = nameplate.health:CreateFontString(nil, "OVERLAY", "GameFontNormal") - nameplate.health.text:SetAllPoints() - nameplate.health.text:SetTextColor(1,1,1,1) - - nameplate.name = nameplate:CreateFontString(nil, "OVERLAY") - - nameplate.glow = nameplate:CreateTexture(nil, "BACKGROUND") - nameplate.glow:SetPoint("CENTER", nameplate.health, "CENTER", 0, 0) - nameplate.glow:SetTexture(pfUI.media["img:dot"]) - nameplate.glow:Hide() - - nameplate.guild = nameplate:CreateFontString(nil, "OVERLAY") - nameplate.guild:SetPoint("BOTTOM", nameplate.health, "BOTTOM", 0, 0) - - nameplate.level = nameplate:CreateFontString(nil, "OVERLAY") - - nameplate.raidicon:SetParent(nameplate.health) - nameplate.raidicon:SetDrawLayer("OVERLAY") - nameplate.raidicon:SetTexture(pfUI.media["img:raidicons"]) - - nameplate.totem = CreateFrame("Frame", nil, nameplate) - nameplate.totem:SetPoint("CENTER", nameplate, "CENTER", 0, 0) - nameplate.totem:SetHeight(32) - nameplate.totem:SetWidth(32) - nameplate.totem.icon = nameplate.totem:CreateTexture(nil, "OVERLAY") - nameplate.totem.icon:SetTexCoord(.078, .92, .079, .937) - nameplate.totem.icon:SetAllPoints() - CreateBackdrop(nameplate.totem) - - do -- debuffs - nameplate.debuffs = {} - CreateDebuffIcon(nameplate, 1) - end - - do -- combopoints - local combopoints = { } - for i = 1, 5 do - combopoints[i] = CreateFrame("Frame", nil, nameplate) - combopoints[i]:Hide() - combopoints[i]:SetFrameLevel(8) - combopoints[i].tex = combopoints[i]:CreateTexture("OVERLAY") - combopoints[i].tex:SetAllPoints() - - if i < 3 then - combopoints[i].tex:SetTexture(1, .3, .3, .75) - elseif i < 4 then - combopoints[i].tex:SetTexture(1, 1, .3, .75) - else - combopoints[i].tex:SetTexture(.3, 1, .3, .75) - end - end - nameplate.combopoints = combopoints - end - - do -- castbar - local castbar = CreateFrame("StatusBar", nil, nameplate.health) - castbar:Hide() - - castbar:SetScript("OnShow", function() - if C.nameplates.debuffs["position"] == "BOTTOM" then - nameplate.debuffs[1]:SetPoint("TOPLEFT", this, "BOTTOMLEFT", 0, -4) - end - end) - - castbar:SetScript("OnHide", function() - if C.nameplates.debuffs["position"] == "BOTTOM" then - nameplate.debuffs[1]:SetPoint("TOPLEFT", this:GetParent(), "BOTTOMLEFT", 0, -4) - end - end) - - castbar.text = castbar:CreateFontString("Status", "DIALOG", "GameFontNormal") - castbar.text:SetPoint("RIGHT", castbar, "LEFT", -4, 0) - castbar.text:SetNonSpaceWrap(false) - castbar.text:SetTextColor(1,1,1,.5) - - castbar.spell = castbar:CreateFontString("Status", "DIALOG", "GameFontNormal") - castbar.spell:SetPoint("CENTER", castbar, "CENTER") - castbar.spell:SetNonSpaceWrap(false) - castbar.spell:SetTextColor(1,1,1,1) - - castbar.icon = CreateFrame("Frame", nil, castbar) - castbar.icon.tex = castbar.icon:CreateTexture(nil, "BORDER") - castbar.icon.tex:SetAllPoints() - - nameplate.castbar = castbar - end - - parent.nameplate = nameplate - HookScript(parent, "OnShow", nameplates.OnShow) - HookScript(parent, "OnUpdate", nameplates.OnUpdate) - - nameplates.OnConfigChange(parent) - nameplates.OnShow(parent) - end - - nameplates.OnConfigChange = function(frame) - local parent = frame - local nameplate = frame.nameplate - - local font = C.nameplates.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default - local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size - local font_style = C.nameplates.name.fontstyle - local glowr, glowg, glowb, glowa = GetStringColor(C.nameplates.glowcolor) - local hlr, hlg, hlb, hla = GetStringColor(C.nameplates.highlightcolor) - local hptexture = pfUI.media[C.nameplates.healthtexture] - local rawborder, default_border = GetBorderSize("nameplates") - - local plate_width = C.nameplates.width + 50 - local plate_height = C.nameplates.heighthealth + font_size + 5 - local plate_height_cast = C.nameplates.heighthealth + font_size + 5 + C.nameplates.heightcast + 5 - local combo_size = 5 - - local width = tonumber(C.nameplates.width) - local debuffsize = tonumber(C.nameplates.debuffsize) - local healthoffset = tonumber(C.nameplates.health.offset) - local orientation = C.nameplates.verticalhealth == "1" and "VERTICAL" or "HORIZONTAL" - - local c = combatstate -- load combat state colors - c.CASTING.r, c.CASTING.g, c.CASTING.b, c.CASTING.a = GetStringColor(C.nameplates.combatcasting) - c.THREAT.r, c.THREAT.g, c.THREAT.b, c.THREAT.a = GetStringColor(C.nameplates.combatthreat) - c.NOTHREAT.r, c.NOTHREAT.g, c.NOTHREAT.b, c.NOTHREAT.a = GetStringColor(C.nameplates.combatnothreat) - c.STUN.r, c.STUN.g, c.STUN.b, c.STUN.a = GetStringColor(C.nameplates.combatstun) - - -- Get name offset values from config - local nameOffsetX = tonumber(C.nameplates.nameoffsetx) - local nameOffsetY = tonumber(C.nameplates.nameoffsety) - local levelOffsetX = tonumber(C.nameplates.leveloffsetx) - local levelOffsetY = tonumber(C.nameplates.leveloffsety) - - nameplate:SetWidth(plate_width) - nameplate:SetHeight(plate_height) - nameplate:SetPoint("TOP", parent, "TOP", 0, 0) - - - nameplate.health:SetOrientation(orientation) - nameplate.health:SetPoint("TOP", nameplate, "BOTTOM", 0, healthoffset) - nameplate.health:SetStatusBarTexture(hptexture) - nameplate.health:SetWidth(C.nameplates.width) - nameplate.health:SetHeight(C.nameplates.heighthealth) - nameplate.health.hlr, nameplate.health.hlg, nameplate.health.hlb, nameplate.health.hla = hlr, hlg, hlb, hla - - nameplate.name:SetFont(font, font_size, font_style) - nameplate.name:SetPoint("TOP", nameplate.health, "TOP", nameOffsetX, nameOffsetY) - - CreateBackdrop(nameplate.health, default_border) - - nameplate.health.text:SetFont(font, font_size - 2, "OUTLINE") - nameplate.health.text:SetJustifyH(C.nameplates.hptextpos) - - nameplate.guild:SetFont(font, font_size, font_style) - nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) - - - nameplate.glow:SetWidth(C.nameplates.width + 60) - nameplate.glow:SetHeight(C.nameplates.heighthealth + 30) - nameplate.glow:SetVertexColor(glowr, glowg, glowb, glowa) - - nameplate.raidicon:ClearAllPoints() - nameplate.raidicon:SetPoint(C.nameplates.raidiconpos, nameplate.health, C.nameplates.raidiconpos, C.nameplates.raidiconoffx, C.nameplates.raidiconoffy) - nameplate.level:SetFont(font, font_size, font_style) - DEFAULT_CHAT_FRAME:AddMessage("Level offset: "..tostring(levelOffsetX).." "..tostring(levelOffsetY)) - - nameplate.level:SetPoint("RIGHT", nameplate.health, "LEFT", levelOffsetX, levelOffsetY) - - nameplate.raidicon:SetWidth(C.nameplates.raidiconsize) - nameplate.raidicon:SetHeight(C.nameplates.raidiconsize) - - for i=1,16 do - UpdateDebuffConfig(nameplate, i) - end - - for i=1,5 do - nameplate.combopoints[i]:SetWidth(combo_size) - nameplate.combopoints[i]:SetHeight(combo_size) - nameplate.combopoints[i]:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", -(i-1)*(combo_size+default_border*3), -default_border*3) - CreateBackdrop(nameplate.combopoints[i], default_border) - end - - nameplate.castbar:SetPoint("TOPLEFT", nameplate.health, "BOTTOMLEFT", 0, -default_border*3) - nameplate.castbar:SetPoint("TOPRIGHT", nameplate.health, "BOTTOMRIGHT", 0, -default_border*3) - nameplate.castbar:SetHeight(C.nameplates.heightcast) - nameplate.castbar:SetStatusBarTexture(hptexture) - nameplate.castbar:SetStatusBarColor(.9,.8,0,1) - CreateBackdrop(nameplate.castbar, default_border) - - nameplate.castbar.text:SetFont(font, font_size, "OUTLINE") - nameplate.castbar.spell:SetFont(font, font_size, "OUTLINE") - nameplate.castbar.icon:SetPoint("BOTTOMLEFT", nameplate.castbar, "BOTTOMRIGHT", default_border*3, 0) - nameplate.castbar.icon:SetPoint("TOPLEFT", nameplate.health, "TOPRIGHT", default_border*3, 0) - nameplate.castbar.icon:SetWidth(C.nameplates.heightcast + default_border*3 + C.nameplates.heighthealth) - CreateBackdrop(nameplate.castbar.icon, default_border) - - nameplates:OnDataChanged(nameplate) - end - - nameplates.OnValueChanged = function(arg1) - nameplates:OnDataChanged(this:GetParent().nameplate) - end - - nameplates.OnDataChanged = function(self, plate) - local visible = plate:IsVisible() - local hp = plate.original.healthbar:GetValue() - local hpmin, hpmax = plate.original.healthbar:GetMinMaxValues() - local name = plate.original.name:GetText() - local level = plate.original.level:IsShown() and plate.original.level:GetObjectType() == "FontString" and tonumber(plate.original.level:GetText()) or "??" - local class, ulevel, elite, player, guild = GetUnitData(name, true) - local target = plate.istarget - local mouseover = UnitExists("mouseover") and plate.original.glow:IsShown() or nil - local unitstr = target and "target" or mouseover and "mouseover" or nil - local red, green, blue = plate.original.healthbar:GetStatusBarColor() - local unittype = GetUnitType(red, green, blue) or "ENEMY_NPC" - local font_size = C.nameplates.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size - - -- use superwow unit guid as unitstr if possible - if superwow_active and not unitstr then - unitstr = plate.parent:GetName(1) - end - - -- ignore players with npc names if plate level is lower than player level - if ulevel and ulevel > (level == "??" and -1 or level) then player = nil end - - -- cache name and reset unittype on change - if plate.cache.name ~= name then - plate.cache.name = name - plate.cache.player = nil - end - - -- read and cache unittype - if plate.cache.player then - -- overwrite unittype from cache if existing - player = plate.cache.player == "PLAYER" and true or nil - elseif unitstr then - -- read unit type while unitstr is set - plate.cache.player = UnitIsPlayer(unitstr) and "PLAYER" or "NPC" - end - - if player and unittype == "ENEMY_NPC" then unittype = "ENEMY_PLAYER" end - elite = plate.original.levelicon:IsShown() and not player and "boss" or elite - if not class then plate.wait_for_scan = true end - - -- skip data updates on invisible frames - if not visible then return end - - -- target event sometimes fires too quickly, where nameplate identifiers are not - -- yet updated. So while being inside this event, we cannot trust the unitstr. - if event == "PLAYER_TARGET_CHANGED" then unitstr = nil end - - -- remove unitstr on unit name mismatch - if unitstr and UnitName(unitstr) ~= name then unitstr = nil end - - -- use mobhealth values if addon is running - if (MobHealth3 or MobHealthFrame) and target and name == UnitName('target') and MobHealth_GetTargetCurHP() then - hp = MobHealth_GetTargetCurHP() > 0 and MobHealth_GetTargetCurHP() or hp - hpmax = MobHealth_GetTargetMaxHP() > 0 and MobHealth_GetTargetMaxHP() or hpmax - end - - -- always make sure to keep plate visible - plate:Show() - - if target and C.nameplates.targetglow == "1" then - plate.glow:Show() else plate.glow:Hide() - end - - -- target indicator - if superwow_active and C.nameplates.outcombatstate == "1" then - local guid = plate.parent:GetName(1) or "" - - -- determine color based on combat state - local color = GetCombatStateColor(guid) - if not color then color = combatstate.NONE end - - -- set border color - plate.health.backdrop:SetBackdropBorderColor(color.r, color.g, color.b, color.a) - elseif target and C.nameplates.targethighlight == "1" then - plate.health.backdrop:SetBackdropBorderColor(plate.health.hlr, plate.health.hlg, plate.health.hlb, plate.health.hla) - elseif C.nameplates.outfriendlynpc == "1" and unittype == "FRIENDLY_NPC" then - plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) - elseif C.nameplates.outfriendly == "1" and unittype == "FRIENDLY_PLAYER" then - plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) - elseif C.nameplates.outneutral == "1" and strfind(unittype, "NEUTRAL") then - plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) - elseif C.nameplates.outenemy == "1" and strfind(unittype, "ENEMY") then - plate.health.backdrop:SetBackdropBorderColor(unpack(unitcolors[unittype])) - else - plate.health.backdrop:SetBackdropBorderColor(er,eg,eb,ea) - end - - -- hide frames according to the configuration - local TotemIcon = TotemPlate(name) - - if TotemIcon then - -- create totem icon - plate.totem.icon:SetTexture("Interface\\Icons\\" .. TotemIcon) - - plate.glow:Hide() - plate.level:Hide() - plate.name:Hide() - plate.health:Hide() - plate.guild:Hide() - plate.totem:Show() - elseif HidePlate(unittype, name, (hpmax-hp == hpmin), target) then - plate.level:SetPoint("RIGHT", plate.name, "LEFT", -3, 0) - plate.name:SetParent(plate) - plate.guild:SetPoint("BOTTOM", plate.name, "BOTTOM", -2, -(font_size + 2)) - - plate.level:Show() - plate.name:Show() - plate.health:Hide() - if guild and C.nameplates.showguildname == "1" then - plate.glow:SetPoint("CENTER", plate.name, "CENTER", 0, -(font_size / 2) - 2) - else - plate.glow:SetPoint("CENTER", plate.name, "CENTER", 0, 0) - end - plate.totem:Hide() - else - plate.level:SetPoint("RIGHT", plate.health, "LEFT", -5, 0) - plate.name:SetParent(plate.health) - plate.guild:SetPoint("BOTTOM", plate.health, "BOTTOM", 0, -(font_size + 4)) - - plate.level:Show() - plate.name:Show() - plate.health:Show() - plate.glow:SetPoint("CENTER", plate.health, "CENTER", 0, 0) - plate.totem:Hide() - end - - plate.name:SetText(GetNameString(name)) - plate.level:SetText(string.format("%s%s", level, (elitestrings[elite] or ""))) - - if guild and C.nameplates.showguildname == "1" then - plate.guild:SetText(guild) - if guild == GetGuildInfo("player") then - plate.guild:SetTextColor(0, 0.9, 0, 1) - else - plate.guild:SetTextColor(0.8, 0.8, 0.8, 1) - end - plate.guild:Show() - else - plate.guild:Hide() - end - - plate.health:SetMinMaxValues(hpmin, hpmax) - plate.health:SetValue(hp) - - if C.nameplates.showhp == "1" then - local rhp, rhpmax, estimated - if hpmax > 100 or (round(hpmax/100*hp) ~= hp) then - rhp, rhpmax = hp, hpmax - elseif pfUI.libhealth and pfUI.libhealth.enabled then - rhp, rhpmax, estimated = pfUI.libhealth:GetUnitHealthByName(name,level,tonumber(hp),tonumber(hpmax)) - end - - local setting = C.nameplates.hptextformat - local hasdata = ( estimated or hpmax > 100 or (round(hpmax/100*hp) ~= hp) ) - - if setting == "curperc" and hasdata then - plate.health.text:SetText(string.format("%s | %s%%", Abbreviate(rhp), ceil(hp/hpmax*100))) - elseif setting == "cur" and hasdata then - plate.health.text:SetText(string.format("%s", Abbreviate(rhp))) - elseif setting == "curmax" and hasdata then - plate.health.text:SetText(string.format("%s - %s", Abbreviate(rhp), Abbreviate(rhpmax))) - elseif setting == "curmaxs" and hasdata then - plate.health.text:SetText(string.format("%s / %s", Abbreviate(rhp), Abbreviate(rhpmax))) - elseif setting == "curmaxperc" and hasdata then - plate.health.text:SetText(string.format("%s - %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))) - elseif setting == "curmaxpercs" and hasdata then - plate.health.text:SetText(string.format("%s / %s | %s%%", Abbreviate(rhp), Abbreviate(rhpmax), ceil(hp/hpmax*100))) - elseif setting == "deficit" then - plate.health.text:SetText(string.format("-%s" .. (hasdata and "" or "%%"), Abbreviate(rhpmax - rhp))) - else -- "percent" as fallback - plate.health.text:SetText(string.format("%s%%", ceil(hp/hpmax*100))) - end - else - plate.health.text:SetText() - end - - local r, g, b, a = unpack(unitcolors[unittype]) - - if unittype == "ENEMY_PLAYER" and C.nameplates["enemyclassc"] == "1" and class and RAID_CLASS_COLORS[class] then - r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1 - elseif unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassc"] == "1" and class and RAID_CLASS_COLORS[class] then - r, g, b, a = RAID_CLASS_COLORS[class].r, RAID_CLASS_COLORS[class].g, RAID_CLASS_COLORS[class].b, 1 - end - - if superwow_active and unitstr and UnitIsTapped(unitstr) and not UnitIsTappedByPlayer(unitstr) then - r, g, b, a = .5, .5, .5, .8 - end - - if superwow_active and C.nameplates.barcombatstate == "1" then - local guid = plate.parent:GetName(1) or "" - local color = GetCombatStateColor(guid) - - if color then - r, g, b, a = color.r, color.g, color.b, color.a - end - end - - if r ~= plate.cache.r or g ~= plate.cache.g or b ~= plate.cache.b then - plate.health:SetStatusBarColor(r, g, b, a) - plate.cache.r, plate.cache.g, plate.cache.b = r, g, b - end - - if r + g + b ~= plate.cache.namecolor and unittype == "FRIENDLY_PLAYER" and C.nameplates["friendclassnamec"] == "1" and class and RAID_CLASS_COLORS[class] then - plate.name:SetTextColor(r, g, b, a) - plate.cache.namecolor = r + g + b - end - - -- update combopoints - for i=1, 5 do plate.combopoints[i]:Hide() end - if target and C.nameplates.cpdisplay == "1" then - for i=1, GetComboPoints("target") do plate.combopoints[i]:Show() end - end - - -- update debuffs - local index = 1 - - if C.nameplates["showdebuffs"] == "1" then - local verify = string.format("%s:%s", (name or ""), (level or "")) - - -- update cached debuffs - if C.nameplates["guessdebuffs"] == "1" and unitstr then - plate:CacheDebuffs(unitstr, verify) - end - - -- update all debuff icons - for i = 1, 16 do - local effect, rank, texture, stacks, dtype, duration, timeleft - - if unitstr and C.nameplates.selfdebuff == "1" then - effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitOwnDebuff(unitstr, i) - elseif unitstr then - effect, rank, texture, stacks, dtype, duration, timeleft = libdebuff:UnitDebuff(unitstr, i) - elseif plate.verify == verify then - effect, rank, texture, stacks, dtype, duration, timeleft = plate:UnitDebuff(i) - end - - if effect and texture and DebuffFilter(effect) then - if not plate.debuffs[index] then - CreateDebuffIcon(plate, index) - UpdateDebuffConfig(plate, index) - end - - plate.debuffs[index]:Show() - plate.debuffs[index].icon:SetTexture(texture) - plate.debuffs[index].icon:SetTexCoord(.078, .92, .079, .937) - - if stacks and stacks > 1 and C.nameplates.debuffs["showstacks"] == "1" then - plate.debuffs[index].stacks:SetText(stacks) - plate.debuffs[index].stacks:Show() - else - plate.debuffs[index].stacks:Hide() - end - - if duration and timeleft and debuffdurations then - plate.debuffs[index].cd:SetAlpha(0) - plate.debuffs[index].cd:Show() - CooldownFrame_SetTimer(plate.debuffs[index].cd, GetTime() + timeleft - duration, duration, 1) - end - - index = index + 1 - end - end - end - - -- hide remaining debuffs - for i = index, 16 do - if plate.debuffs[i] then - plate.debuffs[i]:Hide() - end - end - end - - nameplates.OnShow = function(frame) - local frame = frame or this - local nameplate = frame.nameplate - - nameplates:OnDataChanged(nameplate) - end - - nameplates.OnUpdate = function(frame) - local update - local frame = frame or this - local nameplate = frame.nameplate - local original = nameplate.original - local name = original.name:GetText() - local target = UnitExists("target") and frame:GetAlpha() == 1 or nil - local mouseover = UnitExists("mouseover") and original.glow:IsShown() or nil - local namefightcolor = C.nameplates.namefightcolor == "1" - - -- trigger queued event update - if nameplate.eventcache then - nameplates:OnDataChanged(nameplate) - nameplate.eventcache = nil - end - - -- reset strata cache on target change - if nameplate.istarget ~= target then - nameplate.target_strata = nil - end - - -- keep target nameplate above others - if target and nameplate.target_strata ~= 1 then - nameplate:SetFrameStrata("LOW") - nameplate.target_strata = 1 - elseif not target and nameplate.target_strata ~= 0 then - nameplate:SetFrameStrata("BACKGROUND") - nameplate.target_strata = 0 - end - - -- cache target value - nameplate.istarget = target - - -- set non-target plate alpha - if target or not UnitExists("target") then - nameplate:SetAlpha(1) - else - frame:SetAlpha(.95) - nameplate:SetAlpha(tonumber(C.nameplates.notargalpha)) - end - - -- queue update on visual target update - if nameplate.cache.target ~= target then - nameplate.cache.target = target - update = true - end - - -- queue update on visual mouseover update - if nameplate.cache.mouseover ~= mouseover then - nameplate.cache.mouseover = mouseover - update = true - end - - -- trigger update when unit was found - if nameplate.wait_for_scan and GetUnitData(name, true) then - nameplate.wait_for_scan = nil - update = true - end - - -- trigger update when name color changed - local r, g, b = original.name:GetTextColor() - if r + g + b ~= nameplate.cache.namecolor then - nameplate.cache.namecolor = r + g + b - - if namefightcolor then - if r > .9 and g < .2 and b < .2 then - nameplate.name:SetTextColor(1,0.4,0.2,1) -- infight - else - nameplate.name:SetTextColor(r,g,b,1) - end - else - nameplate.name:SetTextColor(1,1,1,1) - end - update = true - end - - -- trigger update when level color changed - local r, g, b = original.level:GetTextColor() - r, g, b = r + .3, g + .3, b + .3 - if r + g + b ~= nameplate.cache.levelcolor then - nameplate.cache.levelcolor = r + g + b - nameplate.level:SetTextColor(r,g,b,1) - update = true - end - - -- scan for debuff timeouts - if nameplate.debuffcache then - for id, data in pairs(nameplate.debuffcache) do - if ( not data.stop or data.stop < GetTime() ) and not data.empty then - data.empty = true - update = true - end - end - end - - -- use timer based updates - if not nameplate.tick or nameplate.tick < GetTime() then - update = true - end - - -- run full updates if required - if update then - nameplates:OnDataChanged(nameplate) - nameplate.tick = GetTime() + .5 - end - - -- target zoom - local w, h = nameplate.health:GetWidth(), nameplate.health:GetHeight() - if target and C.nameplates.targetzoom == "1" then - local zoomval = tonumber(C.nameplates.targetzoomval)+1 - local wc = tonumber(C.nameplates.width)*zoomval - local hc = tonumber(C.nameplates.heighthealth)*(zoomval*.9) - local animation = false - - if wc >= w then - wc = w*1.05 - nameplate.health:SetWidth(wc) - nameplate.health.zoomTransition = true - animation = true - end - - if hc >= h then - hc = h*1.05 - nameplate.health:SetHeight(hc) - nameplate.health.zoomTransition = true - animation = true - end - - if animation == false and not nameplate.health.zoomed then - nameplate.health:SetWidth(wc) - nameplate.health:SetHeight(hc) - nameplate.health.zoomTransition = nil - nameplate.health.zoomed = true - end - elseif nameplate.health.zoomed or nameplate.health.zoomTransition then - local wc = tonumber(C.nameplates.width) - local hc = tonumber(C.nameplates.heighthealth) - local animation = false - - if wc <= w then - wc = w*.95 - nameplate.health:SetWidth(wc) - animation = true - end - - if hc <= h then - hc = h*0.95 - nameplate.health:SetHeight(hc) - animation = true - end - - if animation == false then - nameplate.health:SetWidth(wc) - nameplate.health:SetHeight(hc) - nameplate.health.zoomTransition = nil - nameplate.health.zoomed = nil - end - end - - -- castbar update - if C.nameplates["showcastbar"] == "1" and ( C.nameplates["targetcastbar"] == "0" or target ) then - local channel, cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill - - -- detect cast or channel bars - cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(target and "target" or name) - if not cast then channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(target and "target" or name) end - - -- read enemy casts from SuperWoW if enabled - if superwow_active then - cast, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(nameplate.parent:GetName(1)) - if not cast then channel, nameSubtext, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo(nameplate.parent:GetName(1)) end - end - - if not cast and not channel then - nameplate.castbar:Hide() - elseif cast or channel then - local effect = cast or channel - local duration = endTime - startTime - local max = duration / 1000 - local cur = GetTime() - startTime / 1000 - - -- invert castbar values while channeling - if channel then cur = max + startTime/1000 - GetTime() end - - nameplate.castbar:SetMinMaxValues(0, duration/1000) - nameplate.castbar:SetValue(cur) - nameplate.castbar.text:SetText(round(cur,1)) - if C.nameplates.spellname == "1" then - nameplate.castbar.spell:SetText(effect) - else - nameplate.castbar.spell:SetText("") - end - nameplate.castbar:Show() - - if texture then - nameplate.castbar.icon.tex:SetTexture(texture) - nameplate.castbar.icon.tex:SetTexCoord(.1,.9,.1,.9) - end - end - else - nameplate.castbar:Hide() - end - end - - -- set nameplate game settings - nameplates.SetGameVariables = function() - -- update visibility (hostile) - if C.nameplates["showhostile"] == "1" then - _G.NAMEPLATES_ON = true - ShowNameplates() - else - _G.NAMEPLATES_ON = nil - HideNameplates() - end - - -- update visibility (hostile) - if C.nameplates["showfriendly"] == "1" then - _G.FRIENDNAMEPLATES_ON = true - ShowFriendNameplates() - else - _G.FRIENDNAMEPLATES_ON = nil - HideFriendNameplates() - end - end - - nameplates:SetGameVariables() - - nameplates.UpdateConfig = function() - -- update debuff filters - DebuffFilterPopulate() - - -- update nameplate visibility - nameplates:SetGameVariables() - - -- apply all config changes - for plate in pairs(registry) do - nameplates.OnConfigChange(plate) - end - end - - if pfUI.client <= 11200 then - -- handle vanilla only settings - -- due to the secured lua api, those settings can't be applied to TBC and later. - local hookOnConfigChange = nameplates.OnConfigChange - nameplates.OnConfigChange = function(self) - hookOnConfigChange(self) - - local parent = self - local nameplate = self.nameplate - local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and nameplate or parent - - -- disable all clicks for now - parent:EnableMouse(false) - nameplate:EnableMouse(false) - - -- adjust vertical offset - if C.nameplates["vertical_offset"] ~= "0" then - nameplate:SetPoint("TOP", parent, "TOP", 0, tonumber(C.nameplates["vertical_offset"])) - end - - -- replace clickhandler - if C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0" then - plate:SetScript("OnClick", function() parent:Click() end) - end - - -- enable mouselook on rightbutton down - if C.nameplates["rightclick"] == "1" then - plate:SetScript("OnMouseDown", nameplates.mouselook.OnMouseDown) - else - plate:SetScript("OnMouseDown", nil) - end - end - - local hookOnDataChanged = nameplates.OnDataChanged - nameplates.OnDataChanged = function(self, nameplate) - hookOnDataChanged(self, nameplate) - - -- make sure to keep mouse events disabled on parent nameplate - if (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") then - nameplate.parent:EnableMouse(false) - end - end - - local hookOnUpdate = nameplates.OnUpdate - nameplates.OnUpdate = function(self) - -- initialize shortcut variables - local plate = (C.nameplates["overlap"] == "1" or C.nameplates["vertical_offset"] ~= "0") and this.nameplate or this - local clickable = C.nameplates["clickthrough"] ~= "1" and true or false - - -- disable all click events - if not clickable then - this:EnableMouse(false) - this.nameplate:EnableMouse(false) - else - plate:EnableMouse(clickable) - end - - if C.nameplates["overlap"] == "1" then - if this:GetWidth() > 1 then - -- set parent to 1 pixel to have them overlap each other - this:SetWidth(1) - this:SetHeight(1) - end - else - if not this.nameplate.dwidth then - -- cache initial sizing value for comparison - this.nameplate.dwidth = floor(this.nameplate:GetWidth() * UIParent:GetScale()) - end - - if floor(this:GetWidth()) ~= this.nameplate.dwidth then - -- align parent plate to the actual size - this:SetWidth(this.nameplate:GetWidth() * UIParent:GetScale()) - this:SetHeight(this.nameplate:GetHeight() * UIParent:GetScale()) - end - end - - -- disable click events while spell is targeting - local mouseEnabled = this.nameplate:IsMouseEnabled() - if C.nameplates["clickthrough"] == "0" and C.nameplates["overlap"] == "1" and SpellIsTargeting() == mouseEnabled then - this.nameplate:EnableMouse(not mouseEnabled) - end - - hookOnUpdate(self) - end - - -- enable mouselook on rightbutton down - nameplates.mouselook = CreateFrame("Frame", nil, UIParent) - nameplates.mouselook.time = nil - nameplates.mouselook.frame = nil - nameplates.mouselook.OnMouseDown = function() - if arg1 and arg1 == "RightButton" then - MouselookStart() - - -- start detection of the rightclick emulation - nameplates.mouselook.time = GetTime() - nameplates.mouselook.frame = this - nameplates.mouselook:Show() - end - end - - nameplates.mouselook:SetScript("OnUpdate", function() - -- break here if nothing to do - if not this.time or not this.frame then - this:Hide() - return - end - - -- if threshold is reached (0.5 second) no click action will follow - if not IsMouselooking() and this.time + tonumber(C.nameplates["clickthreshold"]) < GetTime() then - this:Hide() - return - end - - -- run a usual nameplate rightclick action - if not IsMouselooking() then - this.frame:Click("LeftButton") - if UnitCanAttack("player", "target") and not nameplates.combat.inCombat then AttackTarget() end - this:Hide() - return - end - end) - end - - pfUI.nameplates = nameplates -end) From 9ebac88dc6d9858072535608ea42768e772d928f Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Wed, 11 Jun 2025 21:21:06 +0200 Subject: [PATCH 08/10] Remove unnecessary whitespace in GUI module for cleaner code --- modules/gui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gui.lua b/modules/gui.lua index eb2e382e1..fd8b2625b 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -1560,7 +1560,7 @@ pfUI:RegisterModule("gui", "vanilla:tbc", function () CreateConfig(U["infight"], T["Enable Low Health Glow Effects On Screen Edges"], C.appearance.infight, "health", "checkbox") CreateConfig(U["infight"], T["Screen Edge Glow Intensity"], C.appearance.infight, "intensity", "dropdown", pfUI.gui.dropdowns.glowintensity) end) - + CreateGUIEntry(T["Settings"], T["Cooldown"], function() CreateConfig(U["buff"], T["Show Milliseconds When Timer Runs Out"], C.appearance.cd, "milliseconds", "checkbox") CreateConfig(nil, T["Cooldown Color (Less than 3 Sec)"], C.appearance.cd, "lowcolor", "color") From 712cf86214095153de4aac344f5c5f0050a5219a Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Wed, 11 Jun 2025 21:23:12 +0200 Subject: [PATCH 09/10] Remove unused font file 'Francois.ttf' --- fonts/Francois.ttf | Bin 60580 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fonts/Francois.ttf diff --git a/fonts/Francois.ttf b/fonts/Francois.ttf deleted file mode 100644 index 98e0ffa8c47b00f6f1eeb11fdaf396bb54d223c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60580 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 bzlZSTA}FP{J)InD-23O7a^H4j{|)~a{~H<4 From fff82677bfa2d12b7b7f396760e28b33267f5bc2 Mon Sep 17 00:00:00 2001 From: yannick wellens Date: Wed, 11 Jun 2025 21:24:12 +0200 Subject: [PATCH 10/10] Remove unnecessary blank lines in nameplates module for cleaner code --- modules/nameplates.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/nameplates.lua b/modules/nameplates.lua index 993663d40..92cf518b0 100644 --- a/modules/nameplates.lua +++ b/modules/nameplates.lua @@ -550,8 +550,6 @@ pfUI:RegisterModule("nameplates", "vanilla:tbc", function () nameplate.raidicon:ClearAllPoints() nameplate.raidicon:SetPoint(C.nameplates.raidiconpos, nameplate.health, C.nameplates.raidiconpos, C.nameplates.raidiconoffx, C.nameplates.raidiconoffy) - - nameplate.raidicon:SetWidth(C.nameplates.raidiconsize) nameplate.raidicon:SetHeight(C.nameplates.raidiconsize)