第一次发版v0.1.0
This commit is contained in:
554
Auras.lua
Normal file
554
Auras.lua
Normal file
@@ -0,0 +1,554 @@
|
||||
NanamiPlates_Auras = {}
|
||||
|
||||
local NP = NanamiPlates
|
||||
local Settings = NP.Settings
|
||||
local SpellDB = NanamiPlates_SpellDB
|
||||
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local string_find = string.find
|
||||
local string_format = string.format
|
||||
local string_gsub = string.gsub
|
||||
local math_floor = math.floor
|
||||
local GetTime = GetTime
|
||||
local UnitDebuff = UnitDebuff
|
||||
local UnitGUID = UnitGUID
|
||||
local UnitLevel = UnitLevel
|
||||
local UnitName = UnitName
|
||||
local UnitExists = UnitExists
|
||||
local CreateFrame = CreateFrame
|
||||
|
||||
local _, playerClass = UnitClass("player")
|
||||
playerClass = playerClass or ""
|
||||
|
||||
local MAX_DEBUFFS = 16
|
||||
|
||||
local JUDGEMENT_EFFECTS = {
|
||||
"Judgement of Wisdom", "Judgement of Light", "Judgement of the Crusader",
|
||||
"Judgement of Justice", "Judgement"
|
||||
}
|
||||
|
||||
NanamiPlates_Auras.timers = {}
|
||||
local debuffTimers = NanamiPlates_Auras.timers
|
||||
|
||||
local function FormatTime(remaining)
|
||||
if not remaining or remaining < 0 then return "", 1, 1, 1, 1 end
|
||||
if remaining > 3600 then
|
||||
return math_floor(remaining / 3600 + 0.5) .. "h", 0.5, 0.5, 0.5, 1
|
||||
elseif remaining > 60 then
|
||||
return math_floor(remaining / 60 + 0.5) .. "m", 0.5, 0.5, 0.5, 1
|
||||
elseif remaining > 10 then
|
||||
return math_floor(remaining + 0.5) .. "", 0.7, 0.7, 0.7, 1
|
||||
elseif remaining > 5 then
|
||||
return math_floor(remaining + 0.5) .. "", 1, 1, 0, 1
|
||||
elseif remaining > 0 then
|
||||
return string_format("%.1f", remaining), 1, 0, 0, 1
|
||||
end
|
||||
return "", 1, 1, 1, 1
|
||||
end
|
||||
NanamiPlates_Auras.FormatTime = FormatTime
|
||||
|
||||
local function GetSpellData(unit, name, effect, level)
|
||||
if not SpellDB or not SpellDB.objects then return nil end
|
||||
local dataUnit = unit and SpellDB:FindEffectData(unit, level or 0, effect)
|
||||
local dataName = name and SpellDB:FindEffectData(name, level or 0, effect)
|
||||
if dataUnit and dataName then
|
||||
return (dataUnit.start or 0) >= (dataName.start or 0) and dataUnit or dataName
|
||||
end
|
||||
return dataUnit or dataName
|
||||
end
|
||||
|
||||
local function DebuffOnUpdate()
|
||||
local now = GetTime()
|
||||
if (this.tick or 0) > now then return else this.tick = now + 0.1 end
|
||||
if not this:IsShown() then return end
|
||||
if not this.expirationTime or this.expirationTime <= 0 then
|
||||
if this.cd then this.cd:SetText("") end
|
||||
return
|
||||
end
|
||||
|
||||
local timeLeft = this.expirationTime - now
|
||||
if timeLeft > 0 then
|
||||
local text, r, g, b, a = FormatTime(timeLeft)
|
||||
if this.cd then
|
||||
this.cd:SetText(text)
|
||||
if r then this.cd:SetTextColor(r, g, b, a or 1) end
|
||||
this.cd:SetAlpha(1)
|
||||
end
|
||||
else
|
||||
if this.cd then
|
||||
this.cd:SetText("")
|
||||
this.cd:SetAlpha(0)
|
||||
end
|
||||
this.expirationTime = 0
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_Auras:CreateDebuffFrames(nameplate)
|
||||
nameplate.debuffs = {}
|
||||
local plateName = nameplate:GetName() or "UnknownPlate"
|
||||
local size = Settings.debuffIconSize or 20
|
||||
|
||||
for i = 1, MAX_DEBUFFS do
|
||||
local debuff = CreateFrame("Frame", plateName .. "Debuff" .. i, nameplate)
|
||||
debuff:SetWidth(size)
|
||||
debuff:SetHeight(size)
|
||||
debuff:SetFrameLevel(nameplate.health:GetFrameLevel() + 5)
|
||||
debuff:EnableMouse(false)
|
||||
|
||||
debuff.icon = debuff:CreateTexture(nil, "ARTWORK")
|
||||
debuff.icon:SetAllPoints()
|
||||
debuff.icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
debuff.icon:SetDrawLayer("ARTWORK")
|
||||
|
||||
debuff.border = debuff:CreateTexture(nil, "BACKGROUND")
|
||||
debuff.border:SetTexture(0, 0, 0, 1)
|
||||
debuff.border:SetPoint("TOPLEFT", debuff, "TOPLEFT", -1, 1)
|
||||
debuff.border:SetPoint("BOTTOMRIGHT", debuff, "BOTTOMRIGHT", 1, -1)
|
||||
debuff.border:SetDrawLayer("BACKGROUND")
|
||||
|
||||
debuff.cdframe = CreateFrame("Frame", nil, debuff)
|
||||
debuff.cdframe:SetAllPoints(debuff)
|
||||
debuff.cdframe:SetFrameLevel(debuff:GetFrameLevel() + 2)
|
||||
debuff.cdframe:EnableMouse(false)
|
||||
|
||||
debuff.cd = debuff.cdframe:CreateFontString(nil, "OVERLAY")
|
||||
debuff.cd:SetFont(NP.GetFont(), 10, NP.GetFontOutline())
|
||||
debuff.cd:SetPoint("BOTTOM", debuff, "BOTTOM", 0, -1)
|
||||
debuff.cd:SetTextColor(1, 1, 0, 1)
|
||||
debuff.cd:SetText("")
|
||||
debuff.cd:SetDrawLayer("OVERLAY", 7)
|
||||
|
||||
debuff.count = debuff.cdframe:CreateFontString(nil, "OVERLAY")
|
||||
debuff.count:SetFont(NP.GetFont(), 9, NP.GetFontOutline())
|
||||
debuff.count:SetPoint("TOPRIGHT", debuff, "TOPRIGHT", 2, 2)
|
||||
debuff.count:SetTextColor(1, 1, 1, 1)
|
||||
debuff.count:SetText("")
|
||||
debuff.count:SetDrawLayer("OVERLAY", 7)
|
||||
|
||||
debuff:SetScript("OnUpdate", DebuffOnUpdate)
|
||||
debuff:Hide()
|
||||
nameplate.debuffs[i] = debuff
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_Auras:UpdateDebuffs(nameplate, unitstr, plateName, isTarget, hasValidGUID, superwow_active)
|
||||
local size = Settings.debuffIconSize or 20
|
||||
for i = 1, MAX_DEBUFFS do
|
||||
local debuff = nameplate.debuffs[i]
|
||||
debuff:SetWidth(size)
|
||||
debuff:SetHeight(size)
|
||||
|
||||
local cdFontSize = math_floor(size * 0.5 + 0.5)
|
||||
local countFontSize = math_floor(size * 0.4 + 0.5)
|
||||
if cdFontSize < 7 then cdFontSize = 7 end
|
||||
if countFontSize < 6 then countFontSize = 6 end
|
||||
debuff.cd:SetFont(NP.GetFont(), cdFontSize, NP.GetFontOutline())
|
||||
debuff.count:SetFont(NP.GetFont(), countFontSize, NP.GetFontOutline())
|
||||
|
||||
debuff:Hide()
|
||||
debuff.count:SetText("")
|
||||
debuff.expirationTime = 0
|
||||
end
|
||||
|
||||
local now = GetTime()
|
||||
local claimedMyDebuffs = {}
|
||||
|
||||
local effectiveUnit = (isTarget) and "target" or (superwow_active and hasValidGUID and unitstr) or nil
|
||||
if not effectiveUnit and not plateName then return 0 end
|
||||
|
||||
local scanUnit = effectiveUnit
|
||||
if not scanUnit and plateName and UnitExists("target") and UnitName("target") == plateName then
|
||||
scanUnit = "target"
|
||||
end
|
||||
if not scanUnit then return 0 end
|
||||
|
||||
-- Collect debuffs
|
||||
local collectedDebuffs = {}
|
||||
local ownerBoundCounts = {}
|
||||
local ownerBoundFirst = {}
|
||||
|
||||
for i = 1, 40 do
|
||||
local texture, stacks = UnitDebuff(scanUnit, i)
|
||||
if not texture then break end
|
||||
|
||||
local effect = SpellDB and SpellDB:ScanDebuff(scanUnit, i)
|
||||
if (not effect or effect == "") and SpellDB and SpellDB.textureToSpell then
|
||||
effect = SpellDB.textureToSpell[texture]
|
||||
end
|
||||
|
||||
-- If effect is non-empty but not in DEBUFFS (e.g. Chinese name), try fallbacks
|
||||
if SpellDB and effect and effect ~= "" and SpellDB.DEBUFFS and not SpellDB.DEBUFFS[effect] then
|
||||
-- Try texture-based lookup (covers spellbook-scanned entries)
|
||||
local texEffect = SpellDB.textureToSpell and SpellDB.textureToSpell[texture]
|
||||
if texEffect and SpellDB.DEBUFFS[texEffect] then
|
||||
-- Learn this locale mapping for future
|
||||
if SpellDB.LearnLocale then
|
||||
SpellDB:LearnLocale(effect, texEffect)
|
||||
end
|
||||
effect = texEffect
|
||||
elseif SpellDB.WARLOCK_DOT_TEXTURES and SpellDB.WARLOCK_DOT_TEXTURES[texture] then
|
||||
effect = SpellDB.WARLOCK_DOT_TEXTURES[texture]
|
||||
elseif SpellDB.WARLOCK_CURSE_TEXTURES and SpellDB.WARLOCK_CURSE_TEXTURES[texture] then
|
||||
effect = SpellDB.WARLOCK_CURSE_TEXTURES[texture]
|
||||
end
|
||||
end
|
||||
|
||||
local isOwnerBound = effect and SpellDB and SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[effect]
|
||||
|
||||
if isOwnerBound then
|
||||
ownerBoundCounts[effect] = (ownerBoundCounts[effect] or 0) + 1
|
||||
if not ownerBoundFirst[effect] then
|
||||
ownerBoundFirst[effect] = { index = i, texture = texture, stacks = stacks }
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(collectedDebuffs, {
|
||||
index = i, texture = texture, stacks = stacks,
|
||||
effect = effect, isOwnerBound = isOwnerBound
|
||||
})
|
||||
end
|
||||
|
||||
-- Display debuffs
|
||||
local debuffIndex = 1
|
||||
local displayedOwnerBound = {}
|
||||
local unitlevel = (scanUnit == "target") and UnitLevel("target") or (unitstr and UnitLevel(unitstr)) or 0
|
||||
|
||||
for _, debuffData in ipairs(collectedDebuffs) do
|
||||
if debuffIndex > MAX_DEBUFFS then break end
|
||||
|
||||
local effect = debuffData.effect
|
||||
local texture = debuffData.texture
|
||||
local stacks = debuffData.stacks
|
||||
local isOwnerBound = debuffData.isOwnerBound
|
||||
|
||||
local isRoguePoison = false
|
||||
if playerClass == "ROGUE" then
|
||||
if effect and SpellDB.ROGUE_POISONS and SpellDB.ROGUE_POISONS[effect] then
|
||||
isRoguePoison = true
|
||||
elseif texture and SpellDB.ROGUE_POISON_TEXTURES and SpellDB.ROGUE_POISON_TEXTURES[texture] then
|
||||
isRoguePoison = true
|
||||
if not effect or effect == "" then effect = SpellDB.ROGUE_POISON_TEXTURES[texture] end
|
||||
end
|
||||
end
|
||||
|
||||
local isHunterTrap = false
|
||||
if playerClass == "HUNTER" then
|
||||
if effect and SpellDB.HUNTER_TRAPS and SpellDB.HUNTER_TRAPS[effect] then
|
||||
isHunterTrap = true
|
||||
elseif texture and SpellDB.HUNTER_TRAP_TEXTURES and SpellDB.HUNTER_TRAP_TEXTURES[texture] then
|
||||
isHunterTrap = true
|
||||
effect = SpellDB.HUNTER_TRAP_TEXTURES[texture]
|
||||
end
|
||||
end
|
||||
|
||||
local isHunterSting = false
|
||||
if playerClass == "HUNTER" then
|
||||
if effect and SpellDB.HUNTER_STINGS and SpellDB.HUNTER_STINGS[effect] then
|
||||
isHunterSting = true
|
||||
elseif texture and SpellDB.HUNTER_STING_TEXTURES and SpellDB.HUNTER_STING_TEXTURES[texture] then
|
||||
isHunterSting = true
|
||||
effect = SpellDB.HUNTER_STING_TEXTURES[texture]
|
||||
end
|
||||
end
|
||||
|
||||
local isWarlockCurse = false
|
||||
if playerClass == "WARLOCK" then
|
||||
if effect and SpellDB.WARLOCK_CURSES and SpellDB.WARLOCK_CURSES[effect] then
|
||||
isWarlockCurse = true
|
||||
elseif texture and SpellDB.WARLOCK_CURSE_TEXTURES and SpellDB.WARLOCK_CURSE_TEXTURES[texture] then
|
||||
isWarlockCurse = true
|
||||
effect = SpellDB.WARLOCK_CURSE_TEXTURES[texture]
|
||||
end
|
||||
end
|
||||
|
||||
local isWarlockDot = false
|
||||
if playerClass == "WARLOCK" and not isWarlockCurse then
|
||||
if texture and SpellDB.WARLOCK_DOT_TEXTURES and SpellDB.WARLOCK_DOT_TEXTURES[texture] then
|
||||
local dotName = SpellDB.WARLOCK_DOT_TEXTURES[texture]
|
||||
if not effect or effect == "" or not SpellDB.DEBUFFS[effect] then
|
||||
effect = dotName
|
||||
end
|
||||
isWarlockDot = true
|
||||
elseif effect and (effect == "Corruption" or effect == "Siphon Life" or effect == "Immolate" or effect == "Dark Harvest") then
|
||||
isWarlockDot = true
|
||||
end
|
||||
end
|
||||
|
||||
if isWarlockDot or isWarlockCurse then
|
||||
isOwnerBound = effect and SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[effect]
|
||||
end
|
||||
|
||||
local isMyDebuff = false
|
||||
local duration, timeleft = nil, nil
|
||||
|
||||
if isRoguePoison or isHunterTrap or isHunterSting or isWarlockCurse or isWarlockDot then
|
||||
isMyDebuff = true
|
||||
end
|
||||
|
||||
if effect and effect ~= "" then
|
||||
local data = GetSpellData(unitstr, plateName, effect, unitlevel)
|
||||
if data and data.start and data.duration then
|
||||
if data.start + data.duration > now then
|
||||
duration = data.duration
|
||||
timeleft = data.duration + data.start - now
|
||||
if data.isOwn == true and not claimedMyDebuffs[effect] then
|
||||
isMyDebuff = true
|
||||
claimedMyDebuffs[effect] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if playerClass == "PALADIN" and (string_find(effect, "Judgement of ") or string_find(effect, "Seal of ") or effect == "Crusader Strike" or effect == "Hammer of Justice" or effect == "Repentance") then
|
||||
isMyDebuff = true
|
||||
claimedMyDebuffs[effect] = true
|
||||
end
|
||||
|
||||
if not timeleft then
|
||||
local dbDuration = SpellDB:GetDuration(effect, 0)
|
||||
if dbDuration > 0 then
|
||||
SpellDB:AddEffect(plateName, unitlevel, effect, dbDuration, isMyDebuff)
|
||||
if unitstr and unitstr ~= plateName then
|
||||
SpellDB:AddEffect(unitstr, unitlevel, effect, dbDuration, isMyDebuff)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if effect and effect ~= "" and not duration then
|
||||
duration = SpellDB:GetDuration(effect, 0)
|
||||
end
|
||||
|
||||
-- Display logic
|
||||
if isOwnerBound then
|
||||
if not displayedOwnerBound[effect] then
|
||||
local ownerCheckUnit = unitstr or plateName
|
||||
local isMyOwnerBound = isMyDebuff
|
||||
|
||||
if not isMyOwnerBound and SpellDB.IsOwnerBoundDebuffMine then
|
||||
isMyOwnerBound = SpellDB:IsOwnerBoundDebuffMine(ownerCheckUnit, effect)
|
||||
if not isMyOwnerBound and plateName and plateName ~= ownerCheckUnit then
|
||||
isMyOwnerBound = SpellDB:IsOwnerBoundDebuffMine(plateName, effect)
|
||||
end
|
||||
end
|
||||
|
||||
local shouldShowOwnerBound = isMyOwnerBound or not Settings.showOnlyMyDebuffs
|
||||
|
||||
if shouldShowOwnerBound then
|
||||
displayedOwnerBound[effect] = true
|
||||
local debuff = nameplate.debuffs[debuffIndex]
|
||||
debuff.icon:SetTexture(texture)
|
||||
|
||||
local instanceCount = ownerBoundCounts[effect] or 1
|
||||
if instanceCount > 1 then
|
||||
debuff.count:SetText(instanceCount)
|
||||
debuff.count:SetTextColor(0.3, 0.7, 1, 1)
|
||||
elseif stacks and stacks > 1 then
|
||||
debuff.count:SetText(stacks)
|
||||
else
|
||||
debuff.count:SetText("")
|
||||
end
|
||||
|
||||
local debuffKey = (unitstr or plateName) .. "_" .. effect
|
||||
local displayTimeLeft = nil
|
||||
|
||||
if timeleft and timeleft > 0 then
|
||||
displayTimeLeft = timeleft
|
||||
debuffTimers[debuffKey] = { startTime = now - (duration - timeleft), duration = duration, lastSeen = now }
|
||||
else
|
||||
local fallbackDuration = duration or SpellDB:GetDuration(effect, 0)
|
||||
if fallbackDuration <= 0 then fallbackDuration = 30 end
|
||||
if not debuffTimers[debuffKey] then
|
||||
debuffTimers[debuffKey] = { startTime = now, duration = fallbackDuration, lastSeen = now }
|
||||
end
|
||||
local cached = debuffTimers[debuffKey]
|
||||
cached.lastSeen = now
|
||||
if (now - cached.startTime) > cached.duration then
|
||||
cached.startTime = now
|
||||
cached.duration = fallbackDuration
|
||||
end
|
||||
displayTimeLeft = cached.duration - (now - cached.startTime)
|
||||
end
|
||||
|
||||
if Settings.showDebuffTimers and displayTimeLeft and displayTimeLeft > 0 then
|
||||
debuff.expirationTime = now + displayTimeLeft
|
||||
local text, r, g, b, a = FormatTime(displayTimeLeft)
|
||||
debuff.cd:SetText(text)
|
||||
if r then debuff.cd:SetTextColor(r, g, b, a) end
|
||||
debuff.cdframe:Show()
|
||||
else
|
||||
debuff.expirationTime = 0
|
||||
debuff.cd:SetText("")
|
||||
end
|
||||
|
||||
debuff:Show()
|
||||
debuffIndex = debuffIndex + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
local uniqueClass = effect and SpellDB and SpellDB.SHARED_DEBUFFS and SpellDB.SHARED_DEBUFFS[effect]
|
||||
local isUnique = uniqueClass and (uniqueClass == true or uniqueClass == playerClass)
|
||||
|
||||
local shouldDisplay = true
|
||||
if Settings.showOnlyMyDebuffs and not isMyDebuff and not isUnique and not isOwnerBound and not isHunterTrap then
|
||||
shouldDisplay = false
|
||||
end
|
||||
|
||||
if shouldDisplay then
|
||||
local debuff = nameplate.debuffs[debuffIndex]
|
||||
debuff.icon:SetTexture(texture)
|
||||
debuff.count:SetText((stacks and stacks > 1) and stacks or "")
|
||||
|
||||
local debuffKey = (unitstr or plateName) .. "_" .. (effect or texture)
|
||||
local displayTimeLeft = nil
|
||||
|
||||
if timeleft and timeleft > 0 then
|
||||
displayTimeLeft = timeleft
|
||||
debuffTimers[debuffKey] = { startTime = now - (duration - timeleft), duration = duration, lastSeen = now }
|
||||
else
|
||||
local dbDur = (effect and effect ~= "") and SpellDB:GetDuration(effect, 0) or 0
|
||||
local fallbackDuration = (duration and duration > 0 and duration) or (dbDur > 0 and dbDur) or 12
|
||||
if not debuffTimers[debuffKey] then
|
||||
debuffTimers[debuffKey] = { startTime = now, duration = fallbackDuration, lastStacks = stacks or 0 }
|
||||
end
|
||||
local cached = debuffTimers[debuffKey]
|
||||
cached.lastSeen = now
|
||||
|
||||
local stacksChanged = stacks and cached.lastStacks and stacks ~= cached.lastStacks
|
||||
if fallbackDuration > 1 and (cached.duration ~= fallbackDuration or (now - cached.startTime) > cached.duration or stacksChanged) then
|
||||
cached.duration = fallbackDuration
|
||||
cached.startTime = now
|
||||
end
|
||||
cached.lastStacks = stacks or 0
|
||||
displayTimeLeft = cached.duration - (now - cached.startTime)
|
||||
end
|
||||
|
||||
if Settings.showDebuffTimers and displayTimeLeft and displayTimeLeft > 0 then
|
||||
debuff.expirationTime = now + displayTimeLeft
|
||||
local text, r, g, b, a = FormatTime(displayTimeLeft)
|
||||
debuff.cd:SetText(text)
|
||||
if r then debuff.cd:SetTextColor(r, g, b, a) end
|
||||
debuff.cdframe:Show()
|
||||
else
|
||||
debuff.expirationTime = 0
|
||||
debuff.cd:SetText("")
|
||||
end
|
||||
|
||||
debuff:Show()
|
||||
debuffIndex = debuffIndex + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return debuffIndex - 1
|
||||
end
|
||||
|
||||
function NanamiPlates_Auras:UpdateDebuffPositions(nameplate, numDebuffs)
|
||||
if numDebuffs <= 0 then return end
|
||||
local anchor = nameplate.debuffAnchor or nameplate.healthBG
|
||||
for i = 1, numDebuffs do
|
||||
local debuff = nameplate.debuffs[i]
|
||||
if debuff then
|
||||
debuff:ClearAllPoints()
|
||||
if i == 1 then
|
||||
debuff:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -2)
|
||||
else
|
||||
debuff:SetPoint("LEFT", nameplate.debuffs[i - 1], "RIGHT", 1, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local lastDebuffCleanup = 0
|
||||
local lastOwnerBoundCleanup = 0
|
||||
|
||||
function NanamiPlates_Auras:CleanupTimers()
|
||||
local now = GetTime()
|
||||
|
||||
if now - lastDebuffCleanup > 3 then
|
||||
lastDebuffCleanup = now
|
||||
for key, data in pairs(self.timers) do
|
||||
local expired = false
|
||||
if data.lastSeen and (now - data.lastSeen > 5) then
|
||||
expired = true
|
||||
end
|
||||
if data.startTime and data.duration and (now - data.startTime > data.duration + 10) then
|
||||
expired = true
|
||||
end
|
||||
if expired then
|
||||
self.timers[key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if now - lastOwnerBoundCleanup > 10 then
|
||||
lastOwnerBoundCleanup = now
|
||||
if SpellDB and SpellDB.CleanupOwnerBoundCache then
|
||||
SpellDB:CleanupOwnerBoundCache()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Paladin judgement refresh
|
||||
function NanamiPlates_Auras:RefreshJudgementsOnTarget()
|
||||
if playerClass ~= "PALADIN" then return end
|
||||
if not UnitExists("target") then return end
|
||||
if not SpellDB or not SpellDB.objects then return end
|
||||
|
||||
local name = UnitName("target")
|
||||
local level = UnitLevel("target") or 0
|
||||
local guid = UnitGUID and UnitGUID("target")
|
||||
|
||||
local hasNameData = name and SpellDB.objects[name]
|
||||
local hasGuidData = guid and SpellDB.objects[guid]
|
||||
if not hasNameData and not hasGuidData then return end
|
||||
|
||||
for _, effect in ipairs(JUDGEMENT_EFFECTS) do
|
||||
local found = false
|
||||
local dur = SpellDB:GetDuration(effect, 0) or 10
|
||||
|
||||
if hasNameData then
|
||||
for lvl, effects in pairs(SpellDB.objects[name]) do
|
||||
if effects[effect] then
|
||||
effects[effect].start = GetTime()
|
||||
effects[effect].duration = dur
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if hasGuidData and not found then
|
||||
for lvl, effects in pairs(SpellDB.objects[guid]) do
|
||||
if effects[effect] then
|
||||
effects[effect].start = GetTime()
|
||||
effects[effect].duration = dur
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if found then
|
||||
debuffTimers[name .. "_" .. effect] = nil
|
||||
if guid then debuffTimers[guid .. "_" .. effect] = nil end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_Auras:SealHandler(attacker, victim)
|
||||
if playerClass ~= "PALADIN" then return end
|
||||
local isOwn = (attacker == "You" or attacker == UnitName("player"))
|
||||
if not isOwn then return end
|
||||
self:RefreshJudgementsOnTarget()
|
||||
end
|
||||
|
||||
function NanamiPlates_Auras:HolyStrikeHandler(msg)
|
||||
if not msg or playerClass ~= "PALADIN" then return end
|
||||
local holyStrike = string_find(string.sub(msg, 6, 17), "Holy Strike")
|
||||
if not holyStrike then return end
|
||||
if not string_find(msg, "%d+") then return end
|
||||
self:RefreshJudgementsOnTarget()
|
||||
end
|
||||
|
||||
NanamiPlates.Auras = NanamiPlates_Auras
|
||||
111
Castbar.lua
Normal file
111
Castbar.lua
Normal file
@@ -0,0 +1,111 @@
|
||||
NanamiPlates_Castbar = {}
|
||||
|
||||
local GetTime = GetTime
|
||||
local UnitGUID = UnitGUID
|
||||
local UnitName = UnitName
|
||||
local superwow_active = (SpellInfo ~= nil) or (UnitGUID ~= nil) or (SUPERWOW_VERSION ~= nil)
|
||||
|
||||
local castDB
|
||||
|
||||
local function InitReferences()
|
||||
castDB = NanamiPlates.castDB
|
||||
end
|
||||
|
||||
local function HandleUnitCastEvent(guid, target, eventType, spellId, timer)
|
||||
if not castDB then
|
||||
castDB = NanamiPlates.castDB
|
||||
end
|
||||
if not castDB then return false end
|
||||
|
||||
local SpellDB = NanamiPlates_SpellDB
|
||||
|
||||
if eventType == "START" or eventType == "CAST" or eventType == "CHANNEL" then
|
||||
local spell, icon
|
||||
if SpellInfo and spellId then
|
||||
spell, _, icon = SpellInfo(spellId)
|
||||
end
|
||||
spell = spell or "Casting"
|
||||
icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark"
|
||||
|
||||
if SpellDB and eventType == "CAST" then
|
||||
local effectTarget = target
|
||||
local isOwn = (guid == (UnitGUID and UnitGUID("player")))
|
||||
|
||||
if (not effectTarget or effectTarget == "") and isOwn then
|
||||
if UnitExists("target") then
|
||||
effectTarget = UnitGUID and UnitGUID("target") or UnitName("target")
|
||||
end
|
||||
end
|
||||
|
||||
if effectTarget and effectTarget ~= "" then
|
||||
local duration = SpellDB:GetDuration(spell, 0)
|
||||
if duration and duration > 0 then
|
||||
SpellDB:RefreshEffect(effectTarget, 0, spell, duration, isOwn)
|
||||
if NanamiPlates_Auras and NanamiPlates_Auras.timers then
|
||||
NanamiPlates_Auras.timers[effectTarget .. "_" .. spell] = nil
|
||||
end
|
||||
if isOwn then
|
||||
local targetName = UnitExists("target") and UnitName("target")
|
||||
if targetName and targetName ~= effectTarget then
|
||||
SpellDB:RefreshEffect(targetName, 0, spell, duration, true)
|
||||
NanamiPlates_Auras.timers[targetName .. "_" .. spell] = nil
|
||||
end
|
||||
if SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[spell] then
|
||||
SpellDB:TrackOwnerBoundDebuff(effectTarget, spell, duration)
|
||||
if targetName and targetName ~= effectTarget then
|
||||
SpellDB:TrackOwnerBoundDebuff(targetName, spell, duration)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if eventType == "CAST" then
|
||||
if castDB[guid] and castDB[guid].spell ~= spell then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
castDB[guid] = {
|
||||
spell = spell,
|
||||
startTime = GetTime(),
|
||||
duration = timer or 2000,
|
||||
icon = icon,
|
||||
channel = (eventType == "CHANNEL")
|
||||
}
|
||||
elseif eventType == "FAIL" then
|
||||
if castDB[guid] then
|
||||
castDB[guid] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function ClearCastData()
|
||||
if not castDB then castDB = NanamiPlates.castDB end
|
||||
if castDB then
|
||||
for k in pairs(castDB) do castDB[k] = nil end
|
||||
end
|
||||
end
|
||||
|
||||
local function GetCast(guid)
|
||||
if not castDB then castDB = NanamiPlates.castDB end
|
||||
return castDB and castDB[guid]
|
||||
end
|
||||
|
||||
local function RemoveCast(guid)
|
||||
if not castDB then castDB = NanamiPlates.castDB end
|
||||
if castDB and castDB[guid] then castDB[guid] = nil end
|
||||
end
|
||||
|
||||
NanamiPlates_Castbar = {
|
||||
InitReferences = InitReferences,
|
||||
HandleUnitCastEvent = HandleUnitCastEvent,
|
||||
ClearCastData = ClearCastData,
|
||||
GetCast = GetCast,
|
||||
RemoveCast = RemoveCast,
|
||||
}
|
||||
|
||||
NanamiPlates.Castbar = NanamiPlates_Castbar
|
||||
293
CombatLog.lua
Normal file
293
CombatLog.lua
Normal file
@@ -0,0 +1,293 @@
|
||||
NanamiPlates_CombatLog = {}
|
||||
|
||||
local NP = NanamiPlates
|
||||
local SpellDB = NanamiPlates_SpellDB
|
||||
local Auras = NanamiPlates_Auras
|
||||
|
||||
local string_gsub = string.gsub
|
||||
local string_gfind = string.gfind
|
||||
local string_find = string.find
|
||||
local string_sub = string.sub
|
||||
local GetTime = GetTime
|
||||
local UnitExists = UnitExists
|
||||
local UnitName = UnitName
|
||||
local UnitGUID = UnitGUID
|
||||
|
||||
local superwow_active = NP.superwow_active
|
||||
|
||||
local castTracker
|
||||
local recentMeleeHits
|
||||
|
||||
local function InitReferences()
|
||||
castTracker = NP.castTracker
|
||||
recentMeleeHits = NP.recentMeleeHits
|
||||
end
|
||||
|
||||
local function cmatch(str, pattern)
|
||||
if not str or not pattern then return nil end
|
||||
local pat = string_gsub(pattern, "%%%d?%$?s", "(.+)")
|
||||
pat = string_gsub(pat, "%%%d?%$?d", "(%d+)")
|
||||
for a, b, c, d in string_gfind(str, pat) do
|
||||
return a, b, c, d
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local castIcons = {
|
||||
["Fireball"] = "Interface\\Icons\\Spell_Fire_FlameBolt",
|
||||
["Frostbolt"] = "Interface\\Icons\\Spell_Frost_FrostBolt02",
|
||||
["Shadow Bolt"] = "Interface\\Icons\\Spell_Shadow_ShadowBolt",
|
||||
["Greater Heal"] = "Interface\\Icons\\Spell_Holy_GreaterHeal",
|
||||
["Flash Heal"] = "Interface\\Icons\\Spell_Holy_FlashHeal",
|
||||
["Lightning Bolt"] = "Interface\\Icons\\Spell_Nature_Lightning",
|
||||
["Chain Lightning"] = "Interface\\Icons\\Spell_Nature_ChainLightning",
|
||||
["Healing Wave"] = "Interface\\Icons\\Spell_Nature_MagicImmunity",
|
||||
["Fear"] = "Interface\\Icons\\Spell_Shadow_Possession",
|
||||
["Polymorph"] = "Interface\\Icons\\Spell_Nature_Polymorph",
|
||||
["Smite"] = "Interface\\Icons\\Spell_Holy_HolySmite",
|
||||
["Mind Blast"] = "Interface\\Icons\\Spell_Shadow_UnholyFrenzy",
|
||||
["Holy Light"] = "Interface\\Icons\\Spell_Holy_HolyLight",
|
||||
["Starfire"] = "Interface\\Icons\\Spell_Arcane_StarFire",
|
||||
["Wrath"] = "Interface\\Icons\\Spell_Nature_AbolishMagic",
|
||||
["Entangling Roots"] = "Interface\\Icons\\Spell_Nature_StrangleVines",
|
||||
["Moonfire"] = "Interface\\Icons\\Spell_Nature_StarFall",
|
||||
["Regrowth"] = "Interface\\Icons\\Spell_Nature_ResistNature",
|
||||
["Rejuvenation"] = "Interface\\Icons\\Spell_Nature_Rejuvenation",
|
||||
}
|
||||
|
||||
local function ParseCastStart(msg)
|
||||
if not msg then return end
|
||||
if not castTracker then castTracker = NP.castTracker end
|
||||
if not castTracker then return end
|
||||
|
||||
local unit, spell = nil, nil
|
||||
for u, s in string_gfind(msg, "(.+) begins to cast (.+)%.") do
|
||||
unit, spell = u, s
|
||||
end
|
||||
if not unit then
|
||||
for u, s in string_gfind(msg, "(.+) begins to perform (.+)%.") do
|
||||
unit, spell = u, s
|
||||
end
|
||||
end
|
||||
|
||||
if unit and spell then
|
||||
if not castTracker[unit] then castTracker[unit] = {} end
|
||||
table.insert(castTracker[unit], {
|
||||
spell = spell,
|
||||
startTime = GetTime(),
|
||||
duration = 2000,
|
||||
icon = castIcons[spell],
|
||||
})
|
||||
end
|
||||
|
||||
local interruptedUnit = nil
|
||||
for u in string_gfind(msg, "(.+)'s .+ is interrupted%.") do interruptedUnit = u end
|
||||
if not interruptedUnit then
|
||||
for u in string_gfind(msg, "(.+)'s .+ fails%.") do interruptedUnit = u end
|
||||
end
|
||||
if interruptedUnit and castTracker[interruptedUnit] then
|
||||
table.remove(castTracker[interruptedUnit], 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function ParseAttackHit(msg)
|
||||
if not msg then return end
|
||||
local attacker, victim = nil, nil
|
||||
|
||||
if string_sub(msg, 1, 8) == "You hit " then
|
||||
local forPos = string_find(msg, " for ")
|
||||
if forPos then
|
||||
victim = string_sub(msg, 9, forPos - 1)
|
||||
attacker = "You"
|
||||
end
|
||||
elseif string_sub(msg, 1, 9) == "You crit " then
|
||||
local forPos = string_find(msg, " for ")
|
||||
if forPos then
|
||||
victim = string_sub(msg, 10, forPos - 1)
|
||||
attacker = "You"
|
||||
end
|
||||
end
|
||||
|
||||
if attacker == "You" and victim and Auras then
|
||||
Auras:SealHandler(attacker, victim)
|
||||
end
|
||||
|
||||
if not recentMeleeHits then recentMeleeHits = NP.recentMeleeHits end
|
||||
if not recentMeleeHits then return end
|
||||
|
||||
if attacker == "You" and victim then
|
||||
recentMeleeHits[victim] = GetTime()
|
||||
if superwow_active and UnitExists("target") and UnitName("target") == victim then
|
||||
local guid = UnitGUID and UnitGUID("target")
|
||||
if guid then recentMeleeHits[guid] = GetTime() end
|
||||
end
|
||||
end
|
||||
|
||||
if not victim then
|
||||
for a, v in string_gfind(msg, "(.+) hits (.-) for %d+%.") do
|
||||
attacker, victim = a, v
|
||||
break
|
||||
end
|
||||
end
|
||||
if not victim then
|
||||
for a, v in string_gfind(msg, "(.+) crits (.-) for %d+%.") do
|
||||
attacker, victim = a, v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if attacker == "You" and victim and recentMeleeHits then
|
||||
recentMeleeHits[victim] = GetTime()
|
||||
if superwow_active and UnitExists("target") and UnitName("target") == victim then
|
||||
local guid = UnitGUID and UnitGUID("target")
|
||||
if guid then recentMeleeHits[guid] = GetTime() end
|
||||
end
|
||||
end
|
||||
|
||||
if attacker and victim and Auras then
|
||||
Auras:SealHandler(attacker, victim)
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle spell-related combat log events
|
||||
function NanamiPlates_CombatLog.HandleSpellEvent(evnt, msg)
|
||||
if not msg then return end
|
||||
|
||||
InitReferences()
|
||||
|
||||
if evnt == "CHAT_MSG_SPELL_AURA_GONE_OTHER" or evnt == "CHAT_MSG_SPELL_AURA_GONE_SELF" then
|
||||
local target, effect
|
||||
for t, e in string_gfind(msg, "(.+) is no longer afflicted by (.+)%.") do
|
||||
target, effect = t, e
|
||||
end
|
||||
if not target then
|
||||
for e in string_gfind(msg, "(.+) fades from .+%.") do effect = e end
|
||||
end
|
||||
if effect and SpellDB and SpellDB.objects then
|
||||
for unit, levels in pairs(SpellDB.objects) do
|
||||
for lvl, effects in pairs(levels) do
|
||||
if effects[effect] then effects[effect] = nil end
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if evnt == "CHAT_MSG_SPELL_FAILED_LOCALPLAYER" then
|
||||
if SpellDB then SpellDB:RemovePending() end
|
||||
return
|
||||
end
|
||||
|
||||
-- Check for pending spell resolution
|
||||
if SpellDB and SpellDB.pending and SpellDB.pending[3] then
|
||||
local effect = SpellDB.pending[3]
|
||||
|
||||
-- Check if spell was resisted/missed/etc
|
||||
local removePatterns = NP.REMOVE_PENDING_PATTERNS
|
||||
if removePatterns then
|
||||
for _, pattern in ipairs(removePatterns) do
|
||||
if cmatch(msg, pattern) then
|
||||
SpellDB:RemovePending()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for spell hit/application
|
||||
if string_find(msg, effect) then
|
||||
local affTarget = nil
|
||||
for t in string_gfind(msg, "(.+) is afflicted by " .. effect) do
|
||||
affTarget = t
|
||||
end
|
||||
if affTarget or string_find(msg, "Your " .. effect) then
|
||||
SpellDB:PersistPending(effect)
|
||||
|
||||
if SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[effect] then
|
||||
local targetName = affTarget or (UnitExists("target") and UnitName("target"))
|
||||
if targetName then
|
||||
SpellDB:TrackOwnerBoundDebuff(targetName, effect)
|
||||
if superwow_active and UnitExists("target") and UnitName("target") == targetName then
|
||||
local guid = UnitGUID and UnitGUID("target")
|
||||
if guid then SpellDB:TrackOwnerBoundDebuff(guid, effect) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: track debuffs via "afflicted by" messages using recentCasts.
|
||||
-- Handles instant-cast DOTs (e.g. Corruption) whose pending was overwritten
|
||||
-- by a subsequent cast before the combat log confirmed application.
|
||||
do
|
||||
local affTarget, affEffect
|
||||
for t, e in string_gfind(msg, "(.+) is afflicted by (.+)%.") do
|
||||
affTarget, affEffect = t, e
|
||||
end
|
||||
if affEffect and SpellDB and SpellDB.recentCasts then
|
||||
if SpellDB.WARLOCK_CURSES and SpellDB.WARLOCK_CURSES[affEffect] then
|
||||
local hasMalediction = SpellDB.HasMalediction and SpellDB:HasMalediction()
|
||||
for otherCurse, _ in pairs(SpellDB.WARLOCK_CURSES) do
|
||||
if otherCurse ~= affEffect then
|
||||
local otherRecent = SpellDB.recentCasts[otherCurse]
|
||||
local thisRecent = SpellDB.recentCasts[affEffect]
|
||||
if otherRecent and thisRecent and otherRecent.time > thisRecent.time then
|
||||
if hasMalediction and SpellDB:CanCursesCoexist(affEffect, otherCurse) then
|
||||
-- skip: these two curses can coexist
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local recent = SpellDB.recentCasts[affEffect]
|
||||
if recent and (GetTime() - recent.time) < 4 then
|
||||
SpellDB:RefreshEffect(affTarget, 0, affEffect, recent.duration, true)
|
||||
|
||||
if superwow_active and UnitExists("target") and UnitName("target") == affTarget then
|
||||
local guid = UnitGUID and UnitGUID("target")
|
||||
if guid then
|
||||
SpellDB:RefreshEffect(guid, 0, affEffect, recent.duration, true)
|
||||
end
|
||||
end
|
||||
|
||||
if SpellDB.OWNER_BOUND_DEBUFFS and SpellDB.OWNER_BOUND_DEBUFFS[affEffect] then
|
||||
SpellDB:TrackOwnerBoundDebuff(affTarget, affEffect, recent.duration)
|
||||
if superwow_active and UnitExists("target") and UnitName("target") == affTarget then
|
||||
local guid = UnitGUID and UnitGUID("target")
|
||||
if guid then SpellDB:TrackOwnerBoundDebuff(guid, affEffect, recent.duration) end
|
||||
end
|
||||
end
|
||||
|
||||
if Auras and Auras.timers then
|
||||
Auras.timers[affTarget .. "_" .. affEffect] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Parse cast starts for non-SuperWoW fallback
|
||||
if NP.SPELL_DAMAGE_EVENTS and NP.SPELL_DAMAGE_EVENTS[evnt] then
|
||||
ParseCastStart(msg)
|
||||
end
|
||||
|
||||
-- Holy Strike handler for Paladin
|
||||
if evnt == "CHAT_MSG_SPELL_SELF_DAMAGE" and Auras then
|
||||
Auras:HolyStrikeHandler(msg)
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_CombatLog.HandleCombatEvent(evnt, msg)
|
||||
if not msg then return end
|
||||
InitReferences()
|
||||
ParseAttackHit(msg)
|
||||
end
|
||||
|
||||
NanamiPlates_CombatLog.cmatch = cmatch
|
||||
NanamiPlates_CombatLog.castIcons = castIcons
|
||||
NanamiPlates_CombatLog.ParseCastStart = ParseCastStart
|
||||
NanamiPlates_CombatLog.ParseAttackHit = ParseAttackHit
|
||||
NanamiPlates_CombatLog.InitReferences = InitReferences
|
||||
|
||||
NanamiPlates.CombatLog = NanamiPlates_CombatLog
|
||||
193
ComboPoints.lua
Normal file
193
ComboPoints.lua
Normal file
@@ -0,0 +1,193 @@
|
||||
NanamiPlates_ComboPoints = {}
|
||||
|
||||
local NP = NanamiPlates
|
||||
local Settings = NP.Settings
|
||||
local GetComboPoints = GetComboPoints
|
||||
local UnitExists = UnitExists
|
||||
local CreateFrame = CreateFrame
|
||||
|
||||
local MAX_COMBO_POINTS = 5
|
||||
local CP_SIZE = 10
|
||||
local CP_SPACING = 3
|
||||
|
||||
local COMBO_COLORS = {
|
||||
{0.3, 1.0, 0.3, 1},
|
||||
{0.6, 1.0, 0.0, 1},
|
||||
{1.0, 0.85, 0.0, 1},
|
||||
{1.0, 0.45, 0.0, 1},
|
||||
{1.0, 0.1, 0.1, 1},
|
||||
}
|
||||
|
||||
local _, playerClass = UnitClass("player")
|
||||
playerClass = playerClass or ""
|
||||
local canUseComboPoints = (playerClass == "ROGUE" or playerClass == "DRUID")
|
||||
|
||||
function NanamiPlates_ComboPoints:CanUseComboPoints()
|
||||
return canUseComboPoints
|
||||
end
|
||||
|
||||
function NanamiPlates_ComboPoints:CreateComboPointFrames(nameplate)
|
||||
if not canUseComboPoints then return end
|
||||
|
||||
nameplate.comboPoints = {}
|
||||
local plateName = nameplate:GetName() or "UnknownPlate"
|
||||
local size = Settings.comboPointsSize or CP_SIZE
|
||||
|
||||
for i = 1, MAX_COMBO_POINTS do
|
||||
local cp = CreateFrame("Frame", plateName .. "CP" .. i, nameplate)
|
||||
cp:SetWidth(size)
|
||||
cp:SetHeight(size)
|
||||
cp:SetFrameLevel(nameplate.health:GetFrameLevel() + 6)
|
||||
cp:EnableMouse(false)
|
||||
|
||||
-- Outer glow/shadow
|
||||
cp.glow = cp:CreateTexture(nil, "BACKGROUND")
|
||||
cp.glow:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
cp.glow:SetPoint("CENTER", cp, "CENTER", 0, 0)
|
||||
cp.glow:SetWidth(size + 4)
|
||||
cp.glow:SetHeight(size + 4)
|
||||
cp.glow:SetVertexColor(0, 0, 0, 0.6)
|
||||
cp.glow:SetDrawLayer("BACKGROUND")
|
||||
|
||||
-- Border (themed)
|
||||
cp.border = cp:CreateTexture(nil, "BORDER")
|
||||
cp.border:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
cp.border:SetPoint("CENTER", cp, "CENTER", 0, 0)
|
||||
cp.border:SetWidth(size + 2)
|
||||
cp.border:SetHeight(size + 2)
|
||||
local brR, brG, brB = NP.GetThemeColor("panelBorder", 0.55, 0.30, 0.42, 1)
|
||||
cp.border:SetVertexColor(brR, brG, brB, 1)
|
||||
cp.border:SetDrawLayer("BORDER")
|
||||
|
||||
-- Main fill
|
||||
cp.icon = cp:CreateTexture(nil, "ARTWORK")
|
||||
cp.icon:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||||
cp.icon:SetPoint("CENTER", cp, "CENTER", 0, 0)
|
||||
cp.icon:SetWidth(size)
|
||||
cp.icon:SetHeight(size)
|
||||
cp.icon:SetVertexColor(0.12, 0.08, 0.12, 0.6)
|
||||
cp.icon:SetDrawLayer("ARTWORK")
|
||||
|
||||
-- Number text
|
||||
cp.text = cp:CreateFontString(nil, "OVERLAY")
|
||||
cp.text:SetFont(NP.GetFont(), size - 2, NP.GetFontOutline())
|
||||
cp.text:SetPoint("CENTER", cp, "CENTER", 0, 0)
|
||||
cp.text:SetText("")
|
||||
cp.text:SetDrawLayer("OVERLAY")
|
||||
|
||||
cp.active = false
|
||||
cp:Hide()
|
||||
nameplate.comboPoints[i] = cp
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_ComboPoints:UpdateComboPoints(nameplate, isTarget)
|
||||
if not canUseComboPoints then return 0 end
|
||||
if not nameplate.comboPoints then return 0 end
|
||||
if not Settings.showComboPoints then
|
||||
for i = 1, MAX_COMBO_POINTS do
|
||||
if nameplate.comboPoints[i] then nameplate.comboPoints[i]:Hide() end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local numPoints = 0
|
||||
if isTarget and UnitExists("target") then
|
||||
numPoints = GetComboPoints("player", "target") or 0
|
||||
end
|
||||
|
||||
local size = Settings.comboPointsSize or CP_SIZE
|
||||
|
||||
for i = 1, MAX_COMBO_POINTS do
|
||||
local cp = nameplate.comboPoints[i]
|
||||
if cp then
|
||||
cp:SetWidth(size)
|
||||
cp:SetHeight(size)
|
||||
cp.glow:SetWidth(size + 4)
|
||||
cp.glow:SetHeight(size + 4)
|
||||
cp.border:SetWidth(size + 2)
|
||||
cp.border:SetHeight(size + 2)
|
||||
cp.icon:SetWidth(size)
|
||||
cp.icon:SetHeight(size)
|
||||
cp.text:SetFont(NP.GetFont(), size - 2 > 6 and size - 2 or 6, NP.GetFontOutline())
|
||||
|
||||
if i <= numPoints then
|
||||
local color = COMBO_COLORS[i] or COMBO_COLORS[MAX_COMBO_POINTS]
|
||||
cp.icon:SetVertexColor(color[1], color[2], color[3], 1)
|
||||
cp.glow:SetVertexColor(color[1] * 0.4, color[2] * 0.4, color[3] * 0.4, 0.7)
|
||||
cp.text:SetText(i)
|
||||
cp.text:SetTextColor(1, 1, 1, 0.9)
|
||||
cp.active = true
|
||||
cp:Show()
|
||||
elseif numPoints > 0 then
|
||||
cp.icon:SetVertexColor(0.12, 0.08, 0.12, 0.5)
|
||||
cp.glow:SetVertexColor(0, 0, 0, 0.4)
|
||||
cp.text:SetText("")
|
||||
cp.active = false
|
||||
cp:Show()
|
||||
else
|
||||
cp:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return numPoints
|
||||
end
|
||||
|
||||
function NanamiPlates_ComboPoints:UpdateComboPointPositions(nameplate, numDebuffs)
|
||||
if not canUseComboPoints then return end
|
||||
if not nameplate.comboPoints then return end
|
||||
if not Settings.showComboPoints then return end
|
||||
if not nameplate.healthBG then return end
|
||||
|
||||
local numVisible = 0
|
||||
for i = 1, MAX_COMBO_POINTS do
|
||||
if nameplate.comboPoints[i] and nameplate.comboPoints[i]:IsShown() then
|
||||
numVisible = MAX_COMBO_POINTS
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if numVisible == 0 then
|
||||
if nameplate.name and nameplate._cpNameOffset then
|
||||
nameplate.name:ClearAllPoints()
|
||||
nameplate.name:SetPoint("BOTTOM", nameplate.healthBG, "TOP", 0, 2)
|
||||
nameplate._cpNameOffset = nil
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local size = Settings.comboPointsSize or CP_SIZE
|
||||
local spacing = CP_SPACING
|
||||
local totalWidth = (size * MAX_COMBO_POINTS) + (spacing * (MAX_COMBO_POINTS - 1))
|
||||
local startOffset = -totalWidth / 2 + size / 2
|
||||
|
||||
for i = 1, MAX_COMBO_POINTS do
|
||||
local cp = nameplate.comboPoints[i]
|
||||
if cp then
|
||||
cp:ClearAllPoints()
|
||||
local xOffset = startOffset + (i - 1) * (size + spacing)
|
||||
cp:SetPoint("BOTTOM", nameplate.healthBG, "TOP", xOffset, 2)
|
||||
end
|
||||
end
|
||||
|
||||
if nameplate.name then
|
||||
nameplate.name:ClearAllPoints()
|
||||
nameplate.name:SetPoint("BOTTOM", nameplate.healthBG, "TOP", 0, 2 + size + 2)
|
||||
nameplate._cpNameOffset = true
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_ComboPoints:HideComboPoints(nameplate)
|
||||
if not nameplate.comboPoints then return end
|
||||
for i = 1, MAX_COMBO_POINTS do
|
||||
if nameplate.comboPoints[i] then nameplate.comboPoints[i]:Hide() end
|
||||
end
|
||||
if nameplate.name and nameplate._cpNameOffset then
|
||||
nameplate.name:ClearAllPoints()
|
||||
nameplate.name:SetPoint("BOTTOM", nameplate.healthBG, "TOP", 0, 2)
|
||||
nameplate._cpNameOffset = nil
|
||||
end
|
||||
end
|
||||
|
||||
NanamiPlates.ComboPoints = NanamiPlates_ComboPoints
|
||||
228
Config.lua
Normal file
228
Config.lua
Normal file
@@ -0,0 +1,228 @@
|
||||
local NP = NanamiPlates
|
||||
|
||||
local function GetThemeColor(key, fallbackR, fallbackG, fallbackB, fallbackA)
|
||||
if SFrames and SFrames.ActiveTheme and SFrames.ActiveTheme[key] then
|
||||
local c = SFrames.ActiveTheme[key]
|
||||
return c[1] or fallbackR, c[2] or fallbackG, c[3] or fallbackB, c[4] or fallbackA or 1
|
||||
end
|
||||
return fallbackR, fallbackG, fallbackB, fallbackA or 1
|
||||
end
|
||||
NP.GetThemeColor = GetThemeColor
|
||||
|
||||
local function GetFont()
|
||||
if SFrames and SFrames.GetFont then
|
||||
return SFrames:GetFont()
|
||||
end
|
||||
return "Fonts\\ARIALN.TTF"
|
||||
end
|
||||
NP.GetFont = GetFont
|
||||
|
||||
local function GetFontOutline()
|
||||
if SFrames and SFrames.Media and SFrames.Media.fontOutline then
|
||||
return SFrames.Media.fontOutline
|
||||
end
|
||||
return "OUTLINE"
|
||||
end
|
||||
NP.GetFontOutline = GetFontOutline
|
||||
|
||||
local function GetTexture()
|
||||
if SFrames and SFrames.GetTexture then
|
||||
return SFrames:GetTexture()
|
||||
end
|
||||
return "Interface\\TargetingFrame\\UI-StatusBar"
|
||||
end
|
||||
NP.GetTexture = GetTexture
|
||||
|
||||
NP.Settings = {
|
||||
healthbarHeight = 12,
|
||||
healthbarWidth = 120,
|
||||
healthFontSize = 9,
|
||||
healthTextFormat = 4,
|
||||
|
||||
friendHealthbarHeight = 4,
|
||||
friendHealthbarWidth = 85,
|
||||
friendHealthFontSize = 8,
|
||||
friendHealthTextFormat = 1,
|
||||
|
||||
castbarHeight = 10,
|
||||
castbarWidth = 120,
|
||||
showCastbarIcon = true,
|
||||
|
||||
friendCastbarHeight = 6,
|
||||
friendCastbarWidth = 85,
|
||||
friendShowCastbarIcon = true,
|
||||
|
||||
castbarColor = {1, 0.8, 0, 1},
|
||||
|
||||
levelFontSize = 9,
|
||||
nameFontSize = 9,
|
||||
friendLevelFontSize = 7,
|
||||
friendNameFontSize = 8,
|
||||
|
||||
raidIconPosition = "LEFT",
|
||||
namePosition = "BOTTOM",
|
||||
|
||||
showOnlyMyDebuffs = false,
|
||||
showDebuffTimers = true,
|
||||
debuffIconSize = 20,
|
||||
|
||||
showComboPoints = true,
|
||||
comboPointsSize = 12,
|
||||
|
||||
showTargetGlow = true,
|
||||
targetArrowStyle = 1,
|
||||
targetArrowSize = 24,
|
||||
targetArrowOffset = 0,
|
||||
targetArrowTint = 0,
|
||||
nonTargetAlpha = 0.35,
|
||||
|
||||
showCritterNameplates = false,
|
||||
|
||||
showManaBar = true,
|
||||
manabarHeight = 3,
|
||||
|
||||
nameplateYOffset = 15,
|
||||
|
||||
pvpEnemyAsFriendly = false,
|
||||
pvpEnemyNoClassColors = false,
|
||||
|
||||
showQuestIcon = true,
|
||||
}
|
||||
|
||||
NP.Colors = {
|
||||
hostile = {0.85, 0.2, 0.2, 1},
|
||||
neutral = {0.9, 0.7, 0.0, 1},
|
||||
friendly = {0.2, 0.8, 0.2, 1},
|
||||
tapped = {0.5, 0.5, 0.5, 1},
|
||||
|
||||
class = {
|
||||
WARRIOR = {0.78, 0.61, 0.43},
|
||||
MAGE = {0.41, 0.80, 0.94},
|
||||
ROGUE = {1.0, 0.96, 0.41},
|
||||
DRUID = {1.0, 0.49, 0.04},
|
||||
HUNTER = {0.67, 0.83, 0.45},
|
||||
SHAMAN = {0.14, 0.35, 1.0},
|
||||
PRIEST = {1.0, 1.0, 1.0},
|
||||
WARLOCK = {0.58, 0.51, 0.79},
|
||||
PALADIN = {0.96, 0.55, 0.73},
|
||||
},
|
||||
|
||||
power = {
|
||||
[0] = {0.0, 0.0, 1.0},
|
||||
[1] = {1.0, 0.0, 0.0},
|
||||
[2] = {1.0, 0.5, 0.0},
|
||||
[3] = {1.0, 1.0, 0.0},
|
||||
[4] = {0.0, 1.0, 1.0},
|
||||
},
|
||||
}
|
||||
|
||||
NP.THREAT_COLORS = {
|
||||
DPS = {
|
||||
AGGRO = {1.0, 0.2, 0.2, 1},
|
||||
HIGH_THREAT = {1.0, 0.6, 0.0, 1},
|
||||
NO_AGGRO = {0.85, 0.2, 0.2, 1},
|
||||
},
|
||||
TANK = {
|
||||
AGGRO = {0.2, 0.8, 0.2, 1},
|
||||
LOSING_AGGRO = {1.0, 0.6, 0.0, 1},
|
||||
NO_AGGRO = {1.0, 0.1, 0.1, 1},
|
||||
OTHER_TANK = {0.6, 0.8, 1.0, 1},
|
||||
},
|
||||
TAPPED = {0.5, 0.5, 0.5, 1},
|
||||
STUN = {0.376, 0.027, 0.431, 1},
|
||||
}
|
||||
|
||||
NP.Critters = {
|
||||
["adder"] = true, ["beetle"] = true, ["belfry bat"] = true,
|
||||
["biletoad"] = true, ["black rat"] = true, ["brown prairie dog"] = true,
|
||||
["caged rabbit"] = true, ["caged sheep"] = true, ["caged squirrel"] = true,
|
||||
["caged toad"] = true, ["cat"] = true, ["chicken"] = true,
|
||||
["cleo"] = true, ["core rat"] = true, ["cow"] = true,
|
||||
["cured deer"] = true, ["cured gazelle"] = true, ["deeprun rat"] = true,
|
||||
["deer"] = true, ["dog"] = true, ["effsee"] = true,
|
||||
["enthralled deeprun rat"] = true, ["fang"] = true, ["fawn"] = true,
|
||||
["fire beetle"] = true, ["fluffy"] = true, ["frog"] = true,
|
||||
["gazelle"] = true, ["hare"] = true, ["horse"] = true,
|
||||
["huge toad"] = true, ["infected deer"] = true, ["infected squirrel"] = true,
|
||||
["jungle toad"] = true, ["krakle's thermometer"] = true, ["lady"] = true,
|
||||
["larva"] = true, ["lava crab"] = true, ["maggot"] = true,
|
||||
["moccasin"] = true, ["mouse"] = true, ["mr. bigglesworth"] = true,
|
||||
["nibbles"] = true, ["noarm"] = true, ["old blanchy"] = true,
|
||||
["parrot"] = true, ["pig"] = true, ["pirate treasure trigger mob"] = true,
|
||||
["plagued insect"] = true, ["plagued maggot"] = true, ["plagued rat"] = true,
|
||||
["plagueland termite"] = true, ["polymorphed chicken"] = true,
|
||||
["polymorphed rat"] = true, ["prairie dog"] = true, ["rabbit"] = true,
|
||||
["ram"] = true, ["rat"] = true, ["riding ram"] = true,
|
||||
["roach"] = true, ["salome"] = true, ["school of fish"] = true,
|
||||
["scorpion"] = true, ["sheep"] = true, ["shen'dralar wisp"] = true,
|
||||
["sickly deer"] = true, ["sickly gazelle"] = true, ["snake"] = true,
|
||||
["spider"] = true, ["spike"] = true, ["squirrel"] = true,
|
||||
["swine"] = true, ["tainted cockroach"] = true, ["tainted rat"] = true,
|
||||
["toad"] = true, ["transporter malfunction"] = true, ["turtle"] = true,
|
||||
["underfoot"] = true, ["voice of elune"] = true,
|
||||
["waypoint (only gm can see it)"] = true, ["wisp"] = true,
|
||||
}
|
||||
|
||||
function NP:DetectTankSpec()
|
||||
local _, playerClass = UnitClass("player")
|
||||
if not playerClass then return end
|
||||
|
||||
if playerClass ~= "WARRIOR" and playerClass ~= "PALADIN" and playerClass ~= "DRUID" then
|
||||
return
|
||||
end
|
||||
|
||||
if not GetTalentTabInfo then return end
|
||||
|
||||
local _, _, p1 = GetTalentTabInfo(1)
|
||||
local _, _, p2 = GetTalentTabInfo(2)
|
||||
local _, _, p3 = GetTalentTabInfo(3)
|
||||
p1 = p1 or 0
|
||||
p2 = p2 or 0
|
||||
p3 = p3 or 0
|
||||
|
||||
local isTank = false
|
||||
if playerClass == "WARRIOR" then
|
||||
isTank = (p3 >= p1 and p3 >= p2 and p3 >= 11)
|
||||
elseif playerClass == "PALADIN" then
|
||||
isTank = (p2 >= p1 and p2 >= p3 and p2 >= 11)
|
||||
elseif playerClass == "DRUID" then
|
||||
isTank = (p2 >= p1 and p2 >= p3 and p2 >= 11)
|
||||
end
|
||||
|
||||
local newRole = isTank and "TANK" or "DPS"
|
||||
if newRole ~= self.playerRole then
|
||||
self.playerRole = newRole
|
||||
self:SaveSettings()
|
||||
self.Print("Auto role: " .. newRole)
|
||||
if NanamiPlates_Threat then
|
||||
NanamiPlates_Threat.BroadcastTankMode(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NP:LoadSettings()
|
||||
if NanamiPlatesDB then
|
||||
for k, v in pairs(NanamiPlatesDB) do
|
||||
if self.Settings[k] ~= nil then
|
||||
self.Settings[k] = v
|
||||
end
|
||||
end
|
||||
if NanamiPlatesDB.playerRole then
|
||||
self.playerRole = NanamiPlatesDB.playerRole
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NP:SaveSettings()
|
||||
NanamiPlatesDB = {}
|
||||
for k, v in pairs(self.Settings) do
|
||||
NanamiPlatesDB[k] = v
|
||||
end
|
||||
NanamiPlatesDB.playerRole = self.playerRole
|
||||
end
|
||||
|
||||
if SFrames and SFrames.Config and SFrames.Config.colors and SFrames.Config.colors.class then
|
||||
for k, v in pairs(SFrames.Config.colors.class) do
|
||||
NP.Colors.class[k] = {v.r, v.g, v.b}
|
||||
end
|
||||
end
|
||||
215
Core.lua
Normal file
215
Core.lua
Normal file
@@ -0,0 +1,215 @@
|
||||
NanamiPlates = {}
|
||||
NanamiPlates.modules = {}
|
||||
NanamiPlates.registry = {}
|
||||
|
||||
local pairs = pairs
|
||||
local tostring = tostring
|
||||
local CreateFrame = CreateFrame
|
||||
|
||||
local superwow_active = (SpellInfo ~= nil) or (UnitGUID ~= nil) or (SUPERWOW_VERSION ~= nil)
|
||||
NanamiPlates.superwow_active = superwow_active
|
||||
|
||||
local _, playerClass = UnitClass("player")
|
||||
playerClass = playerClass or ""
|
||||
NanamiPlates.playerClass = playerClass
|
||||
|
||||
NanamiPlates.castDB = {}
|
||||
NanamiPlates.castTracker = {}
|
||||
NanamiPlates.debuffTracker = {}
|
||||
NanamiPlates.recentMeleeCrits = {}
|
||||
NanamiPlates.recentMeleeHits = {}
|
||||
NanamiPlates.playerClassCache = {}
|
||||
NanamiPlates.playerRole = "DPS"
|
||||
|
||||
local function Print(msg)
|
||||
if DEFAULT_CHAT_FRAME then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff88cc[Nanami-Plates]|r " .. tostring(msg))
|
||||
end
|
||||
end
|
||||
NanamiPlates.Print = Print
|
||||
|
||||
local function HookScript(frame, script, func)
|
||||
local prev = frame:GetScript(script)
|
||||
frame:SetScript(script, function(a1, a2, a3, a4, a5, a6, a7, a8, a9)
|
||||
if prev then prev(a1, a2, a3, a4, a5, a6, a7, a8, a9) end
|
||||
func(a1, a2, a3, a4, a5, a6, a7, a8, a9)
|
||||
end)
|
||||
end
|
||||
NanamiPlates.HookScript = HookScript
|
||||
|
||||
local function DisableShaguTweaksNameplates()
|
||||
if ShaguTweaks and ShaguTweaks.libnameplate then
|
||||
ShaguTweaks.libnameplate:SetScript("OnUpdate", nil)
|
||||
ShaguTweaks.libnameplate.OnInit = {}
|
||||
ShaguTweaks.libnameplate.OnShow = {}
|
||||
ShaguTweaks.libnameplate.OnUpdate = {}
|
||||
ShaguTweaks.libnameplate.disabled_by_nanamiplates = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function DisablePfUINameplates()
|
||||
if pfUI then
|
||||
if pfUI.modules then pfUI.modules["nameplates"] = nil end
|
||||
if pfNameplates then
|
||||
pfNameplates:Hide()
|
||||
pfNameplates:UnregisterAllEvents()
|
||||
end
|
||||
if pfUI.nameplates then pfUI.nameplates = nil end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
DisableShaguTweaksNameplates()
|
||||
DisablePfUINameplates()
|
||||
|
||||
local function GetPlayerClassByName(name)
|
||||
if not name then return nil end
|
||||
local cache = NanamiPlates.playerClassCache
|
||||
if cache[name] then return cache[name] end
|
||||
|
||||
local playerName = UnitName("player")
|
||||
if name == playerName then
|
||||
cache[name] = playerClass
|
||||
return playerClass
|
||||
end
|
||||
|
||||
local numRaid = GetNumRaidMembers()
|
||||
if numRaid > 0 then
|
||||
for i = 1, numRaid do
|
||||
local raidName, _, _, _, _, raidClass = GetRaidRosterInfo(i)
|
||||
if raidName == name then
|
||||
cache[name] = raidClass
|
||||
return raidClass
|
||||
end
|
||||
end
|
||||
else
|
||||
local numParty = GetNumPartyMembers()
|
||||
for i = 1, numParty do
|
||||
local partyUnit = "party" .. i
|
||||
if UnitExists(partyUnit) then
|
||||
local partyName = UnitName(partyUnit)
|
||||
if partyName == name then
|
||||
local _, partyClass = UnitClass(partyUnit)
|
||||
cache[name] = partyClass
|
||||
return partyClass
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
NanamiPlates.GetPlayerClassByName = GetPlayerClassByName
|
||||
|
||||
local NP_EventFrame = CreateFrame("Frame", "NanamiPlatesFrame", UIParent)
|
||||
|
||||
NP_EventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
NP_EventFrame:RegisterEvent("ADDON_LOADED")
|
||||
NP_EventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
|
||||
NP_EventFrame:RegisterEvent("UNIT_AURA")
|
||||
NP_EventFrame:RegisterEvent("PARTY_MEMBERS_CHANGED")
|
||||
NP_EventFrame:RegisterEvent("RAID_ROSTER_UPDATE")
|
||||
NP_EventFrame:RegisterEvent("PLAYER_LEVEL_UP")
|
||||
NP_EventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
||||
NP_EventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
||||
NP_EventFrame:RegisterEvent("UNIT_CASTEVENT")
|
||||
NP_EventFrame:RegisterEvent("SPELLCAST_STOP")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_FAILED_LOCALPLAYER")
|
||||
NP_EventFrame:RegisterEvent("CHARACTER_POINTS_CHANGED")
|
||||
NP_EventFrame:RegisterEvent("QUEST_LOG_UPDATE")
|
||||
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_SELF_DAMAGE")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_TRADESKILLS")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_AURA_GONE_OTHER")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_AURA_GONE_SELF")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_PARTY_BUFF")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF")
|
||||
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_HITS")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_COMBAT_PARTY_HITS")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_COMBAT_FRIENDLYPLAYER_HITS")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_HITS")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_COMBAT_SELF_RANGED_HITS")
|
||||
NP_EventFrame:RegisterEvent("CHAT_MSG_COMBAT_PARTY_RANGED_HITS")
|
||||
|
||||
NanamiPlates.EventFrame = NP_EventFrame
|
||||
|
||||
NanamiPlates.SPELL_EVENTS = {
|
||||
["CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF"] = true,
|
||||
["CHAT_MSG_SPELL_SELF_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_TRADESKILLS"] = true,
|
||||
["CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_AURA_GONE_OTHER"] = true,
|
||||
["CHAT_MSG_SPELL_AURA_GONE_SELF"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_PARTY_BUFF"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF"] = true,
|
||||
["CHAT_MSG_SPELL_FAILED_LOCALPLAYER"] = true,
|
||||
}
|
||||
|
||||
NanamiPlates.SPELL_DAMAGE_EVENTS = {
|
||||
["CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_SELF_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE"] = true,
|
||||
["CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"] = true,
|
||||
}
|
||||
|
||||
NanamiPlates.COMBAT_EVENTS = {
|
||||
["CHAT_MSG_COMBAT_SELF_HITS"] = true,
|
||||
["CHAT_MSG_COMBAT_PARTY_HITS"] = true,
|
||||
["CHAT_MSG_COMBAT_FRIENDLYPLAYER_HITS"] = true,
|
||||
["CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_HITS"] = true,
|
||||
["CHAT_MSG_COMBAT_SELF_RANGED_HITS"] = true,
|
||||
["CHAT_MSG_COMBAT_PARTY_RANGED_HITS"] = true,
|
||||
}
|
||||
|
||||
NanamiPlates.STUN_EFFECTS = {
|
||||
"Cheap Shot", "Kidney Shot", "Bash", "Hammer of Justice",
|
||||
"Charge Stun", "Intercept Stun", "Concussion Blow",
|
||||
"Gouge", "Sap", "Pounce"
|
||||
}
|
||||
|
||||
NanamiPlates.REMOVE_PENDING_PATTERNS = {
|
||||
SPELLIMMUNESELFOTHER or "%s is immune to your %s.",
|
||||
IMMUNEDAMAGECLASSSELFOTHER or "%s is immune to your %s damage.",
|
||||
SPELLMISSSELFOTHER or "Your %s missed %s.",
|
||||
SPELLRESISTSELFOTHER or "Your %s was resisted by %s.",
|
||||
SPELLEVADEDSELFOTHER or "Your %s was evaded by %s.",
|
||||
SPELLDODGEDSELFOTHER or "Your %s was dodged by %s.",
|
||||
SPELLDEFLECTEDSELFOTHER or "Your %s was deflected by %s.",
|
||||
SPELLREFLECTSELFOTHER or "Your %s was reflected back by %s.",
|
||||
SPELLPARRIEDSELFOTHER or "Your %s was parried by %s.",
|
||||
SPELLLOGABSORBSELFOTHER or "Your %s is absorbed by %s.",
|
||||
}
|
||||
|
||||
NanamiPlates.TANK_CLASSES = {
|
||||
["Warrior"] = true,
|
||||
["Paladin"] = true,
|
||||
["Druid"] = true,
|
||||
["Shaman"] = true,
|
||||
}
|
||||
|
||||
if DEFAULT_CHAT_FRAME then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffff88cc[Nanami-Plates]|r Loading...")
|
||||
end
|
||||
153
Healthbar.lua
Normal file
153
Healthbar.lua
Normal file
@@ -0,0 +1,153 @@
|
||||
NanamiPlates_Healthbar = {}
|
||||
|
||||
local NP = NanamiPlates
|
||||
local NEUTRAL_COLOR = {0.9, 0.7, 0.0, 1}
|
||||
local HOSTILE_THRESHOLD = {r_min = 0.9, g_max = 0.2, b_max = 0.2}
|
||||
local NEUTRAL_THRESHOLD = {r_min = 0.9, g_min = 0.9, b_max = 0.2}
|
||||
|
||||
function NanamiPlates_Healthbar.ResetCache(nameplate)
|
||||
if not nameplate then return end
|
||||
nameplate.cachedIsFriendly = nil
|
||||
nameplate.cachedIsHostile = nil
|
||||
nameplate.cachedIsNeutral = nil
|
||||
nameplate.wasNeutral = nil
|
||||
nameplate.lastPlateName = nil
|
||||
nameplate.lastColorR = nil
|
||||
nameplate.lastColorG = nil
|
||||
nameplate.lastColorB = nil
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.DetectUnitType(nameplate, original)
|
||||
if not nameplate or not original or not original.healthbar then
|
||||
return false, false, true, 0, 1, 0
|
||||
end
|
||||
|
||||
local r, g, b = original.healthbar:GetStatusBarColor()
|
||||
|
||||
-- Guard: if color is (0,0,0) the engine hasn't set it yet, use previous cache or default hostile
|
||||
if r == 0 and g == 0 and b == 0 then
|
||||
if nameplate.cachedIsHostile ~= nil then
|
||||
return nameplate.cachedIsHostile, nameplate.cachedIsNeutral, nameplate.cachedIsFriendly, r, g, b
|
||||
end
|
||||
return true, false, false, 1, 0, 0
|
||||
end
|
||||
|
||||
local isHostile, isNeutral, isFriendly
|
||||
|
||||
local lastR, lastG, lastB = nameplate.lastColorR, nameplate.lastColorG, nameplate.lastColorB
|
||||
|
||||
if r == lastR and g == lastG and b == lastB then
|
||||
isHostile = nameplate.cachedIsHostile
|
||||
isNeutral = nameplate.cachedIsNeutral
|
||||
isFriendly = nameplate.cachedIsFriendly
|
||||
else
|
||||
isHostile = r > HOSTILE_THRESHOLD.r_min and g < HOSTILE_THRESHOLD.g_max and b < HOSTILE_THRESHOLD.b_max
|
||||
isNeutral = r > NEUTRAL_THRESHOLD.r_min and g > NEUTRAL_THRESHOLD.g_min and b < NEUTRAL_THRESHOLD.b_max
|
||||
isFriendly = not isHostile and not isNeutral
|
||||
|
||||
nameplate.lastColorR = r
|
||||
nameplate.lastColorG = g
|
||||
nameplate.lastColorB = b
|
||||
nameplate.cachedIsHostile = isHostile
|
||||
nameplate.cachedIsNeutral = isNeutral
|
||||
nameplate.cachedIsFriendly = isFriendly
|
||||
|
||||
if nameplate.wasNeutral == nil then
|
||||
nameplate.wasNeutral = isNeutral
|
||||
end
|
||||
end
|
||||
|
||||
return isHostile, isNeutral, isFriendly, r, g, b
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.CheckUnitChange(nameplate, plateName, isNeutral)
|
||||
if not nameplate then return end
|
||||
if plateName and plateName ~= nameplate.lastPlateName then
|
||||
nameplate.lastPlateName = plateName
|
||||
nameplate.wasNeutral = isNeutral
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.IsNeutral(nameplate)
|
||||
if not nameplate then return false end
|
||||
return nameplate.cachedIsNeutral or nameplate.wasNeutral or false
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.WasNeutral(nameplate)
|
||||
if not nameplate then return false end
|
||||
return nameplate.wasNeutral or false
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.IsFriendly(nameplate)
|
||||
if not nameplate then return false end
|
||||
return nameplate.cachedIsFriendly or false
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.IsHostile(nameplate)
|
||||
if not nameplate then return false end
|
||||
return nameplate.cachedIsHostile or false
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.GetNeutralColor()
|
||||
return NEUTRAL_COLOR[1], NEUTRAL_COLOR[2], NEUTRAL_COLOR[3], NEUTRAL_COLOR[4]
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.ApplyNeutralColor(nameplate)
|
||||
if not nameplate or not nameplate.health then return end
|
||||
nameplate.health:SetStatusBarColor(NEUTRAL_COLOR[1], NEUTRAL_COLOR[2], NEUTRAL_COLOR[3], NEUTRAL_COLOR[4])
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.ShouldShowNeutral(nameplate, isNeutral, isAttackingPlayer)
|
||||
if not nameplate then return false end
|
||||
return (isNeutral or nameplate.wasNeutral) and not isAttackingPlayer
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.IsCritter(frame, nameplate, original, unitstr)
|
||||
if unitstr and UnitCreatureType then
|
||||
local creatureType = UnitCreatureType(unitstr)
|
||||
if creatureType == "Critter" then return true end
|
||||
end
|
||||
|
||||
if NP and NP.Critters then
|
||||
local plateName = nil
|
||||
if original and original.name and original.name.GetText then
|
||||
plateName = original.name:GetText()
|
||||
end
|
||||
if not plateName and nameplate and nameplate.name and nameplate.name.GetText then
|
||||
plateName = nameplate.name:GetText()
|
||||
end
|
||||
if plateName and NP.Critters[string.lower(plateName)] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
if not unitstr and original and original.healthbar then
|
||||
local r, g, b = original.healthbar:GetStatusBarColor()
|
||||
local isNeutralColor = r > 0.9 and g > 0.9 and b < 0.2
|
||||
if isNeutralColor then
|
||||
local levelText = nil
|
||||
if original.level and original.level.GetText then
|
||||
levelText = original.level:GetText()
|
||||
end
|
||||
local _, hpmax = original.healthbar:GetMinMaxValues()
|
||||
if levelText == "1" and hpmax and hpmax > 0 and hpmax < 100 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function NanamiPlates_Healthbar.ShouldSkipNameplate(frame, nameplate, original, Settings)
|
||||
if not Settings or Settings.showCritterNameplates then
|
||||
return false
|
||||
end
|
||||
local unitstr = nil
|
||||
if NP and NP.superwow_active and frame and frame.GetName then
|
||||
unitstr = frame:GetName(1)
|
||||
end
|
||||
return NanamiPlates_Healthbar.IsCritter(frame, nameplate, original, unitstr)
|
||||
end
|
||||
|
||||
NanamiPlates.Healthbar = NanamiPlates_Healthbar
|
||||
22
Nanami-Plates.toc
Normal file
22
Nanami-Plates.toc
Normal file
@@ -0,0 +1,22 @@
|
||||
## Interface: 11200
|
||||
## Title: Nanami-Plates
|
||||
## Notes: Nanami-UI style nameplates for Turtle WoW
|
||||
## Author: Nanami
|
||||
## Version: 1.0.0
|
||||
## Dependencies: Nanami-UI
|
||||
## OptionalDeps: TWThreat
|
||||
## SavedVariables: NanamiPlatesDB
|
||||
|
||||
Core.lua
|
||||
Config.lua
|
||||
SpellDB.lua
|
||||
Scanner.lua
|
||||
Healthbar.lua
|
||||
Castbar.lua
|
||||
Auras.lua
|
||||
ComboPoints.lua
|
||||
Threat.lua
|
||||
Target.lua
|
||||
Plates.lua
|
||||
CombatLog.lua
|
||||
Options.lua
|
||||
1072
Options.lua
Normal file
1072
Options.lua
Normal file
File diff suppressed because it is too large
Load Diff
1402
Plates.lua
Normal file
1402
Plates.lua
Normal file
File diff suppressed because it is too large
Load Diff
107
README.md
Normal file
107
README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Nanami-Plates
|
||||
|
||||
> Turtle WoW 高度可定制的姓名板替换插件,采用 Nanami-UI 风格,提供血条、施法条、减益追踪、仇恨着色、连击点、目标指示、任务怪标记等丰富功能,完整支持 SuperWoW 增强特性。
|
||||
|
||||
**Nanami-Plates** 完全替换游戏默认姓名板,为 Turtle WoW (1.12) 提供现代化、美观且功能强大的姓名板体验。作为 Nanami-UI 套件的一部分,它自动继承主题配色、字体和材质,与整体 UI 风格浑然一体。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 血条系统
|
||||
|
||||
- 敌方与友方姓名板独立设置宽度、高度、字号
|
||||
- 6 种血量显示格式:隐藏 / 百分比 / 当前值 / 当前(百分比) / 当前/最大 / 当前/最大 %
|
||||
- 等级文本根据与玩家的等级差自动着色(红/橙/黄/绿/灰)
|
||||
- 精英(+)、稀有、世界Boss(??) 分类标识
|
||||
- 玩家单位自动显示职业颜色
|
||||
- 被其他玩家标记的怪物显示灰色(Tapped 检测)
|
||||
- 可选隐藏小动物(兔子、松鼠等)姓名板
|
||||
|
||||
### 施法条
|
||||
|
||||
- 显示法术名称、法术图标和施法倒计时
|
||||
- 完整支持 SuperWoW 的 `UNIT_CASTEVENT` / `UnitCastingInfo` / `UnitChannelInfo`
|
||||
- 无 SuperWoW 时回退至暴雪原生施法条数据
|
||||
- 敌方与友方施法条独立配置尺寸
|
||||
|
||||
### 减益 (Debuff) 追踪
|
||||
|
||||
- 在姓名板下方显示减益图标,支持最多 16 个
|
||||
- 图标上实时显示剩余时间倒计时(颜色随时间变化:白→黄→红)
|
||||
- 内置完整的法术持续时间数据库(SpellDB),涵盖所有职业的 DoT、诅咒、毒药、陷阱等
|
||||
- 可选「仅显示自己的减益」过滤模式
|
||||
- 术士诅咒互斥逻辑完美处理(含 Malediction 天赋支持)
|
||||
- 自动学习本地化法术名称映射,多语言服务器无缝兼容
|
||||
|
||||
### 连击点显示
|
||||
|
||||
- 盗贼和德鲁伊(猫形态)在目标姓名板上方显示连击点
|
||||
- 每个连击点独立着色(绿→黄→橙→红),带数字标识
|
||||
- 可自定义连击点大小
|
||||
|
||||
### 仇恨系统
|
||||
|
||||
- 集成 TWThreat 插件数据,实时仇恨着色
|
||||
- 自动检测坦克天赋(战士防护 / 骑士防护 / 德鲁伊野性)自动切换坦克/输出模式
|
||||
- **坦克模式:** 持有仇恨=绿色 / 即将丢失=橙色 / 未持有=红色 / 其他坦克持有=蓝色
|
||||
- **输出模式:** OT=红色 / 高仇恨=橙色 / 安全=默认色
|
||||
- 目标被控制(击晕/闷棍等)时显示紫色
|
||||
- 通过插件频道在队伍/团队中广播坦克模式
|
||||
|
||||
### 目标指示
|
||||
|
||||
- 4 种箭头样式可选,显示在目标姓名板两侧 `>> [血条] <<`
|
||||
- 箭头大小、偏移量、染色强度均可调节
|
||||
- 目标血条高亮发光效果(自适应亮度的 Additive 混合)
|
||||
- 目标血条边框高亮
|
||||
- 目标姓名板自动提升 FrameStrata 保持在最前
|
||||
- 非目标姓名板透明度可调(默认 35%)
|
||||
|
||||
### 法力/能量/怒气条
|
||||
|
||||
- SuperWoW 环境下可在血条下方显示目标的法力条
|
||||
- 根据能量类型自动着色(蓝=法力 / 红=怒气 / 橙=能量 / 黄=幸福值)
|
||||
|
||||
### 任务怪标记
|
||||
|
||||
- 自动扫描任务日志,在任务目标怪物旁显示 "!" 图标
|
||||
- 显示击杀进度(如 "3/8")
|
||||
- 兼容 pfQuest 的提示数据
|
||||
|
||||
### 团队标记
|
||||
|
||||
- 正确显示团队标记图标(骷髅、叉、月亮等)
|
||||
|
||||
## 设置面板
|
||||
|
||||
输入 `/np` 或点击小地图按钮即可打开设置界面,共 5 个标签页:
|
||||
|
||||
| 标签 | 内容 |
|
||||
|------|------|
|
||||
| 血条 | 敌方/友方血条尺寸、字号、血量格式、垂直偏移 |
|
||||
| 施法条 | 敌方/友方施法条尺寸、法术图标开关 |
|
||||
| 减益 | 减益计时器、仅显示自己的减益、图标大小、连击点设置 |
|
||||
| 目标 | 箭头样式/大小/偏移/染色、非目标透明度、坦克/输出角色切换 |
|
||||
| 其他 | 小动物姓名板、法力条、任务怪图标、重置设置 |
|
||||
|
||||
## 命令
|
||||
|
||||
| 命令 | 功能 |
|
||||
|------|------|
|
||||
| `/np` | 打开设置面板 |
|
||||
| `/np tank` | 切换坦克/输出模式 |
|
||||
| `/np reset` | 重置所有设置并重载 UI |
|
||||
| `/np minimap` | 显示/隐藏小地图按钮 |
|
||||
| `/np debug` | 输出当前目标的 Debuff 调试信息 |
|
||||
|
||||
## 依赖与兼容
|
||||
|
||||
- **必需:** Nanami-UI 主框架
|
||||
- **可选:** TWThreat(仇恨数据源)
|
||||
- **增强:** SuperWoW — 启用后解锁施法条详情、精确减益追踪、法力条显示等高级功能
|
||||
- **自动禁用冲突:** 自动禁用 ShaguTweaks 和 pfUI 的姓名板模块,避免冲突
|
||||
|
||||
## 安装
|
||||
|
||||
1. 将 `Nanami-Plates` 文件夹放入 `Interface\AddOns\`
|
||||
2. 确保已安装 Nanami-UI
|
||||
3. 进入游戏,按 `V` 键显示姓名板
|
||||
63
Scanner.lua
Normal file
63
Scanner.lua
Normal file
@@ -0,0 +1,63 @@
|
||||
NanamiPlates_Scanner = {}
|
||||
|
||||
local initializedChildren = 0
|
||||
local cachedWorldChildren = {}
|
||||
|
||||
local function CheckRegionForBorder(r)
|
||||
if r and r.GetObjectType and r:GetObjectType() == "Texture" and r.GetTexture then
|
||||
return r:GetTexture() == "Interface\\Tooltips\\Nameplate-Border"
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function NanamiPlates_Scanner.IsNamePlate(frame)
|
||||
if not frame then return nil end
|
||||
local objType = frame:GetObjectType()
|
||||
if objType ~= "Frame" and objType ~= "Button" then return nil end
|
||||
|
||||
local r1, r2, r3, r4, r5, r6 = frame:GetRegions()
|
||||
if CheckRegionForBorder(r1) then return true end
|
||||
if CheckRegionForBorder(r2) then return true end
|
||||
if CheckRegionForBorder(r3) then return true end
|
||||
if CheckRegionForBorder(r4) then return true end
|
||||
if CheckRegionForBorder(r5) then return true end
|
||||
if CheckRegionForBorder(r6) then return true end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function NanamiPlates_Scanner.ScanForNewNameplates(registry, callback)
|
||||
local parentcount = WorldFrame:GetNumChildren()
|
||||
if initializedChildren >= parentcount then
|
||||
return false
|
||||
end
|
||||
|
||||
cachedWorldChildren = { WorldFrame:GetChildren() }
|
||||
local foundNew = false
|
||||
|
||||
for i = initializedChildren + 1, parentcount do
|
||||
local plate = cachedWorldChildren[i]
|
||||
if plate and not registry[plate] then
|
||||
if NanamiPlates_Scanner.IsNamePlate(plate) then
|
||||
callback(plate)
|
||||
foundNew = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
initializedChildren = parentcount
|
||||
return foundNew
|
||||
end
|
||||
|
||||
function NanamiPlates_Scanner.Reset()
|
||||
initializedChildren = 0
|
||||
for k in pairs(cachedWorldChildren) do
|
||||
cachedWorldChildren[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_Scanner.GetInitializedCount()
|
||||
return initializedChildren
|
||||
end
|
||||
|
||||
NanamiPlates.Scanner = NanamiPlates_Scanner
|
||||
1484
SpellDB.lua
Normal file
1484
SpellDB.lua
Normal file
File diff suppressed because it is too large
Load Diff
65
Target.lua
Normal file
65
Target.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
NanamiPlates_Target = {}
|
||||
|
||||
local NP = NanamiPlates
|
||||
local Settings = NP.Settings
|
||||
|
||||
local function ApplyArrowTint(ind)
|
||||
if not ind or not ind.tex then return end
|
||||
local tint = Settings.targetArrowTint or 0
|
||||
if tint <= 0 then
|
||||
ind.tex:SetVertexColor(1, 1, 1, 1)
|
||||
else
|
||||
local acR, acG, acB = NP.GetThemeColor("accent", 1.0, 0.5, 0.8, 1)
|
||||
local r = 1 + (acR - 1) * tint
|
||||
local g = 1 + (acG - 1) * tint
|
||||
local b = 1 + (acB - 1) * tint
|
||||
ind.tex:SetVertexColor(r, g, b, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_Target.UpdateTarget(nameplate, isTarget)
|
||||
if not nameplate then return end
|
||||
|
||||
local indL = nameplate.targetArrowL
|
||||
local indR = nameplate.targetArrowR
|
||||
if not indL or not indR then return end
|
||||
|
||||
if isTarget and Settings.showTargetGlow then
|
||||
local acR, acG, acB = NP.GetThemeColor("accent", 1.0, 0.5, 0.8, 0.9)
|
||||
ApplyArrowTint(indL)
|
||||
ApplyArrowTint(indR)
|
||||
indL:Show()
|
||||
indR:Show()
|
||||
-- Highlight healthBG border for target
|
||||
if nameplate.healthBG then
|
||||
nameplate.healthBG:SetBackdropBorderColor(acR, acG, acB, 1)
|
||||
end
|
||||
-- Show additive glow overlay (color/alpha synced in UpdateNamePlate)
|
||||
if nameplate.targetGlow then
|
||||
if not nameplate.targetGlow:IsShown() then
|
||||
local barR, barG, barB = nameplate.health:GetStatusBarColor()
|
||||
local lum = 0.299 * barR + 0.587 * barG + 0.114 * barB
|
||||
local glowAlpha = 0.75
|
||||
if lum > 0.5 then
|
||||
glowAlpha = 0.75 * math.max(0.15, (1.0 - lum) * 2)
|
||||
end
|
||||
nameplate.targetGlow:SetStatusBarColor(barR, barG, barB, 1)
|
||||
nameplate.targetGlow:SetAlpha(glowAlpha)
|
||||
end
|
||||
nameplate.targetGlow:Show()
|
||||
end
|
||||
else
|
||||
indL:Hide()
|
||||
indR:Hide()
|
||||
-- Restore default border for non-target
|
||||
if nameplate.healthBG then
|
||||
local brR, brG, brB, brA = NP.GetThemeColor("panelBorder", 0.55, 0.30, 0.42, 0.9)
|
||||
nameplate.healthBG:SetBackdropBorderColor(brR, brG, brB, brA)
|
||||
end
|
||||
if nameplate.targetGlow then
|
||||
nameplate.targetGlow:Hide()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
NanamiPlates.Target = NanamiPlates_Target
|
||||
204
Threat.lua
Normal file
204
Threat.lua
Normal file
@@ -0,0 +1,204 @@
|
||||
NanamiPlates_Threat = {}
|
||||
|
||||
local NP = NanamiPlates
|
||||
|
||||
local string_find = string.find
|
||||
local string_sub = string.sub
|
||||
local string_format = string.format
|
||||
local string_gfind = string.gfind
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local tonumber = tonumber
|
||||
local tostring = tostring
|
||||
local GetTime = GetTime
|
||||
local UnitName = UnitName
|
||||
local UnitExists = UnitExists
|
||||
local UnitIsUnit = UnitIsUnit
|
||||
local UnitInRaid = UnitInRaid
|
||||
local UnitInParty = UnitInParty
|
||||
local SendAddonMessage = SendAddonMessage
|
||||
|
||||
local GP_TankModeThreats = {}
|
||||
local GP_Threats = {}
|
||||
local GP_TankPlayers = {}
|
||||
|
||||
NanamiPlates_Threat.GP_TankModeThreats = GP_TankModeThreats
|
||||
NanamiPlates_Threat.GP_Threats = GP_Threats
|
||||
NanamiPlates_Threat.GP_TankPlayers = GP_TankPlayers
|
||||
|
||||
local function GP_Split(str, delimiter)
|
||||
local result = {}
|
||||
local pattern = "([^" .. delimiter .. "]+)"
|
||||
for match in string_gfind(str, pattern) do
|
||||
table.insert(result, match)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function GP_HandleTankModePacket(packet)
|
||||
local startPos = string_find(packet, "TMTv1=")
|
||||
if not startPos then return end
|
||||
local dataStr = string_sub(packet, startPos + 6)
|
||||
|
||||
for k in pairs(GP_TankModeThreats) do GP_TankModeThreats[k] = nil end
|
||||
|
||||
local entries = GP_Split(dataStr, ";")
|
||||
for _, entry in ipairs(entries) do
|
||||
local parts = GP_Split(entry, ":")
|
||||
if parts[1] and parts[2] and parts[3] and parts[4] then
|
||||
GP_TankModeThreats[parts[2]] = {
|
||||
creature = parts[1],
|
||||
name = parts[3],
|
||||
perc = tonumber(parts[4]) or 0
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function GP_HandleThreatPacket(packet)
|
||||
local startPos = string_find(packet, "TWTv4=")
|
||||
if not startPos then return end
|
||||
local dataStr = string_sub(packet, startPos + 6)
|
||||
|
||||
for k in pairs(GP_Threats) do GP_Threats[k] = nil end
|
||||
|
||||
local entries = GP_Split(dataStr, ";")
|
||||
for _, entry in ipairs(entries) do
|
||||
local parts = GP_Split(entry, ":")
|
||||
if parts[1] and parts[3] and parts[4] then
|
||||
GP_Threats[parts[1]] = {
|
||||
threat = tonumber(parts[3]) or 0,
|
||||
perc = tonumber(parts[4]) or 0,
|
||||
tank = (parts[6] == "1")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local NP_ADDON_PREFIX = "NanamiPlates"
|
||||
local TANK_BROADCAST_DEBOUNCE = 5
|
||||
local lastTankBroadcast = 0
|
||||
|
||||
function NanamiPlates_Threat.BroadcastTankMode(force)
|
||||
local now = GetTime()
|
||||
if not force and (now - lastTankBroadcast) < TANK_BROADCAST_DEBOUNCE then return end
|
||||
|
||||
local playerRole = NP and NP.playerRole
|
||||
local isTank = (playerRole == "TANK")
|
||||
local msg = isTank and "TM=1" or "TM=0"
|
||||
|
||||
local myName = UnitName("player")
|
||||
if myName then
|
||||
GP_TankPlayers[myName] = isTank or nil
|
||||
end
|
||||
|
||||
if UnitInRaid("player") then
|
||||
SendAddonMessage(NP_ADDON_PREFIX, msg, "RAID")
|
||||
lastTankBroadcast = now
|
||||
elseif UnitInParty() then
|
||||
SendAddonMessage(NP_ADDON_PREFIX, msg, "PARTY")
|
||||
lastTankBroadcast = now
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_Threat.IsPlayerTank(playerName)
|
||||
if not playerName then return false end
|
||||
return GP_TankPlayers[playerName] == true
|
||||
end
|
||||
|
||||
local function GP_HandleTankModeMessage(sender, msg)
|
||||
if string_find(msg, "TM=") then
|
||||
local isTank = string_sub(msg, 4, 4) == "1"
|
||||
GP_TankPlayers[sender] = isTank or nil
|
||||
end
|
||||
end
|
||||
|
||||
function NanamiPlates_Threat.GetTWTankModeThreat(mobGUID, mobName)
|
||||
local playerName = UnitName("player")
|
||||
|
||||
if mobGUID then
|
||||
local data = GP_TankModeThreats[mobGUID]
|
||||
if data then
|
||||
local playerHasAggro = (data.name == playerName)
|
||||
return true, playerHasAggro, data.name, data.perc or 0
|
||||
end
|
||||
end
|
||||
|
||||
if mobName then
|
||||
for guid, data in pairs(GP_TankModeThreats) do
|
||||
if data.creature == mobName then
|
||||
local playerHasAggro = (data.name == playerName)
|
||||
return true, playerHasAggro, data.name, data.perc or 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false, false, nil, 0
|
||||
end
|
||||
|
||||
function NanamiPlates_Threat.GetGPThreatData()
|
||||
local playerName = UnitName("player")
|
||||
local playerPct = 0
|
||||
local highestOtherPct = 0
|
||||
local hasData = false
|
||||
local threatHolderName = nil
|
||||
|
||||
for name, data in pairs(GP_Threats) do
|
||||
hasData = true
|
||||
local pct = data.perc or 0
|
||||
if name == playerName then
|
||||
playerPct = pct
|
||||
else
|
||||
if pct > highestOtherPct then highestOtherPct = pct end
|
||||
end
|
||||
if pct >= 100 then threatHolderName = name end
|
||||
end
|
||||
|
||||
return hasData, (playerPct >= 100), playerPct, highestOtherPct, threatHolderName
|
||||
end
|
||||
|
||||
function NanamiPlates_Threat.IsInPlayerGroup(unit)
|
||||
if not unit or not UnitExists(unit) then return false end
|
||||
if UnitIsUnit(unit, "player") then return true end
|
||||
|
||||
for i = 1, 4 do
|
||||
if UnitIsUnit(unit, "party" .. i) then return true end
|
||||
end
|
||||
if UnitInRaid("player") then
|
||||
for i = 1, 40 do
|
||||
if UnitIsUnit(unit, "raid" .. i) then return true end
|
||||
end
|
||||
end
|
||||
if UnitIsUnit(unit, "pet") then return true end
|
||||
for i = 1, 4 do
|
||||
if UnitIsUnit(unit, "partypet" .. i) then return true end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local NP_ThreatFrame = CreateFrame("Frame")
|
||||
NP_ThreatFrame:RegisterEvent("CHAT_MSG_ADDON")
|
||||
NP_ThreatFrame:RegisterEvent("PARTY_MEMBERS_CHANGED")
|
||||
NP_ThreatFrame:RegisterEvent("RAID_ROSTER_UPDATE")
|
||||
NP_ThreatFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
NP_ThreatFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||
NP_ThreatFrame:SetScript("OnEvent", function()
|
||||
if event == "CHAT_MSG_ADDON" then
|
||||
local prefix = arg1
|
||||
local msg = arg2 or ""
|
||||
local sender = arg4
|
||||
|
||||
if string_find(msg, "TMTv1=") then GP_HandleTankModePacket(msg) end
|
||||
if string_find(msg, "TWTv4=") then GP_HandleThreatPacket(msg) end
|
||||
|
||||
if prefix == NP_ADDON_PREFIX and msg and sender then
|
||||
GP_HandleTankModeMessage(sender, msg)
|
||||
end
|
||||
elseif event == "PARTY_MEMBERS_CHANGED" or event == "RAID_ROSTER_UPDATE"
|
||||
or event == "ZONE_CHANGED_NEW_AREA" or event == "PLAYER_ENTERING_WORLD" then
|
||||
NanamiPlates_Threat.BroadcastTankMode(true)
|
||||
end
|
||||
end)
|
||||
|
||||
NanamiPlates.Threat = NanamiPlates_Threat
|
||||
BIN
img/arrow.tga
Normal file
BIN
img/arrow.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 256 KiB |
BIN
img/icon.tga
Normal file
BIN
img/icon.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Reference in New Issue
Block a user