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.

712 lines
20 KiB

5 years ago
-------------------------------------------------------------------------------
-- BigWigs API
-- @module BigWigs
-- @alias core
local core, bossPrototype, pluginPrototype
do
local _, tbl =...
core = tbl.core
bossPrototype = tbl.bossPrototype
pluginPrototype = tbl.pluginPrototype
core.name = "BigWigs"
local at = LibStub("AceTimer-3.0")
at:Embed(core)
at:Embed(bossPrototype)
at:Embed(pluginPrototype)
end
local adb = LibStub("AceDB-3.0")
local lds = LibStub("LibDualSpec-1.0")
local L = BigWigsAPI:GetLocale("BigWigs")
local CL = BigWigsAPI:GetLocale("BigWigs: Common")
local loader = BigWigsLoader
core.SendMessage = loader.SendMessage
local mod, bosses, plugins = {}, {}, {}
local coreEnabled = false
-- Try to grab unhooked copies of critical loading funcs (hooked by some crappy addons)
local GetBestMapForUnit = loader.GetBestMapForUnit
local SendAddonMessage = loader.SendAddonMessage
local GetInstanceInfo = loader.GetInstanceInfo
local UnitName = BigWigsLoader.UnitName
local UnitGUID = BigWigsLoader.UnitGUID
-- Upvalues
local next, type, setmetatable = next, type, setmetatable
local pName = UnitName("player")
-------------------------------------------------------------------------------
-- Event handling
--
do
local noEvent = "Module %q tried to register/unregister an event without specifying which event."
local noFunc = "Module %q tried to register an event with the function '%s' which doesn't exist in the module."
local eventMap = {}
local bwUtilityFrame = CreateFrame("Frame")
bwUtilityFrame:SetScript("OnEvent", function(_, event, ...)
for k,v in next, eventMap[event] do
if type(v) == "function" then
v(event, ...)
else
k[v](k, event, ...)
end
end
end)
function core:RegisterEvent(event, func)
if type(event) ~= "string" then error((noEvent):format(self.moduleName)) end
if (not func and not self[event]) or (type(func) == "string" and not self[func]) then error((noFunc):format(self.moduleName or "?", func or event)) end
if not eventMap[event] then eventMap[event] = {} end
eventMap[event][self] = func or event
bwUtilityFrame:RegisterEvent(event)
end
function core:UnregisterEvent(event)
if type(event) ~= "string" then error((noEvent):format(self.moduleName)) end
if not eventMap[event] then return end
eventMap[event][self] = nil
if not next(eventMap[event]) then
bwUtilityFrame:UnregisterEvent(event)
eventMap[event] = nil
end
end
local function UnregisterAllEvents(_, module)
for k,v in next, eventMap do
for j in next, v do
if j == module then
module:UnregisterEvent(k)
end
end
end
end
loader.RegisterMessage(mod, "BigWigs_OnBossDisable", UnregisterAllEvents)
loader.RegisterMessage(mod, "BigWigs_OnBossWipe", UnregisterAllEvents)
loader.RegisterMessage(mod, "BigWigs_OnPluginDisable", UnregisterAllEvents)
end
-------------------------------------------------------------------------------
-- ENCOUNTER event handler
--
function mod:ENCOUNTER_START(_, id)
for _, module in next, bosses do
if module.engageId == id then
if not module.enabled then
module:Enable()
if UnitGUID("boss1") then -- Only if _START fired after IEEU
module:Engage()
end
end
end
end
end
-------------------------------------------------------------------------------
-- Target monitoring
--
local enablezones, enablemobs = {}, {}
local function enableBossModule(module, sync)
if not module.enabled then
module:Enable()
if sync and not module.worldBoss then
module:Sync("Enable", module.moduleName)
end
end
end
local function shouldReallyEnable(unit, moduleName, mobId, sync)
local module = bosses[moduleName]
if not module or module.enabled then return end
if (not module.VerifyEnable or module:VerifyEnable(unit, mobId, GetBestMapForUnit("player"))) then
enableBossModule(module, sync)
end
end
local function targetSeen(unit, targetModule, mobId, sync)
if type(targetModule) == "string" then
shouldReallyEnable(unit, targetModule, mobId, sync)
else
for i = 1, #targetModule do
local module = targetModule[i]
shouldReallyEnable(unit, module, mobId, sync)
end
end
end
local function targetCheck(unit, sync)
local name = UnitName(unit)
if not name or UnitIsCorpse(unit) or UnitIsDead(unit) or UnitPlayerControlled(unit) then return end
local guid = UnitGUID(unit)
if not guid then
core:Error(("Found unit '%s' with name '%s' but no guid, tell the BigWigs authors."):format(unit, name))
return
end
local _, _, _, _, _, mobId = strsplit("-", guid)
local id = tonumber(mobId)
if id and enablemobs[id] then
targetSeen(unit, enablemobs[id], id, sync)
end
end
local function updateMouseover() targetCheck("mouseover", true) end
local function unitTargetChanged(event, target)
targetCheck(target .. "target")
end
function core:RegisterEnableMob(module, ...)
for i = 1, select("#", ...) do
local mobId = select(i, ...)
if type(mobId) ~= "number" or mobId < 1 then
core:Error(("Module %q tried to register the mobId %q, but it wasn't a valid number."):format(module.moduleName, tostring(mobId)))
else
module.enableMobs[mobId] = true -- Module specific list
-- Global list
local entryType = type(enablemobs[mobId])
if entryType == "nil" then
enablemobs[mobId] = module.moduleName
elseif entryType == "table" then
enablemobs[mobId][#enablemobs[mobId] + 1] = module.moduleName
elseif entryType == "string" then -- Converting from 1 module registered to this mobId, to multiple modules
local previousModuleEntry = enablemobs[mobId]
enablemobs[mobId] = { previousModuleEntry, module.moduleName }
else
core:Error(("Unknown type in a enable trigger table at index %d for %q."):format(i, module.moduleName))
end
end
end
end
function core:GetEnableMobs()
local t = {}
for k,v in next, enablemobs do
t[k] = v
end
return t
end
-------------------------------------------------------------------------------
-- Testing
--
do
local callbackRegistered = false
local messages = {}
local count = 1
local colors = {"green", "red", "orange", "yellow", "cyan", "blue", "blue", "purple"}
local sounds = {"Long", "Warning", "Alert", "Alarm", "Info", "onyou", "underyou", false}
local function barStopped(event, bar)
local a = bar:Get("bigwigs:anchor")
local key = bar:GetLabel()
if a and messages[key] then
if not colors[count] then count = 1 end
local color = colors[count]
local sound = sounds[count]
local emphasized = count == 2
if count == 6 then
core:SendMessage("BigWigs_Flash", core, key)
end
core:Print(L.test .." - ".. color ..": ".. key)
core:SendMessage("BigWigs_Message", core, key, color..": "..key, color, messages[key], emphasized)
core:SendMessage("BigWigs_Sound", core, key, sound)
count = count + 1
messages[key] = nil
end
end
local lastNamePlateBar = 0
local lastSpell = 1
local lastTest = 1
function core:Test()
if not callbackRegistered then
LibStub("LibCandyBar-3.0").RegisterCallback(core, "LibCandyBar_Stop", barStopped)
callbackRegistered = true
end
local msg = CL.count:format(L.test, lastTest)
local icon = GetSpellTexture(lastSpell)
while not icon or icon == 136243 do -- 136243 = cogwheel
lastSpell = lastSpell + 1
icon = GetSpellTexture(lastSpell)
end
lastSpell = lastSpell + 1
lastTest = lastTest + 1
local time = random(11, 30)
messages[msg] = icon
core:SendMessage("BigWigs_StartBar", core, msg, msg, time, icon)
local guid = UnitGUID("target")
if guid and UnitCanAttack("player", "target") then
for i = 1, 40 do
local unit = ("nameplate%d"):format(i)
if UnitGUID(unit) == guid then
local t = GetTime()
if (t - lastNamePlateBar) > 25 then
lastNamePlateBar = t
core:Print(L.testNameplate)
core:SendMessage("BigWigs_StartNameplateBar", core, msg, msg, 25, icon, false, guid)
end
return
end
end
end
end
end
-------------------------------------------------------------------------------
-- Communication
--
local function bossComm(_, msg, extra, sender)
if msg == "Enable" and extra then
local m = bosses[extra]
if m and not m.enabled and sender ~= pName then
enableBossModule(m)
end
end
end
function mod:RAID_BOSS_WHISPER(_, msg) -- Purely for Transcriptor to assist in logging purposes.
if IsInGroup() then
SendAddonMessage("Transcriptor", msg, IsInGroup(2) and "INSTANCE_CHAT" or "RAID")
end
end
-------------------------------------------------------------------------------
-- Initialization
--
local initModules = {}
do
local function InitializeModules()
local count = #initModules
if count > 0 then
for i = 1, count do
initModules[i]:Initialize()
end
initModules = {}
end
end
local function profileUpdate()
core:SendMessage("BigWigs_ProfileUpdate")
end
local addonName = ...
function mod:ADDON_LOADED(_, name)
if name ~= addonName then return end
local defaults = {
profile = {
flash = true,
showZoneMessages = true,
fakeDBMVersion = false,
},
global = {
optionShiftIndexes = {},
watchedMovies = {},
},
}
local db = adb:New("BigWigs3DB", defaults, true)
lds:EnhanceDatabase(db, "BigWigs3DB")
db.RegisterCallback(mod, "OnProfileChanged", profileUpdate)
db.RegisterCallback(mod, "OnProfileCopied", profileUpdate)
db.RegisterCallback(mod, "OnProfileReset", profileUpdate)
core.db = db
mod.ADDON_LOADED = InitializeModules
InitializeModules()
end
core.RegisterEvent(mod, "ADDON_LOADED")
end
do
local function DisableModules()
for _, module in next, bosses do
module:Disable()
end
for _, module in next, plugins do
module:Disable()
end
end
local function DisableCore()
if coreEnabled then
coreEnabled = false
loader.UnregisterMessage(mod, "BigWigs_BossComm")
core.UnregisterEvent(mod, "ZONE_CHANGED_NEW_AREA")
core.UnregisterEvent(mod, "PLAYER_LEAVING_WORLD")
core.UnregisterEvent(mod, "ENCOUNTER_START")
core.UnregisterEvent(mod, "RAID_BOSS_WHISPER")
core.UnregisterEvent(mod, "UPDATE_MOUSEOVER_UNIT")
core.UnregisterEvent(mod, "UNIT_TARGET")
core:CancelAllTimers()
core:SendMessage("BigWigs_StopConfigureMode")
if BigWigsOptions then
BigWigsOptions:Close()
end
DisableModules()
core:SendMessage("BigWigs_CoreDisabled")
end
end
local function zoneChanged()
-- Not if you released spirit on a world boss or if the GUI is open
if not UnitIsDeadOrGhost("player") and (not BigWigsOptions or (not BigWigsOptions:IsOpen() and not BigWigsOptions:InConfigureMode())) then
local bars = core:GetPlugin("Bars", true)
if bars and not bars:HasActiveBars() then -- Not if bars are showing
DisableCore() -- Alive in a non-enable zone, disable
end
end
end
local function EnablePlugins()
for _, module in next, plugins do
module:Enable()
end
end
function core:Enable(unit)
if not coreEnabled then
coreEnabled = true
loader.RegisterMessage(mod, "BigWigs_BossComm", bossComm)
core.RegisterEvent(mod, "ENCOUNTER_START")
core.RegisterEvent(mod, "RAID_BOSS_WHISPER")
core.RegisterEvent(mod, "UPDATE_MOUSEOVER_UNIT", updateMouseover)
core.RegisterEvent(mod, "UNIT_TARGET", unitTargetChanged)
core.RegisterEvent(mod, "PLAYER_LEAVING_WORLD", DisableCore) -- Simple disable when leaving instances
local _, instanceType = GetInstanceInfo()
if instanceType == "none" then -- We don't want to be disabling in instances
core.RegisterEvent(mod, "ZONE_CHANGED_NEW_AREA", zoneChanged) -- Special checks for disabling after world bosses
end
if IsLoggedIn() then
EnablePlugins()
else
core.RegisterEvent(mod, "PLAYER_LOGIN", EnablePlugins)
end
core:SendMessage("BigWigs_CoreEnabled")
end
if type(unit) == "string" then
targetCheck(unit) -- Mainly for the Loader to tell the core to enable a world boss after loading the world boss addon
end
end
end
function core:IsEnabled()
return coreEnabled
end
function core:Print(msg)
print("BigWigs: |cffffff00"..msg.."|r")
end
function core:Error(msg)
core:Print(msg)
geterrorhandler()(msg)
end
-------------------------------------------------------------------------------
-- API
--
do
local L = GetLocale()
if L == "enGB" then L = "enUS" end
function core:NewBossLocale(moduleName, locale)
local module = bosses[moduleName]
if module and L == locale then
return module:GetLocale()
end
end
end
-------------------------------------------------------------------------------
-- Module handling
--
do
local EJ_GetEncounterInfo = EJ_GetEncounterInfo
local errorAlreadyRegistered = "%q already exists as a module in BigWigs, but something is trying to register it again."
local errorJournalIdInvalid = "%q is using the invalid journal id of %q."
local bossMeta = { __index = bossPrototype, __metatable = false }
function core:NewBoss(moduleName, zoneId, journalId, instanceId)
if bosses[moduleName] then
core:Print(errorAlreadyRegistered:format(moduleName))
else
local m = setmetatable({
name = "BigWigs_Bosses_"..moduleName, -- XXX AceAddon/AceDB backwards compat
moduleName = moduleName,
-- Embed callback handler
RegisterMessage = loader.RegisterMessage,
UnregisterMessage = loader.UnregisterMessage,
SendMessage = loader.SendMessage,
-- Embed event handler
RegisterEvent = core.RegisterEvent,
UnregisterEvent = core.UnregisterEvent,
}, bossMeta)
bosses[moduleName] = m
initModules[#initModules+1] = m
if journalId then
local name = EJ_GetEncounterInfo(journalId)
if name then
m.journalId = journalId
m.displayName = EJ_GetEncounterInfo(journalId)
else
m.displayName = moduleName
core:Print(errorJournalIdInvalid:format(moduleName, journalId))
end
else
m.displayName = moduleName
end
if zoneId > 0 then
m.instanceId = zoneId
else
m.mapId = -zoneId
end
return m, CL
end
end
local pluginMeta = { __index = pluginPrototype, __metatable = false }
function core:NewPlugin(moduleName)
if plugins[moduleName] then
core:Print(errorAlreadyRegistered:format(moduleName))
else
local m = setmetatable({
name = "BigWigs_Plugins_"..moduleName, -- XXX AceAddon/AceDB backwards compat
moduleName = moduleName,
-- Embed callback handler
RegisterMessage = loader.RegisterMessage,
UnregisterMessage = loader.UnregisterMessage,
SendMessage = loader.SendMessage,
-- Embed event handler
RegisterEvent = core.RegisterEvent,
UnregisterEvent = core.UnregisterEvent,
}, pluginMeta)
plugins[moduleName] = m
initModules[#initModules+1] = m
return m, CL
end
end
end
function core:IterateBossModules()
return next, bosses
end
function core:GetBossModule(moduleName, silent)
if not silent and not bosses[moduleName] then
error(("No boss module named '%s' found."):format(moduleName))
else
return bosses[moduleName]
end
end
function core:IteratePlugins()
return next, plugins
end
function core:GetPlugin(moduleName, silent)
if not silent and not plugins[moduleName] then
error(("No plugin named '%s' found."):format(moduleName))
else
return plugins[moduleName]
end
end
do
local GetSpellInfo, C_EncounterJournal_GetSectionInfo = GetSpellInfo, C_EncounterJournal.GetSectionInfo
local C = core.C -- Set from Constants.lua
local standardFlag = C.BAR + C.CASTBAR + C.MESSAGE + C.ICON + C.SOUND + C.SAY + C.SAY_COUNTDOWN + C.PROXIMITY + C.FLASH + C.ALTPOWER + C.VOICE + C.INFOBOX + C.NAMEPLATEBAR
local defaultToggles = setmetatable({
berserk = C.BAR + C.MESSAGE + C.SOUND,
proximity = C.PROXIMITY,
altpower = C.ALTPOWER,
infobox = C.INFOBOX,
}, {__index = function()
return standardFlag
end})
local function setupOptions(module)
if module.optionHeaders then
for k, v in next, module.optionHeaders do
if type(v) == "string" then
if CL[v] then
module.optionHeaders[k] = CL[v]
end
elseif type(v) == "number" then
if v > 0 then
local n = GetSpellInfo(v)
if not n then core:Error(("Invalid spell ID %d in the optionHeaders for module %s."):format(v, module.name)) end
module.optionHeaders[k] = n or v
else
local tbl = C_EncounterJournal_GetSectionInfo(-v)
if not tbl then core:Error(("Invalid journal ID (-)%d in the optionHeaders for module %s."):format(-v, module.name)) end
module.optionHeaders[k] = tbl and tbl.title or v
end
end
end
end
if module.toggleOptions then
module.toggleDefaults = {}
for k, v in next, module.toggleOptions do
local bitflags = 0
local disabled = false
local t = type(v)
if t == "table" then
for i = 2, #v do
local flagName = v[i]
if C[flagName] then
bitflags = bitflags + C[flagName]
elseif flagName == "OFF" then
disabled = true
break
else
error(("%q tried to register '%q' as a bitflag for toggleoption '%q'"):format(module.moduleName, flagName, v[1]))
end
end
v = v[1]
t = type(v)
end
-- mix in default toggles for keys we know
-- this allows for mod.toggleOptions = {{1234, "bar", "message"}}
-- while option keys don't usually specify common features such as bar or message
for _, b in next, C do
if bit.band(defaultToggles[v], b) == b and bit.band(bitflags, b) ~= b then
bitflags = bitflags + b
end
end
if disabled then
if not module.toggleDisabled then
module.toggleDisabled = {}
end
module.toggleDisabled[v] = bitflags
bitflags = 0
end
if t == "string" then
local custom = v:match("^custom_(o[nf]f?)_.*")
if custom then
module.toggleDefaults[v] = custom == "on" and true or false
else
module.toggleDefaults[v] = bitflags
end
elseif t == "number" then
if v > 0 then
local n = GetSpellInfo(v)
if not n then core:Error(("Invalid spell ID %d in the toggleOptions for module %s."):format(v, module.name)) end
module.toggleDefaults[v] = bitflags
else
local tbl = C_EncounterJournal_GetSectionInfo(-v)
if not tbl then core:Error(("Invalid journal ID (-)%d in the toggleOptions for module %s."):format(-v, module.name)) end
module.toggleDefaults[v] = bitflags
end
end
end
module.db = core.db:RegisterNamespace(module.name, { profile = module.toggleDefaults })
end
end
local function moduleOptions(self)
if self.GetOptions then
local toggles, headers, altNames = self:GetOptions(CL)
if toggles then self.toggleOptions = toggles end
if headers then self.optionHeaders = headers end
if altNames then self.altNames = altNames end
self.GetOptions = nil
end
setupOptions(self)
self.SetupOptions = nil
end
function core:RegisterBossModule(module)
module.SetupOptions = moduleOptions
-- Call the module's OnRegister (which is our OnInitialize replacement)
if type(module.OnRegister) == "function" then
module:OnRegister()
module.OnRegister = nil
end
core:SendMessage("BigWigs_BossModuleRegistered", module.moduleName, module)
local id = module.instanceId or -(module.mapId)
if not enablezones[id] then
enablezones[id] = true
end
end
local function profileUpdate()
core:SendMessage("BigWigs_ProfileUpdate")
end
function core:RegisterPlugin(module)
if type(module.defaultDB) == "table" then
module.db = core.db:RegisterNamespace(module.name, { profile = module.defaultDB } )
module.db.RegisterCallback(module, "OnProfileChanged", profileUpdate)
module.db.RegisterCallback(module, "OnProfileCopied", profileUpdate)
module.db.RegisterCallback(module, "OnProfileReset", profileUpdate)
end
setupOptions(module)
-- Call the module's OnRegister (which is our OnInitialize replacement)
if type(module.OnRegister) == "function" then
module:OnRegister()
module.OnRegister = nil
end
core:SendMessage("BigWigs_PluginRegistered", module.moduleName, module)
if coreEnabled then
module:Enable() -- Support LoD plugins that load after we're enabled (e.g. zone based)
end
end
end
function core:AddColors(moduleName, options)
local module = bosses[moduleName]
if not module then
-- core:Error(("AddColors: Invalid module %q."):format(moduleName))
return
end
module.colorOptions = options
end
function core:AddSounds(moduleName, options)
local module = bosses[moduleName]
if not module then
-- core:Error(("AddSounds: Invalid module %q."):format(moduleName))
return
end
module.soundOptions = options
end
-------------------------------------------------------------------------------
-- Global
--
BigWigs = setmetatable({}, { __index = core, __newindex = function() end, __metatable = false })