AuctioneerSuite/Enchantrix/EnxStorage.lua
2026-04-13 17:48:13 -04:00

684 lines
21 KiB
Lua

--[[
Enchantrix Addon for World of Warcraft(tm).
Version: 5.9.4961 (WhackyWallaby)
Revision: $Id: EnxStorage.lua 3767 2008-11-05 17:57:29Z Norganna $
URL: http://enchantrix.org/
Database functions and saved variables.
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
]]
Enchantrix_RegisterRevision("$URL: http://svn.norganna.org/auctioneer/branches/5.9/Enchantrix/EnxStorage.lua $", "$Rev: 3767 $")
--[[
Usages:
Enchantrix.Storage["4:2:4:1234"] = { [5432] = { 10, 20 } }
print(Enchantrix.Storage["4:2:4"])
]]
-- Global functions
local getItemDisenchants -- Enchantrix.Storage.GetItemDisenchants()
local getItemDisenchantTotals -- Enchantrix.Storage.GetItemDisenchantTotals()
local getItemDisenchantFromTable -- Enchantrix.Storage.GetItemDisenchantFromTable()
local getItemDisenchantFromTableForOneMaterial -- Enchantrix.Storage.GetItemDisenchantFromTableForOneMaterial()
local saveDisenchant -- Enchantrix.Storage.SaveDisenchant()
local addonLoaded -- Enchantrix.Storage.AddonLoaded()
local saveNonDisenchantable -- Enchantrix.Storage.SaveNonDisenchantable()
local saveProspect -- Enchantrix.Storage.SaveProspect()
local getItemProspects -- Enchantrix.Storage.GetItemProspects()
local getItemProspectTotals -- Enchantrix.Storage.GetItemProspectTotals()
local saveMilling -- Enchantrix.Storage.SaveMilling
local getItemMilling -- Enchantrix.Storage.GetItemMilling()
local getItemMillingTotals -- Enchantrix.Storage.GetItemMillingTotals()
-- Local functions
local unserialize
local serialize
local normalizeDisenchant
local mergeDisenchant
local mergeDisenchantLists
local tooltip = LibStub("nTipHelper:1")
-- Database
local N_DISENCHANTS = 1
local N_REAGENTS = 2
function unserialize(str)
-- Break up a disenchant string to a table for easy manipulation
local tbl = {}
if type(str) == "string" then
for de in Enchantrix.Util.Spliterator(str, ";") do
local id, d, r = de:match("(%d+):(%d+):(%d+)")
id, d, r = tonumber(id), tonumber(d), tonumber(r)
if (id and d > 0 and r > 0) then
tbl[id] = {[N_DISENCHANTS] = d, [N_REAGENTS] = r}
end
end
end
return tbl
end
function serialize(tbl)
-- Serialize a table into a string
if type(tbl) == "table" then
local str
for id, counts in pairs(tbl) do
if (type(id) == "number" and counts[N_DISENCHANTS] > 0 and counts[N_REAGENTS] > 0) then
if (str) then
str = ("%s;%d:%d:%d:0"):format(str, id, counts[N_DISENCHANTS], counts[N_REAGENTS])
else
str = ("%d:%d:%d:0"):format(id, counts[N_DISENCHANTS], counts[N_REAGENTS])
end
end
end
return str
end
end
function mergeDisenchant(str1, str2)
-- Merge two disenchant strings into a single string
local tbl1, tbl2 = unserialize(str1), unserialize(str2)
for id, counts in pairs(tbl2) do
if (not tbl1[id]) then
tbl1[id] = counts
else
tbl1[id][N_DISENCHANTS] = tbl1[id][N_DISENCHANTS] + counts[N_DISENCHANTS]
tbl1[id][N_REAGENTS] = tbl1[id][N_REAGENTS] + counts[N_REAGENTS]
end
end
return serialize(tbl1)
end
function normalizeDisenchant(str)
-- Divide all counts in disenchant string by gcd
local div = 0
local count = 0
local tbl = unserialize(str)
for id, counts in pairs(tbl) do
div = Enchantrix.Util.GCD(div, counts[N_DISENCHANTS])
div = Enchantrix.Util.GCD(div, counts[N_REAGENTS])
count = count + 1
end
-- Only normalize if there's more than one kind of reagent
if count > 1 then
for id, counts in pairs(tbl) do
counts[N_DISENCHANTS] = counts[N_DISENCHANTS] / div
counts[N_REAGENTS] = counts[N_REAGENTS] / div
end
return serialize(tbl)
end
return str
end
function mergeDisenchantLists()
-- DisenchantList no longer exists
-- it used to be merged in here
--[[
-- Merge items from EnchantedLocal into EnchantedItemTypes
-- now only useful to developers
EnchantedItemTypes = {}
for sig, disenchant in pairs(EnchantedLocal) do
local item = Enchantrix.Util.GetItemIdFromSig(sig)
local itype = Enchantrix.Util.GetItemType(item)
if itype then
EnchantedItemTypes[itype] = mergeDisenchant(EnchantedItemTypes[itype], disenchant)
end
end
]]
-- now we need to merge the user non-disenchantables with the default non-disenchantables
if not NonDisenchantablesLocal then NonDisenchantablesLocal = {} end
for sig, value in pairs(NonDisenchantablesLocal) do
NonDisenchantables[sig] = value;
end
-- Take out the trash
collectgarbage("collect")
end
function saveDisenchant(sig, reagentID, count)
-- Update tables after a disenchant has been detected
assert(type(sig) == "string");
assert(tonumber(reagentID));
assert(tonumber(count));
local id = Enchantrix.Util.GetItemIdFromSig(sig)
local itype = Enchantrix.Util.GetIType(id)
local disenchant = ("%d:1:%d:0"):format(reagentID, count)
EnchantedLocal[sig] = mergeDisenchant(EnchantedLocal[sig], disenchant)
if itype then
EnchantedItemTypes[itype] = mergeDisenchant(EnchantedItemTypes[itype], disenchant)
end
end
-- for this, we need to pass in a list of reagents
function saveProspect(sig, reagentList )
-- Update tables after a prospect has been detected
assert(type(sig) == "string");
local id = Enchantrix.Util.GetItemIdFromSig(sig)
if (not ProspectedLocal[id]) then
ProspectedLocal[id] = {}
ProspectedLocal[id].total = 0;
end
ProspectedLocal[id].total = ProspectedLocal[id].total + 1;
for reagentID, quantity in pairs( reagentList ) do
if (not ProspectedLocal[id][reagentID]) then
ProspectedLocal[id][reagentID] = 0;
end
ProspectedLocal[id][reagentID] = ProspectedLocal[id][reagentID] + quantity;
end
end
-- this will return nil for anything that is not prospectable
function getItemProspects(link)
local itemType, itemID = tooltip:DecodeLink(link)
if (itemType ~= "item") then return end
return Enchantrix.Constants.ProspectableItems[ itemID ];
end
-- for this, we need to pass in a list of reagents
function saveMilling(sig, reagentList )
-- Update tables after a prospect has been detected
assert(type(sig) == "string");
local id = Enchantrix.Util.GetItemIdFromSig(sig)
if (not MillingLocal[id]) then
MillingLocal[id] = {}
MillingLocal[id].total = 0;
end
MillingLocal[id].total = MillingLocal[id].total + 1;
for reagentID, quantity in pairs( reagentList ) do
if (not MillingLocal[id][reagentID]) then
MillingLocal[id][reagentID] = 0;
end
MillingLocal[id][reagentID] = MillingLocal[id][reagentID] + quantity;
end
end
-- this will return nil for anything that is not millable
--- ccox - WOTLK - this needs to use item level not just item->result
-- similar code in EnxTooltip.lua / millingTooltip
function getItemMilling(link)
local itemType, itemID = tooltip:DecodeLink(link)
if (itemType ~= "item") then return end
local resultGroup = Enchantrix.Constants.MillableItems[ itemID ];
if not resultGroup then
return nil
end
return Enchantrix.Constants.MillGroupYields[ resultGroup ];
end
function getItemDisenchants(link)
local iType
if (type(link) == "string") then
-- link format: item number, enchant, dk, dk, dk, dk, random unique id
local id = link:match("(%d+):%d+:%d+:%d+:%d+:%d+:%d+:%d+")
id = tonumber(id)
if (id) then
iType = Enchantrix.Util.GetIType("item:"..id..":0:0:0:0:0:0:0")
else
iType = Enchantrix.Util.GetIType(link)
end
else
-- probably a number
iType = Enchantrix.Util.GetIType(link)
end
if (not iType) then
-- NOTE - ccox - GetIType can return nil for items that are not disenchantable
-- a nil result does not mean that we could not find the IType
return nil
end
-- see if it is on our non-disenchantable list
if type(link) == "string" then
sig = Enchantrix.Util.GetSigFromLink(link);
else
local _, sLink = GetItemInfo(link);
sig = Enchantrix.Util.GetSigFromLink(sLink);
end
if (NonDisenchantables[sig]) then
return nil
end
local data = Enchantrix.Storage[iType]
if not data then
-- we really should have data
Enchantrix.Util.DebugPrint("getItemDisenchants", ENX_INFO, "No data", "No data returned for iType:", iType, link)
return nil
end
return data
end
-- NOTE - Rabbitbunny - copied from getItemDisenchantTotals directly below
-- NOTE - ccox - calculation copied from itemTooltip, I couldn't easily reuse the code
-- TODO - REVISIT - ccox - share the code with itemTooltip
-- NOTE - RockSlice - this function now returns value for one ore
function getItemProspectTotals(link)
local data = Enchantrix.Storage.GetItemProspects(link)
if not data then
-- error message would have been printed inside GetItemProspects
return
end
local totalHSP, totalMed, totalMkt, totalFive = 0,0,0,0
for result, resProb in pairs(data) do
local style, extra = Enchantrix.Util.GetPricingModel()
local hsp, med, mkt, five = Enchantrix.Util.GetReagentPrice(result,extra)
local resHSP, resMed, resMkt, resFive = (hsp or 0)*resProb, (med or 0)*resProb, (mkt or 0)*resProb, (five or 0)*resProb
totalHSP = totalHSP + resHSP
totalMed = totalMed + resMed
totalMkt = totalMkt + resMkt
totalFive = totalFive + resFive
end
--so far, we've been calculating per prospect. We need price per unit
totalHSP, totalMed, totalMkt, totalFive = totalHSP/5, totalMed/5, totalMkt/5, totalFive/5
return totalHSP, totalMed, totalMkt, totalFive
end
-- NOTE - ccox - calculation copied from itemTooltip, I couldn't easily reuse the code
-- TODO - REVISIT - ccox - share the code with itemTooltip
function getItemDisenchantTotals(link)
local data = Enchantrix.Storage.GetItemDisenchants(link)
if not data then
-- error message would have been printed inside GetItemDisenchants
return
end
local total = data.total
local totalHSP, totalMed, totalMkt, totalFive = 0,0,0,0
if (total and total[1] > 0) then
local totalNumber, totalQuantity = unpack(total)
for result, resData in pairs(data) do
if (result ~= "total") then
local resNumber, resQuantity = unpack(resData)
local style, extra = Enchantrix.Util.GetPricingModel()
local hsp, med, mkt, five = Enchantrix.Util.GetReagentPrice(result,extra)
local resProb, resCount = resNumber/totalNumber, resQuantity/resNumber
local resYield = resProb * resCount; -- == resQuantity / totalNumber;
local resHSP, resMed, resMkt, resFive = (hsp or 0)*resYield, (med or 0)*resYield, (mkt or 0)*resYield, (five or 0)*resYield
totalHSP = totalHSP + resHSP
totalMed = totalMed + resMed
totalMkt = totalMkt + resMkt
totalFive = totalFive + resFive
end
end
else
return
end
return totalHSP, totalMed, totalMkt, totalFive
end
-- NOTE - ccox - calculation copied from itemTooltip, I couldn't easily reuse the code
-- TODO - REVISIT - ccox - share the code with itemTooltip
-- NOTE - RockSlice - this function now returns value for one herb
function getItemMillingTotals(link)
local data = Enchantrix.Storage.GetItemMilling(link)
if not data then
-- error message would have been printed inside GetItemMilling
return
end
local totalHSP, totalMed, totalMkt, totalFive = 0,0,0,0
for result, resProb in pairs(data) do
local style, extra = Enchantrix.Util.GetPricingModel()
local hsp, med, mkt, five = Enchantrix.Util.GetReagentPrice(result,extra)
local resHSP, resMed, resMkt, resFive = (hsp or 0)*resProb, (med or 0)*resProb, (mkt or 0)*resProb, (five or 0)*resProb
totalHSP = totalHSP + resHSP
totalMed = totalMed + resMed
totalMkt = totalMkt + resMkt
totalFive = totalFive + resFive
end
--so far, we've been calculating per milling. We need price per unit
totalHSP, totalMed, totalMkt, totalFive = totalHSP/5, totalMed/5, totalMkt/5, totalFive/5
return totalHSP, totalMed, totalMkt, totalFive
end
-- NOTE - ccox - calculation copied from itemTooltip, but not remotely shareable
-- this version takes a table of pre-calculated reagent prices
-- this simplifies the inner loop of some calculations, and allow for custom pricing
function getItemDisenchantFromTable(link, reagentTable)
local data = Enchantrix.Storage.GetItemDisenchants(link)
if not data then
-- error message would have been printed inside GetItemDisenchants
return
end
local total = data.total
local priceTotal = 0;
if (total and total[1] > 0) then
local totalNumber, totalQuantity = unpack(total)
for result, resData in pairs(data) do
if (result ~= "total") then
local resNumber, resQuantity = unpack(resData)
local reagentPrice = reagentTable[ result ];
if (not reagentPrice) then
Enchantrix.Util.DebugPrint("reagentTable", ENX_INFO, "No data", "No data in reagent table for ", result, reagentTable )
end
local resYield = resQuantity / totalNumber;
local resPrice = (reagentPrice or 0) * resYield;
priceTotal = priceTotal + resPrice;
end
end
else
return
end
return priceTotal
end
function getItemDisenchantFromTableForOneMaterial(link, reagentTable, material)
local data = Enchantrix.Storage.GetItemDisenchants(link)
if not data then
-- error message would have been printed inside GetItemDisenchants
return
end
local total = data.total
local priceTotal = 0;
if (total and total[1] > 0) then
local totalNumber, totalQuantity = unpack(total)
for result, resData in pairs(data) do
if (result ~= "total" and result == material) then
local resNumber, resQuantity = unpack(resData)
local reagentPrice = reagentTable[ result ];
if (not reagentPrice) then
Enchantrix.Util.DebugPrint("reagentTable", ENX_INFO, "No data", "No data in reagent table for ", result, reagentTable )
end
local resYield = resQuantity / totalNumber;
local resPrice = (reagentPrice or 0) * resYield;
local percentage = resNumber / totalNumber;
local simpleYield = resQuantity/resNumber;
return resPrice, percentage, simpleYield;
end
end
end
-- material not matched
return
end
local _G
local lib = Enchantrix.Storage
lib.data = {}
local function addResults(data, ...)
if not data then return end
local result, number, quantity
local n = select("#", ...)
local stats
if (not data.total) then data.total = { 0, 0 } end
for i = 1, n do
stats = select(i, ...)
result, number, quantity = strsplit(":", stats)
result = tonumber(result)
if (result) then
number = tonumber(number) or 0
quantity = tonumber(quantity) or 0
if (not data[result]) then data[result] = { 0, 0 } end
data[result][1] = data[result][1] + number
data[result][2] = data[result][2] + quantity
data.total[1] = data.total[1] + number
data.total[2] = data.total[2] + quantity
end
end
end
-- take an ilevel and round it up to the corresponding bracket
local function roundupLevel(level)
for _, bracket in pairs(Enchantrix.Constants.levelUpperBounds) do
if bracket >= level then
return bracket
end
end
return nil
end
-- get entry from disenchant table (or nil if nothing found)
local function getBaseTableDisenchants(level, quality, type, item)
local rLevel = roundupLevel(level);
if Enchantrix.Constants.baseDisenchantTable[quality]
and Enchantrix.Constants.baseDisenchantTable[quality][type]
and Enchantrix.Constants.baseDisenchantTable[quality][type][rLevel] then
return Enchantrix.Constants.baseDisenchantTable[quality][type][rLevel]
end
-- no matching entry found, this is bad because this is the backup!
Enchantrix.Util.DebugPrint("disenchantTable", ENX_INFO, "No data", "No match found in base disenchant table for", rLevel, quality, type, level, item )
return nil
end
-- normal (history) data is material, number of times disenchanted, number of items returned
-- base data is material, percentage given, number returned
-- this will work as-is, but return a total count of 1
-- we have to multiply this to get a reasonable result after the confidence function
-- TODO - ccox - clean this up now that the confidence function is gone
local BASE_SCALE = 1000
local function addResultFromBaseTable(data, baseData)
if not data then return end
local result, number, quantity
if (not data.total) then data.total = { 0, 0 } end
for _, stats in pairs(baseData) do
result, number, quantity = stats[1], stats[2], stats[3];
result = tonumber(result)
if (result) then
number = tonumber(number) or 0
quantity = tonumber(quantity) or 0
number = BASE_SCALE * number
quantity = number * quantity
if (not data[result]) then data[result] = { 0, 0 } end
data[result][1] = number
data[result][2] = quantity
data.total[1] = data.total[1] + number
data.total[2] = data.total[2] + quantity
end
end
end
local compactres = {}
local function compact(data)
while #compactres > 0 do
table.remove(compactres)
end
for item, value in pairs(data) do
table.insert(compactres, strjoin(":", item, value[1], value[2]))
end
return strjoin(",", unpack(compactres))
end
local function index(self, key)
local iLevel,iQual,iType,iItem
if (type(key) == "string") then
iLevel,iQual,iType,iItem = strsplit(":", key)
end
if (iQual) then
local data = {}
iLevel = tonumber(iLevel) or 0
iQual = tonumber(iQual) or 0
iType = tonumber(iType) or 0
if (iLevel > 0 and iQual >= 2 and (iType == 2 or iType == 4)) then
local baseData = getBaseTableDisenchants(iLevel,iQual,iType,iItem);
if (baseData) then
addResultFromBaseTable(data,baseData);
end
end
return data
end
local val = rawget(self, key)
if (val) then
return val
end
return nil
end
local function newindex(self, key, value)
local iLevel,iQual,iType,iItem
if (type(key) == "string") then
iLevel,iQual,iType,iItem = strsplit(":", key)
end
if (iQual) then
if (type(value) ~= "table") then return end
local data = {}
iLevel = tonumber(iLevel) or 0
iQual = tonumber(iQual) or 0
iType = tonumber(iType) or 0
iItem = tonumber(iItem) or 0
if (iLevel > 0 and iQual >= 2 and (iType == 2 or iType == 4) and iItem > 0) then
local key = strjoin(":", iLevel, iQual, iType)
if (not EnchantrixData) then EnchantrixData = {} end
if (not EnchantrixData.disenchants) then EnchantrixData.disenchants = {} end
if (not EnchantrixData.disenchants[key]) then EnchantrixData.disenchants[key] = {}
else
for itemId, itemData in pairs(EnchantrixData.disenchants[key]) do
if (itemId == iItem) then
addResults(data, strsplit(",", itemData))
break
end
end
end
for resultId, resultData in pairs(value) do
if (not data[resultId]) then data[resultId] = { 0, 0 } end
local node = data[resultId]
node[1] = node[1] + resultData[1]
node[2] = node[2] + resultData[2]
end
EnchantrixData.disenchants[key][iItem] = compact(data)
end
return
end
if (self.locked) then return end
rawset(self, key, value)
end
function saveNonDisenchantable(itemLink)
if not NonDisenchantablesLocal then NonDisenchantablesLocal = {} end
local sig = Enchantrix.Util.GetSigFromLink(itemLink);
-- put this in the local and combined list
-- only the local list will be saved in SavedVariables
if (not NonDisenchantables[sig]) then
if (Enchantrix.Settings.GetSetting('chatShowFindings')) then
Enchantrix.Util.ChatPrint(_ENCH("FrmtFoundNotDisenchant"):format(itemLink))
end
NonDisenchantablesLocal[sig] = true;
NonDisenchantables[sig] = true;
end
end
function addonLoaded()
-- Create and setup saved variables
if not EnchantedLocal then EnchantedLocal = {} end
if not EnchantedBaseItems then EnchantedBaseItems = {} end
if not EnchantedItemTypes then EnchantedItemTypes = {} end
if not NonDisenchantables then NonDisenchantables = {} end
if not NonDisenchantablesLocal then NonDisenchantablesLocal = {} end
if not ProspectedLocal then ProspectedLocal = {} end
if not MillingLocal then MillingLocal = {} end
mergeDisenchantLists()
end
Enchantrix.Storage = {
data={},
locked=false,
AddonLoaded = addonLoaded,
GetItemDisenchants = getItemDisenchants,
GetItemDisenchantTotals = getItemDisenchantTotals,
GetItemDisenchantFromTable = getItemDisenchantFromTable,
GetItemDisenchantFromTableForOneMaterial = getItemDisenchantFromTableForOneMaterial,
SaveDisenchant = saveDisenchant,
SaveNonDisenchantable = saveNonDisenchantable,
SaveProspect = saveProspect,
GetItemProspects = getItemProspects,
GetItemProspectTotals = getItemProspectTotals,
SaveMilling = saveMilling,
GetItemMilling = getItemMilling,
GetItemMillingTotals = getItemMillingTotals,
}
-- Make all globals local to this file
_G = getfenv(0)
local metatable = {__index = index, __newindex = newindex}
setmetatable(Enchantrix.Storage, metatable)
setfenv(1, Enchantrix.Storage)
-- Stops any other addon from modifying our stuff.
loaded = true