From 89c8a90b950c2d33e896668a165fa9dd1acc12fa Mon Sep 17 00:00:00 2001 From: James Whitehead Ii Date: Sun, 18 Mar 2007 23:51:21 +0000 Subject: [PATCH] * Initial commit * Set Astrolabe as an external, may break on version changes --- Dongle.lua | 921 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ DongleFrames.lua | 165 ++++++++++ TomTom.lua | 309 ++++++++++++++++++ TomTom.toc | 9 + 4 files changed, 1404 insertions(+) create mode 100755 Dongle.lua create mode 100755 DongleFrames.lua create mode 100755 TomTom.lua create mode 100755 TomTom.toc diff --git a/Dongle.lua b/Dongle.lua new file mode 100755 index 0000000..417716b --- /dev/null +++ b/Dongle.lua @@ -0,0 +1,921 @@ +--[[------------------------------------------------------------------------- + Copyright (c) 2006, Dongle Development Team + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the Dongle Development Team nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +---------------------------------------------------------------------------]] +local major = "DongleStub-Beta0" +local minor = tonumber(string.match("$Revision: 221 $", "(%d+)") or 1) + +local g = getfenv(0) + +if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then + local lib = setmetatable({}, { + __call = function(t,k) + if type(t.versions) == "table" and t.versions[k] then + return t.versions[k].instance + else + error("Cannot find a library with name '"..tostring(k).."'", 2) + end + end + }) + + function lib:IsNewerVersion(major, minor) + local versionData = self.versions and self.versions[major] + + if not versionData then return true end + local oldmajor,oldminor = versionData.instance:GetVersion() + + return minor > oldminor + end + + local function NilCopyTable(src, dest) + for k,v in pairs(dest) do dest[k] = nil end + for k,v in pairs(src) do dest[k] = v end + end + + function lib:Register(newInstance, activate, deactivate) + local major,minor = newInstance:GetVersion() + if not self:IsNewerVersion(major, minor) then return false end + if not self.versions then self.versions = {} end + + local versionData = self.versions[major] + if not versionData then + -- New major version + versionData = { + ["instance"] = newInstance, + ["deactivate"] = deactivate, + } + + self.versions[major] = versionData + if type(activate) == "function" then + activate(newInstance) + end + return newInstance + end + + local oldDeactivate = versionData.deactivate + local oldInstance = versionData.instance + + versionData.deactivate = deactivate + + local skipCopy + if type(activate) == "function" then + skipCopy = activate(newInstance, oldInstance) + end + + -- Deactivate the old libary if necessary + if type(oldDeactivate) == "function" then + oldDeactivate(oldInstance, newInstance) + end + + -- Re-use the old table, and discard the new one + if not skipCopy then + NilCopyTable(newInstance, oldInstance) + end + return oldInstance + end + + function lib:GetVersion() return major,minor end + + local function Activate(new, old) + if old then + new.versions = old.versions + end + g.DongleStub = new + end + + -- Actually trigger libary activation here + local stub = g.DongleStub or lib + stub:Register(lib, Activate) +end + +--[[------------------------------------------------------------------------- + Begin Library Implementation +---------------------------------------------------------------------------]] + +local major = "Dongle-Beta0" +local minor = tonumber(string.match("$Revision: 239 $", "(%d+)") or 1) + +assert(DongleStub, string.format("Dongle requires DongleStub.", major)) +assert(DongleStub and DongleStub:GetVersion() == "DongleStub-Beta0", + string.format("Dongle requires DongleStub-Beta0. You are using an older version.", major)) + +if not DongleStub:IsNewerVersion(major, minor) then return end + +local Dongle = {} +local methods = { + "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", + "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage", + "EnableDebug", "Print", "PrintF", "Debug", "DebugF", + "InitializeDB", + "InitializeSlashCommand", + "NewModule", "HasModule", "IterateModules", +} + +local registry = {} +local lookup = {} +local loadqueue = {} +local loadorder = {} +local events = {} +local databases = {} +local commands = {} +local messages = {} + +local frame + +local function assert(level,condition,message) + if not condition then + error(message,level) + end +end + +local function argcheck(value, num, ...) + assert(1, type(num) == "number", + "Bad argument #2 to 'argcheck' (number expected, got " .. type(level) .. ")") + + for i=1,select("#", ...) do + if type(value) == select(i, ...) then return end + end + + local types = strjoin(", ", ...) + local name = string.match(debugstack(), "`argcheck'.-[`<](.-)['>]") or "Unknown" + error(string.format("bad argument #%d to '%s' (%s expected, got %s)", + num, name, types, type(value)), 3) +end + +local function safecall(func,...) + local success,err = pcall(func,...) + if not success then + geterrorhandler()(err) + end +end + +function Dongle:New(name, obj) + argcheck(name, 2, "string") + argcheck(obj, 3, "table", "nil") + + if not obj then + obj = {} + end + + if registry[name] then + error("A Dongle with the name '"..name.."' is already registered.") + end + + local reg = {["obj"] = obj, ["name"] = name} + + registry[name] = reg + lookup[obj] = reg + lookup[name] = reg + + for k,v in pairs(methods) do + obj[v] = self[v] + end + + -- Add this Dongle to the end of the queue + table.insert(loadqueue, obj) + return obj,name +end + +function Dongle:NewModule(name, obj) + local reg = lookup[self] + assert(3, reg, "You must call 'NewModule' from a registered Dongle.") + argcheck(name, 2, "string") + argcheck(obj, 3, "table", "nil") + + obj,name = Dongle:New(name, obj) + + if not reg.modules then reg.modules = {} end + reg.modules[obj] = obj + reg.modules[name] = obj + + return obj,name +end + +function Dongle:HasModule(module) + local reg = lookup[self] + assert(3, reg, "You must call 'HasModule' from a registered Dongle.") + argcheck(module, 2, "string", "table") + + return reg.modules[module] +end + +local function ModuleIterator(t, name) + if not t then return end + local obj + repeat + name,obj = next(t, name) + until type(name) == "string" or not name + + return name,obj +end + +function Dongle:IterateModules() + local reg = lookup[self] + assert(3, reg, "You must call 'IterateModules' from a registered Dongle.") + + return ModuleIterator, reg.modules +end + +local function ADDON_LOADED(event, ...) + for i=1, #loadqueue do + local obj = loadqueue[i] + table.insert(loadorder, obj) + + if type(obj.Initialize) == "function" then + safecall(obj.Initialize, obj) + end + + if Dongle.initialized and type(obj.Enable) == "function" then + safecall(obj.Enable, obj) + end + loadqueue[i] = nil + end +end + +local function PLAYER_LOGOUT(event) + self:ClearDBDefaults() + for k,v in pairs(registry) do + local obj = v.obj + if type(obj["Disable"]) == "function" then + safecall(obj["Disable"], obj) + end + end +end + +local function PLAYER_LOGIN() + Dongle.initialized = true + for i,obj in ipairs(loadorder) do + if type(obj.Enable) == "function" then + safecall(obj.Enable, obj) + end + end +end + +local function OnEvent(frame, event, ...) + local eventTbl = events[event] + if eventTbl then + for obj,func in pairs(eventTbl) do + if type(func) == "string" then + if type(obj[func]) == "function" then + safecall(obj[func], obj, event, ...) + end + else + safecall(func, event, ...) + end + end + end +end + +function Dongle:RegisterEvent(event, func) + local reg = lookup[self] + assert(3, reg, "You must call 'RegisterEvent' from a registered Dongle.") + argcheck(event, 2, "string") + argcheck(func, 3, "string", "function", "nil") + + -- Name the method the same as the event if necessary + if not func then func = event end + + if not events[event] then + events[event] = {} + frame:RegisterEvent(event) + end + events[event][self] = func +end + +function Dongle:UnregisterEvent(event) + local reg = lookup[self] + assert(3, reg, "You must call 'UnregisterEvent' from a registered Dongle.") + argcheck(event, 2, "string") + + local tbl = events[event] + if tbl then + tbl[self] = nil + if not next(tbl) then + events[event] = nil + frame:UnregisterEvent(event) + end + end +end + +function Dongle:UnregisterAllEvents() + assert(3, lookup[self], "You must call 'UnregisterAllEvents' from a registered Dongle.") + + for event,tbl in pairs(events) do + tbl[self] = nil + if not next(tbl) then + events[event] = nil + frame:UnregisterEvent(event) + end + end +end + +function Dongle:RegisterMessage(msg, func) + local reg = lookup[self] + assert(3, reg, "You must call 'RegisterMessage' from a registered Dongle.") + argcheck(msg, 2, "string") + argcheck(func, 3, "string", "function", "nil") + + -- Name the method the same as the event if necessary + if not func then func = msg end + + if not messages[msg] then + messages[msg] = {} + end + messages[msg][self] = func +end + +function Dongle:UnregisterMessage(msg) + local reg = lookup[self] + assert(3, reg, "You must call 'UnregisterMessage' from a registered Dongle.") + argcheck(msg, 2, "string") + + local tbl = messages[msg] + if tbl then + tbl[self] = nil + if not next(tbl) then + messages[msg] = nil + end + end +end + +function Dongle:UnregisterAllMessages() + assert(3, lookup[self], "You must call 'UnregisterAllMessages' from a registered Dongle.") + + for msg,tbl in pairs(messages) do + tbl[self] = nil + if not next(tbl) then + messages[msg] = nil + end + end +end + +function Dongle:TriggerMessage(msg, ...) + argcheck(msg, 2, "string") + local msgTbl = messages[msg] + if not msgTbl then return end + + for obj,func in pairs(msgTbl) do + if type(func) == "string" then + if type(obj[func]) == "function" then + safecall(obj[func], obj, msg, ...) + end + else + safecall(func, msg, ...) + end + end +end + +function Dongle:EnableDebug(level) + local reg = lookup[self] + assert(3, reg, "You must call 'EnableDebug' from a registered Dongle.") + argcheck(level, 2, "number", "nil") + + reg.debugLevel = level +end + +do + local function argsToStrings(a1, ...) + if select("#", ...) > 0 then + return tostring(a1), argsToStrings(...) + else + return tostring(a1) + end + end + + local function printHelp(obj, method, msg, ...) + local reg = lookup[obj] + assert(4, reg, "You must call '"..method.."' from a registered Dongle.") + + local name = reg.name + msg = "|cFF33FF99"..name.."|r: "..tostring(msg) + if select("#", ...) > 0 then + msg = string.join(", ", msg, argsToStrings(...)) + end + + ChatFrame1:AddMessage(msg) + end + + local function printFHelp(obj, method, msg, ...) + local reg = lookup[obj] + assert(4, reg, "You must call '"..method.."' from a registered Dongle.") + + local name = reg.name + local success,txt = pcall(string.format, "|cFF33FF99%s|r: "..msg, name, ...) + if success then + ChatFrame1:AddMessage(txt) + else + error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3) + end + end + + function Dongle:Print(...) + return printHelp(self, "Print", ...) + end + + function Dongle:PrintF(...) + return printFHelp(self, "PrintF", ...) + end + + function Dongle:Debug(level, ...) + local reg = lookup[self] + assert(3, reg, "You must call 'Debug' from a registered Dongle.") + argcheck(level, 2, "number") + + if reg.debugLevel and level <= reg.debugLevel then + printHelp(self, "Debug", ...) + end + end + + function Dongle:DebugF(level, ...) + local reg = lookup[self] + assert(3, reg, "You must call 'DebugF' from a registered Dongle.") + argcheck(level, 2, "number") + + if reg.debugLevel and level <= reg.debugLevel then + printFHelp(self, "DebugF", ...) + end + end +end + +local dbMethods = { + "RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile", + "ResetProfile", "ResetDB", +} + +local function initdb(parent, name, defaults, defaultProfile, olddb) + local sv = getglobal(name) + + if not sv then + sv = {} + setglobal(name, sv) + + -- Lets do the initial setup + + sv.char = {} + sv.faction = {} + sv.realm = {} + sv.class = {} + sv.global = {} + sv.profiles = {} + end + + -- Initialize the specific databases + local char = string.format("%s of %s", UnitName("player"), GetRealmName()) + local realm = string.format("%s", GetRealmName()) + local class = UnitClass("player") + local race = select(2, UnitRace("player")) + local faction = UnitFactionGroup("player") + + -- Initialize the containers + if not sv.char then sv.char = {} end + if not sv.realm then sv.realm = {} end + if not sv.class then sv.class = {} end + if not sv.faction then sv.faction = {} end + if not sv.global then sv.global = {} end + if not sv.profiles then sv.profiles = {} end + if not sv.profileKeys then sv.profileKeys = {} end + + -- Initialize this characters profiles + if not sv.char[char] then sv.char[char] = {} end + if not sv.realm[realm] then sv.realm[realm] = {} end + if not sv.class[class] then sv.class[class] = {} end + if not sv.faction[faction] then sv.faction[faction] = {} end + + -- Try to get the profile selected from the char db + local profileKey = sv.profileKeys[char] or defaultProfile or char + sv.profileKeys[char] = profileKey + + local profileCreated + if not sv.profiles[profileKey] then sv.profiles[profileKey] = {} profileCreated = true end + + if olddb then + for k,v in pairs(olddb) do olddb[k] = nil end + end + + local db = olddb or {} + db.char = sv.char[char] + db.realm = sv.realm[realm] + db.class = sv.class[class] + db.faction = sv.faction[faction] + db.profile = sv.profiles[profileKey] + db.global = sv.global + db.profiles = sv.profiles + + -- Copy methods locally + for idx,method in pairs(dbMethods) do + db[method] = Dongle[method] + end + + -- Set some properties in the object we're returning + db.sv = sv + db.sv_name = name + db.profileKey = profileKey + db.parent = parent + db.charKey = char + db.realmKey = realm + db.classKey = class + db.factionKey = faction + + databases[db] = true + + if defaults then + db:RegisterDefaults(defaults) + end + + return db,profileCreated +end + +function Dongle:InitializeDB(name, defaults, defaultProfile) + local reg = lookup[self] + assert(3, reg, "You must call 'InitializeDB' from a registered Dongle.") + argcheck(name, 2, "string") + argcheck(defaults, 3, "table", "nil") + argcheck(defaultProfile, 4, "string", "nil") + + local db,profileCreated = initdb(self, name, defaults, defaultProfile) + + if profileCreated then + Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, self, db.sv_name, db.profileKey) + end + return db +end + +local function copyDefaults(dest, src, force) + for k,v in pairs(src) do + if type(v) == "table" then + if not dest[k] then dest[k] = {} end + copyDefaults(dest[k], v, force) + else + if (dest[k] == nil) or force then + dest[k] = v + end + end + end +end + +-- This function operates on a Dongle DB object +function Dongle.RegisterDefaults(db, defaults) + assert(3, databases[db], "You must call 'RegisterDefaults' from a Dongle database object.") + argcheck(defaults, 2, "table") + + if defaults.char then copyDefaults(db.char, defaults.char) end + if defaults.realm then copyDefaults(db.realm, defaults.realm) end + if defaults.class then copyDefaults(db.class, defaults.class) end + if defaults.faction then copyDefaults(db.faction, defaults.faction) end + if defaults.global then copyDefaults(db.global, defaults.global) end + if defaults.profile then copyDefaults(db.profile, defaults.profile) end + + db.defaults = defaults +end + +local function removeDefaults(db, defaults) + if not db then return end + for k,v in pairs(defaults) do + if type(v) == "table" and db[k] then + removeDefaults(db[k], v) + if not next(db[k]) then + db[k] = nil + end + else + if db[k] == defaults[k] then + db[k] = nil + end + end + end +end + +function Dongle:ClearDBDefaults() + for db in pairs(databases) do + local defaults = db.defaults + local sv = db.sv + + if db and defaults then + if defaults.char then removeDefaults(db.char, defaults.char) end + if defaults.realm then removeDefaults(db.realm, defaults.realm) end + if defaults.class then removeDefaults(db.class, defaults.class) end + if defaults.faction then removeDefaults(db.faction, defaults.faction) end + if defaults.global then removeDefaults(db.global, defaults.global) end + if defaults.profile then + for k,v in pairs(sv.profiles) do + removeDefaults(sv.profiles[k], defaults.profile) + end + end + + -- Remove any blank "profiles" + if not next(db.char) then sv.char[db.charKey] = nil end + if not next(db.realm) then sv.realm[db.realmKey] = nil end + if not next(db.class) then sv.class[db.classKey] = nil end + if not next(db.faction) then sv.faction[db.factionKey] = nil end + if not next(db.global) then sv.global = nil end + end + end +end + +function Dongle.SetProfile(db, name) + assert(3, databases[db], "You must call 'SetProfile' from a Dongle database object.") + argcheck(name, 2, "string") + + local sv = db.sv + local old = sv.profiles[db.profileKey] + local new = sv.profiles[name] + local profileCreated + + if not new then + sv.profiles[name] = {} + new = sv.profiles[name] + profileCreated = true + end + + if db.defaults and db.defaults.profile then + -- Remove the defaults from the old profile + removeDefaults(old, db.defaults.profile) + + -- Inject the defaults into the new profile + copyDefaults(new, db.defaults.profile) + end + + db.profile = new + + -- Save this new profile name + sv.profileKeys[db.charKey] = name + db.profileKey = name + + if profileCreated then + Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, db.parent, db.sv_name, db.profileKey) + end + + Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.profileKey) +end + +function Dongle.GetProfiles(db, t) + assert(3, databases[db], "You must call 'GetProfiles' from a Dongle database object.") + argcheck(t, 2, "table", "nil") + + t = t or {} + local i = 1 + for profileKey in pairs(db.profiles) do + t[i] = profileKey + i = i + 1 + end + return t, i - 1 +end + +function Dongle.DeleteProfile(db, name) + assert(3, databases[db], "You must call 'DeleteProfile' from a Dongle database object.") + argcheck(name, 2, "string") + + if db.profileKey == name then + error("You cannot delete your active profile. Change profiles, then attempt to delete.", 2) + end + + db.sv.profiles[name] = nil + Dongle:TriggerMessage("DONGLE_PROFILE_DELETED", db, db.parent, db.sv_name, name) +end + +function Dongle.CopyProfile(db, name) + assert(3, databases[db], "You must call 'CopyProfile' from a Dongle database object.") + argcheck(name, 2, "string") + + assert(3, db.profileKey ~= name, "Source/Destination profile cannot be the same profile") + assert(3, type(db.sv.profiles[name]) == "table", "Profile \""..name.."\" doesn't exist.") + + local profile = db.profile + local source = db.sv.profiles[name] + + copyDefaults(profile, source, true) + Dongle:TriggerMessage("DONGLE_PROFILE_COPIED", db, db.parent, db.sv_name, name, db.profileKey) +end + +function Dongle.ResetProfile(db) + assert(3, databases[db], "You must call 'ResetProfile' from a Dongle database object.") + + local profile = db.profile + + for k,v in pairs(profile) do + profile[k] = nil + end + if db.defaults and db.defaults.profile then + copyDefaults(profile, db.defaults.profile) + end + Dongle:TriggerMessage("DONGLE_PROFILE_RESET", db, db.parent, db.sv_name, db.profileKey) +end + + +function Dongle.ResetDB(db, defaultProfile) + assert(3, databases[db], "You must call 'ResetDB' from a Dongle database object.") + argcheck(defaultProfile, 2, "nil", "string") + + local sv = db.sv + for k,v in pairs(sv) do + sv[k] = nil + end + + local parent = db.parent + + initdb(parent, db.sv_name, db.defaults, defaultProfile, db) + Dongle:TriggerMessage("DONGLE_DATABASE_RESET", db, parent, db.sv_name, db.profileKey) + Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, db.parent, db.sv_name, db.profileKey) + Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.profileKey) + return db +end + +local slashCmdMethods = { + "RegisterSlashHandler", + "PrintUsage", +} + +local function OnSlashCommand(cmd, cmd_line) + if cmd.patterns then + for pattern, tbl in pairs(cmd.patterns) do + if string.match(cmd_line, pattern) then + if type(tbl.handler) == "string" then + cmd.parent[tbl.handler](cmd.parent, string.match(cmd_line, pattern)) + else + tbl.handler(string.match(cmd_line, pattern)) + end + return + end + end + end + cmd:PrintUsage() +end + +function Dongle:InitializeSlashCommand(desc, name, ...) + local reg = lookup[self] + assert(3, reg, "You must call 'InitializeSlashCommand' from a registered Dongle.") + argcheck(desc, 2, "string") + argcheck(name, 3, "string") + argcheck(select(1, ...), 4, "string") + for i = 2,select("#", ...) do + argcheck(select(i, ...), i+2, "string") + end + + local cmd = {} + cmd.desc = desc + cmd.name = name + cmd.parent = self + cmd.slashes = { ... } + for idx,method in pairs(slashCmdMethods) do + cmd[method] = Dongle[method] + end + + local genv = getfenv(0) + + for i = 1,select("#", ...) do + genv["SLASH_"..name..tostring(i)] = "/"..select(i, ...) + end + + genv.SlashCmdList[name] = function(...) OnSlashCommand(cmd, ...) end + + commands[cmd] = true + + return cmd +end + +function Dongle.RegisterSlashHandler(cmd, desc, pattern, handler) + assert(3, commands[cmd], "You must call 'RegisterSlashHandler' from a Dongle slash command object.") + + argcheck(desc, 2, "string") + argcheck(pattern, 3, "string") + argcheck(handler, 4, "function", "string") + + if not cmd.patterns then + cmd.patterns = {} + end + cmd.patterns[pattern] = { + ["desc"] = desc, + ["handler"] = handler, + } +end + +function Dongle.PrintUsage(cmd) + assert(3, commands[cmd], "You must call 'PrintUsage' from a Dongle slash command object.") + + local usage = cmd.desc.."\n".."/"..table.concat(cmd.slashes, ", /")..":\n" + if cmd.patterns then + local descs = {} + for pattern,tbl in pairs(cmd.patterns) do + table.insert(descs, tbl.desc) + end + table.sort(descs) + for _,desc in pairs(descs) do + usage = usage.." - "..desc.."\n" + end + end + cmd.parent:Print(usage) +end + +--[[------------------------------------------------------------------------- + Begin DongleStub required functions and registration +---------------------------------------------------------------------------]] + +function Dongle:GetVersion() return major,minor end + +local function Activate(self, old) + if old then + self.registry = old.registry or registry + self.lookup = old.lookup or lookup + self.loadqueue = old.loadqueue or loadqueue + self.loadorder = old.loadorder or loadorder + self.events = old.events or events + self.databases = old.databases or databases + self.commands = old.commands or commands + self.messages = old.messages or messages + + registry = self.registry + lookup = self.lookup + loadqueue = self.loadqueue + loadorder = self.loadorder + events = self.events + databases = self.databases + commands = self.commands + messages = self.messages + + frame = old.frame + + local reg = self.registry[major] + reg.obj = self + lookup[self] = reg + lookup[major] = reg + else + self.registry = registry + self.lookup = lookup + self.loadqueue = loadqueue + self.loadorder = loadorder + self.events = events + self.databases = databases + self.commands = commands + self.messages = messages + + local reg = {obj = self, name = "Dongle"} + registry[major] = reg + lookup[self] = reg + lookup[major] = reg + end + + if not frame then + frame = CreateFrame("Frame") + end + + self.frame = frame + frame:SetScript("OnEvent", OnEvent) + + -- Register for events using Dongle itself + self:RegisterEvent("ADDON_LOADED", ADDON_LOADED) + self:RegisterEvent("PLAYER_LOGIN", PLAYER_LOGIN) + self:RegisterEvent("PLAYER_LOGOUT", PLAYER_LOGOUT) + + -- Convert all the modules handles + for name,obj in pairs(registry) do + for k,v in ipairs(methods) do + obj[k] = self[v] + end + end + + -- Convert all database methods + for db in pairs(databases) do + for idx,method in ipairs(dbMethods) do + db[method] = self[method] + end + end + + -- Convert all slash command methods + for cmd in pairs(commands) do + for idx,method in ipairs(slashCmdMethods) do + cmd[method] = self[method] + end + end +end + +local function Deactivate(self, new) + self:UnregisterAllEvents() + lookup[self] = nil +end + +DongleStub:Register(Dongle, Activate, Deactivate) diff --git a/DongleFrames.lua b/DongleFrames.lua new file mode 100755 index 0000000..9a27dd4 --- /dev/null +++ b/DongleFrames.lua @@ -0,0 +1,165 @@ +local major = "DongleFrames-1.0" +local minor = tonumber(string.match("$Revision: 249 $", "(%d+)") or 1) + +assert(DongleStub, string.format("%s requires DongleStub.", major)) +if not DongleStub:IsNewerVersion(major, minor) then return end + +--[[------------------------------------------------------------------------- + Library implementation +---------------------------------------------------------------------------]] + +local lib = {} + +-- Iterator written by Iriel taken from SecureStateHeader.lua +local function splitNext(sep, body) + if (body) then + local pre, post = string.split(sep, body, 2) + if (post) then + return post, pre + end + return false, body + end +end +local function commaIterator(str) return splitNext, ",", str end +local function semicolonIterator(str) return splitNext, ";", str end +local function poundIterator(str) return splitNext, "#", str end + +local function tonumberAll(...) + local n = select("#",...) + if n < 1 then return + elseif n == 1 then return tonumber(...) + else return tonumber((...)),tonumberAll(select(2,...)) end +end + +local handlers + +local function stripAttribute(name, attribs) + local pattern = "#"..name + if attribs:match(pattern.."#") or attribs:match(pattern.."$") then + attribs = attribs:gsub(pattern,"") + return nil,attribs + else + pattern = pattern.."=([^#]*)" + local value = attribs:match(pattern) + attribs = attribs:gsub(pattern,"") + return value,attribs + end +end + +local function parseBaseAttributes(attribs) + attribs = attribs:gsub("^#?","#") + + local name,attribs = stripAttribute("n",attribs) + local template,attribs = stripAttribute("inh",attribs) + local frameType,attribs = stripAttribute("t",attribs) + local parent,attribs = stripAttribute("p",attribs) + return frameType,parent,name,template,attribs +end + +local function parseHandlerString(frame,handler,value) + local t,method,regex = string.split(',',handler,3) + if regex then value = value:match(regex) end + if t == "number" then value = tonumber(value) + elseif t == "bool" then value = value == "true" and true or false + elseif t == "true" then value = true + elseif t == "false" then value = false + elseif t == "global" then value = getfenv(0)[value] + end + frame[method](frame,value) +end + +function lib:Create(parent,attributes,...) + local sp1 + if type(parent) ~= "table" then + sp1 = attributes + attributes = parent + parent = nil + end + if type(attributes) ~= "string" then return end + local objType,parent2,name,template,attributes = parseBaseAttributes(attributes) + parent = parent or parent2 + if type(parent) == "string" then + parent = getfenv(0)[parent] + end + + local obj + objType = objType and objType:lower() or "frame" + if objType == "texture" then + if not parent then return end -- TODO: Error here + obj = parent:CreateTexture(name,nil,template) + elseif objType == "fontstring" then + if not parent then return end -- TODO: Error here + obj = parent:CreateFontString(name,nil,template) + else + obj = CreateFrame(objType,name,parent,template) + end + + for _,section in poundIterator(attributes) do + local attribute,value = section:match("^([^=]+)=?(.*)$") + local handler = handlers[attribute] + if type(handler) == "function" then handler(obj,value) + elseif type(handler) == "string" then + for _,handlerString in poundIterator(handler) do + parseHandlerString(obj,handlerString,value) + end + end + end + + if sp1 then + obj:SetPoint(sp1,...) + elseif select("#",...) > 0 then + obj:SetPoint(...) + end + return obj +end + +handlers = { + size = "number,SetWidth,(%d+)%s*[%s,]%s*%d+#number,SetHeight,%d+%s*[%s,]%s*(%d+)", + w = "number,SetWidth", + h = "number,SetHeight", + movable = "true,SetMovable", + mouse = "true,EnableMouse", + hide = "true,Hide", + clamp = "true,SetClampedToScreen", + mousewheel = "true,EnableMouseWheel", + toplevel = "true,SetToplevel", + click = "string,RegisterForClicks", + drag = "string,RegisterForDrag", + strata = "string,SetFrameStrata", + level = "number,SetFrameLevel", + a = "number,SetAlpha", + id = "number,SetID", + scale = "number,SetScale", + layer = "string,SetDrawLayer", + text = "string,SetText", + normtex = "string,SetNormalTexture", + + sap = function(frame, value) + if value then value = getfenv(0)[value] + else value = frame:GetParent() end + if not value then return end + frame:SetAllPoints(value) + end, + tex = function(texture,value) + local r,g,b,a = value:match("^(%d?%.?%d*)%s*[,%s]%s*(%d?%.?%d*)%s*[,%s]%s*(%d?%.?%d*)$") + if not r then + r,g,b,a = value:match("^(%d?%.?%d*)%s*[,%s]%s*(%d?%.?%d*)%s*[,%s]%s*(%d?%.?%d*)%s*[,%s]%s*(%d?%.?%d*)$") + end + if r then + texture:SetTexture(tonumberAll(r,g,b),a and tonumber(a)) + else + texture:SetTexture(value) + end + end, + texc = function(texture,value) + texture:SetTexCoord(tonumberAll(strsplit(',',value))) + end, +} + +--[[------------------------------------------------------------------------- + Library implementation +---------------------------------------------------------------------------]] + +function lib:GetVersion() return major,minor end + +DongleStub:Register(lib) diff --git a/TomTom.lua b/TomTom.lua new file mode 100755 index 0000000..51f8dfb --- /dev/null +++ b/TomTom.lua @@ -0,0 +1,309 @@ +local L = { + TOOLTIP_TITLE = "TomTom"; + TOOLTIP_SUBTITLE = "Zone Coordinates"; + TOOLTIP_LOCKED = "This window is locked in place."; + TOOLTIP_LEFTCLICK = "Left-click and drag to move this window."; + TOOLTIP_RIGHTCLICK = "Right-click to toggle the options panel."; +} + +TomTom = DongleStub("Dongle-Beta0"):New("TomTom") +local DongleFrames = DongleStub("DongleFrames-1.0") +local Astrolabe = DongleStub("Astrolabe-0.3") +local profile + +function TomTom:Enable() + self.defaults = { + profile = { + show = true, + lock = false, + worldmap = true, + cursor = true, + alpha = 1, + notes = { + }, + } + } + + self.db = self:InitializeDB("TomTomDB", self.defaults) + profile = self.db.profile + + self:CreateSlashCommands() +end + +local OnMouseDown = function(self,button) + if button == "LeftButton" and not profile.lock then + self:StartMoving() + self.isMoving = true + end +end + +local OnMouseUp = function(self,button) + if self.isMoving then + self:StopMovingOrSizing() + self.isMoving = false + end +end + +local OnEnter = function(self) + if profile.tooltip then + GameTooltip:SetDefaultAnchor(GameTooltip, self) + GameTooltip:SetText(L["TOOLTIP_TITLE"]) + GameTooltip:AddLine(L["TOOLTIP_SUBTITLE"]) + if profile.lock then + GameTooltip:AddLine(L["TOOLTIP_LOCKED"]) + else + GameTooltip:AddLine(L["TOOLTIP_LEFTCLICK"]) + end + GameTooltipTextLeft1:SetTextColor(1,1,1); + GameTooltipTextLeft2:SetTextColor(1,1,1); + GameTooltip:Show() + end +end + +local OnLeave = function(self) + GameTooltip:Hide(); +end + +local OnUpdate = function(self, elapsed) + local c,z,x,y = Astrolabe:GetCurrentPlayerPosition() + + if not x or not y then + text = "---" + else + text = string.format("%.2f, %.2f", x*100, y*100) + end + + self.Text:SetText(text) +end + +local MinimapSize = { + indoor = { + [0] = 300, -- scale + [1] = 240, -- 1.25 + [2] = 180, -- 5/3 + [3] = 120, -- 2.5 + [4] = 80, -- 3.75 + [5] = 50, -- 6 + }, + outdoor = { + [0] = 466 + 2/3, -- scale + [1] = 400, -- 7/6 + [2] = 333 + 1/3, -- 1.4 + [3] = 266 + 2/6, -- 1.75 + [4] = 200, -- 7/3 + [5] = 133 + 1/3, -- 3.5 + }, +} + +local halfpi = math.pi / 2 + +local function updateAngle(icon) + local angle = Astrolabe:GetDirectionToIcon(icon) + + local x = .03875* math.cos(angle + halfpi) + 0.04875 + local y = .03875* math.sin(angle + halfpi) + 0.04875 + icon.arrow:SetPosition(x,y,0) + icon.arrow:SetFacing(angle) +end + +local function iconOnUpdate(icon, elapsed) + if not icon.arrow then return end + + local edge = Astrolabe:IsIconOnEdge(icon) + + if edge then + icon.arrow:Show() + icon.icon:Hide() + elseif icon.arrow:IsVisible() then + icon.arrow:Hide() + icon.icon:Show() + end +end + +function TomTom:GetMinimapIcon(desc) + self.mmicons = self.mmicons or {} + + local icon = table.remove(self.mmicons) + if not icon then + icon = CreateFrame("Button", nil, Minimap) + icon:SetHeight(12) + icon:SetWidth(12) + + local texture = icon:CreateTexture() + texture:SetTexture("Interface\\Minimap\\ObjectIcons") + texture:SetTexCoord(0.5, 0.75, 0, 0.25) + texture:SetAllPoints() + icon.icon = texture + icon:SetScript("OnUpdate", iconOnUpdate) + + local frame = CreateFrame("Model", nil, Minimap) + frame:SetHeight(140.8) + frame:SetWidth(140.8) + frame:SetPoint("CENTER", Minimap, "CENTER", 0, 0) + frame:SetModel("Interface\\Minimap\\Rotating-MinimapArrow.mdx") + frame:SetFogColor(0.9999977946281433,0.9999977946281433,0.9999977946281433,0.9999977946281433) + frame:SetFogFar(1) + frame:SetFogNear(0) + frame:SetLight(0,1,0,0,0,1,1,1,1,1,1,1,1) + frame:SetModelScale(.600000023841879) + icon.arrow = frame + local total = 0 + frame:SetScript("OnEnter", function() TomTom:Print("enter") end) + frame:SetScript("OnUpdate", function(self, elapsed) + -- Update arrow direction here + updateAngle(icon) + end) + + table.insert(self.points, icon) + return icon + else + return icon + end +end + +function TomTom:GetWorldMapIcon() + self.wmicons = self.wmicons or {} + + local icon = table.remove(self.wmicons) + if not icon then + icon = CreateFrame("Button", nil, WorldMapFrame) + icon:SetHeight(12) + icon:SetWidth(12) + local texture = icon:CreateTexture() + texture:SetTexture("Interface\\Minimap\\ObjectIcons") + texture:SetTexCoord(0.5, 0.75, 0, 0.25) + texture:SetAllPoints() + icon.icon = texture + icon:SetScript("OnUpdate", iconOnUpdate) + + --table.insert(self.points, icon) + return icon + else + return icon + end +end + +function TomTom:CreateSlashCommands() + -- Options slash commands + self.cmd = self:InitializeSlashCommand("TomTom Slash Command", "TOMTOM", "tomtom") + + local ToggleDisplay = function(origin) + if origin == "coord" and TomTomFrame then + profile.show = not profile.show + TomTomFrame[profile.show and "Show" or "Hide"](TomTomFrame) + self:PrintF("The coordinate display has been %s.", profile.show and "shown" or "hidden") + elseif origin == "mapcoord" and TomTomWorldFrame then + profile.worldmap = not profile.worldmap + TomTomWorldFrame[profile.worldmap and "Show" or "Hide"](TomTomWorldFrame) + self:PrintF("The world map coordinate display has been %s.", profile.worldmap and "shown" or "hidden") + end + end + + local LockDisplay = function() + profile.lock = not profile.lock + self:PrintF("The coordinate display has been %s.", profile.lock and "locked" or "unlocked") + end + + self.cmd:RegisterSlashHandler("Show/Hide the coordinate display", "^(coord)$", ToggleDisplay) + self.cmd:RegisterSlashHandler("Show/Hide the world map coordinate display", "^(mapcoord)$", ToggleDisplay) + self.cmd:RegisterSlashHandler("Lock/Unlock the coordinate display", "^lock$", LockDisplay) + + -- Waypoint placement slash commands + self.cmd_way = self:InitializeSlashCommand("TomTom - Waypoints", "TOMTOM_WAY", "way") + + local total = 0 + local Way_Update = function(frame,elapsed) + if total >= 0.2 then + total = total + elapsed + return + else + total = 0 + local c,z,x,y = Astrolabe:GetCurrentPlayerPosition() + local zone = self.points and self.points[c] and self.points[c][z] + if not zone then return end + + for idx,entry in ipairs(zone) do + -- Bail out if we're too high + local range = .001 + local xeq = x <= entry.x + range and x >= entry.x - range + local yeq = y <= entry.y + range and y >= entry.y - range + if xeq and yeq then + self:PrintF("You have reached your destination at %.2f, %.2f.", x * 100 , y * 100) + Astrolabe:RemoveIconFromMinimap(entry.icon) + table.insert(self.mmicons, icon) + table.remove(zone, idx) + frame:SetScript("OnUpdate", nil) + end + end + end + end + + local sortFunc = function(a,b) + if a.x == b.x then return a.y < b.y else return a.x < b.x end + end + + local Way_Set = function(x,y,desc) + if not self.points then self.points = {} end + + x = x / 100 + y = y / 100 + + local icon = self:GetMinimapIcon(desc) + local c,z = Astrolabe:GetCurrentPlayerPosition() + Astrolabe:PlaceIconOnMinimap(icon, c, z, x, y) + local icon = self:GetWorldMapIcon(desc) + Astrolabe:PlaceIconOnWorldMap(WorldMapFrame, icon, c, z, x, y) + + self:PrintF("Setting a waypoint at %.2f, %.2f.", x * 100, y * 100) + + self.points[c] = self.points[c] or {} + self.points[c][z] = self.points[c][z] or {} + + table.insert(self.points[c][z], {["x"] = x, ["y"] = y, ["icon"] = icon}) + table.sort(self.points[c][z], sortFunc) + self.frame:SetScript("OnUpdate", Way_Update) + end + + local Way_Reset = function() + if #self.points == 0 then + self:Print("There are no waypoints to remove.") + return + end + + for idx,icon in ipairs(self.points) do + Astrolabe:RemoveIconFromMinimap(icon) + icon:Hide() + end + self:Print("All waypoints have been removed.") + end + + self.cmd_way:RegisterSlashHandler("Remove all current waypoints", "^reset$", Way_Reset) + self.cmd_way:RegisterSlashHandler("Add a new waypoint with optional note", "^(%d*%.?%d*)[%s]+(%d*%.?%d*)[%s]*(.*)", Way_Set) +end + +function TomTom:CreateFrames() + DongleFrames:Create("n=TomTomFrame#p=UIParent#size=100,32#toplevel#strata=HIGH#mouse#movable#clamp", "CENTER", 0, 0) + TomTomFrame.Text = DongleFrames:Create("p=TomTomFrame#t=FontString#inh=GameFontNormal", "CENTER", 0, 0) + TomTomFrame:SetBackdrop({ + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + edgeSize = 16, + insets = {left = 4, right = 4, top = 4, bottom = 4}, + }) + TomTomFrame:SetBackdropColor(0,0,0,0.4) + TomTomFrame:SetBackdropBorderColor(1,0.8,0,0.8) + TomTomFrame:SetScript("OnMouseDown", OnMouseDown) + TomTomFrame:SetScript("OnMouseUp", OnMouseUp) + TomTomFrame:SetScript("OnHide", OnMouseUp) + TomTomFrame:SetScript("OnEnter", OnEnter) + TomTomFrame:SetScript("OnLeave", OnLeave) + TomTomFrame:SetScript("OnUpdate", OnUpdate) + + DongleFrames:Create("n=TomTomWorldFrame#p=WorldMapFrame") + TomTomWorldFrame.Text = DongleFrames:Create("p=TomTomWorldFrame#t=FontString#inh=GameFontHighlightSmall", "BOTTOM", WorldMapPositioningGuide, "BOTTOM", 0, 11) + TomTomWorldFrame:SetScript("OnUpdate", OnUpdate) + + self.frame = CreateFrame("Frame") +end + +TomTom:CreateFrames() diff --git a/TomTom.toc b/TomTom.toc new file mode 100755 index 0000000..f7e1a9c --- /dev/null +++ b/TomTom.toc @@ -0,0 +1,9 @@ +## Interface: 20003 +## Title: TomTom +## Notes: Acts as your portable navigation assistant +## SavedVariables: TomTomDB + +Dongle.lua +DongleFrames.lua +Astrolabe\Load.xml +TomTom.lua -- 1.7.9.5