You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1319 lines
42 KiB

---------------
-- Globals --
---------------
DBM.InfoFrame = {}
-------------------
-- Local Globals --
-------------------
local isRetail = WOW_PROJECT_ID == (WOW_PROJECT_MAINLINE or 1)
local DDM = _G["LibStub"]:GetLibrary("LibDropDownMenu")
local UIDropDownMenu_AddButton, UIDropDownMenu_Initialize, ToggleDropDownMenu = DDM.UIDropDownMenu_AddButton, DDM.UIDropDownMenu_Initialize, DDM.ToggleDropDownMenu
local DBM = DBM
local L = DBM_CORE_L
local UnitClass, GetTime, GetPartyAssignment, UnitGroupRolesAssigned, GetRaidTargetIndex, UnitExists, UnitGetTotalAbsorbs, UnitName, UnitHealth, UnitPower, UnitPowerMax, UnitIsDeadOrGhost, UnitThreatSituation, UnitPosition, UnitIsUnit = UnitClass, GetTime, GetPartyAssignment, UnitGroupRolesAssigned, GetRaidTargetIndex, UnitExists, UnitGetTotalAbsorbs, UnitName, UnitHealth, UnitPower, UnitPowerMax, UnitIsDeadOrGhost, UnitThreatSituation, UnitPosition, UnitIsUnit
local error, tostring, type, pairs, ipairs, select, tonumber, tsort, twipe, mfloor, mmax, mmin, mrandom, schar, ssplit = error, tostring, type, pairs, ipairs, select, tonumber, table.sort, table.wipe, math.floor, math.max, math.min, math.random, string.char, string.split
local NORMAL_FONT_COLOR, SPELL_FAILED_OUT_OF_RANGE = NORMAL_FONT_COLOR, SPELL_FAILED_OUT_OF_RANGE
local RAID_CLASS_COLORS = _G["CUSTOM_CLASS_COLORS"] or RAID_CLASS_COLORS-- for Phanx' Class Colors
--Hard code STANDARD_TEXT_FONT since skinning mods like to taint it (or worse, set it to nil, wtf?)
local standardFont
if LOCALE_koKR then
standardFont = "Fonts\\2002.TTF"
elseif LOCALE_zhCN then
standardFont = "Fonts\\ARKai_T.ttf"
elseif LOCALE_zhTW then
standardFont = "Fonts\\blei00d.TTF"
elseif LOCALE_ruRU then
standardFont = "Fonts\\FRIZQT___CYR.TTF"
else
standardFont = "Fonts\\FRIZQT__.TTF"
end
--------------
-- Locals --
--------------
local infoFrame = DBM.InfoFrame
local frame, initializeDropdown, currentMapId, currentEvent, createFrame
local maxLines, modLines, maxCols, modCols, prevLines = 5, 5, 1, 1, 0
local sortMethod = 1--1 Default, 2 SortAsc, 3 GroupId
local lines, sortedLines, icons, value = {}, {}, {}, {}
local playerName = UnitName("player")
---------------------
-- Dropdown Menu --
---------------------
do
local function toggleLocked()
DBM.Options.InfoFrameLocked = not DBM.Options.InfoFrameLocked
end
local function toggleShowSelf()
DBM.Options.InfoFrameShowSelf = not DBM.Options.InfoFrameShowSelf
end
local function setLines(_, line)
prevLines = 0
DBM.Options.InfoFrameLines = line
if line ~= 0 then
maxLines = line
else
maxLines = modLines or 5
end
end
local function setCols(_, col)
prevLines = 0
DBM.Options.InfoFrameCols = col
if col ~= 0 then
maxCols = col
else
maxCols = modCols or 5
end
end
function initializeDropdown(_, level, menu)
local info
if level == 1 then
info = {}
info.text = LOCK_FRAME
if DBM.Options.InfoFrameLocked then
info.checked = true
end
info.func = toggleLocked
UIDropDownMenu_AddButton(info, 1)
info = {}
info.keepShownOnClick = true
info.text = L.INFOFRAME_SHOW_SELF
if DBM.Options.InfoFrameShowSelf then
info.checked = true
end
info.func = toggleShowSelf
UIDropDownMenu_AddButton(info, 1)
info = {}
info.text = L.INFOFRAME_SETLINES
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "lines"
UIDropDownMenu_AddButton(info, 1)
info = {}
info.text = L.INFOFRAME_SETCOLS
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "cols"
UIDropDownMenu_AddButton(info, 1)
info = {}
info.text = HIDE
info.notCheckable = true
info.func = infoFrame.Hide
info.arg1 = infoFrame
UIDropDownMenu_AddButton(info, 1)
elseif level == 2 then
if menu == "lines" then
info = {}
info.text = L.INFOFRAME_LINESDEFAULT
info.func = setLines
info.arg1 = 0
info.checked = (DBM.Options.InfoFrameLines == 0)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_LINES_TO:format(3)
info.func = setLines
info.arg1 = 3
info.checked = (DBM.Options.InfoFrameLines == 3)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_LINES_TO:format(5)
info.func = setLines
info.arg1 = 5
info.checked = (DBM.Options.InfoFrameLines == 5)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_LINES_TO:format(8)
info.func = setLines
info.arg1 = 8
info.checked = (DBM.Options.InfoFrameLines == 8)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_LINES_TO:format(10)
info.func = setLines
info.arg1 = 10
info.checked = (DBM.Options.InfoFrameLines == 10)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_LINES_TO:format(15)
info.func = setLines
info.arg1 = 15
info.checked = (DBM.Options.InfoFrameLines == 15)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_LINES_TO:format(20)
info.func = setLines
info.arg1 = 20
info.checked = (DBM.Options.InfoFrameLines == 20)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_LINES_TO:format(isRetail and 30 or 40)
info.func = setLines
info.arg1 = isRetail and 30 or 40 -- Use 40 in classic for full man raids
info.checked = (DBM.Options.InfoFrameLines == 30)
UIDropDownMenu_AddButton(info, 2)
elseif menu == "cols" then
info = {}
info.text = L.INFOFRAME_LINESDEFAULT
info.func = setCols
info.arg1 = 0
info.checked = (DBM.Options.InfoFrameCols == 0)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_COLS_TO:format(1)
info.func = setCols
info.arg1 = 1
info.checked = (DBM.Options.InfoFrameCols == 1)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_COLS_TO:format(2)
info.func = setCols
info.arg1 = 2
info.checked = (DBM.Options.InfoFrameCols == 2)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_COLS_TO:format(3)
info.func = setCols
info.arg1 = 3
info.checked = (DBM.Options.InfoFrameCols == 3)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_COLS_TO:format(4)
info.func = setCols
info.arg1 = 4
info.checked = (DBM.Options.InfoFrameCols == 4)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_COLS_TO:format(5)
info.func = setCols
info.arg1 = 5
info.checked = (DBM.Options.InfoFrameCols == 5)
UIDropDownMenu_AddButton(info, 2)
info = {}
info.text = L.INFOFRAME_COLS_TO:format(6)
info.func = setCols
info.arg1 = 6
info.checked = (DBM.Options.InfoFrameCols == 6)
UIDropDownMenu_AddButton(info, 2)
end
end
end
end
------------------------
-- Create the frame --
------------------------
function createFrame()
frame = CreateFrame("Frame", "DBMInfoFrame", UIParent, "BackdropTemplate")
frame:Hide()
frame:SetFrameStrata("DIALOG")
frame.backdropInfo = {
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", -- 131071
tile = true,
tileSize = 16
}
frame:ApplyBackdrop()
frame:SetPoint(DBM.Options.InfoFramePoint, UIParent, DBM.Options.InfoFramePoint, DBM.Options.InfoFrameX, DBM.Options.InfoFrameY)
frame:SetSize(10, 10)
frame:SetClampedToScreen(true)
frame:EnableMouse(true)
frame:SetToplevel(true)
frame:SetMovable(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function(self)
if not DBM.Options.InfoFrameLocked then
self:StartMoving()
end
end)
frame:SetScript("OnDragStop", function(self)
self:StopMovingOrSizing()
local point, _, _, x, y = self:GetPoint(1)
DBM.Options.InfoFrameX = x
DBM.Options.InfoFrameY = y
DBM.Options.InfoFramePoint = point
end)
frame:SetScript("OnEvent", function(self, event, ...)
if infoFrame[event] then
infoFrame[event](self, ...)
end
end)
frame:SetScript("OnMouseDown", function(_, button)
if button == "RightButton" then
local dropdownFrame = DDM.Create_DropDownMenu("Frame", "DBMInfoFrameDropdown", frame)
UIDropDownMenu_Initialize(dropdownFrame, initializeDropdown)
ToggleDropDownMenu(1, nil, dropdownFrame, "cursor", 5, -10)
end
end)
local header = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
header:SetTextColor(1, 1, 1)
header:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 2, 2)
frame.header = header
infoFrame:SetHeader()
frame.lines = {}
end
------------------------
-- Update functions --
------------------------
local updateCallbacks = {}
local function sortFuncDesc(a, b)
return lines[a] > lines[b]
end
local function sortFuncAsc(a, b)
return lines[a] < lines[b]
end
local function sortGroupId(a, b)
return DBM.GetGroupId(DBM, a) < DBM.GetGroupId(DBM, b)
end
local function updateLines(preSorted)
twipe(sortedLines)
if preSorted then
-- Copy table as code from mod keeps around references this this and the "normal" table is wiped regularly
for i, v in ipairs(preSorted) do
sortedLines[i] = v
end
else
for i in pairs(lines) do
sortedLines[#sortedLines + 1] = i
end
if sortMethod == 3 then
tsort(sortedLines, sortGroupId)
elseif sortMethod == 2 then
tsort(sortedLines, sortFuncAsc)
else
tsort(sortedLines, sortFuncDesc)
end
end
for _, v in ipairs(updateCallbacks) do
v(sortedLines)
end
end
--[[
local function namesortFuncAsc(a, b)
return a < b
end
local function updateNamesortLines()
twipe(sortedLines)
for i in pairs(lines) do
sortedLines[#sortedLines + 1] = i
end
tsort(sortedLines, namesortFuncAsc)
for _, v in ipairs(updateCallbacks) do
v(sortedLines)
end
end
]]--
local function updateLinesCustomSort(sortFunc)
twipe(sortedLines)
for i in pairs(lines) do
sortedLines[#sortedLines + 1] = i
end
tsort(sortedLines, sortFunc)
for _, v in ipairs(updateCallbacks) do
v(sortedLines)
end
end
local function updateIcons()
twipe(icons)
for uId in DBM:GetGroupMembers() do
local icon = GetRaidTargetIndex(uId)
local icon2 = GetRaidTargetIndex(uId .. "target")
if icon and icon <= 8 then
icons[DBM:GetUnitFullName(uId)] = ("|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_%d:0|t"):format(icon)
end
if icon2 and icon2 <= 8 then
icons[DBM:GetUnitFullName(uId .. "target")] = ("|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_%d:0|t"):format(icon2)
end
end
for i = 1, 10 do
local icon = GetRaidTargetIndex("boss" .. i)
if icon then
icons[UnitName("boss" .. i)] = ("|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_%d:0|t"):format(icon)
end
end
end
local function updateHealth()
twipe(lines)
local threshold = value[1]
for uId in DBM:GetGroupMembers() do
if UnitHealth(uId) < threshold and not UnitIsDeadOrGhost(uId) then
lines[DBM:GetUnitFullName(uId)] = UnitHealth(uId) - threshold
end
end
updateLines()
updateIcons()
end
local function updatePlayerPower()
twipe(lines)
local threshold = value[1]
local powerType = value[2]
local spellFilter = value[3]
-- Value 4 is the noUpdate handler
-- Value 5 is sorting method, handled in show handler
for uId in DBM:GetGroupMembers() do
if not spellFilter or not DBM:UnitDebuff(uId, spellFilter) then
local maxPower = UnitPowerMax(uId, powerType)
if maxPower ~= 0 and not UnitIsDeadOrGhost(uId) and UnitPower(uId, powerType) / UnitPowerMax(uId, powerType) * 100 >= threshold then
lines[DBM:GetUnitFullName(uId)] = UnitPower(uId, powerType)
end
end
end
if DBM.Options.InfoFrameShowSelf and not lines[playerName] and UnitPower("player", powerType) > 0 then
lines[playerName] = UnitPower("player", powerType)
end
updateLines()
updateIcons()
end
local function updateEnemyPower()
twipe(lines)
local threshold = value[1]
local powerType = value[2]
local specificUnit = value[3]
if powerType then -- Only do power type defined
if specificUnit then
if not isRetail then
specificUnit = UnitExists(specificUnit) or DBM:GetUnitIdFromGUID(specificUnit)--unitID already passed or GUID we convert into unitID
end
if UnitExists(specificUnit) then
local currentPower, maxPower = UnitPower(specificUnit, powerType), UnitPowerMax(specificUnit, powerType)
if maxPower and maxPower > 0 then
local percent = currentPower / maxPower * 100
if percent >= threshold then
lines[UnitName(specificUnit)] = mfloor(percent) .. "%"
end
end
end
else
for i = 1, 10 do
local uId = "boss" .. i
local currentPower, maxPower = UnitPower(uId), UnitPowerMax(uId)
if maxPower and maxPower > 0 then
local percent = currentPower / maxPower * 100
if percent >= threshold then
lines[UnitName(uId)] = mfloor(percent) .. "%"
end
end
end
end
else -- Check primary power type and alternate power types together. This should only be used if BOTH power types exist on same boss
if specificUnit then
if not isRetail then
specificUnit = UnitExists(specificUnit) or DBM:GetUnitIdFromGUID(specificUnit)--unitID already passed or GUID we convert into unitID
end
if UnitExists(specificUnit) then
-- Primary Power
local currentPower, maxPower = UnitPower(specificUnit), UnitPowerMax(specificUnit)
if maxPower and maxPower > 0 then
local percent = currentPower / maxPower * 100
if percent >= threshold then
lines[UnitName(specificUnit)] = mfloor(percent) .. "%"
end
end
-- Alternate Power
local currentAltPower, maxAltPower = UnitPower(specificUnit, 10), UnitPowerMax(specificUnit, 10)
if maxAltPower and maxAltPower > 0 then
if currentAltPower / maxAltPower * 100 >= threshold then
lines[UnitName(specificUnit)] = L.INFOFRAME_ALT .. currentAltPower
end
end
end
else
for i = 1, 10 do
local uId = "boss" .. i
-- Primary Power
local currentPower, maxPower = UnitPower(uId), UnitPowerMax(uId)
if maxPower and maxPower > 0 then
if currentPower / maxPower * 100 >= threshold then
lines[UnitName(uId)] = currentPower
end
end
-- Alternate Power
local currentAltPower, maxAltPower = UnitPower(uId, 10), UnitPowerMax(uId, 10)
if maxAltPower and maxAltPower > 0 then
if currentAltPower / maxAltPower * 100 >= threshold then
lines[UnitName(uId)] = L.INFOFRAME_ALT .. currentAltPower
end
end
end
end
end
updateLines()
updateIcons()
end
local function updateEnemyAbsorb()
twipe(lines)
local spellInput = value[1]
local totalAbsorb = value[2]
local specificUnit = value[3]
if specificUnit then
specificUnit = UnitExists(specificUnit) or DBM:GetUnitIdFromGUID(specificUnit)--unitID already passed or GUID we convert into unitID
if UnitExists(specificUnit) then
local absorbAmount
if spellInput then -- Get specific spell absorb
absorbAmount = select(16, DBM:UnitBuff(specificUnit, spellInput)) or select(16, DBM:UnitDebuff(specificUnit, spellInput))
else -- Get all of them
absorbAmount = UnitGetTotalAbsorbs(specificUnit)
end
if absorbAmount and absorbAmount > 0 then
if totalAbsorb then
lines[UnitName(specificUnit)] = mfloor(absorbAmount / totalAbsorb * 100) .. "%"
else
lines[UnitName(specificUnit)] = mfloor(absorbAmount)
end
end
end
else--Generic absorbs for bosses. Not to be mistaken for updateMultiEnemyAbsorb, which supports checking multiple units that might or might not be bosses
for i = 1, 10 do
local uId = "boss" .. i
if UnitExists(uId) then
local absorbAmount
if spellInput then -- Get specific spell absorb
absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
else -- Get all of them
absorbAmount = UnitGetTotalAbsorbs(uId)
end
if absorbAmount and absorbAmount > 0 then
if totalAbsorb then
lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
else
lines[UnitName(uId)] = mfloor(absorbAmount)
end
end
end
end
end
updateLines()
updateIcons()
end
--Really hate splitting this off from updateEnemyAbsorb but having them merged was even uglier
--Note, this method also less efficient than boss only or single unit absorb tracking.
--Don't use this function to be lazy (ie just passing 1 guid instead of finding valid unit at mod level)
local function updateMultiEnemyAbsorb()
twipe(lines)
local spellInput = value[1]
local totalAbsorb = value[2]
local guidTable = value[3]--Multi target by table
local guidTracked = {}
for i = 1, 10 do
if #guidTable == #guidTracked then--Stop searching, found everything we're looking for.
break
end
local uId = "boss" .. i
if UnitExists(uId) then
local targetGUID = UnitGUID(uId)
if guidTable[targetGUID] and not guidTracked[targetGUID] then
guidTracked[targetGUID] = true
local absorbAmount
if spellInput then -- Get specific spell absorb
absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
else -- Get all of them
absorbAmount = UnitGetTotalAbsorbs(uId)
end
if absorbAmount and absorbAmount > 0 then
if totalAbsorb then
lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
else
lines[UnitName(uId)] = mfloor(absorbAmount)
end
end
end
end
end
for unitId in DBM:GetGroupMembers() do--Do not use self on this function, because self might be bossModPrototype
if #guidTable == #guidTracked then--Stop searching, found everything we're looking for.
break
end
local uId = unitId .. "target"
local targetGUID = UnitGUID(unitId)
if guidTable[targetGUID] and not guidTracked[targetGUID] then
guidTracked[targetGUID] = true
local absorbAmount
if spellInput then -- Get specific spell absorb
absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
else -- Get all of them
absorbAmount = UnitGetTotalAbsorbs(uId)
end
if absorbAmount and absorbAmount > 0 then
if totalAbsorb then
lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
else
lines[UnitName(uId)] = mfloor(absorbAmount)
end
end
end
end
updateLines()
updateIcons()
end
local function updateAllAbsorb()
twipe(lines)
local spellInput = value[1]
local totalAbsorb = value[2]
for i = 1, 10 do
local uId = "boss" .. i
if UnitExists(uId) then
local absorbAmount
if spellInput then -- Get specific spell absorb
absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
else -- Get all of them
absorbAmount = UnitGetTotalAbsorbs(uId)
end
if absorbAmount and absorbAmount > 0 then
if totalAbsorb then
lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
else
lines[UnitName(uId)] = mfloor(absorbAmount)
end
end
end
end
if spellInput then
for uId in DBM:GetGroupMembers() do
local absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
if absorbAmount and absorbAmount > 0 then
if totalAbsorb then
lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
else
lines[UnitName(uId)] = mfloor(absorbAmount)
end
end
end
end
updateLines()
updateIcons()
end
local function updatePlayerAbsorb()
twipe(lines)
local spellInput = value[1]
local totalAbsorb = value[2]
for uId in DBM:GetGroupMembers() do
local absorbAmount
if spellInput then -- Get specific spell absorb
absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
else--Not even spell input given, this is a very generic infoframe
absorbAmount = UnitGetTotalAbsorbs(uId)
end
if absorbAmount and absorbAmount > 0 then
if totalAbsorb then
lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
else
lines[UnitName(uId)] = mfloor(absorbAmount)
end
end
end
updateLines()
updateIcons()
end
-- Buffs that are good to have, therefor bad not to have them.
local function updatePlayerBuffs()
twipe(lines)
local spellName = value[1]
local tankIgnored = value[2]
for uId in DBM:GetGroupMembers() do
if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, 1)) then
else
if not DBM:UnitBuff(uId, spellName) and not UnitIsDeadOrGhost(uId) then
lines[DBM:GetUnitFullName(uId)] = ""
end
end
end
updateLines()
updateIcons()
end
-- Debuffs that are good to have, therefor it's bad NOT to have them.
local function updateGoodPlayerDebuffs()
twipe(lines)
local spellInput = value[1]
local tankIgnored = value[2]
for uId in DBM:GetGroupMembers() do
if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, 1)) then
else
if not DBM:UnitDebuff(uId, spellInput) and not UnitIsDeadOrGhost(uId) then
lines[DBM:GetUnitFullName(uId)] = ""
end
end
end
updateLines()
updateIcons()
end
-- Debuffs that are bad to have, therefor it is bad to have them.
-- Function will auto handle spellName or spellId via DBMs unit debuff handler and spellInput type
local function updateBadPlayerDebuffs()
twipe(lines)
local spellInput = value[1]
local tankIgnored = value[2]
for uId in DBM:GetGroupMembers() do
if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, 1)) then
else
if DBM:UnitDebuff(uId, spellInput) and not UnitIsDeadOrGhost(uId) then
lines[DBM:GetUnitFullName(uId)] = ""
end
end
end
updateLines()
updateIcons()
end
--Debuffs with important durations that we track
local function updatePlayerDebuffRemaining()
twipe(lines)
local spellInput = value[1]
for uId in DBM:GetGroupMembers() do
local expires = select(6, DBM:UnitDebuff(uId, spellInput))
if expires then
if expires == 0 then
lines[DBM:GetUnitFullName(uId)] = 9000--Force sorting the unknowns under the ones we do know.
else
local debuffTime = expires - GetTime()
lines[DBM:GetUnitFullName(uId)] = mfloor(debuffTime)
end
end
end
updateLines()
updateIcons()
end
-- Buffs with important durations that we track
local function updatePlayerBuffRemaining()
twipe(lines)
local spellInput = value[1]
for uId in DBM:GetGroupMembers() do
local expires = select(6, DBM:UnitBuff(uId, spellInput))
if expires then
if expires == 0 then
lines[DBM:GetUnitFullName(uId)] = 9000--Force sorting the unknowns under the ones we do know.
else
local debuffTime = expires - GetTime()
lines[DBM:GetUnitFullName(uId)] = mfloor(debuffTime)
end
end
end
updateLines()
updateIcons()
end
-- Debuffs that are bad to have, but we want to show players who do NOT have them
-- Function will auto handle spellName or spellId via DBMs unit debuff handler and spellInput type
local function updateReverseBadPlayerDebuffs()
twipe(lines)
local spellInput = value[1]
local tankIgnored = value[2]
for uId in DBM:GetGroupMembers() do
if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, 1)) then
else
if not DBM:UnitDebuff(uId, spellInput) and not UnitIsDeadOrGhost(uId) and not DBM:UnitBuff(uId, 27827) then--27827 Spirit of Redemption. This particular info frame wants to ignore this
lines[DBM:GetUnitFullName(uId)] = ""
end
end
end
updateLines()
updateIcons()
end
local function updatePlayerBuffStacks()
twipe(lines)
local spellInput = value[1]
for uId in DBM:GetGroupMembers() do
local spellName, _, count = DBM:UnitBuff(uId, spellInput)
if spellName and count then
lines[DBM:GetUnitFullName(uId)] = count
end
end
updateIcons()
updateLines()
end
local function updatePlayerDebuffStacks()
twipe(lines)
local spellInput = value[1]
for uId in DBM:GetGroupMembers() do
local spellName, _, count, _, _, _, _, _, _, _, _, _, _, _, _, count2 = DBM:UnitDebuff(uId, spellInput)
if spellName and (count or count2) then
lines[DBM:GetUnitFullName(uId)] = count2 or count
end
end
updateIcons()
updateLines()
end
local function updatePlayerAggro()
twipe(lines)
local aggroType = value[1]
local tankIgnored = value[2]
for uId in DBM:GetGroupMembers() do
if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, 1)) then
else
local currentThreat = UnitThreatSituation(uId) or 0
if currentThreat >= aggroType then
lines[DBM:GetUnitFullName(uId)] = ""
end
end
end
updateLines()
updateIcons()
end
local function updatePlayerTargets()
twipe(lines)
local cId = value[1]
for uId, _ in DBM:GetGroupMembers() do
if DBM:GetUnitCreatureId(uId .. "target") ~= cId and (UnitGroupRolesAssigned(uId) == "DAMAGER" or UnitGroupRolesAssigned(uId) == "NONE") then
lines[DBM:GetUnitFullName(uId)] = ""
end
end
updateLines()
updateIcons()
end
local function updateByFunction()
local func = value[1]
local sortFunc = value[2]
local useIcon = value[3]
local presortedLines
lines, presortedLines = func()
if sortFunc then
if type(sortFunc) == "function" then
updateLinesCustomSort(sortFunc)
else
updateLines()
end
else
updateLines(presortedLines)
end
if useIcon then
updateIcons()
end
end
-- Unsorted table maintained by mod and just sent here.
-- Never updated by onupdate method, requires manual updates when mod updates table
local function updateByTable(table)
twipe(lines)
if table then
for i, v in pairs(table) do
lines[i] = v
end
end
updateLines()
updateIcons()
end
local function updateTest()
twipe(lines)
lines["Alpha"] = 1
lines["Beta"] = 10
lines["Gamma"] = 25
lines["Delta"] = 50
lines["Epsilon"] = 100
updateLines()
end
local test2Table
local function updateTest2()
twipe(lines)
if not test2Table then
test2Table = {}
for _ = 1, 40 do
local ident = ""
for _ = 1, mrandom(5, 12) do
ident = ident .. schar(mrandom(65, 90))
end
test2Table[ident] = mrandom(1, 10) * 10
end
end
for k, v in pairs(test2Table) do
lines[k] = v
end
updateLines()
end
local test3Counter = 0
local function updateTest3()
twipe(lines)
if not test2Table then
test2Table = {}
for _ = 1, 40 do
local ident = ""
for _ = 1, mrandom(5, 12) do
ident = ident .. schar(mrandom(65, 90))
end
test2Table[ident] = mrandom(1, 10) * 10
end
end
if test3Counter < 40 then
local count = 0
for k, _ in pairs(test2Table) do
if count > 40 - test3Counter then
break
end
count = count + 1
lines[k] = mrandom(1, 10) * 10
end
test3Counter = test3Counter + 1
end
updateLines()
end
local events = {
["health"] = updateHealth,
["playerpower"] = updatePlayerPower,
["enemypower"] = updateEnemyPower,
["enemyabsorb"] = updateEnemyAbsorb,
["multienemyabsorb"] = updateMultiEnemyAbsorb,
["allabsorb"] = updateAllAbsorb,
["playerabsorb"] = updatePlayerAbsorb,
["playerbuff"] = updatePlayerBuffs,
["playergooddebuff"] = updateGoodPlayerDebuffs,
["playerbaddebuff"] = updateBadPlayerDebuffs,
["playerdebuffremaining"] = updatePlayerDebuffRemaining,
["playerbuffremaining"] = updatePlayerBuffRemaining,
["reverseplayerbaddebuff"] = updateReverseBadPlayerDebuffs,
["playeraggro"] = updatePlayerAggro,
["playerbuffstacks"] = updatePlayerBuffStacks,
["playerdebuffstacks"] = updatePlayerDebuffStacks,
["playertargets"] = updatePlayerTargets,
["function"] = updateByFunction,
["table"] = updateByTable,
["test"] = updateTest,
["test2"] = updateTest2,
["test3"] = updateTest3
}
----------------
-- OnUpdate --
----------------
local friendlyEvents = {
["health"] = true,
["playerpower"] = true,
["playerabsorb"] = true,
["playerbuff"] = true,
["playergooddebuff"] = true,
["playerbaddebuff"] = true,
["playerdebuffremaining"] = true,
["playerbuffremaining"] = true,
["reverseplayerbaddebuff"] = true,
["playeraggro"] = true,
["playerbuffstacks"] = true,
["playerdebuffstacks"] = true,
["playertargets"] = true
}
local function onUpdate(frame, table)
if events[currentEvent] then
events[currentEvent](table)
else
if frame then
frame:Hide()
end
end
local color = NORMAL_FONT_COLOR
infoFrame:ClearLines()
local linesShown = 0
for i = 1, #sortedLines do
if linesShown >= maxLines * maxCols then
break
end
local leftText = sortedLines[i]
if not leftText then
error("DBM InfoFrame: leftText cannot be nil, Notify DBM author. Infoframe force shutting down ", 2)
frame:Hide()
return
elseif leftText and type(leftText) ~= "string" then
leftText = tostring(leftText)
end
local rightText = lines[leftText]
local extra, extraName = ssplit("*", leftText) -- Find just unit name, if extra info had to be added to make unique
local icon = icons[extraName or leftText] and icons[extraName or leftText] .. leftText
if friendlyEvents[currentEvent] then
local unitId = DBM:GetRaidUnitId(DBM:GetUnitFullName(extraName or leftText)) or "player"--Prevent nil logical error
if unitId then
local _, _, _, mapId = UnitPosition(unitId)
if mapId == currentMapId then
local _, class = UnitClass(unitId)
if class then
color = RAID_CLASS_COLORS[class]
if DBM.Options.StripServerName then -- StripServerName option is checked here, even though it's checked in GetShortServerName function, because we have to apply custom 3rd column hack reconstruction
local shortName = DBM:GetShortServerName(extraName or leftText)
if extraName then -- 3 column hack is present, we need to reconstruct leftText with shortened name
leftText = extra.."*"..shortName
else -- LeftText is name, just replace it with shortname
leftText = shortName
end
end
end
linesShown = linesShown + 1
if unitId and UnitIsUnit(unitId, "player") then -- It's player.
if currentEvent == "health" or currentEvent == "playerpower" or currentEvent == "playerabsorb" or currentEvent == "playerbuff" or currentEvent == "playergooddebuff" or currentEvent == "playerbaddebuff" or currentEvent == "playerdebuffremaining" or currentEvent == "playerdebuffstacks" or currentEvent == "playerbuffremaining" or currentEvent == "playertargets" or currentEvent == "playeraggro" then--Red
infoFrame:SetLine(linesShown, icon or leftText, rightText, 255, 0, 0, 255, 255, 255)
else -- Green
infoFrame:SetLine(linesShown, icon or leftText, rightText, 0, 255, 0, 255, 255, 255)
end
else -- It's not player, do nothing special with it. Ordinary class colored text.
if currentEvent == "playerdebuffremaining" or currentEvent == "playerbuffremaining" then
local numberValue = tonumber(rightText)
if numberValue < 6 then
infoFrame:SetLine(linesShown, icon or leftText, rightText, color.r, color.g, color.b, 255, 0, 0)--Red
elseif numberValue < 11 then
infoFrame:SetLine(linesShown, icon or leftText, rightText, color.r, color.g, color.b, 255, 127.5, 0)--Orange
else
if numberValue == 9000 then -- Out of range players
infoFrame:SetLine(linesShown, icon or leftText, SPELL_FAILED_OUT_OF_RANGE, color.r, color.g, color.b, 255, 0, 0)--Red
else
infoFrame:SetLine(linesShown, icon or leftText, rightText, color.r, color.g, color.b, 255, 255, 255)--White
end
end
else
infoFrame:SetLine(linesShown, icon or leftText, rightText, color.r, color.g, color.b, 255, 255, 255)
end
end
end
end
else
local color2 = NORMAL_FONT_COLOR -- Only custom into frames will have chance of putting player names on right side
local unitId = DBM:GetRaidUnitId(DBM:GetUnitFullName(extraName or leftText))
local unitId2 = DBM:GetRaidUnitId(DBM:GetUnitFullName(rightText))
-- Class color names in custom functions too, IF unitID exists
if unitId then -- Check left text
local _, class = UnitClass(unitId)
if class then
color = RAID_CLASS_COLORS[class]
if DBM.Options.StripServerName then--StripServerName option is checked here, even though it's checked in GetShortServerName function, because we have to apply custom 3rd column hack reconstruction
local shortName = DBM:GetShortServerName(extraName or leftText)
if extraName then -- 3 column hack is present, we need to reconstruct leftText with shortened name
leftText = extra.."*"..shortName
else -- LeftText is name, just replace it with shortname
leftText = shortName
end
end
end
else
color = NORMAL_FONT_COLOR
leftText = extraName or leftText
end
if unitId2 then -- Check right text
local _, class = UnitClass(unitId2)
if class then
color2 = RAID_CLASS_COLORS[class]
rightText = DBM:GetShortServerName(rightText)
end
end
linesShown = linesShown + 1
infoFrame:SetLine(linesShown, icon or leftText, rightText, color.r, color.g, color.b, color2.r, color2.g, color2.b)
end
end
local maxWidth1, maxWidth2, linesPerRow = {}, {}, 5
if linesShown > 5 then
linesPerRow = mmin(maxLines, mfloor(linesShown / maxCols + 0.99))
end
local shouldUpdate = prevLines ~= linesShown
for i = 1, linesShown do
if shouldUpdate then
infoFrame:AlignLine(i * 2 - 1, linesPerRow * 2)
infoFrame:AlignLine(i * 2, linesPerRow * 2)
end
local m = mfloor(i / linesPerRow + 0.99)
frame.lines[i * 2 - 1]:SetWidth(0) -- Hack here, because the string width doesn't calculate properly.
frame.lines[i * 2]:SetWidth(0)
maxWidth1[m] = mmax(maxWidth1[m] or 0, frame.lines[i * 2 - 1]:GetStringWidth())
maxWidth2[m] = mmax(maxWidth2[m] or 0, frame.lines[i * 2]:GetStringWidth())
end
local size = DBM.Options.InfoFrameFontSize
local width = 0
for i, _ in pairs(maxWidth1) do
local maxWid, maxWid2 = maxWidth1[i], maxWidth2[i]
width = width + maxWid + maxWid2 + size + (size / 2)
for ii = 1, linesPerRow do
local m = ((i - 1) * linesPerRow * 2) + (ii * 2)
if not frame.lines[m] then
break
end
frame.lines[m - 1]:SetSize(maxWid, size)
frame.lines[m]:SetSize(maxWid2, size)
end
end
if width == 0 then
width = 105
end
local height = size
if linesShown > linesPerRow then
height = height + (linesPerRow * size)
else
height = height + (mmin(linesShown, maxLines) * size)
end
frame:SetSize(width, height)
frame:Show()
prevLines = linesShown
end
---------------
-- Methods --
---------------
--Arg 1: spellName, health/powervalue, customfunction, table type. Arg 2: TankIgnore, Powertype, SortFunction, totalAbsorb, sortmethod (table/stacks). Arg 3: SpellFilter, UseIcon. Arg 4: disable onUpdate. Arg 5: sortmethod (playerpower)
function infoFrame:Show(modMaxLines, event, ...)
if DBM.Options.DontShowInfoFrame and not (event or ""):find("test") then
return
end
prevLines = 0
currentMapId = select(4, UnitPosition("player"))
modLines = modMaxLines
if DBM.Options.InfoFrameLines and DBM.Options.InfoFrameLines ~= 0 then
maxLines = DBM.Options.InfoFrameLines
else
maxLines = modMaxLines or 5
end
if DBM.Options.InfoFrameCols and DBM.Options.InfoFrameCols ~= 0 then
maxCols = DBM.Options.InfoFrameCols
else
-- TODO, still needs more finite control via gui later on
-- I think dynamic options modMaxLines/10 modMaxLines/5 are both valid options, as well as just capping at 2 >= 10
maxCols = modMaxLines and modMaxLines >= 10 and 2 or 1
end
twipe(value)
for i = 1, select("#", ...) do
value[i] = select(i, ...)
end
if not frame then
createFrame()
end
-- Orders event to use spellID no matter what and not spell name
if event:find("byspellid") then
event = event:gsub("byspellid", "") -- Strip off the byspellid, it served it's purpose, it simply told infoframe to not convert to spellName
if type(value[1]) ~= "number" then
error("DBM-InfoFrame: byspellid method must use spellId", 2)
return
end
-- If spellId is given as value one and it's not a byspellid event, convert to spellname
-- This also allows spell name to be given by mod, since value 1 verifies it's a number
elseif type(value[1]) == "number" and event ~= "health" and event ~= "function" and event ~= "table" and event ~= "playertargets" and event ~= "playeraggro" and event ~= "playerpower" and event ~= "enemypower" and event ~= "test" and event ~= "test2" then
-- Outside of "byspellid" functions, typical frames will still use spell NAME matching not spellID.
-- This just determines if we convert the spell input to a spell Name, if a spellId was provided for a non byspellid infoframe
value[1] = DBM:GetSpellInfo(value[1])
end
currentEvent = event
if event == "playerbuff" or event == "playerbaddebuff" or event == "playergooddebuff" then
sortMethod = 3 -- Sort by group ID
elseif event == "health" or event == "playerdebuffremaining" then
sortMethod = 2 -- Sort lowest first
elseif (event == "playerdebuffstacks" or event == "table") and value[2] and type(value[2]) == "number" then
sortMethod = value[2]
elseif event == "playerpower" and value[5] and type(value[5]) == "number" then
sortMethod = value[5]
else
sortMethod = 1 -- Sort highest first
end
if events[currentEvent] then
events[currentEvent](value[1])
else
error("DBM-InfoFrame: Unsupported event", 2)
return
end
if not friendlyEvents[currentEvent] then
twipe(icons)
end
frame:Show()
onUpdate(frame, value[1])
if not frame.ticker and not value[4] and event ~= "table" then
frame.ticker = C_Timer.NewTicker(0.5, function()
onUpdate(frame)
end)
elseif frame.ticker and value[4] then -- Redundancy, in event calling a non onupdate infoframe show without a hide event to unschedule ticker based infoframe
frame.ticker:Cancel()
frame.ticker = nil
end
end
function infoFrame:RegisterCallback(cb)
updateCallbacks[#updateCallbacks + 1] = cb
end
function infoFrame:Update(time)
if not frame then
createFrame()
end
if frame:IsShown() then
if time then
DBM:Unschedule(onUpdate)
DBM:Schedule(time, onUpdate, frame)
else
onUpdate(frame)
end
end
end
function infoFrame:UpdateTable(table, time)
if not frame then
createFrame()
end
if frame:IsShown() and table then
if time then
DBM:Unschedule(onUpdate)
DBM:Schedule(time, onUpdate, frame, table)
else
onUpdate(frame, table)
end
end
end
function infoFrame:SetHeader(text)
if not frame then
createFrame()
end
frame.header:SetText(text or "DBM Info Frame")
end
function infoFrame:ClearLines()
if not frame then
createFrame()
end
for i = 1, #frame.lines do
frame.lines[i]:SetText("")
frame.lines[i]:Hide()
end
end
function infoFrame:AlignLine(lineNum, linesPerRow)
local size = DBM.Options.InfoFrameFontSize
local line = frame.lines[lineNum]
line:SetJustifyH(lineNum % 2 == 0 and "RIGHT" or "LEFT")
if lineNum == 1 then -- 1st entry left
line:SetPoint("TOPLEFT", frame, "TOPLEFT", size / 2, -(size / 2))
elseif lineNum == 2 then -- 1st entry right
line:SetPoint("TOPLEFT", frame.lines[1], "TOPRIGHT", size / 2, 0)
else
if lineNum % linesPerRow == 1 then -- Column 2-x, 1st entry left
line:SetPoint("TOPLEFT", frame.lines[lineNum - linesPerRow + 1], "TOPRIGHT", size, 0)
elseif lineNum % 2 == 0 then -- Column 2-x, Right entry
line:SetPoint("TOPLEFT", frame.lines[lineNum - 1], "TOPRIGHT", size / 2, 0)
else -- Column 2-x, Left entry
line:SetPoint("TOPLEFT", frame.lines[lineNum - 2], "LEFT", 0, -(size / 2))
end
end
end
function infoFrame:UpdateStyle()
if not frame then
createFrame()
end
prevLines = 0
local font = DBM.Options.InfoFrameFont == "standardFont" and standardFont or DBM.Options.InfoFrameFont
local size = DBM.Options.InfoFrameFontSize
local style = DBM.Options.InfoFrameFontStyle == "none" and nil or DBM.Options.InfoFrameFontStyle
for i = 1, #frame.lines do
frame.lines[i]:SetFont(font, size, style)
end
end
function infoFrame:SetLine(lineNum, leftText, rightText, colorR, colorG, colorB, color2R, color2G, color2B)
if not frame then
createFrame()
end
lineNum = lineNum * 2 - 1
if not frame.lines[lineNum] then
local font = DBM.Options.InfoFrameFont == "standardFont" and standardFont or DBM.Options.InfoFrameFont
local size = DBM.Options.InfoFrameFontSize
local style = DBM.Options.InfoFrameFontStyle == "none" and nil or DBM.Options.InfoFrameFontStyle
frame.lines[lineNum] = frame:CreateFontString("Line" .. lineNum, "OVERLAY", "GameFontNormal")
frame.lines[lineNum]:SetFont(font, size, style)
frame.lines[lineNum + 1] = frame:CreateFontString("Line" .. lineNum + 1, "OVERLAY", "GameFontNormal")
frame.lines[lineNum + 1]:SetFont(font, size, style)
frame.lines[lineNum + 1]:SetJustifyH("RIGHT")
end
frame.lines[lineNum]:SetText(leftText)
frame.lines[lineNum]:SetTextColor(colorR or 255, colorG or 255, colorB or 255)
frame.lines[lineNum]:Show()
frame.lines[lineNum + 1]:SetText(rightText)
frame.lines[lineNum + 1]:SetTextColor(color2R or 255, color2G or 255, color2B or 255)
frame.lines[lineNum + 1]:Show()
end
function infoFrame:Hide()
twipe(lines)
twipe(icons)
twipe(sortedLines)
twipe(updateCallbacks)
infoFrame:SetHeader()
twipe(value)
if frame then
if frame.ticker then
frame.ticker:Cancel()
frame.ticker = nil
end
frame:Hide()
end
currentEvent = nil
sortMethod = 1
end
function infoFrame:SetLines(lines)
modLines = lines
if DBM.Options.InfoFrameLines == 0 then
maxLines = lines
end
end
function infoFrame:SetColumns(columns)
modCols = columns
if DBM.Options.InfoFrameCols == 0 then
maxCols = columns
end
end
function infoFrame:IsShown()
return frame and frame:IsShown()
end
function infoFrame:SetSortingAsc()
sortMethod = 2
end
function infoFrame:SetSortingGroupId()
sortMethod = 3
end