AuctioneerSuite/Auc-ScanData/ScanData.lua

546 lines
16 KiB
Lua
Raw Permalink Normal View History

2026-04-13 17:48:13 -04:00
--[[
Auctioneer - ScanData
Version: 5.9.4961 (WhackyWallaby)
Revision: $Id: ScanData.lua 4840 2010-08-04 21:44:00Z 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 libType, libName = "Util", "ScanData"
local lib,parent,private = AucAdvanced.NewModule(libType, libName)
if not lib then return end
local DATABASE_VERSION = 1.3
local INTERFACE_VERSION = "A" -- must match CoreScan's SCANDATA_VERSION
local aucPrint,decode,_,_,replicate,empty,get,set,default,debugPrint,fill = AucAdvanced.GetModuleLocals()
private.distributionCache = {}
private.worthCache = {}
local Const = AucAdvanced.Const
local QueryImage = AucAdvanced.API.QueryImage
local PriceCalcLevel = AucAdvanced.Modules.Util.PriceLevel and AucAdvanced.Modules.Util.PriceLevel.CalcLevel
local type = type
local pairs = pairs
local format = format
local floor = floor
local tostring, strjoin = tostring, strjoin
local tinsert, tremove, tconcat, unpack, wipe = tinsert, tremove, table.concat, unpack, wipe
local colorDist = {
exact = { red=0, orange=0, yellow=0, green=0, blue=0 },
suffix = { red=0, orange=0, yellow=0, green=0, blue=0 },
base = { red=0, orange=0, yellow=0, green=0, blue=0 },
stack = { },
all = { red=0, orange=0, yellow=0, green=0, blue=0 },
}
--[[ MODULE FUNCTIONS ]]--
lib.Processors = {}
--[[
function lib.Processors.tooltip(callbackType, ...)
private.ProcessTooltip(...)
end
--]]
function lib.Processors.scanstats()
wipe(private.distributionCache)
wipe(private.worthCache)
end
function lib.Processors.load(callbackType, addon)
if addon == "auc-scandata" then
if private.OnLoad then
private.OnLoad()
end
end
end
local tmp = {}
function lib.Colored(doIt, counts, alt, shorten)
local n=0
if (counts.blue > 0) then
n=n+1
if shorten and counts.blue>=1000 then
tmp[n] = format("|cff3399ff%dk|r", floor(counts.blue/1000+0.5))
else
tmp[n] = format("|cff3399ff%d|r", counts.blue)
end
end
if (counts.green > 0) then
n=n+1
if shorten and counts.green>=1000 then
tmp[n] = format("|cff33ff44%dk|r", floor(counts.green/1000+0.5))
else
tmp[n] = format("|cff33ff44%d|r", counts.green)
end
end
if (counts.yellow > 0) then
n=n+1
if shorten and counts.yellow>=1000 then
tmp[n] = format("|cffffff00%dk|r", floor(counts.yellow/1000+0.5))
else
tmp[n] = format("|cffffff00%d|r", counts.yellow)
end
end
if (counts.orange > 0) then
n=n+1
if shorten and counts.orange>=1000 then
tmp[n] = format("|cffff9900%dk|r", floor(counts.orange/1000+0.5))
else
tmp[n] = format("|cffff9900%d|r", counts.orange)
end
end
if (counts.red > 0) then
n=n+1
if shorten and counts.red>=1000 then
tmp[n] = format("|cffff0000%dk|r", floor(counts.red/1000+0.5))
else
tmp[n] = format("|cffff0000%d|r", counts.red)
end
end
local text = tconcat(tmp, " / ", 1, n)
if alt then
if text and text ~= "" then
text = "( "..text.." )"
else
text = alt
end
end
return text
end
local query3 = {} -- 3 fields itemId, suffix & factor
function lib.GetImageCounts(hyperlink, maxPrice, items, serverKey)
if type(hyperlink) == "number" then
query3.itemId = hyperlink
query3.suffix = 0
query3.factor = 0
else
local iType, iID, iSuffix, iFactor = decode(hyperlink)
if iType == "item" then
query3.itemId = iID
query3.suffix = iSuffix
query3.factor = iFactor
else
return
end
end
local image = QueryImage(query3, serverKey)
local totalBid, totalBuy = 0, 0
for i=1, #image do
local item = image[i]
local count = item[Const.COUNT]
local bid = item[Const.PRICE]
local buy = item[Const.BUYOUT]
local matched = false
if maxPrice then
if buy > 0 and buy <= maxPrice then
totalBuy = totalBuy + count
matched = true
elseif bid <= maxPrice then
totalBid = totalBid + count
matched = true
end
else
if buy > 0 then
totalBuy = totalBuy + count
matched = true
else
totalBid = totalBid + count
matched = true
end
end
if items and matched then
tinsert(items, item)
end
end
return totalBuy, totalBid
end
local query1 = {} -- only 1 field itemId
function lib.GetDistribution(hyperlink)
local iType, iID, iSuffix, iFactor = decode(hyperlink)
if iType ~= "item" then return end
local sig = strjoin(":", iID, iSuffix, iFactor)
if private.distributionCache[sig] then return unpack(private.distributionCache[sig]) end
local exact, suffix, base, myColors = 0,0,0,{}
for k,v in pairs(colorDist) do
myColors[k] = {}
for c,n in pairs(v) do
myColors[k][c] = 0
end
end
query1.itemId = iID
local image = QueryImage(query1)
local sigTemplate = iID..":%d:%d"
for i=1, #image do
local item = image[i]
local vSuffix = item[Const.SUFFIX]
local vFactor = item[Const.FACTOR]
local vCount = item[Const.COUNT]
local vColor
if (PriceCalcLevel) then
local _
local vLink = item[Const.LINK]
local vBid = item[Const.PRICE]
local vBuy = item[Const.BUYOUT]
local vSig = sigTemplate:format(vSuffix, vFactor)
_,_,_,_,_, vColor, private.worthCache[vSig] = PriceCalcLevel(vLink, vCount, vBid, vBuy, private.worthCache[vSig])
end
if (vSuffix == iSuffix) then
if (vFactor == iFactor) then
exact = exact + vCount
if (vColor) then
myColors.exact[vColor] = myColors.exact[vColor] + vCount
end
else
suffix = suffix + vCount
if (vColor) then
myColors.suffix[vColor] = myColors.suffix[vColor] + vCount
end
end
else
base = base + vCount
if (vColor) then
myColors.base[vColor] = myColors.base[vColor] + vCount
end
end
if (vColor) then
myColors.all[vColor] = myColors.all[vColor] + vCount
-- Set up colours per stack size as well.
if not myColors.stack[vCount] then myColors.stack[vCount] = { red=0, orange=0, yellow=0, green=0, blue=0 } end
myColors.stack[vCount][vColor] = myColors.stack[vCount][vColor] + vCount
end
end
private.distributionCache[sig] = {exact, suffix, base, myColors}
return exact, suffix, base, myColors
end
function lib.Processors.tooltip(callbackType, tooltip, name, hyperlink, quality, quantity, cost)
if not get("scandata.tooltip.display") then return end
tooltip:SetColor(0.3, 0.9, 0.8)
local doColor = true
local exact, suffix, base, dist = lib.GetDistribution(hyperlink)
if base+suffix+exact <= 0 then
tooltip:AddLine("No matches in image.")
else
if get("scandata.tooltip.modifier") and IsShiftKeyDown() then
tooltip:AddLine("Items in image:")
if (exact > 0) then
tooltip:AddLine(" |cffddeeff"..exact.."|r exact "..lib.Colored(doColor, dist.exact, "matches"))
end
if (suffix > 0) then
tooltip:AddLine(" |cffddeeff"..suffix.."|r suffix "..lib.Colored(doColor, dist.suffix, "matches"))
end
if (base > 0) then
tooltip:AddLine(" |cffddeeff"..base.."|r base "..lib.Colored(doColor, dist.base, "matches"))
end
if (dist.stack) then
for stackSize, stackColor in pairs(dist.stack) do
tooltip:AddLine(" Stacks of "..stackSize.." "..lib.Colored(doColor, stackColor, "in image"))
end
end
else
if (suffix+base > 0) then
tooltip:AddLine("|cffddeeff"..exact.." +"..(suffix+base).."|r matches "..lib.Colored(doColor, dist.all, "in image"))
else
tooltip:AddLine("|cffddeeff"..exact.."|r matches "..lib.Colored(doColor, dist.exact, "in image"))
end
end
end
end
--[[ DATABASE FUNCTIONS ]]--
function lib.GetAddOnInfo()
return private.isLoaded, INTERFACE_VERSION
end
private.dataCache = {}
function lib.GetScanData(serverKey)
local cache = private.dataCache[serverKey]
if cache then return cache end
local realm, faction = AucAdvanced.SplitServerKey(serverKey)
if not realm then
debugPrint("AucScanData: invalid serverKey passed to GetScanData: "..tostring(serverKey), "ScanData", "Invalid serverKey", "Error")
return
end
local realmdata = AucScanData.scans[realm]
if not realmdata then return end -- not in database
local livedata = serverKey == Const.ServerKeyHome or serverKey == Const.ServerKeyNeutral -- 'live' data can be changed by scanning
local scandata = realmdata[faction]
if scandata then
if not livedata then
-- Copy scandata info into a clone table and call Unpack on that
-- The original does not get unpacked, so does not need repacking
local clone = {
image = scandata.image, -- will be overwritten by unpack
ropes = scandata.ropes, -- will be deleted by unpack
scanstats = replicate(scandata.scanstats)
}
scandata = clone
end
if type(scandata.scanstats) ~= "table" then
scandata.scanstats = {ImageUpdated = scandata.time or time()}
end
if not scandata.image then
scandata.image = {}
scandata.scanstats.ImageUpdated = time()
end
-- delete obsolete entries
scandata.nextID = nil
scandata.time = nil
scandata.LastFullScan = nil
scandata.LastGetAll = nil
else
scandata = {image = {}, scanstats = {ImageUpdated = time()} }
if livedata then
realmdata[faction] = scandata
end
end
private.Unpack(scandata)
private.dataCache[serverKey] = scandata
return scandata
end
function lib.ClearScanData(command)
local report, serverKey
local keyword, extra = "faction", "" -- default
if type(command) == "string" then
local _, ind, key = strfind(command, "(%S+)")
if key then
key = AucAdvanced.API.IsKeyword(key)
if key then
keyword = key -- recognised keyword
extra = strtrim(strsub(command, ind+1))
else
extra = strtrim(command) -- processor will try to resolve whole command
end
end
elseif command then -- only valid types are string or nil
error("Unrecognised parameter type to ClearScanData: "..type(command)..":"..tostring(command))
end
if keyword == "ALL" then
if extra == "" then
wipe(AucScanData.scans)
report = "All realms"
end
else
if keyword == "server" then
if extra == "" then extra = Const.PlayerRealm end
elseif keyword == "faction" then
if extra == "" then extra = AucAdvanced.GetFaction() end
end
if AucScanData.scans[extra] then -- it's a realm name in our database
AucScanData.scans[extra] = nil
report = extra
else
local fac = AucAdvanced.IsFaction(extra)
if fac then -- convert faction group to serverKey
extra = Const.PlayerRealm.."-"..fac
end
local realm, faction, text = AucAdvanced.SplitServerKey(extra)
if faction and AucScanData.scans[realm] then
AucScanData.scans[realm][faction] = nil
report = text
serverKey = extra
end
end
end
wipe(private.dataCache)
-- Our functions expect home faction to exist - create a new one if it has just been deleted
if not AucScanData.scans[Const.PlayerRealm] then AucScanData.scans[Const.PlayerRealm] = {} end
lib.GetScanData(Const.ServerKeyHome) -- force create (if needed) and put back in cache
if report then
aucPrint("Auctioneer: ScanData cleared for {{"..report.."}}.")
local clearstats = {
source = "clear",
clearType = "scandata",
clearRequest = command,
clearReport = report,
serverKey = serverKey,
}
AucAdvanced.SendProcessorMessage("scanstats", clearstats) -- notify modules to flush caches
else
aucPrint("Auctioneer: Unable to clear ScanData for {{"..command.."}}")
end
end
function private.Unpack(scandata)
if type(scandata.image) == "string" then
if scandata.image ~= "rope" then
scandata.ropes = { scandata.image }
end
scandata.image = {}
for pos, rope in ipairs(scandata.ropes) do
local loader, err = loadstring(rope)
if loader then
local test, items = pcall(loader)
if test then
for pos, item in ipairs(items) do
tinsert(scandata.image, item)
end
err = nil
else
err = items
end
end
if err then
aucPrint("Error loading scan image: {{", err, "}}")
-- if we get an error from any rope, assume the whole packed image is corrupt
scandata.image = {}
scandata.scanstats.ImageUpdated = time()
break
end
end
elseif type(scandata.image) ~= "table" then
scandata.image = {}
scandata.scanstats.ImageUpdated = time()
end
scandata.ropes = nil
end
function private.OnLoad()
private.OnLoad = nil
private.UpgradeDB()
if not AucScanData.scans[Const.PlayerRealm] then AucScanData.scans[Const.PlayerRealm] = {} end
lib.GetScanData(Const.ServerKeyHome) -- force unpack of home faction data
private.isLoaded = true
end
function private.UpgradeDB()
private.UpgradeDB = nil
if AucScanData then
if type(AucScanData.scans) ~= "table" then AucScanData.scans = {} end
if AucScanData.Version == DATABASE_VERSION then return end
if AucScanData.Version == "1.2" then
-- version "1.2" to version 1.3
-- Database structure is virtually the same, we won't try to update the whole database here
-- Each time GetScanData is called it will check/update that record as needed
aucPrint("Auc-ScanData is upgrading database version 1.2 to 1.3")
AucScanData.Version = DATABASE_VERSION
return
end
-- Unknown version - wipe and start from fresh
aucPrint("Auc-ScanData database error: unknown version, resetting database")
wipe(AucScanData.scans)
AucScanData.Version = DATABASE_VERSION
else
AucScanData = { Version = DATABASE_VERSION, scans = {} }
end
end
function lib.OnUnload()
local StringRope = LibStub:GetLibrary("StringRope")
local rope = StringRope:New(-1)
local maxLen = 2^22
if not (AucScanData and AucScanData.scans) then return end
-- Convert all image data to loadstring strings
for server, sData in pairs(AucScanData.scans) do
for faction, fData in pairs(sData) do
if fData.image and type(fData.image) == "table" then
fData.ropes = {}
rope:Add("return {")
local fCount = #fData.image
for i = 1, fCount do
local item = fData.image[i]
if item and type(item) == "table" then
rope:Add("{")
local pos = 1
while item[pos] or item[pos+1] or item[pos+2] or item[pos+3] do
local v = item[pos]
if v == nil then
rope:Add("nil,")
else
local t = type(v)
if t == "string" then
rope:Add(("%q,"):format(v))
elseif t == "number" then
rope:Add(v..",")
elseif t == "boolean" then
rope:Add(tostring(v)..",")
else
rope:Add("nil--[["..t.."]],")
end
end
pos = pos + 1
end
rope:Add("},")
elseif item == nil then
rope:Add("nil,")
else
rope:Add("nil--[["..type(item).."]],")
end
if rope.len and rope.len > maxLen then
rope:Add("}");
tinsert(fData.ropes, rope:Get())
rope:Clear()
rope:Add("return {")
end
end
rope:Add("}")
fData.image = "rope"
tinsert(fData.ropes, rope:Get())
rope:Clear()
end
end
end
end
AucAdvanced.RegisterRevision("$URL: http://svn.norganna.org/auctioneer/branches/5.9/Auc-ScanData/ScanData.lua $", "$Rev: 4840 $")