This commit is contained in:
Andrew6810
2022-11-05 21:19:42 -07:00
parent b79f4bd588
commit f3e579cb57
386 changed files with 93729 additions and 2 deletions
+415
View File
@@ -0,0 +1,415 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- TSM's error handler.
local TSM = select(2, ...)
local AceGUI = LibStub("AceGUI-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster")
local origErrorHandler, ignoreErrors, isErrorFrameVisible, isAssert
TSMERRORLOG = {}
local tsmStack = {}
local stackNameLookup = {}
local addonSuites = {
{name="ArkInventory"},
{name="AtlasLoot"},
{name="Altoholic"},
{name="Auc-Advanced", commonTerm="Auc-"},
{name="Bagnon"},
{name="BigWigs"},
{name="Broker"},
{name="ButtonFacade"},
{name="Carbonite"},
{name="DataStore"},
{name="DBM"},
{name="Dominos"},
{name="DXE"},
{name="EveryQuest"},
{name="Forte"},
{name="FuBar"},
{name="GatherMate2"},
{name="Grid"},
{name="LightHeaded"},
{name="LittleWigs"},
{name="Masque"},
{name="MogIt"},
{name="Odyssey"},
{name="Overachiever"},
{name="PitBull4"},
{name="Prat-3.0"},
{name="RaidAchievement"},
{name="Skada"},
{name="SpellFlash"},
{name="TidyPlates"},
{name="TipTac"},
{name="Titan"},
{name="UnderHood"},
{name="WowPro"},
{name="ZOMGBuffs"},
}
local function StrStartCmp(str, startStr)
local startLen = strlen(startStr)
if startLen <= strlen(str) then
return strsub(str, 1, startLen) == startStr
end
end
local function GetModule(msg)
if strfind(msg, "TradeSkillMaster_") then
return strmatch(msg, "TradeSkillMaster_[A-Za-z]+")
elseif strfind(msg, "TradeSkillMaster\\") then
return "TradeSkillMaster"
end
return "?"
end
local function ExtractErrorMessage(...)
local msg = ""
for _, var in ipairs({...}) do
local varStr
local varType = type(var)
if varType == "boolean" then
varStr = var and "true" or "false"
elseif varType == "table" then
varStr = "<table>"
elseif varType == "function" then
varStr = "<function>"
elseif var == nil then
varStr = "<nil>"
else
varStr = var
end
msg = msg.." "..varStr
end
return msg
end
local function GetDebugStack()
local stackInfo = {}
local stackString = ""
local stack = debugstack(2) or debugstack(1)
if type(stack) == "string" then
local lines = {("\n"):split(stack)}
for _, line in ipairs(lines) do
local strStart = strfind(line, "in function")
if strStart and not strfind(line, "ErrorHandler.lua") then
line = gsub(line, "`", "<", 1)
line = gsub(line, "'", ">", 1)
local inFunction = strmatch(line, "<[^>]*>", strStart)
if inFunction then
inFunction = gsub(gsub(inFunction, ".*\\", ""), "<", "")
if inFunction ~= "" then
local str = strsub(line, 1, strStart-2)
str = strsub(str, strfind(str, "TradeSkillMaster") or 1)
if strfind(inFunction, "`") then
inFunction = strsub(inFunction, 2, -2)..">"
end
str = gsub(str, "TradeSkillMaster", "TSM")
tinsert(stackInfo, str.." <"..inFunction)
end
end
end
end
end
return table.concat(stackInfo, "\n")
end
local function GetTSMStack()
local stackInfo = {}
local index = #tsmStack
for i=1, 10 do -- only show up to 10 lines
if not tsmStack[index] then break end
tinsert(stackInfo, tsmStack[index])
index = index - 1
end
return table.concat(stackInfo, "\n")
end
local function GetEventLog()
local eventInfo = {}
local eventLog = TSM:GetEventLog()
for i, entry in ipairs(eventLog) do
tinsert(eventInfo, format("%d | %s | %s", i, entry.event, tostring(entry.arg)))
end
return table.concat(eventInfo, "\n")
end
local function GetAddonList()
local hasAddonSuite = {}
local addons = {}
local addonString = ""
for i = 1, GetNumAddOns() do
local name, _, _, enabled = GetAddOnInfo(i)
local version = GetAddOnMetadata(name, "X-Curse-Packaged-Version") or GetAddOnMetadata(name, "Version") or ""
if enabled then
local isSuite
for _, addonSuite in ipairs(addonSuites) do
local commonTerm = addonSuite.commonTerm or addonSuite.name
if StrStartCmp(name, commonTerm) then
isSuite = commonTerm
break
end
end
if isSuite then
if not hasAddonSuite[isSuite] then
tinsert(addons, {name=name, version=version})
hasAddonSuite[isSuite] = true
end
elseif StrStartCmp(name, "TradeSkillMaster") then
tinsert(addons, {name=gsub(name, "TradeSkillMaster", "TSM"), version=version})
else
tinsert(addons, {name=name, version=version})
end
end
end
for i, addonInfo in ipairs(addons) do
local info = addonInfo.name .. " (" .. addonInfo.version .. ")"
if i == #addons then
addonString = addonString .. " " .. info
else
addonString = addonString .. " " .. info .. "\n"
end
end
return addonString
end
local function ShowError(msg, isVerify)
if not AceGUI then
TSMAPI:CreateTimeDelay("errHandlerShowDelay", 0.1, function()
if AceGUI and UIParent then
CancelFrame("errHandlerShowDelay")
ShowError(msg, isVerify)
end
end, 0.1)
return
end
local f = AceGUI:Create("TSMWindow")
f:SetCallback("OnClose", function(self) isErrorFrameVisible = false AceGUI:Release(self) end)
f:SetTitle(L["TradeSkillMaster Error Window"])
f:SetLayout("Flow")
f:SetWidth(500)
f:SetHeight(400)
local l = AceGUI:Create("Label")
l:SetFullWidth(true)
l:SetFontObject(GameFontNormal)
if isVerify then
l:SetText(L["Looks like TradeSkillMaster has detected an error with your configuration. Please address this in order to ensure TSM remains functional."].."\n"..L["|cffffff00DO NOT report this as an error to the developers.|r If you require assistance with this, make a post on the TSM forums instead."].."|r")
else
l:SetText(L["Looks like TradeSkillMaster has encountered an error. Please help the author fix this error by copying the entire error below and following the instructions for reporting bugs listed here (unless told elsewhere by the author):"].." |cffffff00http://tradeskillmaster.com/wiki|r")
end
f:AddChild(l)
local heading = AceGUI:Create("Heading")
heading:SetText("")
heading:SetFullWidth(true)
f:AddChild(heading)
local eb = AceGUI:Create("MultiLineEditBox")
eb:SetLabel(L["Error Info:"])
eb:SetMaxLetters(0)
eb:SetFullWidth(true)
eb:SetText(msg)
eb:DisableButton(true)
eb:SetFullHeight(true)
f:AddChild(eb)
f.frame:SetFrameStrata("FULLSCREEN_DIALOG")
f.frame:SetFrameLevel(100)
isErrorFrameVisible = true
end
function TSM:IsValidError(...)
if ignoreErrors then return end
ignoreErrors = true
local msg = ExtractErrorMessage(...)
ignoreErrors = false
if not strfind(msg, "TradeSkillMaster") then return end
if strfind(msg, "auc%-stat%-wowuction") then return end
return msg
end
function TSMAPI:Verify(cond, err)
if cond then return end
ignoreErrors = true
tinsert(TSMERRORLOG, err)
if not isErrorFrameVisible then
TSM:Print(L["Looks like TradeSkillMaster has detected an error with your configuration. Please address this in order to ensure TSM remains functional."])
ShowError(err, true)
elseif isErrorFrameVisible == true then
TSM:Print(L["Additional error suppressed"])
isErrorFrameVisible = 1
end
ignoreErrors = false
end
local function TSMErrorHandler(msg)
-- ignore errors while we are handling this error
ignoreErrors = true
TSMERRORTEMP = msg
local color = TSMAPI.Design and TSMAPI.Design:GetInlineColor("link2") or ""
local color2 = TSMAPI.Design and TSMAPI.Design:GetInlineColor("advanced") or ""
local errorMessage = ""
errorMessage = errorMessage..color.."Addon:|r "..color2..GetModule(msg).."|r\n"
errorMessage = errorMessage..color.."Message:|r "..msg.."\n"
errorMessage = errorMessage..color.."Date:|r "..date("%m/%d/%y %H:%M:%S").."\n"
errorMessage = errorMessage..color.."Client:|r "..GetBuildInfo().."\n"
errorMessage = errorMessage..color.."Locale:|r "..GetLocale().."\n"
errorMessage = errorMessage..color.."Stack:|r\n"..GetDebugStack().."\n"
errorMessage = errorMessage..color.."TSM Stack:|r\n"..GetTSMStack().."\n"
errorMessage = errorMessage..color.."Local Variables:|r\n"..(debuglocals(isAssert and 5 or 4) or "").."\n"
errorMessage = errorMessage..color.."TSM Event Log:|r\n"..GetEventLog().."\n"
errorMessage = errorMessage..color.."Addons:|r\n"..GetAddonList().."\n"
tinsert(TSMERRORLOG, errorMessage)
if not isErrorFrameVisible then
TSM:Print(L["Looks like TradeSkillMaster has encountered an error. Please help the author fix this error by following the instructions shown."])
ShowError(errorMessage)
elseif isErrorFrameVisible == true then
TSM:Print(L["Additional error suppressed"])
isErrorFrameVisible = 1
end
-- need to clear the stack
tsmStack = {}
ignoreErrors = false
end
function TSMAPI:Assert(cond, err)
if cond then return end
isAssert = true
TSMErrorHandler(err)
isAssert = false
end
do
origErrorHandler = geterrorhandler()
local errHandlerFrame = CreateFrame("Frame", nil, nil, "TSMErrorHandlerTemplate")
errHandlerFrame.errorHandler = TSMErrorHandler
errHandlerFrame.origErrorHandler = origErrorHandler
seterrorhandler(errHandlerFrame.handler)
end
--[===[@debug@
--- Disables TSM's error handler until the game is reloaded.
-- This is mainly used for debugging errors with TSM's error handler and should not be used in actual code.
function TSMAPI:DisableErrorHandler()
seterrorhandler(origErrorHandler)
end
--@end-debug@]===]
-- other debug functions
TSMAPI.Debug = {}
local dumpDefaults = {
DEVTOOLS_MAX_ENTRY_CUTOFF = 30, -- Maximum table entries shown
DEVTOOLS_LONG_STRING_CUTOFF = 200, -- Maximum string size shown
DEVTOOLS_DEPTH_CUTOFF = 10, -- Maximum table depth
}
function TSMAPI.Debug:DumpTable(tbl, maxDepth, maxItems, maxStr)
DEVTOOLS_DEPTH_CUTOFF = maxDepth or dumpDefaults.DEVTOOLS_DEPTH_CUTOFF
DEVTOOLS_MAX_ENTRY_CUTOFF = maxItems or dumpDefaults.DEVTOOLS_MAX_ENTRY_CUTOFF
DEVTOOLS_DEPTH_CUTOFF = maxStr or dumpDefaults.DEVTOOLS_DEPTH_CUTOFF
if not IsAddOnLoaded("Blizzard_DebugTools") then
LoadAddOn("Blizzard_DebugTools")
end
DevTools_Dump(tbl)
for i, v in pairs(dumpDefaults) do
_G[i] = v
end
end
-- stack tracing functions
local function FormatTSMStack(obj, name, ...)
local args
for i=2, select('#', ...) do
local arg = select(i, ...)
local str
if stackNameLookup[arg] then
str = "<"..stackNameLookup[arg]..">"
elseif type(arg) == "table" then
if getmetatable(arg) and getmetatable(arg).__tostring then
str = "<"..tostring(arg)..">"
else
local _, addr = (":"):split(tostring(arg))
str = "table:"..tonumber(addr, 16)
end
elseif type(arg) == "string" then
str = '"'..tostring(arg)..'"'
elseif type(arg) == "function" then
local _, addr = (":"):split(tostring(arg))
str = "function:"..tonumber(addr, 16)
else
str = tostring(arg)
end
if args then
args = args..", "..str
else
args = str
end
end
local funcCall = "?"
if obj == select(1, ...) and args then
funcCall = (stackNameLookup[obj] or tostring(obj))..":"..name.."("..args..")"
end
return funcCall
end
-- this must be a separate function so we can return the ... after popping off the stack
local function TrackPopStack(...)
tremove(tsmStack, #tsmStack)
return ...
end
local function RegisterForTracing(obj, name)
stackNameLookup[obj] = name
for name, v in pairs(obj) do
if type(v) == "function" then
TSM:RawHook(obj, name, function(...)
tinsert(tsmStack, FormatTSMStack(obj, name, ...))
return TrackPopStack(v(...))
end)
end
end
end
function TSMAPI:RegisterForTracing(obj, name)
-- wait one frame to ensure all functions are declared
TSMAPI:CreateTimeDelay(0, function() RegisterForTracing(obj, name) end)
end
+49
View File
@@ -0,0 +1,49 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- TSM's error handler.
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster")
local eventObj = TSMAPI:GetEventObject()
local currentIndex = 1
local NUM_LOG_ENTRIES = 20
local debugLog = {}
local alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_="
local base = #alpha
local alphaTable = {}
local alphaTableLookup = {}
for i = 1, base do
local char = strsub(alpha, i, i)
tinsert(alphaTable, char)
alphaTableLookup[char] = i
end
local function EventCallback(event, arg)
debugLog[currentIndex] = {event=event, arg=arg}
currentIndex = currentIndex + 1
if currentIndex > NUM_LOG_ENTRIES then
currentIndex = 1
end
end
eventObj:SetCallbackAnyEvent(EventCallback)
function TSM:GetEventLog()
local temp = {}
for i=1, #debugLog do
local index = currentIndex - i
if index <= 0 then
index = index + NUM_LOG_ENTRIES
end
tinsert(temp, debugLog[index])
end
return temp
end
+62
View File
@@ -0,0 +1,62 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- TSM's event handler.
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster")
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Events_private")
private.objects = {}
private.eventObjectCallbacks = {
SetCallbackAnyEvent = function(self, callback)
self._anyEventCallback = callback
end,
SetCallback = function(self, event, callback, matchAll)
self._callbacks[event] = {func = callback, matchAll = (matchAll and true or false)} -- need to convert matchAll to a boolean
end,
ClearAllCallbacks = function(self)
wipe(self._callbacks)
end
}
function TSMAPI:GetEventObject()
local obj = {}
obj._callbacks = {}
obj._anyEventCallback = nil
for name, func in pairs(private.eventObjectCallbacks) do
obj[name] = func
end
tinsert(private.objects, obj)
return obj
end
function private:OnEventFired(event, arg, fullEvent)
local isPartial = event ~= fullEvent and true or false
for _, obj in ipairs(private.objects) do
if not isPartial and obj._anyEventCallback then
obj._anyEventCallback(fullEvent, arg)
end
local callback = obj._callbacks[event]
if callback then
if isPartial == callback.matchAll then
callback.func(fullEvent, arg)
end
end
end
end
function TSMAPI:FireEvent(event, arg)
local parts = {(":"):split(event)}
for i=1, #parts do
local partialEvent = table.concat(parts, ":", 1, i)
private:OnEventFired(partialEvent, arg, event)
end
end
File diff suppressed because it is too large Load Diff
+368
View File
@@ -0,0 +1,368 @@
-- ------------------------------------------------------------------------------ --
-- 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.ignoreFactionrealm = operation.ignoreFactionrealm 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.ignoreFactionrealm = operation.ignoreFactionrealm 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 factionrealm = TSM.db.keys.factionrealm
local playerKey = UnitName("player").." - "..factionrealm
return operation.ignorePlayer[playerKey] or operation.ignoreFactionrealm[factionrealm]
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
+699
View File
@@ -0,0 +1,699 @@
-- ------------------------------------------------------------------------------ --
-- 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 moving items between bags / bank.
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries
local lib = TSMAPI
local bankType
local fullMoves, splitMoves, bagState = {}, {}, {}
local callbackMsg = {}
-- this is a set of wrapper functions so that I can switch
-- between guildbank and bank function easily (taken from warehousing)
TSM.pickupContainerItemSrc = nil
TSM.getContainerItemIDSrc = nil
TSM.getContainerNumSlotsSrc = nil
TSM.getContainerItemLinkSrc = nil
TSM.getContainerNumFreeSlotsSrc = nil
TSM.splitContainerItemSrc = nil
TSM.pickupContainerItemDest = nil
TSM.getContainerItemIDDest = nil
TSM.getContainerNumSlotsDest = nil
TSM.getContainerItemLinkDest = nil
TSM.getContainerNumFreeSlotsDest = nil
TSM.autoStoreItem = nil
TSM.getContainerItemQty = nil
function TSM:OnEnable()
local next = next
TSM:RegisterEvent("GUILDBANKFRAME_OPENED", function(event)
bankType = "guildbank"
end)
TSM:RegisterEvent("BANKFRAME_OPENED", function(event)
bankType = "bank"
end)
TSM:RegisterEvent("GUILDBANKFRAME_CLOSED", function(event, addon)
bankType = nil
TSM:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED")
end)
TSM:RegisterEvent("BANKFRAME_CLOSED", function(event)
bankType = nil
TSM:UnregisterEvent("BAG_UPDATE")
end)
end
local function setSrcBagFunctions(bagType)
if bagType == "guildbank" then
TSM.autoStoreItem = function(bag, slot) AutoStoreGuildBankItem(bag, slot)
end
TSM.getContainerItemQty = function(bag, slot) return select(2, GetGuildBankItemInfo(bag, slot))
end
TSM.splitContainerItemSrc = function(bag, slot, need) SplitGuildBankItem(bag, slot, need);
end
TSM.pickupContainerItemSrc = function(bag, slot) PickupGuildBankItem(bag, slot)
end
TSM.getContainerNumSlotsSrc = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98
end
TSM.getContainerItemLinkSrc = function(bag, slot) return GetGuildBankItemLink(bag, slot)
end
TSM.getContainerNumFreeSlotsSrc = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98
end --need to change this eventually
TSM.getContainerItemIDSrc = function(bag, slot)
local tmpLink = GetGuildBankItemLink(bag, slot)
local quantity = select(2, GetGuildBankItemInfo(bag, slot))
if tmpLink then
return TSMAPI:GetBaseItemString(tmpLink, true), quantity
else
return nil
end
end
else
TSM.autoStoreItem = function(bag, slot) UseContainerItem(bag, slot)
end
TSM.getContainerItemQty = function(bag, slot) return select(2, GetContainerItemInfo(bag, slot))
end
TSM.splitContainerItemSrc = function(bag, slot, need) SplitContainerItem(bag, slot, need)
end
TSM.pickupContainerItemSrc = function(bag, slot) PickupContainerItem(bag, slot)
end
TSM.getContainerItemIDSrc = function(bag, slot)
local tmpLink = GetContainerItemLink(bag, slot)
local quantity = select(2, GetContainerItemInfo(bag, slot))
return TSMAPI:GetBaseItemString(tmpLink, true), quantity
end
TSM.getContainerNumSlotsSrc = function(bag) return GetContainerNumSlots(bag)
end
TSM.getContainerItemLinkSrc = function(bag, slot) return GetContainerItemLink(bag, slot)
end
TSM.getContainerNumFreeSlotsSrc = function(bag) return GetContainerNumFreeSlots(bag)
end
end
end
local function setDestBagFunctions(bagType)
if bagType == "guildbank" then
TSM.pickupContainerItemDest = function(bag, slot) PickupGuildBankItem(bag, slot)
end
TSM.getContainerNumSlotsDest = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98
end
TSM.getContainerNumFreeSlotsDest = function(bag) return GetEmptySlotCount(bag)
end --need to change this eventually
TSM.getContainerItemLinkDest = function(bag, slot) return GetGuildBankItemLink(bag, slot)
end
TSM.getContainerItemIDDest = function(bag, slot)
local tmpLink = GetGuildBankItemLink(bag, slot)
local quantity = select(2, GetGuildBankItemInfo(bag, slot))
if tmpLink then
return TSMAPI:GetBaseItemString(tmpLink, true), quantity
else
return nil
end
end
else
TSM.pickupContainerItemDest = function(bag, slot) PickupContainerItem(bag, slot)
end
TSM.getContainerItemIDDest = function(bag, slot)
local tmpLink = GetContainerItemLink(bag, slot)
local quantity = select(2, GetContainerItemInfo(bag, slot))
return TSMAPI:GetBaseItemString(tmpLink, true), quantity
end
TSM.getContainerNumSlotsDest = function(bag) return GetContainerNumSlots(bag)
end
TSM.getContainerItemLinkDest = function(bag, slot) return GetContainerItemLink(bag, slot)
end
TSM.getContainerNumFreeSlotsDest = function(bag) return GetContainerNumFreeSlots(bag)
end
end
end
local function getContainerTable(cnt)
local t = {}
if cnt == "bank" then
local numSlots, _ = GetNumBankSlots()
for i = 1, numSlots + 1 do
if i == 1 then
t[i] = -1
else
t[i] = i + 3
end
end
return t
elseif cnt == "guildbank" then
for i = 1, GetNumGuildBankTabs() do
local canView, canDeposit, stacksPerDay = GetGuildBankTabInfo(i);
if canView and canDeposit and stacksPerDay then
t[i] = i
end
end
return t
elseif cnt == "bags" then
for i = 1, NUM_BAG_SLOTS + 1 do t[i] = i - 1
end
return t
end
end
local function GetEmptySlots(container)
local emptySlots = {}
for i, bag in ipairs(getContainerTable(container)) do
if TSM.getContainerNumSlotsDest(bag) > 0 then
for slot = 1, TSM.getContainerNumSlotsDest(bag) do
if not TSM.getContainerItemIDDest(bag, slot) then
if not emptySlots[bag] then emptySlots[bag] = {}
end
table.insert(emptySlots[bag], slot)
end
end
end
end
return emptySlots
end
local function GetEmptySlotCount(bag)
local count = 0
for slot = 1, TSM.getContainerNumSlotsDest(bag) do
if not TSM.getContainerItemLinkDest(bag, slot) then
count = count + 1
end
end
if count ~= 0 then
return count
else
return false
end
end
local function canGoInBag(itemString, destTable)
local itemFamily = GetItemFamily(itemString)
local default
for _, bag in pairs(destTable) do
local bagFamily = GetItemFamily(GetBagName(bag)) or 0
if itemFamily and bagFamily and bagFamily > 0 and bit.band(itemFamily, bagFamily) > 0 then
if GetEmptySlotCount(bag) then
return bag
end
elseif bagFamily == 0 then
if GetEmptySlotCount(bag) then
if not default then
default = bag
end
end
end
end
return default
end
local function findExistingStack(itemLink, dest, quantity, gbank)
for i, bag in ipairs(getContainerTable(dest)) do
if gbank then
if bag == GetCurrentGuildBankTab() then
for slot = 1, TSM.getContainerNumSlotsDest(bag) do
if TSM.getContainerItemIDDest(bag, slot) == TSMAPI:GetBaseItemString(itemLink, true) then
local maxStack = select(8, TSMAPI:GetSafeItemInfo(itemLink))
local _, currentQuantity = TSM.getContainerItemIDDest(bag, slot)
if currentQuantity and (currentQuantity + quantity) <= maxStack then
return bag, slot
end
end
end
end
else
for slot = 1, TSM.getContainerNumSlotsDest(bag) do
if TSM.getContainerItemIDDest(bag, slot) == TSMAPI:GetBaseItemString(itemLink, true) then
local maxStack = select(8, TSMAPI:GetSafeItemInfo(itemLink))
local _, currentQuantity = TSM.getContainerItemIDDest(bag, slot)
if currentQuantity and (currentQuantity + quantity) <= maxStack then
return bag, slot
end
end
end
end
end
end
local function getTotalItems(src)
local results = {}
if src == "bank" then
for _, _, itemString, quantity in TSMAPI:GetBankIterator(true, true) do
results[itemString] = (results[itemString] or 0) + quantity
end
return results
elseif src == "guildbank" then
for bag = 1, GetNumGuildBankTabs() do
for slot = 1, MAX_GUILDBANK_SLOTS_PER_TAB or 98 do
local link = GetGuildBankItemLink(bag, slot)
local itemString = TSMAPI:GetBaseItemString(link, true)
if itemString then
local quantity = select(2, GetGuildBankItemInfo(bag, slot))
results[itemString] = (results[itemString] or 0) + quantity
end
end
end
return results
elseif src == "bags" then
for _, _, itemString, quantity in TSMAPI:GetBagIterator(true, true) do
results[itemString] = (results[itemString] or 0) + quantity
end
return results
end
end
function TSM.generateMoves(includeSoulbound)
if not TSM:areBanksVisible() then
wipe(splitMoves)
wipe(fullMoves)
for _, callback in ipairs(callbackMsg) do
callback(L["Cancelled - You must be at a bank or guildbank"])
end
wipe(callbackMsg)
return
end
local next = next
local bagsFull, bankFull = false, false
local bagMoves, bankMoves = {}, {}
wipe(splitMoves)
wipe(fullMoves)
local currentBagState = getTotalItems("bags")
for itemString, quantity in pairs(bagState) do
local currentQty = currentBagState[itemString] or 0
if quantity < currentQty then
bagMoves[itemString] = currentQty - quantity
elseif quantity > currentQty then
bankMoves[itemString] = quantity - currentQty
end
end
if next(bagMoves) ~= nil then -- generate moves from bags to bank
setSrcBagFunctions("bags")
setDestBagFunctions(bankType)
for item, _ in pairs(bagMoves) do
for i, bag in ipairs(getContainerTable("bags")) do
for slot = 1, TSM.getContainerNumSlotsSrc(bag) do
local itemLink = TSM.getContainerItemLinkSrc(bag, slot)
local itemString = TSMAPI:GetBaseItemString(itemLink, true)
if itemString and itemString == item then
if not TSMAPI:IsSoulbound(bag, slot) or includeSoulbound then
local have = TSM.getContainerItemQty(bag, slot)
local need = bagMoves[itemString]
if have and need then
-- check if the source item stack can fit into a destination bag
local destBag
if bankType == "guildbank" then
destBag = findExistingStack(itemLink, bankType, min(have, need), true)
if not destBag then
if GetEmptySlotCount(GetCurrentGuildBankTab()) ~= false then
destBag = GetCurrentGuildBankTab()
end
end
else
destBag = findExistingStack(itemLink, bankType, min(have, need))
if not destBag then
if next(GetEmptySlots(bankType)) ~= nil then
destBag = canGoInBag(itemString, getContainerTable(bankType))
end
end
end
if destBag then
if have > need then
tinsert(splitMoves, { src = "bags", bag = bag, slot = slot, quantity = need })
bagMoves[itemString] = nil
else
tinsert(fullMoves, { src = "bags", bag = bag, slot = slot, quantity = have })
bagMoves[itemString] = bagMoves[itemString] - have
if bagMoves[itemString] <= 0 then
bagMoves[itemString] = nil
end
end
else
bankFull = true
end
end
end
end
end
end
end
end
if next(bankMoves) ~= nil then -- generate moves from bank to bags
setSrcBagFunctions(bankType)
setDestBagFunctions("bags")
for item, _ in pairs(bankMoves) do
for i, bag in ipairs(getContainerTable(bankType)) do
for slot = 1, TSM.getContainerNumSlotsSrc(bag) do
local itemLink = TSM.getContainerItemLinkSrc(bag, slot)
local itemString = TSMAPI:GetBaseItemString(itemLink, true)
if itemString and itemString == item then
local have = TSM.getContainerItemQty(bag, slot)
local need = bankMoves[itemString]
if have and need then
if not TSMAPI:IsSoulbound(bag, slot) or includeSoulbound then
local destBag = findExistingStack(itemLink, "bags", min(have, need)) or canGoInBag(itemString, getContainerTable("bags"))
if destBag then
if have > need then
tinsert(splitMoves, { src = bankType, bag = bag, slot = slot, quantity = need })
bankMoves[itemString] = nil
else
tinsert(fullMoves, { src = bankType, bag = bag, slot = slot, quantity = have })
bankMoves[itemString] = bankMoves[itemString] - have
if bankMoves[itemString] <= 0 then
bankMoves[itemString] = nil
end
end
else
bagsFull = true
end
end
end
end
end
end
end
end
if next(fullMoves) ~= nil then
if bankType == "guildbank" then
TSMAPI:CreateTimeDelay("moveItem", 0.05, TSM.moveItem, 0.35)
else
TSMAPI:CreateTimeDelay("moveItem", 0.05, TSM.moveItem, 0.05)
end
elseif next(splitMoves) ~= nil then
if bankType == "guildbank" then
TSMAPI:CreateTimeDelay("moveSplitItem", 0.05, TSM.moveSplitItem, 0.75)
else
TSMAPI:CreateTimeDelay("moveSplitItem", 0.05, TSM.moveSplitItem, 0.4)
end
else
if bagsFull and not bankFull then
for _, callback in ipairs(callbackMsg) do
callback(L["Cancelled - Bags are full"])
end
elseif bankFull and not bagsFull then
for _, callback in ipairs(callbackMsg) do
if bankType == "guildbank" then
callback(L["Cancelled - Guildbank is full"])
elseif bankType == "bank" then
callback(L["Cancelled - Bank is full"])
else
callback("Cancelled - " .. bankType .. " is full")
end
end
elseif bagsFull and bankFull then
for _, callback in ipairs(callbackMsg) do
if bankType == "guildbank" then
callback(L["Cancelled - Bags and guildbank are full"])
elseif bankType == "bank" then
callback(L["Cancelled - Bags and bank are full"])
else
callback("Cancelled - Bags and " .. bankType .. " are full")
end
end
else
for _, callback in ipairs(callbackMsg) do
callback(L["Done"])
end
end
wipe(callbackMsg)
end
end
function TSM.moveItem()
if not TSM:areBanksVisible() then
wipe(fullMoves)
wipe(splitMoves)
TSMAPI:CancelFrame("moveItem")
for _, callback in ipairs(callbackMsg) do
callback(L["Cancelled - You must be at a bank or guildbank"])
end
wipe(callbackMsg)
return
end
local next = next
if #fullMoves > 0 then
local i = next(fullMoves)
if fullMoves[i].src == "bags" then
setSrcBagFunctions("bags")
setDestBagFunctions(bankType)
local itemString = TSMAPI:GetBaseItemString(TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot), true)
local itemLink = TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot)
local have = TSM.getContainerItemQty(fullMoves[i].bag, fullMoves[i].slot)
local need = fullMoves[i].quantity
if have and need then
if bankType == "guildbank" then
if findExistingStack(itemLink, bankType, need, true) then
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
elseif GetEmptySlotCount(GetCurrentGuildBankTab()) then
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
else
TSMAPI:CancelFrame("moveItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
else
if findExistingStack(itemLink, bankType, need) then
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
elseif next(GetEmptySlots(bankType)) ~= nil and canGoInBag(itemString, getContainerTable(bankType)) then
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
else
TSMAPI:CancelFrame("moveItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
end
end
else
setSrcBagFunctions(bankType)
setDestBagFunctions("bags")
local itemString = TSMAPI:GetBaseItemString(TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot), true)
local itemLink = TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot)
local have = TSM.getContainerItemQty(fullMoves[i].bag, fullMoves[i].slot)
local need = fullMoves[i].quantity
if have and need then
if findExistingStack(itemLink, "bags", need) then
if bankType == "guildbank" then
if GetCurrentGuildBankTab() ~= fullMoves[i].bag then
SetCurrentGuildBankTab(fullMoves[i].bag)
end
end
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
elseif next(GetEmptySlots("bags")) ~= nil and canGoInBag(itemString, getContainerTable("bags")) then
if bankType == "guildbank" then
if GetCurrentGuildBankTab() ~= fullMoves[i].bag then
SetCurrentGuildBankTab(fullMoves[i].bag)
end
end
TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot)
else
TSMAPI:CancelFrame("moveItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
end
end
tremove(fullMoves, i)
else
TSMAPI:CancelFrame("moveItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
end
function TSM.moveSplitItem()
if not TSM:areBanksVisible() then
wipe(fullMoves)
wipe(splitMoves)
TSMAPI:CancelFrame("moveSplitItem")
for _, callback in ipairs(callbackMsg) do
callback(L["Cancelled - You must be at a bank or guildbank"])
end
wipe(callbackMsg)
return
end
local next = next
--if next(moves) ~= nil then
if #splitMoves > 0 then
local i = next(splitMoves)
if splitMoves[i].src == "bags" then
setSrcBagFunctions("bags")
setDestBagFunctions(bankType)
local itemLink = TSM.getContainerItemLinkSrc(splitMoves[i].bag, splitMoves[i].slot)
local itemString = TSMAPI:GetBaseItemString(itemLink, true)
local have = TSM.getContainerItemQty(splitMoves[i].bag, splitMoves[i].slot)
local need = splitMoves[i].quantity
if have and need then
local destBag, destSlot
destBag, destSlot = findExistingStack(itemLink, bankType, need)
if destBag and destSlot then
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
else
local emptyBankSlots = GetEmptySlots(bankType)
destBag = canGoInBag(itemString, getContainerTable(bankType))
if emptyBankSlots[destBag] then
destSlot = emptyBankSlots[destBag][1]
end
if destBag and destSlot then
if bankType == "guildbank" then
if GetCurrentGuildBankTab() ~= destBag then
SetCurrentGuildBankTab(destBag)
end
end
if GetEmptySlotCount(destBag) then
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
else
if next(GetEmptySlots(bankType)) ~= nil then
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves)
end
end
end
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves)
end
else
setSrcBagFunctions(bankType)
setDestBagFunctions("bags")
local itemLink = TSM.getContainerItemLinkSrc(splitMoves[i].bag, splitMoves[i].slot)
local itemString = TSMAPI:GetBaseItemString(itemLink, true)
local have = TSM.getContainerItemQty(splitMoves[i].bag, splitMoves[i].slot)
local need = splitMoves[i].quantity
if have and need then
local destBag, destSlot
destBag, destSlot = findExistingStack(itemLink, "bags", need)
if destBag and destSlot then
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
else
local emptyBagSlots = GetEmptySlots("bags")
destBag = canGoInBag(itemString, getContainerTable("bags"))
if emptyBagSlots[destBag] then
destSlot = emptyBagSlots[destBag][1]
end
if destBag and destSlot then
if bankType == "guildbank" then
if GetCurrentGuildBankTab() ~= splitMoves[i].bag then
SetCurrentGuildBankTab(splitMoves[i].bag)
end
end
TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need)
TSM.pickupContainerItemDest(destBag, destSlot)
end
end
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves)
end
end
tremove(splitMoves, i)
else
TSMAPI:CancelFrame("moveSplitItem")
TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves)
end
end
function TSMAPI:MoveItems(requestedItems, callback, includeSoulbound)
wipe(bagState)
if callback then
assert(type(callback) == "function", format("Expected function, got %s.", type(callback)))
tinsert(callbackMsg, callback)
end
bagState = getTotalItems("bags") -- create initial bagstate
-- iterates over the requested items and adjusts bagState quantities , negative removes from bagState, positive adds to bagState
-- this gives the final states to generate the moves from
for itemString, qty in pairs(requestedItems) do
if not bagState[itemString] then bagState[itemString] = 0
end
bagState[itemString] = bagState[itemString] + qty
if bagState[itemString] < 0 then
bagState[itemString] = 0
end
end
TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves(includeSoulbound))
end
function TSM:areBanksVisible()
if BagnonFrameguildbank and BagnonFrameguildbank:IsVisible() then
return true
elseif BagnonFramebank and BagnonFramebank:IsVisible() then
return true
elseif GuildBankFrame and GuildBankFrame:IsVisible() then
return true
elseif BankFrame and BankFrame:IsVisible() then
return true
elseif (ARKINV_Frame4 and ARKINV_Frame4:IsVisible()) or (ARKINV_Frame3 and ARKINV_Frame3:IsVisible()) then
return true
elseif (BagginsBag8 and BagginsBag8:IsVisible()) or (BagginsBag9 and BagginsBag9:IsVisible()) or (BagginsBag10 and BagginsBag10:IsVisible()) or (BagginsBag11 and BagginsBag11:IsVisible()) or (BagginsBag12 and BagginsBag12:IsVisible()) then
return true
elseif (CombuctorFrame2 and CombuctorFrame2:IsVisible()) then
return true
elseif (BaudBagContainer2_1 and BaudBagContainer2_1:IsVisible()) then
return true
elseif (AdiBagsContainer2 and AdiBagsContainer2:IsVisible()) then
return true
elseif (OneBankFrame and OneBankFrame:IsVisible()) then
return true
elseif (EngBank_frame and EngBank_frame:IsVisible()) then
return true
elseif (TBnkFrame and TBnkFrame:IsVisible()) then
return true
elseif (famBankFrame and famBankFrame:IsVisible()) then
return true
elseif (LUIBank and LUIBank:IsVisible()) then
return true
elseif (ElvUI_BankContainerFrame and ElvUI_BankContainerFrame:IsVisible()) then
return true
elseif (TukuiBank and TukuiBank:IsShown()) then
return true
elseif (AdiBagsContainer1 and AdiBagsContainer1.isBank and AdiBagsContainer1:IsVisible()) or (AdiBagsContainer2 and AdiBagsContainer2.isBank and AdiBagsContainer2:IsVisible()) then
return true
elseif BagsFrameBank and BagsFrameBank:IsVisible() then
return true
elseif AspUIBank and AspUIBank:IsVisible() then
return true
elseif NivayacBniv_Bank and NivayacBniv_Bank:IsVisible() then
return true
elseif DufUIBank and DufUIBank:IsVisible() then
return true
end
return nil
end
File diff suppressed because it is too large Load Diff
+458
View File
@@ -0,0 +1,458 @@
-- ------------------------------------------------------------------------------ --
-- 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 price related TSMAPI functions.
local TSM = select(2, ...)
local moduleObjects = TSM.moduleObjects
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
TSM_PRICE_TEMP = {loopError=function(str) TSM:Printf(L["Loop detected in the following custom price:"].." "..TSMAPI.Design:GetInlineColor("link")..str.."|r") end}
local MONEY_PATTERNS = {
"([0-9]+g[ ]*[0-9]+s[ ]*[0-9]+c)", -- g/s/c
"([0-9]+g[ ]*[0-9]+s)", -- g/s
"([0-9]+g[ ]*[0-9]+c)", -- g/c
"([0-9]+s[ ]*[0-9]+c)", -- s/c
"([0-9]+g)", -- g
"([0-9]+s)", -- s
"([0-9]+c)", -- c
}
local MATH_FUNCTIONS = {
["avg"] = "_avg",
["min"] = "_min",
["max"] = "_max",
["first"] = "_first",
["check"] = "_check",
}
function TSMAPI:GetPriceSources()
local sources = {}
for _, obj in pairs(moduleObjects) do
if obj.priceSources then
for _, info in ipairs(obj.priceSources) do
sources[info.key] = info.label
end
end
end
return sources
end
local itemValueKeyCache = {}
function TSMAPI:GetItemValue(link, key)
local itemLink = select(2, TSMAPI:GetSafeItemInfo(link)) or link
if not itemLink then return end
-- look in module objects for this key
if itemValueKeyCache[key] then
local info = itemValueKeyCache[key]
return info.callback(itemLink, info.arg)
end
for _, obj in pairs(moduleObjects) do
if obj.priceSources then
for _, info in ipairs(obj.priceSources) do
if info.key == key then
itemValueKeyCache[key] = info
local value = info.callback(itemLink, info.arg)
return (type(value) == "number" and value > 0) and value or nil
end
end
end
end
end
-- validates a price string that was passed into TSMAPI:ParseCustomPrice
local supportedOperators = { "+", "-", "*", "/" }
local function ParsePriceString(str, badPriceSource)
if tonumber(str) then
return function() return tonumber(str) end
end
local origStr = str
-- make everything lower case
str = strlower(str)
-- remove any colors around gold/silver/copper
str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])g|r", "g")
str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])s|r", "s")
str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])c|r", "c")
-- replace all formatted gold amount with their copper value
local start = 1
local goldAmountContinue = true
while goldAmountContinue do
goldAmountContinue = false
local minFind = {}
for _, pattern in ipairs(MONEY_PATTERNS) do
local s, e, sub = strfind(str, pattern, start)
if s and (not minFind.s or minFind.s > s) then
minFind.s = s
minFind.e = e
minFind.sub = sub
end
end
if minFind.s then
local value = TSMAPI:UnformatTextMoney(minFind.sub)
if not value then return end -- sanity check
local preStr = strsub(str, 1, minFind.s-1)
local postStr = strsub(str, minFind.e+1)
str = preStr .. value .. postStr
start = #str - #postStr + 1
goldAmountContinue = true
end
end
-- remove up to 1 occurance of convert(priceSource[, item])
local convertPriceSource, convertItem
local _, _, convertParams = strfind(str, "convert%(([^%)]+)%)")
if convertParams then
local source
local s = strfind(convertParams, "|c")
if s then
local _, e = strfind(convertParams, "|r")
local itemString = e and TSMAPI:GetItemString(strsub(convertParams, s, e))
if not itemString then return nil, L["Invalid item link."] end -- there's an invalid item link in the convertParams
convertItem = itemString
source = strsub(convertParams, 1, s - 1)
elseif strfind(convertParams, "item:") then
local s, e = strfind(convertParams, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)")
convertItem = strsub(convertParams, s, e)
source = strsub(convertParams, 1, s - 1)
elseif strfind(convertParams, "battlepet:") then
local s, e = strfind(convertParams, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)")
convertItem = strsub(convertParams, s, e)
source = strsub(convertParams, 1, s - 1)
else
source = convertParams
end
source = gsub(source:trim(), ",$", ""):trim()
for key in pairs(TSMAPI:GetPriceSources()) do
if strlower(key) == source then
convertPriceSource = key
break
end
end
if not convertPriceSource then
return nil, L["Invalid price source in convert."]
end
local num = 0
str, num = gsub(str, "convert%(([^%)]+)%)", "~convert~")
if num > 1 then
return nil, L["A maximum of 1 convert() function is allowed."]
end
end
-- replace all item links with "~item~"
local items = {}
while true do
local s = strfind(str, "|c")
if not s then break end -- no more item links
local _, e = strfind(str, "|r")
local itemString = e and TSMAPI:GetItemString(strsub(str, s, e))
if not itemString then return nil, L["Invalid item link."] end -- there's an invalid item link in the str
tinsert(items, itemString)
str = strsub(str, 1, s - 1) .. "~item~" .. strsub(str, e + 1)
end
-- replace all itemStrings with "~item~"
while true do
local s, e
if strfind(str, "item:") then
s, e = strfind(str, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)")
elseif strfind(str, "battlepet:") then
s, e = strfind(str, "battlepet:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)")
else
break
end
local itemString = strsub(str, s, e)
tinsert(items, itemString)
str = strsub(str, 1, s - 1) .. "~item~" .. strsub(str, e + 1)
end
-- make sure there's spaces on either side of operators
for _, operator in ipairs(supportedOperators) do
str = gsub(str, TSMAPI:StrEscape(operator), " " .. operator .. " ")
end
-- add space to start of string for percentage matching
str = " "..str
-- fix any whitespace issues around item links and remove parenthesis
str = gsub(str, "%(([ ]*)~item~([ ]*)%)", " ~item~")
-- ensure a space on either side of parenthesis and commas
str = gsub(str, "%(", " ( ")
str = gsub(str, "%)", " ) ")
str = gsub(str, ",", " , ")
-- remove any occurances of more than one consecutive space
str = gsub(str, "([ ]+)", " ")
-- ensure equal number of left/right parenthesis
if select(2, gsub(str, "%(", "")) ~= select(2, gsub(str, "%)", "")) then return nil, L["Unbalanced parentheses."] end
-- convert percentages to decimal numbers
for leading, pctValue in gmatch(str, "([^0-9%.])([0-9%.]+)%%") do
if tonumber(pctValue) then
local number = tonumber(pctValue) / 100
str = gsub(str, leading..pctValue.."%%", leading .. number .. " *")
end
end
-- create array of valid price sources
local priceSourceKeys = {}
for key in pairs(TSMAPI:GetPriceSources()) do
tinsert(priceSourceKeys, strlower(key))
end
for key in pairs(TSM.db.global.customPriceSources) do
tinsert(priceSourceKeys, strlower(key))
end
-- validate all words in the string
local parts = TSMAPI:SafeStrSplit(str:trim(), " ")
for i, word in ipairs(parts) do
if tContains(supportedOperators, word) then
if i == #parts then
return nil, L["Invalid operator at end of custom price."]
end
-- valid operand
elseif badPriceSource == word then
-- price source that's explicitly invalid
return nil, format(L["You cannot use %s as part of this custom price."], word)
elseif tContains(priceSourceKeys, word) then
-- valid price source
elseif tonumber(word) then
-- make sure it's not an itemID (incorrect)
if i > 2 and parts[i-1] == "(" and tContains(priceSourceKeys, parts[i-2]) then
return nil, L["Invalid parameter to price source."]
end
-- valid number
elseif word == "~item~" then
-- make sure previous word was a price source
if i > 1 and tContains(priceSourceKeys, parts[i-1]) then
-- valid item parameter
else
return nil, L["Item links may only be used as parameters to price sources."]
end
elseif word == "(" or word == ")" then
-- valid parenthesis
elseif word == "," then
if not parts[i+1] or parts[i+1] == ")" then
return nil, L["Misplaced comma"]
else
-- we're hoping this is a valid comma within a function, will be caught by loadstring otherwise
end
elseif MATH_FUNCTIONS[word] then
if not parts[i+1] or parts[i+1] ~= "(" then
return nil, format(L["Invalid word: '%s'"], word)
end
-- valid math function
elseif word == "~convert~" then
-- valid convert statement
elseif word:trim() == "" then
-- harmless extra spaces
else
return nil, format(L["Invalid word: '%s'"], word)
end
end
for key in pairs(TSMAPI:GetPriceSources()) do
-- replace all "<priceSource> ~item~" occurances with the parameters to TSMAPI:GetItemValue (with "~item~" left in for the item)
for match in gmatch(" "..str.." ", " "..strlower(key).." ~item~") do
match = match:trim()
str = gsub(str, match, "(\"~item~\",\"" .. key .. "\",\"reg\")")
end
-- replace all "<priceSource>" occurances with the parameters to TSMAPI:GetItemValue (with _item for the item)
for match in gmatch(" "..str.." ", " "..strlower(key).." ") do
match = match:trim()
str = gsub(str, match, "(\"_item\",\"" .. key .. "\",\"reg\")")
end
end
for key in pairs(TSM.db.global.customPriceSources) do
-- price sources need to have at least 1 capital letter for this algorithm to work, so temporarily give it one
key = strupper(strsub(key, 1, 1))..strsub(key, 2)
-- replace all "<customPriceSource> ~item~" occurances with the parameters to TSMAPI:GetCustomPriceSourceValue (with "~item~" left in for the item)
for match in gmatch(" "..str.." ", " " .. strlower(key) .. " ~item~") do
match = match:trim()
str = gsub(str, match, "(\"~item~\",\"" .. key .. "\",\"custom\")")
end
-- replace all "<customPriceSource>" occurances with the parameters to TSMAPI:GetCustomPriceSourceValue (with _item for the item)
for match in gmatch(" "..str.." ", " " .. strlower(key) .. " ") do
match = match:trim()
str = gsub(str, match, "(\"_item\",\"" .. key .. "\",\"custom\")")
end
-- change custom price sources back to lower case
str = gsub(str, TSMAPI:StrEscape("(\"~item~\",\"" .. key .. "\",\"custom\")"), strlower("(\"~item~\",\"" .. key .. "\",\"custom\")"))
str = gsub(str, TSMAPI:StrEscape("(\"_item\",\"" .. key .. "\",\"custom\")"), strlower("(\"_item\",\"" .. key .. "\",\"custom\")"))
end
-- replace all occurances of "~item~" with the item link
for match in gmatch(str, "~item~") do
local itemString = tremove(items, 1)
if not itemString then return nil, L["Wrong number of item links."] end
str = gsub(str, match, itemString, 1)
end
-- replace any itemValue API calls with a lookup in the 'values' array
local itemValues = {}
for match in gmatch(str, "%(\"([^%)]+)%)") do
local index = #itemValues + 1
itemValues[index] = "{\"" .. match .. "}"
str = gsub(str, TSMAPI:StrEscape("(\"" .. match .. ")"), "values[" .. index .. "]")
end
-- replace "~convert~" appropriately
if convertPriceSource then
tinsert(itemValues, "{\"" .. (convertItem or "_item") .. "\",\"convert\",\"" .. convertPriceSource .. "\"}")
str = gsub(str, "~convert~", "values[" .. #itemValues .. "]")
end
-- replace math functions special custom function names
for word, funcName in pairs(MATH_FUNCTIONS) do
str = gsub(str, word, funcName)
end
-- remove any unused values
for i in ipairs(itemValues) do
if not strfind(" "..str.." ", " values%["..i.."%] ") then
itemValues[i] = "{}"
end
end
-- finally, create and return the function
local funcTemplate = [[
return function(_item)
local NAN = math.huge*0
local origStr = %s
local isTop
if not TSM_PRICE_TEMP.num then
TSM_PRICE_TEMP.num = 0
isTop = true
end
TSM_PRICE_TEMP.num = TSM_PRICE_TEMP.num + 1
if TSM_PRICE_TEMP.num > 100 then
if (TSM_PRICE_TEMP.lastPrint or 0) + 1 < time() then
TSM_PRICE_TEMP.lastPrint = time()
TSM_PRICE_TEMP.loopError(origStr)
end
return
end
local function isNAN(num)
return tostring(num) == tostring(NAN)
end
local function _min(...)
local nums = {...}
for i=#nums, 1, -1 do
if isNAN(nums[i]) then
tremove(nums, i)
end
end
if #nums == 0 then return NAN end
return min(unpack(nums))
end
local function _max(...)
local nums = {...}
for i=#nums, 1, -1 do
if isNAN(nums[i]) then
tremove(nums, i)
end
end
if #nums == 0 then return NAN end
return max(unpack(nums))
end
local function _first(...)
local nums = {...}
for i=1, #nums do
if type(nums[i]) == "number" and not isNAN(nums[i]) then
return nums[i]
end
end
return NAN
end
local function _avg(...)
local nums = {...}
local total, count = 0, 0
for i=#nums, 1, -1 do
if type(nums[i]) == "number" and not isNAN(nums[i]) then
total = total + nums[i]
count = count + 1
end
end
if count == 0 then return NAN end
return floor(total / count + 0.5)
end
local function _check(...)
if select('#', ...) > 3 then return end
local check, ifValue, elseValue = ...
check = check or NAN
ifValue = ifValue or NAN
elseValue = elseValue or NAN
return check > 0 and ifValue or elseValue
end
local values = {}
for i, params in ipairs({%s}) do
local itemString, key, extraParam = unpack(params)
if itemString then
itemString = (itemString == "_item") and _item or itemString
if key == "convert" then
values[i] = TSMAPI:GetConvertCost(itemString, extraParam)
elseif extraParam == "custom" then
values[i] = TSMAPI:GetCustomPriceSourceValue(itemString, key)
else
values[i] = TSMAPI:GetItemValue(itemString, key)
end
values[i] = values[i] or NAN
end
end
local result = floor((%s) + 0.5)
if TSM_PRICE_TEMP.num then
TSM_PRICE_TEMP.num = TSM_PRICE_TEMP.num - 1
end
if isTop then
TSM_PRICE_TEMP.num = nil
end
return not isNAN(result) and result or nil
end
]]
local func, loadErr = loadstring(format(funcTemplate, "\""..origStr.."\"", table.concat(itemValues, ","), str))
if loadErr then
loadErr = gsub(loadErr:trim(), "([^:]+):.", "")
return nil, L["Invalid function."].." Details: "..loadErr
end
local success, func = pcall(func)
if not success then return nil, L["Invalid function."] end
return func
end
local customPriceCache = {}
local badCustomPriceCache = {}
function TSMAPI:ParseCustomPrice(priceString, badPriceSource)
priceString = strlower(tostring(priceString):trim())
if priceString == "" then return nil, L["Empty price string."] end
if badCustomPriceCache[priceString] then return nil, badCustomPriceCache[priceString] end
if customPriceCache[priceString] then return customPriceCache[priceString] end
local func, err = ParsePriceString(priceString, badPriceSource)
if err then
badCustomPriceCache[priceString] = err
return nil, err
end
customPriceCache[priceString] = func
return func
end
function TSMAPI:GetCustomPriceSourceValue(itemString, key)
local source = TSM.db.global.customPriceSources[key]
if not source then return end
local func = TSMAPI:ParseCustomPrice(source)
if not func then return end
return func(itemString)
end
+205
View File
@@ -0,0 +1,205 @@
-- ------------------------------------------------------------------------------ --
-- 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 handled multi-account syncing and communication
-- register this file with Ace libraries
local TSM = select(2, ...)
local Sync = TSM:NewModule("Sync", "AceComm-3.0", "AceEvent-3.0")
TSMAPI.Sync = {}
local private = {callbacks={}, addedFriends={}, invalidPlayers={}}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Sync_private")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
-- Request friend info from the server
ShowFriends()
function Sync:OnEnable()
Sync:RegisterComm("TSMSyncData")
Sync:RegisterEvent("CHAT_MSG_SYSTEM")
local data = {characters={}, accountKey=TSMAPI.Sync:GetAccountKey()}
for name in pairs(TSM.db.factionrealm.characters) do
data.characters[name] = TSMAPI.Sync:GetAccountKey()
end
TSMAPI:CreateTimeDelay("syncSetupDelay", 3, function() TSMAPI.Sync:BroadcastData("TradeSkillMaster", "SETUP", data) end)
end
--Load the libraries
local libS = LibStub:GetLibrary("AceSerializer-3.0")
local libC = LibStub:GetLibrary("LibCompress")
local libCE = libC:GetAddonEncodeTable()
function Sync:OnCommReceived(_, data, _, source)
-- remove realm name from source
source = ("-"):split(source)
-- Decode the compressed data
data = libCE:Decode(data)
-- Decompress the decoded data
local data = libC:Decompress(data)
if not data then return end
-- Deserialize the decompressed data
local success
local tmpData = data
success, data = libS:Deserialize(data)
if not success or not data then return end
-- data is good, do callback
local key = data.__key
local module = data.__module
local account = data.__account
if not key or not module or not private.callbacks[module] then return end
data.__key = nil
data.__module = nil
data.__account = nil
-- make sure we are getting this from a known source
if not TSM.db.factionrealm.syncAccounts[account] and (module ~= "TradeSkillMaster" and not data.isSetup) then return end
private.callbacks[module](key, data, source)
end
function Sync:CHAT_MSG_SYSTEM(_, msg)
if #private.addedFriends == 0 then return end
if msg == ERR_FRIEND_NOT_FOUND then
tremove(private.addedFriends, 1)
else
for i, v in ipairs(private.addedFriends) do
if format(ERR_FRIEND_ADDED_S, v) == msg then
tremove(private.addedFriends, i)
private.invalidPlayers[strlower(v)] = true
end
end
end
end
function TSMAPI.Sync:GetAccountKey()
return TSM.db.factionrealm.accountKey
end
function TSM:RegisterSyncCallback(module, callback)
private.callbacks[module] = callback
end
function private:IsPlayerOnline(target, noAdd)
for i=1, GetNumFriends() do
local name, _, _, _, connected = GetFriendInfo(i)
if name and strlower(name) == strlower(target) then
return connected
end
end
if not noAdd and not private.invalidPlayers[strlower(target)] and GetNumFriends() ~= 50 then
-- add them as a friend
AddFriend(target)
tinsert(private.addedFriends, target)
for i=1, GetNumFriends() do
local name, _, _, _, connected = GetFriendInfo(i)
if name and strlower(name) == strlower(target) then
return connected
end
end
end
end
function TSMAPI.Sync:SendData(module, key, data, target)
local playerOnline = private:IsPlayerOnline(target)
if not playerOnline then return end
data.__module = module
data.__key = key
data.__account = TSMAPI.Sync:GetAccountKey()
-- we will encode using Huffman, LZW, and no compression separately
-- this is to deal with a bug in the compression code
local orig = data
local serialized = libS:Serialize(data)
local encodedData = {}
encodedData[1] = libCE:Encode(libC:CompressHuffman(serialized))
encodedData[2] = libCE:Encode(libC:CompressLZW(serialized))
encodedData[3] = libCE:Encode("\001"..serialized)
-- verify each compresion and pick the shorted valid one
local minIndex = -1
local minLen = math.huge
for i=#encodedData, 1, -1 do
local test = libC:Decompress(libCE:Decode(encodedData[i]))
if test and test == serialized and #encodedData[i] < minLen then
minLen = #encodedData[i]
minIndex = i
end
end
-- sanity check
if not encodedData[minIndex] then error("Could not encode data.") end
-- send off the serialized, compressed, and encoded data
Sync:SendCommMessage("TSMSyncData", encodedData[minIndex], "WHISPER", target)
end
function TSMAPI.Sync:BroadcastData(module, key, data)
for account, players in pairs(TSM.db.factionrealm.syncAccounts) do
if account ~= TSMAPI.Sync:GetAccountKey() then
local sent
for player in pairs(players) do
if private:IsPlayerOnline(player, true) then
TSMAPI.Sync:SendData(module, key, data, player)
sent = true
break
end
end
if not sent then
for player in pairs(players) do
if private:IsPlayerOnline(player) then
TSMAPI.Sync:SendData(module, key, data, player)
sent = true
break
end
end
end
end
end
end
function private:SendSetupData(target, isResponse, isSetup)
local data = {isResponse=isResponse, isSetup=isSetup, characters={}, accountKey=TSMAPI.Sync:GetAccountKey()}
for name in pairs(TSM.db.factionrealm.characters) do
data.characters[name] = true
end
TSMAPI.Sync:SendData("TradeSkillMaster", "SETUP", data, target)
end
function TSM:DoSyncSetup(target)
if target == "" then
private.syncSetupTarget = nil
return
end
private.syncSetupTarget = target
private:SendSetupData(target, nil, true)
end
function TSM:SyncCallback(key, data, source)
if key == "SETUP" then
if (data.isSetup and strlower(source) ~= strlower(private.syncSetupTarget or "")) or (not data.isSetup and not TSM.db.factionrealm.syncAccounts[data.accountKey]) then
return
end
TSMAPI:Verify(data.accountKey ~= TSMAPI.Sync:GetAccountKey(), "It appears that you've manually copied your saved variables between accounts which will cause TSM's automatic sync'ing to not work. You'll need to undo this, and/or delete the TradeSkillMaster, TSM_Crafting, and TSM_ItemTracker saved variables files on both accounts (with WoW closed) in order to fix this.")
TSM.db.factionrealm.syncAccounts[data.accountKey] = data.characters
if data.isSetup then
TSMAPI:CloseFrame()
TSM:Printf(L["Setup account sync'ing with the account which '%s' is on."], source)
end
if data.isResponse then return end -- already responded
private:SendSetupData(source, true, data.isSetup)
end
end
+21
View File
@@ -0,0 +1,21 @@
<Ui>
<Frame name="TSMErrorHandlerTemplate" virtual="true">
<Scripts>
<OnLoad>
self.handler = function(...)
local msg = LibStub("AceAddon-3.0"):GetAddon("TradeSkillMaster"):IsValidError(...)
if msg then
local status, ret = pcall(self.errorHandler, msg, ...)
if status then
return ret
else
self.origErrorHandler(...)
end
elseif self.origErrorHandler then
return self.origErrorHandler(...)
end
end
</OnLoad>
</Scripts>
</Frame>
</Ui>
+113
View File
@@ -0,0 +1,113 @@
-- ------------------------------------------------------------------------------ --
-- 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 code for running stuff in a pseudo-thread
local TSM = select(2, ...)
local private = {frames={}, threads={}}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Threading_private")
TSMAPI.Threading = {}
local STATUS_YIELD = {}
local STATUS_DEAD = {}
local MAX_QUANTUM = 50
local ThreadPrototype = {
Resume = function(self, runTime)
self._endTime = debugprofilestop() + runTime
local noErr, status = coroutine.resume(self._co, self, self._param)
if noErr then
return status
else
-- throw error in delay so we don't kill this execution path
TSMAPI:CreateTimeDelay(0, function() error(status) end)
return STATUS_DEAD
end
end,
Yield = function(self, force)
local currentTime = debugprofilestop()
if force or currentTime > self._endTime then
coroutine.yield(STATUS_YIELD)
end
end,
Sleep = function(self, seconds)
self._sleeping = seconds
coroutine.yield(STATUS_YIELD)
end,
}
local quantums = {}
function private.RunScheduler(_, elapsed)
-- deal with sleeping threads and try and assign requested quantums
local totalTime = min(elapsed * 1000, MAX_QUANTUM)
local usedTime = 0
for i, thread in ipairs(private.threads) do
if thread._sleeping then
thread._sleeping = thread._sleeping - elapsed
if thread._sleeping <= 0 then
thread._sleeping = nil
end
end
quantums[i] = thread._sleeping and 0 or (thread._percent * totalTime)
usedTime = usedTime + quantums[i]
end
-- scale everything down if the total is > the total time
if usedTime > totalTime then
for i=1, #quantums do
quantums[i] = quantums[i] * (totalTime / usedTime)
end
end
-- run the threads that aren't sleeping
for i, thread in ipairs(private.threads) do
if quantums[i] > 0 then
-- resume running for a quantum based on its percent
local status = thread:Resume(quantums[i])
if status ~= STATUS_YIELD then
tremove(private.threads, i)
if thread._callback and status ~= STATUS_DEAD then thread._callback() end
end
end
end
end
function TSMAPI.Threading:Start(func, percent, callback, param)
local thread = CopyTable(ThreadPrototype)
thread._co = coroutine.create(func)
thread._percent = percent
thread._callback = callback
thread._param = param
thread._sleeping = nil
tinsert(private.threads, thread)
end
do
local frame = CreateFrame("Frame")
frame:SetScript("OnUpdate", private.RunScheduler)
end
-- -- EXAMPLE USAGE:
-- local function TestFunc(self, letter)
-- for i = 1, 10 do
-- self:Sleep(1)
-- print(letter, i)
-- if letter == "B" and i == 10 then print(_G[i].nonExistant) end
-- end
-- end
-- function TSMTest()
-- local start = GetTime()
-- TSMAPI.Threading:Start(TestFunc, 1, function() print("DONE", GetTime()-start) end, "A")
-- TSMAPI.Threading:Start(TestFunc, 1, function() print("DONE", GetTime()-start) end, "B")
-- end
+300
View File
@@ -0,0 +1,300 @@
-- ------------------------------------------------------------------------------ --
-- 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 tooltip options
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries
local lib = TSMAPI
local tooltipLib = LibStub("LibExtraTip-1")
local moduleObjects = TSM.moduleObjects
local moduleNames = TSM.moduleNames
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Tooltips_private")
private.tooltipInfo = {}
-- **************************************************************************
-- LibExtraTip Functions
-- **************************************************************************
function TSM:SetupTooltips()
-- tooltipLib:AddCallback({type = "battlepet", callback = private.LoadTooltip})
tooltipLib:AddCallback({type = "item", callback = private.LoadTooltip})
tooltipLib:RegisterTooltip(GameTooltip)
tooltipLib:RegisterTooltip(ItemRefTooltip)
-- tooltipLib:RegisterTooltip(BattlePetTooltip)
local orig = OpenMailAttachment_OnEnter
OpenMailAttachment_OnEnter = function(self, index)
private.lastMailTooltipUpdate = private.lastMailTooltipUpdate or 0
if private.lastMailTooltipIndex ~= index or private.lastMailTooltipUpdate + 0.1 < GetTime() then
private.lastMailTooltipUpdate = GetTime()
private.lastMailTooltipIndex = index
orig(self, index)
end
end
end
local tooltipLines = {lastUpdate = 0, modifier=0}
local function GetTooltipLines(itemString, quantity)
local modifier = (IsShiftKeyDown() and 4 or 0) + (IsAltKeyDown() and 2 or 0) + (IsControlKeyDown() and 1 or 0)
if modifier ~= tooltipLines.modifier then
tooltipLines.modifier = modifier
tooltipLines.lastUpdate = 0
end
if tooltipLines.itemString ~= itemString or tooltipLines.quantity ~= quantity or (tooltipLines.lastUpdate + 0.5) < GetTime() then
wipe(tooltipLines)
for _, moduleName in ipairs(moduleNames) do
if moduleObjects[moduleName].GetTooltip then
local moduleLines = moduleObjects[moduleName]:GetTooltip(itemString, quantity)
if type(moduleLines) ~= "table" then moduleLines = {} end
for _, line in ipairs(moduleLines) do
tinsert(tooltipLines, line)
end
end
end
tooltipLines.itemString = itemString
tooltipLines.quantity = quantity
tooltipLines.lastUpdate = GetTime()
end
return tooltipLines
end
function private.LoadTooltip(tipFrame, link, quantity)
local itemString = TSMAPI:GetItemString(link)
if not itemString then return end
local lines = GetTooltipLines(itemString, quantity)
if #lines > 0 then
tooltipLib:AddLine(tipFrame, " ", 1, 1, 0, TSM.db.profile.embeddedTooltip)
local r, g, b = unpack(TSM.db.profile.design.inlineColors.tooltip or { 130, 130, 250 })
for i = 1, #lines do
if type(lines[i]) == "table" then
tooltipLib:AddDoubleLine(tipFrame, lines[i].left, lines[i].right, r / 255, g / 255, b / 255, r / 255, g / 255, b / 255, TSM.db.profile.embeddedTooltip)
else
tooltipLib:AddLine(tipFrame, lines[i], r / 255, g / 255, b / 255, TSM.db.profile.embeddedTooltip)
end
end
tooltipLib:AddLine(tipFrame, " ", 1, 1, 0, TSM.db.profile.embeddedTooltip)
end
end
-- **************************************************************************
-- TSM Tooltip Options
-- **************************************************************************
function TSM:RegisterTooltipInfo(module, info)
info = CopyTable(info)
info.module = module
tinsert(private.tooltipInfo, info)
end
function TSMAPI:GetMoneyCoinsTooltip()
return TSM.db.profile.moneyCoinsTooltip
end
local loadTooltipOptionsTab
function TSM:LoadTooltipOptions(parent)
local tabs = {}
local next = next
for _, info in ipairs(private.tooltipInfo) do
tinsert(tabs, { text = info.module, value = info.module })
end
if next(tabs) then
sort(tabs, function(a, b)
return a.text < b.text
end)
end
tinsert(tabs, 1, { text = L["General"], value = "Help" })
local tabGroup = AceGUI:Create("TSMTabGroup")
tabGroup:SetLayout("Fill")
tabGroup:SetTabs(tabs)
tabGroup:SetCallback("OnGroupSelected", function(_, _, value)
tabGroup:ReleaseChildren()
if value == "Help" then
private:DrawTooltipHelp(tabGroup)
else
for _, info in ipairs(private.tooltipInfo) do
if info.module == value then
info.callback(tabGroup, loadTooltipOptionsTab and loadTooltipOptionsTab.tooltip)
end
end
end
end)
parent:AddChild(tabGroup)
tabGroup:SelectTab(loadTooltipOptionsTab and loadTooltipOptionsTab.module or "Help")
end
function private:DrawTooltipHelp(container)
local priceSources = TSMAPI:GetPriceSources()
priceSources["Crafting"] = nil
priceSources["VendorBuy"] = nil
priceSources["VendorSell"] = nil
priceSources["Disenchant"] = nil
local page = {
{
-- scroll frame to contain everything
type = "ScrollFrame",
layout = "List",
children = {
{
type = "InlineGroup",
layout = "flow",
title = L["General Options"],
children = {
{
type = "Label",
text = L["Display prices in tooltips as:"],
relativeWidth = 0.25,
},
{
type = "CheckBox",
label = L["Coins:"],
relativeWidth = 0.09,
settingInfo = {TSM.db.profile, "moneyCoinsTooltip"},
callback = function(_, _, value)
if value == true then
TSM.db.profile.moneyTextTooltip = false
end
container:ReloadTab()
end,
},
{
type = "Label",
relativeWidth = 0.22,
text = TSMAPI:FormatTextMoneyIcon(3451267, "|cffffffff", false, true),
},
{
type = "CheckBox",
label = L["Text:"],
relativeWidth = 0.09,
settingInfo = {TSM.db.profile, "moneyTextTooltip"},
callback = function(_, _, value)
if value == true then
TSM.db.profile.moneyCoinsTooltip = false
end
container:ReloadTab()
end,
},
{
type = "Label",
text = TSMAPI:FormatTextMoney(3451267, "|cffffffff", false, true),
},
{
type = "HeadingLine",
},
{
type = "CheckBox",
label = L["Embed TSM Tooltips"],
settingInfo = {TSM.db.profile, "embeddedTooltip"},
tooltip = L["If checked, TSM's tooltip lines will be embedded in the item tooltip. Otherwise, it will show as a separate box below the item's tooltip."],
},
{
type = "CheckBox",
label = L["Display Group / Operation Info in Tooltips"],
settingInfo = {TSM.db.profile, "tooltip"},
},
{
type = "CheckBox",
label = L["Display vendor buy price in tooltip."],
settingInfo = { TSM.db.profile, "vendorBuyTooltip" },
tooltip = L["If checked, the price of buying the item from a vendor is displayed."],
},
{
type = "CheckBox",
label = L["Display vendor sell price in tooltip."],
settingInfo = { TSM.db.profile, "vendorSellTooltip" },
tooltip = L["If checked, the price of selling the item to a vendor displayed."],
},
},
},
{
type = "InlineGroup",
layout = "flow",
title = L["Destroy Values"],
children = {
{
type = "Dropdown",
label = L["Destroy Value Source:"],
settingInfo = {TSM.db.profile, "destroyValueSource"},
list = priceSources,
relativeWidth = 0.5,
tooltip = L["Select the price source for calculating destroy values."],
},
{
type = "CheckBox",
label = L["Display Detailed Destroy Tooltips"],
settingInfo = { TSM.db.profile, "detailedDestroyTooltip" },
relativeWidth = 0.49,
tooltip = L["If checked, a detailed list of items which an item destroys into will be displayed below the destroy value in the tooltip."],
},
{
type = "HeadingLine",
},
{
type = "CheckBox",
label = L["Display mill value in tooltip."],
settingInfo = { TSM.db.profile, "millTooltip" },
relativeWidth = 0.5,
tooltip = L["If checked, the mill value of the item will be shown. This value is calculated using the average market value of materials the item will mill into."],
},
{
type = "CheckBox",
label = L["Display prospect value in tooltip."],
settingInfo = { TSM.db.profile, "prospectTooltip" },
relativeWidth = 0.5,
tooltip = L["If checked, the prospect value of the item will be shown. This value is calculated using the average market value of materials the item will prospect into."],
},
{
type = "CheckBox",
label = L["Display disenchant value in tooltip."],
settingInfo = { TSM.db.profile, "deTooltip" },
relativeWidth = 0.5,
tooltip = L["If checked, the disenchant value of the item will be shown. This value is calculated using the average market value of materials the item will disenchant into."],
},
},
},
},
},
}
if next(TSM.db.global.customPriceSources) then
local inlineGroup = {
type = "InlineGroup",
layout = "flow",
title = L["Custom Price Sources"],
children = {
{
type = "Label",
text = L["Custom price sources to display in item tooltips:"],
relativeWidth = 1,
},
},
}
for name in pairs(TSM.db.global.customPriceSources) do
local checkbox = {
type = "CheckBox",
label = name,
relativeWidth = 0.5,
settingInfo = { TSM.db.global.customPriceTooltips, name },
tooltip = L["If checked, this custom price will be displayed in item tooltips."],
}
tinsert(inlineGroup.children, checkbox)
end
tinsert(page[1].children, inlineGroup)
end
TSMAPI:BuildPage(container, page)
end