---@class DBMCoreNamespace local private = select(2, ...) local L = DBM_CORE_L local CL = DBM_COMMON_L local DBMScheduler = private:GetModule("DBMScheduler") local stringUtils = private:GetPrototype("StringUtils") local checkEntry = private:GetPrototype("TableUtils").checkEntry ---@class DBM local DBM = private:GetPrototype("DBM") ---@class Announce local announcePrototype = private:GetPrototype("Announce") ---@class DBMMod local bossModPrototype = private:GetPrototype("DBMMod") local test = private:GetPrototype("DBMTest") local mt = {__index = announcePrototype} ---@diagnostic disable: inject-field local frame = CreateFrame("Frame", "DBMWarning", UIParent) local font1u = CreateFrame("Frame", "DBMWarning1Updater", UIParent) local font2u = CreateFrame("Frame", "DBMWarning2Updater", UIParent) local font3u = CreateFrame("Frame", "DBMWarning3Updater", UIParent) local font1 = frame:CreateFontString("DBMWarning1", "OVERLAY", "GameFontNormal") font1:SetWidth(1024) font1:SetHeight(0) font1:SetPoint("TOP", 0, 0) local font2 = frame:CreateFontString("DBMWarning2", "OVERLAY", "GameFontNormal") font2:SetWidth(1024) font2:SetHeight(0) font2:SetPoint("TOP", font1, "BOTTOM", 0, 0) local font3 = frame:CreateFontString("DBMWarning3", "OVERLAY", "GameFontNormal") font3:SetWidth(1024) font3:SetHeight(0) font3:SetPoint("TOP", font2, "BOTTOM", 0, 0) frame:SetMovable(true) frame:SetWidth(1) frame:SetHeight(1) frame:SetFrameStrata("HIGH") frame:SetClampedToScreen(true) frame:SetPoint("CENTER", UIParent, "CENTER", 0, 300) font1u:Hide() font2u:Hide() font3u:Hide() local font1elapsed, font2elapsed, font3elapsed local function fontHide1() local duration = DBM.Options.WarningDuration2 if font1elapsed > duration * 1.3 then font1u:Hide() font1:Hide() if frame.font1ticker then frame.font1ticker:Cancel() frame.font1ticker = nil end elseif font1elapsed > duration then font1elapsed = font1elapsed + 0.05 local alpha = 1 - (font1elapsed - duration) / (duration * 0.3) font1:SetAlpha(alpha > 0 and alpha or 0) else font1elapsed = font1elapsed + 0.05 font1:SetAlpha(1) end end local function fontHide2() local duration = DBM.Options.WarningDuration2 if font2elapsed > duration * 1.3 then font2u:Hide() font2:Hide() if frame.font2ticker then frame.font2ticker:Cancel() frame.font2ticker = nil end elseif font2elapsed > duration then font2elapsed = font2elapsed + 0.05 local alpha = 1 - (font2elapsed - duration) / (duration * 0.3) font2:SetAlpha(alpha > 0 and alpha or 0) else font2elapsed = font2elapsed + 0.05 font2:SetAlpha(1) end end local function fontHide3() local duration = DBM.Options.WarningDuration2 if font3elapsed > duration * 1.3 then font3u:Hide() font3:Hide() if frame.font3ticker then frame.font3ticker:Cancel() frame.font3ticker = nil end elseif font3elapsed > duration then font3elapsed = font3elapsed + 0.05 local alpha = 1 - (font3elapsed - duration) / (duration * 0.3) font3:SetAlpha(alpha > 0 and alpha or 0) else font3elapsed = font3elapsed + 0.05 font3:SetAlpha(1) end end font1u:SetScript("OnUpdate", function(self) local diff = GetTime() - font1.lastUpdate local origSize = DBM.Options.WarningFontSize if diff > 0.4 then font1:SetTextHeight(origSize) self:Hide() elseif diff > 0.2 then font1:SetTextHeight(origSize * (1.5 - (diff - 0.2) * 2.5)) else font1:SetTextHeight(origSize * (1 + diff * 2.5)) end end) font2u:SetScript("OnUpdate", function(self) local diff = GetTime() - font2.lastUpdate local origSize = DBM.Options.WarningFontSize if diff > 0.4 then font2:SetTextHeight(origSize) self:Hide() elseif diff > 0.2 then font2:SetTextHeight(origSize * (1.5 - (diff - 0.2) * 2.5)) else font2:SetTextHeight(origSize * (1 + diff * 2.5)) end end) font3u:SetScript("OnUpdate", function(self) local diff = GetTime() - font3.lastUpdate local origSize = DBM.Options.WarningFontSize if diff > 0.4 then font3:SetTextHeight(origSize) self:Hide() elseif diff > 0.2 then font3:SetTextHeight(origSize * (1.5 - (diff - 0.2) * 2.5)) else font3:SetTextHeight(origSize * (1 + diff * 2.5)) end end) function DBM:UpdateWarningOptions() frame:ClearAllPoints() frame:SetPoint(self.Options.WarningPoint, UIParent, self.Options.WarningPoint, self.Options.WarningX, self.Options.WarningY) local font = self.Options.WarningFont == "standardFont" and private.standardFont or self.Options.WarningFont font1:SetFont(font, self.Options.WarningFontSize, self.Options.WarningFontStyle == "None" and nil or self.Options.WarningFontStyle) font2:SetFont(font, self.Options.WarningFontSize, self.Options.WarningFontStyle == "None" and nil or self.Options.WarningFontStyle) font3:SetFont(font, self.Options.WarningFontSize, self.Options.WarningFontStyle == "None" and nil or self.Options.WarningFontStyle) if self.Options.WarningFontShadow then font1:SetShadowOffset(1, -1) font2:SetShadowOffset(1, -1) font3:SetShadowOffset(1, -1) else font1:SetShadowOffset(0, 0) font2:SetShadowOffset(0, 0) font3:SetShadowOffset(0, 0) end end function DBM:AddWarning(text, force, announceObject) local added = false if not frame.font1ticker then font1elapsed = 0 font1.lastUpdate = GetTime() font1:SetText(text) font1:Show() font1u:Show() added = true frame.font1ticker = frame.font1ticker or C_Timer.NewTicker(0.05, fontHide1) elseif not frame.font2ticker then font2elapsed = 0 font2.lastUpdate = GetTime() font2:SetText(text) font2:Show() font2u:Show() added = true frame.font2ticker = frame.font2ticker or C_Timer.NewTicker(0.05, fontHide2) elseif not frame.font3ticker or force then font3elapsed = 0 font3.lastUpdate = GetTime() font3:SetText(text) font3:Show() font3u:Show() fontHide3() added = true frame.font3ticker = frame.font3ticker or C_Timer.NewTicker(0.05, fontHide3) end if not added then local prevText1 = font2:GetText() local prevText2 = font3:GetText() font1:SetText(prevText1) font1elapsed = font2elapsed font2:SetText(prevText2) font2elapsed = font3elapsed self:AddWarning(text, true, announceObject) else test:Trace(announceObject and announceObject.mod or self, "ShowAnnounce", announceObject, text) end end do local anchorFrame local function moveEnd(self) anchorFrame:Hide() if anchorFrame.ticker then anchorFrame.ticker:Cancel() anchorFrame.ticker = nil end font1elapsed = self.Options.WarningDuration2 font2elapsed = self.Options.WarningDuration2 font3elapsed = self.Options.WarningDuration2 frame:SetFrameStrata("HIGH") self:Unschedule(moveEnd) DBT:CancelBar(L.MOVE_WARNING_BAR) end function DBM:MoveWarning() if not anchorFrame then anchorFrame = CreateFrame("Frame", nil, frame) anchorFrame:SetWidth(32) anchorFrame:SetHeight(32) anchorFrame:EnableMouse(true) anchorFrame:SetPoint("TOP", frame, "TOP", 0, 32) anchorFrame:RegisterForDrag("LeftButton") anchorFrame:SetClampedToScreen(true) anchorFrame:Hide() local texture = anchorFrame:CreateTexture() texture:SetTexture("Interface\\Addons\\DBM-Core\\textures\\dot.blp") texture:SetPoint("CENTER", anchorFrame, "CENTER", 0, 0) texture:SetWidth(32) texture:SetHeight(32) anchorFrame:SetScript("OnDragStart", function() frame:StartMoving() self:Unschedule(moveEnd) DBT:CancelBar(L.MOVE_WARNING_BAR) end) anchorFrame:SetScript("OnDragStop", function() frame:StopMovingOrSizing() local point, _, _, xOfs, yOfs = frame:GetPoint(1) self.Options.WarningPoint = point self.Options.WarningX = xOfs self.Options.WarningY = yOfs self:Schedule(15, moveEnd, self) DBT:CreateBar(15, L.MOVE_WARNING_BAR, private.isRetail and 237538 or 136106) end) end if anchorFrame:IsShown() then moveEnd(self) else anchorFrame:Show() anchorFrame.ticker = anchorFrame.ticker or C_Timer.NewTicker(5, function() self:AddWarning(L.MOVE_WARNING_MESSAGE) end) self:AddWarning(L.MOVE_WARNING_MESSAGE) self:Schedule(15, moveEnd, self) DBT:CreateBar(15, L.MOVE_WARNING_BAR, private.isRetail and 237538 or 136106) frame:Show() frame:SetFrameStrata("TOOLTIP") frame:SetAlpha(1) end end end local textureCode = " |T%s:12:12|t " local textureExp = " |T(%S+......%S+):12:12|t "--Fix texture file including blank not strips(example: Interface\\Icons\\Spell_Frost_Ring of Frost). But this have limitations. Since I'm poor at regular expressions, this is not good fix. Do you have another good regular expression, tandanu? -- TODO: is there a good reason that this is a weak table? local cachedColorFunctions = setmetatable({}, {__mode = "kv"}) local function setText(announceType, spellId, castTime, preWarnTime, customName, originalSpellID) local spellName if customName then spellName = customName else spellName = DBM:ParseSpellName(spellId, announceType) or CL.UNKNOWN end local text if announceType == "cast" then local spellHaste = select(4, DBM:GetSpellInfo(10059)) / 10000 -- 10059 = Stormwind Portal, should have 10000 ms cast time local timer = (select(4, DBM:GetSpellInfo(originalSpellID or spellId)) or 1000) / spellHaste text = L.AUTO_ANNOUNCE_TEXTS[announceType]:format(spellName, castTime or (timer / 1000)) elseif announceType == "prewarn" then if type(preWarnTime) == "string" then text = L.AUTO_ANNOUNCE_TEXTS[announceType]:format(spellName, preWarnTime) else text = L.AUTO_ANNOUNCE_TEXTS[announceType]:format(spellName, L.SEC_FMT:format(tostring(preWarnTime or 5))) end elseif announceType == "stage" or announceType == "prestage" then text = L.AUTO_ANNOUNCE_TEXTS[announceType]:format(tostring(spellId)) elseif announceType == "stagechange" then text = L.AUTO_ANNOUNCE_TEXTS.spell else text = L.AUTO_ANNOUNCE_TEXTS[announceType]:format(spellName) end return text, spellName end function announcePrototype:SetText(customName) local text, spellName = setText(self.announceType, self.spellId, self.castTime, self.preWarnTime, customName) self.text = text self.spellName = spellName end ---Not to be confused with SetText, which only sets the text of object. ---
This changes actual ID so announce callback also swaps ID for WAs ---@param altSpellId string|number function announcePrototype:UpdateKey(altSpellId) self.spellId = altSpellId self.icon = DBM:ParseSpellIcon(altSpellId, self.announceType, self.icon) if self.announceType then --Regenerate auto localized text if it's an auto localized alert local text, spellName = setText(self.announceType, self.spellId, self.castTime, self.preWarnTime) self.text = text self.spellName = spellName else--Just regenerating spellName not message text because it's likely a custom text object such as NewSpecialWarning self.spellName = DBM:ParseSpellName(altSpellId) end end ---@class Announce0: Announce ---@field Show fun(self: Announce0) ---@class Announce1: Announce ---@field Show fun(self: Announce1, arg1: string|number) ---@class Announce1Num: Announce ---@field Show fun(self: Announce1Num, arg1: number) ---@class Announce2: Announce ---@field Show fun(self: Announce2, arg1: string|number, arg2: string|number) ---@class Announce2StrNum: Announce ---@field Show fun(self: Announce2StrNum, arg1: string|number, arg2: number) ---@class Announce2NumStr: Announce ---@field Show fun(self: Announce2NumStr, arg1: number, arg2: string|number) -- TODO: this function is an abomination, it needs to be rewritten. Also: check if these work-arounds are still necessary function announcePrototype:Show(...) -- todo: reduce amount of unneeded strings if not self.option or self.mod.Options[self.option] then if DBM.Options.DontShowBossAnnounces then return end -- don't show the announces if the spam filter option is set if DBM.Options.DontShowTargetAnnouncements and (self.announceType == "target" or self.announceType == "targetcount") and not self.noFilter then return end--don't show announces that are generic target announces local argTable = {...} local colorCode = ("|cff%.2x%.2x%.2x"):format(self.color.r * 255, self.color.g * 255, self.color.b * 255) if #self.combinedtext > 0 then --Throttle spam. if DBM.Options.WarningAlphabetical then table.sort(self.combinedtext) end local combinedText = table.concat(self.combinedtext, "<, >") if self.combinedcount == 1 then combinedText = combinedText .. " " .. L.GENERIC_WARNING_OTHERS elseif self.combinedcount > 1 then combinedText = combinedText .. " " .. L.GENERIC_WARNING_OTHERS2:format(self.combinedcount) end --Process for i = 1, #argTable do if type(argTable[i]) == "string" then argTable[i] = combinedText end end end local announceCount if self.announceType and (self.announceType == "count" or self.announceType == "targetcount" or self.announceType == "sooncount" or self.announceType == "incomingcount") then--Don't use find "count" here, it'll match countdown --Stage triggers don't pass count, but they do not need to, there is a stage callback and trigger option in WA that should be used if type(argTable[1]) == "number" then announceCount = argTable[1] end end local message = stringUtils.pformat(self.text, unpack(argTable)) local text = ("%s%s%s|r%s"):format( (DBM.Options.WarningIconLeft and self.icon and textureCode:format(self.icon)) or "", colorCode, message, (DBM.Options.WarningIconRight and self.icon and textureCode:format(self.icon)) or "" ) self.combinedcount = 0 self.combinedtext = {} if not cachedColorFunctions[self.color] then local color = self.color -- upvalue for the function to colorize names, accessing self in the colorize closure is not safe as the color of the announce object might change (it would also prevent the announce from being garbage-collected but announce objects are never destroyed) cachedColorFunctions[color] = function(cap) cap = cap:sub(2, -2) local noStrip = cap:match("noStrip ") if not noStrip then local name = cap local playerClass, playerIcon = DBM:GetRaidClass(name) if playerClass ~= "UNKNOWN" then cap = DBM:GetShortServerName(cap)--Only run realm strip function if class color was valid (IE it's an actual playername) end local playerColor = RAID_CLASS_COLORS[playerClass] or color if playerColor then if playerIcon > 0 and playerIcon <= 8 then cap = ("|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_%d:0|t"):format(playerIcon) .. ("|r|cff%.2x%.2x%.2x%s|r|cff%.2x%.2x%.2x"):format(playerColor.r * 255, playerColor.g * 255, playerColor.b * 255, cap, color.r * 255, color.g * 255, color.b * 255) else cap = ("|r|cff%.2x%.2x%.2x%s|r|cff%.2x%.2x%.2x"):format(playerColor.r * 255, playerColor.g * 255, playerColor.b * 255, cap, color.r * 255, color.g * 255, color.b * 255) end end else cap = cap:sub(9) end return cap end end text = text:gsub(">.-<", cachedColorFunctions[self.color]) DBM:AddWarning(text, nil, self) if DBM.Options.ShowWarningsInChat then if not DBM.Options.WarningIconChat then text = text:gsub(textureExp, "") -- textures @ chat frame can (and will) distort the font if using certain combinations of UI scale, resolution and font size TODO: is this still true as of cataclysm? end self.mod:AddMsg(text, nil) end --Message: Full message text --Icon: Texture path/id for icon --Type: Announce type ----Types: you, target, targetcount, targetsource, spell, ends, endtarget, fades, adds, count, stack, cast, soon, sooncount, prewarn, bait, stage, stagechange, prestage, moveto ------Personal/Role (Applies to you, or your job): you, stack, bait, moveto, fades ------General Target Messages (informative, doesn't usually apply to you): target, targetsource, targetcount ------Fight Changes (Stages, adds, boss buff/debuff, etc): stage, stagechange, prestage, adds, ends, endtarget ------General (can really apply to anything): spell, count, soon, sooncount, prewarn --SpellId: Raw spell or encounter journal Id if available. --Mod ID: Encounter ID as string, or a generic string for mods that don't have encounter ID (such as trash, dummy/test mods) --boolean: Whether or not this warning is a special warning (higher priority). BW would call this "emphasized" --announceCount: If it's a count announce, this will provide access to the number value of that count. This, along with spellId should be used instead of message text scanning for most weak auras that need to target specific count casts DBM:FireEvent("DBM_Announce", message, self.icon, self.type, self.spellId, self.mod.id, false, announceCount) if self.sound > 0 then--0 means muted, 1 means no voice pack support, 2 means voice pack version/support if self.sound > 1 and DBM.Options.ChosenVoicePack2 ~= "None" and DBM.Options.VPReplacesAnnounce and not private.voiceSessionDisabled and not DBM.Options.VPDontMuteSounds and self.sound <= private.swFilterDisabled then return end if not self.option or self.mod.Options[self.option .. "SWSound"] ~= "None" then DBM:PlaySoundFile(DBM.Options.RaidWarningSound, nil, true)--Validate true end end else self.combinedcount = 0 self.combinedtext = {} end end ---Object that's used when precision isn't possible (number of targets variable or unknown) ---@param delay number ---@param ... any function announcePrototype:CombinedShow(delay, ...) if self.option and not self.mod.Options[self.option] then return end if DBM.Options.DontShowBossAnnounces then return end -- don't show the announces if the spam filter option is set if DBM.Options.DontShowTargetAnnouncements and (self.announceType == "target" or self.announceType == "targetcount") and not self.noFilter then return end--don't show announces that are generic target announces local argTable = {...} for i = 1, #argTable do if type(argTable[i]) == "string" then if #self.combinedtext < 7 then--Throttle spam. We may not need more than 6 targets.. if not checkEntry(self.combinedtext, argTable[i]) then self.combinedtext[#self.combinedtext + 1] = argTable[i] end else self.combinedcount = self.combinedcount + 1 end end end DBMScheduler:Unschedule(self.Show, self.mod, self) local id = DBMScheduler:Schedule(delay or 0.5, self.Show, self.mod, self, ...) test:Trace(self.mod, "SchedulerHideFromTraceIfUnscheduled", id) test:Trace(self.mod, "SetScheduleMethodName", id, self, "CombinedShow", ...) end ---New object that allows defining count instead of scheduling for more efficient and immediate warnings when precise count is known ---@param maxTotal number ---@param ... any function announcePrototype:PreciseShow(maxTotal, ...) test:Trace(self.mod, "CombinedWarningPreciseShow", self, maxTotal) if self.option and not self.mod.Options[self.option] then return end if DBM.Options.DontShowBossAnnounces then return end -- don't show the announces if the spam filter option is set if DBM.Options.DontShowTargetAnnouncements and (self.announceType == "target" or self.announceType == "targetcount") and not self.noFilter then return end--don't show announces that are generic target announces local argTable = {...} for i = 1, #argTable do if type(argTable[i]) == "string" then if #self.combinedtext < 7 then--Throttle spam. We may not need more than 6 targets.. if not checkEntry(self.combinedtext, argTable[i]) then self.combinedtext[#self.combinedtext + 1] = argTable[i] end else self.combinedcount = self.combinedcount + 1 end end end DBMScheduler:Unschedule(self.Show, self.mod, self) local viableTotal = DBM:NumRealAlivePlayers() if (maxTotal <= #self.combinedtext + self.combinedcount) or (viableTotal <= #self.combinedtext + self.combinedcount) then--All targets gathered, show immediately self:Show(...)--Does this need self or mod? will it have this bug? https://github.com/DeadlyBossMods/DBM-Unified/issues/153 test:Trace(self.mod, "CombinedWarningPreciseShowSuccess", self, maxTotal) else--And even still, use scheduling backup in case counts still fail local id = DBMScheduler:Schedule(1.2, self.Show, self.mod, self, ...) test:Trace(self.mod, "SchedulerHideFromTraceIfUnscheduled", id, maxTotal) test:Trace(self.mod, "SetScheduleMethodName", id, self, "PreciseShow", maxTotal, ...) end end ---@param t number ---@param ... any function announcePrototype:Schedule(t, ...) local id = DBMScheduler:Schedule(t, self.Show, self.mod, self, ...) test:Trace(self.mod, "SetScheduleMethodName", id, self, "Schedule", ...) return id end ---@param time number? ---@param numAnnounces number? ---@param ... any function announcePrototype:Countdown(time, numAnnounces, ...) DBMScheduler:ScheduleCountdown(time, numAnnounces, self.Show, self.mod, self, ...) end function announcePrototype:Cancel(...) return DBMScheduler:Unschedule(self.Show, self.mod, self, ...) end ---@param name VPSound? ---@param customPath? string|number function announcePrototype:Play(name, customPath) local voice = DBM.Options.ChosenVoicePack2 if private.voiceSessionDisabled or voice == "None" or not DBM.Options.VPReplacesAnnounce then return end local always = DBM.Options.AlwaysPlayVoice if DBM.Options.DontShowTargetAnnouncements and (self.announceType == "target" or self.announceType == "targetcount") and not self.noFilter and not always then return end--don't show announces that are generic target announces if (not DBM.Options.DontShowBossAnnounces and (not self.option or self.mod.Options[self.option]) or always) and self.sound <= private.swFilterDisabled then --Filter tank specific voice alerts for non tanks if tank filter enabled --But still allow AlwaysPlayVoice to play as well. if (name == "changemt" or name == "tauntboss") and DBM.Options.FilterTankSpec and not self.mod:IsTank() and not always then return end local path = customPath or ("Interface\\AddOns\\DBM-VP" .. voice .. "\\" .. name .. ".ogg") DBM:PlaySoundFile(path) end end ---@param t number ---@param name VPSound? ---@param customPath? string|number function announcePrototype:ScheduleVoice(t, name, customPath) if private.voiceSessionDisabled or DBM.Options.ChosenVoicePack2 == "None" or not DBM.Options.VPReplacesAnnounce then return end DBMScheduler:Unschedule(self.Play, self.mod, self)--Allow ScheduleVoice to be used in same way as CombinedShow local id = DBMScheduler:Schedule(t, self.Play, self.mod, self, name, customPath) test:Trace(self.mod, "SetScheduleMethodName", id, self, "ScheduleVoice", name, customPath) return id end ---Object Permits scheduling voice multiple times for same object ---@param t number ---@param name VPSound? ---@param customPath? string|number function announcePrototype:ScheduleVoiceOverLap(t, name, customPath) if private.voiceSessionDisabled or DBM.Options.ChosenVoicePack2 == "None" or not DBM.Options.VPReplacesAnnounce then return end local id = DBMScheduler:Schedule(t, self.Play, self.mod, self, name, customPath) test:Trace(self.mod, "SetScheduleMethodName", id, self, "ScheduleVoiceOverLap", name, customPath) return id end function announcePrototype:CancelVoice(...) if private.voiceSessionDisabled or DBM.Options.ChosenVoicePack2 == "None" or not DBM.Options.VPReplacesAnnounce then return end return DBMScheduler:Unschedule(self.Play, self.mod, self, ...) end ---old constructor (no auto-localize) ---@param text string ---@param color number? 1 = Positive Message, 2 = Normal Message, 3 - Higher Priority, 4 - Highest Priority ---@param icon number|string? Use number for spellId, -number for journalID, number as string for textureID ---@param optionDefault SpecFlags|boolean? ---@param optionName string|boolean? String for custom option name. Using false hides option completely ---@param soundOption number|boolean? 0 = No Sound, 1 = Sound with no voice pack support, >=2 = Voice pack version/support ---@param spellID number|string? Used to define a spellID used for GroupSpells and WeakAura key ---@param waCustomName any? Used to show custom name/text for Spell header (usually used when a made up SpellID is used) function bossModPrototype:NewAnnounce(text, color, icon, optionDefault, optionName, soundOption, spellID, waCustomName) if not text then error("NewAnnounce: you must provide announce text", 2) end if type(text) == "number" then DBM:Debug("|cffff0000NewAnnounce: Non auto localized text cannot be numbers, fix this for |r" .. text) end if type(optionName) == "number" then DBM:Debug("|cffff0000NewAnnounce: Non auto localized optionNames cannot be numbers, fix this for |r" .. text) optionName = nil end if soundOption and type(soundOption) == "boolean" then soundOption = 0--No Sound end icon = DBM:ParseSpellIcon(icon) ---@class Announce local obj = setmetatable( { objClass = "Announce", text = self.localization.warnings[text], combinedtext = {}, combinedcount = 0, color = DBM.Options.WarningColors[color or 1] or DBM.Options.WarningColors[1], sound = soundOption or 1, mod = self, icon = icon, spellId = spellID,--For WeakAuras / other callbacks }, mt ) test:Trace(self, "NewAnnounce", obj, "untyped") if optionName then obj.option = optionName self:AddBoolOption(obj.option, optionDefault, "announce", nil, nil, nil, spellID, nil, waCustomName) elseif optionName ~= false then obj.option = text self:AddBoolOption(obj.option, optionDefault, "announce", nil, nil, nil, spellID, nil, waCustomName) end tinsert(self.announces, obj) return obj end -- new constructor (partially auto-localized warnings and options, yay!) ---@return Announce|Announce0|Announce1|Announce2 local function newAnnounce(self, announceType, spellId, color, icon, optionDefault, optionName, castTime, preWarnTime, soundOption, noFilter) if not spellId then error("newAnnounce: you must provide spellId", 2) end local optionVersion, alternateSpellId if type(optionName) == "number" then if optionName > 10 then--Being used as spell name shortening if DBM.Options.WarningShortText then alternateSpellId = optionName end else--Being used as option version optionVersion = optionName end optionName = nil end if soundOption and type(soundOption) == "boolean" then soundOption = 0--No Sound end local text, spellName = setText(announceType, alternateSpellId or spellId, castTime, preWarnTime, nil, spellId) icon = DBM:ParseSpellIcon(icon or spellId) ---@class Announce local obj = setmetatable( -- todo: fix duplicate code { objClass = "Announce", text = text, combinedtext = {}, combinedcount = 0, announceType = announceType, color = DBM.Options.WarningColors[color or 1] or DBM.Options.WarningColors[1], mod = self, icon = icon, sound = soundOption or 1, type = announceType, spellId = spellId, spellName = spellName, noFilter = noFilter, castTime = castTime, preWarnTime = preWarnTime, }, mt ) test:Trace(self, "NewAnnounce", obj, announceType) local catType = "announce"--Default to General announce if not self.NoSortAnnounce then--ALL announce objects will be assigned "announce", usually for mods that sort by phase instead --Change if Personal or Other if announceType == "target" or announceType == "targetcount" or announceType == "stack" then catType = "announceother" end end if optionName then obj.option = optionName self:AddBoolOption(obj.option, optionDefault, catType, nil, nil, nil, spellId, announceType) elseif optionName ~= false then obj.option = catType .. spellId .. announceType .. (optionVersion or "") self:AddBoolOption(obj.option, optionDefault, catType, nil, nil, nil, spellId, announceType) if noFilter and announceType == "target" then self.localization.options[obj.option] = L.AUTO_ANNOUNCE_OPTIONS["targetNF"]:format(spellId) else self.localization.options[obj.option] = L.AUTO_ANNOUNCE_OPTIONS[announceType]:format(spellId) end end tinsert(self.announces, obj) return obj end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewYouAnnounce(spellId, color, ...) ---@type Announce0 return newAnnounce(self, "you", spellId, color or 1, ...) end ---@param optionDefault SpecFlags|boolean? function bossModPrototype:NewTargetNoFilterAnnounce(spellId, color, icon, optionDefault, optionName, castTime, preWarnTime, soundOption) -- spellId, color, icon, optionDefault, optionName, castTime, preWarnTime, soundOption, noFilter ---@type Announce1 return newAnnounce(self, "target", spellId, color or 3, icon, optionDefault, optionName, castTime, preWarnTime, soundOption, true) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1 function bossModPrototype:NewTargetAnnounce(spellId, color, ...) ---@type Announce1 return newAnnounce(self, "target", spellId, color or 3, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce2 function bossModPrototype:NewTargetSourceAnnounce(spellId, color, ...) ---@type Announce2 return newAnnounce(self, "targetsource", spellId, color or 3, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce2NumStr function bossModPrototype:NewTargetCountAnnounce(spellId, color, ...) ---@type Announce2NumStr return newAnnounce(self, "targetcount", spellId, color or 3, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewSpellAnnounce(spellId, color, ...) ---@type Announce0 return newAnnounce(self, "spell", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1 function bossModPrototype:NewSpellSourceAnnounce(spellId, color, ...) ---@type Announce1 return newAnnounce(self, "spellsource", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewIncomingAnnounce(spellId, color, ...) ---@type Announce0 return newAnnounce(self, "incoming", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1Num function bossModPrototype:NewIncomingCountAnnounce(spellId, color, ...) ---@type Announce1Num return newAnnounce(self, "incomingcount", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewEndAnnounce(spellId, color, ...) ---@type Announce0 return newAnnounce(self, "ends", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1 function bossModPrototype:NewEndTargetAnnounce(spellId, color, ...) ---@type Announce1 return newAnnounce(self, "endtarget", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewFadesAnnounce(spellId, color, ...) ---@type Announce0 return newAnnounce(self, "fades", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1Num function bossModPrototype:NewAddsLeftAnnounce(spellId, color, ...) ---@type Announce1Num return newAnnounce(self, "addsleft", spellId, color or 3, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1Num function bossModPrototype:NewCountAnnounce(spellId, color, ...) ---@type Announce1Num return newAnnounce(self, "count", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce2StrNum function bossModPrototype:NewStackAnnounce(spellId, color, ...) ---@type Announce2StrNum return newAnnounce(self, "stack", spellId, color or 2, ...) end ---@param spellId number|string ---@param castTime number? ---@param color number? ---@param icon number|string? ---@param optionDefault SpecFlags|boolean? ---@param optionName string|number|boolean? ---@param soundOption number|boolean? function bossModPrototype:NewCastAnnounce(spellId, color, castTime, icon, optionDefault, optionName, _, soundOption) -- spellId, color, castTime, icon, optionDefault, optionName, noArg, soundOption ---@type Announce0 return newAnnounce(self, "cast", spellId, color or 3, icon, optionDefault, optionName, castTime, nil, soundOption) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewSoonAnnounce(spellId, color, ...) ---@type Announce0 return newAnnounce(self, "soon", spellId, color or 2, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1Num function bossModPrototype:NewSoonCountAnnounce(spellId, color, ...) ---@type Announce1Num return newAnnounce(self, "sooncount", spellId, color or 2, ...) end ---This object disables sounds, it's almost always used in combation with a countdown timer. Even if not a countdown, its a text only spam not a sound spam ---@param spellId number|string ---@param castTime number? ---@param preWarnTime number? ---@param color number? ---@param icon number|string? ---@param optionDefault SpecFlags|boolean? ---@param optionName string|number|boolean? ---@param noFilter boolean? function bossModPrototype:NewCountdownAnnounce(spellId, color, icon, optionDefault, optionName, castTime, preWarnTime, _, noFilter) -- spellId, color, icon, optionDefault, optionName, castTime, preWarnTime, soundOption, noFilter ---@type Announce1Num return newAnnounce(self, "countdown", spellId, color or 4, icon, optionDefault, optionName, castTime, preWarnTime, 0, noFilter) end ---@param spellId number|string ---@param time number ---@param color number? ---@param icon number|string? ---@param optionDefault SpecFlags|boolean? ---@param optionName string|number|boolean? ---@param soundOption number|boolean? function bossModPrototype:NewPreWarnAnnounce(spellId, time, color, icon, optionDefault, optionName, _, soundOption) -- spellId, time, color, icon, optionDefault, optionName, noArg, soundOption ---@type Announce0 return newAnnounce(self, "prewarn", spellId, color or 2, icon, optionDefault, optionName, nil, time, soundOption) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewBaitAnnounce(spellId, color, ...) ---@type Announce0 return newAnnounce(self, "bait", spellId, color or 3, ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewPhaseAnnounce(stage, color, icon, ...) ---@type Announce0 return newAnnounce(self, "stage", stage, color or 2, icon or "136116", ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1 function bossModPrototype:NewPhaseChangeAnnounce(color, icon, ...) ---@type Announce1 return newAnnounce(self, "stagechange", 0, color or 2, icon or "136116", ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce0 function bossModPrototype:NewPrePhaseAnnounce(stage, color, icon, ...) ---@type Announce0 return newAnnounce(self, "prestage", stage, color or 2, icon or "136116", ...) end ---@overload fun(self, spellId: number|string, color: number?, icon: number|string?, optionDefault: SpecFlags|boolean?, optionName: string|number|boolean?, castTime: number?, preWarnTime: number?, soundOption: number|boolean?, noFilter: boolean?): Announce1 function bossModPrototype:NewMoveToAnnounce(spellId, color, ...) ---@type Announce1 return newAnnounce(self, "moveto", spellId, color or 3, ...) end