diff --git a/Bindings.xml b/Bindings.xml
new file mode 100644
index 0000000..5029676
--- /dev/null
+++ b/Bindings.xml
@@ -0,0 +1,17 @@
+
+
+ WowRadioBinding_Toggle();
+
+
+ WowRadioBinding_PlayStop();
+
+
+ WowRadioBinding_Next();
+
+
+ WowRadioBinding_Prev();
+
+
+ WowRadioBinding_Random();
+
+
diff --git a/Core.lua b/Core.lua
new file mode 100644
index 0000000..561e9ca
--- /dev/null
+++ b/Core.lua
@@ -0,0 +1,1417 @@
+BINDING_HEADER_WOWRADIO = "WowRadio"
+BINDING_NAME_WOWRADIO_TOGGLE = "Open Radio Window"
+BINDING_NAME_WOWRADIO_PLAYSTOP = "Play / Stop"
+BINDING_NAME_WOWRADIO_NEXT = "Next Station"
+BINDING_NAME_WOWRADIO_PREV = "Previous Station"
+BINDING_NAME_WOWRADIO_RANDOM = "Random Station"
+
+function WowRadioBinding_Toggle()
+ if WowRadio then WowRadio:ToggleUI() end
+end
+
+function WowRadioBinding_PlayStop()
+ if WowRadio then
+ if WowRadio:isStopped() then WowRadio:play() else WowRadio:stop() end
+ end
+end
+
+function WowRadioBinding_Next()
+ if WowRadio then WowRadio:next() end
+end
+
+function WowRadioBinding_Prev()
+ if WowRadio then WowRadio:prev() end
+end
+
+function WowRadioBinding_Random()
+ if WowRadio then WowRadio:rnd() end
+end
+
+local L = AceLibrary("AceLocale-2.2"):new("WowRadio")
+local version = "0.7i-nowplaying-bottom"
+local customUrl = nil
+local stopped = false
+
+WowRadio = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceConsole-2.0", "AceDB-2.0")
+
+-----------------------------------------------------------------
+-- Slash Commands
+-----------------------------------------------------------------
+
+-- Main command now opens/closes the UI.
+WowRadio:RegisterChatCommand({ "/wowradio", "/wr" }, {
+ type = 'execute',
+ name = "ui",
+ desc = "",
+ func = "ToggleUI",
+}, "WOWRADIO00")
+
+-- Old help/usage command preserved.
+WowRadio:RegisterChatCommand({ "/wrhelp", "/wrusage" }, {
+ type = 'execute',
+ name = "usage",
+ desc = "",
+ func = "usage",
+}, "WOWRADIOHELP")
+
+WowRadio:RegisterChatCommand({"/wrtune"}, {
+ type = 'text',
+ name = "tune",
+ desc = "",
+ usage = "",
+ get = false,
+ set = "setStation",
+}, "WOWRADIO01")
+
+WowRadio:RegisterChatCommand({"/wrplay"}, {
+ type = 'execute',
+ name = "play",
+ desc = "",
+ func = "play",
+}, "WOWRADIO02")
+
+WowRadio:RegisterChatCommand({"/wrstop"}, {
+ type = 'execute',
+ name = "stop",
+ desc = "",
+ func = "stop",
+}, "WOWRADIO03")
+
+WowRadio:RegisterChatCommand({"/wrnext"}, {
+ type = 'execute',
+ name = "next",
+ desc = "",
+ func = "next",
+}, "WOWRADIO04")
+
+WowRadio:RegisterChatCommand({"/wrprev"}, {
+ type = 'execute',
+ name = "prev",
+ desc = "",
+ func = "prev",
+}, "WOWRADIO05")
+
+WowRadio:RegisterChatCommand({"/wrrnd"}, {
+ type = 'execute',
+ name = "rnd",
+ desc = "",
+ func = "rnd",
+}, "WOWRADIO06")
+
+WowRadio:RegisterChatCommand({"/wrlist"}, {
+ type = 'execute',
+ name = "list",
+ desc = "",
+ func = "list",
+}, "WOWRADIO07")
+
+WowRadio:RegisterChatCommand({"/wrindex", "/wri"}, {
+ type = 'execute',
+ name = "index",
+ desc = "",
+ func = "index",
+}, "WOWRADIO11")
+
+WowRadio:RegisterChatCommand({"/wrfull"}, {
+ type = 'execute',
+ name = "full",
+ desc = "",
+ func = "listFull",
+}, "WOWRADIO12")
+
+WowRadio:RegisterChatCommand({"/wrfind", "/wrsearch"}, {
+ type = 'text',
+ name = "find",
+ desc = "",
+ usage = "",
+ get = false,
+ set = "findStation",
+}, "WOWRADIO13")
+
+WowRadio:RegisterChatCommand({"/wrurl"}, {
+ type = 'execute',
+ name = "url",
+ desc = "",
+ func = "currentUrl",
+}, "WOWRADIO14")
+
+WowRadio:RegisterChatCommand({"/wrinfo"}, {
+ type = 'execute',
+ name = "info",
+ desc = "",
+ func = "info",
+}, "WOWRADIO08")
+
+WowRadio:RegisterChatCommand({"/wrauto"}, {
+ type = 'execute',
+ name = "autostart",
+ desc = "",
+ func = "toggleAutostart",
+}, "WOWRADIO09")
+
+WowRadio:RegisterChatCommand({"/wrcustom"}, {
+ type = 'text',
+ name = "custom",
+ desc = "",
+ usage = "",
+ get = false,
+ set = "url",
+}, "WOWRADIO10")
+
+-- Explicit UI aliases.
+WowRadio:RegisterChatCommand({"/wrgui", "/wrui"}, {
+ type = 'execute',
+ name = "gui",
+ desc = "",
+ func = "ToggleUI",
+}, "WOWRADIO15")
+
+-----------------------------------------------------------------
+-- Database
+-----------------------------------------------------------------
+
+WowRadio:RegisterDB("WowRadioDB","WowRadioDBPC")
+
+WowRadio:RegisterDefaults("account", {
+ autostart = false,
+ station = 1,
+ fadeOnMove = false,
+
+ ui = {
+ shown = false,
+ tab = "ALL",
+ page = 1,
+ scale = 1.0,
+ framePoint = "CENTER",
+ frameRelPoint = "CENTER",
+ frameX = 0,
+ frameY = 0,
+ },
+
+ favorites = {},
+
+ minimap = {
+ point = "TOPLEFT",
+ relPoint = "TOPLEFT",
+ x = -8,
+ y = -8,
+ },
+})
+
+-----------------------------------------------------------------
+-- Lifecycle
+-----------------------------------------------------------------
+
+function WowRadio:OnEnable()
+ WowRadio:EnsureUIDefaults()
+ WowRadio:CreateMinimapButton()
+ WowRadio:CreateController()
+
+ if WowRadio.db.account.ui.shown == true then
+ WowRadio:ShowController()
+ end
+
+ if WowRadio:isAutostart() == true then
+ WowRadio:play()
+ end
+
+ self:Print(L["msg_enabled"])
+end
+
+-----------------------------------------------------------------
+-- Core Radio Functions
+-----------------------------------------------------------------
+
+function WowRadio:setStation(arg)
+ local station = tonumber(arg)
+
+ if station == nil then
+ wr_msg("invalid argument")
+ return
+ end
+
+ WowRadio:play(station)
+end
+
+function WowRadio:getStation()
+ return tonumber(self.db.account.station)
+end
+
+function WowRadio:toggleAutostart()
+ self.db.account.autostart = not self.db.account.autostart
+
+ if self.db.account.autostart == true then
+ wr_msg("|cff7FFF7FWowRadio: "..L["wrauto_enabled"]..":|r")
+ else
+ wr_msg("|cff7FFF7FWowRadio: "..L["wrauto_disabled"]..":|r")
+ end
+
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:isAutostart()
+ return self.db.account.autostart
+end
+
+function WowRadio:info()
+ if customUrl == nil then
+ wr_alert("["..WowRadio:getStation().."] "..stationMsg[WowRadio:getStation()])
+ else
+ wr_alert("[custom] "..customUrl)
+ end
+end
+
+function WowRadio:play(station)
+ if station == nil then
+ station = WowRadio:getStation()
+ elseif station > table.getn(stationUrl) then
+ station = 1
+ elseif station < 1 then
+ station = table.getn(stationUrl)
+ end
+
+ wr_alert("["..station.."] "..stationMsg[station])
+ PlayMusic(stationUrl[station])
+
+ self.db.account.station = station
+ stopped = false
+ customUrl = nil
+
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:stop()
+ StopMusic()
+ stopped = true
+ wr_alert("Music stopped.")
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:next()
+ local station = WowRadio:getStation() + 1
+ WowRadio:play(station)
+end
+
+function WowRadio:prev()
+ local station = WowRadio:getStation() - 1
+ WowRadio:play(station)
+end
+
+function WowRadio:rnd()
+ WowRadio:play(math.random(table.getn(stationUrl)))
+end
+
+function WowRadio:url(url)
+ if url == nil or url == "" then
+ wr_msg("Usage: /wrcustom URL")
+ return
+ end
+
+ wr_alert(url)
+ customUrl = url
+ PlayMusic(url)
+ stopped = false
+
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:usage()
+ wr_msg("|cff7FFF7FWowRadio "..version.." "..L["usage"]..":|r")
+ wr_msg("|cffFFFF00/wr - |r|cffFFFFFFopen/close the radio controller|r")
+ wr_msg("|cffFFFF00/wrhelp - |r|cffFFFFFFshow this help text|r")
+ wr_msg("|cffFFFF00/wrtune # - |r|cffFFFFFF"..L["wrtune"].."|r")
+ wr_msg("|cffFFFF00/wrplay - |r|cffFFFFFF"..L["wrplay"].."|r")
+ wr_msg("|cffFFFF00/wrstop - |r|cffFFFFFF"..L["wrstop"].."|r")
+ wr_msg("|cffFFFF00/wrnext - |r|cffFFFFFF"..L["wrnext"].."|r")
+ wr_msg("|cffFFFF00/wrprev - |r|cffFFFFFF"..L["wrprev"].."|r")
+ wr_msg("|cffFFFF00/wrrnd - |r|cffFFFFFF"..L["wrrnd"].."|r")
+ wr_msg("|cffFFFF00/wrlist - |r|cffFFFFFFcompact station index, 3 per row|r")
+ wr_msg("|cffFFFF00/wrindex or /wri - |r|cffFFFFFFcompact station index, 3 per row|r")
+ wr_msg("|cffFFFF00/wrfull - |r|cffFFFFFFfull station list, one per row|r")
+ wr_msg("|cffFFFF00/wrfind text - |r|cffFFFFFFsearch stations by name, description, or URL|r")
+ wr_msg("|cffFFFF00/wrurl - |r|cffFFFFFFshow the current station URL|r")
+ wr_msg("|cffFFFF00/wrinfo - |r|cffFFFFFF"..L["wrinfo"].."|r")
+ wr_msg("|cffFFFF00/wrauto - |r|cffFFFFFF"..L["wrauto"].."|r")
+ wr_msg("|cffFFFF00/wrcustom URL - |r|cffFFFFFF"..L["wrcustom"].."|r")
+end
+
+-----------------------------------------------------------------
+-- Existing List/Search Helpers
+-----------------------------------------------------------------
+
+function WowRadio:list()
+ WowRadio:index()
+end
+
+function WowRadio:index()
+ local perRow = 3
+ local colWidth = 36
+ local line = ""
+ local total = table.getn(stationUrl)
+
+ wr_msg("|cff7FFF7FWowRadio stations ("..total..") - use /wrtune #|r")
+
+ for index = 1, total do
+ local entry = "["..index.."] "..WowRadio:getStationShortName(index)
+ line = line..wr_pad(entry, colWidth)
+
+ if math.mod(index, perRow) == 0 then
+ wr_msg(line)
+ line = ""
+ end
+ end
+
+ if line ~= "" then
+ wr_msg(line)
+ end
+end
+
+function WowRadio:listFull()
+ for index,value in ipairs(stationMsg) do
+ wr_msg("["..index.."] "..value)
+ end
+end
+
+function WowRadio:findStation(arg)
+ local query = string.lower(arg or "")
+ local found = 0
+
+ if query == "" then
+ wr_msg("Usage: /wrfind text")
+ return
+ end
+
+ wr_msg("|cff7FFF7FWowRadio search: "..arg.."|r")
+
+ for index = 1, table.getn(stationUrl) do
+ local name = WowRadio:getStationShortName(index)
+ local msg = stationMsg[index] or ""
+ local url = stationUrl[index] or ""
+ local haystack = string.lower(name.." "..msg.." "..url)
+
+ if string.find(haystack, query, 1, true) then
+ found = found + 1
+ wr_msg("["..index.."] "..name)
+ end
+ end
+
+ if found == 0 then
+ wr_msg("No stations found.")
+ end
+end
+
+function WowRadio:currentUrl()
+ if customUrl == nil then
+ local station = WowRadio:getStation()
+ wr_msg("["..station.."] "..WowRadio:getStationShortName(station))
+ wr_msg(stationUrl[station])
+ else
+ wr_msg("[custom] "..customUrl)
+ end
+end
+
+function WowRadio:getStationShortName(index)
+ local msg = stationMsg[index] or ""
+ local pos = string.find(msg, " %- ")
+
+ if pos then
+ return string.sub(msg, 1, pos - 1)
+ end
+
+ return msg
+end
+
+-----------------------------------------------------------------
+-- Generic Helpers
+-----------------------------------------------------------------
+
+function wr_alert(txt)
+ if IsAddOnLoaded("SCT") then
+ SCT:DisplayMessage(txt,{r=1,g=1,b=1})
+ wr_msg(txt)
+ else
+ UIErrorsFrame:AddMessage(txt,1, 1, 1, 1,4)
+ wr_msg(txt)
+ end
+end
+
+function wr_msg(txt)
+ DEFAULT_CHAT_FRAME:AddMessage(txt)
+end
+
+function wr_pad(txt, width)
+ local len = string.len(txt)
+
+ if len > width then
+ return string.sub(txt, 1, width - 1).."~"
+ end
+
+ return txt..string.rep(" ", width - len)
+end
+
+function wr_trim(txt, width)
+ if txt == nil then
+ return ""
+ end
+
+ if string.len(txt) > width then
+ return string.sub(txt, 1, width - 3).."..."
+ end
+
+ return txt
+end
+
+function WowRadio:getStationSize()
+ return table.getn(stationUrl)
+end
+
+function WowRadio:getStationNames()
+ return stationMsg
+end
+
+function WowRadio:getCustomUrl()
+ return customUrl
+end
+
+function WowRadio:isStopped()
+ return stopped
+end
+
+-----------------------------------------------------------------
+-- WowRadio GUI Layer
+-- Vanilla / 1.12.x compatible controller UI
+-----------------------------------------------------------------
+
+local WR_LINES_PER_PAGE = 8
+local WR_FRAME_WIDTH = 560
+local WR_FRAME_HEIGHT = 430
+local WR_BACKDROP_TEXTURE_LEFT = "Interface\\AddOns\\WowRadio-Vanilla\\bg_left"
+local WR_BACKDROP_TEXTURE_RIGHT = "Interface\\AddOns\\WowRadio-Vanilla\\bg_right"
+
+local WR_TABS = {
+ { key = "ALL", label = "All", width = 45 },
+ { key = "FAV", label = "Fav", width = 45 },
+ { key = "GAME", label = "Game", width = 55 },
+ { key = "TALK", label = "Talk", width = 55 },
+ { key = "ROCK", label = "Rock", width = 55 },
+ { key = "ELECTRONIC", label = "Electronic", width = 75 },
+ { key = "JAZZ", label = "Jazz", width = 55 },
+ { key = "VARIETY", label = "Various", width = 70 },
+}
+
+function WowRadio:EnsureUIDefaults()
+ if self.db.account.ui == nil then
+ self.db.account.ui = {}
+ end
+
+ if self.db.account.ui.shown == nil then
+ self.db.account.ui.shown = false
+ end
+
+ if self.db.account.ui.tab == nil then
+ self.db.account.ui.tab = "ALL"
+ end
+
+ -- Old saved states from prior versions should not point at removed panels.
+ if self.db.account.ui.tab == "CUSTOM" or self.db.account.ui.tab == "TEST" then
+ self.db.account.ui.tab = "ALL"
+ end
+
+ if self.db.account.ui.page == nil then
+ self.db.account.ui.page = 1
+ end
+
+ if self.db.account.favorites == nil then
+ self.db.account.favorites = {}
+ end
+
+ if self.db.account.minimap == nil then
+ self.db.account.minimap = {}
+ end
+
+ if self.db.account.minimap.point == nil then
+ self.db.account.minimap.point = "TOPLEFT"
+ end
+
+ if self.db.account.minimap.relPoint == nil then
+ self.db.account.minimap.relPoint = "TOPLEFT"
+ end
+
+ if self.db.account.minimap.x == nil then
+ self.db.account.minimap.x = -8
+ end
+
+ if self.db.account.minimap.y == nil then
+ self.db.account.minimap.y = -8
+ end
+
+ if self.db.account.volume == nil then
+ self.db.account.volume = WowRadio:ReadCurrentMusicVolume()
+ end
+
+ if self.db.account.fadeOnMove == nil then
+ self.db.account.fadeOnMove = false
+ end
+end
+
+function WowRadio:ShowCustomUrlDialog()
+ if not WowRadioUrlDialog then return end
+ WowRadioUrlDialog:ClearAllPoints()
+ WowRadioUrlDialog:SetPoint("CENTER", UIParent, "CENTER")
+ WowRadioUrlEditBox:SetText(customUrl or "")
+ WowRadioUrlDialog:Show()
+ WowRadioUrlEditBox:SetFocus()
+end
+
+function WowRadio:ToggleFadeOnMove()
+ self.db.account.fadeOnMove = not self.db.account.fadeOnMove
+
+ if WowRadioFrame and not self.db.account.fadeOnMove then
+ WowRadioFrame:SetAlpha(1)
+ end
+
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:SafeGetCVar(name)
+ if not GetCVar then
+ return nil
+ end
+
+ local ok, value = pcall(GetCVar, name)
+
+ if ok then
+ return value
+ end
+
+ return nil
+end
+
+function WowRadio:SafeSetCVar(name, value)
+ if not SetCVar then
+ return false
+ end
+
+ local ok = pcall(SetCVar, name, tostring(value))
+ return ok
+end
+
+function WowRadio:ReadCurrentMusicVolume()
+ local v = nil
+
+ -- Vanilla/1.12 commonly uses MusicVolume. Later clients use Sound_MusicVolume.
+ -- GetCVar can hard-error on unknown names in 1.12, so every lookup is wrapped.
+ v = tonumber(WowRadio:SafeGetCVar("MusicVolume"))
+
+ if v == nil then
+ v = tonumber(WowRadio:SafeGetCVar("Sound_MusicVolume"))
+ end
+
+ if v == nil then
+ v = tonumber(WowRadio:SafeGetCVar("SoundVolume"))
+ end
+
+ if v == nil then
+ v = 1
+ end
+
+ if v < 0 then
+ v = 0
+ elseif v > 1 then
+ v = 1
+ end
+
+ return v
+end
+
+function WowRadio:GetVolume()
+ local v = tonumber(self.db.account.volume)
+
+ if v == nil then
+ v = WowRadio:ReadCurrentMusicVolume()
+ end
+
+ if v < 0 then
+ v = 0
+ elseif v > 1 then
+ v = 1
+ end
+
+ return v
+end
+
+function WowRadio:ApplyVolume(value, quiet)
+ local v = tonumber(value)
+
+ if v == nil then
+ v = 1
+ end
+
+ if v < 0 then
+ v = 0
+ elseif v > 1 then
+ v = 1
+ end
+
+ self.db.account.volume = v
+
+ -- Vanilla/1.12 commonly uses EnableMusic + MusicVolume.
+ -- Later clients may use Sound_EnableMusic + Sound_MusicVolume.
+ WowRadio:SafeSetCVar("EnableMusic", "1")
+ WowRadio:SafeSetCVar("Sound_EnableMusic", "1")
+ WowRadio:SafeSetCVar("MusicVolume", v)
+ WowRadio:SafeSetCVar("Sound_MusicVolume", v)
+
+ if WowRadioFrame and WowRadioFrame.volumeValueText then
+ WowRadioFrame.volumeValueText:SetText(math.floor((v * 100) + 0.5).."%")
+ end
+
+ if quiet ~= true then
+ wr_msg("Music volume: "..math.floor((v * 100) + 0.5).."%")
+ end
+end
+
+function WowRadio:ToggleUI()
+ if WowRadioFrame and WowRadioFrame:IsVisible() then
+ WowRadio:HideController()
+ else
+ WowRadio:ShowController()
+ end
+end
+
+function WowRadio:ShowController()
+ if not WowRadioFrame then
+ WowRadio:CreateController()
+ end
+
+ WowRadioFrame:Show()
+ self.db.account.ui.shown = true
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:HideController()
+ if WowRadioFrame then
+ WowRadioFrame:Hide()
+ end
+
+ self.db.account.ui.shown = false
+end
+
+function WowRadio:CreateMinimapButton()
+ if WowRadioMinimapButton then
+ return
+ end
+
+ local button = CreateFrame("Button", "WowRadioMinimapButton", Minimap)
+ button:SetWidth(31)
+ button:SetHeight(31)
+ button:SetFrameStrata("MEDIUM")
+ button:SetFrameLevel(4)
+ button:EnableMouse(true)
+ button:SetMovable(true)
+ button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
+ button:RegisterForDrag("LeftButton")
+ button:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
+
+ local icon = button:CreateTexture(nil, "BACKGROUND")
+ icon:SetTexture("Interface\\Icons\\INV_Misc_EngGizmos_13")
+ icon:SetTexCoord(0.05, 0.95, 0.05, 0.95)
+ icon:SetWidth(20)
+ icon:SetHeight(20)
+ icon:SetPoint("TOPLEFT", button, "TOPLEFT", 7, -5)
+
+ local border = button:CreateTexture(nil, "OVERLAY")
+ border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
+ border:SetWidth(53)
+ border:SetHeight(53)
+ border:SetPoint("TOPLEFT", button, "TOPLEFT")
+
+ local mm = self.db.account.minimap
+ button:ClearAllPoints()
+ button:SetPoint(mm.point, Minimap, mm.relPoint, mm.x, mm.y)
+
+ button:SetScript("OnClick", function()
+ if arg1 == "RightButton" then
+ if WowRadio:isStopped() then
+ WowRadio:play()
+ else
+ WowRadio:stop()
+ end
+ else
+ WowRadio:ToggleUI()
+ end
+ end)
+
+ button:SetScript("OnDragStart", function()
+ if IsShiftKeyDown() then
+ this:StartMoving()
+ end
+ end)
+
+ button:SetScript("OnDragStop", function()
+ this:StopMovingOrSizing()
+
+ local point, relativeTo, relPoint, x, y = this:GetPoint(1)
+
+ WowRadio.db.account.minimap.point = point
+ WowRadio.db.account.minimap.relPoint = relPoint
+ WowRadio.db.account.minimap.x = x
+ WowRadio.db.account.minimap.y = y
+ end)
+
+ button:SetScript("OnEnter", function()
+ GameTooltip:SetOwner(this, "ANCHOR_LEFT")
+ GameTooltip:SetText("|cff7FFF7FWowRadio|r")
+ GameTooltip:AddLine("Left-click: open controller", 1, 1, 1)
+ GameTooltip:AddLine("Right-click: stop / resume", 1, 1, 1)
+ GameTooltip:AddLine("Shift-drag: reposition", 1, 1, 1)
+ GameTooltip:Show()
+ end)
+
+ button:SetScript("OnLeave", function()
+ GameTooltip:Hide()
+ end)
+end
+
+function WowRadio:CreateController()
+ if WowRadioFrame then
+ return
+ end
+
+ local f = CreateFrame("Frame", "WowRadioFrame", UIParent)
+ f:SetWidth(WR_FRAME_WIDTH)
+ f:SetHeight(WR_FRAME_HEIGHT)
+ local savedScale = WowRadio.db.account.ui.scale or 1.0
+ f:SetScale(savedScale)
+ local uiPos = WowRadio.db.account.ui
+ f:SetPoint(uiPos.framePoint or "CENTER", UIParent, uiPos.frameRelPoint or "CENTER", uiPos.frameX or 0, uiPos.frameY or 0)
+ f:SetFrameStrata("DIALOG")
+ f:EnableMouse(true)
+ f:SetMovable(true)
+ f:RegisterForDrag("LeftButton")
+ f:SetBackdrop({
+ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
+ tile = true,
+ tileSize = 16,
+ edgeSize = 16,
+ insets = { left = 5, right = 5, top = 5, bottom = 5 }
+ })
+
+ local bgLeft = f:CreateTexture(nil, "BACKGROUND")
+ bgLeft:SetTexture(WR_BACKDROP_TEXTURE_LEFT)
+ bgLeft:SetPoint("TOPLEFT", f, "TOPLEFT", 5, -5)
+ bgLeft:SetPoint("BOTTOMRIGHT", f, "BOTTOM", 0, 5)
+ bgLeft:SetTexCoord(0, 1, 0, 1)
+ f.bgTextureLeft = bgLeft
+
+ local bgRight = f:CreateTexture(nil, "BACKGROUND")
+ bgRight:SetTexture(WR_BACKDROP_TEXTURE_RIGHT)
+ bgRight:SetPoint("TOPLEFT", f, "TOP", 0, -5)
+ bgRight:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -5, 5)
+ bgRight:SetTexCoord(0, 1, 0, 1)
+ f.bgTextureRight = bgRight
+
+ f:Hide()
+
+ f:SetScript("OnDragStart", function()
+ this:StartMoving()
+ end)
+
+ f:SetScript("OnDragStop", function()
+ this:StopMovingOrSizing()
+ local pt, _, rpt, x, y = this:GetPoint(1)
+ WowRadio.db.account.ui.framePoint = pt
+ WowRadio.db.account.ui.frameRelPoint = rpt
+ WowRadio.db.account.ui.frameX = x
+ WowRadio.db.account.ui.frameY = y
+ end)
+
+ -- Title/banner is part of the custom backdrop image now.
+ -- No code-created title bar here.
+
+ local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
+ if close.SetFrameLevel then
+ close:SetFrameLevel(f:GetFrameLevel() + 20)
+ end
+ close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -4)
+ close:SetScript("OnClick", function()
+ WowRadio:HideController()
+ end)
+
+ local fadeBtn = WowRadio:CreateWRButton(f, "Fade", 8, -6, 50, 18, function()
+ WowRadio:ToggleFadeOnMove()
+ end)
+ if fadeBtn.SetFrameLevel then
+ fadeBtn:SetFrameLevel(f:GetFrameLevel() + 20)
+ end
+ f.fadeButton = fadeBtn
+
+ local sizer = CreateFrame("Button", nil, f)
+ sizer:SetWidth(16)
+ sizer:SetHeight(16)
+ sizer:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
+ if sizer.SetFrameLevel then
+ sizer:SetFrameLevel(f:GetFrameLevel() + 20)
+ end
+ sizer:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
+ sizer:SetHighlightTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Highlight")
+ sizer:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Down")
+ local sizerDragging = false
+ local sizerStartX, sizerStartScale
+
+ sizer:SetScript("OnMouseDown", function()
+ sizerDragging = true
+ sizerStartX = GetCursorPosition()
+ sizerStartScale = WowRadioFrame:GetScale()
+ end)
+
+ sizer:SetScript("OnMouseUp", function()
+ sizerDragging = false
+ WowRadio.db.account.ui.scale = WowRadioFrame:GetScale()
+ end)
+
+ sizer:SetScript("OnUpdate", function()
+ if not sizerDragging then return end
+ local cx = GetCursorPosition()
+ local newScale = sizerStartScale + (cx - sizerStartX) * 0.002
+ if newScale < 0.5 then newScale = 0.5 end
+ if newScale > 2.0 then newScale = 2.0 end
+ WowRadioFrame:SetScale(newScale)
+ end)
+
+ local wasMoving = false
+ local lastMX, lastMY = 0, 0
+ local moveCheckTimer = 0
+ local moveWatcher = CreateFrame("Frame", nil, UIParent)
+ moveWatcher:SetScript("OnUpdate", function()
+ moveCheckTimer = moveCheckTimer + arg1
+ if moveCheckTimer < 0.1 then return end
+ moveCheckTimer = 0
+
+ if not WowRadioFrame or not WowRadioFrame:IsVisible() then return end
+ if not WowRadio.db.account.fadeOnMove then
+ if wasMoving then
+ WowRadioFrame:SetAlpha(1)
+ wasMoving = false
+ end
+ return
+ end
+ local mx, my = GetPlayerMapPosition("player")
+ if mx == 0 and my == 0 then return end
+ local moving = (mx ~= lastMX or my ~= lastMY)
+ lastMX, lastMY = mx, my
+ if moving and not wasMoving then
+ WowRadioFrame:SetAlpha(0.25)
+ wasMoving = true
+ elseif not moving and wasMoving then
+ WowRadioFrame:SetAlpha(1)
+ wasMoving = false
+ end
+ end)
+ moveWatcher:Show()
+ f.moveWatcher = moveWatcher
+
+ -- Now-playing display.
+ -- Moved to the lower panel area so it does not fight the image title.
+ local nowBack = f:CreateTexture(nil, "ARTWORK")
+ nowBack:SetTexture(0, 0, 0, 0.82)
+ nowBack:SetPoint("TOPLEFT", f, "TOPLEFT", 20, -364)
+ nowBack:SetPoint("BOTTOMRIGHT", f, "TOPRIGHT", -20, -386)
+ f.nowBack = nowBack
+
+ local nowStatus = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+ nowStatus:SetPoint("TOPLEFT", f, "TOPLEFT", 34, -369)
+ nowStatus:SetWidth(72)
+ nowStatus:SetJustifyH("LEFT")
+ nowStatus:SetText("|cffffffffStopped:|r")
+ f.nowStatusText = nowStatus
+
+ local nowStation = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
+ nowStation:SetPoint("LEFT", nowStatus, "RIGHT", 4, 0)
+ nowStation:SetWidth(320)
+ nowStation:SetJustifyH("LEFT")
+ nowStation:SetText("|cff00ff00[1] Everlook Broadcasting Co.|r")
+ f.nowStationText = nowStation
+
+ -- Legacy reference retained so older calls do not hard-error.
+ f.nowText = nowStation
+
+ -- Main controls.
+ f.prevButton = WowRadio:CreateWRButton(f, "<<", 205, -100, 46, 20, function()
+ WowRadio:prev()
+ end)
+
+ f.playButton = WowRadio:CreateWRButton(f, ">", 254, -100, 46, 20, function()
+ WowRadio:play()
+ end)
+
+ f.stopButton = WowRadio:CreateWRButton(f, "[ ]", 303, -100, 46, 20, function()
+ WowRadio:stop()
+ end)
+
+ f.nextButton = WowRadio:CreateWRButton(f, ">>", 352, -100, 46, 20, function()
+ WowRadio:next()
+ end)
+
+ f.randomButton = WowRadio:CreateWRButton(f, "?", 401, -100, 54, 20, function()
+ WowRadio:rnd()
+ end)
+
+ f.autoButton = WowRadio:CreateWRButton(f, "Auto", 458, -100, 82, 20, function()
+ WowRadio:toggleAutostart()
+ WowRadio:RefreshUI()
+ end)
+
+ local volumeLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
+ volumeLabel:SetPoint("TOPLEFT", f, "TOPLEFT", 20, -105)
+ volumeLabel:SetText("Vol")
+ f.volumeLabel = volumeLabel
+
+ local volume = CreateFrame("Slider", "WowRadioVolumeSlider", f, "OptionsSliderTemplate")
+ if volume.SetFrameLevel then
+ volume:SetFrameLevel(f:GetFrameLevel() + 10)
+ end
+ volume:SetWidth(108)
+ volume:SetHeight(14)
+ volume:SetPoint("TOPLEFT", f, "TOPLEFT", 44, -102)
+ volume:SetMinMaxValues(0, 1)
+ volume:SetValueStep(0.05)
+
+ local low = getglobal("WowRadioVolumeSliderLow")
+ local high = getglobal("WowRadioVolumeSliderHigh")
+ local text = getglobal("WowRadioVolumeSliderText")
+
+ if low then low:SetText("") end
+ if high then high:SetText("") end
+ if text then text:SetText("") end
+
+ local volumeValue = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
+ volumeValue:SetPoint("TOPLEFT", f, "TOPLEFT", 158, -105)
+ volumeValue:SetWidth(40)
+ volumeValue:SetJustifyH("LEFT")
+ f.volumeValueText = volumeValue
+
+ volume:SetScript("OnValueChanged", function()
+ WowRadio:ApplyVolume(this:GetValue(), true)
+ end)
+
+ volume:SetValue(WowRadio:GetVolume())
+ f.volumeSlider = volume
+
+ -- Category tabs. One row, no search/custom/test controls.
+ f.tabButtons = {}
+
+ local tabX = 20
+ local tabY = -130
+ for _, tabInfo in ipairs(WR_TABS) do
+ local tabKey = tabInfo.key
+ local tabButton = WowRadio:CreateWRButton(f, tabInfo.label, tabX, tabY, tabInfo.width, 20, function()
+ WowRadio:SetUITab(tabKey)
+ end)
+
+ f.tabButtons[tabInfo.key] = tabButton
+ tabX = tabX + tabInfo.width + 5
+ end
+
+ -- Station rows.
+ f.rows = {}
+
+ for i = 1, WR_LINES_PER_PAGE do
+ local y = -158 - ((i - 1) * 25)
+
+ local star = WowRadio:CreateWRButton(f, "+", 20, y, 24, 20, function()
+ if this.stationIndex then
+ WowRadio:ToggleFavorite(this.stationIndex)
+ end
+ end)
+
+ local row = WowRadio:CreateWRButton(f, "", 48, y, 485, 20, function()
+ if this.stationIndex then
+ WowRadio:play(this.stationIndex)
+ end
+ end)
+
+ f.rows[i] = {
+ star = star,
+ row = row,
+ }
+ end
+
+ f.pageText = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
+ f.pageText:SetPoint("TOPLEFT", f, "TOPLEFT", 20, -394)
+ f.pageText:SetWidth(330)
+ f.pageText:SetJustifyH("LEFT")
+ f.pageText:SetText("Page 1")
+
+ f.customButton = WowRadio:CreateWRButton(f, "Custom", 355, -391, 88, 22, function()
+ WowRadio:ShowCustomUrlDialog()
+ end)
+
+ f.pagePrev = WowRadio:CreateWRButton(f, "<", 449, -391, 44, 22, function()
+ WowRadio:PageBack()
+ end)
+
+ f.pageNext = WowRadio:CreateWRButton(f, ">", 496, -391, 44, 22, function()
+ WowRadio:PageForward()
+ end)
+
+ -- Custom URL input dialog (parented to UIParent so it survives frame hide).
+ local urlDialog = CreateFrame("Frame", "WowRadioUrlDialog", UIParent)
+ urlDialog:SetWidth(430)
+ urlDialog:SetHeight(95)
+ urlDialog:SetFrameStrata("FULLSCREEN_DIALOG")
+ urlDialog:SetPoint("CENTER", UIParent, "CENTER")
+ urlDialog:SetMovable(true)
+ urlDialog:EnableMouse(true)
+ urlDialog:RegisterForDrag("LeftButton")
+ urlDialog:SetScript("OnDragStart", function() this:StartMoving() end)
+ urlDialog:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
+ urlDialog:SetBackdrop({
+ bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
+ edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
+ tile = true, tileSize = 32, edgeSize = 32,
+ insets = { left = 11, right = 12, top = 12, bottom = 11 },
+ })
+ urlDialog:Hide()
+
+ local urlLabel = urlDialog:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+ urlLabel:SetPoint("TOPLEFT", urlDialog, "TOPLEFT", 16, -14)
+ urlLabel:SetText("Custom Stream URL:")
+
+ local urlEditBox = CreateFrame("EditBox", "WowRadioUrlEditBox", urlDialog, "InputBoxTemplate")
+ urlEditBox:SetWidth(390)
+ urlEditBox:SetHeight(20)
+ urlEditBox:SetPoint("TOPLEFT", urlDialog, "TOPLEFT", 16, -34)
+ urlEditBox:SetMaxLetters(512)
+ urlEditBox:SetAutoFocus(false)
+ urlEditBox:SetScript("OnEscapePressed", function()
+ urlDialog:Hide()
+ end)
+ urlEditBox:SetScript("OnEnterPressed", function()
+ local text = this:GetText()
+ if text and text ~= "" then WowRadio:url(text) end
+ urlDialog:Hide()
+ end)
+
+ local urlPlay = CreateFrame("Button", nil, urlDialog, "UIPanelButtonTemplate")
+ urlPlay:SetWidth(80)
+ urlPlay:SetHeight(22)
+ urlPlay:SetText("Play")
+ urlPlay:SetPoint("BOTTOMRIGHT", urlDialog, "BOTTOMRIGHT", -16, 10)
+ urlPlay:SetScript("OnClick", function()
+ local text = WowRadioUrlEditBox:GetText()
+ if text and text ~= "" then WowRadio:url(text) end
+ urlDialog:Hide()
+ end)
+
+ local urlCancel = CreateFrame("Button", nil, urlDialog, "UIPanelButtonTemplate")
+ urlCancel:SetWidth(80)
+ urlCancel:SetHeight(22)
+ urlCancel:SetText("Cancel")
+ urlCancel:SetPoint("RIGHT", urlPlay, "LEFT", -5, 0)
+ urlCancel:SetScript("OnClick", function()
+ urlDialog:Hide()
+ end)
+
+ f.urlDialog = urlDialog
+ f.urlEditBox = urlEditBox
+
+ WowRadio:ApplyVolume(WowRadio:GetVolume(), true)
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:CreateWRButton(parent, text, x, y, width, height, onclick)
+ local b = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
+
+ -- Keep buttons above the custom backdrop texture in WoW 1.12.
+ if parent and parent.GetFrameLevel and b.SetFrameLevel then
+ b:SetFrameLevel(parent:GetFrameLevel() + 10)
+ end
+
+ b:SetWidth(width)
+ b:SetHeight(height)
+ b:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
+ b:SetText(text)
+ b:SetScript("OnClick", onclick)
+ return b
+end
+
+function WowRadio:UpdateTabButtons()
+ if not WowRadioFrame or not WowRadioFrame.tabButtons then
+ return
+ end
+
+ local currentTab = self.db.account.ui.tab or "ALL"
+
+ for _, tabInfo in ipairs(WR_TABS) do
+ local b = WowRadioFrame.tabButtons[tabInfo.key]
+
+ if b then
+ if currentTab == tabInfo.key then
+ b:SetText("|cff7FFF7F"..tabInfo.label.."|r")
+ else
+ b:SetText(tabInfo.label)
+ end
+ end
+ end
+end
+
+function WowRadio:SetUITab(tab)
+ self.db.account.ui.tab = tab
+ self.db.account.ui.page = 1
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:PageBack()
+ local page = tonumber(self.db.account.ui.page) or 1
+ page = page - 1
+
+ if page < 1 then
+ page = 1
+ end
+
+ self.db.account.ui.page = page
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:PageForward()
+ local visible = WowRadio:GetVisibleStations()
+ local total = table.getn(visible)
+ local maxPage = math.ceil(total / WR_LINES_PER_PAGE)
+
+ if maxPage < 1 then
+ maxPage = 1
+ end
+
+ local page = tonumber(self.db.account.ui.page) or 1
+ page = page + 1
+
+ if page > maxPage then
+ page = maxPage
+ end
+
+ self.db.account.ui.page = page
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:IsFavorite(index)
+ if self.db.account.favorites == nil then
+ self.db.account.favorites = {}
+ end
+
+ return self.db.account.favorites[tostring(index)] == true
+end
+
+function WowRadio:ToggleFavorite(index)
+ if self.db.account.favorites == nil then
+ self.db.account.favorites = {}
+ end
+
+ local key = tostring(index)
+ self.db.account.favorites[key] = not self.db.account.favorites[key]
+
+ WowRadio:RefreshUI()
+end
+
+function WowRadio:GetStationCategory(index)
+ if stationCategory and stationCategory[index] then
+ return stationCategory[index]
+ end
+
+ return "VARIETY"
+end
+
+function WowRadio:GetVisibleStations()
+ local visible = {}
+ local tab = self.db.account.ui.tab or "ALL"
+
+ if tab == "CUSTOM" or tab == "TEST" then
+ tab = "ALL"
+ self.db.account.ui.tab = "ALL"
+ end
+
+ for i = 1, table.getn(stationUrl) do
+ local include = true
+
+ if tab == "FAV" and not WowRadio:IsFavorite(i) then
+ include = false
+ elseif tab ~= "ALL" and tab ~= "FAV" then
+ if WowRadio:GetStationCategory(i) ~= tab then
+ include = false
+ end
+ end
+
+ if include then
+ table.insert(visible, i)
+ end
+ end
+
+ return visible
+end
+
+function WowRadio:RefreshUI()
+ if not WowRadioFrame then
+ return
+ end
+
+ local f = WowRadioFrame
+ local tab = self.db.account.ui.tab or "ALL"
+
+ if tab == "CUSTOM" or tab == "TEST" then
+ tab = "ALL"
+ self.db.account.ui.tab = "ALL"
+ end
+
+ local state = "Playing"
+ if stopped == true then
+ state = "Stopped"
+ end
+
+ if customUrl == nil then
+ local station = WowRadio:getStation()
+
+ if f.nowStatusText then
+ f.nowStatusText:SetText("|cffffffff"..state..":|r")
+ end
+
+ if f.nowStationText then
+ f.nowStationText:SetText("|cff00ff00["..station.."] "..wr_trim(WowRadio:getStationShortName(station), 40).."|r")
+ elseif f.nowText then
+ f.nowText:SetText("|cffffffff"..state..":|r |cff00ff00["..station.."] "..WowRadio:getStationShortName(station).."|r")
+ end
+ else
+ if f.nowStatusText then
+ f.nowStatusText:SetText("|cffffffff"..state..":|r")
+ end
+
+ if f.nowStationText then
+ f.nowStationText:SetText("|cff00ff00[custom] "..wr_trim(customUrl, 40).."|r")
+ elseif f.nowText then
+ f.nowText:SetText("|cffffffff"..state..":|r |cff00ff00[custom] "..wr_trim(customUrl, 80).."|r")
+ end
+ end
+
+ if self.db.account.autostart == true then
+ f.autoButton:SetText("Auto: On")
+ else
+ f.autoButton:SetText("Auto: Off")
+ end
+
+ if f.fadeButton then
+ if self.db.account.fadeOnMove then
+ f.fadeButton:SetText("|cff7FFF7FFade|r")
+ else
+ f.fadeButton:SetText("Fade")
+ end
+ end
+
+ if f.volumeSlider then
+ local v = WowRadio:GetVolume()
+ if f.volumeSlider:GetValue() ~= v then
+ f.volumeSlider:SetValue(v)
+ end
+ end
+
+ WowRadio:UpdateTabButtons()
+ WowRadio:ShowStationPanel()
+end
+
+function WowRadio:ShowStationPanel()
+ local f = WowRadioFrame
+
+ f.pageText:Show()
+ f.pagePrev:Show()
+ f.pageNext:Show()
+
+ local visible = WowRadio:GetVisibleStations()
+ local total = table.getn(visible)
+ local page = tonumber(self.db.account.ui.page) or 1
+ local maxPage = math.ceil(total / WR_LINES_PER_PAGE)
+
+ if maxPage < 1 then
+ maxPage = 1
+ end
+
+ if page > maxPage then
+ page = maxPage
+ end
+
+ if page < 1 then
+ page = 1
+ end
+
+ self.db.account.ui.page = page
+
+ local startIndex = ((page - 1) * WR_LINES_PER_PAGE) + 1
+
+ for rowIndex = 1, WR_LINES_PER_PAGE do
+ local stationListIndex = startIndex + rowIndex - 1
+ local stationIndex = visible[stationListIndex]
+ local row = f.rows[rowIndex]
+
+ if stationIndex then
+ local name = "["..stationIndex.."] "..WowRadio:getStationShortName(stationIndex)
+
+ row.star:Show()
+ row.row:Show()
+
+ row.star.stationIndex = stationIndex
+ row.row.stationIndex = stationIndex
+
+ if WowRadio:IsFavorite(stationIndex) then
+ row.star:SetText("-")
+ else
+ row.star:SetText("+")
+ end
+
+ row.row:SetText(wr_trim(name, 70))
+ else
+ row.star:Hide()
+ row.row:Hide()
+
+ row.star.stationIndex = nil
+ row.row.stationIndex = nil
+ end
+ end
+
+ f.pageText:SetText("Page "..page.." / "..maxPage.." Stations: "..total)
+end
+
+-----------------------------------------------------------------
+-- WowRadio
+-- Author: Tormentor @Mannoroth
+--
+-- 0.3 initial beta release
+--
+-- 0.4 info now correctly shows url if playing custom url
+-- Titan Panel Plugin included
+-- moved stations to Stations.lua
+--
+-- Modified:
+-- /wrlist is now compact, 3 stations per row
+-- /wrindex and /wri added as compact aliases
+-- /wrfull added for the original full list
+-- /wrfind added for station search
+-- /wrurl added to show the current stream URL
+-- /wr now opens the UI controller
+-- minimap button added
+-- controller UI added
+-- favorites added; + adds a favorite, - removes one
+-- custom URL tab added
+-- per-row URL buttons removed; station rows widened
+-- category tabs kept: Game, Talk, Rock, Elec, Jazz, Variety
+-- main station buttons/page arrows restored and spaced lower
+-- volume slider moved to its own row
+-- Test and Custom UI tabs removed; custom stream remains available through /wrcustom
+-- Search box removed from controller
+-- Music volume slider added
+-- Window and control layout tightened
+-- Custom backdrop added using 512-safe split TGAs: bg_left.tga and bg_right.tga
+-- Original 1024x512 bg.tga did not display reliably in 1.12.x UI texture loading
+-- Volume CVar reads/writes wrapped for 1.12.x compatibility
+-- Title text/backing removed because title is now part of bg artwork
+-- Buttons and slider raised above custom backdrop texture
+-- Volume CVar reads/writes wrapped for 1.12.x compatibility
+-- Now-playing display moved to bottom strip under stations, above page arrows
+--
+-----------------------------------------------------------------
\ No newline at end of file
diff --git a/Libs/AceAddon-2.0/AceAddon-2.0.lua b/Libs/AceAddon-2.0/AceAddon-2.0.lua
new file mode 100644
index 0000000..fd5a681
--- /dev/null
+++ b/Libs/AceAddon-2.0/AceAddon-2.0.lua
@@ -0,0 +1,1078 @@
+--[[
+Name: AceAddon-2.0
+Revision: $Rev: 17957 $
+Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
+Inspired By: Ace 1.x by Turan (turan@gryphon.com)
+Website: http://www.wowace.com/
+Documentation: http://www.wowace.com/index.php/AceAddon-2.0
+SVN: http://svn.wowace.com/root/trunk/Ace2/AceAddon-2.0
+Description: Base for all Ace addons to inherit from.
+Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0, (optional) AceConsole-2.0
+]]
+
+local MAJOR_VERSION = "AceAddon-2.0"
+local MINOR_VERSION = "$Revision: 17957 $"
+
+-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version
+if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
+if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
+
+if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
+if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end
+
+local function safecall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+ local success, err = pcall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+ if not success then geterrorhandler()(err) end
+end
+
+-- Localization
+local STANDBY, TITLE, NOTES, VERSION, AUTHOR, DATE, CATEGORY, EMAIL, CREDITS, WEBSITE, COMMANDS, CATEGORIES, ABOUT, PRINT_ADDON_INFO
+if GetLocale() == "deDE" then
+ STANDBY = "|cffff5050(Standby)|r" -- capitalized
+
+ TITLE = "Titel"
+ NOTES = "Anmerkung"
+ VERSION = "Version"
+ AUTHOR = "Autor"
+ DATE = "Datum"
+ CATEGORY = "Kategorie"
+ EMAIL = "E-mail"
+ WEBSITE = "Webseite"
+ CREDITS = "Credits" -- fix
+ COMMANDS = "Commands"
+
+ ABOUT = "\195\188ber"
+ PRINT_ADDON_INFO = "Gibt Addondaten aus"
+
+ CATEGORIES = {
+ ["Action Bars"] = "Aktionsleisten",
+ ["Auction"] = "Auktion",
+ ["Audio"] = "Audio",
+ ["Battlegrounds/PvP"] = "Schlachtfeld/PvP",
+ ["Buffs"] = "Buffs",
+ ["Chat/Communication"] = "Chat/Kommunikation",
+ ["Druid"] = "Druide",
+ ["Hunter"] = "Jäger",
+ ["Mage"] = "Magier",
+ ["Paladin"] = "Paladin",
+ ["Priest"] = "Priester",
+ ["Rogue"] = "Schurke",
+ ["Shaman"] = "Schamane",
+ ["Warlock"] = "Hexenmeister",
+ ["Warrior"] = "Krieger",
+ ["Healer"] = "Heiler",
+ ["Tank"] = "Tank", -- noone use "Brecher"...
+ ["Caster"] = "Caster",
+ ["Combat"] = "Kampf",
+ ["Compilations"] = "Compilations", -- whats that o_O
+ ["Data Export"] = "Datenexport",
+ ["Development Tools"] = "Entwicklungs Tools",
+ ["Guild"] = "Gilde",
+ ["Frame Modification"] = "Frame Modifikation",
+ ["Interface Enhancements"] = "Interface Verbesserungen",
+ ["Inventory"] = "Inventar",
+ ["Library"] = "Library",
+ ["Map"] = "Map",
+ ["Mail"] = "Mail",
+ ["Miscellaneous"] = "Diverses",
+ ["Quest"] = "Quest",
+ ["Raid"] = "Schlachtzug",
+ ["Tradeskill"] = "Handelsf\195\164higkeit",
+ ["UnitFrame"] = "UnitFrame",
+ }
+elseif GetLocale() == "frFR" then
+ STANDBY = "|cffff5050(attente)|r"
+
+ TITLE = "Titre"
+ NOTES = "Notes"
+ VERSION = "Version"
+ AUTHOR = "Auteur"
+ DATE = "Date"
+ CATEGORY = "Cat\195\169gorie"
+ EMAIL = "E-mail"
+ WEBSITE = "Site web"
+ CREDITS = "Credits" -- fix
+ COMMANDS = "Commands"
+
+ ABOUT = "A propos"
+ PRINT_ADDON_INFO = "Afficher les informations sur l'addon"
+
+ CATEGORIES = {
+ ["Action Bars"] = "Barres d'action",
+ ["Auction"] = "H\195\180tel des ventes",
+ ["Audio"] = "Audio",
+ ["Battlegrounds/PvP"] = "Champs de bataille/JcJ",
+ ["Buffs"] = "Buffs",
+ ["Chat/Communication"] = "Chat/Communication",
+ ["Druid"] = "Druide",
+ ["Hunter"] = "Chasseur",
+ ["Mage"] = "Mage",
+ ["Paladin"] = "Paladin",
+ ["Priest"] = "Pr\195\170tre",
+ ["Rogue"] = "Voleur",
+ ["Shaman"] = "Chaman",
+ ["Warlock"] = "D\195\169moniste",
+ ["Warrior"] = "Guerrier",
+ ["Healer"] = "Soigneur",
+ ["Tank"] = "Tank",
+ ["Caster"] = "Casteur",
+ ["Combat"] = "Combat",
+ ["Compilations"] = "Compilations",
+ ["Data Export"] = "Exportation de donn\195\169es",
+ ["Development Tools"] = "Outils de d\195\169veloppement",
+ ["Guild"] = "Guilde",
+ ["Frame Modification"] = "Modification des fen\195\170tres",
+ ["Interface Enhancements"] = "Am\195\169liorations de l'interface",
+ ["Inventory"] = "Inventaire",
+ ["Library"] = "Biblioth\195\168ques",
+ ["Map"] = "Carte",
+ ["Mail"] = "Courrier",
+ ["Miscellaneous"] = "Divers",
+ ["Quest"] = "Qu\195\170tes",
+ ["Raid"] = "Raid",
+ ["Tradeskill"] = "M\195\169tiers",
+ ["UnitFrame"] = "Fen\195\170tres d'unit\195\169",
+ }
+elseif GetLocale() == "koKR" then
+ STANDBY = "|cffff5050(사용가능)|r"
+
+ TITLE = "제목"
+ NOTES = "노트"
+ VERSION = "버전"
+ AUTHOR = "저작자"
+ DATE = "날짜"
+ CATEGORY = "분류"
+ EMAIL = "E-mail"
+ WEBSITE = "웹사이트"
+ CREDITS = "Credits" -- fix
+ COMMANDS = "Commands"
+
+ ABOUT = "정보"
+ PRINT_ADDON_INFO = "애드온 정보 출력"
+
+ CATEGORIES = {
+ ["Action Bars"] = "액션바",
+ ["Auction"] = "경매",
+ ["Audio"] = "음향",
+ ["Battlegrounds/PvP"] = "전장/PvP",
+ ["Buffs"] = "버프",
+ ["Chat/Communication"] = "대화/의사소통",
+ ["Druid"] = "드루이드",
+ ["Hunter"] = "사냥꾼",
+ ["Mage"] = "마법사",
+ ["Paladin"] = "성기사",
+ ["Priest"] = "사제",
+ ["Rogue"] = "도적",
+ ["Shaman"] = "주술사",
+ ["Warlock"] = "흑마법사",
+ ["Warrior"] = "전사",
+ ["Healer"] = "힐러",
+ ["Tank"] = "탱커",
+ ["Caster"] = "캐스터",
+ ["Combat"] = "전투",
+ ["Compilations"] = "복합",
+ ["Data Export"] = "자료 출력",
+ ["Development Tools"] = "개발 도구",
+ ["Guild"] = "길드",
+ ["Frame Modification"] = "구조 변경",
+ ["Interface Enhancements"] = "인터페이스 강화",
+ ["Inventory"] = "인벤토리",
+ ["Library"] = "라이브러리",
+ ["Map"] = "지도",
+ ["Mail"] = "우편",
+ ["Miscellaneous"] = "기타",
+ ["Quest"] = "퀘스트",
+ ["Raid"] = "공격대",
+ ["Tradeskill"] = "전문기술",
+ ["UnitFrame"] = "유닛 프레임",
+ }
+elseif GetLocale() == "zhTW" then
+ STANDBY = "|cffff5050(待命)|r"
+
+ TITLE = "標題"
+ NOTES = "註記"
+ VERSION = "版本"
+ AUTHOR = "作者"
+ DATE = "日期"
+ CATEGORY = "類別"
+ EMAIL = "E-mail"
+ WEBSITE = "網站"
+ CREDITS = "Credits" -- fix
+ COMMANDS = "Commands"
+
+ ABOUT = "關於"
+ PRINT_ADDON_INFO = "顯示插件資訊"
+
+ CATEGORIES = {
+ ["Action Bars"] = "動作列",
+ ["Auction"] = "拍賣",
+ ["Audio"] = "音樂",
+ ["Battlegrounds/PvP"] = "戰場/PvP",
+ ["Buffs"] = "增益",
+ ["Chat/Communication"] = "聊天/通訊",
+ ["Druid"] = "德魯伊",
+ ["Hunter"] = "獵人",
+ ["Mage"] = "法師",
+ ["Paladin"] = "聖騎士",
+ ["Priest"] = "牧師",
+ ["Rogue"] = "盜賊",
+ ["Shaman"] = "薩滿",
+ ["Warlock"] = "術士",
+ ["Warrior"] = "戰士",
+ ["Healer"] = "治療者",
+ ["Tank"] = "坦克",
+ ["Caster"] = "施法者",
+ ["Combat"] = "戰鬥",
+ ["Compilations"] = "編輯",
+ ["Data Export"] = "資料匯出",
+ ["Development Tools"] = "開發工具",
+ ["Guild"] = "公會",
+ ["Frame Modification"] = "框架修改",
+ ["Interface Enhancements"] = "介面增強",
+ ["Inventory"] = "背包",
+ ["Library"] = "資料庫",
+ ["Map"] = "地圖",
+ ["Mail"] = "郵件",
+ ["Miscellaneous"] = "綜合",
+ ["Quest"] = "任務",
+ ["Raid"] = "團隊",
+ ["Tradeskill"] = "商業技能",
+ ["UnitFrame"] = "單位框架",
+ }
+elseif GetLocale() == "zhCN" then
+ STANDBY = "|cffff5050(\230\154\130\230\140\130)|r"
+
+ TITLE = "\230\160\135\233\162\152"
+ NOTES = "\233\153\132\230\179\168"
+ VERSION = "\231\137\136\230\156\172"
+ AUTHOR = "\228\189\156\232\128\133"
+ DATE = "\230\151\165\230\156\159"
+ CATEGORY = "\229\136\134\231\177\187"
+ EMAIL = "\231\148\181\229\173\144\233\130\174\228\187\182"
+ WEBSITE = "\231\189\145\231\171\153"
+ CREDITS = "Credits" -- fix
+ COMMANDS = "Commands"
+
+ ABOUT = "\229\133\179\228\186\142"
+ PRINT_ADDON_INFO = "\229\141\176\229\136\151\229\135\186\230\143\146\228\187\182\228\191\161\230\129\175"
+
+ CATEGORIES = {
+ ["Action Bars"] = "\229\138\168\228\189\156\230\157\161",
+ ["Auction"] = "\230\139\141\229\141\150",
+ ["Audio"] = "\233\159\179\233\162\145",
+ ["Battlegrounds/PvP"] = "\230\136\152\229\156\186/PvP",
+ ["Buffs"] = "\229\162\158\231\155\138\233\173\148\230\179\149",
+ ["Chat/Communication"] = "\232\129\138\229\164\169/\228\186\164\230\181\129",
+ ["Druid"] = "\229\190\183\233\178\129\228\188\138",
+ ["Hunter"] = "\231\140\142\228\186\186",
+ ["Mage"] = "\230\179\149\229\184\136",
+ ["Paladin"] = "\229\156\163\233\170\145\229\163\171",
+ ["Priest"] = "\231\137\167\229\184\136",
+ ["Rogue"] = "\231\155\151\232\180\188",
+ ["Shaman"] = "\232\144\168\230\187\161\231\165\173\229\143\184",
+ ["Warlock"] = "\230\156\175\229\163\171",
+ ["Warrior"] = "\230\136\152\229\163\171",
+-- ["Healer"] = "\230\178\187\231\150\151\228\191\157\233\154\156",
+-- ["Tank"] = "\232\191\145\230\136\152\230\142\167\229\136\182",
+-- ["Caster"] = "\232\191\156\231\168\139\232\190\147\229\135\186",
+ ["Combat"] = "\230\136\152\230\150\151",
+ ["Compilations"] = "\231\188\150\232\175\145",
+ ["Data Export"] = "\230\149\176\230\141\174\229\175\188\229\135\186",
+ ["Development Tools"] = "\229\188\128\229\143\145\229\183\165\229\133\183",
+ ["Guild"] = "\229\133\172\228\188\154",
+ ["Frame Modification"] = "\230\161\134\230\158\182\228\191\174\230\148\185",
+ ["Interface Enhancements"] = "\231\149\140\233\157\162\229\162\158\229\188\186",
+ ["Inventory"] = "\232\131\140\229\140\133",
+ ["Library"] = "\229\186\147",
+ ["Map"] = "\229\156\176\229\155\190",
+ ["Mail"] = "\233\130\174\228\187\182",
+ ["Miscellaneous"] = "\230\157\130\233\161\185",
+ ["Quest"] = "\228\187\187\229\138\161",
+ ["Raid"] = "\229\155\162\233\152\159",
+ ["Tradeskill"] = "\229\149\134\228\184\154\230\138\128\232\131\189",
+ ["UnitFrame"] = "\229\164\180\229\131\143\230\161\134\230\158\182",
+ }
+elseif GetLocale() == "ruRU" then
+ STANDBY = "|cffff5050(в режиме ожидания)|r"
+
+ TITLE = "Название"
+ NOTES = "Описание"
+ VERSION = "Версия"
+ AUTHOR = "Автор"
+ DATE = "Дата"
+ CATEGORY = "Категория"
+ EMAIL = "E-mail"
+ WEBSITE = "Сайт"
+ CREDITS = "Титры"
+ COMMANDS = "Команды"
+
+ ABOUT = "Об аддоне"
+ PRINT_ADDON_INFO = "Показать информацию об аддоне."
+
+ CATEGORIES = {
+ ["Action Bars"] = "Панели команд",
+ ["Auction"] = "Аукцион",
+ ["Audio"] = "Аудио",
+ ["Battlegrounds/PvP"] = "Поля сражений/PvP",
+ ["Buffs"] = "Баффы",
+ ["Chat/Communication"] = "Чат/Коммуникация",
+ ["Druid"] = "Друид",
+ ["Hunter"] = "Охотник",
+ ["Mage"] = "Маг",
+ ["Paladin"] = "Паладин",
+ ["Priest"] = "Жрец",
+ ["Rogue"] = "Разбойник",
+ ["Shaman"] = "Шаман",
+ ["Warlock"] = "Чернокнижник",
+ ["Warrior"] = "Воин",
+ ["Healer"] = "Лекарь",
+ ["Tank"] = "Танк",
+ ["Caster"] = "Кастер",
+ ["Combat"] = "Сражения",
+ ["Compilations"] = "Компиляция",
+ ["Data Export"] = "Экспорт данных",
+ ["Development Tools"] = "Инструменты разработчика",
+ ["Guild"] = "Гильдия",
+ ["Frame Modification"] = "Модификация фреймов",
+ ["Interface Enhancements"] = "Улучшение интерфейса",
+ ["Inventory"] = "Инвентарь",
+ ["Library"] = "Библиотеки",
+ ["Map"] = "Карта",
+ ["Mail"] = "Почта",
+ ["Miscellaneous"] = "Разное",
+ ["Quest"] = "Задания",
+ ["Raid"] = "Рейд",
+ ["Tradeskill"] = "Умения",
+ ["UnitFrame"] = "Фреймы персонажей",
+ }
+else -- enUS
+ STANDBY = "|cffff5050(standby)|r"
+
+ TITLE = "Title"
+ NOTES = "Notes"
+ VERSION = "Version"
+ AUTHOR = "Author"
+ DATE = "Date"
+ CATEGORY = "Category"
+ EMAIL = "E-mail"
+ WEBSITE = "Website"
+ CREDITS = "Credits"
+ COMMANDS = "Commands"
+
+ ABOUT = "About"
+ PRINT_ADDON_INFO = "Print out addon info"
+
+ CATEGORIES = {
+ ["Action Bars"] = "Action Bars",
+ ["Auction"] = "Auction",
+ ["Audio"] = "Audio",
+ ["Battlegrounds/PvP"] = "Battlegrounds/PvP",
+ ["Buffs"] = "Buffs",
+ ["Chat/Communication"] = "Chat/Communication",
+ ["Druid"] = "Druid",
+ ["Hunter"] = "Hunter",
+ ["Mage"] = "Mage",
+ ["Paladin"] = "Paladin",
+ ["Priest"] = "Priest",
+ ["Rogue"] = "Rogue",
+ ["Shaman"] = "Shaman",
+ ["Warlock"] = "Warlock",
+ ["Warrior"] = "Warrior",
+ ["Healer"] = "Healer",
+ ["Tank"] = "Tank",
+ ["Caster"] = "Caster",
+ ["Combat"] = "Combat",
+ ["Compilations"] = "Compilations",
+ ["Data Export"] = "Data Export",
+ ["Development Tools"] = "Development Tools",
+ ["Guild"] = "Guild",
+ ["Frame Modification"] = "Frame Modification",
+ ["Interface Enhancements"] = "Interface Enhancements",
+ ["Inventory"] = "Inventory",
+ ["Library"] = "Library",
+ ["Map"] = "Map",
+ ["Mail"] = "Mail",
+ ["Miscellaneous"] = "Miscellaneous",
+ ["Quest"] = "Quest",
+ ["Raid"] = "Raid",
+ ["Tradeskill"] = "Tradeskill",
+ ["UnitFrame"] = "UnitFrame",
+ }
+end
+
+setmetatable(CATEGORIES, { __index = function(self, key) -- case-insensitive
+ local lowerKey = string.lower(key)
+ for k,v in pairs(CATEGORIES) do
+ if string.lower(k) == lowerKey then
+ return v
+ end
+ end
+end })
+
+-- Create the library object
+
+local AceOO = AceLibrary("AceOO-2.0")
+local AceAddon = AceOO.Class()
+local AceEvent
+local AceConsole
+local AceModuleCore
+
+function AceAddon:ToString()
+ return "AceAddon"
+end
+
+local function print(text)
+ DEFAULT_CHAT_FRAME:AddMessage(text)
+end
+
+function AceAddon:ADDON_LOADED(name)
+ while table.getn(self.nextAddon) > 0 do
+ local addon = table.remove(self.nextAddon, 1)
+ table.insert(self.addons, addon)
+ if not self.addons[name] then
+ self.addons[name] = addon
+ end
+ self:InitializeAddon(addon, name)
+ end
+end
+
+local function RegisterOnEnable(self)
+ if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage then -- HACK
+ AceAddon.playerLoginFired = true
+ end
+ if AceAddon.playerLoginFired then
+ AceAddon.addonsStarted[self] = true
+ if (type(self.IsActive) ~= "function" or self:IsActive()) and (not AceModuleCore or not AceModuleCore:IsModule(self) or AceModuleCore:IsModuleActive(self)) then
+ local current = self.class
+ while true do
+ if current == AceOO.Class then
+ break
+ end
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedEnable) == "function" then
+ safecall(mixin.OnEmbedEnable,mixin,self)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(self.OnEnable) == "function" then
+ safecall(self.OnEnable,self)
+ end
+ if AceEvent then
+ AceEvent:TriggerEvent("Ace2_AddonEnabled", self)
+ end
+ end
+ else
+ if not AceAddon.addonsToOnEnable then
+ AceAddon.addonsToOnEnable = {}
+ end
+ table.insert(AceAddon.addonsToOnEnable, self)
+ end
+end
+
+local function stripSpaces(text)
+ if type(text) == "string" then
+ return (string.gsub(string.gsub(text, "^%s*(.-)%s*$", "%1"), "%s%s+", " "))
+ end
+ return text
+end
+
+function AceAddon:InitializeAddon(addon, name)
+ if addon.name == nil then
+ addon.name = name
+ end
+ if GetAddOnMetadata then
+ -- TOC checks
+ if addon.title == nil then
+ addon.title = GetAddOnMetadata(name, "Title")
+ end
+ if addon.title then
+ local num = string.find(addon.title, " |cff7fff7f %-Ace2%-|r$")
+ if num then
+ addon.title = string.sub(addon.title, 1, num - 1)
+ end
+ addon.title = stripSpaces(addon.title)
+ end
+
+ if addon.notes == nil then
+ addon.notes = GetAddOnMetadata(name, "Notes")
+ addon.notes = stripSpaces(addon.notes)
+ end
+ if addon.version == nil then
+ addon.version = GetAddOnMetadata(name, "Version")
+ end
+ if addon.version then
+ if string.find(addon.version, "%$Revision: (%d+) %$") then
+ addon.version = string.gsub(addon.version, "%$Revision: (%d+) %$", "%1")
+ elseif string.find(addon.version, "%$Rev: (%d+) %$") then
+ addon.version = string.gsub(addon.version, "%$Rev: (%d+) %$", "%1")
+ elseif string.find(addon.version, "%$LastChangedRevision: (%d+) %$") then
+ addon.version = string.gsub(addon.version, "%$LastChangedRevision: (%d+) %$", "%1")
+ end
+ end
+ addon.version = stripSpaces(addon.version)
+ if addon.author == nil then
+ addon.author = GetAddOnMetadata(name, "Author")
+ addon.author = stripSpaces(addon.author)
+ end
+ if addon.credits == nil then
+ addon.credits = GetAddOnMetadata(name, "X-Credits")
+ addon.credits = stripSpaces(addon.credits)
+ end
+ if addon.date == nil then
+ addon.date = GetAddOnMetadata(name, "X-Date") or GetAddOnMetadata(name, "X-ReleaseDate")
+ end
+ if addon.date then
+ if string.find(addon.date, "%$Date: (.-) %$") then
+ addon.date = string.gsub(addon.date, "%$Date: (.-) %$", "%1")
+ elseif string.find(addon.date, "%$LastChangedDate: (.-) %$") then
+ addon.date = string.gsub(addon.date, "%$LastChangedDate: (.-) %$", "%1")
+ end
+ end
+ addon.date = stripSpaces(addon.date)
+
+ if addon.category == nil then
+ addon.category = GetAddOnMetadata(name, "X-Category")
+ addon.category = stripSpaces(addon.category)
+ end
+ if addon.email == nil then
+ addon.email = GetAddOnMetadata(name, "X-eMail") or GetAddOnMetadata(name, "X-Email")
+ addon.email = stripSpaces(addon.email)
+ end
+ if addon.website == nil then
+ addon.website = GetAddOnMetadata(name, "X-Website")
+ addon.website = stripSpaces(addon.website)
+ end
+ if addon.commands == nil then
+ addon.commands = GetAddOnMetadata(name, "X-Commands")
+ addon.commands = stripSpaces(addon.commands)
+ end
+ end
+ local current = addon.class
+ while true do
+ if current == AceOO.Class then
+ break
+ end
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedInitialize) == "function" then
+ mixin:OnEmbedInitialize(addon, name)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(addon.OnInitialize) == "function" then
+ safecall(addon.OnInitialize, addon, name)
+ end
+ if AceEvent then
+ AceEvent:TriggerEvent("Ace2_AddonInitialized", addon)
+ end
+ RegisterOnEnable(addon)
+end
+
+function AceAddon.prototype:PrintAddonInfo()
+ local x
+ if self.title then
+ x = "|cffffff7f" .. tostring(self.title) .. "|r"
+ elseif self.name then
+ x = "|cffffff7f" .. tostring(self.name) .. "|r"
+ else
+ x = "|cffffff7f<" .. tostring(self.class) .. " instance>|r"
+ end
+ if type(self.IsActive) == "function" then
+ if not self:IsActive() then
+ x = x .. " " .. STANDBY
+ end
+ end
+ if self.version then
+ x = x .. " - |cffffff7f" .. tostring(self.version) .. "|r"
+ end
+ if self.notes then
+ x = x .. " - " .. tostring(self.notes)
+ end
+ print(x)
+ if self.author then
+ print(" - |cffffff7f" .. AUTHOR .. ":|r " .. tostring(self.author))
+ end
+ if self.credits then
+ print(" - |cffffff7f" .. CREDITS .. ":|r " .. tostring(self.credits))
+ end
+ if self.date then
+ print(" - |cffffff7f" .. DATE .. ":|r " .. tostring(self.date))
+ end
+ if self.category then
+ local category = CATEGORIES[self.category]
+ if category then
+ print(" - |cffffff7f" .. CATEGORY .. ":|r " .. category)
+ end
+ end
+ if self.email then
+ print(" - |cffffff7f" .. EMAIL .. ":|r " .. tostring(self.email))
+ end
+ if self.website then
+ print(" - |cffffff7f" .. WEBSITE .. ":|r " .. tostring(self.website))
+ end
+ if self.commands then
+ print(" - |cffffff7f" .. COMMANDS .. ":|r " .. tostring(self.commands))
+ end
+end
+
+local options
+function AceAddon:GetAceOptionsDataTable(target)
+ if not options then
+ options = {
+ about = {
+ name = ABOUT,
+ desc = PRINT_ADDON_INFO,
+ type = "execute",
+ func = "PrintAddonInfo",
+ order = -1,
+ }
+ }
+ end
+ return options
+end
+
+function AceAddon:PLAYER_LOGIN()
+ self.playerLoginFired = true
+ if self.addonsToOnEnable then
+ while table.getn(self.addonsToOnEnable) > 0 do
+ local addon = table.remove(self.addonsToOnEnable, 1)
+ self.addonsStarted[addon] = true
+ if (type(addon.IsActive) ~= "function" or addon:IsActive()) and (not AceModuleCore or not AceModuleCore:IsModule(addon) or AceModuleCore:IsModuleActive(addon)) then
+ local current = addon.class
+ while true do
+ if current == AceOO.Class then
+ break
+ end
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedEnable) == "function" then
+ safecall(mixin.OnEmbedEnable,mixin,addon)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(addon.OnEnable) == "function" then
+ safecall(addon.OnEnable,addon)
+ end
+ if AceEvent then
+ AceEvent:TriggerEvent("Ace2_AddonEnabled", addon)
+ end
+ end
+ end
+ self.addonsToOnEnable = nil
+ end
+end
+
+function AceAddon.prototype:Inject(t)
+ AceAddon:argCheck(t, 2, "table")
+ for k,v in pairs(t) do
+ self[k] = v
+ end
+end
+
+function AceAddon.prototype:init()
+ if not AceEvent then
+ error(MAJOR_VERSION .. " requires AceEvent-2.0", 4)
+ end
+ AceAddon.super.prototype.init(self)
+
+ self.super = self.class.prototype
+
+ AceAddon:RegisterEvent("ADDON_LOADED", "ADDON_LOADED", true)
+ table.insert(AceAddon.nextAddon, self)
+end
+
+function AceAddon.prototype:ToString()
+ local x
+ if type(self.title) == "string" then
+ x = self.title
+ elseif type(self.name) == "string" then
+ x = self.name
+ else
+ x = "<" .. tostring(self.class) .. " instance>"
+ end
+ if (type(self.IsActive) == "function" and not self:IsActive()) or (AceModuleCore and AceModuleCore:IsModule(addon) and AceModuleCore:IsModuleActive(addon)) then
+ x = x .. " " .. STANDBY
+ end
+ return x
+end
+
+AceAddon.new = function(self, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20)
+ local class = AceAddon:pcall(AceOO.Classpool, self, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20)
+ return class:new()
+end
+
+local function external(self, major, instance)
+ if major == "AceEvent-2.0" then
+ AceEvent = instance
+
+ AceEvent:embed(self)
+
+ self:RegisterEvent("PLAYER_LOGIN", "PLAYER_LOGIN", true)
+ elseif major == "AceConsole-2.0" then
+ AceConsole = instance
+
+ local slashCommands = { "/ace2" }
+ local _,_,_,enabled,loadable = GetAddOnInfo("Ace")
+ if not enabled or not loadable then
+ table.insert(slashCommands, "/ace")
+ end
+ local function listAddon(addon, depth)
+ if not depth then
+ depth = 0
+ end
+
+ local s = string.rep(" ", depth) .. " - " .. tostring(addon)
+ if rawget(addon, 'version') then
+ s = s .. " - |cffffff7f" .. tostring(addon.version) .. "|r"
+ end
+ if rawget(addon, 'slashCommand') then
+ s = s .. " |cffffff7f(" .. tostring(addon.slashCommand) .. ")|r"
+ end
+ print(s)
+ if type(rawget(addon, 'modules')) == "table" then
+ local i = 0
+ for k,v in pairs(addon.modules) do
+ i = i + 1
+ if i == 6 then
+ print(string.rep(" ", depth + 1) .. " - more...")
+ break
+ else
+ listAddon(v, depth + 1)
+ end
+ end
+ end
+ end
+ local function listNormalAddon(i)
+ local name,_,_,enabled,loadable = GetAddOnInfo(i)
+ if not loadable then
+ enabled = false
+ end
+ if self.addons[name] then
+ local addon = self.addons[name]
+ if not AceCoreAddon or not AceCoreAddon:IsModule(addon) then
+ listAddon(addon)
+ end
+ else
+ local s = " - " .. tostring(GetAddOnMetadata(i, "Title") or name)
+ local version = GetAddOnMetadata(i, "Version")
+ if version then
+ if string.find(version, "%$Revision: (%d+) %$") then
+ version = string.gsub(version, "%$Revision: (%d+) %$", "%1")
+ elseif string.find(version, "%$Rev: (%d+) %$") then
+ version = string.gsub(version, "%$Rev: (%d+) %$", "%1")
+ elseif string.find(version, "%$LastChangedRevision: (%d+) %$") then
+ version = string.gsub(version, "%$LastChangedRevision: (%d+) %$", "%1")
+ end
+ s = s .. " - |cffffff7f" .. version .. "|r"
+ end
+ if not enabled then
+ s = s .. " |cffff0000(disabled)|r"
+ end
+ if IsAddOnLoadOnDemand(i) then
+ s = s .. " |cff00ff00[LoD]|r"
+ end
+ print(s)
+ end
+ end
+ local function mySort(alpha, bravo)
+ return tostring(alpha) < tostring(bravo)
+ end
+ AceConsole.RegisterChatCommand(self, slashCommands, {
+ desc = "AddOn development framework",
+ name = "Ace2",
+ type = "group",
+ args = {
+ about = {
+ desc = "Get information about Ace2",
+ name = "About",
+ type = "execute",
+ func = function()
+ print("|cffffff7fAce2|r - |cffffff7f2.0." .. string.gsub(MINOR_VERSION, "%$Revision: (%d+) %$", "%1") .. "|r - AddOn development framework")
+ print(" - |cffffff7f" .. AUTHOR .. ":|r Ace Development Team")
+ print(" - |cffffff7f" .. WEBSITE .. ":|r http://www.wowace.com/")
+ end
+ },
+ list = {
+ desc = "List addons",
+ name = "List",
+ type = "group",
+ args = {
+ ace2 = {
+ desc = "List addons using Ace2",
+ name = "Ace2",
+ type = "execute",
+ func = function()
+ print("|cffffff7fAddon list:|r")
+ local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0")
+ table.sort(self.addons, mySort)
+ for _,v in ipairs(self.addons) do
+ if not AceCoreAddon or not AceCoreAddon:IsModule(v) then
+ listAddon(v)
+ end
+ end
+ end
+ },
+ all = {
+ desc = "List all addons",
+ name = "All",
+ type = "execute",
+ func = function()
+ print("|cffffff7fAddon list:|r")
+ local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0")
+ local count = GetNumAddOns()
+ for i = 1, count do
+ listNormalAddon(i)
+ end
+ end
+ },
+ enabled = {
+ desc = "List all enabled addons",
+ name = "Enabled",
+ type = "execute",
+ func = function()
+ print("|cffffff7fAddon list:|r")
+ local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0")
+ local count = GetNumAddOns()
+ for i = 1, count do
+ local _,_,_,enabled,loadable = GetAddOnInfo(i)
+ if enabled and loadable then
+ listNormalAddon(i)
+ end
+ end
+ end
+ },
+ disabled = {
+ desc = "List all disabled addons",
+ name = "Disabled",
+ type = "execute",
+ func = function()
+ print("|cffffff7fAddon list:|r")
+ local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0")
+ local count = GetNumAddOns()
+ for i = 1, count do
+ local _,_,_,enabled,loadable = GetAddOnInfo(i)
+ if not enabled or not loadable then
+ listNormalAddon(i)
+ end
+ end
+ end
+ },
+ lod = {
+ desc = "List all LoadOnDemand addons",
+ name = "LoadOnDemand",
+ type = "execute",
+ func = function()
+ print("|cffffff7fAddon list:|r")
+ local AceCoreAddon = AceLibrary:HasInstance("AceCoreAddon-2.0") and AceLibrary("AceCoreAddon-2.0")
+ local count = GetNumAddOns()
+ for i = 1, count do
+ if IsAddOnLoadOnDemand(i) then
+ listNormalAddon(i)
+ end
+ end
+ end
+ },
+ ace1 = {
+ desc = "List all addons using Ace1",
+ name = "Ace 1.x",
+ type = "execute",
+ func = function()
+ print("|cffffff7fAddon list:|r")
+ local count = GetNumAddOns()
+ for i = 1, count do
+ local dep1, dep2, dep3, dep4 = GetAddOnDependencies(i)
+ if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then
+ listNormalAddon(i)
+ end
+ end
+ end
+ },
+ libs = {
+ desc = "List all libraries using AceLibrary",
+ name = "Libraries",
+ type = "execute",
+ func = function()
+ if type(AceLibrary) == "table" and type(AceLibrary.libs) == "table" then
+ print("|cffffff7fLibrary list:|r")
+ for name, data in pairs(AceLibrary.libs) do
+ local s
+ if data.minor then
+ s = " - " .. tostring(name) .. "." .. tostring(data.minor)
+ else
+ s = " - " .. tostring(name)
+ end
+ if AceLibrary(name).slashCommand then
+ s = s .. " |cffffff7f(" .. tostring(AceLibrary(name).slashCommand) .. "|cffffff7f)"
+ end
+ print(s)
+ end
+ end
+ end
+ },
+ search = {
+ desc = "Search by name",
+ name = "Search",
+ type = "text",
+ usage = "",
+ input = true,
+ get = false,
+ set = function(...)
+ for i,v in ipairs(arg) do
+ arg[i] = string.lower(string.gsub(string.gsub(v, '%*', '.*'), '%%', '%%%%'))
+ end
+ local count = GetNumAddOns()
+ for i = 1, count do
+ local name = GetAddOnInfo(i)
+ local good = true
+ for _,v in ipairs(arg) do
+ if not string.find(string.lower(name), v) then
+ good = false
+ break
+ end
+ end
+ if good then
+ listNormalAddon(i)
+ end
+ end
+ end
+ }
+ },
+ },
+ enable = {
+ desc = "Enable addon",
+ name = "Enable",
+ type = "text",
+ usage = "",
+ get = false,
+ set = function(text)
+ local name,title,_,_,_,reason = GetAddOnInfo(text)
+ if reason == "MISSING" then
+ print(string.format("|cffffff7fAce2:|r AddOn %q does not exist", text))
+ else
+ EnableAddOn(text)
+ print(string.format("|cffffff7fAce2:|r %s is now enabled", title or name))
+ end
+ end,
+ },
+ disable = {
+ desc = "Disable addon",
+ name = "Disable",
+ type = "text",
+ usage = "",
+ get = false,
+ set = function(text)
+ local name,title,_,_,_,reason = GetAddOnInfo(text)
+ if reason == "MISSING" then
+ print(string.format("|cffffff7fAce2:|r AddOn %q does not exist", text))
+ else
+ DisableAddOn(text)
+ print(string.format("|cffffff7fAce2:|r %s is now disabled", title or name))
+ end
+ end,
+ },
+ load = {
+ desc = "Load addon",
+ name = "Load",
+ type = "text",
+ usage = "",
+ get = false,
+ set = function(text)
+ local name,title,_,_,loadable,reason = GetAddOnInfo(text)
+ if reason == "MISSING" then
+ print(string.format("|cffffff7fAce2:|r AddOn %q does not exist.", text))
+ elseif not loadable then
+ print(string.format("|cffffff7fAce2:|r AddOn %q is not loadable. Reason: %s", text, reason))
+ else
+ LoadAddOn(text)
+ print(string.format("|cffffff7fAce2:|r %s is now loaded", title or name))
+ end
+ end
+ },
+ info = {
+ desc = "Display information",
+ name = "Information",
+ type = "execute",
+ func = function()
+ local mem, threshold = gcinfo()
+ print(string.format(" - |cffffff7fMemory usage [|r%.3f MiB|cffffff7f]|r", mem / 1024))
+ print(string.format(" - |cffffff7fThreshold [|r%.3f MiB|cffffff7f]|r", threshold / 1024))
+ print(string.format(" - |cffffff7fFramerate [|r%.0f fps|cffffff7f]|r", GetFramerate()))
+ local bandwidthIn, bandwidthOut, latency = GetNetStats()
+ bandwidthIn, bandwidthOut = floor(bandwidthIn * 1024), floor(bandwidthOut * 1024)
+ print(string.format(" - |cffffff7fLatency [|r%.0f ms|cffffff7f]|r", latency))
+ print(string.format(" - |cffffff7fBandwidth in [|r%.0f B/s|cffffff7f]|r", bandwidthIn))
+ print(string.format(" - |cffffff7fBandwidth out [|r%.0f B/s|cffffff7f]|r", bandwidthOut))
+ print(string.format(" - |cffffff7fTotal addons [|r%d|cffffff7f]|r", GetNumAddOns()))
+ print(string.format(" - |cffffff7fAce2 addons [|r%d|cffffff7f]|r", table.getn(self.addons)))
+ local ace = 0
+ local enabled = 0
+ local disabled = 0
+ local lod = 0
+ for i = 1, GetNumAddOns() do
+ local dep1, dep2, dep3, dep4 = GetAddOnDependencies(i)
+ if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then
+ ace = ace + 1
+ end
+ if IsAddOnLoadOnDemand(i) then
+ lod = lod + 1
+ end
+ local _,_,_,IsActive,loadable = GetAddOnInfo(i)
+ if not IsActive or not loadable then
+ disabled = disabled + 1
+ else
+ enabled = enabled + 1
+ end
+ end
+ print(string.format(" - |cffffff7fAce 1.x addons [|r%d|cffffff7f]|r", ace))
+ print(string.format(" - |cffffff7fLoadOnDemand addons [|r%d|cffffff7f]|r", lod))
+ print(string.format(" - |cffffff7fenabled addons [|r%d|cffffff7f]|r", enabled))
+ print(string.format(" - |cffffff7fdisabled addons [|r%d|cffffff7f]|r", disabled))
+ local libs = 0
+ if type(AceLibrary) == "table" and type(AceLibrary.libs) == "table" then
+ for _ in pairs(AceLibrary.libs) do
+ libs = libs + 1
+ end
+ end
+ print(string.format(" - |cffffff7fAceLibrary instances [|r%d|cffffff7f]|r", libs))
+ end
+ }
+ }
+ })
+ elseif major == "AceModuleCore-2.0" then
+ AceModuleCore = instance
+ end
+end
+
+local function activate(self, oldLib, oldDeactivate)
+ AceAddon = self
+
+ if oldLib then
+ self.playerLoginFired = oldLib.playerLoginFired or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage
+ self.addonsToOnEnable = oldLib.addonsToOnEnable
+ self.addons = oldLib.addons
+ self.nextAddon = oldLib.nextAddon
+ self.addonsStarted = oldLib.addonsStarted
+ end
+ if not self.addons then
+ self.addons = {}
+ end
+ if not self.nextAddon then
+ self.nextAddon = {}
+ end
+ if not self.addonsStarted then
+ self.addonsStarted = {}
+ end
+ if oldDeactivate then
+ oldDeactivate(oldLib)
+ end
+end
+
+AceLibrary:Register(AceAddon, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
+AceAddon = AceLibrary(MAJOR_VERSION)
diff --git a/Libs/AceConsole-2.0/AceConsole-2.0.lua b/Libs/AceConsole-2.0/AceConsole-2.0.lua
new file mode 100644
index 0000000..2964f96
--- /dev/null
+++ b/Libs/AceConsole-2.0/AceConsole-2.0.lua
@@ -0,0 +1,1909 @@
+--[[
+Name: AceConsole-2.0
+Revision: $Rev: 17638 $
+Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
+Inspired By: Ace 1.x by Turan (turan@gryphon.com)
+Website: http://www.wowace.com/
+Documentation: http://www.wowace.com/index.php/AceConsole-2.0
+SVN: http://svn.wowace.com/root/trunk/Ace2/AceConsole-2.0
+Description: Mixin to allow for input/output capabilities. This uses the
+ AceOptions data table format to determine input.
+ http://wiki.wowace.com/index.php/AceOptions_data_table
+Dependencies: AceLibrary, AceOO-2.0
+]]
+
+local MAJOR_VERSION = "AceConsole-2.0"
+local MINOR_VERSION = "$Revision: 17638 $"
+
+if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
+if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
+
+if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
+if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end
+
+local MAP_ONOFF, USAGE, IS_CURRENTLY_SET_TO, IS_NOW_SET_TO, IS_NOT_A_VALID_OPTION_FOR, IS_NOT_A_VALID_VALUE_FOR, NO_OPTIONS_AVAILABLE, OPTION_HANDLER_NOT_FOUND, OPTION_HANDLER_NOT_VALID, OPTION_IS_DISABLED
+if GetLocale() == "deDE" then
+ MAP_ONOFF = { [false] = "|cffff0000Aus|r", [true] = "|cff00ff00An|r" }
+ USAGE = "Benutzung"
+ IS_CURRENTLY_SET_TO = "|cffffff7f%s|r steht momentan auf |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOW_SET_TO = "|cffffff7f%s|r ist nun auf |cffffff7f[|r%s|cffffff7f]|r gesetzt"
+ IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] ist keine g\195\188ltige Option f\195\188r |cffffff7f%s|r"
+ IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] ist kein g\195\188ltiger Wert f\195\188r |cffffff7f%s|r"
+ NO_OPTIONS_AVAILABLE = "Keine Optionen verfügbar"
+ OPTION_HANDLER_NOT_FOUND = "Optionen handler |cffffff7f%q|r nicht gefunden."
+ OPTION_HANDLER_NOT_VALID = "Optionen handler nicht g\195\188ltig."
+ OPTION_IS_DISABLED = "Option |cffffff7f%s|r deaktiviert."
+elseif GetLocale() == "frFR" then
+ MAP_ONOFF = { [false] = "|cffff0000Inactif|r", [true] = "|cff00ff00Actif|r" }
+ USAGE = "Utilisation"
+ IS_CURRENTLY_SET_TO = "|cffffff7f%s|r est actuellement positionn\195\169 sur |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOW_SET_TO = "|cffffff7f%s|r est maintenant positionn\195\169 sur |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] n'est pas une option valide pour |cffffff7f%s|r"
+ IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] n'est pas une valeur valide pour |cffffff7f%s|r"
+ NO_OPTIONS_AVAILABLE = "Pas d'options disponibles"
+ OPTION_HANDLER_NOT_FOUND = "Le gestionnaire d'option |cffffff7f%q|r n'a pas \195\169t\195\169 trouv\195\169."
+ OPTION_HANDLER_NOT_VALID = "Le gestionnaire d'option n'est pas valide."
+ OPTION_IS_DISABLED = "L'option |cffffff7f%s|r est d\195\169sactiv\195\169e."
+elseif GetLocale() == "koKR" then
+ MAP_ONOFF = { [false] = "|cffff0000끔|r", [true] = "|cff00ff00켬|r" }
+ USAGE = "사용법"
+ IS_CURRENTLY_SET_TO = "|cffffff7f%s|r|1은;는; 현재 상태는 |cffffff7f[|r%s|cffffff7f]|r|1으로;로; 설정되어 있습니다"
+ IS_NOW_SET_TO = "|cffffff7f%s|r|1을;를; |cffffff7f[|r%s|cffffff7f]|r 상태로 변경합니다"
+ IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r]|1은;는; |cffffff7f%s|r에서 사용불가능한 설정입니다"
+ IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r]|1은;는; |cffffff7f%s|r에서 사용불가능한 설정값입니다"
+ NO_OPTIONS_AVAILABLE = "가능한 설정이 없습니다"
+ OPTION_HANDLER_NOT_FOUND = "설정 조정값인 |cffffff7f%q|r|1을;를; 찾지 못했습니다."
+ OPTION_HANDLER_NOT_VALID = "설정 조정값이 올바르지 않습니다."
+ OPTION_IS_DISABLED = "|cffffff7f%s|r 설정은 사용할 수 없습니다."
+elseif GetLocale() == "zhCN" then
+ MAP_ONOFF = { [false] = "|cffff0000\229\133\179\233\151\173|r", [true] = "|cff00ff00\229\188\128\229\144\175|r" }
+ USAGE = "\231\148\168\230\179\149"
+ IS_CURRENTLY_SET_TO = "|cffffff7f%s|r \229\189\147\229\137\141\232\162\171\232\174\190\231\189\174 |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOW_SET_TO = "|cffffff7f%s|r \231\142\176\229\156\168\232\162\171\232\174\190\231\189\174\228\184\186 |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] \228\184\141\230\152\175\228\184\128\228\184\170\230\156\137\230\149\136\231\154\132\233\128\137\233\161\185 \228\184\186 |cffffff7f%s|r"
+ IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] \228\184\141\230\152\175\228\184\128\228\184\170\230\156\137\230\149\136\229\128\188 \228\184\186 |cffffff7f%s|r"
+ NO_OPTIONS_AVAILABLE = "\230\178\161\230\156\137\233\128\137\233\161\185\229\143\175\231\148\168"
+ OPTION_HANDLER_NOT_FOUND = "\233\128\137\233\161\185\229\164\132\231\144\134\231\168\139\229\186\143 |cffffff7f%q|r \230\178\161\230\159\165\230\137\190."
+ OPTION_HANDLER_NOT_VALID = "\233\128\137\233\161\185\229\164\132\231\144\134\231\168\139\229\186\143 \230\151\160\230\149\136."
+ OPTION_IS_DISABLED = "\233\128\137\233\161\185 |cffffff7f%s|r \228\184\141\229\174\140\230\149\180."
+elseif GetLocale() == "zhTW" then
+ MAP_ONOFF = { [false] = "|cffff0000關閉|r", [true] = "|cff00ff00開啟|r" }
+ USAGE = "用法"
+ IS_CURRENTLY_SET_TO = "|cffffff7f%s|r 目前的設定為 |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOW_SET_TO = "|cffffff7f%s|r 現在被設定為 |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] 是一個不符合規定的選項,對 |cffffff7f%s|r"
+ IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] 是一個不符合規定的數值,對 |cffffff7f%s|r"
+ NO_OPTIONS_AVAILABLE = "沒有可用的選項處理器。"
+ OPTION_HANDLER_NOT_FOUND = "找不到 |cffffff7f%q|r 選項處理器。"
+ OPTION_HANDLER_NOT_VALID = "選項處理器不符合規定。"
+ OPTION_IS_DISABLED = "|cffffff7f%s|r 已被停用。"
+elseif GetLocale() == "ruRU" then
+ MAP_ONOFF = { [false] = "|cffff0000Выкл|r", [true] = "|cff00ff00Вкл|r" }
+ USAGE = "Использование"
+ IS_CURRENTLY_SET_TO = "|cffffff7f%s|r в настоящее время установлен на |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOW_SET_TO = "|cffffff7f%s|r теперь установлен |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] - недействительная опция для |cffffff7f%s|r"
+ IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] - недействительное значение для |cffffff7f%s|r"
+ NO_OPTIONS_AVAILABLE = "Нет доступных опций"
+ OPTION_HANDLER_NOT_FOUND = "Оператор опции |cffffff7f%q|r не найден."
+ OPTION_HANDLER_NOT_VALID = "Оператор опции недействителен."
+ OPTION_IS_DISABLED = "Опция |cffffff7f%s|r отключена."
+else -- enUS
+ MAP_ONOFF = { [false] = "|cffff0000Off|r", [true] = "|cff00ff00On|r" }
+ USAGE = "Usage"
+ IS_CURRENTLY_SET_TO = "|cffffff7f%s|r is currently set to |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOW_SET_TO = "|cffffff7f%s|r is now set to |cffffff7f[|r%s|cffffff7f]|r"
+ IS_NOT_A_VALID_OPTION_FOR = "[|cffffff7f%s|r] is not a valid option for |cffffff7f%s|r"
+ IS_NOT_A_VALID_VALUE_FOR = "[|cffffff7f%s|r] is not a valid value for |cffffff7f%s|r"
+ NO_OPTIONS_AVAILABLE = "No options available"
+ OPTION_HANDLER_NOT_FOUND = "Option handler |cffffff7f%q|r not found."
+ OPTION_HANDLER_NOT_VALID = "Option handler not valid."
+ OPTION_IS_DISABLED = "Option |cffffff7f%s|r is disabled."
+end
+
+local NONE = NONE or "None"
+
+local AceOO = AceLibrary("AceOO-2.0")
+local AceEvent
+
+local AceConsole = AceOO.Mixin { "Print", "PrintComma", "CustomPrint", "RegisterChatCommand" }
+local Dewdrop
+
+local _G = getfenv(0)
+
+local table_setn
+do
+ local version = GetBuildInfo()
+ if string.find(version, "^2%.") then
+ -- 2.0.0
+ table_setn = function() end
+ else
+ table_setn = table.setn
+ end
+end
+
+local function print(text, name, r, g, b, frame, delay)
+ if not text or string.len(text) == 0 then
+ text = " "
+ end
+ if not name or name == AceConsole then
+ (frame or DEFAULT_CHAT_FRAME):AddMessage(text, r, g, b, nil, delay or 5)
+ else
+ (frame or DEFAULT_CHAT_FRAME):AddMessage("|cffffff78" .. tostring(name) .. ":|r " .. text, r, g, b, nil, delay or 5)
+ end
+end
+
+local tmp
+function AceConsole:CustomPrint(r, g, b, frame, delay, connector, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ a1 = tostring(a1)
+ if string.find(a1, "%%") then
+ print(string.format(a1, tostring(a2), tostring(a3), tostring(a4), tostring(a5), tostring(a6), tostring(a7), tostring(a8), tostring(a9), tostring(a10), tostring(a11), tostring(a12), tostring(a13), tostring(a14), tostring(a15), tostring(a16), tostring(a17), tostring(a18), tostring(a19), tostring(a20)), self, r, g, b, frame or self.printFrame, delay)
+ else
+ if not tmp then
+ tmp = {}
+ end
+ tmp[1] = a1
+ tmp[2] = a2
+ tmp[3] = a3
+ tmp[4] = a4
+ tmp[5] = a5
+ tmp[6] = a6
+ tmp[7] = a7
+ tmp[8] = a8
+ tmp[9] = a9
+ tmp[10] = a10
+ tmp[11] = a11
+ tmp[12] = a12
+ tmp[13] = a13
+ tmp[14] = a14
+ tmp[15] = a15
+ tmp[16] = a16
+ tmp[17] = a17
+ tmp[18] = a18
+ tmp[19] = a19
+ tmp[20] = a20
+ local n = 20
+ while tmp[n] == nil do
+ n = n - 1
+ end
+ table_setn(tmp, n)
+ for k = 1, n do
+ tmp[k] = tostring(tmp[k])
+ end
+ print(table.concat(tmp, connector or " "), self, r, g, b, frame or self.printFrame, delay)
+ for k,v in pairs(tmp) do
+ tmp[k] = nil
+ end
+ table_setn(tmp, 0)
+ end
+end
+
+function AceConsole:Print(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ return AceConsole.CustomPrint(self, nil, nil, nil, nil, nil, " ", a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+end
+
+function AceConsole:PrintComma(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ return AceConsole.CustomPrint(self, nil, nil, nil, nil, nil, ", ", a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+end
+
+local work
+local argwork
+
+local string_gfind = string.gmatch or string.gfind
+
+local function findTableLevel(self, options, chat, text, index, passTable)
+ if not index then
+ index = 1
+ if work then
+ for k,v in pairs(work) do
+ work[k] = nil
+ end
+ table_setn(work, 0)
+ for k,v in pairs(argwork) do
+ argwork[k] = nil
+ end
+ table_setn(argwork, 0)
+ else
+ work = {}
+ argwork = {}
+ end
+ local len = string.len(text)
+ local count
+ repeat
+ text, count = string.gsub(text, "(|cff%x%x%x%x%x%x|Hitem:%d-:%d-:%d-:%d-|h%[[^%]]-) (.-%]|h|r)", "%1\001%2")
+ until count == 0
+ text = string.gsub(text, "(%]|h|r)(|cff%x%x%x%x%x%x|Hitem:%d-:%d-:%d-:%d-|h%[)", "%1 %2")
+ for token in string_gfind(text, "([^%s]+)") do
+ local token = token
+ local num = tonumber(token)
+ if num then
+ token = num
+ else
+ token = string.gsub(token, "\001", " ")
+ end
+ table.insert(work, token)
+ end
+ end
+
+ local path = chat
+ for i = 1, index - 1 do
+ path = path .. " " .. tostring(work[i])
+ end
+
+ if type(options.args) == "table" then
+ local disabled, hidden = options.disabled, options.cmdHidden or options.hidden
+ if hidden then
+ if type(hidden) == "function" then
+ hidden = hidden()
+ elseif type(hidden) == "string" then
+ local handler = options.handler or self
+ if type(handler[hidden]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, hidden))
+ end
+ hidden = handler[hidden](handler)
+ end
+ end
+ if hidden then
+ disabled = true
+ elseif disabled then
+ if type(disabled) == "function" then
+ disabled = disabled()
+ elseif type(disabled) == "string" then
+ local handler = options.handler or self
+ if type(handler[disabled]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, disabled))
+ end
+ disabled = handler[disabled](handler)
+ end
+ end
+ if not disabled then
+ local next = work[index] and string.lower(work[index])
+ if next then
+ for k,v in pairs(options.args) do
+ local good = false
+ if string.lower(k) == next then
+ good = true
+ elseif type(v.aliases) == "table" then
+ for _,alias in ipairs(v.aliases) do
+ if string.lower(alias) == next then
+ good = true
+ break
+ end
+ end
+ elseif type(v.aliases) == "string" and string.lower(v.aliases) == next then
+ good = true
+ end
+ if good then
+ return findTableLevel(options.handler or self, v, chat, text, index + 1, options.pass and options or nil)
+ end
+ end
+ end
+ end
+ end
+ for i = index, table.getn(work) do
+ table.insert(argwork, work[i])
+ end
+ return options, path, argwork, options.handler or self, passTable, passTable and work[index - 1]
+end
+
+local function validateOptionsMethods(self, options, position)
+ if type(options) ~= "table" then
+ return "Options must be a table.", position
+ end
+ self = options.handler or self
+ if options.type == "execute" then
+ if options.func and type(options.func) ~= "string" and type(options.func) ~= "function" then
+ return "func must be a string or function", position
+ end
+ if options.func and type(options.func) == "string" and type(self[options.func]) ~= "function" then
+ return string.format("%q is not a proper function", options.func), position
+ end
+ else
+ if options.get then
+ if type(options.get) ~= "string" and type(options.get) ~= "function" then
+ return "get must be a string or function", position
+ end
+ if type(options.get) == "string" and type(self[options.get]) ~= "function" then
+ return string.format("%q is not a proper function", options.get), position
+ end
+ end
+ if options.set then
+ if type(options.set) ~= "string" and type(options.set) ~= "function" then
+ return "set must be a string or function", position
+ end
+ if type(options.set) == "string" and type(self[options.set]) ~= "function" then
+ return string.format("%q is not a proper function", options.set), position
+ end
+ end
+ if options.validate and type(options.validate) ~= "table" then
+ if type(options.validate) ~= "string" and type(options.validate) ~= "function" then
+ return "validate must be a string or function", position
+ end
+ if type(options.validate) == "string" and type(self[options.validate]) ~= "function" then
+ return string.format("%q is not a proper function", options.validate), position
+ end
+ end
+ end
+ if options.disabled and type(options.disabled) == "string" and type(self[options.disabled]) ~= "function" then
+ return string.format("%q is not a proper function", options.disabled), position
+ end
+ if options.cmdHidden and type(options.cmdHidden) == "string" and type(self[options.cmdHidden]) ~= "function" then
+ return string.format("%q is not a proper function", options.cmdHidden), position
+ end
+ if options.guiHidden and type(options.guiHidden) == "string" and type(self[options.guiHidden]) ~= "function" then
+ return string.format("%q is not a proper function", options.guiHidden), position
+ end
+ if options.hidden and type(options.hidden) == "string" and type(self[options.hidden]) ~= "function" then
+ return string.format("%q is not a proper function", options.hidden), position
+ end
+ if options.type == "group" and type(options.args) == "table" then
+ for k,v in pairs(options.args) do
+ if type(v) == "table" then
+ local newposition
+ if position then
+ newposition = position .. ".args." .. k
+ else
+ newposition = "args." .. k
+ end
+ local err, pos = validateOptionsMethods(self, v, newposition)
+ if err then
+ return err, pos
+ end
+ end
+ end
+ end
+end
+
+local function validateOptions(options, position, baseOptions, fromPass)
+ if not baseOptions then
+ baseOptions = options
+ end
+ if type(options) ~= "table" then
+ return "Options must be a table.", position
+ end
+ local kind = options.type
+ if type(kind) ~= "string" then
+ return '"type" must be a string.', position
+ elseif kind ~= "group" and kind ~= "range" and kind ~= "text" and kind ~= "execute" and kind ~= "toggle" and kind ~= "color" and kind ~= "header" then
+ return '"type" must either be "range", "text", "group", "toggle", "execute", "color", or "header".', position
+ end
+ if options.aliases then
+ if type(options.aliases) ~= "table" and type(options.aliases) ~= "string" then
+ return '"alias" must be a table or string', position
+ end
+ end
+ if not fromPass then
+ if kind == "execute" then
+ if type(options.func) ~= "string" and type(options.func) ~= "function" then
+ return '"func" must be a string or function', position
+ end
+ elseif kind == "range" or kind == "text" or kind == "toggle" then
+ if type(options.set) ~= "string" and type(options.set) ~= "function" then
+ return '"set" must be a string or function', position
+ end
+ if kind == "text" and options.get == false then
+ elseif type(options.get) ~= "string" and type(options.get) ~= "function" then
+ return '"get" must be a string or function', position
+ end
+ elseif kind == "group" and options.pass then
+ if options.pass ~= true then
+ return '"pass" must be either nil, true, or false', position
+ end
+ if not options.func then
+ if type(options.set) ~= "string" and type(options.set) ~= "function" then
+ return '"set" must be a string or function', position
+ end
+ if type(options.get) ~= "string" and type(options.get) ~= "function" then
+ return '"get" must be a string or function', position
+ end
+ elseif type(options.func) ~= "string" and type(options.func) ~= "function" then
+ return '"func" must be a string or function', position
+ end
+ end
+ else
+ if kind == "group" then
+ return 'cannot have "type" = "group" as a subgroup of a passing group', position
+ end
+ end
+ if options ~= baseOptions then
+ if kind == "header" then
+ elseif type(options.desc) ~= "string" then
+ return '"desc" must be a string', position
+ elseif string.len(options.desc) == 0 then
+ return '"desc" cannot be a 0-length string', position
+ end
+ end
+
+ if options ~= baseOptions or kind == "range" or kind == "text" or kind == "toggle" or kind == "color" then
+ if options.type == "header" and not options.cmdName and not options.name then
+ elseif options.cmdName then
+ if type(options.cmdName) ~= "string" then
+ return '"cmdName" must be a string or nil', position
+ elseif string.len(options.cmdName) == 0 then
+ return '"cmdName" cannot be a 0-length string', position
+ end
+ if type(options.guiName) ~= "string" then
+ if not options.guiNameIsMap then
+ return '"guiName" must be a string or nil', position
+ end
+ elseif string.len(options.guiName) == 0 then
+ return '"guiName" cannot be a 0-length string', position
+ end
+ else
+ if type(options.name) ~= "string" then
+ return '"name" must be a string', position
+ elseif string.len(options.name) == 0 then
+ return '"name" cannot be a 0-length string', position
+ end
+ end
+ end
+ if options.guiNameIsMap then
+ if type(options.guiNameIsMap) ~= "boolean" then
+ return '"guiNameIsMap" must be a boolean or nil', position
+ elseif options.type ~= "toggle" then
+ return 'if "guiNameIsMap" is true, then "type" must be set to \'toggle\'', position
+ elseif type(options.map) ~= "table" then
+ return '"map" must be a table', position
+ end
+ end
+ if options.message and type(options.message) ~= "string" then
+ return '"message" must be a string or nil', position
+ end
+ if options.error and type(options.error) ~= "string" then
+ return '"error" must be a string or nil', position
+ end
+ if options.current and type(options.current) ~= "string" then
+ return '"current" must be a string or nil', position
+ end
+ if options.order then
+ if type(options.order) ~= "number" or (-1 < options.order and options.order < 0.999) then
+ return '"order" must be a non-zero number or nil', position
+ end
+ end
+ if options.disabled then
+ if type(options.disabled) ~= "function" and type(options.disabled) ~= "string" and options.disabled ~= true then
+ return '"disabled" must be a function, string, or boolean', position
+ end
+ end
+ if options.cmdHidden then
+ if type(options.cmdHidden) ~= "function" and type(options.cmdHidden) ~= "string" and options.cmdHidden ~= true then
+ return '"cmdHidden" must be a function, string, or boolean', position
+ end
+ end
+ if options.guiHidden then
+ if type(options.guiHidden) ~= "function" and type(options.guiHidden) ~= "string" and options.guiHidden ~= true then
+ return '"guiHidden" must be a function, string, or boolean', position
+ end
+ end
+ if options.hidden then
+ if type(options.hidden) ~= "function" and type(options.hidden) ~= "string" and options.hidden ~= true then
+ return '"hidden" must be a function, string, or boolean', position
+ end
+ end
+ if kind == "text" then
+ if type(options.validate) == "table" then
+ local t = options.validate
+ local iTable = nil
+ for k,v in pairs(t) do
+ if type(k) == "number" then
+ if iTable == nil then
+ iTable = true
+ elseif not iTable then
+ return '"validate" must either have all keys be indexed numbers or strings', position
+ elseif k < 1 or k > table.getn(t) then
+ return '"validate" numeric keys must be indexed properly. >= 1 and <= table.getn', position
+ end
+ else
+ if iTable == nil then
+ iTable = false
+ elseif iTable then
+ return '"validate" must either have all keys be indexed numbers or strings', position
+ end
+ end
+ if type(v) ~= "string" then
+ return '"validate" values must all be strings', position
+ end
+ end
+ else
+ if type(options.usage) ~= "string" then
+ return '"usage" must be a string', position
+ elseif options.validate and type(options.validate) ~= "string" and type(options.validate) ~= "function" then
+ return '"validate" must be a string, function, or table', position
+ end
+ end
+ elseif kind == "range" then
+ if options.min or options.max then
+ if type(options.min) ~= "number" then
+ return '"min" must be a number', position
+ elseif type(options.max) ~= "number" then
+ return '"max" must be a number', position
+ elseif options.min >= options.max then
+ return '"min" must be less than "max"', position
+ end
+ end
+ if options.step then
+ if type(options.step) ~= "number" then
+ return '"step" must be a number', position
+ elseif options.step < 0 then
+ return '"step" must be nonnegative', position
+ end
+ end
+ if options.isPercent and options.isPercent ~= true then
+ return '"isPercent" must either be nil, true, or false', position
+ end
+ elseif kind == "toggle" then
+ if options.map then
+ if type(options.map) ~= "table" then
+ return '"map" must be a table', position
+ elseif type(options.map[true]) ~= "string" then
+ return '"map[true]" must be a string', position
+ elseif type(options.map[false]) ~= "string" then
+ return '"map[false]" must be a string', position
+ end
+ end
+ elseif kind == "color" then
+ if options.hasAlpha and options.hasAlpha ~= true then
+ return '"hasAlpha" must be nil, true, or false', position
+ end
+ elseif kind == "group" then
+ if options.pass and options.pass ~= true then
+ return '"pass" must be nil, true, or false', position
+ end
+ if type(options.args) ~= "table" then
+ return '"args" must be a table', position
+ end
+ for k,v in pairs(options.args) do
+ if type(k) ~= "string" then
+ return '"args" keys must be strings', position
+ elseif string.find(k, "%s") then
+ return string.format('"args" keys must not include spaces. %q is not appropriate.', k), position
+ elseif string.len(k) == 0 then
+ return '"args" keys must not be 0-length strings.', position
+ end
+ if type(v) ~= "table" then
+ return '"args" values must be tables', position and position .. "." .. k or k
+ end
+ local newposition
+ if position then
+ newposition = position .. ".args." .. k
+ else
+ newposition = "args." .. k
+ end
+ local err, pos = validateOptions(v, newposition, baseOptions, options.pass)
+ if err then
+ return err, pos
+ end
+ end
+ end
+end
+
+local colorTable
+local colorFunc
+local colorCancelFunc
+
+local order
+
+local mysort_args
+local mysort
+
+local function printUsage(self, handler, realOptions, options, path, args, quiet, filter)
+ if filter then
+ filter = "^" .. string.gsub(filter, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
+ end
+ local hidden, disabled = options.cmdHidden or options.hidden, options.disabled
+ if hidden then
+ if type(hidden) == "function" then
+ hidden = hidden()
+ elseif type(hidden) == "string" then
+ if type(handler[hidden]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, hidden))
+ end
+ hidden = handler[hidden](handler)
+ end
+ end
+ if hidden then
+ disabled = true
+ elseif disabled then
+ if type(disabled) == "function" then
+ disabled = disabled()
+ elseif type(disabled) == "string" then
+ if type(handler[disabled]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, disabled))
+ end
+ disabled = handler[disabled](handler)
+ end
+ end
+ local kind = string.lower(options.type or "group")
+ if disabled then
+ print(string.format(OPTION_IS_DISABLED, path), realOptions.cmdName or realOptions.name or self)
+ elseif kind == "text" then
+ local var
+ if passTable then
+ if not passTable.get then
+ elseif type(passTable.get) == "function" then
+ var = passTable.get(passValue)
+ else
+ local handler = passTable.handler or handler
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ var = handler[passTable.get](handler, passValue)
+ end
+ else
+ if not options.get then
+ elseif type(options.get) == "function" then
+ var = options.get()
+ else
+ local handler = options.handler or handler
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ var = handler[options.get](handler)
+ end
+ end
+
+ local usage
+ if type(options.validate) == "table" then
+ if filter then
+ if not order then
+ order = {}
+ end
+ for k,v in pairs(options.validate) do
+ if string.find(v, filter) then
+ table.insert(order, v)
+ end
+ end
+ usage = "{" .. table.concat(order, " || ") .. "}"
+ for k in pairs(order) do
+ order[k] = nil
+ end
+ table_setn(order, 0)
+ else
+ if not order then
+ order = {}
+ end
+ for k,v in pairs(options.validate) do
+ table.insert(order, v)
+ end
+ usage = "{" .. table.concat(order, " || ") .. "}"
+ for k in pairs(order) do
+ order[k] = nil
+ end
+ table_setn(order, 0)
+ end
+ var = options.validate[var] or var
+ else
+ usage = options.usage or ""
+ end
+ if not quiet then
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, usage), realOptions.cmdName or realOptions.name or self)
+ end
+ if (passTable and passTable.get) or options.get then
+ print(string.format(options.current or IS_CURRENTLY_SET_TO, tostring(options.cmdName or options.name), tostring(var or NONE)))
+ end
+ elseif kind == "range" then
+ local var
+ if passTable then
+ if type(passTable.get) == "function" then
+ var = passTable.get(passValue)
+ else
+ local handler = passTable.handler or handler
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ var = handler[passTable.get](handler, passValue)
+ end
+ else
+ if type(options.get) == "function" then
+ var = options.get()
+ else
+ local handler = options.handler or handler
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ var = handler[options.get](handler)
+ end
+ end
+
+ local usage
+ local min = options.min or 0
+ local max = options.max or 1
+ if options.isPercent then
+ min, max = min * 100, max * 100
+ var = tostring(var * 100) .. "%"
+ end
+ local bit = "-"
+ if min < 0 or max < 0 then
+ bit = " - "
+ end
+ usage = string.format("(%s%s%s)", min, bit, max)
+ if not quiet then
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, usage), realOptions.cmdName or realOptions.name or self)
+ end
+ print(string.format(options.current or IS_CURRENTLY_SET_TO, tostring(options.cmdName or options.name), tostring(var or NONE)))
+ elseif kind == "group" then
+ local usage
+ if next(options.args) then
+ if not order then
+ order = {}
+ end
+ for k,v in pairs(options.args) do
+ if v.type ~= "header" then
+ local hidden = v.cmdHidden or v.hidden
+ if hidden then
+ if type(hidden) == "function" then
+ hidden = hidden()
+ elseif type(hidden) == "string" then
+ local handler = v.handler or handler
+ if type(handler[hidden]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, hidden))
+ end
+ hidden = handler[hidden](handler)
+ end
+ end
+ if not hidden then
+ if filter then
+ if string.find(k, filter) then
+ table.insert(order, k)
+ elseif type(v.aliases) == "table" then
+ for _,bit in ipairs(v.aliases) do
+ if string.find(v.aliases, filter) then
+ table.insert(order, k)
+ break
+ end
+ end
+ elseif type(v.aliases) == "string" then
+ if string.find(v.aliases, filter) then
+ table.insert(order, k)
+ end
+ end
+ else
+ table.insert(order, k)
+ end
+ end
+ end
+ end
+ if not mysort then
+ mysort = function(a, b)
+ local alpha, bravo = mysort_args[a], mysort_args[b]
+ local alpha_order = alpha and alpha.order or 100
+ local bravo_order = bravo and bravo.order or 100
+ if alpha_order == bravo_order then
+ return tostring(a) < tostring(b)
+ else
+ if alpha_order < 0 then
+ if bravo_order > 0 then
+ return false
+ end
+ else
+ if bravo_order < 0 then
+ return true
+ end
+ end
+ if alpha_order > 0 and bravo_order > 0 then
+ return tostring(a) < tostring(b)
+ end
+ return alpha_order < bravo_order
+ end
+ end
+ end
+ mysort_args = options.args
+ table.sort(order, mysort)
+ mysort_args = nil
+ if not quiet then
+ if options == realOptions then
+ if options.desc then
+ print(tostring(options.desc), realOptions.cmdName or realOptions.name or self)
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"))
+ elseif self.description or self.notes then
+ print(tostring(self.description or self.notes), realOptions.cmdName or realOptions.name or self)
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"))
+ else
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"), realOptions.cmdName or realOptions.name or self)
+ end
+ else
+ if options.desc then
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"), realOptions.cmdName or realOptions.name or self)
+ print(tostring(options.desc))
+ else
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, "{" .. table.concat(order, " || ") .. "}"), realOptions.cmdName or realOptions.name or self)
+ end
+ end
+ end
+ for _,k in ipairs(order) do
+ local v = options.args[k]
+ if v then
+ local disabled = v.disabled
+ if disabled then
+ if type(disabled) == "function" then
+ disabled = disabled()
+ elseif type(disabled) == "string" then
+ local handler = v.handler or handler
+ if type(handler[disabled]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, disabled))
+ end
+ disabled = handler[disabled](handler)
+ end
+ end
+ if type(v.aliases) == "table" then
+ k = k .. " || " .. table.concat(v.aliases, " || ")
+ elseif type(v.aliases) == "string" then
+ k = k .. " || " .. v.aliases
+ end
+ if v.get then
+ local a1,a2,a3,a4
+ if type(v.get) == "function" then
+ if options.pass then
+ a1,a2,a3,a4 = v.get(k)
+ else
+ a1,a2,a3,a4 = v.get()
+ end
+ else
+ local handler = v.handler or handler
+ if type(handler[v.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, v.get))
+ end
+ if options.pass then
+ a1,a2,a3,a4 = handler[v.get](handler, k)
+ else
+ a1,a2,a3,a4 = handler[v.get](handler)
+ end
+ end
+ if v.type == "color" then
+ if v.hasAlpha then
+ if not a1 or not a2 or not a3 or not a4 then
+ s = NONE
+ else
+ s = string.format("|c%02x%02x%02x%02x%02x%02x%02x%02x|r", a4*255, a1*255, a2*255, a3*255, a4*255, a1*255, a2*255, a3*255)
+ end
+ else
+ if not a1 or not a2 or not a3 then
+ s = NONE
+ else
+ s = string.format("|cff%02x%02x%02x%02x%02x%02x|r", a1*255, a2*255, a3*255, a1*255, a2*255, a3*255)
+ end
+ end
+ elseif v.type == "toggle" then
+ if v.map then
+ s = tostring(v.map[a1 and true or false] or NONE)
+ else
+ s = tostring(MAP_ONOFF[a1 and true or false] or NONE)
+ end
+ elseif v.type == "range" then
+ if v.isPercent then
+ s = tostring(a1 * 100) .. "%"
+ else
+ s = tostring(a1)
+ end
+ elseif v.type == "text" and type(v.validate) == "table" then
+ s = tostring(v.validate[a1] or a1)
+ else
+ s = tostring(a1 or NONE)
+ end
+ if disabled then
+ local s = string.gsub(s, "|cff%x%x%x%x%x%x(.-)|r", "%1")
+ local desc = string.gsub(v.desc or NONE, "|cff%x%x%x%x%x%x(.-)|r", "%1")
+ print(string.format("|cffcfcfcf - %s: [%s] %s|r", k, s, desc))
+ else
+ print(string.format(" - |cffffff7f%s: [|r%s|cffffff7f]|r %s", k, s, v.desc or NONE))
+ end
+ else
+ if disabled then
+ local desc = string.gsub(v.desc or NONE, "|cff%x%x%x%x%x%x(.-)|r", "%1")
+ print(string.format("|cffcfcfcf - %s: %s", k, desc))
+ else
+ print(string.format(" - |cffffff7f%s:|r %s", k, v.desc or NONE))
+ end
+ end
+ end
+ end
+ for k in pairs(order) do
+ order[k] = nil
+ end
+ table_setn(order, 0)
+ else
+ if options.desc then
+ desc = options.desc
+ print(string.format("|cffffff7f%s:|r %s", USAGE, path), realOptions.cmdName or realOptions.name or self)
+ print(tostring(options.desc))
+ elseif options == realOptions and (self.description or self.notes) then
+ print(tostring(self.description or self.notes), realOptions.cmdName or realOptions.name or self)
+ print(string.format("|cffffff7f%s:|r %s", USAGE, path))
+ else
+ print(string.format("|cffffff7f%s:|r %s", USAGE, path), realOptions.cmdName or realOptions.name or self)
+ end
+ print(NO_OPTIONS_AVAILABLE)
+ end
+ end
+end
+
+local function handlerFunc(self, chat, msg, options)
+ if not msg then
+ msg = ""
+ else
+ msg = string.gsub(msg, "^%s*(.-)%s*$", "%1")
+ msg = string.gsub(msg, "%s+", " ")
+ end
+
+ local realOptions = options
+ local options, path, args, handler, passTable, passValue = findTableLevel(self, options, chat, msg)
+
+ local hidden, disabled = options.cmdHidden or options.hidden, options.disabled
+ if hidden then
+ if type(hidden) == "function" then
+ hidden = hidden()
+ elseif type(hidden) == "string" then
+ if type(handler[hidden]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, hidden))
+ end
+ hidden = handler[hidden](handler)
+ end
+ end
+ if hidden then
+ disabled = true
+ elseif disabled then
+ if type(disabled) == "function" then
+ disabled = disabled()
+ elseif type(disabled) == "string" then
+ if type(handler[disabled]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, disabled))
+ end
+ disabled = handler[disabled](handler)
+ end
+ end
+ local _G_this = this
+ local kind = string.lower(options.type or "group")
+ if disabled then
+ print(string.format(OPTION_IS_DISABLED, path), realOptions.cmdName or realOptions.name or self)
+ elseif kind == "text" then
+ if table.getn(args) > 0 then
+ if (type(options.validate) == "table" and table.getn(args) > 1) or (type(options.validate) ~= "table" and not options.input) then
+ local arg = table.concat(args, " ")
+ for k,v in pairs(args) do
+ args[k] = nil
+ end
+ table_setn(args, 0)
+ table.insert(args, arg)
+ end
+ if options.validate then
+ local good
+ if type(options.validate) == "function" then
+ good = options.validate(unpack(args))
+ elseif type(options.validate) == "table" then
+ local arg = args[1]
+ arg = string.lower(tostring(arg))
+ for k,v in pairs(options.validate) do
+ if string.lower(v) == arg then
+ args[1] = type(k) == "string" and k or v
+ good = true
+ break
+ end
+ end
+ if not good and type((next(options.validate))) == "string" then
+ for k,v in pairs(options.validate) do
+ if type(k) == "string" and string.lower(k) == arg then
+ args[1] = k
+ good = true
+ break
+ end
+ end
+ end
+ else
+ if type(handler[options.validate]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.validate))
+ end
+ good = handler[options.validate](handler, unpack(args))
+ end
+ if not good then
+ local usage
+ if type(options.validate) == "table" then
+ if not order then
+ order = {}
+ end
+ for k,v in pairs(options.validate) do
+ table.insert(order, v)
+ end
+ usage = "{" .. table.concat(order, " || ") .. "}"
+ for k in pairs(order) do
+ order[k] = nil
+ end
+ table_setn(order, 0)
+ else
+ usage = options.usage or ""
+ end
+ print(string.format(options.error or IS_NOT_A_VALID_OPTION_FOR, tostring(table.concat(args, " ")), path), realOptions.cmdName or realOptions.name or self)
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, usage))
+ return
+ end
+ end
+
+ local var
+ if passTable then
+ if not passTable.get then
+ elseif type(passTable.get) == "function" then
+ var = passTable.get(passValue)
+ else
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ var = handler[passTable.get](handler, passValue)
+ end
+ else
+ if not options.get then
+ elseif type(options.get) == "function" then
+ var = options.get()
+ else
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ var = handler[options.get](handler)
+ end
+ end
+
+ if var ~= args[1] then
+ if passTable then
+ if type(passTable.set) == "function" then
+ passTable.set(passValue, unpack(args))
+ else
+ if type(handler[passTable.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.set))
+ end
+ handler[passTable.set](handler, passTable.set, unpack(args))
+ end
+ else
+ if type(options.set) == "function" then
+ options.set(unpack(args))
+ else
+ if type(handler[options.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.set))
+ end
+ handler[options.set](handler, unpack(args))
+ end
+ end
+ end
+ end
+
+ if table.getn(args) > 0 then
+ local var
+ if passTable then
+ if not passTable.get then
+ elseif type(passTable.get) == "function" then
+ var = passTable.get(passValue)
+ else
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ var = handler[passTable.get](handler, passValue)
+ end
+ else
+ if not options.get then
+ elseif type(options.get) == "function" then
+ var = options.get()
+ else
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ var = handler[options.get](handler)
+ end
+ end
+ if type(options.validate) == "table" then
+ var = options.validate[var] or var
+ end
+ if (passTable and passTable.get) or options.get then
+ print(string.format(options.message or IS_NOW_SET_TO, tostring(options.cmdName or options.name), tostring(var or NONE)), realOptions.cmdName or realOptions.name or self)
+ end
+ if var == args[1] then
+ return
+ end
+ else
+ printUsage(self, handler, realOptions, options, path, args)
+ return
+ end
+ elseif kind == "execute" then
+ if passTable then
+ if type(passFunc) == "function" then
+ set(passValue)
+ else
+ if type(handler[passFunc]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passFunc))
+ end
+ handler[passFunc](handler, passValue)
+ end
+ else
+ local ret, msg
+ if type(options.func) == "function" then
+ options.func()
+ else
+ local handler = options.handler or self
+ if type(handler[options.func]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.func))
+ end
+ handler[options.func](handler)
+ end
+ end
+ elseif kind == "toggle" then
+ local var
+ if passTable then
+ if type(passTable.get) == "function" then
+ var = passTable.get(passValue)
+ else
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ var = handler[passTable.get](handler, passValue)
+ end
+ if type(passTable.set) == "function" then
+ passTable.set(passValue, not var)
+ else
+ if type(handler[passTable.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.set))
+ end
+ handler[passTable.set](handler, passValue, not var)
+ end
+ if type(passTable.get) == "function" then
+ var = passTable.get(passValue)
+ else
+ var = handler[passTable.get](handler, passValue)
+ end
+ else
+ local handler = options.handler or self
+ if type(options.get) == "function" then
+ var = options.get()
+ else
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ var = handler[options.get](handler)
+ end
+ if type(options.set) == "function" then
+ options.set(not var)
+ else
+ if type(handler[options.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.set))
+ end
+ handler[options.set](handler, not var)
+ end
+ if type(options.get) == "function" then
+ var = options.get()
+ else
+ var = handler[options.get](handler)
+ end
+ end
+
+ print(string.format(options.message or IS_NOW_SET_TO, tostring(options.cmdName or options.name), (options.map or MAP_ONOFF)[var and true or false] or NONE), realOptions.cmdName or realOptions.name or self)
+ elseif kind == "range" then
+ local arg
+ if table.getn(args) <= 1 then
+ arg = args[1]
+ else
+ arg = table.concat(args, " ")
+ end
+
+ if arg then
+ local min = options.min or 0
+ local max = options.max or 1
+ local good = false
+ if type(arg) == "number" then
+ if options.isPercent then
+ arg = arg / 100
+ end
+
+ if arg >= min and arg <= max then
+ good = true
+ end
+
+ if good and type(options.step) == "number" and options.step > 0 then
+ local step = options.step
+ arg = math.floor((arg - min) / step + 0.5) * step + min
+ if arg > max then
+ arg = max
+ elseif arg < min then
+ arg = min
+ end
+ end
+ end
+ if not good then
+ local usage
+ local min = options.min or 0
+ local max = options.max or 1
+ if options.isPercent then
+ min, max = min * 100, max * 100
+ end
+ local bit = "-"
+ if min < 0 or max < 0 then
+ bit = " - "
+ end
+ usage = string.format("(%s%s%s)", min, bit, max)
+ print(string.format(options.error or IS_NOT_A_VALID_VALUE_FOR, tostring(arg), path), realOptions.cmdName or realOptions.name or self)
+ print(string.format("|cffffff7f%s:|r %s %s", USAGE, path, usage))
+ return
+ end
+
+ local var
+ if passTable then
+ if type(passTable.get) == "function" then
+ var = passTable.get(passValue)
+ else
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ var = handler[passTable.get](handler, passValue)
+ end
+ else
+ if type(options.get) == "function" then
+ var = options.get()
+ else
+ local handler = options.handler or self
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ var = handler[options.get](handler)
+ end
+ end
+
+ if var ~= arg then
+ if passTable then
+ if type(passTable.set) == "function" then
+ passTable.set(passValue, arg)
+ else
+ if type(handler[passTable.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.set))
+ end
+ handler[passTable.set](handler, passValue, arg)
+ end
+ else
+ if type(options.set) == "function" then
+ options.set(arg)
+ else
+ local handler = options.handler or self
+ if type(handler[options.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.set))
+ end
+ handler[options.set](handler, arg)
+ end
+ end
+ end
+ end
+
+ if arg then
+ local var
+ if passTable then
+ if type(passTable.get) == "function" then
+ var = passTable.get(passValue)
+ else
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ var = handler[passTable.get](handler, passValue)
+ end
+ else
+ if type(options.get) == "function" then
+ var = options.get()
+ else
+ local handler = options.handler or self
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ var = handler[options.get](handler)
+ end
+ end
+
+ if var and options.isPercent then
+ var = tostring(var * 100) .. "%"
+ end
+ print(string.format(options.message or IS_NOW_SET_TO, tostring(options.cmdName or options.name), tostring(var or NONE)), realOptions.cmdName or realOptions.name or self)
+ if var == arg then
+ return
+ end
+ else
+ printUsage(self, handler, realOptions, options, path, args)
+ return
+ end
+ elseif kind == "color" then
+ if table.getn(args) > 0 then
+ local r,g,b,a
+ if table.getn(args) == 1 then
+ local arg = tostring(args[1])
+ if options.hasAlpha then
+ if string.len(arg) == 8 and string.find(arg, "^%x*$") then
+ r,g,b,a = tonumber(string.sub(arg, 1, 2), 16) / 255, tonumber(string.sub(arg, 3, 4), 16) / 255, tonumber(string.sub(arg, 5, 6), 16) / 255, tonumber(string.sub(arg, 7, 8), 16) / 255
+ end
+ else
+ if string.len(arg) == 6 and string.find(arg, "^%x*$") then
+ r,g,b = tonumber(string.sub(arg, 1, 2), 16) / 255, tonumber(string.sub(arg, 3, 4), 16) / 255, tonumber(string.sub(arg, 5, 6), 16) / 255
+ end
+ end
+ elseif table.getn(args) == 4 and options.hasAlpha then
+ local a1,a2,a3,a4 = args[1], args[2], args[3], args[4]
+ if type(a1) == "number" and type(a2) == "number" and type(a3) == "number" and type(a4) == "number" and a1 <= 1 and a2 <= 1 and a3 <= 1 and a4 <= 1 then
+ r,g,b,a = a1,a2,a3,a4
+ elseif (type(a1) == "number" or string.len(a1) == 2) and string.find(a1, "^%x*$") and (type(a2) == "number" or string.len(a2) == 2) and string.find(a2, "^%x*$") and (type(a3) == "number" or string.len(a3) == 2) and string.find(a3, "^%x*$") and (type(a4) == "number" or string.len(a4) == 2) and string.find(a4, "^%x*$") then
+ r,g,b,a = tonumber(a1, 16) / 255, tonumber(a2, 16) / 255, tonumber(a3, 16) / 255, tonumber(a4, 16) / 255
+ end
+ elseif table.getn(args) == 3 and not options.hasAlpha then
+ local a1,a2,a3 = args[1], args[2], args[3]
+ if type(a1) == "number" and type(a2) == "number" and type(a3) == "number" and a1 <= 1 and a2 <= 1 and a3 <= 1 then
+ r,g,b = a1,a2,a3
+ elseif (type(a1) == "number" or string.len(a1) == 2) and string.find(a1, "^%x*$") and (type(a2) == "number" or string.len(a2) == 2) and string.find(a2, "^%x*$") and (type(a3) == "number" or string.len(a3) == 2) and string.find(a3, "^%x*$") then
+ r,g,b = tonumber(a1, 16) / 255, tonumber(a2, 16) / 255, tonumber(a3, 16) / 255
+ end
+ end
+ if not r then
+ print(string.format(options.error or IS_NOT_A_VALID_OPTION_FOR, table.concat(args, ' '), path), realOptions.cmdName or realOptions.name or self)
+ print(string.format("|cffffff7f%s:|r %s {0-1} {0-1} {0-1}%s", USAGE, path, options.hasAlpha and " {0-1}" or ""))
+ return
+ end
+ if passTable then
+ if type(passTable.set) == "function" then
+ passTable.set(passValue, r,g,b,a)
+ else
+ if type(handler[passTable.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.set))
+ end
+ handler[passTable.set](handler, passValue, r,g,b,a)
+ end
+ else
+ if type(options.set) == "function" then
+ options.set(r,g,b,a)
+ else
+ if type(handler[options.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.set))
+ end
+ handler[options.set](handler, r,g,b,a)
+ end
+ end
+
+ local r,g,b,a
+ if passTable then
+ if type(passTable.get) == "function" then
+ r,g,b,a = passTable.get(passValue)
+ else
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ r,g,b,a = handler[passTable.get](handler, passValue)
+ end
+ else
+ if type(options.get) == "function" then
+ r,g,b,a = options.get()
+ else
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ r,g,b,a = handler[options.get](handler)
+ end
+ end
+
+ local s
+ if type(r) == "number" and type(g) == "number" and type(b) == "number" then
+ if options.hasAlpha and type(a) == "number" then
+ s = string.format("|c%02x%02x%02x%02x%02x%02x%02x%02x|r", a*255, r*255, g*255, b*255, r*255, g*255, b*255, a*255)
+ else
+ s = string.format("|cff%02x%02x%02x%02x%02x%02x|r", r*255, g*255, b*255, r*255, g*255, b*255)
+ end
+ else
+ s = NONE
+ end
+ print(string.format(options.message or IS_NOW_SET_TO, tostring(options.cmdName or options.name), s), realOptions.cmdName or realOptions.name or self)
+ else
+ local r,g,b,a
+ if passTable then
+ if type(passTable.get) == "function" then
+ r,g,b,a = passTable.get(passValue)
+ else
+ if type(handler[passTable.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, passTable.get))
+ end
+ r,g,b,a = handler[passTable.get](handler, passValue)
+ end
+ else
+ if type(options.get) == "function" then
+ r,g,b,a = options.get()
+ else
+ if type(handler[options.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, options.get))
+ end
+ r,g,b,a = handler[options.get](handler)
+ end
+ end
+
+ if not colorTable then
+ colorTable = {}
+ local t = colorTable
+
+ if ColorPickerOkayButton then
+ local ColorPickerOkayButton_OnClick = ColorPickerOkayButton:GetScript("OnClick")
+ ColorPickerOkayButton:SetScript("OnClick", function()
+ if ColorPickerOkayButton_OnClick then
+ ColorPickerOkayButton_OnClick()
+ end
+ if t.active then
+ ColorPickerFrame.cancelFunc = nil
+ ColorPickerFrame.func = nil
+ ColorPickerFrame.opacityFunc = nil
+ local r,g,b,a
+ if t.passValue then
+ if type(t.get) == "function" then
+ r,g,b,a = t.get(t.passValue)
+ else
+ if type(t.handler[t.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, t.get))
+ end
+ r,g,b,a = t.handler[t.get](t.handler, t.passValue)
+ end
+ else
+ if type(t.get) == "function" then
+ r,g,b,a = t.get()
+ else
+ if type(t.handler[t.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, t.get))
+ end
+ r,g,b,a = t.handler[t.get](t.handler)
+ end
+ end
+ if r ~= t.r or g ~= t.g or b ~= t.b or (t.hasAlpha and a ~= t.a) then
+ local s
+ if type(r) == "number" and type(g) == "number" and type(b) == "number" then
+ if t.hasAlpha and type(a) == "number" then
+ s = string.format("|c%02x%02x%02x%02x%02x%02x%02x%02x|r", a*255, r*255, g*255, b*255, r*255, g*255, b*255, a*255)
+ else
+ s = string.format("|cff%02x%02x%02x%02x%02x%02x|r", r*255, g*255, b*255, r*255, g*255, b*255)
+ end
+ else
+ s = NONE
+ end
+ print(string.format(t.message, tostring(t.name), s), t.realOptions.cmdName or t.realOptions.name or self)
+ end
+ for k,v in pairs(t) do
+ t[k] = nil
+ end
+ end
+ end)
+ end
+ else
+ for k,v in pairs(colorTable) do
+ colorTable[k] = nil
+ end
+ end
+
+ if type(r) ~= "number" or type(g) ~= "number" or type(b) ~= "number" then
+ r,g,b = 1, 1, 1
+ end
+ if type(a) ~= "number" then
+ a = 1
+ end
+ local t = colorTable
+ t.r = r
+ t.g = g
+ t.b = b
+ if hasAlpha then
+ t.a = a
+ end
+ t.realOptions = realOptions
+ t.hasAlpha = options.hasAlpha
+ t.handler = handler
+ t.set = passTable and passTable.set or options.set
+ t.get = passTable and passTable.get or options.get
+ t.name = options.cmdName or options.name
+ t.message = options.message or IS_NOW_SET_TO
+ t.passValue = passValue
+ t.active = true
+
+ if not colorFunc then
+ colorFunc = function()
+ local r,g,b = ColorPickerFrame:GetColorRGB()
+ if t.hasAlpha then
+ local a = 1 - OpacitySliderFrame:GetValue()
+ if type(t.set) == "function" then
+ if t.passValue then
+ t.set(t.passValue, r,g,b,a)
+ else
+ t.set(r,g,b,a)
+ end
+ else
+ if type(t.handler[t.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, t.set))
+ end
+ if t.passValue then
+ t.handler[t.set](t.handler, t.passValue, r,g,b,a)
+ else
+ t.handler[t.set](t.handler, r,g,b,a)
+ end
+ end
+ else
+ if type(t.set) == "function" then
+ if t.passValue then
+ t.set(t.passValue, r,g,b)
+ else
+ t.set(r,g,b)
+ end
+ else
+ if type(t.handler[t.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, t.set))
+ end
+ if t.passValue then
+ t.handler[t.set](t.handler, t.passValue, r,g,b)
+ else
+ t.handler[t.set](t.handler, r,g,b)
+ end
+ end
+ end
+ end
+ end
+
+ ColorPickerFrame.func = colorFunc
+ ColorPickerFrame.hasOpacity = options.hasAlpha
+ if options.hasAlpha then
+ ColorPickerFrame.opacityFunc = ColorPickerFrame.func
+ ColorPickerFrame.opacity = 1 - a
+ end
+ ColorPickerFrame:SetColorRGB(r,g,b)
+
+ if not colorCancelFunc then
+ colorCancelFunc = function()
+ if t.hasAlpha then
+ if type(t.set) == "function" then
+ if t.passValue then
+ t.set(t.passValue, t.r,t.g,t.b,t.a)
+ else
+ t.set(t.r,t.g,t.b,t.a)
+ end
+ else
+ if type(t.handler[t.get]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, t.get))
+ end
+ if t.passValue then
+ t.handler[t.set](t.handler, t.passValue, t.r,t.g,t.b,t.a)
+ else
+ t.handler[t.set](t.handler, t.r,t.g,t.b,t.a)
+ end
+ end
+ else
+ if type(t.set) == "function" then
+ if t.passValue then
+ t.set(t.passValue, t.r,t.g,t.b)
+ else
+ t.set(t.r,t.g,t.b)
+ end
+ else
+ if type(t.handler[t.set]) ~= "function" then
+ AceConsole:error("%s: %s", tostring(handler), string.format(OPTION_HANDLER_NOT_FOUND, t.set))
+ end
+ if t.passValue then
+ t.handler[t.set](t.handler, t.passValue, t.r,t.g,t.b)
+ else
+ t.handler[t.set](t.handler, t.r,t.g,t.b)
+ end
+ end
+ end
+ for k,v in pairs(t) do
+ t[k] = nil
+ end
+ ColorPickerFrame.cancelFunc = nil
+ ColorPickerFrame.func = nil
+ ColorPickerFrame.opacityFunc = nil
+ end
+ end
+
+ ColorPickerFrame.cancelFunc = colorCancelFunc
+
+ ShowUIPanel(ColorPickerFrame)
+ end
+ return
+ elseif kind == "group" then
+ if table.getn(args) == 0 then
+ printUsage(self, handler, realOptions, options, path, args)
+ else
+ -- invalid argument
+ print(string.format(options.error or IS_NOT_A_VALID_OPTION_FOR, args[1], path), realOptions.cmdName or realOptions.name or self)
+ end
+ return
+ end
+ this = _G_this
+ if Dewdrop then
+ Dewdrop:Refresh(1)
+ Dewdrop:Refresh(2)
+ Dewdrop:Refresh(3)
+ Dewdrop:Refresh(4)
+ Dewdrop:Refresh(5)
+ end
+end
+
+local external
+function AceConsole:RegisterChatCommand(slashCommands, options, name)
+ if type(slashCommands) ~= "table" and slashCommands ~= false then
+ AceConsole:error("Bad argument #2 to `RegisterChatCommand' (expected table, got %s)", type(slashCommands))
+ end
+ if not slashCommands and type(name) ~= "string" then
+ AceConsole:error("Bad argument #4 to `RegisterChatCommand' (expected string, got %s)", type(name))
+ end
+ if type(options) ~= "table" and type(options) ~= "function" and options ~= nil then
+ AceConsole:error("Bad argument #3 to `RegisterChatCommand' (expected table, function, or nil, got %s)", type(options))
+ end
+ if name then
+ if type(name) ~= "string" then
+ AceConsole:error("Bad argument #4 to `RegisterChatCommand' (expected string or nil, got %s)", type(name))
+ elseif not string.find(name, "^%w+$") or string.upper(name) ~= name or string.len(name) == 0 then
+ AceConsole:error("Argument #4 must be an uppercase, letters-only string with at least 1 character")
+ end
+ end
+ if slashCommands then
+ if table.getn(slashCommands) == 0 then
+ AceConsole:error("Argument #2 to `RegisterChatCommand' must include at least one string")
+ end
+
+ for k,v in pairs(slashCommands) do
+ if type(k) ~= "number" then
+ AceConsole:error("All keys in argument #2 to `RegisterChatCommand' must be numbers")
+ end
+ if type(v) ~= "string" then
+ AceConsole:error("All values in argument #2 to `RegisterChatCommand' must be strings")
+ elseif not string.find(v, "^/[A-Za-z][A-Za-z0-9_]*$") then
+ AceConsole:error("All values in argument #2 to `RegisterChatCommand' must be in the form of \"/word\"")
+ end
+ end
+ end
+
+ if not options then
+ options = {
+ type = 'group',
+ args = {},
+ handler = self
+ }
+ end
+
+ if type(options) == "table" then
+ local err, position = validateOptions(options)
+ if err then
+ if position then
+ AceConsole:error(position .. ": " .. err)
+ else
+ AceConsole:error(err)
+ end
+ end
+
+ if not options.handler then
+ options.handler = self
+ end
+
+ if options.handler == self and string.lower(options.type) == "group" and self.class then
+ AceConsole:InjectAceOptionsTable(self, options)
+ end
+ end
+
+ local chat
+ if slashCommands then
+ chat = slashCommands[1]
+ else
+ chat = _G["SLASH_"..name..1]
+ end
+
+ local handler
+ if type(options) == "function" then
+ handler = options
+ for k,v in pairs(_G) do
+ if handler == v then
+ local k = k
+ handler = function(msg)
+ return _G[k](msg)
+ end
+ end
+ end
+ else
+ function handler(msg)
+ handlerFunc(self, chat, msg, options)
+ end
+ end
+
+ if not _G.SlashCmdList then
+ _G.SlashCmdList = {}
+ end
+
+ if not name then
+ repeat
+ name = string.char(math.random(26) + string.byte('A') - 1) .. string.char(math.random(26) + string.byte('A') - 1) .. string.char(math.random(26) + string.byte('A') - 1) .. string.char(math.random(26) + string.byte('A') - 1) .. string.char(math.random(26) + string.byte('A') - 1) .. string.char(math.random(26) + string.byte('A') - 1) .. string.char(math.random(26) + string.byte('A') - 1) .. string.char(math.random(26) + string.byte('A') - 1)
+ until not _G.SlashCmdList[name]
+ end
+
+ if slashCommands then
+ if _G.SlashCmdList[name] then
+ local i = 0
+ while true do
+ i = i + 1
+ if _G["SLASH_"..name..i] then
+ _G["SLASH_"..name..i] = nil
+ else
+ break
+ end
+ end
+ end
+
+ local i = 0
+ for _,command in ipairs(slashCommands) do
+ i = i + 1
+ _G["SLASH_"..name..i] = command
+ if string.lower(command) ~= command then
+ i = i + 1
+ _G["SLASH_"..name..i] = string.lower(command)
+ end
+ end
+ end
+ _G.SlashCmdList[name] = handler
+ if self ~= AceConsole and self.slashCommand == nil then
+ self.slashCommand = chat
+ end
+
+ if not AceEvent and AceLibrary:HasInstance("AceEvent-2.0") then
+ external(AceConsole, "AceEvent-2.0", AceLibrary("AceEvent-2.0"))
+ end
+ if AceEvent then
+ if not AceConsole.nextAddon then
+ AceConsole.nextAddon = {}
+ end
+ if type(options) == "table" then
+ AceConsole.nextAddon[self] = options
+ if not self.playerLogin then
+ AceConsole:RegisterEvent("PLAYER_LOGIN", "PLAYER_LOGIN", true)
+ end
+ end
+ end
+
+ AceConsole.registry[name] = options
+end
+
+function AceConsole:InjectAceOptionsTable(handler, options)
+ self:argCheck(handler, 2, "table")
+ self:argCheck(options, 3, "table")
+ if string.lower(options.type) ~= "group" then
+ self:error('Cannot inject into options table argument #3 if its type is not "group"')
+ end
+ if options.handler ~= nil and options.handler ~= handler then
+ self:error("Cannot inject into options table argument #3 if it has a different handler than argument #2")
+ end
+ options.handler = handler
+ local class = handler.class
+ if not class then
+ self:error("Cannot retrieve AceOptions tables from a non-object argument #2")
+ end
+ while class and class ~= AceOO.Class do
+ if type(class.GetAceOptionsDataTable) == "function" then
+ local t = class:GetAceOptionsDataTable(handler)
+ for k,v in pairs(t) do
+ if type(options.args) ~= "table" then
+ options.args = {}
+ end
+ if options.args[k] == nil then
+ options.args[k] = v
+ end
+ end
+ end
+ local mixins = class.mixins
+ if mixins then
+ for mixin in pairs(mixins) do
+ if type(mixin.GetAceOptionsDataTable) == "function" then
+ local t = mixin:GetAceOptionsDataTable(handler)
+ for k,v in pairs(t) do
+ if type(options.args) ~= "table" then
+ options.args = {}
+ end
+ if options.args[k] == nil then
+ options.args[k] = v
+ end
+ end
+ end
+ end
+ end
+ class = class.super
+ end
+ return options
+end
+
+function AceConsole:PLAYER_LOGIN()
+ self.playerLogin = true
+ for addon, options in pairs(self.nextAddon) do
+ local err, position = validateOptionsMethods(addon, options)
+ if err then
+ if position then
+ error(tostring(addon) .. ": AceConsole: " .. position .. ": " .. err)
+ else
+ error(tostring(addon) .. ": AceConsole: " .. err)
+ end
+ end
+ self.nextAddon[addon] = nil
+ end
+
+ self:RegisterChatCommand({ "/reload", "/rl", "/reloadui" }, ReloadUI, "RELOAD")
+
+ local version = GetBuildInfo()
+ if string.find(version, "^2%.") then
+ -- 2.0.0
+ self:RegisterChatCommand({ "/print" }, function(text)
+ RunScript("local function func(...) local arg = {...}; for k = 1,select('#', ...) do arg[k] = tostring(arg[k]) end DEFAULT_CHAT_FRAME:AddMessage(table.concat(arg, ' ')) end func(" .. text .. ")")
+ end, "PRINT")
+ else
+ self:RegisterChatCommand({ "/print" }, function(text)
+ RunScript("local function func(...) for k = 1,table.getn(arg) do arg[k] = tostring(arg[k]) end DEFAULT_CHAT_FRAME:AddMessage(table.concat(arg, ' ')) end func(" .. text .. ")")
+ end, "PRINT")
+ end
+end
+
+function AceConsole:TabCompleteInfo(cmdpath)
+ local _, _, cmd = string.find(cmdpath, "(/%S+)")
+ if not cmd then
+ return
+ end
+ local path = string.sub(cmdpath, string.len(cmd) + 2)
+ for name in pairs(SlashCmdList) do --global
+ if AceConsole.registry[name] then
+ local i = 0
+ while true do
+ i = i + 1
+ local scmd = _G["SLASH_"..name..i]
+ if not scmd then break end
+ if cmd == scmd then
+ return name, cmd, path
+ end
+ end
+ end
+ end
+end
+
+function external(self, major, instance)
+ if major == "AceEvent-2.0" then
+ if not AceEvent then
+ AceEvent = instance
+
+ AceEvent:embed(self)
+ end
+ elseif major == "AceTab-2.0" then
+ instance:RegisterTabCompletion("AceConsole", "%/.*", function(t, cmdpath, pos)
+ local ac = AceLibrary("AceConsole-2.0")
+ local name, cmd, path = ac:TabCompleteInfo(string.sub(cmdpath, 1, pos))
+
+ if not ac.registry[name] then
+ return false
+ else
+ local validArgs = findTableLevel(ac, ac.registry[name], cmd, path or "")
+ if validArgs.args then
+ for arg in pairs(validArgs.args) do
+ table.insert(t, arg)
+ end
+ end
+ end
+ end, function(u, matches, gcs, cmdpath)
+ local ac = AceLibrary("AceConsole-2.0")
+ local name, cmd, path = ac:TabCompleteInfo(cmdpath)
+ if ac.registry[name] then
+ local validArgs, path2, argwork = findTableLevel(ac, ac.registry[name], cmd, path)
+ printUsage(ac, validArgs.handler, ac.registry[name], validArgs, path2, argwork, not gcs or gcs ~= "", gcs)
+ end
+ end)
+ elseif major == "Dewdrop-2.0" then
+ Dewdrop = instance
+ end
+end
+
+local function activate(self, oldLib, oldDeactivate)
+ AceConsole = self
+
+ self.super.activate(self, oldLib, oldDeactivate)
+
+ if oldLib then
+ self.registry = oldLib.registry
+ self.nextAddon = oldLib.nextAddon
+ end
+ if not self.registry then
+ self.registry = {}
+ else
+ for name,options in pairs(self.registry) do
+ self:RegisterChatCommand(false, options, name)
+ end
+ end
+
+ if oldDeactivate then
+ oldDeactivate(oldLib)
+ end
+end
+
+AceLibrary:Register(AceConsole, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
+AceConsole = AceLibrary(MAJOR_VERSION)
diff --git a/Libs/AceDB-2.0/AceDB-2.0.lua b/Libs/AceDB-2.0/AceDB-2.0.lua
new file mode 100644
index 0000000..5a3f6cf
--- /dev/null
+++ b/Libs/AceDB-2.0/AceDB-2.0.lua
@@ -0,0 +1,1518 @@
+--[[
+Name: AceDB-2.0
+Revision: $Rev: 17797 $
+Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
+Inspired By: Ace 1.x by Turan (turan@gryphon.com)
+Website: http://www.wowace.com/
+Documentation: http://www.wowace.com/index.php/AceDB-2.0
+SVN: http://svn.wowace.com/root/trunk/Ace2/AceDB-2.0
+Description: Mixin to allow for fast, clean, and featureful saved variable
+ access.
+Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0
+]]
+
+local MAJOR_VERSION = "AceDB-2.0"
+local MINOR_VERSION = "$Revision: 17797 $"
+
+if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
+if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
+
+if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
+if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
+
+local ACTIVE, ENABLED, STATE, TOGGLE_ACTIVE, MAP_ACTIVESUSPENDED, SET_PROFILE, SET_PROFILE_USAGE, PROFILE, PLAYER_OF_REALM, CHOOSE_PROFILE_DESC, CHOOSE_PROFILE_GUI, COPY_PROFILE_DESC, COPY_PROFILE_GUI, OTHER_PROFILE_DESC, OTHER_PROFILE_GUI, OTHER_PROFILE_USAGE, CHARACTER, REALM, CLASS
+
+local function safecall(func,a,b,c,d,e,f,g,h,i)
+ local success, err = pcall(func,a,b,c,d,e,f,g,h,i)
+ if not success then geterrorhandler()(err) end
+end
+
+if GetLocale() == "deDE" then
+ ACTIVE = "Aktiv"
+ ENABLED = "Aktiviert"
+ STATE = "Status"
+ TOGGLE_ACTIVE = "Stoppt/Aktiviert dieses Addon."
+ MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Aktiv|r", [false] = "|cffff0000Gestoppt|r" }
+ SET_PROFILE = "Setzt das Profil f\195\188r dieses Addon."
+ SET_PROFILE_USAGE = "{Charakter || Klasse || Realm || }"
+ PROFILE = "Profil"
+ PLAYER_OF_REALM = "%s von %s"
+ CHOOSE_PROFILE_DESC = "W\195\164hle ein Profil."
+ CHOOSE_PROFILE_GUI = "W\195\164hle"
+ COPY_PROFILE_DESC = "Kopiert Einstellungen von einem anderem Profil."
+ COPY_PROFILE_GUI = "Kopiere von"
+ OTHER_PROFILE_DESC = "W\195\164hle ein anderes Profil."
+ OTHER_PROFILE_GUI = "Anderes"
+ OTHER_PROFILE_USAGE = ""
+
+ CHARACTER = "Charakter: "
+ REALM = "Realm: "
+ CLASS = "Klasse: "
+elseif GetLocale() == "frFR" then
+ ACTIVE = "Actif"
+ ENABLED = "Activ\195\169"
+ STATE = "Etat"
+ TOGGLE_ACTIVE = "Suspend/active cet addon."
+ MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Actif|r", [false] = "|cffff0000Suspendu|r" }
+ SET_PROFILE = "S\195\169lectionne le profil pour cet addon."
+ SET_PROFILE_USAGE = "{perso || classe || royaume || }"
+ PROFILE = "Profil"
+ PLAYER_OF_REALM = "%s de %s"
+ CHOOSE_PROFILE_DESC = "Choisissez un profil."
+ CHOOSE_PROFILE_GUI = "Choix"
+ COPY_PROFILE_DESC = "Copier les param\195\168tres d'un autre profil."
+ COPY_PROFILE_GUI = "Copier \195\160 partir de"
+ OTHER_PROFILE_DESC = "Choisissez un autre profil."
+ OTHER_PROFILE_GUI = "Autre"
+ OTHER_PROFILE_USAGE = ""
+
+ CHARACTER = "Personnage: "
+ REALM = "Royaume: "
+ CLASS = "Classe: "
+elseif GetLocale() == "koKR" then
+ ACTIVE = "활성화"
+ ENABLED = "활성화"
+ STATE = "상태"
+ TOGGLE_ACTIVE = "이 애드온 중지/계속 실행"
+ MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00활성화|r", [false] = "|cffff0000중지됨|r" }
+ SET_PROFILE = "이 애드온에 프로필 설정"
+ SET_PROFILE_USAGE = "{캐릭터명 || 직업 || 서버명 || <프로필명>}"
+ PROFILE = "프로필"
+ PLAYER_OF_REALM = "%s (%s 서버)"
+ CHOOSE_PROFILE_DESC = "프로파일을 선택합니다."
+ CHOOSE_PROFILE_GUI = "선택"
+ COPY_PROFILE_DESC = "다른 프로파일에서 설정을 복사합니다."
+ COPY_PROFILE_GUI = "복사"
+ OTHER_PROFILE_DESC = "다른 프로파일을 선택합니다."
+ OTHER_PROFILE_GUI = "기타"
+ OTHER_PROFILE_USAGE = "<프로파일명>"
+
+ CHARACTER = "캐릭터: "
+ REALM = "서버: "
+ CLASS = "직업: "
+elseif GetLocale() == "zhTW" then
+ ACTIVE = "啟動"
+ ENABLED = "啟用"
+ STATE = "狀態"
+ TOGGLE_ACTIVE = "暫停/重啟這個插件。"
+ MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00啟動|r", [false] = "|cffff0000已暫停|r" }
+ SET_PROFILE = "設定這插件的記錄檔。"
+ SET_PROFILE_USAGE = "{角色 || 聯業 || 伺服器 || <記錄檔名稱>}"
+ PROFILE = "記錄檔"
+ PLAYER_OF_REALM = "%s 於 %s"
+ CHOOSE_PROFILE_DESC = "選擇一個記錄檔"
+ CHOOSE_PROFILE_GUI = "選擇"
+ COPY_PROFILE_DESC = "由其他記錄檔複製設定。"
+ COPY_PROFILE_GUI = "複製由"
+ OTHER_PROFILE_DESC = "選擇其他記錄檔。"
+ OTHER_PROFILE_GUI = "其他"
+ OTHER_PROFILE_USAGE = "<記錄檔名稱>"
+
+ CHARACTER = "角色:"
+ REALM = "伺服器:"
+ CLASS = "聯業:"
+elseif GetLocale() == "zhCN" then
+ ACTIVE = "\230\156\137\230\149\136"
+ ENABLED = "\229\144\175\231\148\168"
+ STATE = "\231\138\182\230\128\129"
+ TOGGLE_ACTIVE = "\230\154\130\229\129\156/\230\129\162\229\164\141 \230\173\164\230\143\146\228\187\182."
+ MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00\230\156\137\230\149\136|r", [false] = "|cffff0000\230\154\130\229\129\156|r" }
+ SET_PROFILE = "\232\174\190\231\189\174\233\133\141\231\189\174\230\150\135\228\187\182\228\184\186\232\191\153\230\143\146\228\187\182."
+ SET_PROFILE_USAGE = "{\229\173\151\231\172\166 || \233\128\137\228\187\182\231\177\187 || \229\159\159 || <\233\133\141\231\189\174\230\150\135\228\187\182\229\144\141\229\173\151>}"
+ PROFILE = "\233\133\141\231\189\174\230\150\135\228\187\182"
+ PLAYER_OF_REALM = "%s \231\154\132 %s"
+ CHOOSE_PROFILE_DESC = "\233\128\137\230\139\169\233\133\141\231\189\174\230\150\135\228\187\182."
+ CHOOSE_PROFILE_GUI = "\233\128\137\230\139\169"
+ COPY_PROFILE_DESC = "\229\164\141\229\136\182\232\174\190\231\189\174\228\187\142\229\143\166\228\184\128\228\184\170\233\133\141\231\189\174\230\150\135\228\187\182."
+ COPY_PROFILE_GUI = "\229\164\141\229\136\182\228\187\142"
+ OTHER_PROFILE_DESC = "\233\128\137\230\139\169\229\143\166\228\184\128\228\184\170\233\133\141\231\189\174\230\150\135\228\187\182."
+ OTHER_PROFILE_GUI = "\229\133\182\228\187\150"
+ OTHER_PROFILE_USAGE = "<\233\133\141\231\189\174\230\150\135\228\187\182\229\144\141\229\173\151>"
+
+ CHARACTER = "\229\173\151\231\172\166: "
+ REALM = "\229\159\159: "
+ CLASS = "\233\128\137\228\187\182\231\177\187: "
+elseif GetLocale() == "ruRU" then
+ ACTIVE = "Активный"
+ ENABLED = "Включён"
+ STATE = "Состояние"
+ TOGGLE_ACTIVE = "Отключить/Запустить аддон."
+ MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Активный|r", [false] = "|cffff0000Приостановленный|r" }
+ SET_PROFILE = "Установить профиль для этого аддона."
+ SET_PROFILE_USAGE = "{чар || класс || сервер || <название профиля>}"
+ PROFILE = "Профиль"
+ PLAYER_OF_REALM = "%s из %s"
+ CHOOSE_PROFILE_DESC = "Выберите профиль."
+ CHOOSE_PROFILE_GUI = "Выбор"
+ COPY_PROFILE_DESC = "Cкопировать настройки из другого профиля."
+ COPY_PROFILE_GUI = "Скопировать из"
+ OTHER_PROFILE_DESC = "Выбрать другой профиль."
+ OTHER_PROFILE_GUI = "Другое"
+ OTHER_PROFILE_USAGE = "<название профиля>"
+
+ CHARACTER = "Персонаж: "
+ REALM = "Сервер: "
+ CLASS = "Класс: "
+else -- enUS
+ ACTIVE = "Active"
+ ENABLED = "Enabled"
+ STATE = "State"
+ TOGGLE_ACTIVE = "Suspend/resume this addon."
+ MAP_ACTIVESUSPENDED = { [true] = "|cff00ff00Active|r", [false] = "|cffff0000Suspended|r" }
+ SET_PROFILE = "Set profile for this addon."
+ SET_PROFILE_USAGE = "{char || class || realm || }"
+ PROFILE = "Profile"
+ PLAYER_OF_REALM = "%s of %s"
+ CHOOSE_PROFILE_DESC = "Choose a profile."
+ CHOOSE_PROFILE_GUI = "Choose"
+ COPY_PROFILE_DESC = "Copy settings from another profile."
+ COPY_PROFILE_GUI = "Copy from"
+ OTHER_PROFILE_DESC = "Choose another profile."
+ OTHER_PROFILE_GUI = "Other"
+ OTHER_PROFILE_USAGE = ""
+
+ CHARACTER = "Character: "
+ REALM = "Realm: "
+ CLASS = "Class: "
+end
+
+local AceOO = AceLibrary("AceOO-2.0")
+local AceEvent
+local Mixin = AceOO.Mixin
+local AceDB = Mixin {
+ "RegisterDB",
+ "RegisterDefaults",
+ "ResetDB",
+ "SetProfile",
+ "GetProfile",
+ "ToggleActive",
+ "IsActive",
+ "AcquireDBNamespace",
+ }
+local Dewdrop = AceLibrary:HasInstance("Dewdrop-2.0") and AceLibrary("Dewdrop-2.0")
+
+local _G = getfenv(0)
+
+local table_setn
+do
+ local version = GetBuildInfo()
+ if string.find(version, "^2%.") then
+ -- 2.0.0
+ table_setn = function() end
+ else
+ table_setn = table.setn
+ end
+end
+
+local function inheritDefaults(t, defaults)
+ if not defaults then
+ return t
+ end
+ for k,v in pairs(defaults) do
+ if k == "*" then
+ local v = v
+ if type(v) == "table" then
+ setmetatable(t, {
+ __index = function(self, key)
+ if key == nil then
+ return nil
+ end
+ self[key] = {}
+ inheritDefaults(self[key], v)
+ return self[key]
+ end
+ } )
+ else
+ setmetatable(t, {
+ __index = function(self, key)
+ if key == nil then
+ return nil
+ end
+ self[key] = v
+ return self[key]
+ end
+ } )
+ end
+ for key in pairs(t) do
+ if (defaults[key] == nil or key == "*") and type(t[key]) == "table" then
+ inheritDefaults(t[key], v)
+ end
+ end
+ else
+ if type(v) == "table" then
+ if type(t[k]) ~= "table" then
+ t[k] = {}
+ end
+ inheritDefaults(t[k], v)
+ elseif t[k] == nil then
+ t[k] = v
+ end
+ end
+ end
+ return t
+end
+
+local _,race = UnitRace("player")
+local faction
+if race == "Orc" or race == "Scourge" or race == "Troll" or race == "Tauren" then
+ faction = FACTION_HORDE
+else
+ faction = FACTION_ALLIANCE
+end
+local charID = string.format(PLAYER_OF_REALM, UnitName("player"), (string.gsub(GetRealmName(), "^%s*(.-)%s*$", "%1")))
+local realm = string.gsub(GetRealmName(), "^%s*(.-)%s*$", "%1")
+local realmID = realm .. " - " .. faction
+local classID = UnitClass("player")
+
+AceDB.CHAR_ID = charID
+AceDB.REALM_ID = realmID
+AceDB.CLASS_ID = classID
+
+AceDB.FACTION = faction
+AceDB.REALM = realm
+AceDB.NAME = UnitName("player")
+
+local new, del
+do
+ local list = setmetatable({}, {__mode="k"})
+ function new()
+ local t = next(list)
+ if t then
+ list[t] = nil
+ return t
+ else
+ return {}
+ end
+ end
+
+ function del(t)
+ setmetatable(t, nil)
+ for k in pairs(t) do
+ t[k] = nil
+ end
+ table_setn(t, 0)
+ list[t] = true
+ end
+end
+
+local caseInsensitive_mt = {
+ __index = function(self, key)
+ if type(key) ~= "string" then
+ return nil
+ end
+ local lowerKey = string.lower(key)
+ for k,v in pairs(self) do
+ if string.lower(k) == lowerKey then
+ return self[k]
+ end
+ end
+ end,
+ __newindex = function(self, key, value)
+ if type(key) ~= "string" then
+ return error("table index is nil", 2)
+ end
+ local lowerKey = string.lower(key)
+ for k in pairs(self) do
+ if string.lower(k) == lowerKey then
+ rawset(self, k, nil)
+ rawset(self, key, value)
+ return
+ end
+ end
+ rawset(self, key, value)
+ end
+}
+
+local db_mt = { __index = function(db, key)
+ if key == "char" then
+ if db.charName then
+ if type(_G[db.charName]) ~= "table" then
+ _G[db.charName] = {}
+ end
+ if type(_G[db.charName].global) ~= "table" then
+ _G[db.charName].global = {}
+ end
+ rawset(db, 'char', _G[db.charName].global)
+ else
+ if type(db.raw.chars) ~= "table" then
+ db.raw.chars = {}
+ end
+ local id = charID
+ if type(db.raw.chars[id]) ~= "table" then
+ db.raw.chars[id] = {}
+ end
+ rawset(db, 'char', db.raw.chars[id])
+ end
+ if db.defaults and db.defaults.char then
+ inheritDefaults(db.char, db.defaults.char)
+ end
+ return db.char
+ elseif key == "realm" then
+ if type(db.raw.realms) ~= "table" then
+ db.raw.realms = {}
+ end
+ local id = realmID
+ if type(db.raw.realms[id]) ~= "table" then
+ db.raw.realms[id] = {}
+ end
+ rawset(db, 'realm', db.raw.realms[id])
+ if db.defaults and db.defaults.realm then
+ inheritDefaults(db.realm, db.defaults.realm)
+ end
+ return db.realm
+ elseif key == "account" then
+ if type(db.raw.account) ~= "table" then
+ db.raw.account = {}
+ end
+ rawset(db, 'account', db.raw.account)
+ if db.defaults and db.defaults.account then
+ inheritDefaults(db.account, db.defaults.account)
+ end
+ return db.account
+ elseif key == "class" then
+ if type(db.raw.classes) ~= "table" then
+ db.raw.classes = {}
+ end
+ local id = classID
+ if type(db.raw.classes[id]) ~= "table" then
+ db.raw.classes[id] = {}
+ end
+ rawset(db, 'class', db.raw.classes[id])
+ if db.defaults and db.defaults.class then
+ inheritDefaults(db.class, db.defaults.class)
+ end
+ return db.class
+ elseif key == "profile" then
+ if type(db.raw.profiles) ~= "table" then
+ db.raw.profiles = setmetatable({}, caseInsensitive_mt)
+ else
+ setmetatable(db.raw.profiles, caseInsensitive_mt)
+ end
+ local id = db.raw.currentProfile[charID]
+ if id == "char" then
+ id = "char/" .. charID
+ elseif id == "class" then
+ id = "class/" .. classID
+ elseif id == "realm" then
+ id = "realm/" .. realmID
+ end
+ if type(db.raw.profiles[id]) ~= "table" then
+ db.raw.profiles[id] = {}
+ end
+ rawset(db, 'profile', db.raw.profiles[id])
+ if db.defaults and db.defaults.profile then
+ inheritDefaults(db.profile, db.defaults.profile)
+ end
+ return db.profile
+ elseif key == "raw" or key == "defaults" or key == "name" or key == "charName" or key == "namespaces" then
+ return nil
+ end
+ error(string.format('Cannot access key %q in db table. You may want to use db.profile[%q]', tostring(key), tostring(key)), 2)
+end, __newindex = function(db, key, value)
+ error(string.format('Cannot access key %q in db table. You may want to use db.profile[%q]', tostring(key), tostring(key)), 2)
+end }
+
+local CrawlForSerialization
+local CrawlForDeserialization
+
+local function SerializeObject(o)
+ local t = { o:Serialize() }
+ t[0] = o.class:GetLibraryVersion()
+ CrawlForSerialization(t)
+ return t
+end
+
+local function DeserializeObject(t)
+ CrawlForDeserialization(t)
+ local className = t[0]
+ for i = 20, 1, -1 do
+ if t[i] then
+ table_setn(t, i)
+ break
+ end
+ end
+ local o = AceLibrary(className):Deserialize(unpack(t))
+ table_setn(t, 0)
+ return o
+end
+
+local function IsSerializable(t)
+ return AceOO.inherits(t, AceOO.Class) and t.class and type(t.class.Deserialize) == "function" and type(t.Serialize) == "function" and type(t.class.GetLibraryVersion) == "function"
+end
+
+function CrawlForSerialization(t)
+ local tmp = new()
+ for k,v in pairs(t) do
+ tmp[k] = v
+ end
+ for k,v in pairs(tmp) do
+ if type(v) == "table" and type(v[0]) ~= "userdata" then
+ if IsSerializable(v) then
+ v = SerializeObject(v)
+ t[k] = v
+ else
+ CrawlForSerialization(v)
+ end
+ end
+ if type(k) == "table" and type(k[0]) ~= "userdata" then
+ if IsSerializable(k) then
+ t[k] = nil
+ t[SerializeObject(k)] = v
+ else
+ CrawlForSerialization(k)
+ end
+ end
+ tmp[k] = nil
+ k = nil
+ end
+ tmp = del(tmp)
+end
+
+local function IsDeserializable(t)
+ return type(t[0]) == "string" and AceLibrary:HasInstance(t[0])
+end
+
+function CrawlForDeserialization(t)
+ local tmp = new()
+ for k,v in pairs(t) do
+ tmp[k] = v
+ end
+ for k,v in pairs(tmp) do
+ if type(v) == "table" then
+ if IsDeserializable(v) then
+ t[k] = DeserializeObject(v)
+ del(v)
+ v = t[k]
+ elseif type(v[0]) ~= "userdata" then
+ CrawlForDeserialization(v)
+ end
+ end
+ if type(k) == "table" then
+ if IsDeserializable(k) then
+ t[k] = nil
+ t[DeserializeObject(k)] = v
+ del(k)
+ elseif type(k[0]) ~= "userdata" then
+ CrawlForDeserialization(k)
+ end
+ end
+ tmp[k] = nil
+ k = nil
+ end
+ tmp = del(tmp)
+end
+
+local namespace_mt = { __index = function(namespace, key)
+ local db = namespace.db
+ local name = namespace.name
+ if key == "char" then
+ if db.charName then
+ if type(_G[db.charName]) ~= "table" then
+ _G[db.charName] = {}
+ end
+ if type(_G[db.charName].namespaces) ~= "table" then
+ _G[db.charName].namespaces = {}
+ end
+ if type(_G[db.charName].namespaces[name]) ~= "table" then
+ _G[db.charName].namespaces[name] = {}
+ end
+ rawset(namespace, 'char', _G[db.charName].namespaces[name])
+ else
+ if type(db.raw.namespaces) ~= "table" then
+ db.raw.namespaces = {}
+ end
+ if type(db.raw.namespaces[name]) ~= "table" then
+ db.raw.namespaces[name] = {}
+ end
+ if type(db.raw.namespaces[name].chars) ~= "table" then
+ db.raw.namespaces[name].chars = {}
+ end
+ local id = charID
+ if type(db.raw.namespaces[name].chars[id]) ~= "table" then
+ db.raw.namespaces[name].chars[id] = {}
+ end
+ rawset(namespace, 'char', db.raw.namespaces[name].chars[id])
+ end
+ if namespace.defaults and namespace.defaults.char then
+ inheritDefaults(namespace.char, namespace.defaults.char)
+ end
+ return namespace.char
+ elseif key == "realm" then
+ if type(db.raw.namespaces) ~= "table" then
+ db.raw.namespaces = {}
+ end
+ if type(db.raw.namespaces[name]) ~= "table" then
+ db.raw.namespaces[name] = {}
+ end
+ if type(db.raw.namespaces[name].realms) ~= "table" then
+ db.raw.namespaces[name].realms = {}
+ end
+ local id = realmID
+ if type(db.raw.namespaces[name].realms[id]) ~= "table" then
+ db.raw.namespaces[name].realms[id] = {}
+ end
+ rawset(namespace, 'realm', db.raw.namespaces[name].realms[id])
+ if namespace.defaults and namespace.defaults.realm then
+ inheritDefaults(namespace.realm, namespace.defaults.realm)
+ end
+ return namespace.realm
+ elseif key == "account" then
+ if type(db.raw.namespaces) ~= "table" then
+ db.raw.namespaces = {}
+ end
+ if type(db.raw.namespaces[name]) ~= "table" then
+ db.raw.namespaces[name] = {}
+ end
+ if type(db.raw.namespaces[name].account) ~= "table" then
+ db.raw.namespaces[name].account = {}
+ end
+ rawset(namespace, 'account', db.raw.namespaces[name].account)
+ if namespace.defaults and namespace.defaults.account then
+ inheritDefaults(namespace.account, namespace.defaults.account)
+ end
+ return namespace.account
+ elseif key == "class" then
+ if type(db.raw.namespaces) ~= "table" then
+ db.raw.namespaces = {}
+ end
+ if type(db.raw.namespaces[name]) ~= "table" then
+ db.raw.namespaces[name] = {}
+ end
+ if type(db.raw.namespaces[name].classes) ~= "table" then
+ db.raw.namespaces[name].classes = {}
+ end
+ local id = classID
+ if type(db.raw.namespaces[name].classes[id]) ~= "table" then
+ db.raw.namespaces[name].classes[id] = {}
+ end
+ rawset(namespace, 'class', db.raw.namespaces[name].classes[id])
+ if namespace.defaults and namespace.defaults.class then
+ inheritDefaults(namespace.class, namespace.defaults.class)
+ end
+ return namespace.class
+ elseif key == "profile" then
+ if type(db.raw.namespaces) ~= "table" then
+ db.raw.namespaces = {}
+ end
+ if type(db.raw.namespaces[name]) ~= "table" then
+ db.raw.namespaces[name] = {}
+ end
+ if type(db.raw.namespaces[name].profiles) ~= "table" then
+ db.raw.namespaces[name].profiles = setmetatable({}, caseInsensitive_mt)
+ else
+ setmetatable(db.raw.namespaces[name].profiles, caseInsensitive_mt)
+ end
+ local id = db.raw.currentProfile[charID]
+ if id == "char" then
+ id = "char/" .. charID
+ elseif id == "class" then
+ id = "class/" .. classID
+ elseif id == "realm" then
+ id = "realm/" .. realmID
+ end
+ if type(db.raw.namespaces[name].profiles[id]) ~= "table" then
+ db.raw.namespaces[name].profiles[id] = {}
+ end
+ rawset(namespace, 'profile', db.raw.namespaces[name].profiles[id])
+ if namespace.defaults and namespace.defaults.profile then
+ inheritDefaults(namespace.profile, namespace.defaults.profile)
+ end
+ return namespace.profile
+ elseif key == "defaults" or key == "name" or key == "db" then
+ return nil
+ end
+ error(string.format('Cannot access key %q in db table. You may want to use db.profile[%q]', tostring(key), tostring(key)), 2)
+end, __newindex = function(db, key, value)
+ error(string.format('Cannot access key %q in db table. You may want to use db.profile[%q]', tostring(key), tostring(key)), 2)
+end }
+
+function AceDB:InitializeDB(addonName)
+ local db = self.db
+
+ if not db then
+ if addonName then
+ AceDB.addonsLoaded[addonName] = true
+ end
+ return
+ end
+
+ if db.raw then
+ -- someone manually initialized
+ return
+ end
+
+ if type(_G[db.name]) ~= "table" then
+ _G[db.name] = {}
+ else
+ CrawlForDeserialization(_G[db.name])
+ end
+ if type(_G[db.charName]) == "table" then
+ CrawlForDeserialization(_G[db.charName])
+ end
+ rawset(db, 'raw', _G[db.name])
+ if not db.raw.currentProfile then
+ db.raw.currentProfile = {}
+ end
+ if not db.raw.currentProfile[charID] then
+ db.raw.currentProfile[charID] = "Default"
+ end
+ if db.raw.disabled then
+ setmetatable(db.raw.disabled, caseInsensitive_mt)
+ end
+ if self['acedb-profile-copylist'] then
+ local t = self['acedb-profile-copylist']
+ for k,v in pairs(t) do
+ t[k] = nil
+ end
+ if db.raw.profiles then
+ for k in pairs(db.raw.profiles) do
+ if string.find(k, '^char/') then
+ local name = string.sub(k, 6)
+ if name ~= charID then
+ t[k] = CHARACTER .. name
+ end
+ elseif string.find(k, '^realm/') then
+ local name = string.sub(k, 7)
+ if name ~= realmID then
+ t[k] = REALM .. name
+ end
+ elseif string.find(k, '^class/') then
+ local name = string.sub(k, 7)
+ if name ~= classID then
+ t[k] = CLASS .. name
+ end
+ end
+ end
+ end
+ end
+ if self['acedb-profile-list'] then
+ local t = self['acedb-profile-list']
+ for k,v in pairs(t) do
+ t[k] = nil
+ end
+ t.char = CHARACTER .. charID
+ t.realm = REALM .. realmID
+ t.class = CLASS .. classID
+ t.Default = "Default"
+ if db.raw.profiles then
+ for k in pairs(db.raw.profiles) do
+ if not string.find(k, '^char/') and not string.find(k, '^realm/') and not string.find(k, '^class/') then
+ t[k] = k
+ end
+ end
+ end
+ end
+ setmetatable(db, db_mt)
+end
+
+function AceDB:OnEmbedInitialize(target, name)
+ if name then
+ self:ADDON_LOADED(name)
+ end
+ self.InitializeDB(target, name)
+end
+
+function AceDB:RegisterDB(name, charName)
+ AceDB:argCheck(name, 2, "string")
+ AceDB:argCheck(charName, 3, "string", "nil")
+ if self.db then
+ AceDB:error("Cannot call \"RegisterDB\" if self.db is set.")
+ end
+ local stack = debugstack()
+ local addonName = string.gsub(stack, ".-\n.-\\AddOns\\(.-)\\.*", "%1")
+ self.db = {
+ name = name,
+ charName = charName
+ }
+ if AceDB.addonsLoaded[addonName] then
+ AceDB.InitializeDB(self, addonName)
+ else
+ AceDB.addonsToBeInitialized[self] = addonName
+ end
+ AceDB.registry[self] = true
+end
+
+function AceDB:RegisterDefaults(kind, defaults, a3)
+ local name
+ if a3 then
+ name, kind, defaults = kind, defaults, a3
+ AceDB:argCheck(name, 2, "string")
+ AceDB:argCheck(kind, 3, "string")
+ AceDB:argCheck(defaults, 4, "table")
+ if kind ~= "char" and kind ~= "class" and kind ~= "profile" and kind ~= "account" and kind ~= "realm" then
+ AceDB:error("Bad argument #3 to `RegisterDefaults' (\"char\", \"class\", \"profile\", \"account\", or \"realm\" expected, got %q)", kind)
+ end
+ else
+ AceDB:argCheck(kind, 2, "string")
+ AceDB:argCheck(defaults, 3, "table")
+ if kind ~= "char" and kind ~= "class" and kind ~= "profile" and kind ~= "account" and kind ~= "realm" then
+ AceDB:error("Bad argument #2 to `RegisterDefaults' (\"char\", \"class\", \"profile\", \"account\", or \"realm\" expected, got %q)", kind)
+ end
+ end
+ if type(self.db) ~= "table" or type(self.db.name) ~= "string" then
+ AceDB:error("Cannot call \"RegisterDefaults\" unless \"RegisterDB\" has been previously called.")
+ end
+ local db
+ if name then
+ local namespace = self:AcquireDBNamespace(name)
+ if namespace.defaults and namespace.defaults[kind] then
+ AceDB:error("\"RegisterDefaults\" has already been called for %q::%q.", name, kind)
+ end
+ db = namespace
+ else
+ if self.db.defaults and self.db.defaults[kind] then
+ AceDB:error("\"RegisterDefaults\" has already been called for %q.", kind)
+ end
+ db = self.db
+ end
+ if not db.defaults then
+ rawset(db, 'defaults', {})
+ end
+ db.defaults[kind] = defaults
+ if rawget(db, kind) then
+ inheritDefaults(db[kind], defaults)
+ end
+end
+
+function AceDB:ResetDB(kind)
+ AceDB:argCheck(kind, 2, "nil", "string")
+ if not self.db or not self.db.raw then
+ AceDB:error("Cannot call \"ResetDB\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.")
+ end
+ local db = self.db
+ if kind == nil then
+ if db.charName then
+ _G[db.charName] = nil
+ end
+ _G[db.name] = nil
+ rawset(db, 'raw', nil)
+ AceDB.InitializeDB(self)
+ if db.namespaces then
+ for name,v in pairs(db.namespaces) do
+ rawset(v, 'account', nil)
+ rawset(v, 'char', nil)
+ rawset(v, 'class', nil)
+ rawset(v, 'profile', nil)
+ rawset(v, 'realm', nil)
+ end
+ end
+ elseif kind == "account" then
+ db.raw.account = nil
+ rawset(db, 'account', nil)
+ if db.namespaces then
+ for name,v in pairs(db.namespaces) do
+ rawset(v, 'account', nil)
+ end
+ end
+ elseif kind == "char" then
+ if db.charName then
+ _G[db.charName] = nil
+ else
+ if db.raw.chars then
+ db.raw.chars[charID] = nil
+ end
+ if db.raw.namespaces then
+ for name,v in pairs(db.raw.namespaces) do
+ if v.chars then
+ v.chars[charID] = nil
+ end
+ end
+ end
+ end
+ rawset(db, 'char', nil)
+ if db.namespaces then
+ for name,v in pairs(db.namespaces) do
+ rawset(v, 'char', nil)
+ end
+ end
+ elseif kind == "realm" then
+ if db.raw.realms then
+ db.raw.realms[realmID] = nil
+ end
+ rawset(db, 'realm', nil)
+ if db.raw.namespaces then
+ for name,v in pairs(db.raw.namespaces) do
+ if v.realms then
+ v.realms[realmID] = nil
+ end
+ end
+ end
+ if db.namespaces then
+ for name,v in pairs(db.namespaces) do
+ rawset(v, 'realm', nil)
+ end
+ end
+ elseif kind == "class" then
+ if db.raw.realms then
+ db.raw.realms[classID] = nil
+ end
+ rawset(db, 'class', nil)
+ if db.raw.namespaces then
+ for name,v in pairs(db.raw.namespaces) do
+ if v.classes then
+ v.classes[classID] = nil
+ end
+ end
+ end
+ if db.namespaces then
+ for name,v in pairs(db.namespaces) do
+ rawset(v, 'class', nil)
+ end
+ end
+ elseif kind == "profile" then
+ local id = db.raw.currentProfile and db.raw.currentProfile[charID] or "Default"
+ if id == "char" then
+ id = "char/" .. charID
+ elseif id == "class" then
+ id = "class/" .. classID
+ elseif id == "realm" then
+ id = "realm/" .. realmID
+ end
+ if db.raw.profiles then
+ db.raw.profiles[id] = nil
+ end
+ rawset(db, 'profile', nil)
+ if db.raw.namespaces then
+ for name,v in pairs(db.raw.namespaces) do
+ if v.profiles then
+ v.profiles[id] = nil
+ end
+ end
+ end
+ if db.namespaces then
+ for name,v in pairs(db.namespaces) do
+ rawset(v, 'profile', nil)
+ end
+ end
+ end
+end
+
+local function cleanDefaults(t, defaults)
+ if defaults then
+ for k,v in pairs(defaults) do
+ if k == "*" then
+ if type(v) == "table" then
+ for k in pairs(t) do
+ if (defaults[k] == nil or k == "*") and type(t[k]) == "table" then
+ if cleanDefaults(t[k], v) then
+ t[k] = nil
+ end
+ end
+ end
+ else
+ for k in pairs(t) do
+ if (defaults[k] == nil or k == "*") and t[k] == v then
+ t[k] = nil
+ end
+ end
+ end
+ else
+ if type(v) == "table" then
+ if type(t[k]) == "table" then
+ if cleanDefaults(t[k], v) then
+ t[k] = nil
+ end
+ end
+ elseif t[k] == v then
+ t[k] = nil
+ end
+ end
+ end
+ end
+ return t and not next(t)
+end
+
+function AceDB:GetProfile()
+ if not self.db or not self.db.raw then
+ return nil
+ end
+ if not self.db.raw.currentProfile then
+ self.db.raw.currentProfile = {}
+ end
+ if not self.db.raw.currentProfile[charID] then
+ self.db.raw.currentProfile[charID] = "Default"
+ end
+ local profile = self.db.raw.currentProfile[charID]
+ if profile == "char" then
+ return "char", "char/" .. charID
+ elseif profile == "class" then
+ return "class", "class/" .. classID
+ elseif profile == "realm" then
+ return "realm", "realm/" .. realmID
+ end
+ return profile, profile
+end
+
+local function copyTable(to, from)
+ setmetatable(to, nil)
+ for k,v in pairs(from) do
+ if type(k) == "table" then
+ k = copyTable({}, k)
+ end
+ if type(v) == "table" then
+ v = copyTable({}, v)
+ end
+ to[k] = v
+ end
+ table_setn(to, table.getn(from))
+ setmetatable(to, from)
+ return to
+end
+
+function AceDB:SetProfile(name, copyFrom)
+ AceDB:argCheck(name, 2, "string")
+ AceDB:argCheck(copyFrom, 3, "string", "nil")
+ if not self.db or not self.db.raw then
+ AceDB:error("Cannot call \"SetProfile\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.")
+ end
+ local db = self.db
+ local copy = false
+ local lowerName = string.lower(name)
+ local lowerCopyFrom = copyFrom and string.lower(copyFrom)
+ if string.sub(lowerName, 1, 5) == "char/" or string.sub(lowerName, 1, 6) == "realm/" or string.sub(lowerName, 1, 6) == "class/" then
+ if string.sub(lowerName, 1, 5) == "char/" then
+ name, copyFrom = "char", name
+ else
+ name, copyFrom = string.sub(lowerName, 1, 5), name
+ end
+ lowerName = string.lower(name)
+ lowerCopyFrom = string.lower(copyFrom)
+ end
+ if copyFrom then
+ if string.sub(lowerCopyFrom, 1, 5) == "char/" then
+ AceDB:assert(lowerName == "char", "If argument #3 starts with `char/', argument #2 must be `char'")
+ elseif string.sub(lowerCopyFrom, 1, 6) == "realm/" then
+ AceDB:assert(lowerName == "realm", "If argument #3 starts with `realm/', argument #2 must be `realm'")
+ elseif string.sub(lowerCopyFrom, 1, 6) == "class/" then
+ AceDB:assert(lowerName == "class", "If argument #3 starts with `class/', argument #2 must be `class'")
+ else
+ AceDB:assert(lowerName ~= "char" and lowerName ~= "realm" and lowerName ~= "class", "If argument #3 does not start with a special prefix, that prefix cannot be copied to.")
+ end
+ if not db.raw.profiles or not db.raw.profiles[copyFrom] then
+ AceDB:error("Cannot copy profile %q, it does not exist.", copyFrom)
+ elseif (string.sub(lowerName, 1, 5) == "char/" and string.sub(lowerName, 6) == string.lower(charID)) or (string.sub(lowerName, 1, 6) == "realm/" and string.sub(lowerName, 7) == string.lower(realmID)) or (string.sub(lowerName, 1, 6) == "class/" and string.sub(lowerName, 7) == string.lower(classID)) then
+ AceDB:error("Cannot copy profile %q, it is currently in use.", name)
+ end
+ end
+ local oldName = db.raw.currentProfile[charID]
+ if string.lower(oldName) == string.lower(name) then
+ return
+ end
+ local oldProfileData = db.profile
+ local realName = name
+ if lowerName == "char" then
+ realName = name .. "/" .. charID
+ elseif lowerName == "realm" then
+ realName = name .. "/" .. realmID
+ elseif lowerName == "class" then
+ realName = name .. "/" .. classID
+ end
+ local current = self.class
+ while current and current ~= AceOO.Class do
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedProfileDisable) == "function" then
+ safecall(mixin.OnEmbedProfileDisable, mixin, self, realName)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(self.OnProfileDisable) == "function" then
+ safecall(self.OnProfileDisable, self, realName)
+ end
+ local active = self:IsActive()
+ db.raw.currentProfile[charID] = name
+ rawset(db, 'profile', nil)
+ if db.namespaces then
+ for k,v in pairs(db.namespaces) do
+ rawset(v, 'profile', nil)
+ end
+ end
+ if copyFrom then
+ for k,v in pairs(db.profile) do
+ db.profile[k] = nil
+ end
+ copyTable(db.profile, db.raw.profiles[copyFrom])
+ inheritDefaults(db.profile, db.defaults and db.defaults.profile)
+ if db.namespaces then
+ for l,u in pairs(db.namespaces) do
+ for k,v in pairs(u.profile) do
+ u.profile[k] = nil
+ end
+ copyTable(u.profile, db.raw.namespaces[l].profiles[copyFrom])
+ inheritDefaults(u.profile, u.defaults and u.defaults.profile)
+ end
+ end
+ end
+ local current = self.class
+ while current and current ~= AceOO.Class do
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedProfileEnable) == "function" then
+ safecall(mixin.OnEmbedProfileEnable, mixin, self, oldName, oldProfileData, copyFrom)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(self.OnProfileEnable) == "function" then
+ safecall(self.OnProfileEnable, self, oldName, oldProfileData, copyFrom)
+ end
+ if cleanDefaults(oldProfileData, db.defaults and db.defaults.profile) then
+ db.raw.profiles[oldName] = nil
+ if not next(db.raw.profiles) then
+ db.raw.profiles = nil
+ end
+ end
+ local newactive = self:IsActive()
+ if active ~= newactive then
+ if AceOO.inherits(self, "AceAddon-2.0") then
+ local AceAddon = AceLibrary("AceAddon-2.0")
+ if not AceAddon.addonsStarted[self] then
+ return
+ end
+ end
+ if newactive then
+ local current = self.class
+ while current and current ~= AceOO.Class do
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedEnable) == "function" then
+ safecall(mixin.OnEmbedEnable, mixin, self)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(self.OnEnable) == "function" then
+ safecall(self.OnEnable, self)
+ end
+ if AceEvent then
+ AceEvent:TriggerEvent("Ace2_AddonEnabled", self)
+ end
+ else
+ local current = self.class
+ while current and current ~= AceOO.Class do
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedDisable) == "function" then
+ safecall(mixin.OnEmbedDisable, mixin, self)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(self.OnDisable) == "function" then
+ safecall(self.OnDisable, self)
+ end
+ if AceEvent then
+ AceEvent:TriggerEvent("Ace2_AddonDisabled", self)
+ end
+ end
+ end
+ if self['acedb-profile-list'] then
+ if not self['acedb-profile-list'][name] then
+ self['acedb-profile-list'][name] = name
+ end
+ end
+ if Dewdrop then
+ Dewdrop:Refresh(1)
+ Dewdrop:Refresh(2)
+ Dewdrop:Refresh(3)
+ Dewdrop:Refresh(4)
+ Dewdrop:Refresh(5)
+ end
+end
+
+function AceDB:IsActive()
+ return not self.db or not self.db.raw or not self.db.raw.disabled or not self.db.raw.disabled[self.db.raw.currentProfile[charID]]
+end
+
+function AceDB:ToggleActive(state)
+ AceDB:argCheck(state, 2, "boolean", "nil")
+ if not self.db or not self.db.raw then
+ AceDB:error("Cannot call \"ToggleActive\" before \"RegisterDB\" has been called and before \"ADDON_LOADED\" has been fired.")
+ end
+ local db = self.db
+ if not db.raw.disabled then
+ db.raw.disabled = setmetatable({}, caseInsensitive_mt)
+ end
+ local profile = db.raw.currentProfile[charID]
+ local disable
+ if state == nil then
+ disable = not db.raw.disabled[profile]
+ else
+ disable = not state
+ if disable == db.raw.disabled[profile] then
+ return
+ end
+ end
+ db.raw.disabled[profile] = disable or nil
+ if AceOO.inherits(self, "AceAddon-2.0") then
+ local AceAddon = AceLibrary("AceAddon-2.0")
+ if not AceAddon.addonsStarted[self] then
+ return
+ end
+ end
+ if not disable then
+ local current = self.class
+ while current and current ~= AceOO.Class do
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedEnable) == "function" then
+ safecall(mixin.OnEmbedEnable, mixin, self)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(self.OnEnable) == "function" then
+ safecall(self.OnEnable, self)
+ end
+ if AceEvent then
+ AceEvent:TriggerEvent("Ace2_AddonEnabled", self)
+ end
+ else
+ local current = self.class
+ while current and current ~= AceOO.Class do
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnEmbedDisable) == "function" then
+ safecall(mixin.OnEmbedDisable, mixin, self)
+ end
+ end
+ end
+ current = current.super
+ end
+ if type(self.OnDisable) == "function" then
+ safecall(self.OnDisable, self)
+ end
+ if AceEvent then
+ AceEvent:TriggerEvent("Ace2_AddonDisabled", self)
+ end
+ end
+ return not disable
+end
+
+function AceDB:embed(target)
+ self.super.embed(self, target)
+ if not AceEvent then
+ AceDB:error(MAJOR_VERSION .. " requires AceEvent-2.0")
+ end
+end
+
+function AceDB:ADDON_LOADED(name)
+ AceDB.addonsLoaded[name] = true
+ for addon, addonName in pairs(AceDB.addonsToBeInitialized) do
+ if name == addonName then
+ AceDB.InitializeDB(addon, name)
+ AceDB.addonsToBeInitialized[addon] = nil
+ end
+ end
+end
+
+function AceDB:PLAYER_LOGOUT()
+ for addon in pairs(AceDB.registry) do
+ local db = addon.db
+ if db then
+ setmetatable(db, nil)
+ CrawlForSerialization(db.raw)
+ if type(_G[db.charName]) == "table" then
+ CrawlForSerialization(_G[db.charName])
+ end
+ if db.char and cleanDefaults(db.char, db.defaults and db.defaults.char) then
+ if db.charName and _G[db.charName] and _G[db.charName].global == db.char then
+ _G[db.charName].global = nil
+ if not next(_G[db.charName]) then
+ _G[db.charName] = nil
+ end
+ else
+ if db.raw.chars then
+ db.raw.chars[charID] = nil
+ if not next(db.raw.chars) then
+ db.raw.chars = nil
+ end
+ end
+ end
+ end
+ if db.realm and cleanDefaults(db.realm, db.defaults and db.defaults.realm) then
+ if db.raw.realms then
+ db.raw.realms[realmID] = nil
+ if not next(db.raw.realms) then
+ db.raw.realms = nil
+ end
+ end
+ end
+ if db.class and cleanDefaults(db.class, db.defaults and db.defaults.class) then
+ if db.raw.classes then
+ db.raw.classes[classID] = nil
+ if not next(db.raw.classes) then
+ db.raw.classes = nil
+ end
+ end
+ end
+ if db.account and cleanDefaults(db.account, db.defaults and db.defaults.account) then
+ db.raw.account = nil
+ end
+ if db.profile and cleanDefaults(db.profile, db.defaults and db.defaults.profile) then
+ if db.raw.profiles then
+ db.raw.profiles[db.raw.currentProfile and db.raw.currentProfile[charID] or "Default"] = nil
+ if not next(db.raw.profiles) then
+ db.raw.profiles = nil
+ end
+ end
+ end
+ if db.namespaces and db.raw.namespaces then
+ for name,v in pairs(db.namespaces) do
+ if db.raw.namespaces[name] then
+ setmetatable(v, nil)
+ if v.char and cleanDefaults(v.char, v.defaults and v.defaults.char) then
+ if db.charName and _G[db.charName] and _G[db.charName].namespaces and _G[db.charName].namespaces[name] == v then
+ _G[db.charName].namespaces[name] = nil
+ if not next(_G[db.charName].namespaces) then
+ _G[db.charName].namespaces = nil
+ if not next(_G[db.charName]) then
+ _G[db.charName] = nil
+ end
+ end
+ else
+ if db.raw.namespaces[name].chars then
+ db.raw.namespaces[name].chars[charID] = nil
+ if not next(db.raw.namespaces[name].chars) then
+ db.raw.namespaces[name].chars = nil
+ end
+ end
+ end
+ end
+ if v.realm and cleanDefaults(v.realm, v.defaults and v.defaults.realm) then
+ if db.raw.namespaces[name].realms then
+ db.raw.namespaces[name].realms[realmID] = nil
+ if not next(db.raw.namespaces[name].realms) then
+ db.raw.namespaces[name].realms = nil
+ end
+ end
+ end
+ if v.class and cleanDefaults(v.class, v.defaults and v.defaults.class) then
+ if db.raw.namespaces[name].classes then
+ db.raw.namespaces[name].classes[classID] = nil
+ if not next(db.raw.namespaces[name].classes) then
+ db.raw.namespaces[name].classes = nil
+ end
+ end
+ end
+ if v.account and cleanDefaults(v.account, v.defaults and v.defaults.account) then
+ db.raw.namespaces[name].account = nil
+ end
+ if v.profile and cleanDefaults(v.profile, v.defaults and v.defaults.profile) then
+ if db.raw.namespaces[name].profiles then
+ db.raw.namespaces[name].profiles[db.raw.currentProfile and db.raw.currentProfile[charID] or "Default"] = nil
+ if not next(db.raw.namespaces[name].profiles) then
+ db.raw.namespaces[name].profiles = nil
+ end
+ end
+ end
+ if not next(db.raw.namespaces[name]) then
+ db.raw.namespaces[name] = nil
+ end
+ end
+ end
+ if not next(db.raw.namespaces) then
+ db.raw.namespaces = nil
+ end
+ end
+ if db.raw.disabled and not next(db.raw.disabled) then
+ db.raw.disabled = nil
+ end
+ if db.raw.currentProfile then
+ for k,v in pairs(db.raw.currentProfile) do
+ if string.lower(v) == "default" then
+ db.raw.currentProfile[k] = nil
+ end
+ end
+ if not next(db.raw.currentProfile) then
+ db.raw.currentProfile = nil
+ end
+ end
+ if _G[db.name] and not next(_G[db.name]) then
+ _G[db.name] = nil
+ end
+ end
+ end
+end
+
+function AceDB:AcquireDBNamespace(name)
+ AceDB:argCheck(name, 2, "string")
+ local db = self.db
+ if not db then
+ AceDB:error("Cannot call `AcquireDBNamespace' before `RegisterDB' has been called.", 2)
+ end
+ if not db.namespaces then
+ rawset(db, 'namespaces', {})
+ end
+ if not db.namespaces[name] then
+ local namespace = {}
+ db.namespaces[name] = namespace
+ namespace.db = db
+ namespace.name = name
+ setmetatable(namespace, namespace_mt)
+ end
+ return db.namespaces[name]
+end
+
+function AceDB:GetAceOptionsDataTable(target)
+ if not target['acedb-profile-list'] then
+ target['acedb-profile-list'] = setmetatable({}, caseInsensitive_mt)
+ local t = target['acedb-profile-list']
+ for k,v in pairs(t) do
+ t[k] = nil
+ end
+ t.char = CHARACTER .. charID
+ t.realm = REALM .. realmID
+ t.class = CLASS .. classID
+ t.Default = "Default"
+ if target.db and target.db.raw then
+ local db = target.db
+ if db.raw.profiles then
+ for k in pairs(db.raw.profiles) do
+ if not string.find(k, '^char/') and not string.find(k, '^realm/') and not string.find(k, '^class/') then
+ t[k] = k
+ end
+ end
+ end
+ end
+ end
+ if not target['acedb-profile-copylist'] then
+ target['acedb-profile-copylist'] = setmetatable({}, caseInsensitive_mt)
+ if target.db and target.db.raw then
+ local t = target['acedb-profile-copylist']
+ local db = target.db
+
+ if db.raw.profiles then
+ for k in pairs(db.raw.profiles) do
+ if string.find(k, '^char/') then
+ local name = string.sub(k, 6)
+ if name ~= charID then
+ t[k] = CHARACTER .. name
+ end
+ elseif string.find(k, '^realm/') then
+ local name = string.sub(k, 7)
+ if name ~= realmID then
+ t[k] = REALM .. name
+ end
+ elseif string.find(k, '^class/') then
+ local name = string.sub(k, 7)
+ if name ~= classID then
+ t[k] = CLASS .. name
+ end
+ end
+ end
+ end
+ end
+ end
+ return {
+ standby = {
+ cmdName = STATE,
+ guiName = ENABLED,
+ name = ACTIVE,
+ desc = TOGGLE_ACTIVE,
+ type = "toggle",
+ get = "IsActive",
+ set = "ToggleActive",
+ map = MAP_ACTIVESUSPENDED,
+ order = -3,
+ },
+ profile = {
+ type = 'group',
+ name = PROFILE,
+ desc = SET_PROFILE,
+ order = -3.5,
+ get = "GetProfile",
+ args = {
+ choose = {
+ guiName = CHOOSE_PROFILE_GUI,
+ cmdName = PROFILE,
+ desc = CHOOSE_PROFILE_DESC,
+ type = 'text',
+ get = "GetProfile",
+ set = "SetProfile",
+ validate = target['acedb-profile-list']
+ },
+ copy = {
+ guiName = COPY_PROFILE_GUI,
+ cmdName = PROFILE,
+ desc = COPY_PROFILE_DESC,
+ type = 'text',
+ get = "GetProfile",
+ set = "SetProfile",
+ validate = target['acedb-profile-copylist'],
+ disabled = function()
+ return not next(target['acedb-profile-copylist'])
+ end,
+ },
+ other = {
+ guiName = OTHER_PROFILE_GUI,
+ cmdName = PROFILE,
+ desc = OTHER_PROFILE_DESC,
+ usage = OTHER_PROFILE_USAGE,
+ type = 'text',
+ get = "GetProfile",
+ set = "SetProfile",
+ }
+ }
+ },
+ }
+end
+
+local function activate(self, oldLib, oldDeactivate)
+ AceDB = self
+ AceEvent = AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary("AceEvent-2.0")
+
+ self.super.activate(self, oldLib, oldDeactivate)
+
+ for t in pairs(self.embedList) do
+ if t.db then
+ rawset(t.db, 'char', nil)
+ rawset(t.db, 'realm', nil)
+ rawset(t.db, 'class', nil)
+ rawset(t.db, 'account', nil)
+ rawset(t.db, 'profile', nil)
+ setmetatable(t.db, db_mt)
+ end
+ end
+
+ if oldLib then
+ self.addonsToBeInitialized = oldLib.addonsToBeInitialized
+ self.addonsLoaded = oldLib.addonsLoaded
+ self.registry = oldLib.registry
+ end
+ if not self.addonsToBeInitialized then
+ self.addonsToBeInitialized = {}
+ end
+ if not self.addonsLoaded then
+ self.addonsLoaded = {}
+ end
+ if not self.registry then
+ self.registry = {}
+ end
+
+ if oldLib then
+ oldDeactivate(oldLib)
+ end
+end
+
+local function external(self, major, instance)
+ if major == "AceEvent-2.0" then
+ AceEvent = instance
+
+ AceEvent:embed(self)
+
+ self:RegisterEvent("ADDON_LOADED")
+ self:RegisterEvent("PLAYER_LOGOUT")
+ elseif major == "Dewdrop-2.0" then
+ Dewdrop = instance
+ end
+end
+
+AceLibrary:Register(AceDB, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
+AceDB = AceLibrary(MAJOR_VERSION)
diff --git a/Libs/AceEvent-2.0/AceEvent-2.0.lua b/Libs/AceEvent-2.0/AceEvent-2.0.lua
new file mode 100644
index 0000000..c654568
--- /dev/null
+++ b/Libs/AceEvent-2.0/AceEvent-2.0.lua
@@ -0,0 +1,973 @@
+--[[
+Name: AceEvent-2.0
+Revision: $Rev: 17803 $
+Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
+Inspired By: Ace 1.x by Turan (turan@gryphon.com)
+Website: http://www.wowace.com/
+Documentation: http://www.wowace.com/index.php/AceEvent-2.0
+SVN: http://svn.wowace.com/root/trunk/Ace2/AceEvent-2.0
+Description: Mixin to allow for event handling, scheduling, and inter-addon
+ communication.
+Dependencies: AceLibrary, AceOO-2.0
+]]
+
+local MAJOR_VERSION = "AceEvent-2.0"
+local MINOR_VERSION = "$Revision: 17803 $"
+
+if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
+if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
+
+if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
+if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end
+
+local AceOO = AceLibrary:GetInstance("AceOO-2.0")
+local Mixin = AceOO.Mixin
+local AceEvent = Mixin {
+ "RegisterEvent",
+ "RegisterAllEvents",
+ "UnregisterEvent",
+ "UnregisterAllEvents",
+ "TriggerEvent",
+ "ScheduleEvent",
+ "ScheduleRepeatingEvent",
+ "CancelScheduledEvent",
+ "CancelAllScheduledEvents",
+ "IsEventRegistered",
+ "IsEventScheduled",
+ "RegisterBucketEvent",
+ "UnregisterBucketEvent",
+ "UnregisterAllBucketEvents",
+ "IsBucketEventRegistered",
+ }
+
+local table_setn
+do
+ local version = GetBuildInfo()
+ if string.find(version, "^2%.") then
+ -- 2.0.0
+ table_setn = function() end
+ else
+ table_setn = table.setn
+ end
+end
+
+local weakKey = {__mode="k"}
+local new, del
+do
+ local list = setmetatable({}, weakKey)
+ function new()
+ local t = next(list)
+ if t then
+ list[t] = nil
+ return t
+ else
+ return {}
+ end
+ end
+
+ function del(t)
+ setmetatable(t, nil)
+ for k in pairs(t) do
+ t[k] = nil
+ end
+ list[t] = true
+ end
+end
+
+local FAKE_NIL
+local RATE
+
+local eventsWhichHappenOnce = {
+ PLAYER_LOGIN = true,
+ AceEvent_FullyInitialized = true,
+ VARIABLES_LOADED = true,
+ PLAYER_LOGOUT = true,
+}
+
+local registeringFromAceEvent
+function AceEvent:RegisterEvent(event, method, once)
+ AceEvent:argCheck(event, 2, "string")
+ if self == AceEvent and not registeringFromAceEvent then
+ AceEvent:argCheck(method, 3, "function")
+ self = method
+ else
+ AceEvent:argCheck(method, 3, "string", "function", "nil", "boolean", "number")
+ if type(method) == "boolean" or type(method) == "number" then
+ AceEvent:argCheck(once, 4, "nil")
+ once, method = method, event
+ end
+ end
+ AceEvent:argCheck(once, 4, "number", "boolean", "nil")
+ if eventsWhichHappenOnce[event] then
+ once = true
+ end
+ local throttleRate
+ if type(once) == "number" then
+ throttleRate, once = once
+ end
+ if not method then
+ method = event
+ end
+ if type(method) == "string" and type(self[method]) ~= "function" then
+ AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method)
+ else
+ assert(type(method) == "function" or type(method) == "string")
+ end
+
+ local AceEvent_registry = AceEvent.registry
+ if not AceEvent_registry[event] then
+ AceEvent_registry[event] = new()
+ AceEvent.frame:RegisterEvent(event)
+ end
+
+ local remember = true
+ if AceEvent_registry[event][self] then
+ remember = false
+ end
+ AceEvent_registry[event][self] = method
+
+ local AceEvent_onceRegistry = AceEvent.onceRegistry
+ if once then
+ if not AceEvent_onceRegistry then
+ AceEvent.onceRegistry = new()
+ AceEvent_onceRegistry = AceEvent.onceRegistry
+ end
+ if not AceEvent_onceRegistry[event] then
+ AceEvent_onceRegistry[event] = new()
+ end
+ AceEvent_onceRegistry[event][self] = true
+ else
+ if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then
+ AceEvent_onceRegistry[event][self] = nil
+ if not next(AceEvent_onceRegistry[event]) then
+ AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event])
+ end
+ end
+ end
+
+ local AceEvent_throttleRegistry = AceEvent.throttleRegistry
+ if throttleRate then
+ if not AceEvent_throttleRegistry then
+ AceEvent.throttleRegistry = new()
+ AceEvent_throttleRegistry = AceEvent.throttleRegistry
+ end
+ if not AceEvent_throttleRegistry[event] then
+ AceEvent_throttleRegistry[event] = new()
+ end
+ if AceEvent_throttleRegistry[event][self] then
+ AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self])
+ end
+ AceEvent_throttleRegistry[event][self] = setmetatable(new(), weakKey)
+ local t = AceEvent_throttleRegistry[event][self]
+ t[RATE] = throttleRate
+ else
+ if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] then
+ if AceEvent_throttleRegistry[event][self] then
+ AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self])
+ end
+ if not next(AceEvent_throttleRegistry[event]) then
+ AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event])
+ end
+ end
+ end
+
+ if remember then
+ AceEvent:TriggerEvent("AceEvent_EventRegistered", self, event)
+ end
+end
+
+local ALL_EVENTS
+
+function AceEvent:RegisterAllEvents(method)
+ if self == AceEvent then
+ AceEvent:argCheck(method, 1, "function")
+ self = method
+ else
+ AceEvent:argCheck(method, 1, "string", "function")
+ if type(method) == "string" and type(self[method]) ~= "function" then
+ AceEvent:error("Cannot register all events to method %q, it does not exist", method)
+ end
+ end
+
+ local AceEvent_registry = AceEvent.registry
+ if not AceEvent_registry[ALL_EVENTS] then
+ AceEvent_registry[ALL_EVENTS] = new()
+ AceEvent.frame:RegisterAllEvents()
+ end
+
+ AceEvent_registry[ALL_EVENTS][self] = method
+end
+
+local _G = getfenv(0)
+local memstack, timestack = {}, {}
+local memdiff, timediff
+function AceEvent:TriggerEvent(event, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ AceEvent:argCheck(event, 2, "string")
+ local AceEvent_registry = AceEvent.registry
+ if (not AceEvent_registry[event] or not next(AceEvent_registry[event])) and (not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS])) then
+ return
+ end
+ local _G_event = _G.event
+ _G.event = event
+ local lastEvent = AceEvent.currentEvent
+ AceEvent.currentEvent = event
+
+ local AceEvent_onceRegistry = AceEvent.onceRegistry
+ local AceEvent_debugTable = AceEvent.debugTable
+ if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then
+ local tmp = new()
+ for obj, method in pairs(AceEvent_onceRegistry[event]) do
+ tmp[obj] = AceEvent_registry[event] and AceEvent_registry[event][obj] or nil
+ end
+ local obj = next(tmp)
+ while obj do
+ local mem, time
+ if AceEvent_debugTable then
+ if not AceEvent_debugTable[event] then
+ AceEvent_debugTable[event] = new()
+ end
+ if not AceEvent_debugTable[event][obj] then
+ AceEvent_debugTable[event][obj] = new()
+ AceEvent_debugTable[event][obj].mem = 0
+ AceEvent_debugTable[event][obj].time = 0
+ AceEvent_debugTable[event][obj].count = 0
+ end
+ if memdiff then
+ table.insert(memstack, memdiff)
+ table.insert(timestack, timediff)
+ end
+ memdiff, timediff = 0, 0
+ mem, time = gcinfo(), GetTime()
+ end
+ local method = tmp[obj]
+ AceEvent.UnregisterEvent(obj, event)
+ if type(method) == "string" then
+ local obj_method = obj[method]
+ if obj_method then
+ obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ end
+ elseif method then -- function
+ method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ end
+ if AceEvent_debugTable then
+ local dmem, dtime = memdiff, timediff
+ mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff
+ AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem
+ AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time
+ AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1
+
+ memdiff, timediff = table.remove(memstack), table.remove(timestack)
+ if memdiff then
+ memdiff = memdiff + mem + dmem
+ timediff = timediff + time + dtime
+ end
+ end
+ tmp[obj] = nil
+ obj = next(tmp)
+ end
+ del(tmp)
+ end
+
+ local AceEvent_throttleRegistry = AceEvent.throttleRegistry
+ local throttleTable = AceEvent_throttleRegistry and AceEvent_throttleRegistry[event]
+ if AceEvent_registry[event] then
+ local tmp = new()
+ for obj, method in pairs(AceEvent_registry[event]) do
+ tmp[obj] = method
+ end
+ local obj = next(tmp)
+ while obj do
+ local method = tmp[obj]
+ local continue = false
+ if throttleTable and throttleTable[obj] then
+ local a1 = a1
+ if a1 == nil then
+ a1 = FAKE_NIL
+ end
+ if not throttleTable[obj][a1] or GetTime() - throttleTable[obj][a1] >= throttleTable[obj][RATE] then
+ throttleTable[obj][a1] = GetTime()
+ else
+ continue = true
+ end
+ end
+ if not continue then
+ local mem, time
+ if AceEvent_debugTable then
+ if not AceEvent_debugTable[event] then
+ AceEvent_debugTable[event] = new()
+ end
+ if not AceEvent_debugTable[event][obj] then
+ AceEvent_debugTable[event][obj] = new()
+ AceEvent_debugTable[event][obj].mem = 0
+ AceEvent_debugTable[event][obj].time = 0
+ AceEvent_debugTable[event][obj].count = 0
+ end
+ if memdiff then
+ table.insert(memstack, memdiff)
+ table.insert(timestack, timediff)
+ end
+ memdiff, timediff = 0, 0
+ mem, time = gcinfo(), GetTime()
+ end
+ if type(method) == "string" then
+ local obj_method = obj[method]
+ if obj_method then
+ obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ end
+ elseif method then -- function
+ method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ end
+ if AceEvent_debugTable then
+ local dmem, dtime = memdiff, timediff
+ mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff
+ AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem
+ AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time
+ AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1
+
+ memdiff, timediff = table.remove(memstack), table.remove(timestack)
+ if memdiff then
+ memdiff = memdiff + mem + dmem
+ timediff = timediff + time + dtime
+ end
+ end
+ end
+ tmp[obj] = nil
+ obj = next(tmp)
+ end
+ del(tmp)
+ end
+ if AceEvent_registry[ALL_EVENTS] then
+ local tmp = new()
+ for obj, method in pairs(AceEvent_registry[ALL_EVENTS]) do
+ tmp[obj] = method
+ end
+ local obj = next(tmp)
+ while obj do
+ local method = tmp[obj]
+ local mem, time
+ if AceEvent_debugTable then
+ if not AceEvent_debugTable[event] then
+ AceEvent_debugTable[event] = new()
+ end
+ if not AceEvent_debugTable[event][obj] then
+ AceEvent_debugTable[event][obj] = new()
+ AceEvent_debugTable[event][obj].mem = 0
+ AceEvent_debugTable[event][obj].time = 0
+ AceEvent_debugTable[event][obj].count = 0
+ end
+ if memdiff then
+ table.insert(memstack, memdiff)
+ table.insert(timestack, timediff)
+ end
+ memdiff, timediff = 0, 0
+ mem, time = gcinfo(), GetTime()
+ end
+ if type(method) == "string" then
+ local obj_method = obj[method]
+ if obj_method then
+ obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ end
+ elseif method then -- function
+ method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ end
+ if AceEvent_debugTable then
+ local dmem, dtime = memdiff, timediff
+ mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff
+ AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem
+ AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time
+ AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1
+
+ memdiff, timediff = table.remove(memstack), table.remove(timestack)
+ if memdiff then
+ memdiff = memdiff + mem + dmem
+ timediff = timediff + time + dtime
+ end
+ end
+ tmp[obj] = nil
+ obj = next(tmp)
+ end
+ del(tmp)
+ end
+ _G.event = _G_event
+ AceEvent.currentEvent = lastEvent
+end
+
+-- local accessors
+local getn = table.getn
+local tinsert = table.insert
+local tremove = table.remove
+local floor = math.floor
+local GetTime = GetTime
+local next = next
+local pairs = pairs
+local unpack = unpack
+
+local delayRegistry
+local tmp = {}
+local function OnUpdate()
+ local t = GetTime()
+ for k,v in pairs(delayRegistry) do
+ tmp[k] = true
+ end
+ for k in pairs(tmp) do
+ local v = delayRegistry[k]
+ if v then
+ local v_time = v.time
+ if not v_time then
+ delayRegistry[k] = del(v)
+ elseif v_time <= t then
+ local v_repeatDelay = v.repeatDelay
+ if v_repeatDelay then
+ -- use the event time, not the current time, else timing inaccuracies add up over time
+ v.time = v_time + v_repeatDelay
+ end
+ local event = v.event
+ local mem, time
+ if AceEvent_debugTable then
+ mem, time = gcinfo(), GetTime()
+ end
+ if type(event) == "function" then
+ event(unpack(v))
+ else
+ AceEvent:TriggerEvent(event, unpack(v))
+ end
+ if AceEvent_debugTable then
+ mem, time = gcinfo() - mem, GetTime() - time
+ v.mem = v.mem + mem
+ v.timeSpent = v.timeSpent + time
+ v.count = v.count + 1
+ end
+ if not v_repeatDelay then
+ local x = delayRegistry[k]
+ if x and x.time == v_time then -- check if it was manually reset
+ delayRegistry[k] = del(v)
+ end
+ end
+ end
+ end
+ end
+ for k in pairs(tmp) do
+ tmp[k] = nil
+ end
+ if not next(delayRegistry) then
+ AceEvent.frame:Hide()
+ end
+end
+
+local function ScheduleEvent(self, repeating, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ local id
+ if type(event) == "string" or type(event) == "table" then
+ if type(event) == "table" then
+ if not delayRegistry or not delayRegistry[event] then
+ AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.")
+ end
+ end
+ if type(delay) ~= "number" then
+ id, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20
+ AceEvent:argCheck(event, 3, "string", "function", --[[ so message is right ]] "number")
+ AceEvent:argCheck(delay, 4, "number")
+ self:CancelScheduledEvent(id)
+ end
+ else
+ AceEvent:argCheck(event, 2, "string", "function")
+ AceEvent:argCheck(delay, 3, "number")
+ end
+
+ if not delayRegistry then
+ AceEvent.delayRegistry = new()
+ delayRegistry = AceEvent.delayRegistry
+ AceEvent.frame:SetScript("OnUpdate", OnUpdate)
+ end
+ local t
+ if type(id) == "table" then
+ for k in pairs(id) do
+ id[k] = nil
+ end
+ t = id
+ else
+ t = new()
+ end
+ t[1] = a1
+ t[2] = a2
+ t[3] = a3
+ t[4] = a4
+ t[5] = a5
+ t[6] = a6
+ t[7] = a7
+ t[8] = a8
+ t[9] = a9
+ t[10] = a10
+ t[11] = a11
+ t[12] = a12
+ t[13] = a13
+ t[14] = a14
+ t[15] = a15
+ t[16] = a16
+ t[17] = a17
+ t[18] = a18
+ t[19] = a19
+ t[20] = a20
+ table_setn(t, 20)
+ t.event = event
+ t.time = GetTime() + delay
+ t.self = self
+ t.id = id or t
+ t.repeatDelay = repeating and delay
+ if AceEvent_debugTable then
+ t.mem = 0
+ t.count = 0
+ t.timeSpent = 0
+ end
+ delayRegistry[t.id] = t
+ AceEvent.frame:Show()
+ return t.id
+end
+
+function AceEvent:ScheduleEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ if type(event) == "string" or type(event) == "table" then
+ if type(event) == "table" then
+ if not delayRegistry or not delayRegistry[event] then
+ AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.")
+ end
+ end
+ if type(delay) ~= "number" then
+ AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number")
+ AceEvent:argCheck(a1, 4, "number")
+ end
+ else
+ AceEvent:argCheck(event, 2, "string", "function")
+ AceEvent:argCheck(delay, 3, "number")
+ end
+
+ return ScheduleEvent(self, false, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+end
+
+function AceEvent:ScheduleRepeatingEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ if type(event) == "string" or type(event) == "table" then
+ if type(event) == "table" then
+ if not delayRegistry or not delayRegistry[event] then
+ AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.")
+ end
+ end
+ if type(delay) ~= "number" then
+ AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number")
+ AceEvent:argCheck(a1, 4, "number")
+ end
+ else
+ AceEvent:argCheck(event, 2, "string", "function")
+ AceEvent:argCheck(delay, 3, "number")
+ end
+
+ return ScheduleEvent(self, true, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+end
+
+function AceEvent:CancelScheduledEvent(t)
+ AceEvent:argCheck(t, 2, "string", "table")
+ if delayRegistry then
+ local v = delayRegistry[t]
+ if v then
+ delayRegistry[t] = del(v)
+ if not next(delayRegistry) then
+ AceEvent.frame:Hide()
+ end
+ return true
+ end
+ end
+ return false
+end
+
+function AceEvent:IsEventScheduled(t)
+ AceEvent:argCheck(t, 2, "string", "table")
+ if delayRegistry then
+ local v = delayRegistry[t]
+ if v then
+ return true, v.time - GetTime()
+ end
+ end
+ return false, nil
+end
+
+function AceEvent:UnregisterEvent(event)
+ AceEvent:argCheck(event, 2, "string")
+ local AceEvent_registry = AceEvent.registry
+ if AceEvent_registry[event] and AceEvent_registry[event][self] then
+ AceEvent_registry[event][self] = nil
+ local AceEvent_onceRegistry = AceEvent.onceRegistry
+ if AceEvent_onceRegistry and AceEvent_onceRegistry[event] and AceEvent_onceRegistry[event][self] then
+ AceEvent_onceRegistry[event][self] = nil
+ if not next(AceEvent_onceRegistry[event]) then
+ AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event])
+ end
+ end
+ local AceEvent_throttleRegistry = AceEvent.throttleRegistry
+ if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] and AceEvent_throttleRegistry[event][self] then
+ AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self])
+ if not next(AceEvent_throttleRegistry[event]) then
+ AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event])
+ end
+ end
+ if not next(AceEvent_registry[event]) then
+ AceEvent_registry[event] = del(AceEvent_registry[event])
+ if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then
+ AceEvent.frame:UnregisterEvent(event)
+ end
+ end
+ else
+ if self == AceEvent then
+ error(string.format("Cannot unregister event %q. Improperly unregistering from AceEvent-2.0.", event), 2)
+ else
+ AceEvent:error("Cannot unregister event %q. %q is not registered with it.", event, self)
+ end
+ end
+ AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event)
+end
+
+function AceEvent:UnregisterAllEvents()
+ local AceEvent_registry = AceEvent.registry
+ if AceEvent_registry[ALL_EVENTS] and AceEvent_registry[ALL_EVENTS][self] then
+ AceEvent_registry[ALL_EVENTS][self] = nil
+ if not next(AceEvent_registry[ALL_EVENTS]) then
+ del(AceEvent_registry[ALL_EVENTS])
+ AceEvent.frame:UnregisterAllEvents()
+ for k,v in pairs(AceEvent_registry) do
+ if k ~= ALL_EVENTS then
+ AceEvent.frame:RegisterEvent(k)
+ end
+ end
+ AceEvent_registry[event] = nil
+ end
+ end
+ local first = true
+ for event, data in pairs(AceEvent_registry) do
+ if first then
+ if AceEvent_registry.AceEvent_EventUnregistered then
+ event = "AceEvent_EventUnregistered"
+ else
+ first = false
+ end
+ end
+ local x = data[self]
+ data[self] = nil
+ if x and event ~= ALL_EVENTS then
+ if not next(data) then
+ del(data)
+ if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then
+ AceEvent.frame:UnregisterEvent(event)
+ end
+ AceEvent_registry[event] = nil
+ end
+ AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event)
+ end
+ if first then
+ event = nil
+ end
+ end
+ if AceEvent.onceRegistry then
+ for event, data in pairs(AceEvent.onceRegistry) do
+ data[self] = nil
+ end
+ end
+end
+
+function AceEvent:CancelAllScheduledEvents()
+ if delayRegistry then
+ for k,v in pairs(delayRegistry) do
+ if v.self == self then
+ delayRegistry[k] = del(v)
+ end
+ end
+ if not next(delayRegistry) then
+ AceEvent.frame:Hide()
+ end
+ end
+end
+
+function AceEvent:IsEventRegistered(event)
+ AceEvent:argCheck(event, 2, "string")
+ local AceEvent_registry = AceEvent.registry
+ if self == AceEvent then
+ return AceEvent_registry[event] and next(AceEvent_registry[event]) and true or false
+ end
+ if AceEvent_registry[event] and AceEvent_registry[event][self] then
+ return true, AceEvent_registry[event][self]
+ end
+ return false, nil
+end
+
+local bucketfunc
+function AceEvent:RegisterBucketEvent(event, delay, method)
+ AceEvent:argCheck(event, 2, "string", "table")
+ if type(event) == "table" then
+ for k,v in pairs(event) do
+ if type(k) ~= "number" then
+ AceEvent:error("All keys to argument #2 to `RegisterBucketEvent' must be numbers.")
+ elseif type(v) ~= "string" then
+ AceEvent:error("All values to argument #2 to `RegisterBucketEvent' must be strings.")
+ end
+ end
+ end
+ AceEvent:argCheck(delay, 3, "number")
+ if AceEvent == self then
+ AceEvent:argCheck(method, 4, "function")
+ self = method
+ else
+ if type(event) == "string" then
+ AceEvent:argCheck(method, 4, "string", "function", "nil")
+ if not method then
+ method = event
+ end
+ else
+ AceEvent:argCheck(method, 4, "string", "function")
+ end
+
+ if type(method) == "string" and type(self[method]) ~= "function" then
+ AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method)
+ end
+ end
+ if not AceEvent.buckets then
+ AceEvent.buckets = new()
+ end
+ if not AceEvent.buckets[event] then
+ AceEvent.buckets[event] = new()
+ end
+ if not AceEvent.buckets[event][self] then
+ AceEvent.buckets[event][self] = new()
+ AceEvent.buckets[event][self].current = new()
+ AceEvent.buckets[event][self].self = self
+ else
+ AceEvent.CancelScheduledEvent(self, AceEvent.buckets[event][self].id)
+ end
+ local bucket = AceEvent.buckets[event][self]
+ bucket.method = method
+
+ local func = function(arg1)
+ bucket.run = true
+ if arg1 then
+ bucket.current[arg1] = true
+ end
+ end
+ AceEvent.buckets[event][self].func = func
+ if type(event) == "string" then
+ AceEvent.RegisterEvent(self, event, func)
+ else
+ for _,v in ipairs(event) do
+ AceEvent.RegisterEvent(self, v, func)
+ end
+ end
+ if not bucketfunc then
+ bucketfunc = function(bucket)
+ local current = bucket.current
+ local method = bucket.method
+ local self = bucket.self
+ if bucket.run then
+ if type(method) == "string" then
+ self[method](self, current)
+ elseif method then -- function
+ method(current)
+ end
+ for k in pairs(current) do
+ current[k] = nil
+ k = nil
+ end
+ bucket.run = false
+ end
+ end
+ end
+ bucket.id = AceEvent.ScheduleRepeatingEvent(self, bucketfunc, delay, bucket)
+end
+
+function AceEvent:IsBucketEventRegistered(event)
+ AceEvent:argCheck(event, 2, "string", "table")
+ return AceEvent.buckets and AceEvent.buckets[event] and AceEvent.buckets[event][self]
+end
+
+function AceEvent:UnregisterBucketEvent(event)
+ AceEvent:argCheck(event, 2, "string", "table")
+ if not AceEvent.buckets or not AceEvent.buckets[event] or not AceEvent.buckets[event][self] then
+ AceEvent:error("Cannot unregister bucket event %q. %q is not registered with it.", event, self)
+ end
+
+ local bucket = AceEvent.buckets[event][self]
+
+ if type(event) == "string" then
+ AceEvent.UnregisterEvent(self, event)
+ else
+ for _,v in ipairs(event) do
+ AceEvent.UnregisterEvent(self, v)
+ end
+ end
+ AceEvent:CancelScheduledEvent(bucket.id)
+
+ del(bucket.current)
+ AceEvent.buckets[event][self] = del(AceEvent.buckets[event][self])
+ if not next(AceEvent.buckets[event]) then
+ AceEvent.buckets[event] = del(AceEvent.buckets[event])
+ end
+end
+
+function AceEvent:UnregisterAllBucketEvents()
+ if not AceEvent.buckets or not next(AceEvent.buckets) then
+ return
+ end
+ for k,v in pairs(AceEvent.buckets) do
+ if v == self then
+ AceEvent.UnregisterBucketEvent(self, k)
+ k = nil
+ end
+ end
+end
+
+function AceEvent:OnEmbedDisable(target)
+ self.UnregisterAllEvents(target)
+
+ self.CancelAllScheduledEvents(target)
+
+ self.UnregisterAllBucketEvents(target)
+end
+
+function AceEvent:EnableDebugging()
+ if not self.debugTable then
+ self.debugTable = new()
+
+ if delayRegistry then
+ for k,v in pairs(self.delayRegistry) do
+ if not v.mem then
+ v.mem = 0
+ v.count = 0
+ v.timeSpent = 0
+ end
+ end
+ end
+ end
+end
+
+function AceEvent:IsFullyInitialized()
+ return self.postInit or false
+end
+
+function AceEvent:IsPostPlayerLogin()
+ return self.playerLogin or false
+end
+
+function AceEvent:activate(oldLib, oldDeactivate)
+ AceEvent = self
+
+ if oldLib then
+ self.onceRegistry = oldLib.onceRegistry
+ self.throttleRegistry = oldLib.throttleRegistry
+ self.delayRegistry = oldLib.delayRegistry
+ self.buckets = oldLib.buckets
+ self.registry = oldLib.registry
+ self.frame = oldLib.frame
+ self.debugTable = oldLib.debugTable
+ self.playerLogin = oldLib.pew or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage and true
+ self.postInit = oldLib.postInit or self.playerLogin and ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r and true
+ self.ALL_EVENTS = oldLib.ALL_EVENTS
+ self.FAKE_NIL = oldLib.FAKE_NIL
+ self.RATE = oldLib.RATE
+ end
+ if not self.registry then
+ self.registry = {}
+ end
+ if not self.frame then
+ self.frame = CreateFrame("Frame", "AceEvent20Frame")
+ end
+ if not self.ALL_EVENTS then
+ self.ALL_EVENTS = {}
+ end
+ if not self.FAKE_NIL then
+ self.FAKE_NIL = {}
+ end
+ if not self.RATE then
+ self.RATE = {}
+ end
+ ALL_EVENTS = self.ALL_EVENTS
+ FAKE_NIL = self.FAKE_NIL
+ RATE = self.RATE
+ local inPlw = false
+ local blacklist = {
+ UNIT_INVENTORY_CHANGED = true,
+ BAG_UPDATE = true,
+ ITEM_LOCK_CHANGED = true,
+ ACTIONBAR_SLOT_CHANGED = true,
+ }
+ self.frame:SetScript("OnEvent", function()
+ local event = event
+ if event == "PLAYER_ENTERING_WORLD" then
+ inPlw = false
+ elseif event == "PLAYER_LEAVING_WORLD" then
+ inPlw = true
+ end
+ if event and (not inPlw or not blacklist[event]) then
+ self:TriggerEvent(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
+ end
+ end)
+ if self.delayRegistry then
+ delayRegistry = self.delayRegistry
+ self.frame:SetScript("OnUpdate", OnUpdate)
+ end
+
+ self:UnregisterAllEvents()
+ self:CancelAllScheduledEvents()
+
+ registeringFromAceEvent = true
+ self:RegisterEvent("LOOT_OPENED", function()
+ SendAddonMessage("LOOT_OPENED", "", "RAID")
+ end)
+ registeringFromAceEvent = nil
+
+ if not self.playerLogin then
+ registeringFromAceEvent = true
+ self:RegisterEvent("PLAYER_LOGIN", function()
+ self.playerLogin = true
+ end, true)
+ registeringFromAceEvent = nil
+ end
+
+ if not self.postInit then
+ local isReload = true
+ local function func()
+ self.postInit = true
+ self:TriggerEvent("AceEvent_FullyInitialized")
+ if self.registry["CHAT_MSG_CHANNEL_NOTICE"] and self.registry["CHAT_MSG_CHANNEL_NOTICE"][self] then
+ self:UnregisterEvent("CHAT_MSG_CHANNEL_NOTICE")
+ end
+ if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then
+ self:UnregisterEvent("MEETINGSTONE_CHANGED")
+ end
+ if self.registry["MINIMAP_ZONE_CHANGED"] and self.registry["MINIMAP_ZONE_CHANGED"][self] then
+ self:UnregisterEvent("MINIMAP_ZONE_CHANGED")
+ end
+ if self.registry["LANGUAGE_LIST_CHANGED"] and self.registry["LANGUAGE_LIST_CHANGED"][self] then
+ self:UnregisterEvent("LANGUAGE_LIST_CHANGED")
+ end
+ end
+ registeringFromAceEvent = true
+ local f = function()
+ self.playerLogin = true
+ self:ScheduleEvent("AceEvent_FullyInitialized", func, 1)
+ end
+ self:RegisterEvent("MEETINGSTONE_CHANGED", f, true)
+ self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", function()
+ self:ScheduleEvent("AceEvent_FullyInitialized", func, 0.05)
+ end)
+ self:RegisterEvent("LANGUAGE_LIST_CHANGED", function()
+ if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then
+ registeringFromAceEvent = true
+ self:UnregisterEvent("MEETINGSTONE_CHANGED")
+ self:RegisterEvent("MINIMAP_ZONE_CHANGED", f, true)
+ registeringFromAceEvent = nil
+ end
+ end)
+ self:ScheduleEvent("AceEvent_FullyInitialized", func, 10)
+ registeringFromAceEvent = nil
+ end
+
+ self.super.activate(self, oldLib, oldDeactivate)
+ if oldLib then
+ oldDeactivate(oldLib)
+ end
+end
+
+AceLibrary:Register(AceEvent, MAJOR_VERSION, MINOR_VERSION, AceEvent.activate)
+AceEvent = AceLibrary(MAJOR_VERSION)
diff --git a/Libs/AceLibrary/AceLibrary.lua b/Libs/AceLibrary/AceLibrary.lua
new file mode 100644
index 0000000..1e619da
--- /dev/null
+++ b/Libs/AceLibrary/AceLibrary.lua
@@ -0,0 +1,751 @@
+--[[
+Name: AceLibrary
+Revision: $Rev: 17722 $
+Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
+Inspired By: Iriel (iriel@vigilance-committee.org)
+ Tekkub (tekkub@gmail.com)
+ Revision: $Rev: 17722 $
+Website: http://www.wowace.com/
+Documentation: http://www.wowace.com/index.php/AceLibrary
+SVN: http://svn.wowace.com/root/trunk/Ace2/AceLibrary
+Description: Versioning library to handle other library instances, upgrading,
+ and proper access.
+ It also provides a base for libraries to work off of, providing
+ proper error tools. It is handy because all the errors occur in the
+ file that called it, not in the library file itself.
+Dependencies: None
+]]
+
+local ACELIBRARY_MAJOR = "AceLibrary"
+local ACELIBRARY_MINOR = "$Revision: 17723 $"
+
+if loadstring("return function(...) return ... end") and AceLibrary and AceLibrary:HasInstance(ACELIBRARY_MAJOR) then return end -- lua51 check
+
+local _G = getfenv(0)
+local previous = _G[ACELIBRARY_MAJOR]
+if previous and not previous:IsNewVersion(ACELIBRARY_MAJOR, ACELIBRARY_MINOR) then return end
+
+local function donothing() end
+
+local table_setn = string.find(GetBuildInfo(), "^2%.") and donothing or table.setn
+
+local string_gfind = string.gmatch or string.gfind
+
+-- The a1,a2,a3,a4,a5,a6,a7,a8,a9,a10 are used here instead of ... (vararg)
+-- It seems ugly but in Lua 5.1, Vararg functions create a table `arg' each
+-- time the function is called
+local function safecall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+ local success, err = pcall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
+ if not success then geterrorhandler()(err) end
+end
+
+-- @function destroyTable
+-- @brief remove all the contents of a table
+-- @param t table to destroy
+local function destroyTable(t)
+ setmetatable(t, nil)
+ for k,v in pairs(t) do t[k] = nil end
+ table_setn(t, 0)
+end
+
+-- @table AceLibrary
+-- @brief System to handle all versioning of libraries.
+local AceLibrary = {}
+local AceLibrary_mt = {}
+setmetatable(AceLibrary, AceLibrary_mt)
+
+local error
+do
+ local tmp = {}
+ function error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ if type(self) ~= "table" then
+ _G.error(string.format("Bad argument #1 to `error' (table expected, got %s)", type(self)), 2)
+ end
+
+ local stack = debugstack()
+ if not message then
+ local _,_,second = string.find(stack, "\n(.-)\n")
+ message = "error raised! " .. second
+ else
+ destroyTable(tmp)
+ table.insert(tmp, a1)
+ table.insert(tmp, a2)
+ table.insert(tmp, a3)
+ table.insert(tmp, a4)
+ table.insert(tmp, a5)
+ table.insert(tmp, a6)
+ table.insert(tmp, a7)
+ table.insert(tmp, a8)
+ table.insert(tmp, a9)
+ table.insert(tmp, a10)
+ table.insert(tmp, a11)
+ table.insert(tmp, a12)
+ table.insert(tmp, a13)
+ table.insert(tmp, a14)
+ table.insert(tmp, a15)
+ table.insert(tmp, a16)
+ table.insert(tmp, a17)
+ table.insert(tmp, a18)
+ table.insert(tmp, a19)
+ table.insert(tmp, a20)
+ for i = 1,table.getn(tmp) do
+ tmp[i] = tostring(tmp[i])
+ end
+ for i = 1,10 do
+ table.insert(tmp, "nil")
+ end
+ message = string.format(message, unpack(tmp))
+ end
+
+ if getmetatable(self) and getmetatable(self).__tostring then
+ message = string.format("%s: %s", tostring(self), message)
+ elseif type(rawget(self, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self:GetLibraryVersion()) then
+ message = string.format("%s: %s", self:GetLibraryVersion(), message)
+ elseif type(rawget(self, 'class')) == "table" and type(rawget(self.class, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self.class:GetLibraryVersion()) then
+ message = string.format("%s: %s", self.class:GetLibraryVersion(), message)
+ end
+
+ local first = string.gsub(stack, "\n.*", "")
+ local file = string.gsub(first, ".*\\(.*).lua:%d+: .*", "%1")
+ file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
+
+ local i = 0
+ for s in string_gfind(stack, "\n([^\n]*)") do
+ i = i + 1
+ if not string.find(s, file .. "%.lua:%d+:") then
+ file = string.gsub(s, "^.*\\(.*).lua:%d+: .*", "%1")
+ file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
+ break
+ end
+ end
+ local j = 0
+ for s in string_gfind(stack, "\n([^\n]*)") do
+ j = j + 1
+ if j > i and not string.find(s, file .. "%.lua:%d+:") then
+ _G.error(message, j + 1)
+ return
+ end
+ end
+ _G.error(message, 2)
+ return
+ end
+end
+
+local function assert(self, condition, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ if not condition then
+ if not message then
+ local stack = debugstack()
+ local _,_,second = string.find(stack, "\n(.-)\n")
+ message = "assertion failed! " .. second
+ end
+ error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ return
+ end
+ return condition
+end
+
+local function argCheck(self, arg, num, kind, kind2, kind3, kind4, kind5)
+ if type(num) ~= "number" then
+ error(self, "Bad argument #3 to `argCheck' (number expected, got %s)", type(num))
+ elseif type(kind) ~= "string" then
+ error(self, "Bad argument #4 to `argCheck' (string expected, got %s)", type(kind))
+ end
+ local errored = false
+ arg = type(arg)
+ if arg ~= kind and arg ~= kind2 and arg ~= kind3 and arg ~= kind4 and arg ~= kind5 then
+ local _,_,func = string.find(debugstack(), "`argCheck'.-([`<].-['>])")
+ if not func then
+ _,_,func = string.find(debugstack(), "([`<].-['>])")
+ end
+ if kind5 then
+ error(self, "Bad argument #%s to %s (%s, %s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, kind5, arg)
+ elseif kind4 then
+ error(self, "Bad argument #%s to %s (%s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, arg)
+ elseif kind3 then
+ error(self, "Bad argument #%s to %s (%s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, arg)
+ elseif kind2 then
+ error(self, "Bad argument #%s to %s (%s or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, arg)
+ else
+ error(self, "Bad argument #%s to %s (%s expected, got %s)", tonumber(num) or 0/0, func, kind, arg)
+ end
+ end
+end
+
+local function pcall(self, func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = _G.pcall(func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ if not a1 then
+ error(self, string.gsub(a2, ".-%.lua:%d-: ", ""))
+ else
+ return a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20
+ end
+end
+
+local addToPositions
+do
+ local recurse = {}
+ function addToPositions(t, major)
+ if not AceLibrary.positions[t] or AceLibrary.positions[t] == major then
+ rawset(t, recurse, true)
+ AceLibrary.positions[t] = major
+ for k,v in pairs(t) do
+ if type(v) == "table" and not rawget(v, recurse) then
+ addToPositions(v, major)
+ end
+ if type(k) == "table" and not rawget(k, recurse) then
+ addToPositions(k, major)
+ end
+ end
+ local mt = getmetatable(t)
+ if mt and not rawget(mt, recurse) then
+ addToPositions(mt, major)
+ end
+ rawset(t, recurse, nil)
+ end
+ end
+end
+
+local function svnRevisionToNumber(text)
+ if type(text) == "string" then
+ if string.find(text, "^%$Revision: (%d+) %$$") then
+ return tonumber((string.gsub(text, "^%$Revision: (%d+) %$$", "%1")))
+ elseif string.find(text, "^%$Rev: (%d+) %$$") then
+ return tonumber((string.gsub(text, "^%$Rev: (%d+) %$$", "%1")))
+ elseif string.find(text, "^%$LastChangedRevision: (%d+) %$$") then
+ return tonumber((string.gsub(text, "^%$LastChangedRevision: (%d+) %$$", "%1")))
+ end
+ elseif type(text) == "number" then
+ return text
+ end
+ return nil
+end
+
+-- @function crawlReplace
+-- @brief replace all keys/values of one value to another
+-- @param t table to modify
+-- @param to the value to replace
+-- @param from the value to be replaced
+-- @example crawlReplace({a="b",b="c"}, "d", "b") => {a="d",d="c"}
+local crawlReplace
+do
+ local recurse = {}
+ local function func(t, to, from)
+ recurse[t] = true
+ local mt = getmetatable(t)
+ setmetatable(t, nil)
+ rawset(t, to, rawget(t, from))
+ rawset(t, from, nil)
+ for k,v in pairs(t) do
+ if v == from then
+ t[k] = to
+ elseif type(v) == "table" then
+ if not recurse[v] then
+ func(v, to, from)
+ end
+ end
+
+ if type(k) == "table" then
+ if not recurse[k] then
+ func(k, to, from)
+ end
+ end
+ end
+ setmetatable(t, mt)
+ if mt then
+ if mt == from then
+ setmetatable(t, to)
+ elseif not recurse[mt] then
+ func(mt, to, from)
+ end
+ end
+ end
+ function crawlReplace(t, to, from)
+ destroyTable(recurse)
+ func(t, to, from)
+ end
+end
+
+local function isFrame(frame)
+ return type(frame) == "table" and type(rawget(frame, 0)) == "userdata" and type(rawget(frame, 'IsFrameType')) == "function" and getmetatable(frame) and type(rawget(getmetatable(frame), '__index')) == "function"
+end
+
+-- operator new/del with memory management
+local new, del
+do
+ -- this table contains the created tables which are not in use
+ local tables = setmetatable({}, {__mode = "k"})
+
+ function new()
+ local t = next(tables)
+ if t then
+ tables[t] = nil
+ return t
+ else
+ return {}
+ end
+ end
+
+ -- recursively destruct the table
+ function del(t, depth)
+ if depth and depth > 0 then
+ for k,v in pairs(t) do
+ if type(v) == "table" and not isFrame(v) then
+ del(v, depth - 1)
+ end
+ end
+ end
+ destroyTable(t)
+ tables[t] = true
+ end
+end
+
+-- @function copyTable
+-- @brief Create a shallow copy of a table and return it.
+-- @param from The table to copy from
+-- @return A shallow copy of the table
+local function copyTable(from)
+ local to = new()
+ for k,v in pairs(from) do to[k] = v end
+ table_setn(to, table.getn(from))
+ setmetatable(to, getmetatable(from))
+ return to
+end
+
+-- @function deepTransfer
+-- @brief Fully transfer all data, keeping proper previous table
+-- backreferences stable.
+-- @param to The table with which data is to be injected into
+-- @param from The table whose data will be injected into the first
+-- @param saveFields If available, a shallow copy of the basic data is saved
+-- in here.
+-- @param list The account of table references
+-- @param list2 The current status on which tables have been traversed.
+local deepTransfer
+do
+ -- @function examine
+ -- @brief Take account of all the table references to be shared
+ -- between the to and from tables.
+ -- @param to The table with which data is to be injected into
+ -- @param from The table whose data will be injected into the first
+ -- @param list An account of the table references
+ local function examine(to, from, list, major)
+ list[from] = to
+ for k,v in pairs(from) do
+ if rawget(to, k) and type(from[k]) == "table" and type(to[k]) == "table" and not list[from[k]] then
+ if from[k] == to[k] then
+ list[from[k]] = to[k]
+ elseif AceLibrary.positions[from[v]] ~= major and AceLibrary.positions[from[v]] then
+ list[from[k]] = from[k]
+ elseif not list[from[k]] then
+ examine(to[k], from[k], list, major)
+ end
+ end
+ end
+ return list
+ end
+
+ function deepTransfer(to, from, saveFields, major, list, list2)
+ setmetatable(to, nil)
+ local createdList
+ if not list then
+ createdList = true
+ list = new()
+ list2 = new()
+ examine(to, from, list, major)
+ end
+ list2[to] = to
+ for k,v in pairs(to) do
+ if type(rawget(from, k)) ~= "table" or type(v) ~= "table" or isFrame(v) then
+ if saveFields then
+ saveFields[k] = v
+ end
+ to[k] = nil
+ elseif v ~= _G then
+ if saveFields then
+ saveFields[k] = copyTable(v)
+ end
+ end
+ end
+ for k in pairs(from) do
+ if rawget(to, k) and to[k] ~= from[k] and AceLibrary.positions[to[k]] == major and from[k] ~= _G then
+ if not list2[to[k]] then
+ deepTransfer(to[k], from[k], nil, major, list, list2)
+ end
+ to[k] = list[to[k]] or list2[to[k]]
+ else
+ rawset(to, k, from[k])
+ end
+ end
+ table_setn(to, table.getn(from))
+ setmetatable(to, getmetatable(from))
+ local mt = getmetatable(to)
+ if mt then
+ if list[mt] then
+ setmetatable(to, list[mt])
+ elseif mt.__index and list[mt.__index] then
+ mt.__index = list[mt.__index]
+ end
+ end
+ destroyTable(from)
+ if createdList then
+ del(list)
+ del(list2)
+ end
+ end
+end
+
+-- @method TryToLoadStandalone
+-- @brief Attempt to find and load a standalone version of the requested library
+-- @param major A string representing the major version
+-- @return If library is found, return values from the call to LoadAddOn are returned
+-- If the library has been requested previously, nil is returned.
+local function TryToLoadStandalone(major)
+ if not AceLibrary.scannedlibs then AceLibrary.scannedlibs = {} end
+ if AceLibrary.scannedlibs[major] then return end
+
+ AceLibrary.scannedlibs[major] = true
+
+ local name, _, _, enabled, loadable = GetAddOnInfo(major)
+ if loadable then
+ return LoadAddOn(name)
+ end
+
+ for i=1,GetNumAddOns() do
+ if GetAddOnMetadata(i, "X-AceLibrary-"..major) then
+ local name, _, _, enabled, loadable = GetAddOnInfo(i)
+ if loadable then
+ return LoadAddOn(name)
+ end
+ end
+ end
+end
+
+local function minorCheck(minor, func)
+ if type(minor) == "string" then
+ local m = svnRevisionToNumber(minor)
+ if m then
+ return m
+ else
+ _G.error(string.format("Bad argument #3 to `%s'. Must be a number or SVN revision string. %q is not appropriate", func, minor), 3)
+ end
+ else
+ return minor
+ end
+end
+
+-- @method IsNewVersion
+-- @brief Obtain whether the supplied version would be an upgrade to the
+-- current version. This allows for bypass code in library
+-- declaration.
+-- @param major A string representing the major version
+-- @param minor An integer or an svn revision string representing the minor version
+-- @return whether the supplied version would be newer than what is
+-- currently available.
+function AceLibrary:IsNewVersion(major, minor)
+ argCheck(self, major, 2, "string")
+ TryToLoadStandalone(major)
+ minor = minorCheck(minor, "IsNewVersion")
+ argCheck(self, minor, 3, "number")
+ local data = self.libs[major]
+ if not data then
+ return true
+ end
+ return data.minor < minor
+end
+
+-- @method HasInstance
+-- @brief Returns whether an instance exists. This allows for optional support of a library.
+-- @param major A string representing the major version.
+-- @param minor (optional) An integer or an svn revision string representing the minor version.
+-- @return Whether an instance exists.
+function AceLibrary:HasInstance(major, minor)
+ argCheck(self, major, 2, "string")
+ TryToLoadStandalone(major)
+
+ if minor then
+ minor = minorCheck(minor, "HasInstance")
+ argCheck(self, minor, 3, "number")
+ if not self.libs[major] then
+ return
+ end
+ return self.libs[major].minor == minor
+ end
+ return self.libs[major] and true
+end
+
+-- @method GetInstance
+-- @brief Returns the library with the given major/minor version.
+-- @param major A string representing the major version.
+-- @param minor (optional) An integer or an svn revision string representing the minor version.
+-- @return The library with the given major/minor version.
+function AceLibrary:GetInstance(major, minor)
+ argCheck(self, major, 2, "string")
+ TryToLoadStandalone(major)
+
+ local data = self.libs[major]
+ if not data then
+ _G.error(string.format("Cannot find a library instance of %s.", major), 2)
+ return
+ end
+ if minor then
+ minor = minorCheck(minor, "GetInstance")
+ argCheck(self, minor, 2, "number")
+ if data.minor ~= minor then
+ _G.error(string.format("Cannot find a library instance of %s, minor version %d.", major, minor), 2)
+ return
+ end
+ end
+ return data.instance
+end
+
+-- Syntax sugar. AceLibrary("FooBar-1.0")
+AceLibrary_mt.__call = AceLibrary.GetInstance
+
+local AceEvent
+
+-- @method Register
+-- @brief Registers a new version of a given library.
+-- @param newInstance the library to register
+-- @param major the major version of the library
+-- @param minor the minor version of the library
+-- @param activateFunc (optional) A function to be called when the library is
+-- fully activated. Takes the arguments
+-- (newInstance [, oldInstance, oldDeactivateFunc]). If
+-- oldInstance is given, you should probably call
+-- oldDeactivateFunc(oldInstance).
+-- @param deactivateFunc (optional) A function to be called by a newer library's
+-- activateFunc.
+-- Take the arguments
+-- (oldInstance)
+-- @param externalFunc (optional) A function to be called whenever a new
+-- library is registered.
+-- Takes the arguments
+-- (instance, otherInstanceName, otherInstance)
+-- This function will be called on two sides between the new
+-- library and old ones
+function AceLibrary:Register(newInstance, major, minor, activateFunc, deactivateFunc, externalFunc)
+ argCheck(self, newInstance, 2, "table")
+ argCheck(self, major, 3, "string")
+ if type(minor) == "string" then
+ minor = minorCheck(minor, "Register")
+ end
+ argCheck(self, minor, 4, "number")
+ if math.floor(minor) ~= minor or minor < 0 then
+ error(self, "Bad argument #4 to `Register' (integer >= 0 expected, got %s)", minor)
+ end
+ argCheck(self, activateFunc, 5, "function", "nil")
+ argCheck(self, deactivateFunc, 6, "function", "nil")
+ argCheck(self, externalFunc, 7, "function", "nil")
+ if not deactivateFunc then
+ deactivateFunc = donothing
+ end
+ local data = self.libs[major]
+ local isAceLibrary = (AceLibrary == newInstance)
+
+ if not data then
+ -- This is new
+ local instance = copyTable(newInstance)
+ crawlReplace(instance, instance, newInstance)
+ destroyTable(newInstance)
+ if isAceLibrary then
+ self = instance
+ AceLibrary = instance
+ end
+ self.libs[major] = {
+ instance = instance,
+ minor = minor,
+ deactivateFunc = deactivateFunc,
+ externalFunc = externalFunc,
+ }
+ rawset(instance, 'GetLibraryVersion', function(self)
+ return major, minor
+ end)
+ if not rawget(instance, 'error') then
+ rawset(instance, 'error', error)
+ end
+ if not rawget(instance, 'assert') then
+ rawset(instance, 'assert', assert)
+ end
+ if not rawget(instance, 'argCheck') then
+ rawset(instance, 'argCheck', argCheck)
+ end
+ if not rawget(instance, 'pcall') then
+ rawset(instance, 'pcall', pcall)
+ end
+ addToPositions(instance, major)
+ if activateFunc then
+ safecall(activateFunc, instance, nil, nil) -- no old version, so explicit nil
+ end
+
+ if externalFunc then
+ for k,data in pairs(self.libs) do
+ if k ~= major then
+ safecall(externalFunc, instance, k, data.instance)
+ end
+ end
+ end
+
+ for k,data in pairs(self.libs) do
+ if k ~= major and data.externalFunc then
+ safecall(data.externalFunc, data.instance, major, instance)
+ end
+ end
+ if major == "AceEvent-2.0" then
+ AceEvent = instance
+ end
+ if AceEvent then
+ AceEvent.TriggerEvent(self, "AceLibrary_Register", major, instance)
+ end
+
+ return instance
+ end
+ local instance = data.instance
+ if minor <= data.minor then
+ -- This one is already obsolete, raise an error.
+ _G.error(string.format("Obsolete library registered. %s is already registered at version %d. You are trying to register version %d. Hint: if not AceLibrary:IsNewVersion(%q, %d) then return end", major, data.minor, minor, major, minor), 2)
+ return
+ end
+ -- This is an update
+ local oldInstance = new()
+
+ addToPositions(newInstance, major)
+
+ local old_error, old_assert, old_argCheck, old_pcall
+ if isAceLibrary then
+ self = instance
+ AceLibrary = instance
+
+ old_error = instance.error
+ old_assert = instance.assert
+ old_argCheck = instance.argCheck
+ old_pcall = instance.pcall
+
+ self.error = error
+ self.assert = assert
+ self.argCheck = argCheck
+ self.pcall = pcall
+ end
+ deepTransfer(instance, newInstance, oldInstance, major)
+ crawlReplace(instance, instance, newInstance)
+ local oldDeactivateFunc = data.deactivateFunc
+ data.minor = minor
+ data.deactivateFunc = deactivateFunc
+ data.externalFunc = externalFunc
+ rawset(instance, 'GetLibraryVersion', function(self)
+ return major, minor
+ end)
+ if not rawget(instance, 'error') then
+ rawset(instance, 'error', error)
+ end
+ if not rawget(instance, 'assert') then
+ rawset(instance, 'assert', assert)
+ end
+ if not rawget(instance, 'argCheck') then
+ rawset(instance, 'argCheck', argCheck)
+ end
+ if not rawget(instance, 'pcall') then
+ rawset(instance, 'pcall', pcall)
+ end
+ if isAceLibrary then
+ for _,v in pairs(self.libs) do
+ local i = type(v) == "table" and v.instance
+ if type(i) == "table" then
+ if not rawget(i, 'error') or i.error == old_error then
+ rawset(i, 'error', error)
+ end
+ if not rawget(i, 'assert') or i.assert == old_assert then
+ rawset(i, 'assert', assert)
+ end
+ if not rawget(i, 'argCheck') or i.argCheck == old_argCheck then
+ rawset(i, 'argCheck', argCheck)
+ end
+ if not rawget(i, 'pcall') or i.pcall == old_pcall then
+ rawset(i, 'pcall', pcall)
+ end
+ end
+ end
+ end
+ if activateFunc then
+ safecall(activateFunc, instance, oldInstance, oldDeactivateFunc)
+ else
+ safecall(oldDeactivateFunc, oldInstance)
+ end
+ del(oldInstance)
+
+ if externalFunc then
+ for k,data in pairs(self.libs) do
+ if k ~= major then
+ safecall(externalFunc, instance, k, data.instance)
+ end
+ end
+ end
+
+ return instance
+end
+
+do
+ local function iter(t, k)
+ k = next(t, k)
+ if not k then
+ return nil
+ else
+ return k, t[k].instance
+ end
+ end
+
+ -- Example
+ -- for major, instance in AceLibrary:IterateLibraries() do
+ -- ...
+ -- end
+ function AceLibrary:IterateLibraries()
+ return iter, self.libs, nil
+ end
+end
+
+-- @function Activate
+-- @brief The activateFunc for AceLibrary itself. Called when
+-- AceLibrary properly registers.
+-- @param self Reference to AceLibrary
+-- @param oldLib (optional) Reference to an old version of AceLibrary
+-- @param oldDeactivate (optional) Function to deactivate the old lib
+local function activate(self, oldLib, oldDeactivate)
+ if not self.libs then
+ if oldLib then
+ self.libs = oldLib.libs
+ self.scannedlibs = oldLib.scannedlibs
+ end
+ if not self.libs then
+ self.libs = {}
+ end
+ if not self.scannedlibs then
+ self.scannedlibs = {}
+ end
+ end
+ if not self.positions then
+ if oldLib then
+ self.positions = oldLib.positions
+ end
+ if not self.positions then
+ self.positions = setmetatable({}, { __mode = "k" })
+ end
+ end
+
+ -- Expose the library in the global environment
+ _G[ACELIBRARY_MAJOR] = self
+
+ if oldDeactivate then
+ oldDeactivate(oldLib)
+ end
+end
+
+if not previous then
+ previous = AceLibrary
+end
+if not previous.libs then
+ previous.libs = {}
+end
+AceLibrary.libs = previous.libs
+if not previous.positions then
+ previous.positions = setmetatable({}, { __mode = "k" })
+end
+AceLibrary.positions = previous.positions
+AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate)
diff --git a/Libs/AceLocale-2.2/AceLocale-2.2.lua b/Libs/AceLocale-2.2/AceLocale-2.2.lua
new file mode 100644
index 0000000..da95cdb
--- /dev/null
+++ b/Libs/AceLocale-2.2/AceLocale-2.2.lua
@@ -0,0 +1,538 @@
+--[[
+Name: AceLocale-2.2
+Revision: $Rev: 17638 $
+Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
+Inspired By: Ace 1.x by Turan (turan@gryphon.com)
+Website: http://www.wowace.com/
+Documentation: http://www.wowace.com/index.php/AceLocale-2.2
+SVN: http://svn.wowace.com/root/trunk/Ace2/AceLocale-2.2
+Description: Localization library for addons to use to handle proper
+ localization and internationalization.
+Dependencies: AceLibrary
+]]
+
+local MAJOR_VERSION = "AceLocale-2.2"
+local MINOR_VERSION = "$Revision: 17638 $"
+
+if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
+if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
+
+if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
+local AceLocale = {}
+
+local DEFAULT_LOCALE = "enUS"
+local _G = getfenv(0)
+
+local BASE_TRANSLATIONS, DEBUGGING, TRANSLATIONS, BASE_LOCALE, TRANSLATION_TABLES, REVERSE_TRANSLATIONS, STRICTNESS, DYNAMIC_LOCALES, CURRENT_LOCALE, NAME
+
+local rawget = rawget
+local rawset = rawset
+local type = type
+
+local newRegistries = {}
+local scheduleClear
+
+local lastSelf
+local __index = function(self, key)
+ lastSelf = self
+ local value = (rawget(self, TRANSLATIONS) or AceLocale.prototype)[key]
+ rawset(self, key, value)
+ return value
+end
+
+local __newindex = function(self, k, v)
+ if type(v) ~= "function" and type(k) ~= "table" then
+ AceLocale.error(self, "Cannot change the values of an AceLocale instance.")
+ end
+ rawset(self, k, v)
+end
+
+local __tostring = function(self)
+ if type(rawget(self, 'GetLibraryVersion')) == "function" then
+ return self:GetLibraryVersion()
+ else
+ return "AceLocale(" .. self[NAME] .. ")"
+ end
+end
+
+local function clearCache(self)
+ if not rawget(self, BASE_TRANSLATIONS) then
+ return
+ end
+
+ local cache = self[BASE_TRANSLATIONS]
+ rawset(self, REVERSE_TRANSLATIONS, nil)
+
+ for k in pairs(self) do
+ if rawget(cache, k) ~= nil then
+ self[k] = nil
+ end
+ end
+ rawset(self, 'tmp', true)
+ self.tmp = nil
+end
+
+local function refixInstance(instance)
+ if getmetatable(instance) then
+ setmetatable(instance, nil)
+ end
+ local translations = instance[TRANSLATIONS]
+ if translations then
+ if getmetatable(translations) then
+ setmetatable(translations, nil)
+ end
+ local baseTranslations = instance[BASE_TRANSLATIONS]
+ if getmetatable(baseTranslations) then
+ setmetatable(baseTranslations, nil)
+ end
+ if translations == baseTranslations or instance[STRICTNESS] then
+ setmetatable(instance, {
+ __index = __index,
+ __newindex = __newindex,
+ __tostring = __tostring
+ })
+
+ setmetatable(translations, {
+ __index = AceLocale.prototype
+ })
+ else
+ setmetatable(instance, {
+ __index = __index,
+ __newindex = __newindex,
+ __tostring = __tostring
+ })
+
+ setmetatable(translations, {
+ __index = baseTranslations,
+ })
+
+ setmetatable(baseTranslations, {
+ __index = AceLocale.prototype,
+ })
+ end
+ else
+ setmetatable(instance, {
+ __index = __index,
+ __newindex = __newindex,
+ __tostring = __tostring,
+ })
+ end
+ clearCache(instance)
+ newRegistries[instance] = true
+ scheduleClear()
+ return instance
+end
+
+function AceLocale:new(name)
+ self:argCheck(name, 2, "string")
+
+ if self.registry[name] and type(rawget(self.registry[name], 'GetLibraryVersion')) ~= "function" then
+ return self.registry[name]
+ end
+
+ AceLocale.registry[name] = refixInstance({
+ [STRICTNESS] = false,
+ [NAME] = name,
+ })
+ newRegistries[AceLocale.registry[name]] = true
+ return AceLocale.registry[name]
+end
+
+AceLocale.prototype = { class = AceLocale }
+
+function AceLocale.prototype:EnableDebugging()
+ if rawget(self, BASE_TRANSLATIONS) then
+ AceLocale.error(self, "Cannot enable debugging after a translation has been registered.")
+ end
+ rawset(self, DEBUGGING, true)
+end
+
+function AceLocale.prototype:EnableDynamicLocales(override)
+ AceLocale.argCheck(self, override, 2, "boolean", "nil")
+ if not override and rawget(self, BASE_TRANSLATIONS) then
+ AceLocale.error(self, "Cannot enable dynamic locales after a translation has been registered.")
+ end
+ if not rawget(self, DYNAMIC_LOCALES) then
+ rawset(self, DYNAMIC_LOCALES, true)
+ if rawget(self, BASE_LOCALE) then
+ if not rawget(self, TRANSLATION_TABLES) then
+ rawset(self, TRANSLATION_TABLES, {})
+ end
+ self[TRANSLATION_TABLES][self[BASE_LOCALE]] = self[BASE_TRANSLATIONS]
+ self[TRANSLATION_TABLES][self[CURRENT_LOCALE]] = self[TRANSLATIONS]
+ end
+ end
+end
+
+function AceLocale.prototype:RegisterTranslations(locale, func)
+ AceLocale.argCheck(self, locale, 2, "string")
+ AceLocale.argCheck(self, func, 3, "function")
+
+ if locale == rawget(self, BASE_LOCALE) then
+ AceLocale.error(self, "Cannot provide the same locale more than once. %q provided twice.", locale)
+ end
+
+ if rawget(self, BASE_TRANSLATIONS) and GetLocale() ~= locale then
+ if rawget(self, DEBUGGING) or rawget(self, DYNAMIC_LOCALES) then
+ if not rawget(self, TRANSLATION_TABLES) then
+ rawset(self, TRANSLATION_TABLES, {})
+ end
+ if self[TRANSLATION_TABLES][locale] then
+ AceLocale.error(self, "Cannot provide the same locale more than once. %q provided twice.", locale)
+ end
+ local t = func()
+ func = nil
+ if type(t) ~= "table" then
+ AceLocale.error(self, "Bad argument #3 to `RegisterTranslations'. function did not return a table. (expected table, got %s)", type(t))
+ end
+ self[TRANSLATION_TABLES][locale] = t
+ t = nil
+ end
+ func = nil
+ return
+ end
+ local t = func()
+ func = nil
+ if type(t) ~= "table" then
+ AceLocale.error(self, "Bad argument #3 to `RegisterTranslations'. function did not return a table. (expected table, got %s)", type(t))
+ end
+
+ rawset(self, TRANSLATIONS, t)
+ if not rawget(self, BASE_TRANSLATIONS) then
+ rawset(self, BASE_TRANSLATIONS, t)
+ rawset(self, BASE_LOCALE, locale)
+ for key,value in pairs(t) do
+ if value == true then
+ t[key] = key
+ end
+ end
+ else
+ for key, value in pairs(self[TRANSLATIONS]) do
+ if not rawget(self[BASE_TRANSLATIONS], key) then
+ AceLocale.error(self, "Improper translation exists. %q is likely misspelled for locale %s.", key, locale)
+ end
+ if value == true then
+ AceLocale.error(self, "Can only accept true as a value on the base locale. %q is the base locale, %q is not.", rawget(self, BASE_LOCALE), locale)
+ end
+ end
+ end
+ rawset(self, CURRENT_LOCALE, locale)
+ refixInstance(self)
+ if rawget(self, DEBUGGING) or rawget(self, DYNAMIC_LOCALES) then
+ if not rawget(self, TRANSLATION_TABLES) then
+ rawset(self, TRANSLATION_TABLES, {})
+ end
+ self[TRANSLATION_TABLES][locale] = t
+ end
+ t = nil
+end
+
+function AceLocale.prototype:SetLocale(locale)
+ AceLocale.argCheck(self, locale, 2, "string", "boolean")
+ if not rawget(self, DYNAMIC_LOCALES) then
+ AceLocale.error(self, "Cannot call `SetLocale' without first calling `EnableDynamicLocales'.")
+ end
+ if not rawget(self, TRANSLATION_TABLES) then
+ AceLocale.error(self, "Cannot call `SetLocale' without first calling `RegisterTranslations'.")
+ end
+ if locale == true then
+ locale = GetLocale()
+ if not self[TRANSLATION_TABLES][locale] then
+ locale = self[BASE_LOCALE]
+ end
+ end
+
+ if self[CURRENT_LOCALE] == locale then
+ return
+ end
+
+ if not self[TRANSLATION_TABLES][locale] then
+ AceLocale.error(self, "Locale %q not registered.", locale)
+ end
+
+ self[TRANSLATIONS] = self[TRANSLATION_TABLES][locale]
+ self[CURRENT_LOCALE] = locale
+ refixInstance(self)
+end
+
+function AceLocale.prototype:GetLocale()
+ if not rawget(self, TRANSLATION_TABLES) then
+ AceLocale.error(self, "Cannot call `GetLocale' without first calling `RegisterTranslations'.")
+ end
+ return self[CURRENT_LOCALE]
+end
+
+local function iter(t, position)
+ return (next(t, position))
+end
+
+function AceLocale.prototype:IterateAvailableLocales()
+ if not rawget(self, DYNAMIC_LOCALES) then
+ AceLocale.error(self, "Cannot call `IterateAvailableLocales' without first calling `EnableDynamicLocales'.")
+ end
+ if not rawget(self, TRANSLATION_TABLES) then
+ AceLocale.error(self, "Cannot call `IterateAvailableLocales' without first calling `RegisterTranslations'.")
+ end
+ return iter, self[TRANSLATION_TABLES], nil
+end
+
+function AceLocale.prototype:HasLocale(locale)
+ if not rawget(self, DYNAMIC_LOCALES) then
+ AceLocale.error(self, "Cannot call `HasLocale' without first calling `EnableDynamicLocales'.")
+ end
+ AceLocale.argCheck(self, locale, 2, "string")
+ return rawget(self, TRANSLATION_TABLES) and self[TRANSLATION_TABLES][locale] ~= nil
+end
+
+function AceLocale.prototype:SetStrictness(strict)
+ AceLocale.argCheck(self, strict, 2, "boolean")
+ local mt = getmetatable(self)
+ if not mt then
+ AceLocale.error(self, "Cannot call `SetStrictness' without a metatable.")
+ end
+ if not rawget(self, TRANSLATIONS) then
+ AceLocale.error(self, "No translations registered.")
+ end
+ rawset(self, STRICTNESS, strict)
+ refixInstance(self)
+end
+
+local function initReverse(self)
+ rawset(self, REVERSE_TRANSLATIONS, {})
+ local alpha = self[TRANSLATIONS]
+ local bravo = self[REVERSE_TRANSLATIONS]
+ for base, localized in pairs(alpha) do
+ bravo[localized] = base
+ end
+end
+
+function AceLocale.prototype:GetTranslation(text)
+ AceLocale.argCheck(self, text, 1, "string", "number")
+ if not rawget(self, TRANSLATIONS) then
+ AceLocale.error(self, "No translations registered")
+ end
+ return self[text]
+end
+
+function AceLocale.prototype:GetStrictTranslation(text)
+ AceLocale.argCheck(self, text, 1, "string", "number")
+ local x = rawget(self, TRANSLATIONS)
+ if not x then
+ AceLocale.error(self, "No translations registered")
+ end
+ local value = rawget(x, text)
+ if value == nil then
+ AceLocale.error(self, "Translation %q does not exist for locale %s", text, self[CURRENT_LOCALE])
+ end
+ return value
+end
+
+function AceLocale.prototype:GetReverseTranslation(text)
+ local x = rawget(self, REVERSE_TRANSLATIONS)
+ if not x then
+ if not rawget(self, TRANSLATIONS) then
+ AceLocale.error(self, "No translations registered")
+ end
+ initReverse(self)
+ x = self[REVERSE_TRANSLATIONS]
+ end
+ local translation = x[text]
+ if not translation then
+ AceLocale.error(self, "Reverse translation for %q does not exist", text)
+ end
+ return translation
+end
+
+function AceLocale.prototype:GetIterator()
+ local x = rawget(self, TRANSLATIONS)
+ if not x then
+ AceLocale.error(self, "No translations registered")
+ end
+ return next, x, nil
+end
+
+function AceLocale.prototype:GetReverseIterator()
+ local x = rawget(self, REVERSE_TRANSLATIONS)
+ if not x then
+ if not rawget(self, TRANSLATIONS) then
+ AceLocale.error(self, "No translations registered")
+ end
+ initReverse(self)
+ x = self[REVERSE_TRANSLATIONS]
+ end
+ return next, x, nil
+end
+
+function AceLocale.prototype:HasTranslation(text)
+ AceLocale.argCheck(self, text, 1, "string", "number")
+ local x = rawget(self, TRANSLATIONS)
+ if not x then
+ AceLocale.error(self, "No translations registered")
+ end
+ return rawget(x, text) and true
+end
+
+function AceLocale.prototype:HasReverseTranslation(text)
+ local x = rawget(self, REVERSE_TRANSLATIONS)
+ if not x then
+ if not rawget(self, TRANSLATIONS) then
+ AceLocale.error(self, "No translations registered")
+ end
+ initReverse(self)
+ x = self[REVERSE_TRANSLATIONS]
+ end
+ return x[text] and true
+end
+
+function AceLocale.prototype:Debug()
+ if not rawget(self, DEBUGGING) then
+ return
+ end
+ local words = {}
+ local locales = {"enUS", "ruRU", "deDE", "frFR", "koKR", "zhCN", "zhTW", "esES"}
+ local localizations = {}
+ DEFAULT_CHAT_FRAME:AddMessage("--- AceLocale Debug ---")
+ for _,locale in ipairs(locales) do
+ if not self[TRANSLATION_TABLES][locale] then
+ DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q not found", locale))
+ else
+ localizations[locale] = self[TRANSLATION_TABLES][locale]
+ end
+ end
+ local localeDebug = {}
+ for locale, localization in pairs(localizations) do
+ localeDebug[locale] = {}
+ for word in pairs(localization) do
+ if type(localization[word]) == "table" then
+ if type(words[word]) ~= "table" then
+ words[word] = {}
+ end
+ for bit in pairs(localization[word]) do
+ if type(localization[word][bit]) == "string" then
+ words[word][bit] = true
+ end
+ end
+ elseif type(localization[word]) == "string" then
+ words[word] = true
+ end
+ end
+ end
+ for word in pairs(words) do
+ if type(words[word]) == "table" then
+ for bit in pairs(words[word]) do
+ for locale, localization in pairs(localizations) do
+ if not rawget(localization, word) or not localization[word][bit] then
+ localeDebug[locale][word .. "::" .. bit] = true
+ end
+ end
+ end
+ else
+ for locale, localization in pairs(localizations) do
+ if not rawget(localization, word) then
+ localeDebug[locale][word] = true
+ end
+ end
+ end
+ end
+ for locale, t in pairs(localeDebug) do
+ if not next(t) then
+ DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q complete", locale))
+ else
+ DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q missing:", locale))
+ for word in pairs(t) do
+ DEFAULT_CHAT_FRAME:AddMessage(string.format(" %q", word))
+ end
+ end
+ end
+ DEFAULT_CHAT_FRAME:AddMessage("--- End AceLocale Debug ---")
+end
+
+setmetatable(AceLocale.prototype, {
+ __index = function(self, k)
+ if type(k) ~= "table" and k ~= 0 and k ~= "GetLibraryVersion" and k ~= "error" and k ~= "assert" and k ~= "argCheck" and k ~= "pcall" then -- HACK: remove "GetLibraryVersion" and such later.
+ AceLocale.error(lastSelf or self, "Translation %q does not exist.", k)
+ end
+ return nil
+ end
+})
+
+local function activate(self, oldLib, oldDeactivate)
+ AceLocale = self
+
+ self.frame = oldLib and oldLib.frame or CreateFrame("Frame")
+ self.registry = oldLib and oldLib.registry or {}
+ self.BASE_TRANSLATIONS = oldLib and oldLib.BASE_TRANSLATIONS or {}
+ self.DEBUGGING = oldLib and oldLib.DEBUGGING or {}
+ self.TRANSLATIONS = oldLib and oldLib.TRANSLATIONS or {}
+ self.BASE_LOCALE = oldLib and oldLib.BASE_LOCALE or {}
+ self.TRANSLATION_TABLES = oldLib and oldLib.TRANSLATION_TABLES or {}
+ self.REVERSE_TRANSLATIONS = oldLib and oldLib.REVERSE_TRANSLATIONS or {}
+ self.STRICTNESS = oldLib and oldLib.STRICTNESS or {}
+ self.NAME = oldLib and oldLib.NAME or {}
+ self.DYNAMIC_LOCALES = oldLib and oldLib.DYNAMIC_LOCALES or {}
+ self.CURRENT_LOCALE = oldLib and oldLib.CURRENT_LOCALE or {}
+
+ BASE_TRANSLATIONS = self.BASE_TRANSLATIONS
+ DEBUGGING = self.DEBUGGING
+ TRANSLATIONS = self.TRANSLATIONS
+ BASE_LOCALE = self.BASE_LOCALE
+ TRANSLATION_TABLES = self.TRANSLATION_TABLES
+ REVERSE_TRANSLATIONS = self.REVERSE_TRANSLATIONS
+ STRICTNESS = self.STRICTNESS
+ NAME = self.NAME
+ DYNAMIC_LOCALES = self.DYNAMIC_LOCALES
+ CURRENT_LOCALE = self.CURRENT_LOCALE
+
+
+ local GetTime = GetTime
+ local timeUntilClear = GetTime() + 5
+ scheduleClear = function()
+ if next(newRegistries) then
+ self.frame:Show()
+ timeUntilClear = GetTime() + 5
+ end
+ end
+
+ if not self.registry then
+ self.registry = {}
+ else
+ for name, instance in pairs(self.registry) do
+ local name = name
+ local mt = getmetatable(instance)
+ setmetatable(instance, nil)
+ instance[NAME] = name
+ local strict
+ if instance[STRICTNESS] ~= nil then
+ strict = instance[STRICTNESS]
+ elseif instance[TRANSLATIONS] ~= instance[BASE_TRANSLATIONS] then
+ if getmetatable(instance[TRANSLATIONS]).__index == oldLib.prototype then
+ strict = true
+ end
+ end
+ instance[STRICTNESS] = strict and true or false
+ refixInstance(instance)
+ end
+ end
+
+ self.frame:SetScript("OnEvent", scheduleClear)
+ self.frame:SetScript("OnUpdate", function() -- (this, elapsed)
+ if timeUntilClear - GetTime() <= 0 then
+ self.frame:Hide()
+ for k in pairs(newRegistries) do
+ clearCache(k)
+ newRegistries[k] = nil
+ k = nil
+ end
+ end
+ end)
+ self.frame:UnregisterAllEvents()
+ self.frame:RegisterEvent("ADDON_LOADED")
+ self.frame:RegisterEvent("PLAYER_ENTERING_WORLD")
+ self.frame:Show()
+
+ if oldDeactivate then
+ oldDeactivate(oldLib)
+ end
+end
+
+AceLibrary:Register(AceLocale, MAJOR_VERSION, MINOR_VERSION, activate)
diff --git a/Libs/AceOO-2.0/AceOO-2.0.lua b/Libs/AceOO-2.0/AceOO-2.0.lua
new file mode 100644
index 0000000..659be0f
--- /dev/null
+++ b/Libs/AceOO-2.0/AceOO-2.0.lua
@@ -0,0 +1,1004 @@
+--[[
+Name: AceOO-2.0
+Revision: $Rev: 17638 $
+Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
+Inspired By: Ace 1.x by Turan (turan@gryphon.com)
+Website: http://www.wowace.com/
+Documentation: http://www.wowace.com/index.php/AceOO-2.0
+SVN: http://svn.wowace.com/root/trunk/Ace2/AceOO-2.0
+Description: Library to provide an object-orientation framework.
+Dependencies: AceLibrary
+]]
+
+local MAJOR_VERSION = "AceOO-2.0"
+local MINOR_VERSION = "$Revision: 17638 $"
+
+-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version
+if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
+if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
+
+if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
+local table_setn
+do
+ local version = GetBuildInfo()
+ if string.find(version, "^2%.") then
+ -- 2.0.0
+ table_setn = function() end
+ else
+ table_setn = table.setn
+ end
+end
+
+local AceOO = {
+ error = AceLibrary.error,
+ argCheck = AceLibrary.argCheck
+}
+
+-- @function getuid
+-- @brief Obtain a unique string identifier for the object in question.
+-- @param t The object to obtain the uid for.
+-- @return The uid string.
+local function pad(cap)
+ return string.rep('0', 8 - string.len(cap)) .. cap
+end
+local function getuid(t)
+ local mt = getmetatable(t)
+ setmetatable(t, nil)
+ local str = tostring(t)
+ setmetatable(t, mt)
+ local _,_,cap = string.find(str, '[^:]*: 0x(.*)$')
+ if cap then return pad(cap) end
+ _,_,cap = string.find(str, '[^:]*: (.*)$')
+ if cap then return pad(cap) end
+end
+
+local function getlibrary(o)
+ if type(o) == "table" then
+ return o
+ elseif type(o) == "string" then
+ if not AceLibrary:HasInstance(o) then
+ AceOO:error("Library %q does not exist.", o)
+ end
+ return AceLibrary(o)
+ end
+end
+
+-- @function Factory
+-- @brief Construct a factory for the creation of objects.
+-- @param obj The object whose init method will be called on the new factory
+-- object.
+-- @param newobj The object whose init method will be called on the new
+-- objects that the Factory creates, to initialize them.
+-- @param (a1..a20) Arguments which will be passed to obj.init() in addition
+-- to the Factory object.
+-- @return The new factory which creates a newobj when its new method is called,
+-- or when it is called directly (__call metamethod).
+local Factory
+do
+ local function new(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+ a13, a14, a15, a16, a17, a18, a19, a20)
+ local t = {}
+ local uid = getuid(t)
+ local l = getlibrary
+ obj:init(t, l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7),
+ l(a8), l(a9), l(a10), l(a11), l(a12), l(a13),
+ l(a14), l(a15), l(a16), l(a17), l(a18), l(a19),
+ l(a20))
+ t.uid = uid
+ return t
+ end
+
+ local function createnew(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+ a11, a12, a13, a14, a15, a16, a17, a18,
+ a19, a20)
+ local o = self.prototype
+ local l = getlibrary
+ return new(o, l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7),
+ l(a8), l(a9), l(a10), l(a11), l(a12), l(a13),
+ l(a14), l(a15), l(a16), l(a17), l(a18), l(a19),
+ l(a20))
+ end
+
+ function Factory(obj, newobj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+ a11, a12, a13, a14, a15, a16, a17, a18,
+ a19, a20)
+ local t = new(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+ a13, a14, a15, a16, a17, a18, a19, a20)
+ t.prototype = newobj
+ t.new = createnew
+ getmetatable(t).__call = t.new
+ return t
+ end
+end
+
+
+local function objtostring(self)
+ if self.ToString then
+ return self:ToString()
+ elseif self.GetLibraryVersion then
+ return (self:GetLibraryVersion())
+ elseif self.super then
+ local s = "Sub-" .. tostring(self.super)
+ local first = true
+ if self.interfaces then
+ for interface in pairs(self.interfaces) do
+ if first then
+ s = s .. "(" .. tostring(interface)
+ first = false
+ else
+ s = s .. ", " .. tostring(interface)
+ end
+ end
+ end
+ if self.mixins then
+ for mixin in pairs(self.mixins) do
+ if first then
+ s = s .. tostring(mixin)
+ first = false
+ else
+ s = s .. ", " .. tostring(mixin)
+ end
+ end
+ end
+ if first then
+ if self.uid then
+ return s .. ":" .. self.uid
+ else
+ return s
+ end
+ else
+ return s .. ")"
+ end
+ else
+ return self.uid and 'Subclass:' .. self.uid or 'Subclass'
+ end
+end
+
+-- @table Object
+-- @brief Base of all objects, including Class.
+--
+-- @method init
+-- @brief Initialize a new object.
+-- @param newobject The object to initialize
+-- @param class The class to make newobject inherit from
+local Object
+do
+ Object = {}
+ function Object:init(newobject, class)
+ local parent = class or self
+ if not rawget(newobject, 'uid') then
+ newobject.uid = getuid(newobject)
+ end
+ local mt = {
+ __index = parent,
+ __tostring = objtostring,
+ }
+ setmetatable(newobject, mt)
+ end
+ Object.uid = getuid(Object)
+ setmetatable(Object, { __tostring = function() return 'Object' end })
+end
+
+local Interface
+
+local function validateInterface(object, interface)
+ if not object.class and object.prototype then
+ object = object.prototype
+ end
+ for k,v in pairs(interface.interface) do
+ if tostring(type(object[k])) ~= v then
+ return false
+ end
+ end
+ if interface.superinterfaces then
+ for superinterface in pairs(interface.superinterfaces) do
+ if not validateInterface(object, superinterface) then
+ return false
+ end
+ end
+ end
+ if type(object.class) == "table" and rawequal(object.class.prototype, object) then
+ if not object.class.interfaces then
+ rawset(object.class, 'interfaces', {})
+ end
+ object.class.interfaces[interface] = true
+ elseif type(object.class) == "table" and type(object.class.prototype) == "table" then
+ validateInterface(object.class.prototype, interface)
+ -- check if class is proper, thus preventing future checks.
+ end
+ return true
+end
+
+-- @function inherits
+-- @brief Return whether an Object or Class inherits from a given
+-- parent.
+-- @param object Object or Class to check
+-- @param parent Parent to test inheritance from
+-- @return whether an Object or Class inherits from a given
+-- parent.
+local function inherits(object, parent)
+ object = getlibrary(object)
+ if type(parent) == "string" then
+ if not AceLibrary:HasInstance(parent) then
+ return false
+ else
+ parent = AceLibrary(parent)
+ end
+ end
+ AceOO:argCheck(parent, 2, "table")
+ if type(object) ~= "table" then
+ return false
+ end
+ local current
+ if object.class then
+ current = object.class
+ else
+ current = object
+ end
+ if type(current) ~= "table" then
+ return false
+ end
+ if rawequal(current, parent) then
+ return true
+ end
+ if parent.class then
+ while true do
+ if rawequal(current, Object) then
+ break
+ end
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if rawequal(mixin, parent) then
+ return true
+ end
+ end
+ end
+ if current.interfaces then
+ for interface in pairs(current.interfaces) do
+ if rawequal(interface, parent) then
+ return true
+ end
+ end
+ end
+ current = current.super
+ if type(current) ~= "table" then
+ break
+ end
+ end
+
+ local isInterface = false
+ local curr = parent.class
+ while true do
+ if rawequal(curr, Object) then
+ break
+ elseif rawequal(curr, Interface) then
+ isInterface = true
+ break
+ end
+ curr = curr.super
+ if type(curr) ~= "table" then
+ break
+ end
+ end
+ return isInterface and validateInterface(object, parent)
+ else
+ while true do
+ if rawequal(current, parent) then
+ return true
+ elseif rawequal(current, Object) then
+ return false
+ end
+ current = current.super
+ if type(current) ~= "table" then
+ return false
+ end
+ end
+ end
+end
+
+-- @table Class
+-- @brief An object factory which sets up inheritence and supports
+-- 'mixins'.
+--
+-- @metamethod Class call
+-- @brief Call ClassFactory:new() to create a new class.
+--
+-- @method Class new
+-- @brief Construct a new object.
+-- @param (a1..a20) Arguments to pass to the object init function.
+-- @return The new object.
+--
+-- @method Class init
+-- @brief Initialize a new class.
+-- @param parent Superclass.
+-- @param (a1..a20) Mixins.
+--
+-- @method Class ToString
+-- @return A string representing the object, in this case 'Class'.
+local initStatus
+local Class
+local Mixin
+local autoEmbed = false
+local function traverseInterfaces(bit, total)
+ if bit.superinterfaces then
+ for interface in pairs(bit.superinterfaces) do
+ if not total[interface] then
+ total[interface] = true
+ traverseInterfaces(interface, total)
+ end
+ end
+ end
+end
+local class_new
+do
+ Class = Factory(Object, setmetatable({}, {__index = Object}), Object)
+ Class.super = Object
+
+ local function protostring(t)
+ return '<' .. tostring(t.class) .. ' prototype>'
+ end
+ local function classobjectstring(t)
+ if t.ToString then
+ return t:ToString()
+ elseif t.GetLibraryVersion then
+ return (t:GetLibraryVersion())
+ else
+ return '<' .. tostring(t.class) .. ' instance>'
+ end
+ end
+ local function classobjectequal(self, other)
+ if type(self) == "table" and self.Equals then
+ return self:Equals(other)
+ elseif type(other) == "table" and other.Equals then
+ return other:Equals(self)
+ elseif type(self) == "table" and self.CompareTo then
+ return self:CompareTo(other) == 0
+ elseif type(other) == "table" and other.CompareTo then
+ return other:CompareTo(self) == 0
+ else
+ return rawequal(self, other)
+ end
+ end
+ local function classobjectlessthan(self, other)
+ if type(self) == "table" and self.IsLessThan then
+ return self:IsLessThan(other)
+ elseif type(other) == "table" and other.IsLessThanOrEqualTo then
+ return not other:IsLessThanOrEqualTo(self)
+ elseif type(self) == "table" and self.CompareTo then
+ return self:CompareTo(other) < 0
+ elseif type(other) == "table" and other.CompareTo then
+ return other:CompareTo(self) > 0
+ elseif type(other) == "table" and other.IsLessThan and other.Equals then
+ return other:Equals(self) or other:IsLessThan(self)
+ else
+ AceOO:error("cannot compare two objects")
+ end
+ end
+ local function classobjectlessthanequal(self, other)
+ if type(self) == "table" and self.IsLessThanOrEqualTo then
+ return self:IsLessThanOrEqualTo(other)
+ elseif type(other) == "table" and other.IsLessThan then
+ return not other:IsLessThan(self)
+ elseif type(self) == "table" and self.CompareTo then
+ return self:CompareTo(other) <= 0
+ elseif type(other) == "table" and other.CompareTo then
+ return other:CompareTo(self) >= 0
+ elseif type(self) == "table" and self.IsLessThan and self.Equals then
+ return self:Equals(other) or self:IsLessThan(other)
+ else
+ AceOO:error("cannot compare two incompatible objects")
+ end
+ end
+ local function classobjectadd(self, other)
+ if type(self) == "table" and self.Add then
+ return self:Add(other)
+ else
+ AceOO:error("cannot add two incompatible objects")
+ end
+ end
+ local function classobjectsub(self, other)
+ if type(self) == "table" and self.Subtract then
+ return self:Subtract(other)
+ else
+ AceOO:error("cannot subtract two incompatible objects")
+ end
+ end
+ local function classobjectunm(self, other)
+ if type(self) == "table" and self.UnaryNegation then
+ return self:UnaryNegation(other)
+ else
+ AceOO:error("attempt to negate an incompatible object")
+ end
+ end
+ local function classobjectmul(self, other)
+ if type(self) == "table" and self.Multiply then
+ return self:Multiply(other)
+ else
+ AceOO:error("cannot multiply two incompatible objects")
+ end
+ end
+ local function classobjectdiv(self, other)
+ if type(self) == "table" and self.Divide then
+ return self:Divide(other)
+ else
+ AceOO:error("cannot divide two incompatible objects")
+ end
+ end
+ local function classobjectpow(self, other)
+ if type(self) == "table" and self.Exponent then
+ return self:Exponent(other)
+ else
+ AceOO:error("cannot exponentiate two incompatible objects")
+ end
+ end
+ local function classobjectconcat(self, other)
+ if type(self) == "table" and self.Concatenate then
+ return self:Concatenate(other)
+ else
+ AceOO:error("cannot concatenate two incompatible objects")
+ end
+ end
+ function class_new(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+ a13, a14, a15, a16, a17, a18, a19, a20)
+ if self.virtual then
+ AceOO:error("Cannot instantiate a virtual class.")
+ end
+
+ local o = self.prototype
+ local newobj = {}
+ if o.class and o.class.instancemeta then
+ setmetatable(newobj, o.class.instancemeta)
+ else
+ Object:init(newobj, o)
+ end
+
+ if self.interfaces and not self.interfacesVerified then
+ -- Verify the interfaces
+
+ for interface in pairs(self.interfaces) do
+ for field,kind in pairs(interface.interface) do
+ if tostring(type(newobj[field])) ~= kind then
+ AceOO:error("Class did not satisfy all interfaces. %q is required to be a %s. It is a %s", field, kind, tostring(type(newobj[field])))
+ end
+ end
+ end
+ self.interfacesVerified = true
+ end
+ local tmp = initStatus
+ initStatus = newobj
+ newobj:init(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+ a13, a14, a15, a16, a17, a18, a19, a20)
+ if initStatus then
+ initStatus = tmp
+ AceOO:error("Initialization not completed, be sure to call the superclass's init method.")
+ return
+ end
+ initStatus = tmp
+ return newobj
+ end
+ local classmeta = {
+ __tostring = objtostring,
+ __call = function(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+ a13, a14, a15, a16, a17, a18, a19, a20)
+ return self:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
+ a13, a14, a15, a16, a17, a18, a19, a20)
+ end,
+ }
+ function Class:init(newclass, parent, a1, a2, a3, a4, a5, a6, a7, a8, a9,
+ a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20)
+ parent = parent or self
+
+ local total
+
+ if parent.class then
+ total = {
+ parent, a1, a2, a3, a4, a5, a6, a7, a8, a9,
+ a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20
+ }
+ parent = self
+ else
+ total = {
+ a1, a2, a3, a4, a5, a6, a7, a8, a9,
+ a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20
+ }
+ end
+ if not inherits(parent, Class) then
+ AceOO:error("Classes must inherit from a proper class")
+ end
+ if parent.sealed then
+ AceOO:error("Cannot inherit from a sealed class")
+ end
+ for i,v in ipairs(total) do
+ if inherits(v, Mixin) and v.class then
+ if not newclass.mixins then
+ newclass.mixins = {}
+ end
+ if newclass.mixins[v] then
+ AceOO:error("Cannot explicitly inherit from the same mixin twice")
+ end
+ newclass.mixins[v] = true
+ elseif inherits(v, Interface) and v.class then
+ if not newclass.interfaces then
+ newclass.interfaces = {}
+ end
+ if newclass.interfaces[v] then
+ AceOO:error("Cannot explicitly inherit from the same interface twice")
+ end
+ newclass.interfaces[v] = true
+ else
+ AceOO:error("Classes can only inherit from one or zero classes and any number of mixins or interfaces")
+ end
+ end
+ if parent.interfaces then
+ if newclass.interfaces then
+ for interface in pairs(parent.interfaces) do
+ newclass.interfaces[interface] = true
+ end
+ else
+ newclass.interfaces = parent.interfaces
+ end
+ end
+ for k in pairs(total) do
+ total[k] = nil
+ end
+ table_setn(total, 0)
+
+ newclass.super = parent
+
+ newclass.prototype = setmetatable(total, {
+ __index = parent.prototype,
+ __tostring = protostring,
+ })
+ total = nil
+
+ newclass.instancemeta = {
+ __index = newclass.prototype,
+ __tostring = classobjectstring,
+ __eq = classobjectequal,
+ __lt = classobjectlessthan,
+ __le = classobjectlessthanequal,
+ __add = classobjectadd,
+ __sub = classobjectsub,
+ __unm = classobjectunm,
+ __mul = classobjectmul,
+ __div = classobjectdiv,
+ __pow = classobjectpow,
+ __concat = classobjectconcat,
+ }
+
+ setmetatable(newclass, classmeta)
+
+ newclass.new = class_new
+
+ if newclass.mixins then
+ -- Fold in the mixins
+ local err, msg
+ for mixin in pairs(newclass.mixins) do
+ local ret
+ autoEmbed = true
+ ret, msg = pcall(mixin.embed, mixin, newclass.prototype)
+ autoEmbed = false
+ if not ret then
+ err = true
+ break
+ end
+ end
+
+ if err then
+ local pt = newclass.prototype
+ for k,v in pairs(pt) do
+ pt[k] = nil
+ end
+
+ -- method conflict
+ AceOO:error(msg)
+ end
+ end
+
+ newclass.prototype.class = newclass
+
+ if newclass.interfaces then
+ for interface in pairs(newclass.interfaces) do
+ traverseInterfaces(interface, newclass.interfaces)
+ end
+ end
+ if newclass.mixins then
+ for mixin in pairs(newclass.mixins) do
+ if mixin.interfaces then
+ if not newclass.interfaces then
+ newclass.interfaces = {}
+ end
+ for interface in pairs(mixin.interfaces) do
+ newclass.interfaces[interface] = true
+ end
+ end
+ end
+ end
+ end
+ function Class:ToString()
+ if type(self.GetLibraryVersion) == "function" then
+ return (self:GetLibraryVersion())
+ else
+ return "Class"
+ end
+ end
+
+ local tmp
+ function Class.prototype:init()
+ if rawequal(self, initStatus) then
+ initStatus = nil
+ else
+ AceOO:error("Improper self passed to init. You must do MyClass.super.prototype.init(self, ...)", 2)
+ end
+ self.uid = getuid(self)
+ local current = self.class
+ while true do
+ if current == Class then
+ break
+ end
+ if current.mixins then
+ for mixin in pairs(current.mixins) do
+ if type(mixin.OnInstanceInit) == "function" then
+ mixin:OnInstanceInit(self)
+ end
+ end
+ end
+ current = current.super
+ end
+ end
+end
+
+
+-- @object ClassFactory
+-- @brief A factory for creating classes. Rarely used directly.
+local ClassFactory = Factory(Object, Class, Object)
+
+function Class:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
+ a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ local x = ClassFactory:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
+ a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ if AceOO.classes then
+ AceOO.classes[x] = true
+ end
+ return x
+end
+getmetatable(Class).__call = Class.new
+
+-- @class Mixin
+-- @brief A class to create mixin objects, which contain methods that get
+-- "mixed in" to class prototypes.
+--
+-- @object Mixin prototype
+-- @brief The prototype that mixin objects inherit their methods from.
+--
+-- @method Mixin prototype embed
+-- @brief Mix in the methods of our object which are listed in our interface
+-- to the supplied target table.
+--
+-- @method Mixin prototype init
+-- @brief Initialize the mixin object.
+-- @param newobj The new object we're initializing.
+-- @param interface The interface we implement (the list of methods our
+-- prototype provides which should be mixed into the target
+-- table by embed).
+do
+ Mixin = Class()
+ function Mixin:ToString()
+ if self.GetLibraryVersion then
+ return (self:GetLibraryVersion())
+ else
+ return 'Mixin'
+ end
+ end
+ local function _Embed(state, field, target)
+ field = next(state.export, field)
+ if field == nil then
+ return
+ end
+
+ if rawget(target, field) or (target[field] and target[field] ~= state[field]) then
+ AceOO:error("Method conflict in attempt to mixin. Field %q", field)
+ end
+
+ target[field] = state[field]
+
+ local ret,msg = pcall(_Embed, state, field, target)
+ if not ret then
+ -- Mix in the next method according to the defined interface. If that
+ -- fails due to a conflict, re-raise to back out the previous mixed
+ -- methods.
+
+ target[field] = nil
+ AceOO:error(msg)
+ end
+ end
+ function Mixin.prototype:embed(target)
+ local mt = getmetatable(target)
+ setmetatable(target, nil)
+ local err, msg = pcall(_Embed, self, nil, target)
+ if not err then
+ setmetatable(target, mt)
+ AceOO:error(msg)
+ return
+ end
+ if type(self.embedList) == "table" then
+ self.embedList[target] = true
+ end
+ if type(target.class) ~= "table" then
+ target[self] = true
+ end
+ if not autoEmbed and type(self.OnManualEmbed) == "function" then
+ self:OnManualEmbed(target)
+ end
+ setmetatable(target, mt)
+ end
+
+ function Mixin.prototype:activate(oldLib, oldDeactivate)
+ if oldLib and oldLib.embedList then
+ for target in pairs(oldLib.embedList) do
+ local mt = getmetatable(target)
+ setmetatable(target, nil)
+ for field in pairs(oldLib.export) do
+ target[field] = nil
+ end
+ setmetatable(target, mt)
+ end
+ self.embedList = oldLib.embedList
+ for target in pairs(self.embedList) do
+ self:embed(target)
+ end
+ else
+ self.embedList = setmetatable({}, {__mode="k"})
+ end
+ end
+
+ function Mixin.prototype:init(export, a1, a2, a3, a4, a5, a6, a7, a8, a9,
+ a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20)
+ AceOO:argCheck(export, 2, "table")
+ for k,v in pairs(export) do
+ if type(k) ~= "number" then
+ AceOO:error("All keys to argument #2 must be numbers.")
+ elseif type(v) ~= "string" then
+ AceOO:error("All values to argument #2 must be strings.")
+ end
+ end
+ local num = table.getn(export)
+ for i = 1, num do
+ local v = export[i]
+ export[i] = nil
+ export[v] = true
+ end
+ table_setn(export, 0)
+ local interfaces
+ if a1 then
+ local l = getlibrary
+ interfaces = { l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), l(a8),
+ l(a9), l(a10), l(a11), l(a12), l(a13), l(a14), l(a15), l(a16),
+ l(a17), l(a18), l(a19), l(a20) }
+ for _,v in ipairs(interfaces) do
+ if not v.class or not inherits(v, Interface) then
+ AceOO:error("Mixins can inherit only from interfaces")
+ end
+ end
+ local num = table.getn(interfaces)
+ for i = 1, num do
+ local v = interfaces[i]
+ interfaces[i] = nil
+ interfaces[v] = true
+ end
+ table_setn(interfaces, 0)
+ for interface in pairs(interfaces) do
+ traverseInterfaces(interface, interfaces)
+ end
+ for interface in pairs(interfaces) do
+ for field,kind in pairs(interface.interface) do
+ if kind ~= "nil" then
+ local good = false
+ for bit in pairs(export) do
+ if bit == field then
+ good = true
+ break
+ end
+ end
+ if not good then
+ AceOO:error("Mixin does not fully accommodate field %q", field)
+ end
+ end
+ end
+ end
+ end
+ self.super = Mixin.prototype
+ Mixin.super.prototype.init(self)
+ self.export = export
+ self.interfaces = interfaces
+ end
+end
+
+-- @class Interface
+-- @brief A class to create interfaces, which contain contracts that classes
+-- which inherit from this must comply with.
+--
+-- @object Interface prototype
+-- @brief The prototype that interface objects must adhere to.
+--
+-- @method Interface prototype init
+-- @brief Initialize the mixin object.
+-- @param interface The interface we contract (the hash of fields forced).
+-- @param (a1..a20) Superinterfaces
+do
+ Interface = Class()
+ function Interface:ToString()
+ if self.GetLibraryVersion then
+ return (self:GetLibraryVersion())
+ else
+ return 'Instance'
+ end
+ end
+ function Interface.prototype:init(interface, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
+ Interface.super.prototype.init(self)
+ AceOO:argCheck(interface, 2, "table")
+ for k,v in pairs(interface) do
+ if type(k) ~= "string" then
+ AceOO:error("All keys to argument #2 must be numbers.")
+ elseif type(v) ~= "string" then
+ AceOO:error("All values to argument #2 must be strings.")
+ elseif v ~= "nil" and v ~= "string" and v ~= "number" and v ~= "table" and v ~= "function" then
+ AceOO:error('All values to argument #2 must either be "nil", "string", "number", "table", or "function".')
+ end
+ end
+ local l = getlibrary
+ a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), l(a8), l(a9), l(a10), l(a11), l(a12), l(a13), l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), l(a20)
+ if a1 then
+ self.superinterfaces = {a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20}
+ for k,v in ipairs(self.superinterfaces) do
+ if not inherits(v, Interface) or not v.class then
+ AceOO:error('Cannot provide a non-Interface to inherit from')
+ end
+ end
+ local num = table.getn(self.superinterfaces)
+ for i = 1, num do
+ local v = self.superinterfaces[i]
+ self.superinterfaces[i] = nil
+ self.superinterfaces[v] = true
+ end
+ table_setn(self.superinterfaces, 0)
+ end
+ self.interface = interface
+ end
+end
+
+-- @function Classpool
+-- @brief Obtain a read only class from our pool of classes, indexed by the
+-- superclass and mixins.
+-- @param sc The superclass of the class we want.
+-- @param (m1..m20) Mixins of the class we want's objects.
+-- @return A read only class from the class pool.
+local Classpool
+do
+ local pool = setmetatable({}, {__mode = 'v'})
+ local function newindex(k, v)
+ AceOO:error('Attempt to modify a read-only class.')
+ end
+ local function protonewindex(k, v)
+ AceOO:error('Attempt to modify a read-only class prototype.')
+ end
+ local function ts(bit)
+ if type(bit) ~= "table" then
+ return tostring(bit)
+ elseif getmetatable(bit) and bit.__tostring then
+ return tostring(bit)
+ elseif type(bit.GetLibraryVersion) == "function" then
+ return bit:GetLibraryVersion()
+ else
+ return tostring(bit)
+ end
+ end
+ local t
+ local function getcomplexuid(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9,
+ m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20)
+ if not t then t = {} end
+ if sc then if sc.uid then table.insert(t, sc.uid) else AceOO:error("%s is not an appropriate class/mixin", ts(sc)) end
+ if m1 then if m1.uid then table.insert(t, m1.uid) else AceOO:error("%s is not an appropriate mixin", ts(m1)) end
+ if m2 then if m2.uid then table.insert(t, m2.uid) else AceOO:error("%s is not an appropriate mixin", ts(m2)) end
+ if m3 then if m3.uid then table.insert(t, m3.uid) else AceOO:error("%s is not an appropriate mixin", ts(m3)) end
+ if m4 then if m4.uid then table.insert(t, m4.uid) else AceOO:error("%s is not an appropriate mixin", ts(m4)) end
+ if m5 then if m5.uid then table.insert(t, m5.uid) else AceOO:error("%s is not an appropriate mixin", ts(m5)) end
+ if m6 then if m6.uid then table.insert(t, m6.uid) else AceOO:error("%s is not an appropriate mixin", ts(m6)) end
+ if m7 then if m7.uid then table.insert(t, m7.uid) else AceOO:error("%s is not an appropriate mixin", ts(m7)) end
+ if m8 then if m8.uid then table.insert(t, m8.uid) else AceOO:error("%s is not an appropriate mixin", ts(m8)) end
+ if m9 then if m9.uid then table.insert(t, m9.uid) else AceOO:error("%s is not an appropriate mixin", ts(m9)) end
+ if m10 then if m10.uid then table.insert(t, m10.uid) else AceOO:error("%s is not an appropriate mixin", ts(m10)) end
+ if m11 then if m11.uid then table.insert(t, m11.uid) else AceOO:error("%s is not an appropriate mixin", ts(m11)) end
+ if m12 then if m12.uid then table.insert(t, m12.uid) else AceOO:error("%s is not an appropriate mixin", ts(m12)) end
+ if m13 then if m13.uid then table.insert(t, m13.uid) else AceOO:error("%s is not an appropriate mixin", ts(m13)) end
+ if m14 then if m14.uid then table.insert(t, m14.uid) else AceOO:error("%s is not an appropriate mixin", ts(m14)) end
+ if m15 then if m15.uid then table.insert(t, m15.uid) else AceOO:error("%s is not an appropriate mixin", ts(m15)) end
+ if m16 then if m16.uid then table.insert(t, m16.uid) else AceOO:error("%s is not an appropriate mixin", ts(m16)) end
+ if m17 then if m17.uid then table.insert(t, m17.uid) else AceOO:error("%s is not an appropriate mixin", ts(m17)) end
+ if m18 then if m18.uid then table.insert(t, m18.uid) else AceOO:error("%s is not an appropriate mixin", ts(m18)) end
+ if m19 then if m19.uid then table.insert(t, m19.uid) else AceOO:error("%s is not an appropriate mixin", ts(m19)) end
+ if m20 then if m20.uid then table.insert(t, m20.uid) else AceOO:error("%s is not an appropriate mixin", ts(m20)) end
+ end end end end end end end end end end end end end end end end end end end end end
+ table.sort(t)
+ local uid = table.concat(t, '')
+ for k in pairs(t) do t[k] = nil end
+ table_setn(t, 0)
+ return uid
+ end
+ local classmeta
+ function Classpool(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9,
+ m10, m11, m12, m13, m14, m15, m16,
+ m17, m18, m19, m20)
+ local l = getlibrary
+ sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20 = l(sc), l(m1), l(m2), l(m3), l(m4), l(m5), l(m6), l(m7), l(m8), l(m9), l(m10), l(m11), l(m12), l(m13), l(m14), l(m15), l(m16), l(m17), l(m18), l(m19), l(m20)
+ if sc and sc.class then
+ sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20 = Class, sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19
+ end
+ sc = sc or Class
+ local key = getcomplexuid(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20)
+ if not pool[key] then
+ local class = Class(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9,
+ m10, m11, m12, m13, m14, m15, m16, m17,
+ m18, m19, m20)
+ if not classmeta then
+ classmeta = {}
+ local mt = getmetatable(class)
+ for k,v in pairs(mt) do
+ classmeta[k] = v
+ end
+ classmeta.__newindex = newindex
+ end
+ -- Prevent the user from adding methods to this class.
+ -- NOTE: I'm not preventing modifications of existing class members,
+ -- but it's likely that only a truly malicious user will be doing so.
+ class.sealed = true
+ setmetatable(class, classmeta)
+ getmetatable(class.prototype).__newindex = protonewindex
+ pool[key] = class
+ end
+ return pool[key]
+ end
+end
+
+AceOO.Factory = Factory
+AceOO.Object = Object
+AceOO.Class = Class
+AceOO.Mixin = Mixin
+AceOO.Interface = Interface
+AceOO.Classpool = Classpool
+AceOO.inherits = inherits
+
+-- Library handling bits
+
+local function activate(self, oldLib, oldDeactivate)
+ AceOO = self
+ Factory = self.Factory
+ Object = self.Object
+ Class = self.Class
+ ClassFactory.prototype = Class
+ Mixin = self.Mixin
+ Interface = self.Interface
+ Classpool = self.Classpool
+
+ if oldLib then
+ self.classes = oldLib.classes
+ end
+ if not self.classes then
+ self.classes = setmetatable({}, {__mode="k"})
+ else
+ for class in pairs(self.classes) do
+ class.new = class_new
+ end
+ end
+
+ if oldDeactivate then
+ oldDeactivate(oldLib)
+ end
+end
+
+AceLibrary:Register(AceOO, MAJOR_VERSION, MINOR_VERSION, activate)
+AceOO = AceLibrary(MAJOR_VERSION)
diff --git a/Locale-deDE.lua b/Locale-deDE.lua
new file mode 100644
index 0000000..1379b9e
--- /dev/null
+++ b/Locale-deDE.lua
@@ -0,0 +1,18 @@
+local L = AceLibrary("AceLocale-2.2"):new("WowRadio")
+
+L:RegisterTranslations("deDE", function() return {
+ ["usage"] = "Benutzung",
+ ["wrtune"] = "Radiostation w\195\164hlen",
+ ["wrcustom"] = "Spielt die angegebene Url ab.",
+ ["wrlist"] = "Zeigt Liste aller Radiostationen.",
+ ["wrinfo"] = "Zeigt an welche Station gerade gespielt wird.",
+ ["wrplay"] = "Startet die Musikwiedergabe.",
+ ["wrstop"] = "Stoppt die Musikwiedergabe.",
+ ["wrnext"] = "Springt zur n\195\164chsten Station.",
+ ["wrprev"] = "Springt zur vorherigen Station.",
+ ["wrrnd"] = "Spielt zuf\195\164llige Radiostation ab.",
+ ["wrauto"] = "Schaltet Autostart ein / aus",
+ ["wrauto_enabled"] = "Autostart ist jetzt aktiviert",
+ ["wrauto_disabled"] = "Autostart ist jetzt deaktiviert",
+ ["msg_enabled"] = "WowRadio geladen. /wr für Optionen.",
+} end);
diff --git a/Locale-enEN.lua b/Locale-enEN.lua
new file mode 100644
index 0000000..2e39df3
--- /dev/null
+++ b/Locale-enEN.lua
@@ -0,0 +1,18 @@
+local L = AceLibrary("AceLocale-2.2"):new("WowRadio")
+
+L:RegisterTranslations("enEN", function() return {
+ ["usage"] = "Usage",
+ ["wrtune"] = "select radio station",
+ ["wrcustom"] = "Plays given url.",
+ ["wrlist"] = "Overview of radio stations.",
+ ["wrinfo"] = "Displays station which is played.",
+ ["wrplay"] = "Starts music.",
+ ["wrstop"] = "Stops music.",
+ ["wrnext"] = "Switch to next station.",
+ ["wrprev"] = "Switch to previous station.",
+ ["wrrnd"] = "Switch to random radio station.",
+ ["wrauto"] = "Toggles autostart on / off.",
+ ["wrauto_enabled"] = "Autostart is now enabled",
+ ["wrauto_disabled"] = "Autostart is now disabled",
+ ["msg_enabled"] = "WowRadio loaded. /wr for options.",
+} end);
diff --git a/Locale-enUS.lua b/Locale-enUS.lua
new file mode 100644
index 0000000..f0a8908
--- /dev/null
+++ b/Locale-enUS.lua
@@ -0,0 +1,18 @@
+local L = AceLibrary("AceLocale-2.2"):new("WowRadio")
+
+L:RegisterTranslations("enUS", function() return {
+ ["usage"] = "Usage",
+ ["wrtune"] = "select radio station",
+ ["wrcustom"] = "Plays given url.",
+ ["wrlist"] = "Overview of radio stations.",
+ ["wrinfo"] = "Displays station which is played.",
+ ["wrplay"] = "Starts music.",
+ ["wrstop"] = "Stops music.",
+ ["wrnext"] = "Switch to next station.",
+ ["wrprev"] = "Switch to previous station.",
+ ["wrrnd"] = "Switch to random radio station.",
+ ["wrauto"] = "Toggles autostart on / off.",
+ ["wrauto_enabled"] = "Autostart is now enabled",
+ ["wrauto_disabled"] = "Autostart is now disabled",
+ ["msg_enabled"] = "WowRadio loaded. /wr for options.",
+} end);
\ No newline at end of file
diff --git a/Stations.lua b/Stations.lua
new file mode 100644
index 0000000..9482f5a
--- /dev/null
+++ b/Stations.lua
@@ -0,0 +1,135 @@
+-- Each entry: { "stream URL", "Display Name", "CATEGORY" }
+-- Categories: GAME, TALK, ROCK, ELECTRONIC, JAZZ, VARIETY
+-- To add a station: paste one line in the right group. Index and sync are automatic.
+
+local WR_STATIONS = {
+
+ -- game / special
+ { "http://radiodirect2.turtle-music.org/stream", "Everlook Broadcasting Co.", "GAME" },
+
+ -- news / talk / comedy
+ { "http://stream.live.vc.bbcmedia.co.uk/bbc_world_service", "BBC World Service - International news, analysis, and talk radio", "TALK" },
+ { "http://listen.livestreamingservice.com/181-comedy_128k.mp3", "181.FM Comedy Club - Comedy radio", "TALK" },
+
+ -- oldies / classic hits / soul / decades
+ { "http://listen.livestreamingservice.com/181-goodtime_128k.mp3", "181.FM Good Time Oldies - 50s and 60s oldies", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-greatoldies_128k.mp3", "181.FM Classic Hits - Classic hits and oldies", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-soul_128k.mp3", "181.FM Soul - Classic soul and oldies", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-mellow_128k.mp3", "181.FM Mellow Gold - Soft rock and mellow classic hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-70s_128k.mp3", "181.FM Super 70s - 1970s hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-awesome80s_128k.mp3", "181.FM Awesome 80s - 1980s pop and rock", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-lite80s_128k.mp3", "181.FM Lite 80s - Softer 1980s pop and rock", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-star90s_128k.mp3", "181.FM Star 90s - 1990s hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-lite90s_128k.mp3", "181.FM Lite 90s - Softer 1990s pop", "VARIETY" },
+
+ -- Beatles / classic rock / hard rock / alternative
+ { "http://listen.livestreamingservice.com/181-beatles_128k.mp3", "181.FM Beatles - Beatles-focused classic rock", "ROCK" },
+ { "http://streaming.exclusive.radio/er/beatles/icecast.audio", "Exclusive Radio Beatles - Beatles-focused artist stream", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-rock_128k.mp3", "181.FM Rock 181 - Rock and classic rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-eagle_128k.mp3", "181.FM The Eagle - Classic rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-rock40_128k.mp3", "181.FM Rock 40 - Rock and roll hits", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-hardrock_128k.mp3", "181.FM The Rock! - Hard rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-hairband_128k.mp3", "181.FM 80s Hairband - Glam metal and hard rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-90salt_128k.mp3", "181.FM 90s Alternative - 1990s alternative rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-thepoint_128k.mp3", "181.FM The Point - Adult alternative and modern pop-rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-buzz_128k.mp3", "181.FM The Buzz - Alternative rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-classicbuzz_128k.mp3", "181.FM Classic Buzz - Classic alternative rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-yachtrock_128k.mp3", "181.FM Yacht Rock - Soft rock and yacht rock", "ROCK" },
+ { "http://listen.livestreamingservice.com/181-chloe_128k.mp3", "181.FM Chloe - Adult alternative and eclectic pop-rock", "ROCK" },
+
+ -- pop / adult contemporary / workday / top 40
+ { "http://listen.livestreamingservice.com/181-power_128k.mp3", "181.FM Power 181 - Top 40 pop hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-office_128k.mp3", "181.FM The Office - Workday pop and adult hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-themix_128k.mp3", "181.FM The Mix - Pop and adult contemporary mix", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-smoothac_128k.mp3", "181.FM Smooth AC - Smooth adult contemporary", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-uktop40_128k.mp3", "181.FM UK Top 40 - UK pop chart hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-heart_128k.mp3", "181.FM The Heart - Love songs", "VARIETY" },
+
+ -- country / roots / blues
+ { "http://listen.livestreamingservice.com/181-frontporch_128k.mp3", "181.FM Front Porch - Bluegrass and roots music", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-kickincountry_128k.mp3", "181.FM Kickin' Country - Country hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-realcountry_128k.mp3", "181.FM Real Country - Traditional and modern country", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-highway_128k.mp3", "181.FM Highway 181 - Country road music", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-80scountry_128k.mp3", "181.FM 80s Country - 1980s country music", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-90scountry_128k.mp3", "181.FM 90s Country - 1990s country music", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-blues_128k.mp3", "181.FM True Blues - Blues radio", "VARIETY" },
+
+ -- jazz / classical
+ { "http://listen.livestreamingservice.com/181-jazzmix_128k.mp3", "181.FM Jazz Mix - Jazz mix", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-classicaljazz_128k.mp3", "181.FM Classical Jazz - Classic jazz", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-vocals_128k.mp3", "181.FM Vocal Jazz - Vocal jazz standards", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-bebop_128k.mp3", "181.FM BeBop Jazz - Bebop jazz", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-fusionjazz_128k.mp3", "181.FM Fusion Jazz - Jazz fusion", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-trancejazz_128k.mp3", "181.FM Trance Jazz - Trance-influenced jazz", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-acidjazz_128k.mp3", "181.FM Acid Jazz - Acid jazz and groove jazz", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-classicalguitar_128k.mp3","181.FM Classical Guitar - Classical guitar music", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-classical_128k.mp3", "181.FM Classical Music - Classical music", "JAZZ" },
+ { "http://listen.livestreamingservice.com/181-breeze_128k.mp3", "181.FM The Breeze - Easy listening", "VARIETY" },
+
+ -- R&B / hip hop / dance / club
+ { "http://listen.livestreamingservice.com/181-80srnb_128k.mp3", "181.FM 80s RnB - 1980s R&B", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-80sliternb_128k.mp3", "181.FM 80s Lite RnB - Smooth 1980s R&B", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-90sdance_128k.mp3", "181.FM 90s Dance - 1990s dance music", "ELECTRONIC" },
+ { "http://listen.livestreamingservice.com/181-90sliternb_128k.mp3", "181.FM 90s Lite RnB - Smooth 1990s R&B", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-90srnb_128k.mp3", "181.FM 90s RnB - 1990s R&B", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-oldschool_128k.mp3", "181.FM Old School HipHop/RnB - Old school hip hop and R&B", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-vibe_128k.mp3", "181.FM The Vibe of Vegas - Dance, club, and party music", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-beat_128k.mp3", "181.FM The Beat - Hip hop and R&B", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-thebox_128k.mp3", "181.FM The Box - Urban hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-classicrnb_128k.mp3", "181.FM Classic RnB - Classic R&B", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-rnb_128k.mp3", "181.FM True RnB - R&B radio", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-party_128k.mp3", "181.FM Party 181 - Party hits", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-jammin_128k.mp3", "181.FM Jammin 181 - Funk, soul, and dance grooves", "VARIETY" },
+ { "http://listen.livestreamingservice.com/181-energy98_128k.mp3", "181.FM Energy 98 - Dance and club music", "ELECTRONIC" },
+ { "http://listen.livestreamingservice.com/181-classicenergy_128k.mp3", "181.FM Classic Energy - Classic dance and energy hits", "ELECTRONIC" },
+ { "http://listen.livestreamingservice.com/181-energy93_128k.mp3", "181.FM Energy 93 - Dance and electronic hits", "ELECTRONIC" },
+ { "http://listen.livestreamingservice.com/181-ball_128k.mp3", "181.FM Studio 181 - Disco and dance classics", "ELECTRONIC" },
+
+ -- eclectic / freeform / alternative
+ { "http://stream.radioparadise.com/mp3-192", "Radio Paradise - Eclectic rock, world, electronic, and listener-supported radio","VARIETY" },
+ { "http://stream0.wfmu.org/freeform-128k", "WFMU - Freeform independent radio", "VARIETY" },
+ { "http://ice3.somafm.com/live-128-mp3", "SomaFM Live - Eclectic underground and alternative radio", "VARIETY" },
+
+ -- video game / chiptune / demoscene / retro computer music
+ { "http://radio.cvgm.net:8080", "CVGM Radio - Video game, chiptune, demoscene, and computer music", "GAME" },
+ { "http://relay4.slayradio.org:8000/", "SLAY Radio - C64, SID, remix, and retro computer music", "GAME" },
+ { "http://nectarine.ers35.net:8000/necta192.mp3", "Nectarine - Demoscene, tracker, module, and chiptune music", "GAME" },
+ { "http://content.radiosega.net:8006/rs-mpeg.mp3", "RadioSEGA MP3 - SEGA video game music and remixes", "GAME" },
+
+ -- electronic / chill / ambient / experimental
+ { "http://radio.plaza.one/mp3", "Nightwave Plaza - Vaporwave, future funk, and internet radio", "ELECTRONIC" },
+ { "http://ice3.somafm.com/groovesalad-128-mp3", "SomaFM Groove Salad - Downtempo, ambient beats, and chill electronic", "ELECTRONIC" },
+ { "http://listen.livestreamingservice.com/181-chilled_128k.mp3", "181.FM Chilled Out - Chillout and downtempo", "ELECTRONIC" },
+ { "http://listen.livestreamingservice.com/181-technoclub_128k.mp3", "181.FM Techno Club - Techno and dance music", "ELECTRONIC" },
+ { "http://ice3.somafm.com/thetrip-128-mp3", "SomaFM The Trip - Progressive house, trance, and electronic", "ELECTRONIC" },
+ { "http://ice3.somafm.com/digitalis-128-mp3", "SomaFM Digitalis - Indie electronic and digital-era music", "ELECTRONIC" },
+ { "http://ice3.somafm.com/dronezone-128-mp3", "SomaFM Drone Zone - Ambient drone", "ELECTRONIC" },
+ { "http://ice3.somafm.com/deepspaceone-128-mp3", "SomaFM Deep Space One - Space ambient", "ELECTRONIC" },
+ { "http://ice3.somafm.com/spacestation-128-mp3", "SomaFM Space Station Soma - Space electronica", "ELECTRONIC" },
+ { "http://ice3.somafm.com/missioncontrol-128-mp3", "SomaFM Mission Control - NASA audio mixed with ambient music", "ELECTRONIC" },
+ { "http://ice3.somafm.com/darkzone-128-mp3", "SomaFM Dark Zone - Dark ambient and cinematic atmospheres", "ELECTRONIC" },
+ { "http://ice3.somafm.com/bootliquor-128-mp3", "SomaFM Boot Liquor - Americana roots and country western", "VARIETY" },
+ { "http://ice3.somafm.com/defcon-128-mp3", "SomaFM DEF CON Radio - Electronic music for hacking", "ELECTRONIC" },
+ { "http://ice3.somafm.com/sonicuniverse-128-mp3", "SomaFM Sonic Universe - Avant-garde jazz and experimental sounds", "JAZZ" },
+ { "http://ice3.somafm.com/covers-128-mp3", "SomaFM Covers - Cover songs and reinterpretations", "VARIETY" },
+ { "http://ice3.somafm.com/lush-128-mp3", "SomaFM Lush - Female vocals, electronic, and mellow pop", "ELECTRONIC" },
+ { "http://ice3.somafm.com/beatblender-128-mp3", "SomaFM Beat Blender - Deep house and downtempo chill", "ELECTRONIC" },
+ { "http://ice3.somafm.com/folkfwd-128-mp3", "SomaFM Folk Forward - Indie folk and alt-folk", "VARIETY" },
+ { "http://ice3.somafm.com/fluid-128-mp3", "SomaFM Fluid - Instrumental hip hop, future soul, and liquid trap", "ELECTRONIC" },
+ { "http://ice3.somafm.com/reggae-128-mp3", "SomaFM Heavyweight Reggae - Reggae, ska, and rocksteady", "VARIETY" },
+ { "http://ice3.somafm.com/specials-128-mp3", "SomaFM Specials - Seasonal and special programming", "VARIETY" },
+ { "http://uk5.internet-radio.com:8306/", "Moon Mission Recordings - Tokyo Deep and Electronic", "ELECTRONIC" },
+ { "http://uk4.internet-radio.com:8049/", "Dance Attack FM - The Best EDM", "ELECTRONIC" },
+ { "http://dancestream.danceradiouk.com/stream", "Dance UK Radio", "ELECTRONIC" },
+
+}
+
+-- Build the three lookup tables Core.lua expects. Do not edit below this line.
+stationUrl = {}
+stationMsg = {}
+stationCategory = {}
+for _, s in ipairs(WR_STATIONS) do
+ table.insert(stationUrl, s[1])
+ table.insert(stationMsg, s[2])
+ table.insert(stationCategory, s[3])
+end
diff --git a/WowRadio-Vanilla.toc b/WowRadio-Vanilla.toc
new file mode 100644
index 0000000..33c3060
--- /dev/null
+++ b/WowRadio-Vanilla.toc
@@ -0,0 +1,23 @@
+## Interface: 11200
+## Title: WowRadio |cff7fff7f -Ace2-|r
+## Notes: Listen to Internet Radio inside WoW
+## Author: Tormentor @Mannoroth, belthazor
+## Version: 1.0
+## OptionalDeps: Ace2
+## SavedVariables: WowRadioDB
+## SavedVariablesPerCharacter: WowRadioDBPC
+## X-Category: Audio
+
+Libs\AceLibrary\AceLibrary.lua
+Libs\AceOO-2.0\AceOO-2.0.lua
+Libs\AceAddon-2.0\AceAddon-2.0.lua
+Libs\AceDB-2.0\AceDB-2.0.lua
+Libs\AceConsole-2.0\AceConsole-2.0.lua
+Libs\AceEvent-2.0\AceEvent-2.0.lua
+Libs\AceLocale-2.2\AceLocale-2.2.lua
+
+Locale-deDE.lua
+Locale-enEN.lua
+Locale-enUS.lua
+Stations.lua
+Core.lua
\ No newline at end of file
diff --git a/bg_left.tga b/bg_left.tga
new file mode 100644
index 0000000..03faae5
Binary files /dev/null and b/bg_left.tga differ
diff --git a/bg_right.tga b/bg_right.tga
new file mode 100644
index 0000000..804a393
Binary files /dev/null and b/bg_right.tga differ