You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

368 lines
14 KiB

-- ------------------------------------------------------------------------------ --
-- 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