928 lines
32 KiB
Lua
928 lines
32 KiB
Lua
|
|
--[[
|
||
|
|
Auctioneer Advanced
|
||
|
|
Version: 5.9.4961 (WhackyWallaby)
|
||
|
|
Revision: $Id: CoreAPI.lua 4933 2010-10-13 17:16:14Z Nechckn $
|
||
|
|
URL: http://auctioneeraddon.com/
|
||
|
|
|
||
|
|
This is an addon for World of Warcraft that adds statistical history to the auction data that is collected
|
||
|
|
when the auction is scanned, so that you can easily determine what price
|
||
|
|
you will be able to sell an item for at auction or at a vendor whenever you
|
||
|
|
mouse-over an item in the game
|
||
|
|
|
||
|
|
License:
|
||
|
|
This program is free software; you can redistribute it and/or
|
||
|
|
modify it under the terms of the GNU General Public License
|
||
|
|
as published by the Free Software Foundation; either version 2
|
||
|
|
of the License, or (at your option) any later version.
|
||
|
|
|
||
|
|
This program is distributed in the hope that it will be useful,
|
||
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
|
GNU General Public License for more details.
|
||
|
|
|
||
|
|
You should have received a copy of the GNU General Public License
|
||
|
|
along with this program(see GPL.txt); if not, write to the Free Software
|
||
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
|
|
||
|
|
Note:
|
||
|
|
This AddOn's source code is specifically designed to work with
|
||
|
|
World of Warcraft's interpreted AddOn system.
|
||
|
|
You have an implicit license to use this AddOn with these facilities
|
||
|
|
since that is its designated purpose as per:
|
||
|
|
http://www.fsf.org/licensing/licenses/gpl-faq.html#InterpreterIncompat
|
||
|
|
]]
|
||
|
|
if not AucAdvanced then return end
|
||
|
|
local AucAdvanced = AucAdvanced
|
||
|
|
local coremodule = AucAdvanced.GetCoreModule("CoreAPI")
|
||
|
|
if not coremodule then return end -- Someone has explicitely broken us
|
||
|
|
|
||
|
|
|
||
|
|
AucAdvanced.API = {}
|
||
|
|
local lib = AucAdvanced.API
|
||
|
|
local private = {}
|
||
|
|
|
||
|
|
lib.Print = AucAdvanced.Print
|
||
|
|
local Const = AucAdvanced.Const
|
||
|
|
local GetFaction = AucAdvanced.GetFaction
|
||
|
|
local GetSetting = AucAdvanced.Settings.GetSetting
|
||
|
|
local DecodeLink = AucAdvanced.DecodeLink
|
||
|
|
local SanitizeLink = AucAdvanced.SanitizeLink
|
||
|
|
|
||
|
|
local tinsert = table.insert
|
||
|
|
local tremove = table.remove
|
||
|
|
local next,pairs,ipairs,type = next,pairs,ipairs,type
|
||
|
|
local wipe = wipe
|
||
|
|
local ceil,floor,max,abs = ceil,floor,max,abs
|
||
|
|
local tostring,tonumber,strjoin,strsplit,format = tostring,tonumber,strjoin,strsplit,format
|
||
|
|
local GetItemInfo = GetItemInfo
|
||
|
|
local time = time
|
||
|
|
-- GLOBALS: nLog, N_NOTICE, N_WARNING, N_ERROR
|
||
|
|
|
||
|
|
|
||
|
|
coremodule.Processors = {}
|
||
|
|
function coremodule.Processors.scanstats()
|
||
|
|
lib.ClearMarketCache()
|
||
|
|
end
|
||
|
|
function coremodule.Processors.configchanged()
|
||
|
|
lib.ClearMarketCache()
|
||
|
|
end
|
||
|
|
function coremodule.Processors.newmodule()
|
||
|
|
private.ClearEngineCache()
|
||
|
|
lib.ClearMarketCache()
|
||
|
|
end
|
||
|
|
|
||
|
|
do
|
||
|
|
local EPSILON = 0.000001;
|
||
|
|
local IMPROVEMENT_FACTOR = 0.8;
|
||
|
|
local CORRECTION_FACTOR = 1000; -- 10 silver per gold, integration steps at tail
|
||
|
|
local FALLBACK_ERROR = 1; -- 1 silver per gold fallback error max
|
||
|
|
|
||
|
|
-- cache[serverKey][itemsig]={value, seen, #stats}
|
||
|
|
local cache = setmetatable({}, { __index = function(tbl,key)
|
||
|
|
tbl[key] = {}
|
||
|
|
return tbl[key]
|
||
|
|
end
|
||
|
|
})
|
||
|
|
local pdfList = {};
|
||
|
|
local engines = {};
|
||
|
|
local ERROR = 0.05;
|
||
|
|
-- local LOWER_INT_LIMIT, HIGHER_INT_LIMIT = -100000, 10000000;
|
||
|
|
--[[
|
||
|
|
This function acquires the current market value of the mentioned item using
|
||
|
|
a configurable algorithm to process the data used by the other installed
|
||
|
|
algorithms.
|
||
|
|
|
||
|
|
The returned value is the most probable value that the item is worth
|
||
|
|
using the algorithms in each of the STAT modules as specified
|
||
|
|
by the GetItemPDF() function.
|
||
|
|
|
||
|
|
AucAdvanced.API.GetMarketValue(itemLink, serverKey)
|
||
|
|
]]
|
||
|
|
function lib.GetMarketValue(itemLink, serverKey)
|
||
|
|
local _;
|
||
|
|
if type(itemLink) == 'number' then _, itemLink = GetItemInfo(itemLink) end
|
||
|
|
if not itemLink then return end
|
||
|
|
|
||
|
|
local cacheSig = lib.GetSigFromLink(itemLink)
|
||
|
|
if not cacheSig then return end -- not a valid item link
|
||
|
|
serverKey = serverKey or GetFaction() -- call GetFaction once here, instead of in every Stat module
|
||
|
|
|
||
|
|
local cacheEntry = cache[serverKey][cacheSig]
|
||
|
|
if cacheEntry then
|
||
|
|
return cacheEntry[1], cacheEntry[2], cacheEntry[3] -- explicit indexing faster than 'unpack' for 3 values
|
||
|
|
end
|
||
|
|
|
||
|
|
ERROR = GetSetting("marketvalue.accuracy");
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
|
||
|
|
local upperLimit, lowerLimit, seen = 0, 1e11, 0;
|
||
|
|
|
||
|
|
if #engines == 0 then
|
||
|
|
-- Rebuild the engine cache
|
||
|
|
local modules = AucAdvanced.GetAllModules(nil, "Stat")
|
||
|
|
for pos, engineLib in ipairs(modules) do
|
||
|
|
local fn = engineLib.GetItemPDF;
|
||
|
|
if fn then
|
||
|
|
tinsert(engines, {pdf = fn, array = engineLib.GetPriceArray});
|
||
|
|
elseif nLog then
|
||
|
|
nLog.AddMessage("Auctioneer", "Market Pricing", N_WARNING, "Missing PDF", "Auctioneer engine '"..engineLib.GetName().."' does not have a GetItemPDF() function. This check will be removed in the near future in favor of faster calls. Implement this function.");
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Run through all of the stat modules and get the PDFs
|
||
|
|
local c, oldPdfMax, total = 0, #pdfList, 0;
|
||
|
|
local convergedFallback = nil;
|
||
|
|
for _, engine in ipairs(engines) do
|
||
|
|
local i, min, max, area = engine.pdf(saneLink, serverKey);
|
||
|
|
|
||
|
|
if type(i) == 'number' then
|
||
|
|
-- This is a fallback
|
||
|
|
if convergedFallback == nil or (type(convergedFallback) == 'number' and abs(convergedFallback - i) < FALLBACK_ERROR * convergedFallback / 10000) then
|
||
|
|
convergedFallback = i;
|
||
|
|
else
|
||
|
|
convergedFallback = false; -- Cannot converge on fallback pricing
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local priceArray = engine.array(saneLink, serverKey);
|
||
|
|
|
||
|
|
if priceArray and (priceArray.seen or 0) > seen then
|
||
|
|
seen = priceArray.seen;
|
||
|
|
end
|
||
|
|
|
||
|
|
if i and type(i) ~= 'number' then -- pdfList[++c] = i;
|
||
|
|
total = total + (area or 1); -- Add total area, assume 1 if not supplied
|
||
|
|
c = c + 1;
|
||
|
|
pdfList[c] = i;
|
||
|
|
if min < lowerLimit then lowerLimit = min; end
|
||
|
|
if max > upperLimit then upperLimit = max; end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Clean out extras if needed
|
||
|
|
for i = c+1, oldPdfMax do
|
||
|
|
pdfList[i] = nil;
|
||
|
|
end
|
||
|
|
|
||
|
|
if #pdfList == 0 and convergedFallback then
|
||
|
|
if nLog then nLog.AddMessage("Auctioneer", "Market Pricing", N_WARNING, "Fallback Pricing Used", "Fallback pricing used due to no available PDFs on item "..itemLink); end
|
||
|
|
return convergedFallback, 1, 1;
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
if not (lowerLimit > -1/0 and upperLimit < 1/0) then
|
||
|
|
error("Invalid bounds detected while pricing "..(GetItemInfo(itemLink) or itemLink)..": "..tostring(lowerLimit).." to "..tostring(upperLimit))
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
-- Determine the totals from the PDFs
|
||
|
|
local delta = (upperLimit - lowerLimit) * .01;
|
||
|
|
|
||
|
|
if #pdfList == 0 or delta < EPSILON or total < EPSILON then
|
||
|
|
return; -- No PDFs available for this item
|
||
|
|
end
|
||
|
|
|
||
|
|
local limit = total/2;
|
||
|
|
local midpoint, lastMidpoint = 0, 0;
|
||
|
|
|
||
|
|
-- Now find the 50% point
|
||
|
|
repeat
|
||
|
|
lastMidpoint = midpoint;
|
||
|
|
total = 0;
|
||
|
|
|
||
|
|
if not(delta > 0) then
|
||
|
|
error("Infinite loop detected during market pricing for "..(GetItemInfo(itemLink) or itemLink))
|
||
|
|
end
|
||
|
|
|
||
|
|
for x = lowerLimit, upperLimit, delta do
|
||
|
|
for i = 1, #pdfList do
|
||
|
|
local val = pdfList[i](x);
|
||
|
|
total = total + val * delta;
|
||
|
|
end
|
||
|
|
|
||
|
|
if total > limit then
|
||
|
|
midpoint = x;
|
||
|
|
break;
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
delta = delta * IMPROVEMENT_FACTOR;
|
||
|
|
|
||
|
|
|
||
|
|
if midpoint ~= midpoint or midpoint == 0 then
|
||
|
|
if nLog and midpoint ~= midpoint then
|
||
|
|
nLog.AddMessage("Auctioneer", "Market Pricing", N_WARNING, "Unable To Calculate", "A NaN value was detected while processing the midpoint for PDF of "..(GetItemInfo(itemLink) or itemLink).."... Giving up.");
|
||
|
|
elseif nLog then
|
||
|
|
nLog.AddMessage("Auctioneer", "Market Pricing", N_NOTICE, "Unable To Calculate", "A zero total was detected while processing the midpoint for PDF of "..(GetItemInfo(itemLink) or itemLink).."... Giving up.");
|
||
|
|
end
|
||
|
|
|
||
|
|
if convergedFallback then
|
||
|
|
if nLog then
|
||
|
|
nLog.AddMessage("Auctioneer", "Market Pricing", N_WARNING, "Fallback Pricing Used", "Fallback pricing used due to NaN/Zero total for item "..itemLink);
|
||
|
|
end
|
||
|
|
return convergedFallback, 1, 1;
|
||
|
|
end
|
||
|
|
return; -- Cannot calculate: NaN
|
||
|
|
end
|
||
|
|
|
||
|
|
until abs(midpoint - lastMidpoint)/midpoint < ERROR;
|
||
|
|
|
||
|
|
if midpoint and midpoint > 0 then
|
||
|
|
midpoint = floor(midpoint + 0.5); -- Round to nearest copper
|
||
|
|
|
||
|
|
-- Cache before finishing up
|
||
|
|
cache[serverKey][cacheSig] = {midpoint, seen, #pdfList}
|
||
|
|
|
||
|
|
return midpoint, seen, #pdfList;
|
||
|
|
else
|
||
|
|
if nLog then
|
||
|
|
nLog.AddMessage("Auctioneer", "Market Pricing", N_WARNING, "Unable To Calculate", "No midpoint was detected for item "..(GetItemInfo(itemLink) or itemLink).."... Giving up.");
|
||
|
|
end
|
||
|
|
return;
|
||
|
|
end
|
||
|
|
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Clear the cache of Stats engines (called if a new module is registered)
|
||
|
|
function private.ClearEngineCache()
|
||
|
|
wipe(engines)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Clears the results cache for AucAdvanced.API.GetMarketValue()
|
||
|
|
function lib.ClearMarketCache()
|
||
|
|
wipe(cache)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.ClearItem(itemLink, serverKey)
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
local modules = AucAdvanced.GetAllModules("ClearItem")
|
||
|
|
for pos, engineLib in ipairs(modules) do
|
||
|
|
engineLib.ClearItem(saneLink, serverKey)
|
||
|
|
end
|
||
|
|
lib.ClearMarketCache()
|
||
|
|
end
|
||
|
|
|
||
|
|
--[[ AucAdvanced.API.IsKeyword(testword [, keyword])
|
||
|
|
Determine whether testword is equal to or an alias of keyword
|
||
|
|
Returns the keyword if it matches, nil otherwise
|
||
|
|
For case-insensitive keywords, tries both unmodified and lowercase
|
||
|
|
Note: default cases must be handled separately
|
||
|
|
--]]
|
||
|
|
do
|
||
|
|
-- allowable keywords (so far): ALL, faction, server
|
||
|
|
local keywords = { -- entry: alias = keyword,
|
||
|
|
ALL = "ALL",
|
||
|
|
faction = "faction",
|
||
|
|
server = "server",
|
||
|
|
realm = "server",
|
||
|
|
}
|
||
|
|
-- todo: functions to add new keywords, and to add new aliases for keywords
|
||
|
|
function lib.IsKeyword(testword, keyword)
|
||
|
|
if type(testword) ~= "string" then return end
|
||
|
|
local key = keywords[testword] or keywords[testword:lower()] -- try unmodified and lowercased
|
||
|
|
if key then
|
||
|
|
if not keyword or keyword == key then
|
||
|
|
return key
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.ClearData(command)
|
||
|
|
local serverKey1, serverKey2, serverKey3
|
||
|
|
|
||
|
|
-- split command into keyword and extra parts
|
||
|
|
local keyword, extra = "faction", "" -- default
|
||
|
|
if type(command) == "string" then
|
||
|
|
local _, ind, key = strfind(command, "(%S+)")
|
||
|
|
if key then
|
||
|
|
key = lib.IsKeyword(key)
|
||
|
|
if key then
|
||
|
|
keyword = key -- recognised keyword
|
||
|
|
extra = strtrim(strsub(command, ind+1))
|
||
|
|
else
|
||
|
|
extra = strtrim(command) -- try to resolve whole command (as a "faction")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
elseif command then -- only valid types are string or nil
|
||
|
|
error("Unrecognised parameter type to ClearData: "..type(command)..":"..tostring(command))
|
||
|
|
end
|
||
|
|
|
||
|
|
-- At this point keyword should be one of the strings in the following if-block
|
||
|
|
-- extra should be a string, where 'no extra information' is denoted by ""
|
||
|
|
if keyword == "ALL" then
|
||
|
|
if extra == "" then serverKey1 = "ALL" end
|
||
|
|
elseif keyword == "server" then
|
||
|
|
if extra == "" then extra = Const.PlayerRealm end
|
||
|
|
-- otherwise assume the user typed the server name correctly
|
||
|
|
-- modules should silently ignore unrecognised serverKeys
|
||
|
|
serverKey1 = extra.."-Alliance"
|
||
|
|
serverKey2 = extra.."-Horde"
|
||
|
|
serverKey3 = extra.."-Neutral"
|
||
|
|
elseif keyword == "faction" then
|
||
|
|
if extra == "" then
|
||
|
|
serverKey1 = GetFaction()
|
||
|
|
elseif AucAdvanced.SplitServerKey(extra) then -- it's a valid serverKey
|
||
|
|
serverKey1 = extra
|
||
|
|
else
|
||
|
|
local fac = AucAdvanced.IsFaction(extra) -- it's a valid faction group
|
||
|
|
if fac then
|
||
|
|
serverKey1 = Const.PlayerRealm.."-"..fac
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
if serverKey1 then
|
||
|
|
local modules = AucAdvanced.GetAllModules("ClearData")
|
||
|
|
for pos, lib in ipairs(modules) do
|
||
|
|
lib.ClearData(serverKey1)
|
||
|
|
if serverKey2 then
|
||
|
|
lib.ClearData(serverKey2)
|
||
|
|
lib.ClearData(serverKey3)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
lib.ClearMarketCache()
|
||
|
|
else
|
||
|
|
lib.Print("Auctioneer: Unrecognized keyword or faction for ClearData {{"..command.."}}")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
function lib.GetAlgorithms(itemLink)
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
local engines = {}
|
||
|
|
local modules = AucAdvanced.GetAllModules()
|
||
|
|
for pos, engineLib in ipairs(modules) do
|
||
|
|
if engineLib.GetPrice or engineLib.GetPriceArray then
|
||
|
|
if not engineLib.IsValidAlgorithm
|
||
|
|
or engineLib.IsValidAlgorithm(saneLink) then
|
||
|
|
local engine = engineLib.GetName()
|
||
|
|
tinsert(engines, engine)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return engines
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.IsValidAlgorithm(algorithm, itemLink)
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
local modules = AucAdvanced.GetAllModules()
|
||
|
|
for pos, engineLib in ipairs(modules) do
|
||
|
|
if engineLib.GetName() == algorithm and (engineLib.GetPrice or engineLib.GetPriceArray) then
|
||
|
|
if engineLib.IsValidAlgorithm then
|
||
|
|
return engineLib.IsValidAlgorithm(saneLink)
|
||
|
|
end
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
--store the last data request and just return a cache value for the next 5 secs (5 secs is just arbitrary)
|
||
|
|
local LastAlgorithmSig, LastAlgorithmTime, LastAlgorithmPrice, LastAlgorithmSeen, LastAlgorithmArray
|
||
|
|
function lib.GetAlgorithmValue(algorithm, itemLink, serverKey, reserved)
|
||
|
|
if (not algorithm) then
|
||
|
|
if nLog then nLog.AddMessage("Auctioneer", "API", N_ERROR, "Incorrect Usage", "No pricing algorithm supplied to GetAlgorithmValue") end
|
||
|
|
return
|
||
|
|
end
|
||
|
|
if type(itemLink) == "number" then
|
||
|
|
local _
|
||
|
|
_, itemLink = GetItemInfo(itemLink)
|
||
|
|
end
|
||
|
|
if (not itemLink) then
|
||
|
|
if nLog then nLog.AddMessage("Auctioneer", "API", N_ERROR, "Incorrect Usage", "No itemLink supplied to GetAlgorithmValue") end
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
if reserved then
|
||
|
|
lib.ShowDeprecationAlert("AucAdvanced.API.GetAlgorithmValue(algorithm, itemLink, serverKey)",
|
||
|
|
"The 'faction' and 'realm' parameters are deprecated in favor of the new 'serverKey' parameter. Use this instead."
|
||
|
|
);
|
||
|
|
|
||
|
|
serverKey = reserved.."-"..serverKey;
|
||
|
|
end
|
||
|
|
serverKey = serverKey or GetFaction()
|
||
|
|
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
--check if this was just retrieved and return that value
|
||
|
|
local algosig = strjoin(":", algorithm, saneLink, serverKey)
|
||
|
|
if algosig == LastAlgorithmSig and LastAlgorithmTime + 5 > time() then
|
||
|
|
return LastAlgorithmPrice, LastAlgorithmSeen, LastAlgorithmArray
|
||
|
|
end
|
||
|
|
|
||
|
|
local modules = AucAdvanced.GetAllModules()
|
||
|
|
for pos, engineLib in ipairs(modules) do
|
||
|
|
if engineLib.GetName() == algorithm and (engineLib.GetPrice or engineLib.GetPriceArray) then
|
||
|
|
if engineLib.IsValidAlgorithm
|
||
|
|
and not engineLib.IsValidAlgorithm(saneLink) then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
local price, seen, array
|
||
|
|
if (engineLib.GetPriceArray) then
|
||
|
|
array = engineLib.GetPriceArray(saneLink, serverKey)
|
||
|
|
if (array) then
|
||
|
|
price = array.price
|
||
|
|
seen = array.seen
|
||
|
|
end
|
||
|
|
else
|
||
|
|
price = engineLib.GetPrice(saneLink, serverKey)
|
||
|
|
end
|
||
|
|
LastAlgorithmSig = algosig
|
||
|
|
LastAlgorithmTime = time()
|
||
|
|
LastAlgorithmPrice, LastAlgorithmSeen, LastAlgorithmArray = price, seen, array
|
||
|
|
return price, seen, array
|
||
|
|
end
|
||
|
|
end
|
||
|
|
--error(("Cannot find pricing algorithm: %s"):format(algorithm))
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
--[[ resultsTable = AucAdvanced.API.QueryImage(queryTable, serverKey, reserved, ...)
|
||
|
|
'queryTable' specifies the query to perform
|
||
|
|
'serverKey' defaults to the current faction
|
||
|
|
'reserved' must always be nil
|
||
|
|
The working code can be viewed in CoreScan.lua for more details.
|
||
|
|
--]]
|
||
|
|
lib.QueryImage = AucAdvanced.Scan.QueryImage
|
||
|
|
|
||
|
|
-- unpackedTable = AucAdvanced.API.UnpackImageItem(imageItem)
|
||
|
|
-- imageItem is one of the values (subtables) in the table returned by QueryImage or GetImageCopy
|
||
|
|
lib.UnpackImageItem = AucAdvanced.Scan.UnpackImageItem
|
||
|
|
|
||
|
|
-- scanStatsTable = AucAdvanced.API.GetScanStats(serverKey)
|
||
|
|
-- Timestamps: scanstats.LastScan, scanstats.LastFullScan, scanstats.ImageUpdated
|
||
|
|
-- Scan statistics subtables: scanstats[0] (last scan), scanstats[1], scanstats[2] (two scans prior to last scan)
|
||
|
|
lib.GetScanStats = AucAdvanced.Scan.GetScanStats
|
||
|
|
|
||
|
|
-- imageTable = AucAdvanced.API.GetImageCopy(serverKey)
|
||
|
|
-- Generates an independent copy of the current scan data image for the specified serverKey
|
||
|
|
lib.GetImageCopy = AucAdvanced.Scan.GetImageCopy
|
||
|
|
|
||
|
|
function lib.ListUpdate()
|
||
|
|
if lib.IsBlocked() then return end
|
||
|
|
AucAdvanced.SendProcessorMessage("listupdate")
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.BlockUpdate(block, propagate)
|
||
|
|
local blocked
|
||
|
|
if block == true then
|
||
|
|
blocked = true
|
||
|
|
private.isBlocked = true
|
||
|
|
AuctionFrameBrowse:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
|
||
|
|
else
|
||
|
|
blocked = false
|
||
|
|
private.isBlocked = nil
|
||
|
|
AuctionFrameBrowse:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
|
||
|
|
end
|
||
|
|
|
||
|
|
if (propagate) then
|
||
|
|
AucAdvanced.SendProcessorMessage("blockupdate", blocked)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.IsBlocked()
|
||
|
|
return private.isBlocked == true
|
||
|
|
end
|
||
|
|
--[[Progress bars that are usable by any addon.
|
||
|
|
name = string - unique bar name
|
||
|
|
value = 0-100 the % the bar should be filled
|
||
|
|
show = boolean true will keep bar displayed, false will hide the bar and free it for use by another addon
|
||
|
|
text = string - the text to display on the bar
|
||
|
|
options = table containing formatting commands.
|
||
|
|
options.barColor = { R,G,B, A} red, green, blue, alpha values.
|
||
|
|
options.textColor = { R,G,B, A} red, green, blue, alpha values.
|
||
|
|
|
||
|
|
value, text, color, and options are all optional variables
|
||
|
|
]]
|
||
|
|
local availableBars = {}
|
||
|
|
local NumGenericBars = 0
|
||
|
|
--generate new bars as needed
|
||
|
|
local function newBar()
|
||
|
|
local bar = CreateFrame("STATUSBAR", nil, UIParent, "TextStatusBar")
|
||
|
|
bar:SetWidth(300)
|
||
|
|
bar:SetHeight(18)
|
||
|
|
bar:SetBackdrop({
|
||
|
|
bgFile="Interface\\Tooltips\\UI-Tooltip-Background",
|
||
|
|
edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
|
||
|
|
tile=1, tileSize=10, edgeSize=10,
|
||
|
|
insets={left=1, right=1, top=1, bottom=1}
|
||
|
|
})
|
||
|
|
|
||
|
|
bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
|
||
|
|
bar:SetStatusBarColor(0.6,0,0,0.6)
|
||
|
|
bar:SetMinMaxValues(0,100)
|
||
|
|
bar:SetValue(50)
|
||
|
|
bar:Hide()
|
||
|
|
|
||
|
|
bar.text = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||
|
|
bar.text:SetPoint("CENTER", bar, "CENTER")
|
||
|
|
bar.text:SetJustifyH("CENTER")
|
||
|
|
bar.text:SetJustifyV("CENTER")
|
||
|
|
bar.text:SetTextColor(1,1,1)
|
||
|
|
|
||
|
|
if NumGenericBars < 1 then
|
||
|
|
bar:SetPoint("CENTER", UIParent, "CENTER", -5,5)
|
||
|
|
else--attach to previous bar
|
||
|
|
bar:SetPoint("BOTTOM", lib["GenericProgressBar"..NumGenericBars], "TOP", 0, 0)
|
||
|
|
end
|
||
|
|
NumGenericBars = NumGenericBars + 1
|
||
|
|
lib["GenericProgressBar"..(NumGenericBars)] = bar
|
||
|
|
return NumGenericBars
|
||
|
|
end
|
||
|
|
--create 1 bar to start for anchoring
|
||
|
|
newBar()
|
||
|
|
-- handles the rendering
|
||
|
|
local function renderBars(ID, name, value, text, options)
|
||
|
|
local self = lib["GenericProgressBar"..ID]
|
||
|
|
if not self then assert("No bar found available for ID", ID, name, text) end
|
||
|
|
|
||
|
|
--reset all generated bars that are not inuse to defaults
|
||
|
|
if self and not name then
|
||
|
|
self:Hide()
|
||
|
|
self.text:SetText("")
|
||
|
|
self:SetStatusBarColor(0.6, 0, 0, 0.6) --light red color
|
||
|
|
self.text:SetTextColor(1, 1, 1, 1)
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
self:Show()
|
||
|
|
--update progress
|
||
|
|
if value then
|
||
|
|
self:SetValue(value)
|
||
|
|
end
|
||
|
|
--change bars text if desired
|
||
|
|
if text then
|
||
|
|
self.text:SetText(text)
|
||
|
|
end
|
||
|
|
--[[options is a table that contains, "tweaks" ie text or bar color changes
|
||
|
|
Nothing below this line will be processed unless an options table is passed]]
|
||
|
|
if not options or type(options) ~= "table" then return end
|
||
|
|
|
||
|
|
--change bars color
|
||
|
|
local barColor = options.barColor
|
||
|
|
if barColor then
|
||
|
|
local r, g, b, a = barColor[1],barColor[2], barColor[3], barColor[4]
|
||
|
|
if r and g and b then
|
||
|
|
a = a or 0.6
|
||
|
|
self:SetStatusBarColor(r, g, b, a)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
--change text color
|
||
|
|
local textColor = options.textColor
|
||
|
|
if textColor then
|
||
|
|
local r, g, b, a = textColor[1],textColor[2], textColor[3], textColor[4]
|
||
|
|
if r and g and b then
|
||
|
|
a = a or 1
|
||
|
|
self.text:SetTextColor(r, g, b, a)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
--main entry point. Handles which bar will be assigned and recycling bars
|
||
|
|
function lib.ProgressBars(name, value, show, text, options)
|
||
|
|
--setup parent so we can display even if AH is closed
|
||
|
|
if AuctionFrame and AuctionFrame:IsShown() then
|
||
|
|
lib.GenericProgressBar1:SetParent(AuctionFrame)
|
||
|
|
lib.GenericProgressBar1:SetPoint("TOPRIGHT", AuctionFrame, "TOPRIGHT", -5, 5)
|
||
|
|
else
|
||
|
|
lib.GenericProgressBar1:SetParent(UIParent)
|
||
|
|
end
|
||
|
|
if not name then return end
|
||
|
|
|
||
|
|
--find a generic bar available for use
|
||
|
|
local ID = availableBars[name]
|
||
|
|
if show and not ID then --find a bar
|
||
|
|
for i = 1, NumGenericBars do
|
||
|
|
if not availableBars[i] then
|
||
|
|
availableBars[i] = {name, value, text, options}
|
||
|
|
availableBars[name] = i
|
||
|
|
ID = i
|
||
|
|
break
|
||
|
|
end
|
||
|
|
end
|
||
|
|
--no bar available make a new one
|
||
|
|
if not ID then
|
||
|
|
ID = newBar()
|
||
|
|
availableBars[ID] = {name, value, text, options}
|
||
|
|
availableBars[name] = ID
|
||
|
|
end
|
||
|
|
end
|
||
|
|
--Render Bars
|
||
|
|
if show then
|
||
|
|
renderBars(ID, name, value, text, options)
|
||
|
|
else
|
||
|
|
table.remove(availableBars, ID)
|
||
|
|
availableBars[name] = nil
|
||
|
|
--ReRender bars
|
||
|
|
for ID = 1, NumGenericBars do
|
||
|
|
if availableBars[ID] then
|
||
|
|
barData = availableBars[ID]
|
||
|
|
renderBars(ID, barData[1], barData[2], barData[3], barData[4])
|
||
|
|
else--blank bars
|
||
|
|
renderBars(ID)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--[[ Market matcher APIs ]]--
|
||
|
|
|
||
|
|
function lib.GetBestMatch(itemLink, algorithm, serverKey, reserved)
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
|
||
|
|
if reserved then
|
||
|
|
lib.ShowDeprecationAlert("AucAdvanced.API.GetBestMatch(itemLink, algorithm, serverKey)",
|
||
|
|
"The 'realm' and 'faction' parameters have been removed in favor of a single "..
|
||
|
|
"variable 'serverKey' which should be used in the future."
|
||
|
|
);
|
||
|
|
|
||
|
|
serverKey = reserved.."-"..serverKey;
|
||
|
|
end
|
||
|
|
|
||
|
|
-- TODO: Make a configurable algorithm.
|
||
|
|
-- This algorithm is currently less than adequate.
|
||
|
|
|
||
|
|
local price
|
||
|
|
if algorithm == "market" then
|
||
|
|
price = lib.GetMarketValue(saneLink, serverKey)
|
||
|
|
elseif type(algorithm) == "string" then
|
||
|
|
price = lib.GetAlgorithmValue(algorithm, saneLink, serverKey)
|
||
|
|
else
|
||
|
|
price = algorithm
|
||
|
|
end
|
||
|
|
if not price then return end
|
||
|
|
|
||
|
|
local matchers = lib.GetMatchers(saneLink)
|
||
|
|
local total, count, diff = 0, 0, 0
|
||
|
|
local infoString = ""
|
||
|
|
|
||
|
|
for index, matcher in ipairs(matchers) do
|
||
|
|
if lib.IsValidMatcher(matcher, saneLink) then -- todo: shoudn't this already be valid from calling lib.GetMatchers(saneLink) ?
|
||
|
|
local value, MatchpriceArray = lib.GetMatcherValue(matcher, saneLink, price, serverKey)
|
||
|
|
price = value
|
||
|
|
count = count + 1
|
||
|
|
diff = diff + MatchpriceArray.diff
|
||
|
|
if MatchpriceArray.returnstring then
|
||
|
|
infoString = infoString.."\n"..MatchpriceArray.returnstring -- using two .. is faster than calling strjoin
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
if count > 1 then
|
||
|
|
diff = diff / count
|
||
|
|
end
|
||
|
|
|
||
|
|
if price > 0 then
|
||
|
|
return price, total, count, diff, infoString
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.GetMatcherDropdownList()
|
||
|
|
private.matcherlist = GetSetting("matcherlist")
|
||
|
|
if not private.matcherlist or #private.matcherlist == 0 then
|
||
|
|
lib.GetMatchers()
|
||
|
|
end
|
||
|
|
if not private.matcherlist or #private.matcherlist == 0 then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
local dropdownlist = {}
|
||
|
|
for index, value in ipairs(private.matcherlist) do
|
||
|
|
dropdownlist[index] = tostring(index)..": "..tostring(private.matcherlist[index])
|
||
|
|
end
|
||
|
|
return dropdownlist
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.GetMatchers(itemLink)
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
private.matcherlist = GetSetting("matcherlist")
|
||
|
|
local engines = {}
|
||
|
|
local modules = AucAdvanced.GetAllModules()
|
||
|
|
for pos, engineLib in ipairs(modules) do
|
||
|
|
if engineLib.GetMatchArray then
|
||
|
|
if not engineLib.IsValidMatcher
|
||
|
|
or engineLib.IsValidMatcher(saneLink) then
|
||
|
|
local engine = engineLib.GetName()
|
||
|
|
tinsert(engines, engine)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
local insetting = false
|
||
|
|
local stillactive = false
|
||
|
|
--check to see if there are any new matchers. If so, add them to the end of the running order.
|
||
|
|
--There is no check to see if matchers are missing, as this would destroy the saved order. Instead, invalid matchers can be called without errors.
|
||
|
|
if private.matcherlist then
|
||
|
|
for index, matcher in ipairs(engines) do
|
||
|
|
for i, j in ipairs(private.matcherlist) do
|
||
|
|
if matcher == j then insetting = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
if not insetting then
|
||
|
|
AucAdvanced.Print("AucAdvanced: New matcher found: "..tostring(matcher))
|
||
|
|
tinsert(private.matcherlist, matcher)
|
||
|
|
end
|
||
|
|
insetting = false
|
||
|
|
end
|
||
|
|
else
|
||
|
|
private.matcherlist = engines
|
||
|
|
end
|
||
|
|
AucAdvanced.Settings.SetSetting("matcherlist", private.matcherlist)
|
||
|
|
return private.matcherlist
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.IsValidMatcher(matcher, itemLink)
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
local engines = {}
|
||
|
|
local modules = AucAdvanced.GetAllModules()
|
||
|
|
for pos, engineLib in ipairs(modules) do
|
||
|
|
local engine = engineLib.GetName()
|
||
|
|
if engine == matcher and engineLib.GetMatchArray then
|
||
|
|
if engineLib.IsValidMatcher then
|
||
|
|
return engineLib.IsValidMatcher(saneLink)
|
||
|
|
end
|
||
|
|
return engineLib
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
function lib.GetMatcherValue(matcher, itemLink, price, serverKey)
|
||
|
|
local saneLink = SanitizeLink(itemLink)
|
||
|
|
if (type(matcher) == "string") then
|
||
|
|
matcher = lib.IsValidMatcher(matcher, saneLink)
|
||
|
|
end
|
||
|
|
if not matcher then return end
|
||
|
|
--If matcher is not a table at this point, the following code will throw an "attempt to index a <something> value" type error
|
||
|
|
local matchArray = matcher.GetMatchArray(saneLink, price, serverKey)
|
||
|
|
if not matchArray then
|
||
|
|
matchArray = {}
|
||
|
|
matchArray.value = price
|
||
|
|
matchArray.diff = 0
|
||
|
|
end
|
||
|
|
|
||
|
|
return matchArray.value, matchArray
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
-- Signature conversion functions
|
||
|
|
|
||
|
|
-- Creates an AucAdvanced signature from an item link
|
||
|
|
function lib.GetSigFromLink(link)
|
||
|
|
local sig
|
||
|
|
local itype, id, suffix, factor, enchant = DecodeLink(link)
|
||
|
|
if itype == "item" then
|
||
|
|
if enchant ~= 0 then
|
||
|
|
sig = ("%d:%d:%d:%d"):format(id, suffix, factor, enchant)
|
||
|
|
elseif factor ~= 0 then
|
||
|
|
sig = ("%d:%d:%d"):format(id, suffix, factor)
|
||
|
|
elseif suffix ~= 0 then
|
||
|
|
sig = ("%d:%d"):format(id, suffix)
|
||
|
|
else
|
||
|
|
sig = tostring(id)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return sig
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Creates an item link from an AucAdvanced signature
|
||
|
|
function lib.GetLinkFromSig(sig)
|
||
|
|
local id, suffix, factor, enchant = strsplit(":", sig)
|
||
|
|
|
||
|
|
local itemstring = format("item:%d:%d:0:0:0:0:%d:%d:0", id, enchant or 0, suffix or 0, factor or 0)
|
||
|
|
local name, link = GetItemInfo(itemstring)
|
||
|
|
link = SanitizeLink(link)
|
||
|
|
return link, name -- name is ignored by most calls
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Decodes an AucAdvanced signature into numerical values
|
||
|
|
-- Can be compared to the return values from DecodeLink
|
||
|
|
function lib.DecodeSig(sig)
|
||
|
|
if type(sig) ~= "string" then return end
|
||
|
|
local id, suffix, factor, enchant = strsplit(":", sig)
|
||
|
|
id = tonumber(id)
|
||
|
|
if not id or id == 0 then return end
|
||
|
|
suffix = tonumber(suffix) or 0
|
||
|
|
factor = tonumber(factor) or 0
|
||
|
|
enchant = tonumber(enchant) or 0
|
||
|
|
return id, suffix, factor, enchant
|
||
|
|
end
|
||
|
|
|
||
|
|
-------------------------------------------------------------------------------
|
||
|
|
-- Statistical devices created by Matthew 'Shirik' Del Buono
|
||
|
|
-- For Auctioneer
|
||
|
|
-------------------------------------------------------------------------------
|
||
|
|
local sqrtpi = math.sqrt(math.pi);
|
||
|
|
local sqrtpiinv = 1/sqrtpi;
|
||
|
|
local sq2pi = math.sqrt(2*math.pi);
|
||
|
|
local pi = math.pi;
|
||
|
|
local exp = math.exp;
|
||
|
|
local bellCurveMeta = {
|
||
|
|
__index = {
|
||
|
|
SetParameters = function(self, mean, stddev)
|
||
|
|
if (stddev == 0) then
|
||
|
|
error("Standard deviation cannot be zero");
|
||
|
|
elseif (stddev ~= stddev) then
|
||
|
|
error("Standard deviation must be a real number");
|
||
|
|
end
|
||
|
|
if stddev < .1 then --need to prevent obsurdly small stddevs like 1e-11, as they cause freeze-ups
|
||
|
|
stddev = .1
|
||
|
|
end
|
||
|
|
self.mean = mean;
|
||
|
|
self.stddev = stddev;
|
||
|
|
self.param1 = 1/(stddev*sq2pi); -- Make __call a little faster where we can
|
||
|
|
self.param2 = 2*stddev^2;
|
||
|
|
end
|
||
|
|
},
|
||
|
|
-- Simple bell curve call
|
||
|
|
__call = function(self, x)
|
||
|
|
local n = self.param1*exp(-(x-self.mean)^2/self.param2);
|
||
|
|
-- if n ~= n then
|
||
|
|
-- DEFAULT_CHAT_FRAME:AddMessage("-----------------");
|
||
|
|
-- DevTools_Dump{param1 = self.param1, param2 = self.param2, x = x, mean = self.mean, stddev = self.stddev, exp = exp(-(x-self.mean)^2/self.param2)};
|
||
|
|
-- error(x.." produced NAN ("..tostring(n)..")");
|
||
|
|
-- end
|
||
|
|
return n;
|
||
|
|
end
|
||
|
|
}
|
||
|
|
-------------------------------------------------------------------------------
|
||
|
|
-- Creates a bell curve object that can then be manipulated to pass
|
||
|
|
-- as a PDF function. This is a recyclable object -- the mean and
|
||
|
|
-- standard deviation can be updated as necessary so that it does not have
|
||
|
|
-- to be regenerated
|
||
|
|
--
|
||
|
|
-- Note: This creates a bell curve with a standard deviation of 1 and
|
||
|
|
-- mean of 0. You will probably want to update it to your own desired
|
||
|
|
-- values by calling return:SetParameters(mean, stddev)
|
||
|
|
-------------------------------------------------------------------------------
|
||
|
|
function lib.GenerateBellCurve()
|
||
|
|
return setmetatable({mean=0, stddev=1, param1=sqrtpiinv, param2=2}, bellCurveMeta);
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Dumps out market pricing information for debugging. Only handles bell curves for now.
|
||
|
|
function lib.DumpMarketPrice(itemLink, serverKey)
|
||
|
|
local modules = AucAdvanced.GetAllModules(nil, "Stat");
|
||
|
|
for pos, engineLib in ipairs(modules) do
|
||
|
|
local success, result = pcall(engineLib.GetItemPDF, itemLink, serverKey);
|
||
|
|
if success then
|
||
|
|
if getmetatable(result) == bellCurveMeta then
|
||
|
|
print(engineLib.GetName() .. ": Mean = " .. result.mean .. ", Standard Deviation = " .. result.stddev);
|
||
|
|
else
|
||
|
|
print(engineLib.GetName() .. ": Non-Standard PDF: " .. tostring(result));
|
||
|
|
end
|
||
|
|
else
|
||
|
|
print(engineLib.GetName() .. ": Reported error: " .. tostring(result));
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--[[===========================================================================
|
||
|
|
--|| Deprecation Alert Functions
|
||
|
|
--||=========================================================================]]
|
||
|
|
do
|
||
|
|
local SOURCE_PATTERN = "([^\\/:]+:%d+): in function `([^\"']+)[\"']";
|
||
|
|
local seenCalls = {};
|
||
|
|
local uid = 0;
|
||
|
|
|
||
|
|
-------------------------------------------------------------------------------
|
||
|
|
-- Shows a deprecation alert. Indicates that a deprecated function has
|
||
|
|
-- been called and provides a stack trace that can be used to help
|
||
|
|
-- find the culprit.
|
||
|
|
-- @param replacementName (Optional) The displayable name of the replacement function
|
||
|
|
-- @param comments (Optional) Any extra text to display
|
||
|
|
-------------------------------------------------------------------------------
|
||
|
|
function lib.ShowDeprecationAlert(replacementName, comments)
|
||
|
|
local caller, source, functionName =
|
||
|
|
debugstack(3):match(SOURCE_PATTERN), -- Keep in mind this will be truncated to only the first in the tuple
|
||
|
|
debugstack(2):match(SOURCE_PATTERN); -- This will give us both the source and the function name
|
||
|
|
|
||
|
|
functionName = functionName .. "()";
|
||
|
|
|
||
|
|
-- Check for this source & caller combination
|
||
|
|
seenCalls[source] = seenCalls[source] or {};
|
||
|
|
if not seenCalls[source][caller] then
|
||
|
|
-- Not warned yet, so warn them!
|
||
|
|
seenCalls[source][caller]=true
|
||
|
|
-- Display it
|
||
|
|
AucAdvanced.Print(
|
||
|
|
"Auctioneer: "..
|
||
|
|
functionName .. " has been deprecated and was called by |cFF9999FF"..caller:match("^(.+)%.[lLxX][uUmM][aAlL]:").."|r. "..
|
||
|
|
(replacementName and ("Please use "..replacementName.." instead. ") or "")..
|
||
|
|
(comments or "")
|
||
|
|
);
|
||
|
|
geterrorhandler()(
|
||
|
|
"Deprecated function call occurred in Auctioneer API:\n {{{Deprecated Function:}}} "..functionName..
|
||
|
|
"\n {{{Source Module:}}} "..source:match("^(.+)%.[lLxX][uUmM][aAlL]:")..
|
||
|
|
"\n {{{Calling Module:}}} "..caller:match("^(.+)%.[lLxX][uUmM][aAlL]:")..
|
||
|
|
"\n {{{Available Replacement:}}} "..(replacementName or "None")..
|
||
|
|
(comments and "\n\n"..comments or "")
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
end
|
||
|
|
|
||
|
|
end
|
||
|
|
|
||
|
|
AucAdvanced.RegisterRevision("$URL: http://svn.norganna.org/auctioneer/branches/5.9/Auc-Advanced/CoreAPI.lua $", "$Rev: 4933 $")
|