Mercurial > wow > devian
view Devian.lua @ 111:5c591d9b4029 tip
Added tag v8.0.1-20181807-1 for changeset 930922e1ec5b
author | Nenue |
---|---|
date | Wed, 18 Jul 2018 15:02:50 -0400 |
parents | 7a36f5a92f0a |
children |
line wrap: on
line source
--- Devian - Devian.lua -- @file-author@ -- @project-revision@ @project-hash@ -- @file-revision@ @file-hash@ --GLOBALS: Devian, DevianLoadMessage, DEVIAN_WORKSPACE, DEVIAN_PNAME, DEVIAN_PID SLASH_RL1 = "/rl" SlashCmdList.RL = function() ReloadUI() end DEVIAN_WORKSPACE = false DEVIAN_PNAME = 'Dvn' DEVIAN_PID = 0 local ADDON, D = ... local MAJOR, MINOR = 'Devian-2.0', 'r@project-revision@' local D_INITIALIZED local next = next local sub, GetTime, print, _G = string.sub, GetTime, print, _G local format, setmetatable, getprinthandler, setprinthandler = string.format, setmetatable, getprinthandler, setprinthandler local tinsert, tremove, rawset = tinsert, tremove, rawset local currentProfile local playerName = UnitName("player") local playerRealm = playerName .. '-' .. GetRealmName() local num_dock_tabs = 0 local charStates ={} local channels_report = {} local registeredTags = {} DevianCore = {} function DevianCore:OnLoad () self:RegisterEvent('ADDON_LOADED') self:RegisterEvent('PLAYER_LOGIN') self:SetShown(true) end function DevianCore:OnEvent(event, arg) if event == 'ADDON_LOADED' or event == 'PLAYER_LOGIN' then --print(event, arg, DevianDB) if (arg == 'Devian') and not D_INITIALIZED then D_INITIALIZED = true self:Initialize() self:UnregisterAllEvents() end end end DevianLoadMessage = setmetatable({}, { __call = function(t, msg) rawset(t, #t+1, msg) end, __index = function(t) return #t end }) --@debug@ D.debugmode = true --@end-debug@ D.print = function(...) if currentProfile and not currentProfile.workspace then return nop end if D.debugmode then return print('Dvn', ...) else return nop end end local print = D.print D.L = setmetatable({}, { __index= function(t,k) return tostring(k) end, __call = function(t,k,...) return format((t[k]) , ...) end }) D.oldprint = getprinthandler() if not _G.oldprint then _G.oldprint = D.oldprint end local pairs, tostring, tonumber, ipairs, type = pairs, tostring, tonumber, ipairs, type local max, rand, format, print = max, math.random, string.format, print local insert, wipe, concat = table.insert, table.wipe, table.concat local select, unpack = select, unpack local GetNumAddOns, GetAddOnInfo, GetAddOnEnableState, EnableAddOn = GetNumAddOns, GetAddOnInfo, GetAddOnEnableState, EnableAddOn local UnitName, DisableAddOn = UnitName, DisableAddOn local db, L local defaults = { global = {{}, {}}, default_channel = { signature = 'Main', x = 100, y = -200, height = 500, width = 600, enabled = true}, current_profile = 1, main_profile = 1, last_profile = 1, profilesName = {}, profiles = { }, font = [[Interface\Addons\Devian\font\SourceCodePro-Regular.ttf]], -- font info fontsize = 13, fontoutline = 'NONE', headergrad = {'VERTICAL', 0, 0, 0, 1, 1, 0.1, 0.1, 1}, -- header info headerdrop = {1,1,1,1}, headerblend = 'BLEND', headeralpha = 1, headerfontcolor = {1,1,1,1}, backdrop = {1,1,1,1}, -- background frame info backgrad = {'VERTICAL', 0, 0, 0, .75, 0,0,0, .65}, backblend = 'BLEND', backalpha = 1, backborder = {.5,.5,.5,1}, backheader = {.25,.25,.25,1}, frontdrop = {1,1,1,1}, -- foreground frame info frontgrad = {'VERTICAL', 0, 0, 0, 1, 0,0,0, 0.95}, frontblend = 'BLEND', frontalpha = 1, frontborder = {.07,.47,1,1}, frontheader = {1,1,1,1}, tagcolor = {}, -- tag color repository workspace = 2, -- current profile last_workspace = 2, -- default workspace to alternate with when just "/dvn" is issued dock_onshow_fade_time = 2.5, dock_onshow_fade_from = 1, dock_onshow_fade_to = 0.2, dock_alpha_on = 1, dock_alpha_off = 0.2, dock_fade_in = 0.15, dock_fade_out = 0.45, dock_button_alpha_on = 1, dock_button_alpha_off = 0.2, dock_button_fade_in = 0.075, dock_button_fade_out = 0.075, movement_fade = true, movement_fade_time = 0.15, movement_fade_from = 1, movement_fade_to = 0, movement_translation_x = 25, movement_translation_y = 25, } D.console = {} D.max_channel = 0 D.InWorkspace = function () return db.profiles[db.current_profile].workspace end local profileTemplate = { name = function(id, name) return name end, workspace = function(id, name) return (id ~= 1) end, current_channel = 1, default_channel = 1, num_channels = 1, max_channel = 1, -- the highest created channel id enabled = true, -- allow enabled consoles to appear channels = { { index = 1, signature = 'Main', x = 100, y = -200, height = 500, width = 600, enabled = true } }, loadouts = {}, global = {}, tags = {}, char = { [playerRealm] = {} }, unlisted = {} } --- Applies complex template tables -- If he value is a function, then it will invoke f(...) and use whatever gets returned function D.DeepCopy(src, dest, ...) for k,v in pairs(src) do if not dest[k] then --oldprint('Rebuilding conf value', k) if type(v) == 'table' then dest[k] = {} D.DeepCopy(v, dest[k], ...) else if type(v) == 'function' then v = v(...) end dest[k] = v end end end end D.FixProfile = function(forced) local numChannels = 0 local minChannel = 400 local sortedChannels = {} local sortedTags = {} local maxChannel = 0 for k,v in pairs(currentProfile.channels) do numChannels = numChannels + 1 maxChannel = max(tonumber(k), maxChannel) minChannel = min(tonumber(k), minChannel) tinsert(sortedChannels, v) end if (maxChannel > numChannels) or forced then oldprint('fixing channels data') table.sort(sortedChannels, function(a,b) return (b.index > a.index) end) for i, info in ipairs(sortedChannels) do info.tags = info.tags or {} for tag, tagSet in pairs(currentProfile.tags) do for _, index in pairs(tagSet) do if index == info.index then sortedTags[tag] = sortedTags[tag] or {} sortedTags[tag][i] = i tinsert(info.tags, tag) end end end print('Set tags:', table.concat(info.tags, ', ')) info.index = i end currentProfile.channels = sortedChannels currentProfile.tags = sortedTags else minChannel = 2 end currentProfile.lastUpdateFix = MINOR end D.Profile = function (id, name) if name and not id and db.profilesName[name] then id = db.profilesName[name] print('ID located by name, |cFF00FF00'..name..'|r is |cFFFFFF00'.. id..'|r') end if not id or not db.profiles[id] then if not id then id = #db.profiles+1 print('Generated profile ID: |cFFFFFF00'.. id .. '|r') end if not name or db.profilesName[name] then local newName = name or (id == 1 and 'Main' or 'Profile') local prefix = newName local i = 2 while db.profilesName[newName] do i = i + 1 newName = prefix .. i end name = newName print('Generated profile name: |cFF00FF00'..newName..'|r') end print('Creating profile') db.profilesName[name] = id db.profiles[id] = {} end D.currentProfile = db.profiles[id] currentProfile = D.currentProfile D.DeepCopy(profileTemplate, currentProfile, id, name) currentProfile.char[playerRealm] = currentProfile.char[playerRealm] or {} if currentProfile.workspace then DEVIAN_WORKSPACE = true DEVIAN_PNAME = currentProfile.name DEVIAN_PID = id setprinthandler(D.Message) else DEVIAN_WORKSPACE = false DEVIAN_PNAME = nil print = nop end DEVIAN_PID =id -- Attempt to fix bad data --@debug@ MINOR = 70100 --@end-debug@ if (currentProfile.lastUpdateFix or 0) < MINOR then D.FixProfile(true) end D.unlisted = currentProfile.unlisted D.channels = currentProfile.channels D.tags = currentProfile.tags D.channelinfo = currentProfile.channels D.char = currentProfile.char[playerRealm] D.global = currentProfile.global D.num_channels = currentProfile.num_channels D.enabled = currentProfile.enabled D.sig = {} D.sigID = {} D.IDsig = {} D.dock = _G.DevianDock D.dock.buttons = D.dock.buttons or {} return id, name end local targetGlobal, targetChar D.Command = function (cmd) local list_id, scan_func, reload local args = {} if cmd then local i, j = 0, 0 repeat i, j = cmd:find("%S+", j+1) if i and j then tinsert(args, cmd:sub(i, j)) end until not(i or j) end local mode, tag, dest = unpack(args) -- no args, toggle ui if mode == 'rc' then return D:ResetChannels(tag) elseif mode == 'stack' then return D:StackFrames() elseif mode == 'grid' then return D:DistributeFrames() elseif mode == 'tag' then -- tagging return D:Tag(tag, dest) elseif mode == 'new' then return D:New(tag) elseif mode == 'dock' then D.db.dockPoint = tag return D:UpdateDock() elseif mode == 'remove' then return D:Remove(tag) elseif mode ~= nil then -- profile selector or save command if mode == 'save' then list_id = tonumber(tag) else list_id = tonumber(mode) end if not list_id then if db.profilesName[tostring(list_id)] then list_id = db.profilesName[tostring(list_id)] else D:Print(L('Unable to resolve profile ID/name', list_id, dest)) return end end if mode == 'save' then D.Profile(list_id, dest) scan_func = D.Save local name = currentProfile.name if dest then dest = dest:gsub("$%s+", ''):gsub("%s+^", '') if dest then if name then db.profilesName[name] = nil end db.profiles[list_id].name = dest db.profilesName[dest] = list_id name = dest end end D:Print("Profile |cFFFFFF00".. list_id .."|r:|cFF00FFFF".. name .."|r saved.") else if db.profiles[list_id] then D.LoadMessage ("Switched profiles.") if list_id ~= db.main_profile then db.last_profile = list_id end db.current_profile = list_id scan_func = D.Load else return D:PrintHelp() end end elseif mode == nil then list_id = (db.current_profile ~= db.main_profile) and db.main_profile or db.last_profile D.LoadMessage ("Switched between main and recent profile ("..db.current_profile..' and '..list_id..')') db.current_profile = list_id scan_func = D.Load else return D:PrintHelp() end if not db.profiles[list_id] then db.profiles[list_id] = {global = {}, char = {} } D.LoadMessage ("Starting profile #|cFF00FFFF".. list_id..'|r') end if not db.profiles[list_id].char[playerRealm] then db.profiles[list_id].char[playerRealm] = {} end targetGlobal = db.profiles[list_id].global targetChar = db.profiles[list_id].char[playerRealm] if scan_func then wipe(charStates) for id, name, enableState, globalState in D.Addons() do scan_func(id, name, enableState, globalState) end end if scan_func == D.Load then _G.ReloadUI() if AddonList_Update then AddonList_Update() end elseif (scan_func == D.Save) then print('reckoning') local updated = {} for addon, newState in pairs(charStates) do for character, addons in pairs(db.profiles[list_id].char) do if addons[addon] then print(addon, addons[addon], '::', newState) if (addons[addon] ~= newState) then addons[addon] = newState updated[character] = (updated[character] or 0) + 1 end end end end for character, numAddons in pairs(updated) do print(character, numAddons, 'settings') end end D.Profile(db.current_profile) end D.Addons = function() local playername = UnitName("player") return function(n, i) if i >= n then return nil end i = i + 1 local name = GetAddOnInfo(i) local enableState, globalState = GetAddOnEnableState(playername, i), GetAddOnEnableState(nil, i) return i, name, enableState, globalState end, GetNumAddOns(), 0 end D.Load = function(id, name, charState, globalState) print('load', tostring(name), tostring(charState), tostring(globalState)) if targetGlobal[name] == 2 then EnableAddOn(id, true) elseif targetChar[name] == 2 then EnableAddOn(id, playerName) elseif targetGlobal[name] == 0 then DisableAddOn(id, true) elseif targetChar[name] == 0 then DisableAddOn(id, playerName) end if not (targetChar[name] or targetGlobal[name]) then tinsert(D.unlisted, name) end end D.Save = function(id, name, charState, globalState) if (charState ~= 0) or (globalState ~= 0) then print('save', id, name, playerRealm .. ': '.. charState, 'All: '.. globalState) end targetGlobal[name] = globalState targetChar[name] = charState -- if enabling/disabling globally if globalState ~= 1 then charStates[name] = globalState end end D.UpdateTags = function() wipe(registeredTags) for index, channel in ipairs(D.channels) do for _, tag in ipairs(channel.tags) do registeredTags[tag] = registeredTags[tag] or {} tinsert(registeredTags[tag], D.console[index]) end end end D.Tag = function(self, tag, id) local sig if tag and id then --@debug@ --print(tag, dest) --@end-debug@ -- convert to ID local channel, sig if tonumber(id) == nil then sig = id if D.sigID[id] then id = D.sigID[id] channel = D.channels[id] end else id = tonumber(id) channel = D.channels[id] end -- if channel is still nil, create one if not channel then id = #D.channels + 1 D:Print(L('New channel created', (sig and (id..':'..sig)) or id)) channel = D:GetOrCreateChannel(id, sig) else sig = channel.signature end --@debug@ --print('3 tag,dest,channel.sig=',tag, dest, channel.signature)--@end-debug@ if not currentProfile.tags[tag] then -- no tag table? currentProfile.tags[tag] = {} end local existingTag = tContains(channel.tags, tag) if existingTag then -- is tag set? for i, tag in ipairs(channel.tags) do if tag == tag then tremove(channel.tags, i) D:Print(L('Tag removed from channel', tag, channel.index, channel.signature)) break end end else tinsert(channel.tags, tag) D:Print(L('Tag added to channel', tag, channel.index, channel.signature)) end D.UpdateTags() DevianDock:Update() else D:Print(L['Command tag help']) end end D.ResetChannels = function(self, profile) currentProfile.current_channel = 1 currentProfile.primary_channel = 1 currentProfile.channels = {} D.DeepCopy(profileTemplate.channels, currentProfile.channels) currentProfile.tags = {} D.LoadMessage('Profile reset.') ReloadUI() end D.New = function(self, tag) if tag and not self.sigID[tag] then local id = D.max_channel + 1 D.SetChannel(tag, id) end end D.Remove = function(self, dest) dest = D.sigID[dest] or tonumber(dest) if D.console[dest] and D.channels[dest] then for tag, tagDest in pairs(D.tags) do for i = #tagDest, 0 do -- work downward so we aren't skipping entries if tagDest[i] == dest then tremove(tagDest, i) end end end D.console[dest]:Hide() D.channels[dest] = nil tremove(D.console, dest) D.dock.buttons[dest]:SetShown(false) D.dock:Update() D:Print('Removed channel #'..dest) end end --- Queue up a message to appear after UI reload function D.LoadMessage(msg) tinsert(_G.DevianLoadMessage, msg) end --- Creates a Devian-style output. -- The first argument describes the channel to output on, and the remaining arguments are concatenated in a manner similar to default print() -- This becomes the print handler when development mode is active. The original print() function is assigned to oldprint(). -- @param Tag, signature, or numeric index of the channel to output on. Defaults to primary channel. -- @param ... Output contents. local default_sendq = {} function D.Message(prefix, ...) if not currentProfile.workspace then return D.oldprint(prefix, ...) end local print = D.oldprint prefix = tostring(prefix) if prefix == nil then prefix = 'nil*' end local sendq = default_sendq local tag, id, tagged local byName = true if registeredTags[prefix] then sendq = registeredTags[prefix] else if D.sig[prefix] then sendq[D.sig[prefix].index] = D.sig[prefix] elseif not tagged then sendq[1] = D.console[D.primary_channel] end end -- color me timbers local pcolor if (not db.tagcolor[prefix]) and byName then -- numbers, use white if prefix:match('^%d+%.%d+') then pcolor = 'FFFFFF' else local c = { rand(64,255), rand(64,255), rand(64,255) } if c[1] > 223 and c[2] > 223 and c[3] > 223 then c[rand(1,3)] = rand(64,223) end db.tagcolor[prefix] = format('%02X%02X%02X', unpack(c)) pcolor = db.tagcolor[prefix] end else pcolor = db.tagcolor[prefix] end local buffer = {} for i = 1, select('#',...) do local var = select(i, ...) if type(var) == 'table' then if type(var.GetName) == 'function' then var = '[table:'..tostring(var:GetName())..']' else var = '<'..tostring(var)..'>' end elseif type(var) == 'boolean' then var = var and 'true' or 'false' elseif type(var) == 'function' then var = '['..tostring(var)..']' elseif type(var) == 'nil' then var = 'nil' else var = tostring(var) end insert(buffer, var) end local message = concat(buffer, ' ') for id, channel in pairs(sendq) do if channel.width < 250 then prefix = sub(prefix, 0,2) end channel.out:AddMessage('|cFF'.. pcolor..prefix ..'|r ' .. message, 0.8, 0.8, 0.8) if not channel.newMessage then channel.newMessage = true if channel.Dock then channel.Dock:Update() end end end wipe(buffer) end function D:PrintHelp() D:Print("|cFFFFFF00/dvn|r", "\n |cFFFFFF00<number>|r - Loads a saved addon list. List 1 is treated as a gameplay profile and consoles will be disabled by default.") D:Print("|cFFFFFF00/dvc|r [<key>, ...]", "- Hides and show consoles. A list of channel keys can be passed to specify which ones get toggled.") D:Print("|cFFFFFF00/resetdvn|r", "- Resets all but profile data SavedVariables.") D:Print("|cFFFFFF00/cleandvn|r", "- Fully resets SavedVariables, profiles and all.") end local blocked = {profiles = true, debugmode = true} D.SetDefaults = function() local DevianDB = _G.DevianDB for k,v in pairs(DevianDB) do if not blocked[k] then DevianDB[k] = nil end end for k,v in pairs(defaults) do if not blocked[k] then DevianDB[k] = v end end D.LoadMessage "Non-user SavedVars have been reset." _G.ReloadUI() end D.SetDefaultsAll = function () _G.DevianDB = nil D.LoadMessage "All SavedVars wiped." _G.ReloadUI() end D.UnsetColors = function() db.tagcolor = {} D:Print('Tag color cache cleared.') end function D.ConsoleCommand (cmd) DevianDock:ToggleAll() end function DevianCore:Initialize() L = D.L -- pull defaults if not DevianDB then DevianDB = defaults end D.db = _G.DevianDB db = _G.DevianDB --- if #_G.DevianLoadMessage >= 1 then for i, msg in ipairs(_G.DevianLoadMessage) do D:Print(msg) end table.wipe(_G.DevianLoadMessage) end -- commands local cmdlist = { ['dvn'] = "Command", ['devian'] = "Command", ['dvc'] = "ConsoleCommand", ['dvncolors'] = "UnsetColors", ['cleandvn'] = "SetDefaultsAll", ['resetdvn'] = "SetDefaults", } for cmd, func in pairs(cmdlist) do local CMD = cmd:upper() _G['SLASH_' .. CMD .. '1'] = "/"..cmd if type(func == 'string') then --print('SLASH_' .. CMD .. '1','/'.. cmd, func) SlashCmdList[CMD] = D[func] else --print('SLASH_' .. CMD .. '1','/'.. cmd, func) SlashCmdList[CMD] = func end end --- initialize the current profile local id, name = D.Profile(db.current_profile or 1) if currentProfile.workspace then tinsert(channels_report, 'Profile |cFFFFFF00'.. id ..'|r: |cFF00FF00'..currentProfile.name.. '|r') if D.channels[currentProfile.default_channel] then tinsert(channels_report, 'Primary: |cFFFFFF00#'..currentProfile.default_channel..'|r |cFF00FFFF'.. D.channels[currentProfile.default_channel].signature..'|r') end end for index, cinfo in ipairs(D.channels) do --oldprint(index, cinfo.signature) if not D.primary_channel then D.primary_channel = index end D:GetOrCreateChannel(index) D.num_channels = #D.channels end if #channels_report >= 1 then D:Print(concat(channels_report, ', ')) end D.primary_channel = D.primary_channel or 1 D.max_channel = max(D.max_channel, currentProfile.max_channel) if currentProfile.max_channel < D.max_channel then for i = currentProfile.max_channel, D.max_channel do D.console[i]:Hide() end end D.UpdateTags() if currentProfile.workspace then if D.console[currentProfile.current_channel] then --print('bringing', D.console[currentProfile.current_channel].signature, 'to the front') D.console[currentProfile.current_channel]:ToFront() -- bring the current channel to the front end DevianDock:Update() end end function D:Print (...) local msg = '|cFF00FF44Devian|r:' for i = 1, select('#', ...) do msg = msg .. ' ' .. tostring(select(i, ...)) end DEFAULT_CHAT_FRAME:AddMessage(msg) end function D:GetActiveChannel() return D.console[currentProfile.current_channel] end function D:GetOrCreateChannel(id, name) id = id or (#D.channels + 1) local info = D.channels[id] if not info then --print('new channel') name = name or ('Channel ' .. id) info = { index = id, signature = name, tags = {} } D.DeepCopy(defaults.default_channel, info) D.channels[id] = info elseif not info.tags then -- fix old data? info.tags = {info.signature} oldprint(D.db) for tag, tagSet in pairs(D.tags) do for _, index in pairs(tagSet) do if index == id then tinsert(info.tags, tag) end end end end local frame = D.console[id] if not frame then if DEVIAN_WORKSPACE then tinsert(channels_report, (info.index) .. ':' .. (info.signature) .. ' (|cFF00FFFF'.. concat(info.tags, '|r; |cFF00FFFF')..'|r)') end frame = CreateFrame('Frame', 'DevianConsole'..id, Devian, 'DevianConsoleTemplate') frame:SetID(id) D.console[id] = frame D.sigID[info.signature] = id end frame:Setup(info) return frame end