Files
2023-05-26 00:47:20 -07:00

573 lines
15 KiB
Lua

local E, L, V, P, G = unpack(ElvUI)
local ENP = E:NewModule("Enhanced_NamePlates", "AceHook-3.0", "AceEvent-3.0")
local NP = E:GetModule("NamePlates")
local M = E:GetModule("Misc")
local CH = E:GetModule("Chat")
local _G = _G
local ipairs, next, pairs = ipairs, next, pairs
local sub, gsub, floor = string.sub, string.gsub, math.floor
local match, gmatch, format, lower = string.match, string.gmatch, string.format, string.lower
local tinsert, tremove = table.insert, table.remove
local GetGuildInfo = GetGuildInfo
local IsInGuild = IsInGuild
local IsInInstance = IsInInstance
local IsResting = IsResting
local UnitClass = UnitClass
local UnitInParty = UnitInParty
local UnitInRaid = UnitInRaid
local UnitIsPlayer = UnitIsPlayer
local UnitName = UnitName
local UnitPlayerControlled = UnitPlayerControlled
local UnitReaction = UnitReaction
local UNKNOWN = UNKNOWN
local classMap = {}
local guildMap = {}
local npcTitleMap = {}
local function UpdateNameplateByName(name)
for frame in pairs(NP.VisiblePlates) do
if frame.UnitName == name then
NP.OnShow(frame:GetParent(), nil, true)
end
end
end
function ENP:UPDATE_MOUSEOVER_UNIT()
if UnitIsPlayer("mouseover") and UnitReaction("mouseover", "player") ~= 2 then
local name, realm = UnitName("mouseover")
if realm or not name or name == UNKNOWN then return end
if E.db.enhanced.nameplates.classCache then
local _, class = UnitClass("mouseover")
class = classMap[class]
if EnhancedDB.UnitClass[name] ~= class then
EnhancedDB.UnitClass[name] = class
end
end
if E.db.enhanced.nameplates.titleCache then
local guildName = GetGuildInfo("mouseover")
if not guildName then
if EnhancedDB.UnitTitle[name] then
EnhancedDB.UnitTitle[name] = nil
UpdateNameplateByName(name)
end
return
end
if not guildMap[guildName] then
tinsert(EnhancedDB.GuildList, guildName)
guildMap[guildName] = #EnhancedDB.GuildList
end
if EnhancedDB.UnitTitle[name] ~= guildMap[guildName] then
EnhancedDB.UnitTitle[name] = guildMap[guildName]
UpdateNameplateByName(name)
end
end
else
self.scanner:ClearLines()
self.scanner:SetUnit("mouseover")
local name = _G["Enhanced_ScanningTooltipTextLeft1"]:GetText()
if not name then return end
local description = _G["Enhanced_ScanningTooltipTextLeft2"]:GetText()
if not description then return end
if match(description, UNIT_LEVEL_TEMPLATE) then return end
name = gsub(gsub((name), "|c........", "" ), "|r", "")
if name ~= UnitName("mouseover") then return end
if UnitPlayerControlled("mouseover") then return end
if not npcTitleMap[description] then
tinsert(EnhancedDB.NPCList, description)
npcTitleMap[description] = #EnhancedDB.NPCList
end
if EnhancedDB.UnitTitle[name] ~= npcTitleMap[description] then
EnhancedDB.UnitTitle[name] = npcTitleMap[description]
UpdateNameplateByName(name)
end
end
end
-- Class Cache
local grenColorToClass = {}
for class, color in pairs(RAID_CLASS_COLORS) do
grenColorToClass[color.g] = class
end
local function UnitClassHook(self, frame, unitType)
if unitType == "FRIENDLY_PLAYER" then
local unitName = frame.UnitName
local unit = self[unitType][unitName]
if unit then
local _, class = UnitClass(unit)
if class then
return class
end
elseif EnhancedDB.UnitClass[unitName] then
return CLASS_SORT_ORDER[EnhancedDB.UnitClass[unitName]]
else
return NP:GetUnitClassByGUID(frame)
end
elseif unitType == "ENEMY_PLAYER" then
local _, g = frame.oldHealthBar:GetStatusBarColor()
return grenColorToClass[floor(g*100 + 0.5) / 100]
end
end
function ENP:ClassCache()
if E.db.enhanced.nameplates.classCache then
if not self:IsHooked(NP, "UnitClass") then
self:RawHook(NP, "UnitClass", UnitClassHook, true)
end
else
if self:IsHooked(NP, "UnitClass") then
self:Unhook(NP, "UnitClass")
end
end
end
-- Title Cache
local separatorMap = {
[" "] = "%s",
["<"] = "<%s>",
["("] = "(%s)",
["["] = "[%s]",
["{"] = "{%s}"
}
local function Update_NameHook(self, frame)
if not E.db.enhanced.nameplates.titleCache then return end
if frame.Health:IsShown() then
if frame.Title then
frame.Title:SetText()
frame.Title:Hide()
end
return
end
local guildName = EnhancedDB.GuildList[EnhancedDB.UnitTitle[frame.UnitName]]
if frame.UnitType == "FRIENDLY_PLAYER" and guildName then
local db = E.db.enhanced.nameplates.guild
local shown
if IsResting() then
shown = db.visibility.city
else
local _, instanceType = IsInInstance()
if instanceType == "pvp" then
shown = db.visibility.pvp
elseif instanceType == "arena" then
shown = db.visibility.arena
elseif instanceType == "party" then
shown = db.visibility.party
elseif instanceType == "raid" then
shown = db.visibility.raid
else
shown = true
end
end
if shown then
if not frame.Title then
frame.Title = frame:CreateFontString(nil, "OVERLAY")
frame.Title:SetWordWrap(false)
end
frame.Title:SetFont(E.LSM:Fetch("font", db.font), db.fontSize, db.fontOutline)
local color
if UnitInRaid(frame.UnitName) then
color = db.colors.raid
elseif UnitInParty(frame.UnitName) then
color = db.colors.party
elseif IsInGuild and GetGuildInfo("player") == guildName then
color = db.colors.guild
else
color = db.colors.none
end
frame.Title:SetTextColor(color.r, color.g, color.b)
frame.Title:SetPoint("TOP", frame.Name, "BOTTOM")
frame.Title:SetFormattedText(separatorMap[db.separator], guildName)
frame.Title:Show()
elseif frame.Title then
frame.Title:Hide()
end
elseif (frame.UnitType == "FRIENDLY_NPC" or frame.UnitType == "ENEMY_NPC") and EnhancedDB.NPCList[EnhancedDB.UnitTitle[frame.UnitName]] then
if not frame.Title then
frame.Title = frame:CreateFontString(nil, "OVERLAY")
frame.Title:SetWordWrap(false)
end
local db = E.db.enhanced.nameplates.npc
frame.Title:SetFont(E.LSM:Fetch("font", db.font), db.fontSize, db.fontOutline)
if E.db.enhanced.nameplates.npc.reactionColor then
local db = self.db.colors
if frame.UnitReaction == 5 then -- friendly
r, g, b = db.reactions.good.r, db.reactions.good.g, db.reactions.good.b
elseif frame.UnitReaction == 1 or frame.UnitReaction == 2 then -- hostile
r, g, b = db.reactions.bad.r, db.reactions.bad.g, db.reactions.bad.b
elseif frame.UnitReaction == 4 then -- neutral
r, g, b = db.reactions.neutral.r, db.reactions.neutral.g, db.reactions.neutral.b
else
r, g, b = 1, 1, 1
end
frame.Title:SetTextColor(r, g, b)
else
frame.Title:SetTextColor(db.color.r, db.color.g, db.color.b)
end
frame.Title:SetPoint("TOP", frame.Name, "BOTTOM")
frame.Title:SetFormattedText(separatorMap[db.separator], EnhancedDB.NPCList[EnhancedDB.UnitTitle[frame.UnitName]])
frame.Title:Show()
elseif frame.Title then
frame.Title:SetText("")
end
end
function ENP:TitleCache()
if E.db.enhanced.nameplates.titleCache then
if not self:IsHooked(NP, "Update_Name") then
self:Hook(NP, "Update_Name", Update_NameHook)
end
else
if self:IsHooked(NP, "Update_Name") then
self:Unhook(NP, "Update_Name")
end
end
end
-- Chat Bubbles
local events = {
"CHAT_MSG_GUILD",
"CHAT_MSG_OFFICER",
"CHAT_MSG_PARTY",
"CHAT_MSG_PARTY_LEADER",
"CHAT_MSG_RAID",
"CHAT_MSG_RAID_LEADER",
"CHAT_MSG_RAID_WARNING",
"CHAT_MSG_BATTLEGROUND",
"CHAT_MSG_BATTLEGROUND_LEADER",
"CHAT_MSG_CHANNEL",
"CHAT_MSG_SAY",
"CHAT_MSG_YELL",
"CHAT_MSG_MONSTER_SAY",
"CHAT_MSG_MONSTER_YELL"
}
local bubbleList = {}
local delayFrame = CreateFrame("Frame")
delayFrame:Hide()
delayFrame:SetScript("OnUpdate", function(self, elapsed)
local i, frame = 1
while bubbleList[i] do
frame = bubbleList[i]
frame.delay = frame.delay - elapsed
if frame.delay <= 0 then
frame.delay = 0
E:UIFrameFadeOut(frame, .2, frame:GetAlpha(), 0)
tremove(bubbleList, i)
else
i = i + 1
end
end
if #bubbleList == 0 then
self:Hide()
end
end)
local function SetBubbleDelay(frame, delay)
local found
if #bubbleList > 0 then
for _, v in ipairs(bubbleList) do
if v == frame then
v.delay = delay
found = true
break
end
end
end
if not found then
frame.delay = delay
tinsert(bubbleList, frame)
delayFrame:Show()
end
end
local inactiveBubbles = {}
local function ReleaseBubble(frame)
inactiveBubbles[#inactiveBubbles + 1] = frame
frame.parent.bubbleFrame = nil
frame:Hide()
end
local function FadeClosure(frame)
if frame.fadeInfo.mode == "OUT" then
ReleaseBubble(frame)
end
end
local function CreateBubble()
local frame = CreateFrame("Frame")
frame:SetFrameStrata("BACKGROUND")
frame:Hide()
frame.text = frame:CreateFontString()
frame.text:SetJustifyH("CENTER")
frame.text:SetJustifyV("MIDDLE")
frame.text:SetWordWrap(true)
frame.text:SetNonSpaceWrap(true)
frame:SetPoint("TOPLEFT", frame.text, -15, 15)
frame:SetPoint("BOTTOMRIGHT", frame.text, 15, -15)
M:SkinBubble(frame)
frame.delay = 0
frame.FadeObject = {
finishedFuncKeep = true,
finishedArg1 = frame,
finishedFunc = FadeClosure
}
return frame
end
local function AcquireBubble()
local numInactiveObjects = #inactiveBubbles
if numInactiveObjects > 0 then
local frame = inactiveBubbles[numInactiveObjects]
inactiveBubbles[numInactiveObjects] = nil
return frame
end
return CreateBubble()
end
function ENP:AddBubbleMessage(frame, msg, author, guid)
if E.private.general.chatBubbleName then
M:AddChatBubbleName(frame, guid, author)
else
frame.Name:SetText()
end
frame.text:SetText(msg)
if frame.text:GetStringWidth() > 300 then
frame.text:SetWidth(300)
end
if E.private.chat.enable and E.private.general.classColorMentionsSpeech then
local classColorTable, lowerCaseWord, isFirstWord, rebuiltString, tempWord, wordMatch, classMatch
if msg and match(msg, "%s-%S+%s*") then
for word in gmatch(msg, "%s-%S+%s*") do
tempWord = gsub(word, "^[%s%p]-([^%s%p]+)([%-]?[^%s%p]-)[%s%p]*$","%1%2")
lowerCaseWord = lower(tempWord)
classMatch = CH.ClassNames[lowerCaseWord]
wordMatch = classMatch and lowerCaseWord
if wordMatch and not E.global.chat.classColorMentionExcludedNames[wordMatch] then
classColorTable = CUSTOM_CLASS_COLORS and CUSTOM_CLASS_COLORS[classMatch] or RAID_CLASS_COLORS[classMatch]
word = gsub(word, gsub(tempWord, "%-","%%-"), format("\124cff%.2x%.2x%.2x%s\124r", classColorTable.r*255, classColorTable.g*255, classColorTable.b*255, tempWord))
end
if not isFirstWord then
rebuiltString = word
isFirstWord = true
else
rebuiltString = format("%s%s", rebuiltString, word)
end
end
if rebuiltString ~= nil then
frame.text:SetText(rebuiltString)
end
end
end
end
function ENP:FindNameplateByChatMsg(event, msg, author, _, _, _, _, _, channelID, _, _, _, guid)
if author == UnitName("player") or not author then return end
if not next(NP.VisiblePlates) then return end
local chatType = sub(event, 10)
if sub(chatType, 1, 7) == "CHANNEL" then
chatType = "CHANNEL"..channelID
end
local info = ChatTypeInfo[chatType]
if not info then return end
for frame in pairs(NP.VisiblePlates) do
if frame.UnitName == author then
local bubbleFrame
if not frame.bubbleFrame then
bubbleFrame = AcquireBubble()
frame.bubbleFrame = bubbleFrame
bubbleFrame.text:ClearAllPoints()
bubbleFrame.text:SetPoint("BOTTOM", frame, "TOP", 0, 20)
bubbleFrame:Show()
E:UIFrameFadeIn(bubbleFrame, .2, 0, 1)
else
bubbleFrame = frame.bubbleFrame
end
bubbleFrame.parent = frame
bubbleFrame.text:SetSize(0, 0)
bubbleFrame.text:SetTextColor(info.r, info.g, info.b)
bubbleFrame.author = author
if E.private.general.chatBubbles == "backdrop" then
if E.PixelMode then
bubbleFrame:SetBackdropBorderColor(info.r, info.g, info.b)
else
local r, g, b = info.r, info.g, info.b
bubbleFrame.bordertop:SetTexture(r, g, b)
bubbleFrame.borderbottom:SetTexture(r, g, b)
bubbleFrame.borderleft:SetTexture(r, g, b)
bubbleFrame.borderright:SetTexture(r, g, b)
end
end
ENP:AddBubbleMessage(bubbleFrame, msg, author, guid)
if bubbleFrame.delay == 0 then
E:UIFrameFadeRemoveFrame(bubbleFrame)
E:UIFrameFadeIn(bubbleFrame, .2, bubbleFrame:GetAlpha(), 1)
end
local _, delayMult = gsub(msg, "%s+", "")
SetBubbleDelay(bubbleFrame, 2 + (0.5 * delayMult))
end
end
end
local function OnShowHook(frame, ...)
ENP.hooks[NP].OnShow(frame, ...)
if frame.UnitFrame.bubbleFrame then
frame.UnitFrame.bubbleFrame = nil
end
if #bubbleList > 0 then
for _, bubbleFrame in ipairs(bubbleList) do
if frame.UnitFrame.UnitName == bubbleFrame.author then
frame.UnitFrame.bubbleFrame = bubbleFrame
bubbleFrame.parent = frame.UnitFrame
bubbleFrame.text:ClearAllPoints()
bubbleFrame.text:SetPoint("BOTTOM", frame.UnitFrame, "TOP", 0, 20)
bubbleFrame:Show()
break
end
end
end
end
local function OnHideHook(frame)
if frame.UnitFrame.bubbleFrame then
frame.UnitFrame.bubbleFrame:Hide()
end
if frame.UnitFrame.Title then
frame.UnitFrame.Title:SetText()
frame.UnitFrame.Title:Hide()
end
end
function ENP:ChatBubbles()
if E.db.enhanced.nameplates.chatBubbles then
for _, event in ipairs(events) do
ENP:RegisterEvent(event, "FindNameplateByChatMsg")
end
else
for _, event in ipairs(events) do
ENP:UnregisterEvent(event)
end
end
end
function ENP:UpdateAllSettings()
self:ClassCache()
self:ChatBubbles()
self:TitleCache()
if E.db.enhanced.nameplates.titleCache or E.db.enhanced.nameplates.classCache then
if not self.scanner then
self.scanner = CreateFrame("GameTooltip", "Enhanced_ScanningTooltip", nil, "GameTooltipTemplate")
self.scanner:SetOwner(WorldFrame, "ANCHOR_NONE")
end
self:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
elseif not E.db.enhanced.nameplates.titleCache and not E.db.enhanced.nameplates.classCache then
self:UnregisterEvent("UPDATE_MOUSEOVER_UNIT")
end
if E.db.enhanced.nameplates.chatBubbles or E.db.enhanced.nameplates.titleCache then
if not ENP:IsHooked(NP, "OnHide") then
ENP:Hook(NP, "OnHide", OnHideHook, true)
end
elseif not E.db.enhanced.nameplates.chatBubbles and not E.db.enhanced.nameplates.titleCache then
if ENP:IsHooked(NP, "OnHide") then
ENP:Unhook(NP, "OnHide")
end
end
if E.db.enhanced.nameplates.chatBubbles then
if not ENP:IsHooked(NP, "OnShow") then
ENP:RawHook(NP, "OnShow", OnShowHook, true)
end
else
if ENP:IsHooked(NP, "OnShow") then
ENP:Unhook(NP, "OnShow")
end
end
end
function ENP:Initialize()
EnhancedDB.UnitClass = EnhancedDB.UnitClass or {}
EnhancedDB.UnitTitle = EnhancedDB.UnitTitle or {}
if EnhancedDB.GuildList then
for i, guildName in ipairs(EnhancedDB.GuildList) do
guildMap[guildName] = i
end
else
EnhancedDB.GuildList = {}
end
if EnhancedDB.NPCList then
for i, guildName in ipairs(EnhancedDB.NPCList) do
npcTitleMap[guildName] = i
end
else
EnhancedDB.NPCList = {}
end
for i, class in ipairs(CLASS_SORT_ORDER) do
classMap[class] = i
end
ENP:UpdateAllSettings()
end
local function InitializeCallback()
ENP:Initialize()
end
E:RegisterModule(ENP:GetName(), InitializeCallback)