Files
coa-elvui/ElvUI_Enhanced/Modules/Blizzard/DeathRecap.lua
T
Andrew6810 8ad40722a0 Fixes Enhanced character frame
Update nameplate coloring.
Fix world map blips
2022-10-30 16:41:03 -07:00

592 lines
18 KiB
Lua

local E, L, V, P, G = unpack(ElvUI)
local mod = E:GetModule("Enhanced_Blizzard")
local _G = _G
local select = select
local tonumber = tonumber
local unpack = unpack
local band = bit.band
local ceil, floor = math.ceil, math.floor
local format, upper, split, sub = string.format, string.upper, string.split, string.sub
local tsort, twipe = table.sort, table.wipe
local CannotBeResurrected = CannotBeResurrected
local CopyTable = CopyTable
local CreateFrame = CreateFrame
local GetReleaseTimeRemaining = GetReleaseTimeRemaining
local GetSpellInfo = GetSpellInfo
local GetSpellLink = GetSpellLink
local HasSoulstone = HasSoulstone
local IsActiveBattlefieldArena = IsActiveBattlefieldArena
local IsFalling = IsFalling
local IsOutOfBounds = IsOutOfBounds
local RepopMe = RepopMe
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UseSoulstone = UseSoulstone
local ACTION_SWING = ACTION_SWING
local ARENA_SPECTATOR = ARENA_SPECTATOR
local COMBATLOG_FILTER_ME = COMBATLOG_FILTER_ME
local COMBATLOG_UNKNOWN_UNIT = COMBATLOG_UNKNOWN_UNIT
local DEATH_RELEASE_NOTIMER = DEATH_RELEASE_NOTIMER
local DEATH_RELEASE_SPECTATOR = DEATH_RELEASE_SPECTATOR
local DEATH_RELEASE_TIMER = DEATH_RELEASE_TIMER
local MINUTES = MINUTES
local SECONDS = SECONDS
local TEXT_MODE_A_STRING_VALUE_SCHOOL = TEXT_MODE_A_STRING_VALUE_SCHOOL
local lastDeathEvents
local index = 0
local deathList = {}
local eventList = {}
local function AddEvent(timestamp, event, sourceName, spellId, spellName, environmentalType, amount, overkill, school, resisted, blocked, absorbed)
if index > 0 and eventList[index].timestamp + 10 <= timestamp then
index = 0
twipe(eventList)
end
if index < 5 then
index = index + 1
else
index = 1
end
if not eventList[index] then
eventList[index] = {}
else
twipe(eventList[index])
end
eventList[index].timestamp = timestamp
eventList[index].event = event
eventList[index].sourceName = sourceName
eventList[index].spellId = spellId
eventList[index].spellName = spellName
eventList[index].environmentalType = environmentalType
eventList[index].amount = amount
eventList[index].overkill = overkill
eventList[index].school = school
eventList[index].resisted = resisted
eventList[index].blocked = blocked
eventList[index].absorbed = absorbed
eventList[index].currentHP = UnitHealth("player")
eventList[index].maxHP = UnitHealthMax("player")
end
local function HasEvents()
if lastDeathEvents then
return #deathList > 0, #deathList
else
return false, #deathList
end
end
local function EraseEvents()
if index > 0 then
index = 0
twipe(eventList)
end
end
local function AddDeath()
if #eventList > 0 then
local _, deathEvents = HasEvents()
local deathIndex = deathEvents + 1
deathList[deathIndex] = CopyTable(eventList)
EraseEvents()
DEFAULT_CHAT_FRAME:AddMessage("|cff71d5ff|Hdeath:"..deathIndex.."|h["..L["You died."].."]|h|r")
return true
end
end
local function GetDeathEvents(recapID)
if recapID and deathList[recapID] then
local deathEvents = deathList[recapID]
tsort(deathEvents, function(a, b) return a.timestamp > b.timestamp end)
return deathEvents
end
end
local function GetTableInfo(data)
local texture
local nameIsNotSpell = false
local event = data.event
local spellId = data.spellId
local spellName = data.spellName
if event == "SWING_DAMAGE" then
spellId = 6603
spellName = ACTION_SWING
nameIsNotSpell = true
elseif event == "RANGE_DAMAGE" then
nameIsNotSpell = true
-- elseif sub(event, 1, 5) == "SPELL" then
elseif event == "ENVIRONMENTAL_DAMAGE" then
local environmentalType = data.environmentalType
environmentalType = upper(environmentalType)
spellName = _G["ACTION_ENVIRONMENTAL_DAMAGE_"..environmentalType]
nameIsNotSpell = true
if environmentalType == "DROWNING" then
texture = "spell_shadow_demonbreath"
elseif environmentalType == "FALLING" then
texture = "ability_rogue_quickrecovery"
elseif environmentalType == "FIRE" or environmentalType == "LAVA" then
texture = "spell_fire_fire"
elseif environmentalType == "SLIME" then
texture = "inv_misc_slime_01"
elseif environmentalType == "FATIGUE" then
texture = "ability_creature_cursed_05"
else
texture = "ability_creature_cursed_05"
end
texture = "Interface\\Icons\\" .. texture
end
if spellName and nameIsNotSpell then
spellName = format("|Haction:%s|h%s|h", event, spellName)
end
if spellId and not texture then
texture = select(3, GetSpellInfo(spellId))
end
return spellId, spellName, texture
end
local function OpenRecap(recapID)
local self = ElvUI_DeathRecapFrame
if self:IsShown() and self.recapID == recapID then
self:Hide()
return
end
local deathEvents = GetDeathEvents(recapID)
if not deathEvents then return end
self.recapID = recapID
self:Show()
if not deathEvents or #deathEvents <= 0 then
for i = 1, 5 do
self.DeathRecapEntry[i]:Hide()
end
self.Unavailable:Show()
return
end
self.Unavailable:Hide()
local highestDmgIdx, highestDmgAmount = 1, 0
self.DeathTimeStamp = nil
for i = 1, #deathEvents do
local entry = self.DeathRecapEntry[i]
local dmgInfo = entry.DamageInfo
local evtData = deathEvents[i]
local spellId, spellName, texture = GetTableInfo(evtData)
entry:Show()
self.DeathTimeStamp = self.DeathTimeStamp or evtData.timestamp
if evtData.amount then
local amountStr = -evtData.amount
dmgInfo.Amount:SetText(amountStr)
dmgInfo.AmountLarge:SetText(amountStr)
dmgInfo.amount = evtData.amount
dmgInfo.dmgExtraStr = ""
if evtData.overkill and evtData.overkill > 0 then
dmgInfo.dmgExtraStr = format(L["(%d Overkill)"], evtData.overkill)
dmgInfo.amount = evtData.amount - evtData.overkill
end
if evtData.absorbed and evtData.absorbed > 0 then
dmgInfo.dmgExtraStr = dmgInfo.dmgExtraStr.." "..format(L["(%d Absorbed)"], evtData.absorbed)
dmgInfo.amount = evtData.amount - evtData.absorbed
end
if evtData.resisted and evtData.resisted > 0 then
dmgInfo.dmgExtraStr = dmgInfo.dmgExtraStr.." "..format(L["(%d Resisted)"], evtData.resisted)
dmgInfo.amount = evtData.amount - evtData.resisted
end
if evtData.blocked and evtData.blocked > 0 then
dmgInfo.dmgExtraStr = dmgInfo.dmgExtraStr.." "..format(L["(%d Blocked)"], evtData.blocked)
dmgInfo.amount = evtData.amount - evtData.blocked
end
if evtData.amount > highestDmgAmount then
highestDmgIdx = i
highestDmgAmount = evtData.amount
end
dmgInfo.Amount:Show()
dmgInfo.AmountLarge:Hide()
else
dmgInfo.Amount:SetText("")
dmgInfo.AmountLarge:SetText("")
dmgInfo.amount = nil
dmgInfo.dmgExtraStr = nil
end
dmgInfo.timestamp = evtData.timestamp
dmgInfo.hpPercent = floor(evtData.currentHP / evtData.maxHP * 100)
dmgInfo.spellName = spellName
dmgInfo.caster = evtData.sourceName or COMBATLOG_UNKNOWN_UNIT
if evtData.school and evtData.school > 1 then
local colorArray = CombatLog_Color_ColorArrayBySchool(evtData.school)
entry.SpellInfo.FrameIcon:SetBackdropBorderColor(colorArray.r, colorArray.g, colorArray.b)
else
entry.SpellInfo.FrameIcon:SetBackdropBorderColor(unpack(E.media.bordercolor))
end
dmgInfo.school = evtData.school
entry.SpellInfo.Caster:SetText(dmgInfo.caster)
entry.SpellInfo.Name:SetText(spellName)
entry.SpellInfo.Icon:SetTexture(texture)
entry.SpellInfo.spellId = spellId
end
for i = #deathEvents + 1, #self.DeathRecapEntry do
self.DeathRecapEntry[i]:Hide()
end
local entry = self.DeathRecapEntry[highestDmgIdx]
if entry.DamageInfo.amount then
entry.DamageInfo.Amount:Hide()
entry.DamageInfo.AmountLarge:Show()
end
local deathEntry = self.DeathRecapEntry[1]
local tombstoneIcon = deathEntry.tombstone
if entry == deathEntry then
tombstoneIcon:Point("RIGHT", deathEntry.DamageInfo.AmountLarge, "LEFT", -10, 0)
end
end
local function Spell_OnEnter(self)
if self.spellId then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetHyperlink(GetSpellLink(self.spellId))
GameTooltip:Show()
end
end
local function Amount_OnEnter(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:ClearLines()
if self.amount then
local valueStr = self.school and format(TEXT_MODE_A_STRING_VALUE_SCHOOL, self.amount, CombatLog_String_SchoolString(self.school)) or self.amount
GameTooltip:AddLine(format(L["%s %s"], valueStr, self.dmgExtraStr), 1, 0, 0, false)
end
if self.spellName then
if self.caster then
GameTooltip:AddLine(format(L["%s by %s"], self.spellName, self.caster), 1, 1, 1, true)
else
GameTooltip:AddLine(self.spellName, 1, 1, 1, true)
end
end
local seconds = ElvUI_DeathRecapFrame.DeathTimeStamp - self.timestamp
if seconds > 0 then
GameTooltip:AddLine(format(L["%s sec before death at %s%% health."], format("%.1F", seconds), self.hpPercent), 1, 0.824, 0, true)
else
GameTooltip:AddLine(format(L["Killing blow at %s%% health."], self.hpPercent), 1, 0.824, 0, true)
end
GameTooltip:Show()
end
function mod:HideDeathPopup()
E:StaticPopup_Hide("DEATH")
end
function mod:PLAYER_DEAD()
if StaticPopup_FindVisible("DEATH") then
if AddDeath() then
lastDeathEvents = true
else
lastDeathEvents = false
end
StaticPopup_Hide("DEATH")
E:StaticPopup_Show("DEATH", GetReleaseTimeRemaining(), SECONDS)
end
end
function mod:COMBAT_LOG_EVENT_UNFILTERED(_, timestamp, event, _, sourceName, sourceFlags, destGUID, destName, destFlags, ...)
if (band(destFlags, COMBATLOG_FILTER_ME) ~= COMBATLOG_FILTER_ME) or (band(sourceFlags, COMBATLOG_FILTER_ME) == COMBATLOG_FILTER_ME) then return end
if event ~= "ENVIRONMENTAL_DAMAGE"
and event ~= "RANGE_DAMAGE"
and event ~= "SPELL_DAMAGE"
and event ~= "SPELL_EXTRA_ATTACKS"
and event ~= "SPELL_INSTAKILL"
and event ~= "SPELL_PERIODIC_DAMAGE"
and event ~= "SWING_DAMAGE"
then return end
local subVal = sub(event, 1, 5)
local environmentalType, spellId, spellName, amount, overkill, school, resisted, blocked, absorbed
if event == "SWING_DAMAGE" then
amount, overkill, school, resisted, blocked, absorbed = ...
elseif subVal == "SPELL" then
spellId, spellName, _, amount, overkill, school, resisted, blocked, absorbed = ...
elseif event == "ENVIRONMENTAL_DAMAGE" then
environmentalType, amount, overkill, school, resisted, blocked, absorbed = ...
end
if not tonumber(amount) then return end
AddEvent(timestamp, event, sourceName, spellId, spellName, environmentalType, amount, overkill, school, resisted, blocked, absorbed)
end
function mod:SetItemRef(link, ...)
if sub(link, 1, 5) == "death" then
local _, id = split(":", link)
OpenRecap(tonumber(id))
return
else
self.hooks.SetItemRef(link, ...)
end
end
function mod:DeathRecap()
if DeathRecapFrame then return end
if not E.private.enhanced.deathRecap then return end
local S = E:GetModule("Skins")
local frame = CreateFrame("Frame", "ElvUI_DeathRecapFrame", UIParent)
frame:Size(340, 326)
frame:Point("CENTER")
frame:SetTemplate("Transparent")
frame:SetMovable(true)
frame:Hide()
frame:SetScript("OnHide", function(self) self.recapID = nil end)
tinsert(UISpecialFrames, frame:GetName())
frame.Title = frame:CreateFontString("ARTWORK", nil, "GameFontNormal")
frame.Title:Point("TOPLEFT", 12, -9)
frame.Title:SetText(L["Death Recap"])
frame.Unavailable = frame:CreateFontString("ARTWORK", nil, "GameFontNormal")
frame.Unavailable:Point("CENTER")
frame.Unavailable:SetText(L["Death Recap unavailable."])
frame.CloseXButton = CreateFrame("Button", "$parentCloseXButton", frame)
frame.CloseXButton:Size(32, 32)
frame.CloseXButton:Point("TOPRIGHT", 2, 1)
frame.CloseXButton:SetScript("OnClick", function(self) self:GetParent():Hide() end)
S:HandleCloseButton(frame.CloseXButton)
frame.DragButton = CreateFrame("Button", "$parentDragButton", frame)
frame.DragButton:Point("TOPLEFT", 0, 0)
frame.DragButton:Point("BOTTOMRIGHT", frame, "TOPRIGHT", 0, -32)
frame.DragButton:RegisterForDrag("LeftButton")
frame.DragButton:SetScript("OnDragStart", function(self) self:GetParent():StartMoving() end)
frame.DragButton:SetScript("OnDragStop", function(self) self:GetParent():StopMovingOrSizing() end)
frame.DeathRecapEntry = {}
for i = 1, 5 do
local button = CreateFrame("Frame", nil, frame)
button:Size(308, 32)
frame.DeathRecapEntry[i] = button
button.DamageInfo = CreateFrame("Button", nil, button)
button.DamageInfo:Point("TOPLEFT", 0, 0)
button.DamageInfo:Point("BOTTOMRIGHT", button, "BOTTOMLEFT", 80, 0)
button.DamageInfo:SetScript("OnEnter", Amount_OnEnter)
button.DamageInfo:SetScript("OnLeave", GameTooltip_Hide)
button.DamageInfo.Amount = button.DamageInfo:CreateFontString("ARTWORK", nil, "GameFontNormalRight")
button.DamageInfo.Amount:SetJustifyH("RIGHT")
button.DamageInfo.Amount:SetJustifyV("CENTER")
button.DamageInfo.Amount:Size(0, 32)
button.DamageInfo.Amount:Point("TOPRIGHT", 0, 0)
button.DamageInfo.Amount:SetTextColor(0.75, 0.05, 0.05, 1)
button.DamageInfo.AmountLarge = button.DamageInfo:CreateFontString("ARTWORK", nil, "NumberFont_Outline_Large")
button.DamageInfo.AmountLarge:SetJustifyH("RIGHT")
button.DamageInfo.AmountLarge:SetJustifyV("CENTER")
button.DamageInfo.AmountLarge:Size(0, 32)
button.DamageInfo.AmountLarge:Point("TOPRIGHT", 0, 0)
button.DamageInfo.AmountLarge:SetTextColor(1, 0.07, 0.07, 1)
button.SpellInfo = CreateFrame("Button", nil, button)
button.SpellInfo:Point("TOPLEFT", button.DamageInfo, "TOPRIGHT", 16, 0)
button.SpellInfo:Point("BOTTOMRIGHT", 0, 0)
button.SpellInfo:SetScript("OnEnter", Spell_OnEnter)
button.SpellInfo:SetScript("OnLeave", GameTooltip_Hide)
button.SpellInfo.FrameIcon = CreateFrame("Button", nil, button.SpellInfo)
button.SpellInfo.FrameIcon:Size(34, 34)
button.SpellInfo.FrameIcon:Point("LEFT", 0, 0)
button.SpellInfo.FrameIcon:SetTemplate("Default")
button.SpellInfo.Icon = button.SpellInfo:CreateTexture("ARTWORK")
button.SpellInfo.Icon:SetParent(button.SpellInfo.FrameIcon)
button.SpellInfo.Icon:SetTexCoord(unpack(E.TexCoords))
button.SpellInfo.Icon:SetInside()
button.SpellInfo.Name = button.SpellInfo:CreateFontString("ARTWORK", nil, "GameFontNormal")
button.SpellInfo.Name:SetJustifyH("LEFT")
button.SpellInfo.Name:SetJustifyV("BOTTOM")
button.SpellInfo.Name:Point("BOTTOMLEFT", button.SpellInfo.Icon, "RIGHT", 8, 1)
button.SpellInfo.Name:Point("TOPRIGHT", 0, 0)
button.SpellInfo.Caster = button.SpellInfo:CreateFontString("ARTWORK", nil, "SystemFont_Shadow_Small")
button.SpellInfo.Caster:SetJustifyH("LEFT")
button.SpellInfo.Caster:SetJustifyV("TOP")
button.SpellInfo.Caster:Point("TOPLEFT", button.SpellInfo.Icon, "RIGHT", 8, -2)
button.SpellInfo.Caster:Point("BOTTOMRIGHT", 0, 0)
button.SpellInfo.Caster:SetTextColor(0.5, 0.5, 0.5, 1)
if i == 1 then
button:Point("BOTTOMLEFT", 16, 64)
button.tombstone = button:CreateTexture("ARTWORK")
button.tombstone:Size(15, 20)
button.tombstone:Point("RIGHT", button.DamageInfo.Amount, "LEFT", -10, 0)
button.tombstone:SetTexCoord(0.658203125, 0.6875, 0.00390625, 0.08203125)
button.tombstone:SetTexture("Interface\\AddOns\\ElvUI_Enhanced\\media\\textures\\DeathRecap")
else
button:Point("BOTTOM", frame.DeathRecapEntry[i - 1], "TOP", 0, 14)
end
end
frame.CloseButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
frame.CloseButton:Size(144, 21)
frame.CloseButton:Point("BOTTOM", 0, 15)
frame.CloseButton:SetText(CLOSE)
frame.CloseButton:SetScript("OnClick", function(self) ElvUI_DeathRecapFrame:Hide() end)
S:HandleButton(frame.CloseButton)
self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
self:RegisterEvent("PLAYER_DEAD")
self:RegisterEvent("PLAYER_ENTERING_WORLD", "HideDeathPopup")
self:RegisterEvent("RESURRECT_REQUEST", "HideDeathPopup")
self:RegisterEvent("PLAYER_ALIVE", "HideDeathPopup")
self:RegisterEvent("RAISED_AS_GHOUL", "HideDeathPopup")
self:RawHook("SetItemRef", true)
E.PopupDialogs["DEATH"] = {
text = DEATH_RELEASE_TIMER,
button1 = DEATH_RELEASE,
button2 = USE_SOULSTONE,
button3 = L["Death Recap"],
OnShow = function(self)
self.timeleft = GetReleaseTimeRemaining()
local text = HasSoulstone()
if text then
self.button2:SetText(text)
end
if IsActiveBattlefieldArena() then
self.text:SetText(DEATH_RELEASE_SPECTATOR)
elseif self.timeleft == -1 then
self.text:SetText(DEATH_RELEASE_NOTIMER)
end
if HasEvents() then
self.button3:Enable()
self.button3:SetScript("OnEnter", S.SetModifiedBackdrop)
self.button3:SetScript("OnLeave", S.SetOriginalBackdrop)
else
self.button3:Disable()
self.button3:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT")
GameTooltip:SetText(L["Death Recap unavailable."])
GameTooltip:Show()
end)
self.button3:SetScript("OnLeave", GameTooltip_Hide)
end
end,
OnHide = function(self)
self.button3:SetScript("OnEnter", nil)
self.button3:SetScript("OnLeave", nil)
ElvUI_DeathRecapFrame:Hide()
end,
OnAccept = function(self)
if IsActiveBattlefieldArena() then
local info = ChatTypeInfo["SYSTEM"]
DEFAULT_CHAT_FRAME:AddMessage(ARENA_SPECTATOR, info.r, info.g, info.b, info.id)
end
RepopMe()
if CannotBeResurrected() then
return 1
end
end,
OnCancel = function(self, data, reason)
if reason == "override" then
StaticPopup_Show("RECOVER_CORPSE")
return
end
if reason == "timeout" then
return
end
if reason == "clicked" then
if HasSoulstone() then
UseSoulstone()
else
RepopMe()
end
if CannotBeResurrected() then
return 1
end
end
end,
OnAlt = function()
local _, recapID = HasEvents()
OpenRecap(recapID)
end,
OnUpdate = function(self, elapsed)
if self.timeleft > 0 then
local text = _G[self:GetName().."Text"]
local timeleft = self.timeleft
if timeleft < 60 then
text:SetFormattedText(DEATH_RELEASE_TIMER, timeleft, SECONDS)
else
text:SetFormattedText(DEATH_RELEASE_TIMER, ceil(timeleft / 60), MINUTES)
end
end
if IsFalling() and not IsOutOfBounds() then
self.button1:Disable()
self.button2:Disable()
return
else
self.button1:Enable()
end
if HasSoulstone() then
self.button2:Enable()
else
self.button2:Disable()
end
end,
DisplayButton2 = HasSoulstone,
timeout = 0,
whileDead = 1,
interruptCinematic = 1,
noCancelOnReuse = 1,
hideOnEscape = false,
noCloseOnAlt = true
}
end