573 lines
15 KiB
Lua
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)
|