diff --git a/Shopkeeper.lua b/Shopkeeper.lua index 5a059e6..d477a48 100644 --- a/Shopkeeper.lua +++ b/Shopkeeper.lua @@ -1,5 +1,5 @@ -- Shopkeeper Main Addon File --- Last Updated August 4, 2014 +-- Last Updated August 8, 2014 -- Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com -- Released under terms in license accompanying this file. -- Distribution without license is prohibited! @@ -653,7 +653,7 @@ function Shopkeeper.SalesStats(statsDays) local timeWindow = newestTime - oldestTime local dayWindow = 1 if timeWindow > 86400 then dayWindow = math.floor(timeWindow / 86400) + 1 end - local goldPerDay = goldMade / dayWindow + local goldPerDay = math.floor(goldMade / dayWindow) -- If they have the option set to show prices post-cut, calculate that here if not Shopkeeper.savedVariables.showFullPrice then @@ -899,7 +899,11 @@ function Shopkeeper.SwitchViewMode() Shopkeeper.viewMode = "self" end - Shopkeeper.DoSearch(ShopkeeperWindowSearchBox:GetText()) + if Shopkeeper.savedVariables.viewSize == "full" then + Shopkeeper.DoSearch(ShopkeeperWindowSearchBox:GetText()) + else + Shopkeeper.DoSearch(ShopkeeperMiniWindowSearchBox:GetText()) + end end function Shopkeeper.SwitchPriceMode() @@ -925,63 +929,6 @@ function Shopkeeper.SwitchPriceMode() end --- Actually carries out of the scan of a specific guild store's sales history. --- If checkOlder is true, will request older events first if there are any. --- Inserts all events that occurred after guildID guild's last scan into the ScanResults table. --- Inserts all events that occurred after guildID guild's last scan and were sold by the player --- into the SelfSales table. -function Shopkeeper:DoScan(guildID, checkOlder) - local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_SALES) - local thePlayer = string.lower(Shopkeeper.GetAccountName()) - local timeStamp = GetTimeStamp() - for i = 0, numEvents do - local theEvent = {} - _, theEvent.secsSince, theEvent.seller, theEvent.buyer, - theEvent.quant, theEvent.itemName, theEvent.salePrice = GetGuildEventInfo(guildID, GUILD_HISTORY_SALES, i) - theEvent.guild = GetGuildName(guildID) - -- Only worry about items sold since our last scan - if theEvent.secsSince ~= nil then - theEvent.saleTime = timeStamp - theEvent.secsSince - - if Shopkeeper.acctSavedVariables.lastScan[guildID] == nil or theEvent.saleTime > Shopkeeper.acctSavedVariables.lastScan[guildID] then - if theEvent.itemName ~= nil and theEvent.seller ~= nil and theEvent.buyer ~= nil and theEvent.salePrice ~= nil then - -- Grab the icon - local itemIcon, _, _, _ = GetItemLinkInfo(theEvent.itemName) - - -- If the seller is the player and this isn't a deep scan (and thus the first upon login or reset), - -- queue up an alert - if not checkOlder and (Shopkeeper.savedVariables.showChatAlerts or Shopkeeper.savedVariables.showAnnounceAlerts) - and string.lower(theEvent.seller) == thePlayer then - table.insert(Shopkeeper.alertQueue, theEvent) - end - - -- Insert the entry into the ScanResults table - table.insert(Shopkeeper.ScanResults, {theEvent.buyer, theEvent.guild, theEvent.itemName, itemIcon, - theEvent.quant, theEvent.saleTime, theEvent.salePrice, - theEvent.seller}) - - -- And then, if it's the player's sale, insert into that table - if string.lower(theEvent.seller) == thePlayer then - table.insert(Shopkeeper.SelfSales, {theEvent.buyer, theEvent.guild, theEvent.itemName, itemIcon, - theEvent.quant, theEvent.saleTime, theEvent.salePrice, - theEvent.seller}) - end - end - end - end - end - - Shopkeeper.acctSavedVariables.lastScan[guildID] = GetTimeStamp() - if guildID < GetNumGuilds() then - if checkOlder and DoesGuildHistoryCategoryHaveMoreEvents((guildID + 1), GUILD_HISTORY_SALES) then - RequestGuildHistoryCategoryNewest((guildID + 1), GUILD_HISTORY_SALES) - RequestGuildHistoryCategoryOlder((guildID + 1), GUILD_HISTORY_SALES) - else - RequestGuildHistoryCategoryNewest((guildID + 1), GUILD_HISTORY_SALES) - end - end -end - -- Called after store scans complete, updates the search table -- and slider range, then sorts the fresh table. -- Once this is done writes out to the saved variables scan history @@ -989,7 +936,11 @@ end -- the scan was initiated via the 'refresh' button. function Shopkeeper:PostScan(doAlert) Shopkeeper.isScanning = false - Shopkeeper.DoSearch(ShopkeeperWindowSearchBox:GetText()) + if Shopkeeper.savedVariables.viewSize == "full" then + Shopkeeper.DoSearch(ShopkeeperWindowSearchBox:GetText()) + else + Shopkeeper.DoSearch(ShopkeeperMiniWindowSearchBox:GetText()) + end -- Scale the slider's range to the number of items we have minus the number of rows local sliderMax = 0 local tableToUse = Shopkeeper.ScanResults @@ -1052,16 +1003,16 @@ function Shopkeeper:PostScan(doAlert) -- single item sold vs. multiple of an item sold. if Shopkeeper.locale == "de" then if theEvent.quant > 1 then - CENTER_SCREEN_ANNOUNCE:DisplayMessage(CSA_EVENT_SMALL_TEXT, alertSound, + CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_SMALL_TEXT, alertSound, string.format(Shopkeeper.translate('salesAlertColor'), theEvent.quant, zo_strformat("<<t:1>>", theEvent.itemName), stringPrice, theEvent.guild, Shopkeeper.textTimeSince(theEvent.saleTime, true))) else - CENTER_SCREEN_ANNOUNCE:DisplayMessage(CSA_EVENT_SMALL_TEXT, alertSound, + CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_SMALL_TEXT, alertSound, string.format(Shopkeeper.translate('salesAlertColorSingle'), zo_strformat("<<t:1>>", theEvent.itemName), stringPrice, theEvent.guild, Shopkeeper.textTimeSince(theEvent.saleTime, true))) end else - CENTER_SCREEN_ANNOUNCE:DisplayMessage(CSA_EVENT_SMALL_TEXT, alertSound, + CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_SMALL_TEXT, alertSound, string.format(Shopkeeper.translate('salesAlertColor'), zo_strformat("<<t:1>>", theEvent.itemName), theEvent.quant, stringPrice, theEvent.guild, Shopkeeper.textTimeSince(theEvent.saleTime, true))) end @@ -1091,7 +1042,7 @@ function Shopkeeper:PostScan(doAlert) local stringPrice = Shopkeeper.localizedNumber(totalGold) if Shopkeeper.savedVariables.showAnnounceAlerts then - CENTER_SCREEN_ANNOUNCE:DisplayMessage(CSA_EVENT_SMALL_TEXT, Shopkeeper.savedVariables.alertSoundName, + CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_SMALL_TEXT, Shopkeeper.savedVariables.alertSoundName, string.format(Shopkeeper.translate('salesGroupAlertColor'), numSold, stringPrice)) else CHAT_SYSTEM:AddMessage(string.format("[Shopkeeper] " .. Shopkeeper.translate('salesGroupAlert'), @@ -1104,36 +1055,130 @@ function Shopkeeper:PostScan(doAlert) Shopkeeper.DisplayRows() end --- Scans all stores a player has access to with 2-second delays between them. --- Idea for spaced callbacks taken from awesomebilly's Luminary Trade/Sales --- History addon. +-- Actually carries out of the scan of a specific guild store's sales history. +-- If checkOlder is true, will request older events first if there are any. +-- Inserts all events that occurred after guildID guild's last scan into the ScanResults table. +-- Inserts all events that occurred after guildID guild's last scan and were sold by the player +-- into the SelfSales table. +function Shopkeeper:DoScan(guildID, checkOlder, doAlert) + local timeWindow = 10 + if Shopkeeper.missedLastScan[guildID] ~= nil and Shopkeeper.missedLastScan[guildID] == true then + timeWindow = 25 + end + local thePlayer = string.lower(Shopkeeper.GetAccountName()) + local numEvents = GetNumGuildEvents(guildID, GUILD_HISTORY_SALES) + for i = 1, numEvents do + local theEvent = {} + _, theEvent.secsSince, theEvent.seller, theEvent.buyer, + theEvent.quant, theEvent.itemName, theEvent.salePrice, _ = GetGuildEventInfo(guildID, GUILD_HISTORY_SALES, i) + theEvent.guild = GetGuildName(guildID) + -- Only worry about items sold since our last scan + if theEvent.secsSince ~= nil then + theEvent.saleTime = Shopkeeper.requestTimestamp - theEvent.secsSince + + if Shopkeeper.acctSavedVariables.lastScan[guildID] == nil or GetDiffBetweenTimeStamps(theEvent.saleTime, (Shopkeeper.acctSavedVariables.lastScan[guildID] - timeWindow)) >= 0 then + if theEvent.itemName ~= nil and theEvent.seller ~= nil and theEvent.buyer ~= nil and theEvent.salePrice ~= nil then + -- Grab the icon + local itemIcon, _, _, _ = GetItemLinkInfo(theEvent.itemName) + + -- If the seller is the player and this isn't a deep scan (and thus the first upon login or reset), + -- queue up an alert + if not checkOlder and (Shopkeeper.savedVariables.showChatAlerts or Shopkeeper.savedVariables.showAnnounceAlerts) + and string.lower(theEvent.seller) == thePlayer then + table.insert(Shopkeeper.alertQueue, theEvent) + end + + -- Insert the entry into the ScanResults table + table.insert(Shopkeeper.ScanResults, {theEvent.buyer, theEvent.guild, theEvent.itemName, itemIcon, + theEvent.quant, theEvent.saleTime, theEvent.salePrice, + theEvent.seller}) + + -- And then, if it's the player's sale, insert into that table + if string.lower(theEvent.seller) == thePlayer then + table.insert(Shopkeeper.SelfSales, {theEvent.buyer, theEvent.guild, theEvent.itemName, itemIcon, + theEvent.quant, theEvent.saleTime, theEvent.salePrice, + theEvent.seller}) + end + end + end + end + end + + Shopkeeper.acctSavedVariables.lastScan[guildID] = Shopkeeper.requestTimestamp + if guildID < GetNumGuilds() then + local nextGuild = guildID + 1 + local didScan = RequestGuildHistoryCategoryNewest(nextGuild, GUILD_HISTORY_SALES) + if Shopkeeper.missedScan[nextGuild] ~= nil then Shopkeeper.missedLastScan[nextGuild] = Shopkeeper.missedScan[nextGuild] + else Shopkeeper.missedLastScan[nextGuild] = false end + + if not didScan then + Shopkeeper.missedScan[nextGuild] = true + else + Shopkeeper.missedScan[nextGuild] = false + end + Shopkeeper.requestTimestamp = GetTimeStamp() + if checkOlder then + zo_callLater(function() Shopkeeper:ScanOlder(nextGuild, doAlert) end, 1250) + else + zo_callLater(function() Shopkeeper:DoScan(nextGuild , false, doAlert) end, 1250) + end + else + zo_callLater(function() Shopkeeper:PostScan(doAlert) end, 1250) + end +end + +-- Repeatedly checks for older events until there aren't anymore or we've run past +-- the timestamp of the last scan, then called DoScan to pick up sales events +function Shopkeeper:ScanOlder(guildNum, doAlert) + local numEvents = GetNumGuildEvents(guildNum, GUILD_HISTORY_SALES) + local _, secsSince, _, _, _, _, _, _ = GetGuildEventInfo(guildNum, GUILD_HISTORY_SALES, numEvents) + local lastScan = 0 + if Shopkeeper.acctSavedVariables.lastScan[guildNum] ~= nil then lastScan = Shopkeeper.acctSavedVariables.lastScan[guildNum] end + if not DoesGuildHistoryCategoryHaveMoreEvents(guildNum, GUILD_HISTORY_SALES) or + not RequestGuildHistoryCategoryOlder(guildNum, GUILD_HISTORY_SALES) or + GetDiffBetweenTimeStamps(lastScan, GetTimeStamp() - secsSince) > 0 then + zo_callLater(function() Shopkeeper:DoScan(guildNum, true, doAlert) end, 1250) + else + zo_callLater(function() Shopkeeper:ScanOlder(guildNum, doAlert) end, 1250) + end +end + +-- Scans all stores a player has access to with delays between them. function Shopkeeper:ScanStores(checkOlder, doAlert) -- If it's been less than a minute since we last scanned the store, -- don't do it again so we don't hammer the server either accidentally -- or on purpose - local timeLimit = GetTimeStamp() - 60 + local timeLimit = GetTimeStamp() - 59 local guildNum = GetNumGuilds() - if not Shopkeeper.isScanning and (Shopkeeper.acctSavedVariables.lastScan[guildNum] == nil or timeLimit > Shopkeeper.acctSavedVariables.lastScan[guildNum]) then - -- Nothing to scan! - if guildNum == 0 then return end - + -- Nothing to scan! + if guildNum == 0 then return end + if not Shopkeeper.isScanning and (Shopkeeper.acctSavedVariables.lastScan[1] == nil or timeLimit > Shopkeeper.acctSavedVariables.lastScan[1]) then Shopkeeper.isScanning = true - if checkOlder and DoesGuildHistoryCategoryHaveMoreEvents(1, GUILD_HISTORY_SALES) then - RequestGuildHistoryCategoryNewest(1, GUILD_HISTORY_SALES) - RequestGuildHistoryCategoryOlder(1, GUILD_HISTORY_SALES) + + -- We'll track if we get a true back here or not, because if it's false, + -- someone checked this history manually recently and times may be off + -- by up to 10-15 seconds because of that, and the next scan may get items + -- that should have been caught by this one + local didScan = RequestGuildHistoryCategoryNewest(1, GUILD_HISTORY_SALES) + + if Shopkeeper.missedScan[1] ~= nil then + Shopkeeper.missedLastScan[1] = Shopkeeper.missedScan[1] + else + Shopkeeper.missedLastScan[1] = false + end + + if not didScan then + Shopkeeper.missedScan[1] = true else - RequestGuildHistoryCategoryNewest(1, GUILD_HISTORY_SALES) + Shopkeeper.missedScan[1] = false end - - for j = 1, guildNum do - local guildID = GetGuildId(j) - - -- I need a better way to space out these checks than callbacks - -- It works but feels ugly - zo_callLater(function() Shopkeeper:DoScan(guildID, checkOlder) end, (((j - 1) * 2000) + 1000)) + + Shopkeeper.requestTimestamp = GetTimeStamp() + if checkOlder then + zo_callLater(function() Shopkeeper:ScanOlder(1, doAlert) end, 1250) + else + zo_callLater(function() Shopkeeper:DoScan(1, checkOlder, doAlert) end, 1250) end - -- Once scans are done, wait a few seconds and do some cleanup - zo_callLater(function() Shopkeeper:PostScan(doAlert) end, ((guildNum + 1) * 2000)) end end @@ -1154,7 +1199,7 @@ function Shopkeeper.DoRefresh() -- or on purpose local timeLimit = timeStamp - 59 local guildNum = GetNumGuilds() - if timeLimit > Shopkeeper.acctSavedVariables.lastScan[guildNum] then + if Shopkeeper.acctSavedVariables.lastScan[1] == nil or timeLimit > Shopkeeper.acctSavedVariables.lastScan[1] then CHAT_SYSTEM:AddMessage("[Shopkeeper] " .. Shopkeeper.translate('refreshStart')) Shopkeeper:ScanStores(false, true) @@ -1625,11 +1670,4 @@ end EVENT_MANAGER:RegisterForEvent(Shopkeeper.name, EVENT_ADD_ON_LOADED, Shopkeeper.OnAddOnLoaded) -- Set up /shopkeeper as a slash command toggle for the main window -SLASH_COMMANDS["/shopkeeper"] = function() - if ShopkeeperWindow:IsHidden() then - Shopkeeper.DisplayRows() - SetGameCameraUIMode(true) - end - - ShopkeeperWindow:SetHidden(not ShopkeeperWindow:IsHidden()) -end \ No newline at end of file +SLASH_COMMANDS["/shopkeeper"] = function() Shopkeeper.ToggleShopkeeperWindow() end diff --git a/Shopkeeper.txt b/Shopkeeper.txt index 2bef8ca..1bb232d 100644 --- a/Shopkeeper.txt +++ b/Shopkeeper.txt @@ -2,9 +2,9 @@ ## APIVersion: 100008 ## Description: Notifies you when you've sold something in a guild store and presents guild sales info in a convenient table. ## Author: Dan Stone (@khaibit) - dankitymao@gmail.com -## Version: 0.9 +## Version: 0.9a ## License: See license - distribution without license is prohibited! -## LastUpdated: August 4, 2014 +## LastUpdated: August 8, 2014 ## SavedVariables: ShopkeeperSavedVars ## OptionalDependsOn: LibAddonMenu-2.0 LibMediaProvider-1.0 LibStub diff --git a/Shopkeeper.xml b/Shopkeeper.xml index d277117..e1d8b7f 100644 --- a/Shopkeeper.xml +++ b/Shopkeeper.xml @@ -1,6 +1,6 @@ <!-- Shopkeeper UI Layout File - Last Updated August 4, 2014 + Last Updated August 8, 2014 Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com Released under terms in license accompanying this file. Distribution without license is prohibited! diff --git a/Shopkeeper_Namespace_Init.lua b/Shopkeeper_Namespace_Init.lua index ea55f4b..a9fe120 100644 --- a/Shopkeeper_Namespace_Init.lua +++ b/Shopkeeper_Namespace_Init.lua @@ -1,12 +1,12 @@ -- Shopkeeper Namespace Setup --- Last Updated August 4, 2014 +-- Last Updated August 8, 2014 -- Written July 2014 by Dan Stone (@khaibit) - dankitymao@gmail.com -- Released under terms in license accompanying this file. -- Distribution without license is prohibited! Shopkeeper = {} Shopkeeper.name = "Shopkeeper" -Shopkeeper.version = "0.9" +Shopkeeper.version = "0.9a" Shopkeeper.locale = "en" Shopkeeper.viewMode = "self" Shopkeeper.isScanning = false @@ -19,6 +19,8 @@ Shopkeeper.MiniDataRows = {} Shopkeeper.ScanResults = {} Shopkeeper.SelfSales = {} Shopkeeper.SearchTable = {} +Shopkeeper.missedScan = {} +Shopkeeper.missedLastScan = {} Shopkeeper.alertQueue = {} Shopkeeper.shopSlider = {} Shopkeeper.curSort = {"time", "desc"} diff --git a/readme b/readme index 1773a47..6b715aa 100644 --- a/readme +++ b/readme @@ -4,12 +4,11 @@ trademarks or trademarks of ZeniMax Media Inc. in the United States and/or other countries. All rights reserved. -Known Issues August 4, 2014: - The API calls to retrieve store sales histories currently return the most recent 100 sales from each guild. The - function to retrieve older items behaves...inconsistently. If you are part of a busy guild and/or log on infrequently, - Shopkeeper may miss some of your sales. If this is the case, you'll notice your sales also don't show up in the actual - guild sales history window either - I can't report on what the servers won't tell me =\ I'm aware of it and trying to - find a workaround! +Changelog for 0.9a + Rewrite of part of the scanning routines to be more accurate + Fixes for odd behavior in the stats window + Fixes for the "Alert flood" issue if you sell multiple items between scans + Misc. other small bugfixes Changelog for 0.9 (version jump due to being nearly feature-complete): Added a new smaller view mode for the main window