AuctioneerSuite/Auc-Stat-iLevel/iLevel.lua
2026-04-13 17:48:13 -04:00

533 lines
20 KiB
Lua

--[[
Auctioneer - iLevel Standard Deviation Statistics module
Version: 5.7.4568 (KillerKoala)
Revision: $Id: iLevel.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 = "Stat", "iLevel"
local lib,parent,private = AucAdvanced.NewModule(libType, libName)
if not lib then return end
local aucPrint,decode,_,_,replicate,_,get,set,default,debugPrint,fill, _TRANS = AucAdvanced.GetModuleLocals()
local select,next,pairs,ipairs,type,unpack,wipe = select,next,pairs,ipairs,type,unpack,wipe
local tonumber,tostring,strsplit,strjoin = tonumber,tostring,strsplit,strjoin
local floor,abs,max = floor,abs,max
local concat = table.concat
local strmatch = strmatch
local iTypes = AucAdvanced.Const.InvTypes
local GetFaction = AucAdvanced.GetFaction
local KEEP_NUM_POINTS = 250
local ZValues = {.063, .126, .189, .253, .319, .385, .454, .525, .598, .675, .756, .842, .935, 1.037, 1.151, 1.282, 1.441, 1.646, 1.962, 20, 20000}
function lib.CommandHandler(command, ...)
local serverKey = GetFaction()
local _,_,keyText = AucAdvanced.SplitServerKey(serverKey)
if (command == "help") then
aucPrint(_TRANS('ILVL_Help_SlashHelp1') )--Help for Auctioneer Advanced - iLevel
local line = AucAdvanced.Config.GetCommandLead(libType, libName)
aucPrint(line, "help}} - ".._TRANS('ILVL_Help_SlashHelp2') ) -- this iLevel help
aucPrint(line, "clear}} - ".._TRANS('ILVL_Help_SlashHelp3'):format(keyText) ) --clear current %s iLevel price database
elseif (command ==_TRANS( 'clear') ) then
lib.ClearData(serverKey)
end
end
function lib.Processor(callbackType, ...)
if (callbackType == "tooltip") then
lib.ProcessTooltip(...)
elseif (callbackType == "config") then
if private.SetupConfigGui then -- only call it once
private.SetupConfigGui(...)
end
elseif (callbackType == "scanstats") then
private.ResetCache()
private.RepackStats()
end
end
lib.Processors = {}
function lib.Processors.tooltip(callbackType, ...)
lib.ProcessTooltip(...)
end
function lib.Processors.config(callbackType, ...)
if private.SetupConfigGui then -- only call it once
private.SetupConfigGui(...)
end
end
function lib.Processors.scanstats(callbackType, ...)
private.ResetCache()
private.RepackStats()
end
lib.ScanProcessors = {}
function lib.ScanProcessors.create(operation, itemData, oldData)
if not get("stat.ilevel.enable") then return end
-- This function is responsible for processing and storing the stats after each scan
-- Note: itemData gets reused over and over again, so do not make changes to it, or use
-- it in places where you rely on it. Make a deep copy of it if you need it after this
-- function returns.
-- We're only interested in items with buyouts.
local buyout = itemData.buyoutPrice
if not buyout or buyout == 0 then return end
if (itemData.stackSize > 1) then
buyout = buyout.."/"..itemData.stackSize
end
-- Get the signature of this item and find it's stats.
local iLevel, quality, equipPos = itemData.itemLevel, itemData.quality, itemData.equipPos
if quality < 1 then return end
if not equipPos then return end
if equipPos < 1 then return end
local itemSig = ("%d:%d"):format(equipPos, quality)
local serverKey = GetFaction()
local stats = private.GetUnpackedStats(serverKey, itemSig, true) -- read/write
if not stats[iLevel] then stats[iLevel] = {} end
local sz = #stats[iLevel]
stats[iLevel][sz+1] = buyout
end
local BellCurve = AucAdvanced.API.GenerateBellCurve();
-----------------------------------------------------------------------------------
-- The PDF for standard deviation data, standard bell curve
-----------------------------------------------------------------------------------
function lib.GetItemPDF(hyperlink, serverKey)
if not get("stat.ilevel.enable") then return end
-- Get the data
local average, mean, _, stddev, variance, count, confidence = lib.GetPrice(hyperlink, serverKey)
if not (average and stddev) or average == 0 or stddev == 0 then
return nil; -- No data, cannot determine pricing
end
local lower, upper = average - 3 * stddev, average + 3 * stddev;
-- Build the PDF based on standard deviation & average
BellCurve:SetParameters(average, stddev);
return BellCurve, lower, upper; -- This has a __call metamethod so it's ok
end
-----------------------------------------------------------------------------------
function private.GetCfromZ(Z)
--C = 0.05*i
if (not Z) then
return .05
end
if (Z > 10) then
return .99
end
local i = 1
while Z > ZValues[i] do
i = i + 1
end
if i == 1 then
return .05
else
i = i - 1 + ((Z - ZValues[i-1]) / (ZValues[i] - ZValues[i-1]))
return i*0.05
end
end
local weakmeta = {__mode="kv"}
local pricecache = setmetatable({}, weakmeta)
function private.ResetCache()
wipe(pricecache)
end
local datapoints_price = {} -- used temporarily in .GetPrice() to avoid unpacking strings multiple times
local datapoints_stack = {}
function lib.GetPrice(hyperlink, serverKey)
if not get("stat.ilevel.enable") then return end
local itemSig, iLevel = private.GetItemDetail(hyperlink)
if not itemSig then return end
if not serverKey then serverKey = GetFaction() end
local average, mean, stdev, variance, count, confidence
local cacheSig = serverKey..itemSig..";"..iLevel
if pricecache[cacheSig] then
average, mean, stdev, variance, count, confidence = unpack(pricecache[cacheSig], 1, 6)
return average, mean, false, stdev, variance, count, confidence
end
local stats = private.GetUnpackedStats(serverKey, itemSig) -- read only
if not stats[iLevel] then return end
count = #stats[iLevel]
if (count < 1) then return end
local total, number = 0, 0
for i = 1, count do
local price, stack = strsplit("/", stats[iLevel][i])
price = tonumber(price) or 0
stack = tonumber(stack) or 1
if (stack < 1) then stack = 1 end
datapoints_price[i] = price
datapoints_stack[i] = stack
total = total + price
number = number + stack
end
mean = total / number
if (count < 2) then return 0,0,0, mean, count end
variance = 0
for i = 1, count do
variance = variance + ((mean - datapoints_price[i]/datapoints_stack[i]) ^ 2);
end
variance = variance / count;
stdev = variance ^ 0.5
local deviation = 1.5 * stdev
total = 0 -- recomputing with only data within deviation
number = 0
for i = 1, count do
local price,stack = datapoints_price[i], datapoints_stack[i]
if abs((price/stack) - mean) < deviation then
total = total + price
number = number + stack
end
end
confidence = .01
if (number > 0) then -- number<1 will happen if we have e.g. two big clusters: one at 1g and one at 10g
average = total / number
confidence = (.15*average)*(number^0.5)/(stdev)
confidence = private.GetCfromZ(confidence)
end
pricecache[cacheSig] = {average, mean, stdev, variance, count, confidence}
return average, mean, false, stdev, variance, count, confidence
end
function lib.GetPriceColumns()
return "Average", "Mean", false, "Std Deviation", "Variance", "Count", "Confidence"
end
local array = {}
function lib.GetPriceArray(hyperlink, serverKey)
if not get("stat.ilevel.enable") then return end
-- Clean out the old array
wipe(array)
-- Get our statistics
local average, mean, _, stdev, variance, count, confidence = lib.GetPrice(hyperlink, serverKey)
-- These 3 are the ones that most algorithms will look for
array.price = average or mean
array.seen = 0
array.confidence = confidence
-- This is additional data
array.normalized = average
array.mean = mean
array.deviation = stdev
array.variance = variance
array.processed = count
-- Return a temporary array. Data in this array is
-- only valid until this function is called again.
return array
end
function private.SetupConfigGui(gui)
private.SetupConfigGui = nil
local id = gui:AddTab(lib.libName, lib.libType.." Modules")
--gui:MakeScrollable(id)
gui:AddHelp(id, "what ilevel stats",
_TRANS('ILVL_Help_WhatIlevelStats') ,--What are ilevel stats?
_TRANS('ILVL_Help_WhatIlevelStatsAnswer') )--ilevel stats are the numbers that are generated by the iLevel module consisting of a filtered Standard Deviation calculation of item cost.
gui:AddHelp(id, "filtered ilevel",
_TRANS('ILVL_Help_WhatFiltered') ,--What do you mean filtered?
_TRANS('ILVL_Help_WhatFilteredAnswer') )--Items outside a (1.5*Standard) variance are ignored and assumed to be wrongly priced when calculating the deviation.
--all options in here will be duplicated in the tooltip frame
function private.addTooltipControls(id)
gui:AddHelp(id, "what standard deviation",
_TRANS('ILVL_Help_WhatStdDev') ,--What is a Standard Deviation calculation?
_TRANS('ILVL_Help_WhatStdDevAnswer') )--In short terms, it is a distance to mean average calculation.
gui:AddHelp(id, "what normalized",
_TRANS('ILVL_Help_WhatNormalized') ,--What is the Normalized calculation?
_TRANS('ILVL_Help_WhatNormalizedAnswer') )--In short terms again, it is the average of those values determined within the standard deviation variance calculation.
gui:AddHelp(id, "what confidence",
_TRANS('ILVL_Help_WhatConfidence') ,--What does confidence mean?
_TRANS('ILVL_Help_WhatConfidenceAnswer') )--Confidence is a value between 0 and 1 that determines the strength of the calculations (higher the better).
gui:AddHelp(id, "why multiply stack size ilevel",
_TRANS('ILVL_Help_WhyStackSize') ,--Why have the option to multiply by stack size?
_TRANS('ILVL_Help_WhyStackSizeAnswer') )--The original Stat-ilevel multiplied by the stack size of the item, but some like dealing on a per-item basis.
gui:AddControl(id, "Header", 0, _TRANS('ILVL_Interface_IlevelOptions') )--ilevel options
gui:AddControl(id, "Note", 0, 1, nil, nil, " ")
gui:AddControl(id, "Checkbox", 0, 1, "stat.ilevel.enable", _TRANS('ILVL_Interface_EnableILevelStats') )--Enable iLevel Stats
gui:AddTip(id, _TRANS('ILVL_HelpTooltip_EnableILevelStats') )--Allow iLevel to gather and return price data
gui:AddControl(id, "Note", 0, 1, nil, nil, " ")
gui:AddControl(id, "Checkbox", 0, 4, "stat.ilevel.tooltip", _TRANS('ILVL_Interface_ShowiLevel') )--Show iLevel stats in the tooltips?
gui:AddTip(id, _TRANS('ILVL_HelpTooltip_ShowiLevel') )--Toggle display of stats from the iLevel module on or off
gui:AddControl(id, "Checkbox", 0, 6, "stat.ilevel.mean", _TRANS('ILVL_Interface_DisplayMean') )--Display Mean
gui:AddTip(id, _TRANS('ILVL_HelpTooltip_DisplayMean') )--Toggle display of 'Mean' calculation in tooltips on or off
gui:AddControl(id, "Checkbox", 0, 6, "stat.ilevel.normal", _TRANS('ILVL_Interface_DisplayNormalized') )--Display Normalized'
gui:AddTip(id, _TRANS('ILVL_HelpTooltip_DisplayNormalized') )--Toggle display of \'Normalized\' calculation in tooltips on or off
gui:AddControl(id, "Checkbox", 0, 6, "stat.ilevel.stdev", _TRANS('ILVL_Interface_DisplayStdDeviation') )--Display Standard Deviation
gui:AddTip(id, _TRANS('ILVL_HelpTooltip_DisplayStdDeviation') )--Toggle display of \'Standard Deviation\' calculation in tooltips on or off
gui:AddControl(id, "Checkbox", 0, 6, "stat.ilevel.confid", _TRANS('ILVL_Interface_DisplayConfidence') )--Display Confidence
gui:AddTip(id, _TRANS('ILVL_HelpTooltip_DisplayConfidence') )--Toggle display of \'Confidence\' calculation in tooltips on or off
gui:AddControl(id, "Note", 0, 1, nil, nil, " ")
gui:AddControl(id, "Checkbox", 0, 4, "stat.ilevel.quantmul", _TRANS('ILVL_Interface_MultiplyStack') )--Multiply by Stack Size
gui:AddTip(id, _TRANS('ILVL_HelpTooltip_MultiplyStack') )--Multiplies by current stack size if on
gui:AddControl(id, "Note", 0, 1, nil, nil, " ")
end
--This is the Tooltip tab provided by Auctioneer so all tooltip configuration is in one place
local tooltipID = AucAdvanced.Settings.Gui.tooltipID
--now we create a duplicate of these in the tooltip frame
private.addTooltipControls(id)
if tooltipID then private.addTooltipControls(tooltipID) end
end
function lib.ProcessTooltip(tooltip, name, hyperlink, quality, quantity, cost, ...)
if not get("stat.ilevel.tooltip") then return end
if not quantity or quantity < 1 then quantity = 1 end
if not get("stat.ilevel.quantmul") then quantity = 1 end
local average, mean, _, stdev, var, count, confidence = lib.GetPrice(hyperlink)
if (mean and mean > 0) then
tooltip:SetColor(0.3, 0.9, 0.8)
tooltip:AddLine(_TRANS('ILVL_Tooltip_iLevelPrices'):format(count) )--iLevel prices (%s points):
if get("stat.ilevel.mean") then
tooltip:AddLine(" ".._TRANS('ILVL_Tooltip_MeanPrice') , mean*quantity)--Mean price
end
if (average and average > 0) then
if get("stat.ilevel.normal") then
tooltip:AddLine(" ".._TRANS('ILVL_Tooltip_Normalized') , average*quantity)--Normalized
if (quantity > 1) then
tooltip:AddLine(" ".._TRANS('ILVL_Tooltip_Individually') , average)--(or individually)
end
end
if get("stat.ilevel.stdev") then
tooltip:AddLine(" ".._TRANS('ILVL_Tooltip_StdDeviation') , stdev*quantity)--Std Deviation
if (quantity > 1) then
tooltip:AddLine(" ".._TRANS('ILVL_Tooltip_Individually') , stdev)--(or individually)
end
end
if get("stat.ilevel.confid") then
tooltip:AddLine(" ".._TRANS('ILVL_Tooltip_Confidence'):format((floor(confidence*1000))/1000) )--Confidence: %s
end
end
end
end
function lib.OnLoad(addon)
default("stat.ilevel.tooltip", false)
default("stat.ilevel.mean", false)
default("stat.ilevel.normal", false)
default("stat.ilevel.stdev", true)
default("stat.ilevel.confid", true)
default("stat.ilevel.quantmul", true)
default("stat.ilevel.enable", true)
if private.InitData then private.InitData() end
end
function lib.OnUnload()
private.RepackStats()
end
function lib.ClearItem(hyperlink, serverKey)
local itemSig, iLevel, equipPos, quality = private.GetItemDetail(hyperlink)
if not itemSig then return end
if not serverKey then serverKey = GetFaction() end
local stats = private.GetUnpackedStats(serverKey, itemSig, true)
if stats[iLevel] then
stats[iLevel] = nil
private.RepackStats()
private.ResetCache()
local _, _, keyText = AucAdvanced.SplitServerKey(serverKey)
aucPrint(_TRANS('ILVL_Interface_ClearingItems'):format(iLevel, quality, equipPos, keyText))--Stat-iLevel: clearing data for iLevel=%d/quality=%d/equip=%d items for {{%s}}
return
end
aucPrint(_TRANS('ILVL_Interface_ItemNotFound') )--Stat-iLevel: item is not in database
end
--[[ Database Management functions ]]--
local ILRealmData
local unpacked, updated = {}, {}
function private.InitData()
private.InitData = nil
if not AucAdvancedStat_iLevelData then AucAdvancedStat_iLevelData = {} end
ILRealmData = AucAdvancedStat_iLevelData
end
function lib.ClearData(serverKey)
serverKey = serverKey or GetFaction()
private.ResetCache()
if AucAdvanced.API.IsKeyword(serverKey, "ALL") then
wipe(ILRealmData)
wipe(unpacked)
wipe(updated)
aucPrint(_TRANS('ILVL_Help_SlashHelp5').." {{".._TRANS("ADV_Interface_AllRealms").."}}") --Clearing iLevel stats for // All realms
elseif ILRealmData[serverKey] then
ILRealmData[serverKey] = nil
unpacked[serverKey] = nil
-- 'updated' may contain orphaned entries - these will be cleaned up in next RepackStats
local _, _, keyText = AucAdvanced.SplitServerKey(serverKey)
aucPrint(_TRANS('ILVL_Help_SlashHelp5').." {{"..keyText.."}}") --Clearing iLevel stats for
end
end
--[[
itemSig, iLevel, equipPos, quality = GetItemDetail(hyperlink)
--]]
function private.GetItemDetail(hyperlink)
if type(hyperlink) ~= "string" then return end
if not hyperlink:match("item:%d") then return end
local _,_, quality, iLevel, _,_,_,_, equipPos = GetItemInfo(hyperlink)
if not quality or quality < 1 then return end
equipPos = tonumber(iTypes[equipPos])
if not equipPos or equipPos < 1 then return end
local itemSig = ("%d:%d"):format(equipPos, quality)
return itemSig, iLevel, equipPos, quality
end
--[[
stats = GetUnpackedStats (serverKey, itemSig, writing)
Obtain a cached data table for itemSig in serverKey's data.
Set writing to true if you intend to change the data
Caution: if you set 'writing' to true, RepackStats() must be called before the end of the session to save the changes
--]]
function private.GetUnpackedStats(serverKey, itemSig, writing)
local stats = unpacked[serverKey] and unpacked[serverKey][itemSig]
if stats then
if writing then
updated[stats] = true
end
return stats
end
local realmdata = ILRealmData[serverKey]
if not realmdata then
if not AucAdvanced.SplitServerKey(serverKey) then
error("Invalid serverKey passed to Stat-iLevel")
end
realmdata = {}
ILRealmData[serverKey] = realmdata
end
stats = private.UnpackStats(realmdata, itemSig)
if not unpacked[serverKey] then unpacked[serverKey] = {} end
unpacked[serverKey][itemSig] = stats
if writing then
updated[stats] = true
end
return stats
end
--[[
RepackStats()
Write any changed tables in the unpacked cache back to ILRealmData
--]]
function private.RepackStats()
if not next(updated) then return end -- bail out if no updated entries
for serverKey, realmData in pairs(unpacked) do
for item, stats in pairs(realmData) do
if updated[stats] then
local packed = private.PackStats(stats)
if packed == "" then
ILRealmData[serverKey][item] = nil -- delete empty entries from the database
else
ILRealmData[serverKey][item] = packed
end
end
end
end
wipe(updated)
end
--[[ Subfunctions ]]--
function private.UnpackStatIter(data, ...)
local c = select("#", ...)
local v
for i = 1, c do
v = select(i, ...)
local property, info = strsplit(":", v)
property = tonumber(property) or property
if (property and info) then
local t= {strsplit(";", info)}
for k,v in ipairs(t) do
t[k] = tonumber(v) or v
end
data[property] = t
end
end
end
function private.UnpackStats(data, item)
local stats = {}
if (data and data[item]) then
private.UnpackStatIter(stats, strsplit(",", data[item]))
end
return stats
end
local tmp={}
function private.PackStats(data)
local ntmp=0
for property, info in pairs(data) do
ntmp=ntmp+1
local n = max(1, #info - KEEP_NUM_POINTS + 1)
tmp[ntmp] = property..":"..concat(info, ";", n)
end
return concat(tmp, ",", 1, ntmp)
end
AucAdvanced.RegisterRevision("$URL: http://svn.norganna.org/auctioneer/branches/5.9/Auc-Stat-iLevel/iLevel.lua $", "$Rev: 4840 $")