Nenue@50: --- Devian - Devian.lua Nenue@32: -- @file-author@ Nenue@32: -- @project-revision@ @project-hash@ Nenue@32: -- @file-revision@ @file-hash@ Nenue@66: Nenue@66: --GLOBALS: Devian, DevCon, DevianLoadMessage Nenue@66: local ADDON, D = ... Nenue@66: Nenue@66: local currentProfile Nenue@66: local playerName = UnitName("player") Nenue@66: local playerRealm = playerName .. '-' .. GetRealmName() Nenue@66: local num_dock_tabs = 0 Nenue@66: local in_workspace = false Nenue@66: --@debug@ Nenue@66: D.debugmode = true Nenue@66: --@end-debug@ Nenue@66: D.print = function(...) Nenue@66: if currentProfile and not currentProfile.workspace then Nenue@66: return Nenue@66: end Nenue@66: Nenue@66: if D.debugmode then Nenue@66: return print('Dvn', ...) Nenue@66: else Nenue@66: return function() end Nenue@66: end Nenue@0: end Nenue@66: local print = D.print Nenue@66: Nenue@66: setmetatable(D, { Nenue@66: __call = function(t,k) Nenue@66: if not k then Nenue@66: return t.in_workspace Nenue@66: end Nenue@66: Nenue@66: return function(value) Nenue@66: if value then Nenue@66: D[k] = value Nenue@66: end Nenue@66: return D[k] Nenue@66: end Nenue@66: end Nenue@66: }) Nenue@66: D.L = setmetatable({}, { Nenue@66: __index= function(t,k) Nenue@66: return k Nenue@66: end, Nenue@66: __call = function(t,k,...) Nenue@66: return string.format((t[k] or k) , ...) Nenue@66: end Nenue@66: }) Nenue@66: Nenue@57: local MAJOR, MINOR = 'Devian-2.0', 'r@project-revision@' Nenue@66: local D = LibStub("AceAddon-3.0"):NewAddon(D, "Devian", "AceConsole-3.0", "AceEvent-3.0") Nenue@66: local L = D.L Nenue@66: _G.Devian = D Nenue@46: D:SetDefaultModuleState(false) Nenue@66: D.oldprint = getprinthandler() Nenue@66: if not _G.oldprint then _G.oldprint = D.oldprint end Nenue@66: Nenue@47: local pairs, tostring, tonumber, ipairs, type = pairs, tostring, tonumber, ipairs, type Nenue@66: local max, rand, format, print = max, math.random, string.format, print Nenue@47: local insert, wipe, concat = table.insert, table.wipe, table.concat Nenue@47: local select, unpack = select, unpack Nenue@47: local GetNumAddOns, GetAddOnInfo, GetAddOnEnableState, EnableAddOn = GetNumAddOns, GetAddOnInfo, GetAddOnEnableState, EnableAddOn Nenue@47: local UnitName, DisableAddOn = UnitName, DisableAddOn Nenue@66: Nenue@9: local db Nenue@13: local defaults = { Nenue@66: global = {{}, {}}, Nenue@66: default_channel = { Nenue@55: signature = 'Main', Nenue@55: x = 100, y = 800, Nenue@55: height = 500, width = 600, Nenue@66: enabled = true}, Nenue@66: current_profile = 1, Nenue@66: main_profile = 1, Nenue@66: last_profile = 1, Nenue@66: profilesName = {}, Nenue@66: profiles = { Nenue@45: }, Nenue@33: font = [[Interface\Addons\Devian\font\SourceCodePro-Regular.ttf]], -- font info Nenue@13: fontsize = 13, Nenue@13: fontoutline = 'NONE', Nenue@45: Nenue@45: headergrad = {'VERTICAL', 0, 0, 0, 1, Nenue@45: 1, 0.1, 0.1, 1}, -- header info Nenue@33: headerdrop = {1,1,1,1}, Nenue@45: headerblend = 'BLEND', Nenue@33: headeralpha = 1, Nenue@45: headerfontcolor = {1,1,1,1}, Nenue@45: Nenue@45: backdrop = {1,1,1,1}, -- background frame info Nenue@56: backgrad = {'VERTICAL', 0, 0, 0, .75, 0,0,0, .65}, Nenue@55: backblend = 'BLEND', Nenue@45: backalpha = 1, Nenue@45: backborder = {.5,.5,.5,1}, Nenue@45: backheader = {.25,.25,.25,1}, Nenue@45: Nenue@45: frontdrop = {1,1,1,1}, -- foreground frame info Nenue@56: frontgrad = {'VERTICAL', 0, 0, 0, 1, 0,0,0, 0.95}, Nenue@55: frontblend = 'BLEND', Nenue@28: frontalpha = 1, Nenue@45: frontborder = {.07,.47,1,1}, Nenue@45: frontheader = {1,1,1,1}, Nenue@33: tagcolor = {}, -- tag color repository Nenue@36: workspace = 2, -- current profile Nenue@47: last_workspace = 2, -- default workspace to alternate with when just "/dvn" is issued Nenue@47: Nenue@47: dock_onshow_fade_time = 2.5, Nenue@47: dock_onshow_fade_from = 1, Nenue@47: dock_onshow_fade_to = 0.2, Nenue@47: Nenue@47: dock_alpha_on = 1, Nenue@47: dock_alpha_off = 0.2, Nenue@47: dock_fade_in = 0.15, Nenue@47: dock_fade_out = 0.45, Nenue@47: dock_button_alpha_on = 1, Nenue@47: dock_button_alpha_off = 0.2, Nenue@50: dock_button_fade_in = 0.075, Nenue@55: dock_button_fade_out = 0.075, Nenue@47: Nenue@55: movement_fade = true, Nenue@55: movement_fade_time = 0.15, Nenue@60: movement_fade_from = 1, Nenue@60: movement_fade_to = 0, Nenue@60: movement_translation_x = 25, Nenue@60: movement_translation_y = 25, Nenue@13: } Nenue@9: Nenue@66: D.console = {} Nenue@66: D.max_channel = 0 Nenue@0: Nenue@67: D.InWorkspace = function () Nenue@67: return db.profiles[db.current_profile].workspace Nenue@67: end Nenue@67: Nenue@66: D.Profile = function (id, name) Nenue@66: Nenue@66: if name and not id and db.profilesName[name] then Nenue@66: id = db.profilesName[name] Nenue@66: print('ID located by name, |cFF00FF00'..name..'|r is |cFFFFFF00'.. id..'|r') Nenue@66: end Nenue@66: Nenue@66: if not id or not db.profiles[id] then Nenue@66: if not id then Nenue@66: id = #db.profiles+1 Nenue@66: print('Generated profile ID: |cFFFFFF00'.. id .. '|r') Nenue@66: end Nenue@66: Nenue@66: if not name or db.profilesName[name] then Nenue@66: local newName = name or (id == 1 and 'Main' or 'Profile') Nenue@66: local prefix = newName Nenue@66: local i = 2 Nenue@66: while db.profilesName[newName] do Nenue@66: i = i + 1 Nenue@66: newName = prefix .. i Nenue@66: end Nenue@66: name = newName Nenue@66: print('Generated profile name: |cFF00FF00'..newName..'|r') Nenue@66: end Nenue@66: print('Creating profile') Nenue@66: db.profilesName[name] = id Nenue@66: db.profiles[id] = { Nenue@66: name = name, Nenue@66: workspace = (id ~= 1), Nenue@66: current_channel = 1, Nenue@66: default_channel = 1, Nenue@66: num_channels = 1, Nenue@66: max_channel = 1, -- the highest created channel id Nenue@66: enabled = true, -- allow enabled consoles to appear Nenue@66: channels = { Nenue@66: { Nenue@66: index = 1, Nenue@66: signature = 'Main', Nenue@66: x = 100, y = 800, Nenue@66: height = 500, width = 600, Nenue@66: enabled = true Nenue@66: } Nenue@66: }, Nenue@66: loadouts = {}, Nenue@66: global = {}, Nenue@66: tags = {}, Nenue@66: char = { Nenue@66: [playerRealm] = {} Nenue@66: }, Nenue@66: unlisted = {} Nenue@66: } Nenue@66: end Nenue@66: Nenue@66: D.currentProfile = db.profiles[id] Nenue@66: currentProfile = D.currentProfile Nenue@66: currentProfile.char[playerRealm] = currentProfile.char[playerRealm] or {} Nenue@66: if currentProfile.workspace then Nenue@66: setprinthandler(D.Message) Nenue@66: else Nenue@66: print = function() end Nenue@66: end Nenue@66: Nenue@66: D.unlisted = currentProfile.unlisted Nenue@66: D.channels = currentProfile.channels Nenue@66: D.tags = currentProfile.tags Nenue@66: D.channelinfo = currentProfile.channels Nenue@66: D.char = currentProfile.char[playerRealm] Nenue@66: D.global = currentProfile.global Nenue@66: D.num_channels = currentProfile.num_channels Nenue@66: D.enabled = currentProfile.enabled Nenue@66: D.sig = {} Nenue@66: D.sigID = {} Nenue@66: D.IDsig = {} Nenue@66: D.dock = DevianDock Nenue@66: D.dock.buttons = D.dock.buttons or {} Nenue@66: Nenue@66: return id, name Nenue@66: end Nenue@66: Nenue@66: local targetGlobal, targetChar Nenue@66: D.Command = function (self, cmd, ...) Nenue@66: local list_id, scan_func, reload Nenue@14: local args = {} Nenue@14: local arg, n = D:GetArgs(cmd, 1) Nenue@14: while arg do Nenue@47: insert(args, arg) Nenue@14: arg, n = D:GetArgs(cmd,1,n) Nenue@14: end Nenue@14: local mode, tag, dest = unpack(args) Nenue@0: Nenue@14: -- no args, toggle ui Nenue@57: if mode == 'stack' then Nenue@14: return D:StackFrames() Nenue@14: elseif mode == 'grid' then Nenue@14: return D:DistributeFrames() Nenue@14: elseif mode == 'tag' then -- tagging Nenue@66: return D.Tag(self, tag, dest) Nenue@66: elseif tonumber(mode) ~= nil or mode == 'save' then Nenue@66: -- either a number of the save command Nenue@66: if mode == 'save' then Nenue@66: list_id = tonumber(tag) Nenue@66: if list_id == nil then Nenue@66: D:Print(L('Invalid ID from arg', tag)) Nenue@66: end Nenue@66: D.Profile(list_id, dest) Nenue@66: scan_func = D.Save Nenue@66: D:Print("Profile |cFFFFFF00".. list_id .."|r saved.") Nenue@66: else Nenue@32: Nenue@66: DevianLoadMessage = "Switched profiles." Nenue@36: Nenue@66: list_id = tonumber(mode) Nenue@66: if list_id ~= db.main_profile then Nenue@66: db.last_profile = list_id Nenue@18: end Nenue@66: db.current_profile = list_id Nenue@66: scan_func = D.Load Nenue@0: end Nenue@32: elseif mode == nil then Nenue@66: list_id = (db.current_profile ~= db.main_profile) and db.main_profile or db.last_profile Nenue@66: DevianLoadMessage = "Switched between main and recent profile ("..db.current_profile..' and '..list_id..')' Nenue@66: db.current_profile = list_id Nenue@66: scan_func = D.Load Nenue@32: else Nenue@32: return D:PrintHelp() Nenue@0: end Nenue@67: if not db.profiles[list_id].char[playerRealm] then Nenue@67: db.profiles[list_id].char[playerRealm] = {} Nenue@67: end Nenue@67: Nenue@66: targetGlobal = db.profiles[list_id].global Nenue@66: targetChar = db.profiles[list_id].char[playerRealm] Nenue@32: Nenue@67: Nenue@66: if scan_func then Nenue@66: for id, name, enableState, globalState in D.Addons() do Nenue@66: scan_func(id, name, enableState, globalState) Nenue@0: end Nenue@0: end Nenue@0: Nenue@66: if scan_func == D.Load then Nenue@0: ReloadUI() Nenue@66: end Nenue@66: D.Profile(db.current_profile) Nenue@66: end Nenue@66: Nenue@66: D.Addons = function() Nenue@66: local playername = UnitName("player") Nenue@66: return function(n, i) Nenue@66: if i >= n then Nenue@66: return nil Nenue@66: end Nenue@66: Nenue@66: i = i + 1 Nenue@66: local name = GetAddOnInfo(i) Nenue@66: local enableState, globalState = GetAddOnEnableState(playername, i), GetAddOnEnableState(nil, i) Nenue@66: return i, name, enableState, globalState Nenue@66: end, GetNumAddOns(), 0 Nenue@66: end Nenue@66: Nenue@66: D.Load = function(id, name, charState, globalState) Nenue@66: if targetChar[name] or targetGlobal[name] then Nenue@66: if targetGlobal[name] == 2 then Nenue@66: EnableAddOn(id, true) Nenue@66: elseif targetChar[name] == 2 then Nenue@66: EnableAddOn(id, playerName) Nenue@66: else Nenue@70: DisableAddOn(id) Nenue@66: end Nenue@70: --print('load', name, 'global =', targetGlobal[name], 'char =', targetChar[name]) Nenue@0: else Nenue@66: tinsert(D.unlisted, name) Nenue@66: end Nenue@66: end Nenue@66: D.Save = function(id, name, charState, globalState) Nenue@70: --print('save', id, name, charState, globalState) Nenue@66: targetGlobal[name] = globalState Nenue@66: targetChar[name] = charState Nenue@66: end Nenue@57: Nenue@66: D.Tag = function(self, tag, dest) Nenue@66: local sig Nenue@66: if tag ~= nil and dest ~= nil then Nenue@66: --@debug@ Nenue@66: --print(tag, dest) Nenue@66: --@end-debug@ Nenue@66: Nenue@66: -- convert to ID Nenue@66: if tonumber(dest) == nil then Nenue@66: if D.sigID[dest] then Nenue@66: dest = currentProfile.channels[D.sigID[dest]].index Nenue@66: else Nenue@66: sig = dest Nenue@66: end Nenue@57: else Nenue@66: dest = tonumber(dest) Nenue@32: end Nenue@66: --@debug@ Nenue@66: --print('2 tag,dest,sig', tag, dest, sig)--@end-debug@ Nenue@66: Nenue@66: -- make a new channel? Nenue@66: local channel Nenue@66: if not currentProfile.channels[dest] then Nenue@66: dest = D.max_channel + 1 Nenue@66: D:Print(L('New channel created', sig and (dest..':'..sig) or dest )) Nenue@66: channel = D:SetChannel(sig or tag,dest) Nenue@66: else Nenue@66: channel = D.channels[dest] Nenue@66: end Nenue@66: --@debug@ Nenue@66: --print('3 tag,dest,channel.sig=',tag, dest, channel.signature)--@end-debug@ Nenue@66: Nenue@66: if not currentProfile.tags[tag] then -- no tag table? Nenue@66: currentProfile.tags[tag] = {} Nenue@66: end Nenue@66: Nenue@66: if currentProfile.tags[tag][dest] then -- is tag set? Nenue@66: currentProfile.tags[tag][dest] = nil Nenue@66: D:Print(L('Tag removed from channel', tag, currentProfile.channels[dest].index, currentProfile.channels[dest].signature)) Nenue@66: else Nenue@66: currentProfile.tags[tag][dest] = dest Nenue@66: D:Print(L('Tag added to channel', tag, currentProfile.channels[dest].index, currentProfile.channels[dest].signature)) Nenue@66: end Nenue@66: D:UpdateDock() Nenue@66: else Nenue@66: D:Print(L['Command tag help']) Nenue@0: end Nenue@0: end Nenue@0: Nenue@0: Nenue@13: Nenue@18: --- Creates a Devian-style output. Nenue@18: -- The first argument describes the channel to output on, and the remaining arguments are concatenated in a manner similar to default print() Nenue@18: -- This becomes the print handler when development mode is active. The original print() function is assigned to oldprint(). Nenue@18: -- @param Tag, signature, or numeric index of the channel to output on. Defaults to primary channel. Nenue@18: -- @param ... Output contents. Nenue@66: function D.Message(prefix, ...) Nenue@67: if not currentProfile.workspace then Nenue@23: return D.oldprint(prefix, ...) Nenue@23: end Nenue@57: prefix = tostring(prefix) Nenue@57: if prefix == nil then Nenue@55: prefix = 'nil*' Nenue@1: end Nenue@9: Nenue@18: local sendq = {} Nenue@30: local tag, id, tagged Nenue@13: local byName = true Nenue@18: if D.tags[prefix] then Nenue@18: for _, id in pairs(D.tags[prefix]) do Nenue@18: if D.console[id] then Nenue@18: sendq[id] = D.console[id] Nenue@30: tagged = true Nenue@18: end Nenue@18: end Nenue@18: end Nenue@18: Nenue@13: if D.sig[prefix] then Nenue@18: sendq[D.sig[prefix].index] = D.sig[prefix] Nenue@13: elseif D.console[prefix] then Nenue@18: sendq[D.console[prefix]] = D.console[prefix] Nenue@30: elseif not tagged then Nenue@18: sendq[D.primary_channel] = D.console[D.primary_channel] Nenue@13: end Nenue@18: Nenue@9: -- color me timbers Nenue@9: local pcolor Nenue@18: if (not db.tagcolor[prefix]) and byName then Nenue@30: local c = { Nenue@47: rand(64,255), rand(64,255), rand(64,255) Nenue@30: } Nenue@30: if c[1] > 223 and c[2] > 223 and c[3] > 223 then Nenue@47: c[rand(1,3)] = rand(64,223) Nenue@0: end Nenue@30: Nenue@47: db.tagcolor[prefix] = format('%02X%02X%02X', unpack(c)) Nenue@0: end Nenue@18: pcolor = db.tagcolor[prefix] Nenue@0: Nenue@55: local buffer = {} Nenue@0: for i = 1, select('#',...) do Nenue@0: local var = select(i, ...) Nenue@0: Nenue@0: if type(var) == 'table' then Nenue@20: if type(var.GetName) == 'function' then Nenue@46: var = '' Nenue@20: else Nenue@32: var = '<'..tostring(var)..'>' Nenue@20: end Nenue@20: Nenue@0: elseif type(var) == 'boolean' then Nenue@0: var = var and 'true' or 'false' Nenue@0: elseif type(var) == 'function' then Nenue@0: var = '' Nenue@0: elseif type(var) == 'nil' then Nenue@0: var = 'nil' Nenue@60: else Nenue@60: var = tostring(var) Nenue@0: end Nenue@0: Nenue@47: insert(buffer, var) Nenue@0: end Nenue@47: local message = concat(buffer, ' ') Nenue@18: for id, channel in pairs(sendq) do Nenue@55: if channel.width < 250 then Nenue@55: prefix = string.sub(prefix, 0,2) Nenue@55: end Nenue@58: channel.out:AddMessage('|cFF'.. pcolor..prefix ..'|r ' .. message, 0.8, 0.8, 0.8, nil, nil, prefix, GetTime()) Nenue@58: if not D.dock.buttons[id].newMessage then Nenue@58: D.dock.buttons[id].newMessage = true Nenue@58: D.UpdateBeacon(D.dock.buttons[id]) Nenue@58: end Nenue@18: end Nenue@47: wipe(buffer) Nenue@0: end Nenue@0: Nenue@14: Nenue@32: Nenue@14: Nenue@32: function D:PrintHelp() Nenue@32: D:Print("|cFFFFFF00/dvn|r", Nenue@32: "\n |cFFFFFF00|r - Loads a saved addon list. List 1 is treated as a gameplay profile and consoles will be disabled by default.") Nenue@47: D:Print("|cFFFFFF00/dvc|r [, ...]", "- Hides and show consoles. A list of channel keys can be passed to specify which ones get toggled.") Nenue@32: Nenue@32: D:Print("|cFFFFFF00/resetdvn|r", "- Resets all but profile data SavedVariables.") Nenue@32: D:Print("|cFFFFFF00/cleandvn|r", "- Fully resets SavedVariables, profiles and all.") Nenue@32: end Nenue@32: Nenue@66: local blocked = {profiles = true, debugmode = true} Nenue@66: D.SetDefaults = function() Nenue@66: for k,v in pairs(DevianDB) do Nenue@66: if not blocked[k] then Nenue@66: DevianDB[k] = nil Nenue@60: end Nenue@60: end Nenue@66: for k,v in pairs(defaults) do Nenue@66: if not blocked[k] then Nenue@66: DevianDB[k] = v Nenue@60: end Nenue@60: end Nenue@66: DevianLoadMessage = "Non-user SavedVars have been reset." Nenue@66: ReloadUI() Nenue@60: end Nenue@66: D.SetDefaultsAll = function () Nenue@66: DevianDB = nil Nenue@66: DevianLoadMessage = "All SavedVars wiped." Nenue@66: ReloadUI() Nenue@66: end Nenue@70: D.ConfigCommand = function(self, input) Nenue@66: if not self.config then Nenue@66: self.config = DevCon Nenue@66: self:EnableModule("Config") Nenue@66: end Nenue@66: self.modules.Config:ChatCommand(input) Nenue@66: end Nenue@45: Nenue@0: function D:OnEnable() Nenue@33: Nenue@13: -- commands Nenue@13: local cmdlist = { Nenue@66: ['dvn'] = "Command", Nenue@66: ['devian'] = "Command", Nenue@36: ['dvc'] = "Console_Toggle", Nenue@66: ['cleandvn'] = "SetDefaultsAll", Nenue@66: ['resetdvn'] = "SetDefaults", Nenue@66: ['dvg'] = "ConfigCommand" Nenue@13: } Nenue@13: for cmd, func in pairs(cmdlist) do Nenue@13: self:RegisterChatCommand(cmd, func, true) Nenue@13: end Nenue@66: end Nenue@66: Nenue@66: function D:OnInitialize() Nenue@13: Nenue@33: -- pull defaults Nenue@9: if not _G.DevianDB then Nenue@13: _G.DevianDB = defaults Nenue@9: end Nenue@9: db = _G.DevianDB Nenue@47: self.db = db Nenue@0: Nenue@66: --- Nenue@66: if DevianLoadMessage then Nenue@66: D:Print(DevianLoadMessage) Nenue@66: DevianLoadMessage = nil Nenue@0: end Nenue@0: Nenue@66: Nenue@66: --- initialize the current profile Nenue@66: local id, name = D.Profile(db.current_profile or 1) Nenue@66: D:Print('Using profile |cFFFFFF00'.. id ..'|r: |cFF00FF00'..currentProfile.name..'|r') Nenue@66: if currentProfile.workspace then Nenue@66: D:Print('Workspace: '.. (#currentProfile.channels) .. ' channels, ' .. #currentProfile.tags .. ' tags.') Nenue@66: D:Print('Default channel: |cFFFFFF00'..currentProfile.default_channel..'|r: |cFF00FFFF'.. D.channels[currentProfile.default_channel].signature..'|r') Nenue@0: end Nenue@13: Nenue@66: for i, cinfo in pairs(D.channels) do Nenue@14: i = tonumber(i) Nenue@66: if not D.primary_channel then Nenue@66: D.primary_channel = i Nenue@14: end Nenue@66: D:SetChannel(cinfo, i) Nenue@66: D.num_channels = D.num_channels + 1 Nenue@32: end Nenue@32: Nenue@66: D.max_channel = max(D.max_channel, currentProfile.max_channel) Nenue@66: if currentProfile.max_channel < D.max_channel then Nenue@66: for i = currentProfile.max_channel, D.max_channel do Nenue@66: D.console[i]:Hide() Nenue@47: end Nenue@36: end Nenue@36: Nenue@66: if currentProfile.workspace then Nenue@66: if D.console[currentProfile.current_channel] then Nenue@69: --print('bringing', D.console[currentProfile.current_channel].signature, 'to the front') Nenue@66: D.console[currentProfile.current_channel]:ToFront() Nenue@66: -- bring the current channel to the front Nenue@47: end Nenue@66: DevianDock:Show() Nenue@66: D:UpdateDock() Nenue@36: end Nenue@66: end