forked from John-The-Baptist/OctoAuctionator
1375 lines
32 KiB
Lua
1375 lines
32 KiB
Lua
|
|
local addonName, addonTable = "Auctionator", AtrNS;
|
|
local zc = addonTable.zc;
|
|
|
|
KM_NULL_STATE = 0;
|
|
KM_PREQUERY = 1;
|
|
KM_INQUERY = 2;
|
|
KM_POSTQUERY = 3;
|
|
KM_ANALYZING = 4;
|
|
KM_SETTINGSORT = 5;
|
|
|
|
local AUCTION_CLASS_WEAPON = 1;
|
|
local AUCTION_CLASS_ARMOR = 2;
|
|
|
|
local gAllScans = {};
|
|
|
|
local BIGNUM = 999999999999;
|
|
|
|
local ATR_SORTBY_NAME_ASC = 0;
|
|
local ATR_SORTBY_NAME_DES = 1;
|
|
local ATR_SORTBY_PRICE_ASC = 2;
|
|
local ATR_SORTBY_PRICE_DES = 3;
|
|
|
|
-----------------------------------------
|
|
|
|
AtrScan = {};
|
|
AtrScan.__index = AtrScan;
|
|
|
|
-----------------------------------------
|
|
|
|
AtrSearch = {};
|
|
AtrSearch.__index = AtrSearch;
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_NewSearch (itemName, exact, rescanThreshold, callback)
|
|
|
|
local srch = {};
|
|
setmetatable (srch, AtrSearch);
|
|
srch:Init (itemName, exact, rescanThreshold, callback);
|
|
|
|
return srch;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:Init (searchText, exact, rescanThreshold, callback)
|
|
|
|
if (searchText == nil) then
|
|
searchText = "";
|
|
end
|
|
|
|
self.origSearchText = searchText;
|
|
|
|
if (not exact) then
|
|
if (zc.StringStartsWith (searchText, "\"") and zc.StringEndsWith (searchText, "\"")) then
|
|
searchText = string.sub (searchText, 2, string.len(searchText)-1);
|
|
exact = true;
|
|
end
|
|
end
|
|
|
|
self.searchText = searchText;
|
|
self.exact = exact;
|
|
self.processing_state = KM_NULL_STATE
|
|
self.current_page = -1
|
|
self.items = {};
|
|
self.query = Atr_NewQuery();
|
|
self.sortedScans = nil;
|
|
self.sortHow = ATR_SORTBY_PRICE_ASC;
|
|
self.callback = callback;
|
|
|
|
if (exact) then
|
|
|
|
if (rescanThreshold and rescanThreshold > 0) then
|
|
local scan = Atr_FindScan (searchText);
|
|
if (scan and (time() - scan.whenScanned) <= rescanThreshold) then
|
|
self.items[searchText] = scan;
|
|
end
|
|
end
|
|
|
|
if (not self.items[searchText]) then
|
|
self.items[searchText] = Atr_FindScanAndInit (searchText);
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FindScanAndInit (itemName)
|
|
|
|
return Atr_FindScan (itemName, true);
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FindScan (itemName, init)
|
|
|
|
if (itemName == nil or itemName == "") then
|
|
itemName = "nil";
|
|
end
|
|
|
|
local itemNameLC = string.lower (itemName);
|
|
|
|
if (gAllScans[itemNameLC] == nil) then
|
|
|
|
local scn = {};
|
|
setmetatable (scn, AtrScan);
|
|
scn:Init (itemName);
|
|
|
|
gAllScans[itemNameLC] = scn;
|
|
elseif (init) then
|
|
gAllScans[itemNameLC]:Init (itemName);
|
|
end
|
|
|
|
return gAllScans[itemNameLC];
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_ClearScanCache ()
|
|
|
|
-- zc.msg_red ("Clearing Scan Cache");
|
|
|
|
for a,v in pairs (gAllScans) do
|
|
if (a ~= "nil") then
|
|
gAllScans[a] = nil;
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:Init (itemName)
|
|
self.itemName = itemName;
|
|
self.itemLink = nil;
|
|
self.scanData = {};
|
|
self.sortedData = {};
|
|
self.whenScanned = 0;
|
|
self.lowprices = {BIGNUM, BIGNUM, BIGNUM};
|
|
self.absoluteBest = nil;
|
|
self.itemClass = 0;
|
|
self.itemSubclass = 0;
|
|
self.yourBestPrice = nil;
|
|
self.yourWorstPrice = nil;
|
|
self.numYourSingletons = 0;
|
|
self.itemTextColor = { 1.0, 1.0, 1.0 };
|
|
self.searchWasExact = false;
|
|
|
|
self:UpdateItemLink (Atr_GetItemLink (itemName));
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:UpdateItemLink (itemLink)
|
|
|
|
self.itemLink = itemLink;
|
|
|
|
if (itemLink) then
|
|
|
|
Atr_AddToItemLinkCache (self.itemName, itemLink);
|
|
|
|
local _, _, quality, _, _, sType, sSubType = Atr_GetItemInfo (itemLink);
|
|
|
|
self.itemQuality = quality;
|
|
self.itemClass = Atr_ItemType2AuctionClass (sType);
|
|
self.itemSubclass = Atr_SubType2AuctionSubclass (self.itemClass, sSubType);
|
|
|
|
self.itemTextColor = { 1.0, 1.0, 1.0 };
|
|
|
|
if (quality == 0) then self.itemTextColor = { 0.6, 0.6, 0.6 }; end
|
|
if (quality == 2) then self.itemTextColor = { 0.2, 1.0, 0.0 }; end
|
|
if (quality == 3) then self.itemTextColor = { 0.0, 0.5, 1.0 }; end
|
|
if (quality == 4) then self.itemTextColor = { 0.7, 0.3, 1.0 }; end
|
|
end
|
|
|
|
end
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:NumScans()
|
|
|
|
if (self.sortedScans) then
|
|
return getn(self.sortedScans);
|
|
end
|
|
|
|
local count = 0;
|
|
for name,scn in pairs (self.items) do
|
|
count = count + 1;
|
|
end
|
|
|
|
return count;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:NumSortedScans()
|
|
|
|
if (self.sortedScans) then
|
|
return getn(self.sortedScans);
|
|
end
|
|
|
|
return 0;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:GetFirstScan()
|
|
|
|
if (self.sortedScans) then
|
|
return self.sortedScans[1];
|
|
end
|
|
|
|
for name,scn in pairs (self.items) do
|
|
return scn;
|
|
end
|
|
|
|
return nil;
|
|
|
|
end
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:Start ()
|
|
|
|
if (self.searchText == "") then
|
|
return;
|
|
end
|
|
|
|
if (self:IsCompoundSearch (self.searchText)) then
|
|
|
|
local _, itemClass = self:ParseCompoundSearch (self.searchText);
|
|
|
|
if (itemClass == 0) then
|
|
Atr_Error_Display (ZT("The first part of this compound\n\nsearch is not a valid category."));
|
|
return;
|
|
end
|
|
end
|
|
|
|
self.processing_state = KM_SETTINGSORT;
|
|
|
|
Atr_SortAuctionClearSort ("list");
|
|
|
|
BrowseName:SetText (self.searchText); -- not necessary but nice when user switches to Browse tab
|
|
|
|
self.current_page = 0;
|
|
self.processing_state = KM_PREQUERY;
|
|
|
|
self:Continue();
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:Abort ()
|
|
|
|
if (self.processing_state == KM_NULL_STATE) then
|
|
return;
|
|
end
|
|
|
|
self.processing_state = KM_NULL_STATE;
|
|
self:Init();
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:CheckForDuplicatePage ()
|
|
|
|
local isDup = self.query:CheckForDuplicatePage(self.current_page);
|
|
|
|
if (isDup) then
|
|
-- zc.msg_red ("DUPLICATE PAGE FOUND: ", " current_page: ", self.current_page, " numDupPages: ", self.query.numDupPages);
|
|
|
|
self.current_page = self.current_page - 1; -- requery the page
|
|
|
|
self.processing_state = KM_PREQUERY;
|
|
end
|
|
|
|
return isDup;
|
|
end
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:AnalyzeResultsPage()
|
|
|
|
self.processing_state = KM_ANALYZING;
|
|
|
|
if (self.query.numDupPages > 10) then -- hopefully this will never happen but need check to avoid looping
|
|
return true; -- done
|
|
end
|
|
|
|
|
|
local numBatchAuctions, totalAuctions = GetNumAuctionItems("list");
|
|
|
|
self.totalPages = math.ceil (totalAuctions / 50);
|
|
|
|
if (self.current_page == 1 and totalAuctions > 2000) then -- give Blizz servers a break
|
|
Atr_Error_Display (ZT("Too many results\n\nPlease narrow your search"));
|
|
return true; -- done
|
|
end
|
|
|
|
if (totalAuctions >= 50) then
|
|
Atr_SetMessage (string.format (ZT("Scanning auctions: page %d"), self.current_page).." / "..self.totalPages);
|
|
end
|
|
|
|
-- analyze
|
|
|
|
local numNilOwners = 0;
|
|
|
|
if (numBatchAuctions > 0) then
|
|
|
|
local x;
|
|
|
|
for x = 1, numBatchAuctions do
|
|
|
|
local name, texture, count, quality, canUse, level, minBid, minIncrement, buyoutPrice, bidAmount, highBidder, owner = GetAuctionItemInfo("list", x);
|
|
|
|
if (owner == nil) then
|
|
numNilOwners = numNilOwners + 1;
|
|
end
|
|
|
|
local exactMatch = zc.StringSame (name, self.searchText);
|
|
|
|
if (exactMatch or not self.exact) then
|
|
|
|
if (self.items[name] == nil) then
|
|
self.items[name] = Atr_FindScanAndInit (name);
|
|
end
|
|
|
|
local curpage = (tonumber(self.current_page)-1);
|
|
|
|
local scn = self.items[name];
|
|
|
|
scn:AddScanItem (name, count, buyoutPrice, owner, 1, curpage);
|
|
|
|
if (scn.itemLink == nil or self.itemClass == nil) then
|
|
scn:UpdateItemLink (GetAuctionItemLink("list", x));
|
|
end
|
|
|
|
if (self.callback) then
|
|
self.callback (x, numBatchAuctions, count, buyoutPrice, owner);
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
local done = (numBatchAuctions < 50);
|
|
|
|
if (not done) then
|
|
self.processing_state = KM_PREQUERY;
|
|
end
|
|
|
|
return done;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:AddScanItem (name, stackSize, buyoutPrice, owner, numAuctions, curpage)
|
|
|
|
local sd = {};
|
|
local i;
|
|
|
|
if (numAuctions == nil) then
|
|
numAuctions = 1;
|
|
end
|
|
|
|
for i = 1, numAuctions do
|
|
sd["stackSize"] = stackSize;
|
|
sd["buyoutPrice"] = buyoutPrice;
|
|
sd["owner"] = owner;
|
|
sd["pagenum"] = curpage;
|
|
|
|
tinsert (self.scanData, sd);
|
|
|
|
local itemPrice = math.floor (buyoutPrice / stackSize);
|
|
|
|
Atr_AddToLowPrices (self.lowprices, itemPrice);
|
|
end
|
|
|
|
end
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:AddSDXToScan (price, owner, volume) -- helper function for AddExternalDataToScan
|
|
|
|
local sd = {};
|
|
|
|
if (price and price > 0) then
|
|
sd["stackSize"] = 1;
|
|
sd["buyoutPrice"] = price;
|
|
sd["owner"] = owner;
|
|
|
|
if (volume) then
|
|
sd["volume"] = volume;
|
|
end
|
|
|
|
tinsert (self.scanData, sd);
|
|
end
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:AddExternalDataToScan ()
|
|
|
|
if (self.itemLink == nil) then
|
|
return;
|
|
end
|
|
|
|
-- Wowecon
|
|
|
|
if (Wowecon and Wowecon.API) then
|
|
|
|
local priceG, volG = Wowecon.API.GetAuctionPrice_ByLink (self.itemLink, Wowecon.API.GLOBAL_PRICE)
|
|
local priceS, volS = Wowecon.API.GetAuctionPrice_ByLink (self.itemLink, Wowecon.API.SERVER_PRICE)
|
|
|
|
self:AddSDXToScan (priceG, "__wowEconG", volG);
|
|
self:AddSDXToScan (priceS, "__wowEconS", volS);
|
|
|
|
end
|
|
|
|
-- GoingPrice Wowhead
|
|
|
|
local id = zc.ItemIDfromLink (self.itemLink);
|
|
|
|
id = tonumber(id);
|
|
|
|
if (GoingPrice_Wowhead_Data and GoingPrice_Wowhead_Data[id] and GoingPrice_Wowhead_SV._index) then
|
|
local index = GoingPrice_Wowhead_SV._index["Buyout price"];
|
|
|
|
if (index ~= nil) then
|
|
local price = GoingPrice_Wowhead_Data[id][index];
|
|
|
|
self:AddSDXToScan (price, "__wowHead");
|
|
end
|
|
end
|
|
|
|
-- GoingPrice Allakhazam
|
|
|
|
if (GoingPrice_Allakhazam_Data and GoingPrice_Allakhazam_Data[id] and GoingPrice_Allakhazam_SV._index) then
|
|
local index = GoingPrice_Allakhazam_SV._index["Median"];
|
|
|
|
if (index ~= nil) then
|
|
local price = GoingPrice_Allakhazam_Data[id][index];
|
|
|
|
self:AddSDXToScan (price, "__allakhazam");
|
|
end
|
|
end
|
|
|
|
-- most recent historical price
|
|
|
|
local price = Atr_Process_Historydata();
|
|
if (price ~= nil) then
|
|
self:AddSDXToScan (price, "__atrLast");
|
|
end
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:SubtractScanItem (name, stackSize, buyoutPrice)
|
|
|
|
local sd;
|
|
local i;
|
|
|
|
for i,sd in ipairs (self.scanData) do
|
|
|
|
if (sd.stackSize == stackSize and sd.buyoutPrice == buyoutPrice) then
|
|
|
|
tremove (self.scanData, i);
|
|
return;
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:IsCompoundSearch (searchString)
|
|
|
|
return zc.StringContains (searchString, ">");
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:ParseCompoundSearch (searchString)
|
|
|
|
local tbl = { Atr_strsplit (">", searchString) };
|
|
|
|
local queryString = "";
|
|
local itemClass = 0;
|
|
local itemSubclass = 0;
|
|
local minLevel = nil;
|
|
local maxLevel = nil;
|
|
local prevWasItemClass;
|
|
local n;
|
|
|
|
for n = 1,getn(tbl) do
|
|
local s = tbl[n];
|
|
|
|
local handled = false;
|
|
|
|
if (not handled and tonumber(s)) then
|
|
if (minLevel == nil) then
|
|
minLevel = tonumber(s);
|
|
elseif (maxLevel == nil) then
|
|
maxLevel = tonumber(s);
|
|
end
|
|
|
|
handled = true;
|
|
prevWasItemClass = false;
|
|
end
|
|
|
|
if (not handled and prevWasItemClass and itemSubclass == 0) then
|
|
itemSubclass = Atr_SubType2AuctionSubclass (itemClass, s);
|
|
if (itemSubclass > 0) then
|
|
handled = true;
|
|
prevWasItemClass = false;
|
|
end
|
|
end
|
|
|
|
if (not handled and itemClass == 0) then
|
|
itemClass = Atr_ItemType2AuctionClass (s);
|
|
if (itemClass > 0) then
|
|
prevWasItemClass = true;
|
|
handled = true;
|
|
end
|
|
end
|
|
|
|
if (not handled) then
|
|
queryString = s;
|
|
handled = true;
|
|
end
|
|
end
|
|
|
|
return queryString, itemClass, itemSubclass, minLevel, maxLevel;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:Continue()
|
|
|
|
if (Atr_CanSendAuctionQuery ()) then
|
|
|
|
self.processing_state = KM_IN_QUERY;
|
|
|
|
local queryString = self.searchText;
|
|
|
|
queryString = zc.UTF8_Truncate (queryString,63); -- attempting to reduce number of disconnects
|
|
|
|
-- zc.md (queryString.." page:"..self.current_page);
|
|
|
|
local itemClass = 0;
|
|
local itemSubclass = 0;
|
|
local minLevel = nil;
|
|
local maxLevel = nil;
|
|
|
|
-- (the 3.x port narrowed exact searches by item class here; a plain name query is the safest on 1.12)
|
|
|
|
if (self:IsCompoundSearch(queryString)) then
|
|
|
|
queryString, itemClass, itemSubclass, minLevel, maxLevel = self:ParseCompoundSearch (queryString);
|
|
|
|
end
|
|
|
|
Atr_QueryAuctionItems (queryString, minLevel, maxLevel, nil, itemClass, itemSubclass, self.current_page, nil, nil);
|
|
|
|
self.query_sent_when = gAtr_ptime;
|
|
self.namesWaitStart = nil;
|
|
self.processing_state = KM_POSTQUERY;
|
|
self.current_page = self.current_page + 1;
|
|
end
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
local gSortScansBy;
|
|
|
|
-----------------------------------------
|
|
|
|
local function Atr_SortScans (x, y)
|
|
|
|
if (gSortScansBy == ATR_SORTBY_NAME_ASC) then return string.lower (x.itemName) < string.lower (y.itemName); end
|
|
if (gSortScansBy == ATR_SORTBY_NAME_DES) then return string.lower (x.itemName) > string.lower (y.itemName); end
|
|
|
|
local xprice = 0;
|
|
local yprice = 0;
|
|
|
|
if (x.absoluteBest) then xprice = zc.round(x.absoluteBest.buyoutPrice/x.absoluteBest.stackSize); end;
|
|
if (y.absoluteBest) then yprice = zc.round(y.absoluteBest.buyoutPrice/y.absoluteBest.stackSize); end;
|
|
|
|
if (gSortScansBy == ATR_SORTBY_PRICE_ASC) then return xprice < yprice; end
|
|
if (gSortScansBy == ATR_SORTBY_PRICE_DES) then return xprice > yprice; end
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:Finish()
|
|
|
|
local finishTime = time();
|
|
|
|
self.processing_state = KM_NULL_STATE;
|
|
self.current_page = -1;
|
|
self.query_sent_when = nil;
|
|
|
|
self.sortedScans = nil;
|
|
|
|
local wasExactSearch = (self:NumScans() == 1); -- search returned only 1 item
|
|
|
|
local x = 1;
|
|
self.sortedScans = {};
|
|
|
|
for name,scn in pairs (self.items) do
|
|
|
|
self.sortedScans[x] = scn;
|
|
x = x + 1;
|
|
|
|
scn.whenScanned = finishTime;
|
|
scn.searchWasExact = wasExactSearch;
|
|
|
|
scn:CondenseAndSort ();
|
|
|
|
-- update the fullscan DB
|
|
|
|
local newprice = Atr_CalcNewDBprice (scn.itemName, scn.lowprices);
|
|
|
|
if (newprice > 0) then
|
|
if (scn.itemQuality + 1 >= AUCTIONATOR_SCAN_MINLEVEL) then
|
|
gAtr_ScanDB[scn.itemName] = newprice;
|
|
end
|
|
end
|
|
end
|
|
|
|
Atr_ClearBrowseListings();
|
|
|
|
gSortScansBy = self.sortHow;
|
|
table.sort (self.sortedScans, Atr_SortScans);
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
-- 1.12 port: show what has been found so far while the remaining pages are still coming in
|
|
|
|
function AtrSearch:UpdatePartial()
|
|
|
|
local single = (self:NumScans() == 1);
|
|
local x = 1;
|
|
|
|
self.sortedScans = {};
|
|
|
|
for name,scn in pairs (self.items) do
|
|
self.sortedScans[x] = scn;
|
|
x = x + 1;
|
|
scn.searchWasExact = single;
|
|
scn:CondenseAndSort ();
|
|
end
|
|
|
|
gSortScansBy = self.sortHow;
|
|
table.sort (self.sortedScans, Atr_SortScans);
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:IsActive()
|
|
return (self.processing_state ~= KM_NULL_STATE);
|
|
end
|
|
|
|
-----------------------------------------
|
|
-- a purchase takes over the auction queries: ask for the page whose answer may get lost again later
|
|
|
|
function AtrSearch:Pause()
|
|
|
|
if (self.processing_state == KM_POSTQUERY or self.processing_state == KM_IN_QUERY) then
|
|
self.current_page = self.current_page - 1;
|
|
self.processing_state = KM_PREQUERY;
|
|
end
|
|
|
|
self.namesWaitStart = nil;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:ClickPriceCol()
|
|
|
|
if (self.sortHow == ATR_SORTBY_PRICE_ASC) then
|
|
self.sortHow = ATR_SORTBY_PRICE_DES;
|
|
else
|
|
self.sortHow = ATR_SORTBY_PRICE_ASC;
|
|
end
|
|
|
|
gSortScansBy = self.sortHow;
|
|
table.sort (self.sortedScans, Atr_SortScans);
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:ClickNameCol()
|
|
|
|
if (self.sortHow == ATR_SORTBY_NAME_ASC) then
|
|
self.sortHow = ATR_SORTBY_NAME_DES;
|
|
else
|
|
self.sortHow = ATR_SORTBY_NAME_ASC;
|
|
end
|
|
|
|
gSortScansBy = self.sortHow;
|
|
table.sort (self.sortedScans, Atr_SortScans);
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrSearch:UpdateArrows()
|
|
|
|
Atr_Col1_Heading_ButtonArrow:Hide();
|
|
Atr_Col3_Heading_ButtonArrow:Hide();
|
|
|
|
if (self.sortHow == ATR_SORTBY_PRICE_ASC) then
|
|
Atr_Col1_Heading_ButtonArrow:Show();
|
|
Atr_Col1_Heading_ButtonArrow:SetTexCoord(0, 0.5625, 0, 1.0);
|
|
elseif (self.sortHow == ATR_SORTBY_PRICE_DES) then
|
|
Atr_Col1_Heading_ButtonArrow:Show();
|
|
Atr_Col1_Heading_ButtonArrow:SetTexCoord(0, 0.5625, 1.0, 0);
|
|
elseif (self.sortHow == ATR_SORTBY_NAME_ASC) then
|
|
Atr_Col3_Heading_ButtonArrow:Show();
|
|
Atr_Col3_Heading_ButtonArrow:SetTexCoord(0, 0.5625, 0, 1.0);
|
|
elseif (self.sortHow == ATR_SORTBY_NAME_DES) then
|
|
Atr_Col3_Heading_ButtonArrow:Show();
|
|
Atr_Col3_Heading_ButtonArrow:SetTexCoord(0, 0.5625, 1.0, 0);
|
|
end
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_ClearBrowseListings()
|
|
|
|
-- the 3.x version fired a dummy query to empty the Browse list; on 1.12 that needs a busy wait, so we skip it
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_SortAuctionData (x, y)
|
|
|
|
if (gAtr_PriceSortDesc) then
|
|
return x.itemPrice > y.itemPrice;
|
|
end
|
|
|
|
return x.itemPrice < y.itemPrice;
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:CondenseAndSort ()
|
|
|
|
----- Condense the scan data into a table that has only a single entry per stacksize/price combo
|
|
|
|
self.sortedData = {};
|
|
|
|
local i,sd;
|
|
local conddata = {};
|
|
|
|
for i,sd in ipairs (self.scanData) do
|
|
|
|
local ownerCode = "x";
|
|
local dataType = "n"; -- normal
|
|
|
|
if (sd.owner == UnitName("player")) then
|
|
ownerCode = "y";
|
|
-- elseif (Atr_IsMyToon (sd.owner)) then
|
|
-- ownerCode = sd.owner;
|
|
elseif (sd.owner == "__wowEconG") then
|
|
dataType = "eg";
|
|
elseif (sd.owner == "__wowEconS") then
|
|
dataType = "es";
|
|
elseif (sd.owner == "__wowHead") then
|
|
dataType = "h";
|
|
elseif (sd.owner == "__allakhazam") then
|
|
dataType = "k";
|
|
elseif (sd.owner == "__atrLast") then
|
|
dataType = "a";
|
|
end
|
|
|
|
local key = "_"..sd.stackSize.."_"..sd.buyoutPrice.."_"..ownerCode..dataType;
|
|
|
|
if (conddata[key]) then
|
|
conddata[key].count = conddata[key].count + 1;
|
|
conddata[key].minpage = zc.Min (conddata[key].minpage, sd.pagenum);
|
|
conddata[key].maxpage = zc.Max (conddata[key].maxpage, sd.pagenum);
|
|
else
|
|
local data = {};
|
|
|
|
data.stackSize = sd.stackSize;
|
|
data.buyoutPrice = sd.buyoutPrice;
|
|
data.itemPrice = sd.buyoutPrice / sd.stackSize;
|
|
data.minpage = sd.pagenum;
|
|
data.maxpage = sd.pagenum;
|
|
data.count = 1;
|
|
data.type = dataType;
|
|
data.yours = (ownerCode == "y");
|
|
|
|
if (ownerCode ~= "x" and ownerCode ~= "y") then
|
|
data.altname = ownerCode;
|
|
end
|
|
|
|
if (sd.volume) then
|
|
data.volume = sd.volume;
|
|
end
|
|
|
|
conddata[key] = data;
|
|
end
|
|
|
|
end
|
|
|
|
----- create a table of these entries
|
|
|
|
local n = 1;
|
|
|
|
local i, v;
|
|
|
|
for i,v in pairs (conddata) do
|
|
self.sortedData[n] = v;
|
|
n = n + 1;
|
|
end
|
|
|
|
-- sort the table by itemPrice
|
|
|
|
table.sort (self.sortedData, Atr_SortAuctionData);
|
|
|
|
-- analyze and store some info about the data
|
|
|
|
self:AnalyzeSortData ();
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:AnalyzeSortData ()
|
|
|
|
self.absoluteBest = nil;
|
|
self.bestPrices = {}; -- a table with one entry per stacksize that is the cheapest auction for that particular stacksize
|
|
self.numMatches = 0;
|
|
self.numMatchesWithBuyout = 0;
|
|
self.hasStack = false;
|
|
self.yourBestPrice = nil;
|
|
self.yourWorstPrice = nil;
|
|
self.numYourSingletons = 0;
|
|
|
|
local j, sd;
|
|
|
|
----- find the best price per stacksize and overall -----
|
|
|
|
for j,sd in ipairs(self.sortedData) do
|
|
|
|
if (sd.type == "n") then
|
|
|
|
self.numMatches = self.numMatches + 1;
|
|
|
|
if (sd.itemPrice > 0) then
|
|
|
|
self.numMatchesWithBuyout = self.numMatchesWithBuyout + 1;
|
|
|
|
if (self.bestPrices[sd.stackSize] == nil or self.bestPrices[sd.stackSize].itemPrice >= sd.itemPrice) then
|
|
self.bestPrices[sd.stackSize] = sd;
|
|
end
|
|
|
|
if (self.absoluteBest == nil or self.absoluteBest.itemPrice > sd.itemPrice) then
|
|
self.absoluteBest = sd;
|
|
end
|
|
|
|
if (sd.yours) then
|
|
if (self.yourBestPrice == nil or self.yourBestPrice > sd.itemPrice) then
|
|
self.yourBestPrice = sd.itemPrice;
|
|
end
|
|
|
|
if (self.yourWorstPrice == nil or self.yourWorstPrice < sd.itemPrice) then
|
|
self.yourWorstPrice = sd.itemPrice;
|
|
end
|
|
|
|
if (sd.stackSize == 1) then
|
|
self.numYourSingletons = self.numYourSingletons + sd.count;
|
|
end
|
|
end
|
|
end
|
|
|
|
if (sd.stackSize > 1) then
|
|
self.hasStack = true;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:FindInSortedData (stackSize, buyoutPrice)
|
|
local j = 1;
|
|
for j = 1,getn(self.sortedData) do
|
|
sd = self.sortedData[j];
|
|
if (sd.stackSize == stackSize and sd.buyoutPrice == buyoutPrice and sd.yours) then
|
|
return j;
|
|
end
|
|
end
|
|
|
|
return 0;
|
|
end
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:FindMatchByStackSize (stackSize)
|
|
|
|
local index = nil;
|
|
|
|
local basedata = self.absoluteBest;
|
|
|
|
if (self.bestPrices[stackSize]) then
|
|
basedata = self.bestPrices[stackSize];
|
|
end
|
|
|
|
local numrows = getn(self.sortedData);
|
|
|
|
local n;
|
|
|
|
for n = 1,numrows do
|
|
|
|
local data = self.sortedData[n];
|
|
|
|
if (basedata and data.itemPrice == basedata.itemPrice and data.stackSize == basedata.stackSize and data.yours == basedata.yours) then
|
|
index = n;
|
|
break;
|
|
end
|
|
end
|
|
|
|
return index;
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:FindMatchByYours ()
|
|
|
|
local index = nil;
|
|
|
|
local j;
|
|
for j = 1,getn(self.sortedData) do
|
|
local sd = self.sortedData[j];
|
|
if (sd.yours and (index == nil or sd.itemPrice < self.sortedData[index].itemPrice)) then
|
|
index = j;
|
|
end
|
|
end
|
|
|
|
return index;
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:FindCheapest ()
|
|
|
|
local index = nil;
|
|
|
|
local j;
|
|
for j = 1,getn(self.sortedData) do
|
|
local sd = self.sortedData[j];
|
|
if (sd.itemPrice > 0 and (index == nil or sd.itemPrice < self.sortedData[index].itemPrice)) then
|
|
index = j;
|
|
end
|
|
end
|
|
|
|
return index;
|
|
|
|
end
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:GetNumAvailable ()
|
|
|
|
local num = 0;
|
|
|
|
local j, data;
|
|
for j = 1,getn(self.sortedData) do
|
|
|
|
data = self.sortedData[j];
|
|
num = num + (data.count * data.stackSize);
|
|
end
|
|
|
|
return num;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function AtrScan:IsNil ()
|
|
|
|
if (self.itemName == nil or self.itemName == "" or self.itemName == "nil") then
|
|
return true;
|
|
end
|
|
|
|
return false;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
ATR_FS_NULL = 0;
|
|
ATR_FS_STARTED = 1;
|
|
ATR_FS_ANALYZING = 2;
|
|
ATR_FS_CLEANING_UP = 3;
|
|
|
|
gAtr_FullScanState = ATR_FS_NULL;
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_GetDBsize()
|
|
|
|
local n = 0;
|
|
local a,v;
|
|
|
|
for a,v in pairs (gAtr_ScanDB) do
|
|
n = n + 1;
|
|
end
|
|
|
|
return n;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
local gNumAdded, gNumUpdated;
|
|
|
|
-- The 1.12 client has no "get all" query, so a full scan walks every page of an empty query.
|
|
local gFS_page = 0;
|
|
local gFS_pages = 0;
|
|
local gFS_lowprices = {};
|
|
local gFS_qualities = {};
|
|
local gFS_numAuctions = 0;
|
|
local gFS_waiting = false;
|
|
local gFS_sentAt = 0;
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FullScanStart()
|
|
|
|
if (gAtr_FullScanState == ATR_FS_STARTED) then
|
|
return;
|
|
end
|
|
|
|
Atr_FullScanStatus:SetText (ZT("Scanning").."...");
|
|
Atr_FullScanStartButton:Disable();
|
|
|
|
gAtr_FullScanState = ATR_FS_STARTED;
|
|
|
|
gNumAdded = 0;
|
|
gNumUpdated = 0;
|
|
|
|
gFS_page = 0;
|
|
gFS_pages = 0;
|
|
gFS_lowprices = {};
|
|
gFS_qualities = {};
|
|
gFS_numAuctions = 0;
|
|
gFS_waiting = false;
|
|
|
|
Atr_FullScanResults:Hide();
|
|
Atr_FullScanHTML:Show();
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FullScanAbort()
|
|
|
|
gAtr_FullScanState = ATR_FS_NULL;
|
|
gFS_waiting = false;
|
|
gFS_lowprices = {};
|
|
gFS_qualities = {};
|
|
|
|
Atr_FullScanStatus:SetText (ZT("Scan stopped"));
|
|
Atr_FullScanStartButton:Enable();
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FullScanDone_OnClick()
|
|
|
|
if (gAtr_FullScanState == ATR_FS_STARTED) then
|
|
Atr_FullScanAbort();
|
|
end
|
|
|
|
Atr_FullScanFrame:Hide();
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_CalcNewDBprice (name, prices)
|
|
|
|
if (prices[1] ~= BIGNUM) then
|
|
return prices[1];
|
|
end
|
|
|
|
return 0;
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_AddToLowPrices (lowprices, itemPrice)
|
|
|
|
if (itemPrice > 0) then
|
|
if (itemPrice < lowprices[1]) then
|
|
if (lowprices[1] < lowprices[2]) then
|
|
lowprices[2] = lowprices[1];
|
|
end
|
|
lowprices[1] = itemPrice;
|
|
return true;
|
|
elseif (itemPrice < lowprices[2]) then
|
|
lowprices[2] = itemPrice;
|
|
return true;
|
|
end
|
|
end
|
|
|
|
return false;
|
|
end
|
|
|
|
|
|
|
|
|
|
-----------------------------------------
|
|
|
|
local gScanDetails = {}
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FullScanMoreDetails ()
|
|
|
|
zc.msg (" ");
|
|
zc.msg_atr (ZT("Auctions scanned")..": |cffffffff", gScanDetails.numBatchAuctions, " |r("..gScanDetails.totalItems, "items)");
|
|
zc.msg_atr ("|cffa335ee "..ZT("Epic items")..": |r", gScanDetails.numEachQual[5]);
|
|
zc.msg_atr ("|cff0070dd "..ZT("Rare items")..": |r", gScanDetails.numEachQual[4]);
|
|
zc.msg_atr ("|cff1eff00 "..ZT("Uncommon items")..": |r", gScanDetails.numEachQual[3]);
|
|
zc.msg_atr ("|cffffffff "..ZT("Common items")..": |r", gScanDetails.numEachQual[2]);
|
|
zc.msg_atr ("|cff9d9d9d "..ZT("Poor items")..": |r", gScanDetails.numEachQual[1]);
|
|
|
|
|
|
if (gScanDetails.numRemoved[4] > 0) then zc.msg_atr (ZT("Rare items").." "..ZT("removed from database")..": |cffffffff", gScanDetails.numRemoved[4]); end
|
|
if (gScanDetails.numRemoved[3] > 0) then zc.msg_atr (ZT("Uncommon items").." "..ZT("removed from database")..": |cffffffff", gScanDetails.numRemoved[3]); end
|
|
if (gScanDetails.numRemoved[2] > 0) then zc.msg_atr (ZT("Common items").." "..ZT("removed from database")..": |cffffffff", gScanDetails.numRemoved[2]); end
|
|
if (gScanDetails.numRemoved[1] > 0) then zc.msg_atr (ZT("Poor items").." "..ZT("removed from database")..": |cffffffff", gScanDetails.numRemoved[1]); end
|
|
|
|
zc.msg_atr (ZT("Items added to database")..": |cffffffff", gScanDetails.gNumAdded);
|
|
zc.msg_atr (ZT("Items updated in database")..": |cffffffff", gScanDetails.gNumUpdated);
|
|
zc.msg_atr (ZT("Items ignored")..": |cffffffff", gScanDetails.totalItems - (gScanDetails.gNumAdded + gScanDetails.gNumUpdated));
|
|
zc.msg (" ");
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FullScanAnalyze()
|
|
|
|
if (not gFS_waiting) then
|
|
return;
|
|
end
|
|
|
|
gFS_waiting = false;
|
|
|
|
local numBatchAuctions, totalAuctions = GetNumAuctionItems("list");
|
|
|
|
gFS_pages = math.ceil (totalAuctions / 50);
|
|
|
|
local x;
|
|
|
|
for x = 1, numBatchAuctions do
|
|
|
|
local name, texture, count, quality, canUse, level, minBid, minIncrement, buyoutPrice = GetAuctionItemInfo("list", x);
|
|
|
|
if (name ~= nil and name ~= "") then
|
|
|
|
gFS_qualities[name] = quality;
|
|
|
|
if (buyoutPrice ~= nil and count ~= nil and count > 0) then
|
|
|
|
local itemPrice = math.floor (buyoutPrice / count);
|
|
|
|
if (itemPrice > 0) then
|
|
if (not gFS_lowprices[name]) then
|
|
gFS_lowprices[name] = {BIGNUM,BIGNUM,BIGNUM}; -- one extra for later
|
|
end
|
|
|
|
Atr_AddToLowPrices (gFS_lowprices[name], itemPrice);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
gFS_numAuctions = gFS_numAuctions + numBatchAuctions;
|
|
gFS_page = gFS_page + 1;
|
|
|
|
Atr_FullScanStatus:SetText (ZT("Scanning").." "..gFS_page.." / "..gFS_pages);
|
|
|
|
if (gFS_page >= gFS_pages or numBatchAuctions == 0) then
|
|
Atr_FullScanFinish();
|
|
end
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FullScanFinish()
|
|
|
|
gAtr_FullScanState = ATR_FS_ANALYZING;
|
|
|
|
Atr_FullScanStatus:SetText (ZT("Processing"));
|
|
|
|
local numBatchAuctions = gFS_numAuctions;
|
|
local lowprices = gFS_lowprices;
|
|
local qualities = gFS_qualities;
|
|
|
|
local numEachQual = {0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
local totalItems = 0;
|
|
local numRemoved = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
local name, prices;
|
|
|
|
for name,prices in pairs (lowprices) do
|
|
|
|
local newprice = Atr_CalcNewDBprice (name, prices);
|
|
|
|
if (newprice > 0) then
|
|
|
|
local qx = qualities[name] + 1;
|
|
|
|
numEachQual[qx] = numEachQual[qx] + 1;
|
|
totalItems = totalItems + 1;
|
|
|
|
if (qx < AUCTIONATOR_SCAN_MINLEVEL and gAtr_ScanDB[name]) then
|
|
numRemoved[qx] = numRemoved[qx] + 1;
|
|
gAtr_ScanDB[name] = nil;
|
|
end
|
|
|
|
if (qx >= AUCTIONATOR_SCAN_MINLEVEL) then
|
|
|
|
if (gAtr_ScanDB[name] == nil) then
|
|
gNumAdded = gNumAdded + 1;
|
|
else
|
|
gNumUpdated = gNumUpdated + 1;
|
|
end
|
|
|
|
gAtr_ScanDB[name] = newprice;
|
|
end
|
|
end
|
|
end
|
|
|
|
gScanDetails.numBatchAuctions = numBatchAuctions;
|
|
gScanDetails.totalItems = totalItems;
|
|
gScanDetails.numEachQual = numEachQual;
|
|
gScanDetails.numRemoved = numRemoved;
|
|
gScanDetails.gNumAdded = gNumAdded;
|
|
gScanDetails.gNumUpdated = gNumUpdated;
|
|
|
|
Atr_FullScanMoreDetails();
|
|
|
|
Atr_FullScanStartButton:Enable();
|
|
Atr_FullScanDone:Enable();
|
|
Atr_FullScanStatus:SetText (ZT("Scan complete"));
|
|
|
|
Atr_FSR_scanned_count:SetText (numBatchAuctions);
|
|
Atr_FSR_added_count:SetText (gNumAdded);
|
|
Atr_FSR_updated_count:SetText (gNumUpdated);
|
|
Atr_FSR_ignored_count:SetText (totalItems - (gNumAdded + gNumUpdated));
|
|
|
|
Atr_FullScanHTML:Hide();
|
|
Atr_FullScanResults:Show();
|
|
|
|
Atr_FullScanResults:SetBackdropColor (0.3, 0.3, 0.4);
|
|
|
|
AUCTIONATOR_LAST_SCAN_TIME = time();
|
|
|
|
Atr_UpdateFullScanFrame ();
|
|
|
|
PlaySound("AuctionWindowClose");
|
|
|
|
gFS_lowprices = {};
|
|
gFS_qualities = {};
|
|
gAtr_FullScanState = ATR_FS_NULL;
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function auctionator_AuctionFrameBrowse_Update ()
|
|
|
|
return auctionator_orig_AuctionFrameBrowse_Update ();
|
|
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_ShowFullScanFrame()
|
|
|
|
Atr_FullScanHTML:Show();
|
|
Atr_FullScanResults:Hide();
|
|
|
|
Atr_FullScanFrame:Show();
|
|
Atr_FullScanFrame:SetBackdropColor(0,0,0,100);
|
|
|
|
Atr_UpdateFullScanFrame();
|
|
Atr_FullScanStatus:SetText ("");
|
|
|
|
local expText = "<html><body>"
|
|
.."<p>"
|
|
..ZT("Scanning is entirely optional.")
|
|
.."<br/><br/>"
|
|
..ZT("SCAN_EXPLANATION")
|
|
.."</p>"
|
|
.."</body></html>"
|
|
;
|
|
|
|
|
|
|
|
Atr_FullScanHTML:SetText (expText);
|
|
Atr_FullScanHTML:SetSpacing (3);
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_UpdateFullScanFrame()
|
|
|
|
Atr_FullScanDBsize:SetText (Atr_GetDBsize());
|
|
|
|
if (AUCTIONATOR_LAST_SCAN_TIME) then
|
|
Atr_FullScanDBwhen:SetText (date ("%A, %B %d at %I:%M %p", AUCTIONATOR_LAST_SCAN_TIME));
|
|
else
|
|
Atr_FullScanDBwhen:SetText (ZT("Never"));
|
|
end
|
|
|
|
Atr_FullScanNext:SetText(ZT("Now"));
|
|
|
|
if (gAtr_FullScanState ~= ATR_FS_STARTED) then
|
|
Atr_FullScanStartButton:Enable();
|
|
end
|
|
end
|
|
|
|
-----------------------------------------
|
|
|
|
function Atr_FullScanFrameIdle()
|
|
|
|
if (gAtr_FullScanState == ATR_FS_STARTED) then
|
|
|
|
if (not AuctionFrame or not AuctionFrame:IsShown()) then
|
|
Atr_FullScanAbort();
|
|
return;
|
|
end
|
|
|
|
if (gFS_waiting and GetTime() - gFS_sentAt > 20) then -- no answer: ask again
|
|
gFS_waiting = false;
|
|
end
|
|
|
|
if (not gFS_waiting and Atr_CanSendAuctionQuery()) then
|
|
gFS_waiting = true;
|
|
gFS_sentAt = GetTime();
|
|
Atr_QueryAuctionItems ("", nil, nil, 0, 0, 0, gFS_page, 0, 0);
|
|
end
|
|
end
|
|
|
|
end
|