-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- http://www.curse.com/addons/wow/tradeskill-master -- -- -- -- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- -- All Rights Reserved* - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- -- This file contains all the code for the new standardized module registration / format local TSM = select(2, ...) local Modules = TSM:NewModule("Modules", "AceConsole-3.0") local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table local moduleObjects = TSM.moduleObjects local moduleNames = TSM.moduleNames -- initialization stuff function Modules:OnEnable() -- register the chat commands (slash commands) - whenver '/tsm' or '/tradeskillmaster' is typed by the user, Modules:ChatCommand() will be called Modules:RegisterChatCommand("tsm", "ChatCommand") Modules:RegisterChatCommand("tradeskillmaster", "ChatCommand") -- tooltip setup TSM:SetupTooltips() -- no modules popup TSMAPI:CreateTimeDelay("noModulesPopup", 3, function() if #moduleNames == 1 then StaticPopupDialogs["TSMInfoPopup"] = { text = L["|cffffff00Important Note:|r You do not currently have any modules installed / enabled for TradeSkillMaster! |cff77ccffYou must download modules for TradeSkillMaster to have some useful functionality!|r\n\nPlease visit http://www.curse.com/addons/wow/tradeskill-master and check the project description for links to download modules."], button1 = L["I'll Go There Now!"], timeout = 0, whileDead = true, OnAccept = function() TSM:Print(L["Just incase you didn't read this the first time:"]) TSM:Print(L["|cffffff00Important Note:|r You do not currently have any modules installed / enabled for TradeSkillMaster! |cff77ccffYou must download modules for TradeSkillMaster to have some useful functionality!|r\n\nPlease visit http://www.curse.com/addons/wow/tradeskill-master and check the project description for links to download modules."]) end, preferredIndex = 3, } TSMAPI:ShowStaticPopupDialog("TSMInfoPopup") end end) end -- ************************************************************************** -- TSMAPI:NewModule API -- ************************************************************************** -- info on all the possible fields of the module objects which TSM core cares about local moduleFieldInfo = { -- operation fields { key = "operations", type = "table", subFieldInfo = { maxOperations = "number", callbackOptions = "function", callbackInfo = "function" } }, -- tooltip fields { key = "GetTooltip", type = "function" }, -- tooltip options { key = "tooltipOptions", type = "table", subFieldInfo = { callback = "function" } }, -- shared feature fields { key = "slashCommands", type = "table", subTableInfo = { key = "string", label = "string", callback = "function" } }, { key = "icons", type = "table", subTableInfo = { side = "string", desc = "string", callback = "function", icon = "string" } }, { key = "auctionTab", type = "table", subFieldInfo = { callbackShow = "function", callbackHide = "function" } }, { key = "bankUiButton", type = "table", subFieldInfo = { callback = "function" } }, -- data access fields { key = "priceSources", type = "table", subTableInfo = { key = "string", label = "string", callback = "function" } }, { key = "moduleAPIs", type = "table", subTableInfo = { key = "string", callback = "function" } }, -- multi-account sync fields { key = "sync", type = "table", subFieldInfo = { callback = "function" } }, } -- if the passed function is a string, will check if it's a method of the object and return a wrapper function function Modules:GetFunction(obj, func) if type(func) == "string" then local part1, part2 = (":"):split(func) if part2 and obj[part1] and obj[part1][part2] then return function(...) return obj[part1][part2](obj[part1], ...) end elseif obj[part1] then return function(...) return obj[part1](obj, ...) end end end return func end -- validates a simple list of sub-tables which have the basic key/label/callback fields function Modules:ValidateList(obj, val, keys) for i, v in ipairs(val) do if type(v) ~= "table" then return "invalid entry in list at index " .. i end for key, valType in pairs(keys) do if valType == "function" then v[key] = Modules:GetFunction(obj, v[key]) end if type(v[key]) ~= valType then return format("expected %s type for field %s, got %s at index %d", valType, key, type(v[key]), i) end end end end function Modules:ValidateModuleObject(obj) -- make sure it's a table if type(obj) ~= "table" then return format("Expected table, got %s.", type(obj)) end -- simple check that it's an AceAddon object which stores the name in .name and implements a .__tostring metamethod. if tostring(obj) ~= obj.name then return "Passed object is not an AceAddon-3.0 object." end -- validate all the fields for _, fieldInfo in ipairs(moduleFieldInfo) do local val = obj[fieldInfo.key] if val then -- make sure it's of the correct type if type(val) ~= fieldInfo.type then return format("For field '%s', expected type of %s, got %s.", fieldInfo.key, fieldInfo.type, type(val)) end -- if there's required subfields, check them if fieldInfo.subFieldInfo then for key, valType in pairs(fieldInfo.subFieldInfo) do if valType == "function" then val[key] = Modules:GetFunction(obj, val[key]) end if type(val[key]) ~= valType then return format("expected %s type for field %s, got %s at index %d", valType, key, type(val[key]), key) end end end -- if there's subTableInfo specified, run Modules:ValidateList on this field if fieldInfo.subTableInfo then local errMsg = Modules:ValidateList(obj, val, fieldInfo.subTableInfo) if errMsg then return format("Invalid value for '%s': %s.", fieldInfo.key, errMsg) end end end end end function Modules:GetInfo() local info = {} for _, name in ipairs(moduleNames) do local obj = moduleObjects[name] tinsert(info, { name = name, version = obj._version, author = obj._author, desc = obj._desc }) end return info end function TSMAPI:NewModule(obj) local errMsg if obj == TSM then local tmp = TSM.operations TSM.operations = nil errMsg = Modules:ValidateModuleObject(obj) TSM.operations = tmp else errMsg = Modules:ValidateModuleObject(obj) end if errMsg then error(errMsg, 2) end -- register the db callback if obj.db and obj.OnTSMDBShutdown then obj.db:RegisterCallback("OnDatabaseShutdown", TSM.ModuleOnDatabaseShutdown) end -- register it for debug tracing TSMAPI:RegisterForTracing(obj) for _, subModule in pairs(obj.modules or {}) do local name = obj.name.."."..subModule.moduleName TSMAPI:RegisterForTracing(subModule, name) end -- sets the _version, _author, and _desc fields local fullName = gsub(obj.name, "TSM_", "TradeSkillMaster_") obj._version = GetAddOnMetadata(fullName, "X-Curse-Packaged-Version") or GetAddOnMetadata(fullName, "Version") if strsub(obj._version, 1, 1) == "@" then obj._version = "Dev" end obj._author = GetAddOnMetadata(fullName, "Author") obj._desc = GetAddOnMetadata(fullName, "Notes") -- store the object in the local table local moduleName = gsub(obj.name, "TradeSkillMaster_", "") moduleName = gsub(obj.name, "TSM_", "") moduleObjects[moduleName] = obj tinsert(moduleNames, moduleName) sort(moduleNames, function(a, b) if a == "TradeSkillMaster" then return true elseif b == "TradeSkillMaster" then return false else return a < b end end) -- register icons with main frame code if obj.icons then for _, info in ipairs(obj.icons) do if info.slashCommand then obj.slashCommands = obj.slashCommands or {} tinsert(obj.slashCommands, {key=info.slashCommand, label=format("Opens the TSM window to the '%s' page", info.desc), callback=function() TSMAPI:OpenFrame() TSMAPI:SelectIcon(obj.name, info.desc) end}) end TSM:RegisterMainFrameIcon(info.desc, info.icon, info.callback, obj.name, info.side) end end -- register auction buttons with auction frame code if obj.auctionTab then TSM:RegisterAuctionFunction(moduleName, obj.auctionTab.callbackShow, obj.auctionTab.callbackHide) end if obj ~= TSM and obj.operations then -- conversion code from early beta versions if obj.db and obj.db.global.operations then TSM.operations[moduleName] = CopyTable(obj.db.global.operations) obj.db.global.operations = nil end TSM:RegisterOperationInfo(moduleName, obj.operations) TSM.operations[moduleName] = TSM.operations[moduleName] or {} obj.operations = TSM.operations[moduleName] for _, operation in pairs(obj.operations) do operation.ignorePlayer = operation.ignorePlayer or {} operation.ignorerealm = operation.ignorerealm or {} operation.relationships = operation.relationships or {} end TSM:CheckOperationRelationships(moduleName) end -- register tooltip options if obj.tooltipOptions then TSM:RegisterTooltipInfo(moduleName, obj.tooltipOptions) end -- register bankUi Tabs if obj.bankUiButton then TSM:RegisterBankUiButton(moduleName, obj.bankUiButton.callback) end -- -- register sync callback -- if obj.sync then -- TSM:RegisterSyncCallback(moduleName, obj.sync.callback) -- end -- replace default Print and Printf functions local Print = obj.Print obj.Print = function(self, ...) Print(self, TSMAPI:GetChatFrame(), ...) end local Printf = obj.Printf obj.Printf = function(self, ...) Printf(self, TSMAPI:GetChatFrame(), ...) end end function TSM:UpdateModuleProfiles() if TSM.db.global.globalOperations then for moduleName, obj in pairs(moduleObjects) do if obj.operations then TSM.db.global.operations[moduleName] = TSM.db.global.operations[moduleName] or {} obj.operations = TSM.db.global.operations[moduleName] end end TSM.operations = TSM.db.global.operations else for moduleName, obj in pairs(moduleObjects) do if obj.operations then TSM.db.profile.operations[moduleName] = TSM.db.profile.operations[moduleName] or {} obj.operations = TSM.db.profile.operations[moduleName] end end TSM.operations = TSM.db.profile.operations end for module, operations in pairs(TSM.operations) do for _, operation in pairs(operations) do operation.ignorePlayer = operation.ignorePlayer or {} operation.ignorerealm = operation.ignorerealm or {} operation.relationships = operation.relationships or {} end TSM:CheckOperationRelationships(module) end if not TSM.db.profile.design then TSM:LoadDefaultDesign() end end local didDBShutdown = false function TSM:ModuleOnDatabaseShutdown() if didDBShutdown then return end didDBShutdown = true local originalProfile = TSM.db:GetCurrentProfile() for _, obj in pairs(moduleObjects) do -- erroring here would cause the profile to be reset, so use pcall if obj.OnTSMDBShutdown and not pcall(obj.OnTSMDBShutdown) then -- the callback hit an error, so ensure the correct profile is restored TSM.db:SetProfile(originalProfile) end end -- ensure we're back on the correct profile TSM.db:SetProfile(originalProfile) end function TSM:IsOperationIgnored(module, operationName) local obj = moduleObjects[module] local operation = obj.operations[operationName] if not operation then return end local realm = TSM.db.keys.realm local playerKey = UnitName("player").." - "..realm return operation.ignorePlayer[playerKey] or operation.ignorerealm[realm] end function TSM:CheckOperationRelationships(moduleName) for _, operation in pairs(TSM.operations[moduleName]) do for key, target in pairs(operation.relationships or {}) do if not TSM.operations[moduleName][target] then operation.relationships[key] = nil end end end end -- ************************************************************************** -- Module APIs -- ************************************************************************** function TSMAPI:ModuleAPI(moduleName, key, ...) if type(moduleName) ~= "string" or type(key) ~= "string" then return nil, "Invalid args" end if not moduleObjects[moduleName] then return nil, "Invalid module" end for _, info in ipairs(moduleObjects[moduleName].moduleAPIs or {}) do if info.key == key then return info.callback(...) end end return nil, "Key not found" end -- ************************************************************************** -- Slash Commands -- ************************************************************************** function Modules:ChatCommand(input) local parts = { (" "):split(input) } local cmd, args = strlower(parts[1] or ""), table.concat(parts, " ", 2) if cmd == "" then TSMAPI:OpenFrame() TSMAPI:SelectIcon("TradeSkillMaster", L["TSM Status / Options"]) else local foundCmd for _, obj in pairs(moduleObjects) do if obj.slashCommands then for _, info in ipairs(obj.slashCommands) do if strlower(info.key) == cmd then info.callback(args) foundCmd = true end end end end -- If not a registered command, print out slash command help if not foundCmd then local chatFrame = TSMAPI:GetChatFrame() TSM:Print(L["Slash Commands:"]) chatFrame:AddMessage("|cffffaa00" .. L["/tsm|r - opens the main TSM window."]) chatFrame:AddMessage("|cffffaa00" .. L["/tsm help|r - Shows this help listing"]) for _, name in ipairs(moduleNames) do for _, info in ipairs(moduleObjects[name].slashCommands or {}) do chatFrame:AddMessage("|cffffaa00/tsm " .. info.key .. "|r - " .. info.label) end end end end end