You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1268 lines
43 KiB
1268 lines
43 KiB
|
4 years ago
|
-- ------------------------------------------------------------------------------ --
|
||
|
|
-- TradeSkillMaster_Accounting --
|
||
|
|
-- http://www.curse.com/addons/wow/tradeskillmaster_accounting --
|
||
|
|
-- --
|
||
|
|
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
|
||
|
|
-- All Rights Reserved* - Detailed license information included with addon. --
|
||
|
|
-- ------------------------------------------------------------------------------ --
|
||
|
|
|
||
|
|
-- create a local reference to the TradeSkillMaster_Accounting table and register a new module
|
||
|
|
local TSM = select(2, ...)
|
||
|
|
local Data = TSM:NewModule("Data", "AceEvent-3.0", "AceHook-3.0")
|
||
|
|
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster_Accounting") -- loads the localization table
|
||
|
|
local LibParse = LibStub("LibParse")
|
||
|
|
local private = {}
|
||
|
|
TSMAPI:RegisterForTracing(private, "TSM_Accounting.Data_private")
|
||
|
|
|
||
|
|
local SECONDS_PER_DAY = 24 * 60 * 60
|
||
|
|
local TIME_BUCKET = 300 -- group sales/buys within 5 minutes together
|
||
|
|
local REPAIR_COST, REPAIR_MONEY, COULD_REPAIR, CAN_REPAIR = 0, 0, false, ""
|
||
|
|
local expired = AUCTION_EXPIRED_MAIL_SUBJECT:gsub("%%s", "")
|
||
|
|
local cancelled = AUCTION_REMOVED_MAIL_SUBJECT:gsub("%%s", "")
|
||
|
|
local outbid = AUCTION_OUTBID_MAIL_SUBJECT:gsub("%%s", "(.+)")
|
||
|
|
|
||
|
|
--[[
|
||
|
|
****************************** In-Memory Data Layout ***************************
|
||
|
|
TSM = {
|
||
|
|
items = {
|
||
|
|
[itemString] = {
|
||
|
|
sales = {
|
||
|
|
-- Possible keys: Auction, COD, Trade, Vendor
|
||
|
|
{key="...", stackSize=#, quantity=#, time=#, copper=#, player="...", otherPlayer="..."},
|
||
|
|
...
|
||
|
|
},
|
||
|
|
buys = {
|
||
|
|
-- Possible keys: Auction, COD, Trade, Vendor
|
||
|
|
{key="...", stackSize=#, quantity=#, time=#, copper=#, player="...", otherPlayer="..."},
|
||
|
|
...
|
||
|
|
},
|
||
|
|
auctions = {
|
||
|
|
-- Possible keys: Cancel, Expire
|
||
|
|
{key="...", stackSize=#, quantity=#, time=#, player="..."},
|
||
|
|
...
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
money = {
|
||
|
|
income = {
|
||
|
|
-- Possible keys: Transfer
|
||
|
|
{key="...", copper=#, time=#, player="...", otherPlayer="..."},
|
||
|
|
},
|
||
|
|
expense = {
|
||
|
|
-- Possible keys: Postage, Repair, Transfer
|
||
|
|
{key="...", copper=#, time=#, player="...", otherPlayer="..."},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
]]
|
||
|
|
|
||
|
|
function private:CleanRecord(record)
|
||
|
|
record.itemName = nil
|
||
|
|
record.itemString = nil
|
||
|
|
record.time = floor(record.time)
|
||
|
|
record.type = nil
|
||
|
|
record.otherPlayer = record.buyer or record.seller or record.destination or record.source or "?"
|
||
|
|
record.copper = record.price or record.amount
|
||
|
|
record.price = nil
|
||
|
|
record.amount = nil
|
||
|
|
record.buyer = nil
|
||
|
|
record.seller = nil
|
||
|
|
record.destination = nil
|
||
|
|
record.source = nil
|
||
|
|
end
|
||
|
|
function private:LoadItemRecords(csvData, recordType, key)
|
||
|
|
local saveTimeIndex = 1
|
||
|
|
local saveTimes
|
||
|
|
if recordType == "sales" then
|
||
|
|
saveTimes = TSMAPI:SafeStrSplit(TSM.db.factionrealm.saveTimeSales, ",")
|
||
|
|
elseif recordType == "buys" then
|
||
|
|
saveTimes = TSMAPI:SafeStrSplit(TSM.db.factionrealm.saveTimeBuys, ",")
|
||
|
|
end
|
||
|
|
for _, record in ipairs(select(2, LibParse:CSVDecode(csvData)) or {}) do
|
||
|
|
local itemString = record.itemString
|
||
|
|
if itemString and type(record.time) == "number" then
|
||
|
|
local itemName = (record.itemName ~= "?") and record.itemName or nil
|
||
|
|
itemName = itemName or TSMAPI:GetSafeItemInfo(itemString) or TSM:GetItemName(itemString)
|
||
|
|
record.key = key or record.source or "Auction"
|
||
|
|
private:CleanRecord(record)
|
||
|
|
if saveTimes and record.key == "Auction" then
|
||
|
|
record.saveTime = tonumber(saveTimes[saveTimeIndex])
|
||
|
|
saveTimeIndex = saveTimeIndex + 1
|
||
|
|
end
|
||
|
|
TSM.items[itemString] = TSM.items[itemString] or {sales={}, buys={}, auctions={}}
|
||
|
|
TSM.items[itemString].name = TSM.items[itemString].name or itemName
|
||
|
|
tinsert(TSM.items[itemString][recordType], record)
|
||
|
|
TSM.cache[itemString] = {}
|
||
|
|
end
|
||
|
|
end
|
||
|
|
for itemString in ipairs(TSM.items) do
|
||
|
|
sort(TSM.items[itemString][recordType], function(a, b) return (a.time or 0) < (b.time or 0) end)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function private:LoadMoneyRecords(csvData, recordType)
|
||
|
|
TSM.money[recordType] = {}
|
||
|
|
local typeTranslation = {}
|
||
|
|
if recordType == "income" then
|
||
|
|
typeTranslation = {["Money Transfer"]="Transfer"}
|
||
|
|
elseif recordType == "expense" then
|
||
|
|
typeTranslation = {["Money Transfer"]="Transfer", ["Postage"]="Postage", ["Repair Bill"]="Repair"}
|
||
|
|
end
|
||
|
|
for _, record in ipairs(select(2, LibParse:CSVDecode(csvData)) or {}) do
|
||
|
|
record.key = typeTranslation[record.type] or "Transfer"
|
||
|
|
if record.key and type(record.time) == "number" then
|
||
|
|
private:CleanRecord(record)
|
||
|
|
tinsert(TSM.money[recordType], record)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function Data:Load()
|
||
|
|
-- Decode item records
|
||
|
|
TSM.items = {}
|
||
|
|
TSM.cache = {}
|
||
|
|
private:LoadItemRecords(TSM.db.factionrealm.csvSales, "sales")
|
||
|
|
private:LoadItemRecords(TSM.db.factionrealm.csvBuys, "buys")
|
||
|
|
private:LoadItemRecords(TSM.db.factionrealm.csvCancelled, "auctions", "Cancel")
|
||
|
|
private:LoadItemRecords(TSM.db.factionrealm.csvExpired, "auctions", "Expire")
|
||
|
|
|
||
|
|
-- Decode money records
|
||
|
|
TSM.money = {}
|
||
|
|
private:LoadMoneyRecords(TSM.db.factionrealm.csvIncome, "income")
|
||
|
|
private:LoadMoneyRecords(TSM.db.factionrealm.csvExpense, "expense")
|
||
|
|
|
||
|
|
-- Decode the gold log
|
||
|
|
for player, data in pairs(TSM.db.factionrealm.goldLog) do
|
||
|
|
if type(data) == "string" then
|
||
|
|
TSM.db.factionrealm.goldLog[player] = select(2, LibParse:CSVDecode(data))
|
||
|
|
end
|
||
|
|
end
|
||
|
|
Data:SetupDataTracking()
|
||
|
|
Data:PopulateDataCaches()
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data:SetupDataTracking()
|
||
|
|
Data:RawHook("TakeInboxItem", function(...) Data:ScanCollectedMail("TakeInboxItem", 1, ...) end, true)
|
||
|
|
Data:RawHook("TakeInboxMoney", function(...) Data:ScanCollectedMail("TakeInboxMoney", 1, ...) end, true)
|
||
|
|
Data:RawHook("AutoLootMailItem", function(...) Data:ScanCollectedMail("AutoLootMailItem", 1, ...) end, true)
|
||
|
|
Data:RawHook("SendMail", function(...) Data:CheckSendMail("SendMail", ...) end, true)
|
||
|
|
Data:SecureHook("UseContainerItem", function(...) Data:CheckMerchantSale(...) end)
|
||
|
|
Data:SecureHook("BuyMerchantItem", function(...) Data:BuyMerchantItem(...) end)
|
||
|
|
Data:SecureHook("BuybackItem", function(...) Data:BuybackMerchantItem(...) end)
|
||
|
|
Data:RegisterEvent("AUCTION_OWNED_LIST_UPDATE", "ScanAuctionItems")
|
||
|
|
Data:RegisterEvent("MERCHANT_SHOW", "SetupRepairCost")
|
||
|
|
Data:RegisterEvent("MERCHANT_UPDATE", "ResetRepairMoney")
|
||
|
|
Data:RegisterEvent("UPDATE_INVENTORY_DURABILITY", "AddRepairCosts")
|
||
|
|
Data:RegisterEvent("MAIL_SHOW")
|
||
|
|
Data:RegisterEvent("MAIL_CLOSED")
|
||
|
|
TSMAPI:RegisterForBagChange(function(...) Data:ScanBagItems(...) end)
|
||
|
|
TSMAPI:CreateFunctionRepeat("accountingGoldTracking", Data.LogGold)
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:RequestSellerInfo()
|
||
|
|
local isDone = true
|
||
|
|
for i=1, GetInboxNumItems() do
|
||
|
|
local invoiceType, _, seller = GetInboxInvoiceInfo(i)
|
||
|
|
if invoiceType and seller == "" then
|
||
|
|
isDone = false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
if isDone and GetInboxNumItems() > 0 then
|
||
|
|
TSMAPI:CancelFrame("accountingGetSellers")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
function Data:MAIL_SHOW()
|
||
|
|
TSMAPI:CreateTimeDelay("accountingGetSellers", 0.1, private.RequestSellerInfo, 0.1)
|
||
|
|
end
|
||
|
|
function Data:MAIL_CLOSED()
|
||
|
|
TSMAPI:CancelFrame("accountingGetSellers")
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
function private:CanCombineRecords(recordA, recordB)
|
||
|
|
local keys = {"key", "copper", "stackSize", "player", "otherPlayer"}
|
||
|
|
for _, key in ipairs(keys) do
|
||
|
|
if recordA[key] ~= recordB[key] then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return abs(recordA.time-recordB.time) < TIME_BUCKET
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:InsertItemRecord(itemString, dataType, newRecord)
|
||
|
|
newRecord.time = floor(newRecord.time or time())
|
||
|
|
newRecord.player = UnitName("player")
|
||
|
|
TSM.items[itemString] = TSM.items[itemString] or {sales={}, buys={}, auctions={}}
|
||
|
|
for _, record in ipairs(TSM.items[itemString][dataType]) do
|
||
|
|
if private:CanCombineRecords(record, newRecord) then
|
||
|
|
-- combine with existing record
|
||
|
|
record.quantity = record.quantity + newRecord.stackSize -- this is total quantity, not number of stacks
|
||
|
|
return
|
||
|
|
end
|
||
|
|
end
|
||
|
|
tinsert(TSM.items[itemString][dataType], newRecord)
|
||
|
|
TSM.cache[itemString] = {}
|
||
|
|
-- keep the records sorted by time
|
||
|
|
sort(TSM.items[itemString][dataType], function(a, b) return (a.time or 0) < (b.time or 0) end)
|
||
|
|
end
|
||
|
|
function Data:InsertItemSaleRecord(itemString, key, stackSize, copper, buyer, timeStamp)
|
||
|
|
if not (itemString and key and stackSize and copper and buyer and copper > 0) then return end
|
||
|
|
if key ~= "Auction" and key ~= "COD" and key ~= "Trade" and key ~= "Vendor" then return end
|
||
|
|
private:InsertItemRecord(itemString, "sales", {key=key, stackSize=stackSize, quantity=stackSize, copper=copper, otherPlayer=buyer, time=timeStamp})
|
||
|
|
end
|
||
|
|
function Data:InsertItemBuyRecord(itemString, key, stackSize, copper, seller, timeStamp)
|
||
|
|
if not (itemString and key and stackSize and copper and seller and copper > 0) then return end
|
||
|
|
if key ~= "Auction" and key ~= "COD" and key ~= "Trade" and key ~= "Vendor" then return end
|
||
|
|
private:InsertItemRecord(itemString, "buys", {key=key, stackSize=stackSize, quantity=stackSize, copper=copper, otherPlayer=seller, time=timeStamp})
|
||
|
|
end
|
||
|
|
function Data:InsertItemAuctionRecord(itemString, key, stackSize, timeStamp)
|
||
|
|
if not (itemString and key and stackSize) then return end
|
||
|
|
if key ~= "Cancel" and key ~= "Expire" then return end
|
||
|
|
private:InsertItemRecord(itemString, "auctions", {key=key, stackSize=stackSize, quantity=stackSize, time=timeStamp})
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:InsertMoneyRecord(dataType, newRecord)
|
||
|
|
newRecord.time = floor(time())
|
||
|
|
newRecord.player = UnitName("player")
|
||
|
|
for _, record in ipairs(TSM.money[dataType]) do
|
||
|
|
if private:CanCombineRecords(record, newRecord) then
|
||
|
|
-- combine with existing record
|
||
|
|
record.copper = record.copper + newRecord.copper
|
||
|
|
return
|
||
|
|
end
|
||
|
|
end
|
||
|
|
tinsert(TSM.money[dataType], newRecord)
|
||
|
|
end
|
||
|
|
function Data:InsertMoneyIncomeRecord(key, copper, destination, timeStamp)
|
||
|
|
if not (key and copper and destination and copper > 0) then return end
|
||
|
|
if key ~= "Transfer" then return end
|
||
|
|
private:InsertMoneyRecord("income", {key=key, copper=copper, otherPlayer=destination, time=timeStamp})
|
||
|
|
end
|
||
|
|
function Data:InsertMoneyExpenseRecord(key, copper, destination, timeStamp)
|
||
|
|
if not (key and copper and destination and copper > 0) then return end
|
||
|
|
if key ~= "Postage" and key ~= "Repair" and key ~= "Transfer" then return end
|
||
|
|
private:InsertMoneyRecord("expense", {key=key, copper=copper, otherPlayer=destination, time=timeStamp})
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
function Data:CheckMerchantSale(bag, slot, onSelf)
|
||
|
|
if MerchantFrame:IsShown() and not onSelf then
|
||
|
|
local itemString = TSMAPI:GetItemString(GetContainerItemLink(bag, slot))
|
||
|
|
local quantity = select(2, GetContainerItemInfo(bag, slot))
|
||
|
|
local copper = select(11, TSMAPI:GetSafeItemInfo(itemString))
|
||
|
|
Data:InsertItemSaleRecord(itemString, "Vendor", quantity, copper, "Merchant")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data:BuyMerchantItem(index, quantity)
|
||
|
|
local itemName, _, price, batchQuantity = GetMerchantItemInfo(index)
|
||
|
|
if not price or price <= 0 then return end
|
||
|
|
if not quantity then
|
||
|
|
quantity = batchQuantity
|
||
|
|
end
|
||
|
|
local link = GetMerchantItemLink(index);
|
||
|
|
local itemString = TSM.db.global.itemStrings[itemName] or TSMAPI:GetItemString(link)
|
||
|
|
local copper = floor(price / batchQuantity + 0.5)
|
||
|
|
Data:InsertItemBuyRecord(itemString, "Vendor", quantity, copper, "Merchant")
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data:BuybackMerchantItem(index)
|
||
|
|
local itemName, _, price, quantity = GetBuybackItemInfo(index)
|
||
|
|
local link = GetMerchantItemLink(index)
|
||
|
|
local itemString = TSM.db.global.itemStrings[itemName] or TSMAPI:GetItemString(link)
|
||
|
|
local copper = floor(price / quantity + 0.5)
|
||
|
|
Data:InsertItemBuyRecord(itemString, "Vendor", quantity, copper, "Merchant")
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data:AddRepairCosts()
|
||
|
|
if (COULD_REPAIR and REPAIR_COST > 0) then
|
||
|
|
local cash = GetMoney()
|
||
|
|
if (REPAIR_MONEY > cash) then
|
||
|
|
-- this is probably a repair bill
|
||
|
|
local cost = REPAIR_MONEY - cash
|
||
|
|
Data:InsertMoneyExpenseRecord("Repair", cost, "Merchant")
|
||
|
|
-- reset money as this might have been a single item repair
|
||
|
|
REPAIR_MONEY = cash
|
||
|
|
-- reset the repair cost for the next repair
|
||
|
|
REPAIR_COST, CAN_REPAIR = GetRepairAllCost()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- scans the mail that the player just attempted to send (Pre-Hook) to see if COD
|
||
|
|
function Data:CheckSendMail(oFunc, destination, currentSubject, bodyText)
|
||
|
|
local codAmount = GetSendMailCOD()
|
||
|
|
local moneyAmount = GetSendMailMoney()
|
||
|
|
local mailCost = GetSendMailPrice()
|
||
|
|
local subject
|
||
|
|
local total = 0
|
||
|
|
local ignore = false
|
||
|
|
|
||
|
|
if codAmount ~= 0 then
|
||
|
|
for i = 1, 12 do
|
||
|
|
local itemName, _, count, _ = GetSendMailItem(i)
|
||
|
|
if itemName and count then
|
||
|
|
if not subject then
|
||
|
|
subject = itemName
|
||
|
|
end
|
||
|
|
|
||
|
|
if subject == itemName then
|
||
|
|
total = total + count
|
||
|
|
else
|
||
|
|
ignore = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
else
|
||
|
|
ignore = true
|
||
|
|
end
|
||
|
|
|
||
|
|
if moneyAmount > 0 then -- add a record for the money transfer
|
||
|
|
Data:InsertMoneyExpenseRecord("Transfer", moneyAmount, destination)
|
||
|
|
Data:InsertMoneyExpenseRecord("Postage", mailCost - moneyAmount, destination)
|
||
|
|
else
|
||
|
|
-- add a record for the mail cost
|
||
|
|
Data:InsertMoneyExpenseRecord("Postage", mailCost, destination)
|
||
|
|
end
|
||
|
|
|
||
|
|
if not ignore then
|
||
|
|
Data.hooks[oFunc](destination, subject .. " (" .. total .. ") TSM", bodyText)
|
||
|
|
else
|
||
|
|
Data.hooks[oFunc](destination, currentSubject, bodyText)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:CanLootMailIndex(index)
|
||
|
|
local hasItem = select(8, GetInboxHeaderInfo(index))
|
||
|
|
if not hasItem or hasItem == 0 then return true end
|
||
|
|
for j = 1, ATTACHMENTS_MAX_RECEIVE do
|
||
|
|
local itemString = TSMAPI:GetItemString(GetInboxItemLink(index, j))
|
||
|
|
local quantity = select(3, GetInboxItem(index, j))
|
||
|
|
local space = 0
|
||
|
|
if itemString then
|
||
|
|
for bag = 0, NUM_BAG_SLOTS do
|
||
|
|
if TSMAPI:ItemWillGoInBag(itemString, bag) then
|
||
|
|
for slot = 1, GetContainerNumSlots(bag) do
|
||
|
|
local iString = TSMAPI:GetItemString(GetContainerItemLink(bag, slot))
|
||
|
|
if iString == itemString then
|
||
|
|
local stackSize = select(2, GetContainerItemInfo(bag, slot))
|
||
|
|
local maxStackSize = select(8, TSMAPI:GetSafeItemInfo(itemString))
|
||
|
|
if (maxStackSize - stackSize) >= quantity then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
elseif not iString then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- scans the mail that the player just attempted to collected (Pre-Hook)
|
||
|
|
function Data:ScanCollectedMail(oFunc, attempt, index, subIndex)
|
||
|
|
local invoiceType, itemName, buyer, bid, buyout, deposit, ahcut = GetInboxInvoiceInfo(index)
|
||
|
|
--local invoiceType, itemName, buyer, bid, _, _, ahcut, _, _, _, quantity = GetInboxInvoiceInfo(index)
|
||
|
|
local sender, subject, money, codAmount, _, itemCount = select(3, GetInboxHeaderInfo(index))
|
||
|
|
if not subject then return end
|
||
|
|
local success = true
|
||
|
|
if attempt > 2 then
|
||
|
|
if buyer == "" then
|
||
|
|
buyer = "?"
|
||
|
|
elseif sender == "" then
|
||
|
|
sender = "?"
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local quantity = 0
|
||
|
|
for j = 1, ATTACHMENTS_MAX_RECEIVE do
|
||
|
|
quantity = select(3, GetInboxItem(index, j))
|
||
|
|
end
|
||
|
|
if quantity == 0 then
|
||
|
|
quantity = 1
|
||
|
|
end
|
||
|
|
|
||
|
|
if invoiceType == "seller" and buyer and buyer ~= "" then -- AH Sales
|
||
|
|
local daysLeft = select(7, GetInboxHeaderInfo(index))
|
||
|
|
local saleTime = (time() + (daysLeft - 30) * SECONDS_PER_DAY)
|
||
|
|
local link = select(2, TSMAPI:GetSafeItemInfo(itemName))
|
||
|
|
local itemString = TSM.db.global.itemStrings[itemName] or TSMAPI:GetItemString(link)
|
||
|
|
if itemString and private:CanLootMailIndex(index) then
|
||
|
|
local copper = floor((bid - ahcut) / quantity + 0.5)
|
||
|
|
Data:InsertItemSaleRecord(itemString, "Auction", quantity, copper, buyer, saleTime)
|
||
|
|
end
|
||
|
|
elseif invoiceType == "buyer" and buyer and buyer ~= "" then -- AH Buys
|
||
|
|
local link = GetInboxItemLink(index, subIndex or 1)
|
||
|
|
local itemString = TSMAPI:GetItemString(link)
|
||
|
|
if itemString and private:CanLootMailIndex(index) then
|
||
|
|
--might as well grab the name for future lookups
|
||
|
|
local name = TSMAPI:GetSafeItemInfo(link)
|
||
|
|
TSM.db.global.itemStrings[name] = itemString
|
||
|
|
|
||
|
|
local copper = floor(bid / quantity + 0.5)
|
||
|
|
local daysLeft = select(7, GetInboxHeaderInfo(index))
|
||
|
|
local buyTime = (time() + (daysLeft - 30) * SECONDS_PER_DAY)
|
||
|
|
Data:InsertItemBuyRecord(itemString, "Auction", quantity, copper, buyer, buyTime)
|
||
|
|
end
|
||
|
|
elseif codAmount > 0 then -- COD Buys (only if all attachments are same item)
|
||
|
|
local link = GetInboxItemLink(index, subIndex or 1)
|
||
|
|
local itemString = TSMAPI:GetItemString(link)
|
||
|
|
if itemString then
|
||
|
|
--might as well grab the name for future lookups
|
||
|
|
local name = TSMAPI:GetSafeItemInfo(link)
|
||
|
|
TSM.db.global.itemStrings[name] = itemString
|
||
|
|
|
||
|
|
local total = 0
|
||
|
|
local stacks = 0
|
||
|
|
local ignore = false
|
||
|
|
for i = 1, ATTACHMENTS_MAX_RECEIVE do
|
||
|
|
local nameCheck, _, count, _, _ = GetInboxItem(index, i)
|
||
|
|
|
||
|
|
if nameCheck and count then
|
||
|
|
if nameCheck == name then
|
||
|
|
total = total + count
|
||
|
|
stacks = stacks + 1
|
||
|
|
else
|
||
|
|
ignore = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
if total ~= 0 and not ignore and private:CanLootMailIndex(index) then
|
||
|
|
local copper = floor(codAmount / total + 0.5)
|
||
|
|
local daysLeft = select(7, GetInboxHeaderInfo(index))
|
||
|
|
local buyTime = (time() + (daysLeft - 3) * SECONDS_PER_DAY)
|
||
|
|
local maxStack = select(8, TSMAPI:GetSafeItemInfo(link))
|
||
|
|
for i = 1, stacks do
|
||
|
|
local stackSize = (total >= maxStack) and maxStack or total
|
||
|
|
Data:InsertItemBuyRecord(itemString, "COD", stackSize, copper, sender, buyTime)
|
||
|
|
total = total - stackSize
|
||
|
|
if total <= 0 then break end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
elseif money > 0 and invoiceType ~= "seller" and not strfind(subject, outbid) then
|
||
|
|
local str
|
||
|
|
if GetLocale() == "deDE" then
|
||
|
|
str = gsub(subject, gsub(COD_PAYMENT, TSMAPI:StrEscape("%1$s"), ""), "")
|
||
|
|
else
|
||
|
|
str = gsub(subject, gsub(COD_PAYMENT, TSMAPI:StrEscape("%s"), ""), "")
|
||
|
|
end
|
||
|
|
local daysLeft = select(7, GetInboxHeaderInfo(index))
|
||
|
|
local saleTime = (time() + (daysLeft - 31) * SECONDS_PER_DAY)
|
||
|
|
if private:CanLootMailIndex(index) then
|
||
|
|
if str and strfind(str, "TSM$") then -- payment for a COD the player sent
|
||
|
|
local codName = strmatch(str, "([^%(]+)"):trim()
|
||
|
|
local qty = strmatch(str, "%(([0-9]+)%)")
|
||
|
|
qty = tonumber(qty)
|
||
|
|
local itemString = TSM.db.global.itemStrings[codName]
|
||
|
|
if itemString then
|
||
|
|
local copper = floor(money / qty + 0.5)
|
||
|
|
local maxStack = select(8, TSMAPI:GetSafeItemInfo(itemString)) or 1
|
||
|
|
local stacks = ceil(qty / maxStack)
|
||
|
|
|
||
|
|
for i = 1, stacks do
|
||
|
|
local stackSize = (qty >= maxStack) and maxStack or qty
|
||
|
|
Data:InsertItemSaleRecord(itemString, "COD", stackSize, copper, sender, saleTime)
|
||
|
|
qty = qty - stackSize
|
||
|
|
if qty <= 0 then break end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
else -- record a money transfer
|
||
|
|
Data:InsertMoneyIncomeRecord("Transfer", money, sender, saleTime)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
elseif strfind(subject, expired) then -- expired auction
|
||
|
|
local daysLeft = select(7, GetInboxHeaderInfo(index))
|
||
|
|
local expiredTime = (time() + (daysLeft - 30) * SECONDS_PER_DAY)
|
||
|
|
local link = GetInboxItemLink(index, subIndex or 1)
|
||
|
|
local qty = select(3, GetInboxItem(index, subIndex or 1))
|
||
|
|
local itemString = TSMAPI:GetItemString(link)
|
||
|
|
if private:CanLootMailIndex(index) then
|
||
|
|
Data:InsertItemAuctionRecord(itemString, "Expire", qty, expiredTime)
|
||
|
|
end
|
||
|
|
elseif strfind(subject, cancelled) then -- cancelled auction
|
||
|
|
local daysLeft = select(7, GetInboxHeaderInfo(index))
|
||
|
|
local cancelledTime = (time() + (daysLeft - 30) * SECONDS_PER_DAY)
|
||
|
|
local link = GetInboxItemLink(index, subIndex or 1)
|
||
|
|
local qty = select(3, GetInboxItem(index, subIndex or 1))
|
||
|
|
local itemString = TSMAPI:GetItemString(link)
|
||
|
|
if private:CanLootMailIndex(index) then
|
||
|
|
Data:InsertItemAuctionRecord(itemString, "Cancel", qty, cancelledTime)
|
||
|
|
end
|
||
|
|
else
|
||
|
|
success = false
|
||
|
|
end
|
||
|
|
|
||
|
|
if success then
|
||
|
|
Data.hooks[oFunc](index, subIndex)
|
||
|
|
elseif (not select(2, GetInboxHeaderInfo(index)) or (invoiceType and (not buyer or buyer == ""))) and attempt <= 5 then
|
||
|
|
TSMAPI:CreateTimeDelay("accountingHookDelay", 0.2, function() Data:ScanCollectedMail(oFunc, attempt + 1, index, subIndex) end)
|
||
|
|
elseif attempt > 5 then
|
||
|
|
Data.hooks[oFunc](index, subIndex)
|
||
|
|
else
|
||
|
|
Data.hooks[oFunc](index, subIndex)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- scan the auctions the user has on the AH for name -> itemString lookup table
|
||
|
|
function Data:ScanAuctionItems()
|
||
|
|
for i = 1, GetNumAuctionItems("owner") do
|
||
|
|
local name = GetAuctionItemInfo("owner", i)
|
||
|
|
if name then
|
||
|
|
local itemString = TSMAPI:GetItemString(GetAuctionItemLink("owner", i))
|
||
|
|
TSM.db.global.itemStrings[name] = itemString
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- scans the bags to help build the name -> itemString lookup table
|
||
|
|
function Data:ScanBagItems(state)
|
||
|
|
for itemString in pairs(state) do
|
||
|
|
local name = TSMAPI:GetSafeItemInfo(itemString)
|
||
|
|
if name then
|
||
|
|
TSM.db.global.itemStrings[name] = itemString
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
-- ************************************************ --
|
||
|
|
-- GUI Helper Functions --
|
||
|
|
-- ************************************************ --
|
||
|
|
|
||
|
|
-- returns a formatted time in the format that the user has selected
|
||
|
|
function private:GetFormattedTime(rTime)
|
||
|
|
if TSM.db.factionrealm.timeFormat == "ago" then
|
||
|
|
return format(L["%s ago"], SecondsToTime(time() - rTime) or "?")
|
||
|
|
elseif TSM.db.factionrealm.timeFormat == "usdate" then
|
||
|
|
return date("%m/%d/%y %H:%M", rTime)
|
||
|
|
elseif TSM.db.factionrealm.timeFormat == "eudate" then
|
||
|
|
return date("%d/%m/%y %H:%M", rTime)
|
||
|
|
elseif TSM.db.factionrealm.timeFormat == "aidate" then
|
||
|
|
return date("%y/%m/%d %H:%M", rTime)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:IsItemFiltered(itemString, filters)
|
||
|
|
local name, _, rarity = TSMAPI:GetSafeItemInfo(itemString)
|
||
|
|
name = name or TSM.items[itemString].name
|
||
|
|
rarity = rarity or 0
|
||
|
|
if not name then return true end
|
||
|
|
|
||
|
|
if filters.name and not strfind(strlower(name), strlower(filters.name)) then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
|
||
|
|
if filters.rarity and rarity ~= filters.rarity then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
|
||
|
|
if not TSM.db.factionrealm.displayGreys and rarity == 0 then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
|
||
|
|
if filters.group then
|
||
|
|
local groupPath = TSMAPI:GetGroupPath(itemString)
|
||
|
|
if not groupPath or not strfind(groupPath, "^"..TSMAPI:StrEscape(filters.group)) then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:IsRecordFiltered(record, filters)
|
||
|
|
if filters.player and record.player ~= filters.player then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
if filters.otherPlayer and record.otherPlayer ~= filters.otherPlayer then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
if not TSM.db.factionrealm.displayTransfers and record.key == "Transfer" then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
if filters.time and floor(record.time/SECONDS_PER_DAY) < (floor(time()/SECONDS_PER_DAY) - filters.time) then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
if not record.key or (filters.key and record.key ~= filters.key) then
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:GetItemSTData(dataType, filters)
|
||
|
|
local stData = {}
|
||
|
|
for itemString, data in pairs(TSM.items) do
|
||
|
|
if #data[dataType] > 0 and not private:IsItemFiltered(itemString, filters) then
|
||
|
|
for _, record in ipairs(data[dataType]) do
|
||
|
|
if not private:IsRecordFiltered(record, filters) then
|
||
|
|
local row = {
|
||
|
|
cols = {
|
||
|
|
{
|
||
|
|
value = select(2, TSMAPI:GetSafeItemInfo(itemString)) or TSM.items[itemString].name,
|
||
|
|
sortArg = TSM.items[itemString].name or itemString,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.player,
|
||
|
|
sortArg = record.player,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.key,
|
||
|
|
sortArg = record.key,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.stackSize,
|
||
|
|
sortArg = record.stackSize,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = floor(record.quantity / record.stackSize),
|
||
|
|
sortArg = floor(record.quantity / record.stackSize),
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = TSMAPI:FormatTextMoney(record.copper),
|
||
|
|
sortArg = record.copper,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = TSMAPI:FormatTextMoney(record.copper*record.quantity),
|
||
|
|
sortArg = record.copper*record.quantity,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = private:GetFormattedTime(record.time),
|
||
|
|
sortArg = record.time,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
itemString = itemString,
|
||
|
|
record = record,
|
||
|
|
}
|
||
|
|
tinsert(stData, row)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return stData
|
||
|
|
end
|
||
|
|
function Data.GetSaleSTData(filters)
|
||
|
|
return private:GetItemSTData("sales", filters)
|
||
|
|
end
|
||
|
|
function Data.GetBuySTData(filters)
|
||
|
|
return private:GetItemSTData("buys", filters)
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:GetItemSummaryData(filters, includeProfit)
|
||
|
|
local itemData = {}
|
||
|
|
|
||
|
|
for itemString, data in pairs(TSM.items) do
|
||
|
|
if not private:IsItemFiltered(itemString, filters) then
|
||
|
|
local sellTotal, sellNum = 0, 0
|
||
|
|
for _, record in ipairs(data.sales) do
|
||
|
|
if not private:IsRecordFiltered(record, filters) then
|
||
|
|
sellTotal = sellTotal + record.copper * record.quantity
|
||
|
|
sellNum = sellNum + record.quantity
|
||
|
|
end
|
||
|
|
end
|
||
|
|
local avgSell = TSM:Round(sellTotal / sellNum) or 0
|
||
|
|
|
||
|
|
local buyTotal, buyNum = 0, 0
|
||
|
|
for _, record in ipairs(data.buys) do
|
||
|
|
if not private:IsRecordFiltered(record, filters) then
|
||
|
|
buyTotal = buyTotal + record.copper * record.quantity
|
||
|
|
buyNum = buyNum + record.quantity
|
||
|
|
end
|
||
|
|
end
|
||
|
|
local avgBuy = TSM:Round(buyTotal / buyNum) or 0
|
||
|
|
|
||
|
|
local isValidItem
|
||
|
|
local profit, profitText
|
||
|
|
if includeProfit and buyNum > 0 and sellNum > 0 then
|
||
|
|
profit = avgSell - avgBuy
|
||
|
|
local profitPercent = TSM:Round(100 * profit / avgBuy)
|
||
|
|
local color = profit > 0 and "|cff00ff00" or "|cffff0000"
|
||
|
|
profitText = TSMAPI:FormatTextMoney(profit, color) .. " (" .. color .. profitPercent .. "%|r)"
|
||
|
|
isValidItem = true
|
||
|
|
elseif not includeProfit then
|
||
|
|
isValidItem = (buyNum > 0 or sellNum > 0)
|
||
|
|
end
|
||
|
|
|
||
|
|
if isValidItem then
|
||
|
|
itemData[itemString] = {buyNum=buyNum, sellNum=sellNum, profit=profit, profitText=profitText}
|
||
|
|
if TSM.db.factionrealm.priceFormat == "total" then
|
||
|
|
itemData[itemString].avgSell = sellNum > 0 and sellTotal or 0
|
||
|
|
itemData[itemString].avgBuy = buyNum > 0 and buyTotal or 0
|
||
|
|
else
|
||
|
|
itemData[itemString].avgSell = sellNum > 0 and avgSell or 0
|
||
|
|
itemData[itemString].avgBuy = buyNum > 0 and avgBuy or 0
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return itemData
|
||
|
|
end
|
||
|
|
function Data.GetResaleSTData(filters)
|
||
|
|
local resaleItemData = private:GetItemSummaryData(filters, true)
|
||
|
|
local stData = {}
|
||
|
|
for itemString, data in pairs(resaleItemData) do
|
||
|
|
local name = TSM.items[itemString].name
|
||
|
|
local row = {
|
||
|
|
cols = {
|
||
|
|
{
|
||
|
|
value = select(2, TSMAPI:GetSafeItemInfo(itemString)) or TSM.items[itemString].name,
|
||
|
|
sortArg = TSM.items[itemString].name or itemString,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = data.sellNum,
|
||
|
|
sortArg = data.sellNum,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = TSMAPI:FormatTextMoney(data.avgSell),
|
||
|
|
sortArg = data.avgSell,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = data.buyNum,
|
||
|
|
sortArg = data.buyNum,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = TSMAPI:FormatTextMoney(data.avgBuy),
|
||
|
|
sortArg = data.avgBuy,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = data.profitText,
|
||
|
|
sortArg = data.profit,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
itemString = itemString,
|
||
|
|
totalNum = data.sellNum + data.buyNum,
|
||
|
|
}
|
||
|
|
tinsert(stData, row)
|
||
|
|
end
|
||
|
|
|
||
|
|
return stData
|
||
|
|
end
|
||
|
|
function Data.GetItemSummarySTData(filters)
|
||
|
|
local itemData = private:GetItemSummaryData(filters, false)
|
||
|
|
local stData = {}
|
||
|
|
for itemString, data in pairs(itemData) do
|
||
|
|
local name = TSM.items[itemString].name
|
||
|
|
local marketValue = TSMAPI:GetItemValue(itemString, TSM.db.factionrealm.mvSource)
|
||
|
|
local row = {
|
||
|
|
cols = {
|
||
|
|
{
|
||
|
|
value = select(2, TSMAPI:GetSafeItemInfo(itemString)) or name,
|
||
|
|
sortArg = name or itemString,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = TSMAPI:FormatTextMoney(marketValue) or "|cff999999---|r",
|
||
|
|
sortArg = marketValue or 0,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = data.sellNum,
|
||
|
|
sortArg = data.sellNum,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = data.avgSell > 0 and TSMAPI:FormatTextMoney(data.avgSell) or "|cff999999---|r",
|
||
|
|
sortArg = data.avgSell,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = data.buyNum,
|
||
|
|
sortArg = data.buyNum,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = data.avgSell > 0 and TSMAPI:FormatTextMoney(data.avgBuy) or "|cff999999---|r",
|
||
|
|
sortArg = data.avgBuy,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
itemString = itemString,
|
||
|
|
totalNum = data.sellNum + data.buyNum,
|
||
|
|
}
|
||
|
|
tinsert(stData, row)
|
||
|
|
end
|
||
|
|
|
||
|
|
return stData
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:GetAuctionSTData(dataType, filters)
|
||
|
|
local stData = {}
|
||
|
|
for itemString, data in pairs(TSM.items) do
|
||
|
|
if #data.auctions > 0 and not private:IsItemFiltered(itemString, filters) then
|
||
|
|
for _, record in ipairs(data.auctions) do
|
||
|
|
if record.key == dataType and not private:IsRecordFiltered(record, filters) then
|
||
|
|
local row = {
|
||
|
|
cols = {
|
||
|
|
{
|
||
|
|
value = select(2, TSMAPI:GetSafeItemInfo(itemString)) or data.name,
|
||
|
|
sortArg = data.name or itemString,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.player,
|
||
|
|
sortArg = record.player,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.stackSize,
|
||
|
|
sortArg = record.stackSize,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.quantity / record.stackSize,
|
||
|
|
sortArg = record.quantity / record.stackSize,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = private:GetFormattedTime(record.time),
|
||
|
|
sortArg = record.time,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
itemString = itemString,
|
||
|
|
}
|
||
|
|
tinsert(stData, row)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return stData
|
||
|
|
end
|
||
|
|
function Data.GetExpireSTData(filters)
|
||
|
|
return private:GetAuctionSTData("Expire", filters)
|
||
|
|
end
|
||
|
|
function Data.GetCancelSTData(filters)
|
||
|
|
return private:GetAuctionSTData("Cancel", filters)
|
||
|
|
end
|
||
|
|
|
||
|
|
function private:GetMoneySTData(dataType, filters)
|
||
|
|
local stData = {}
|
||
|
|
for _, record in ipairs(TSM.money[dataType]) do
|
||
|
|
if not private:IsRecordFiltered(record, filters) then
|
||
|
|
local row = {
|
||
|
|
cols = {
|
||
|
|
{
|
||
|
|
value = record.key,
|
||
|
|
sortArg = record.key,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.player,
|
||
|
|
sortArg = record.player,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.otherPlayer,
|
||
|
|
sortArg = record.otherPlayer,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = TSMAPI:FormatTextMoney(record.copper),
|
||
|
|
sortArg = record.copper,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = private:GetFormattedTime(record.time),
|
||
|
|
sortArg = record.time,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
tinsert(stData, row)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return stData
|
||
|
|
end
|
||
|
|
function Data.GetIncomeSTData(filters)
|
||
|
|
return private:GetMoneySTData("income", filters)
|
||
|
|
end
|
||
|
|
function Data.GetExpenseSTData(filters)
|
||
|
|
return private:GetMoneySTData("expense", filters)
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data.GetSummaryData(filters)
|
||
|
|
local goldData = {
|
||
|
|
sales = {total=0, month=0, week=0, topGold={}, topQuantity={}},
|
||
|
|
income = {total=0, month=0, week=0, topGold={}, topQuantity={}},
|
||
|
|
buys = {total=0, month=0, week=0, topGold={}, topQuantity={}},
|
||
|
|
expense = {total=0, month=0, week=0, topGold={}, topQuantity={}},
|
||
|
|
profit = {total=0, month=0, week=0},
|
||
|
|
totalTime = 0,
|
||
|
|
monthTime = 0,
|
||
|
|
weekTime = 0,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
local function ProcessSummaryItemData(itemData, resultTbl, itemString)
|
||
|
|
local itemTotal, itemNum = 0, 0
|
||
|
|
for _, record in ipairs(itemData) do
|
||
|
|
if not private:IsRecordFiltered(record, filters) then
|
||
|
|
local timeDiff = time() - record.time
|
||
|
|
|
||
|
|
-- update local variables
|
||
|
|
itemNum = itemNum + record.quantity
|
||
|
|
itemTotal = itemTotal + record.copper * record.quantity
|
||
|
|
|
||
|
|
-- update total data
|
||
|
|
resultTbl.total = resultTbl.total + record.copper * record.quantity
|
||
|
|
goldData.totalTime = max(goldData.totalTime, timeDiff)
|
||
|
|
if timeDiff <= (SECONDS_PER_DAY * 30) then
|
||
|
|
-- update month data
|
||
|
|
resultTbl.month = resultTbl.month + record.copper * record.quantity
|
||
|
|
goldData.monthTime = max(goldData.monthTime, timeDiff)
|
||
|
|
end
|
||
|
|
if timeDiff <= (SECONDS_PER_DAY * 7) then
|
||
|
|
-- update week data
|
||
|
|
resultTbl.week = resultTbl.week + record.copper * record.quantity
|
||
|
|
goldData.weekTime = max(goldData.weekTime, timeDiff)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- check if this is a top item by gold and/or quantity
|
||
|
|
if itemTotal > (resultTbl.topGold.copper or 0) then
|
||
|
|
resultTbl.topGold = {itemString=itemString, copper=itemTotal, itemID=TSMAPI:GetItemID(itemString)}
|
||
|
|
end
|
||
|
|
if itemNum > (resultTbl.topQuantity.num or 0) then
|
||
|
|
resultTbl.topQuantity = {itemString=itemString, num=itemNum, itemID=TSMAPI:GetItemID(itemString)}
|
||
|
|
end
|
||
|
|
end
|
||
|
|
for itemString, data in pairs(TSM.items) do
|
||
|
|
if not private:IsItemFiltered(itemString, filters) then
|
||
|
|
ProcessSummaryItemData(data.sales, goldData.sales, itemString)
|
||
|
|
ProcessSummaryItemData(data.buys, goldData.buys, itemString)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
local function ProcessSummaryMoneyData(moneyData, resultTbl)
|
||
|
|
local moneyKeyNum, moneyKeyGold = {}, {}
|
||
|
|
for _, record in ipairs(moneyData) do
|
||
|
|
if not private:IsRecordFiltered(record, filters) then
|
||
|
|
local timeDiff = time() - record.time
|
||
|
|
|
||
|
|
-- update local variables
|
||
|
|
moneyKeyNum[record.key] = (moneyKeyNum[record.key] or 0) + 1
|
||
|
|
moneyKeyGold[record.key] = (moneyKeyGold[record.key] or 0) + record.copper
|
||
|
|
|
||
|
|
-- update total data
|
||
|
|
resultTbl.total = resultTbl.total + record.copper
|
||
|
|
goldData.totalTime = max(goldData.totalTime, timeDiff)
|
||
|
|
if timeDiff < (SECONDS_PER_DAY * 30) then
|
||
|
|
-- update month data
|
||
|
|
resultTbl.month = resultTbl.month + record.copper
|
||
|
|
goldData.monthTime = max(goldData.monthTime, timeDiff)
|
||
|
|
end
|
||
|
|
if timeDiff < (SECONDS_PER_DAY * 7) then
|
||
|
|
-- update week data
|
||
|
|
resultTbl.week = resultTbl.week + record.copper
|
||
|
|
goldData.weekTime = max(goldData.weekTime, timeDiff)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
for key, total in pairs(moneyKeyNum) do
|
||
|
|
if total > (resultTbl.topQuantity.num or 0) then
|
||
|
|
resultTbl.topQuantity = {key=key, num=total}
|
||
|
|
end
|
||
|
|
end
|
||
|
|
for key, total in pairs(moneyKeyGold) do
|
||
|
|
if total > (resultTbl.topGold.copper or 0) then
|
||
|
|
resultTbl.topGold = {key=key, copper=total}
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
ProcessSummaryMoneyData(TSM.money.income, goldData.income)
|
||
|
|
ProcessSummaryMoneyData(TSM.money.expense, goldData.expense)
|
||
|
|
|
||
|
|
|
||
|
|
goldData.sales.topGold.link = goldData.sales.topGold.itemString and (select(2, TSMAPI:GetSafeItemInfo(goldData.sales.topGold.itemString)) or TSM.items[goldData.sales.topGold.itemString].name) or L["none"]
|
||
|
|
goldData.sales.topQuantity.link = goldData.sales.topQuantity.itemString and (select(2, TSMAPI:GetSafeItemInfo(goldData.sales.topQuantity.itemString)) or TSM.items[goldData.sales.topQuantity.itemString].name) or L["none"]
|
||
|
|
goldData.buys.topGold.link = goldData.buys.topGold.itemString and (select(2, TSMAPI:GetSafeItemInfo(goldData.buys.topGold.itemString)) or TSM.items[goldData.buys.topGold.itemString].name) or L["none"]
|
||
|
|
goldData.buys.topQuantity.link = goldData.buys.topQuantity.itemString and (select(2, TSMAPI:GetSafeItemInfo(goldData.buys.topQuantity.itemString)) or TSM.items[goldData.buys.topQuantity.itemString].name) or L["none"]
|
||
|
|
|
||
|
|
goldData.profit.total = ((goldData.sales.total + goldData.income.total) - (goldData.buys.total + goldData.expense.total))
|
||
|
|
goldData.profit.month = ((goldData.sales.month + goldData.income.month) - (goldData.buys.month + goldData.expense.month))
|
||
|
|
goldData.profit.week = ((goldData.sales.week + goldData.income.week) - (goldData.buys.week + goldData.expense.week))
|
||
|
|
|
||
|
|
if goldData.totalTime > (SECONDS_PER_DAY * 30) then
|
||
|
|
goldData.monthTime = SECONDS_PER_DAY * 30
|
||
|
|
end
|
||
|
|
if goldData.totalTime > (SECONDS_PER_DAY * 7) then
|
||
|
|
goldData.weekTime = SECONDS_PER_DAY * 7
|
||
|
|
end
|
||
|
|
goldData.totalTime = ceil(goldData.totalTime / SECONDS_PER_DAY)
|
||
|
|
goldData.monthTime = ceil(goldData.monthTime / SECONDS_PER_DAY)
|
||
|
|
goldData.weekTime = ceil(goldData.weekTime / SECONDS_PER_DAY)
|
||
|
|
|
||
|
|
return goldData
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data.GetItemDetailData(itemString)
|
||
|
|
if not TSM.items[itemString] then return end
|
||
|
|
|
||
|
|
local data = {activity={}, buys={players={}, price={}, num={}, avg={}}, sales={players={}, price={}, num={}, avg={}}, stData={}}
|
||
|
|
|
||
|
|
local function ProcessItemActivity(itemData, resultTbl, activityType)
|
||
|
|
local totalPrice, totalNum = 0, 0
|
||
|
|
local monthPrice, monthNum = 0, 0
|
||
|
|
local weekPrice, weekNum = 0, 0
|
||
|
|
|
||
|
|
for i, record in ipairs(itemData) do
|
||
|
|
resultTbl.players[record.otherPlayer] = (resultTbl.players[record.otherPlayer] or 0) + record.quantity
|
||
|
|
tinsert(data.activity, {activityType=activityType, record=record})
|
||
|
|
|
||
|
|
totalPrice = totalPrice + record.copper * record.quantity
|
||
|
|
totalNum = totalNum + record.quantity
|
||
|
|
local timeDiff = time() - record.time
|
||
|
|
if timeDiff < (SECONDS_PER_DAY * 30) then
|
||
|
|
monthPrice = monthPrice + record.copper * record.quantity
|
||
|
|
monthNum = monthNum + record.quantity
|
||
|
|
end
|
||
|
|
if timeDiff < (SECONDS_PER_DAY * 7) then
|
||
|
|
weekPrice = weekPrice + record.copper * record.quantity
|
||
|
|
weekNum = weekNum + record.quantity
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
resultTbl.price.total = totalPrice
|
||
|
|
resultTbl.price.month = monthPrice
|
||
|
|
resultTbl.price.week = weekPrice
|
||
|
|
|
||
|
|
resultTbl.num.total = totalNum
|
||
|
|
resultTbl.num.month = monthNum
|
||
|
|
resultTbl.num.week = weekNum
|
||
|
|
|
||
|
|
resultTbl.avg.total = totalNum > 0 and TSM:Round(totalPrice / totalNum) or 0
|
||
|
|
resultTbl.avg.month = monthNum > 0 and TSM:Round(monthPrice / monthNum) or 0
|
||
|
|
resultTbl.avg.week = weekNum > 0 and TSM:Round(weekPrice / weekNum) or 0
|
||
|
|
|
||
|
|
if totalNum > 0 then
|
||
|
|
resultTbl.hasData = true
|
||
|
|
end
|
||
|
|
end
|
||
|
|
ProcessItemActivity(TSM.items[itemString].buys, data.buys, "Purchase")
|
||
|
|
ProcessItemActivity(TSM.items[itemString].sales, data.sales, "Sale")
|
||
|
|
|
||
|
|
for _, stRecord in ipairs(data.activity) do
|
||
|
|
local activityType = stRecord.activityType
|
||
|
|
local record = stRecord.record
|
||
|
|
local row = {
|
||
|
|
cols = {
|
||
|
|
{
|
||
|
|
value = activityType,
|
||
|
|
sortArg = activityType,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.key,
|
||
|
|
sortArg = record.key or "",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.otherPlayer,
|
||
|
|
sortArg = record.otherPlayer,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = record.quantity,
|
||
|
|
sortArg = record.quantity,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = TSMAPI:FormatTextMoney(record.copper),
|
||
|
|
sortArg = record.copper,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = TSMAPI:FormatTextMoney(record.copper * record.quantity),
|
||
|
|
sortArg = record.copper * record.quantity,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
value = private:GetFormattedTime(record.time),
|
||
|
|
sortArg = record.time,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
record = record,
|
||
|
|
recordType = activityType,
|
||
|
|
}
|
||
|
|
tinsert(data.stData, row)
|
||
|
|
end
|
||
|
|
|
||
|
|
return data
|
||
|
|
end
|
||
|
|
|
||
|
|
|
||
|
|
function Data:PopulateDataCaches()
|
||
|
|
Data.playerListCache = {}
|
||
|
|
|
||
|
|
for itemString, data in pairs(TSM.items) do
|
||
|
|
for _, record in ipairs(data.buys) do
|
||
|
|
Data.playerListCache[record.player] = record.player
|
||
|
|
end
|
||
|
|
for _, record in ipairs(data.sales) do
|
||
|
|
Data.playerListCache[record.player] = record.player
|
||
|
|
end
|
||
|
|
for _, record in ipairs(data.auctions) do
|
||
|
|
Data.playerListCache[record.player] = record.player
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
for _, record in pairs(TSM.money.income) do
|
||
|
|
Data.playerListCache[record.player] = record.player
|
||
|
|
end
|
||
|
|
for _, record in pairs(TSM.money.expense) do
|
||
|
|
Data.playerListCache[record.player] = record.player
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data:RemoveOldData(daysOld)
|
||
|
|
local cutOffTime = time() - daysOld * SECONDS_PER_DAY
|
||
|
|
local numRecords, numItems = 0, 0
|
||
|
|
|
||
|
|
for itemString, data in pairs(TSM.items) do
|
||
|
|
local numLeft = 0
|
||
|
|
for _, key in ipairs({"sales", "buys", "auctions"}) do
|
||
|
|
for i=#data[key], 1, -1 do
|
||
|
|
if data[key][i].time < cutOffTime then
|
||
|
|
numRecords = numRecords + 1
|
||
|
|
tremove(data[key], i)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
numLeft = numLeft + #data[key]
|
||
|
|
end
|
||
|
|
if numLeft == 0 then
|
||
|
|
TSM.items[itemString] = nil
|
||
|
|
numItems = numItems + 1
|
||
|
|
end
|
||
|
|
end
|
||
|
|
for dataType, records in pairs(TSM.money) do
|
||
|
|
for i=#records, 1, -1 do
|
||
|
|
if records[i].time < cutOffTime then
|
||
|
|
numRecords = numRecords + 1
|
||
|
|
tremove(records, i)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
TSM:Printf(L["Removed a total of %s old records and %s items with no remaining records."], numRecords, numItems)
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data:SetupRepairCost()
|
||
|
|
REPAIR_MONEY = GetMoney();
|
||
|
|
COULD_REPAIR = CanMerchantRepair();
|
||
|
|
-- if merchant can repair set up variables so we can track repairs
|
||
|
|
if (COULD_REPAIR) then
|
||
|
|
REPAIR_COST, CAN_REPAIR = GetRepairAllCost();
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function Data:ResetRepairMoney()
|
||
|
|
-- Could have bought something before or after repair
|
||
|
|
REPAIR_MONEY = GetMoney()
|
||
|
|
end
|
||
|
|
|
||
|
|
do
|
||
|
|
local tradeInfo
|
||
|
|
|
||
|
|
local function onAcceptUpdate(_, player, target)
|
||
|
|
if (player == 1 or target == 1) and not (GetTradePlayerItemLink(7) or GetTradeTargetItemLink(7)) then
|
||
|
|
-- update tradeInfo
|
||
|
|
tradeInfo = { player = {}, target = {} }
|
||
|
|
tradeInfo.player.money = tonumber(GetPlayerTradeMoney())
|
||
|
|
tradeInfo.target.money = tonumber(GetTargetTradeMoney())
|
||
|
|
tradeInfo.target.name = UnitName("NPC")
|
||
|
|
|
||
|
|
for i = 1, 6 do
|
||
|
|
local link = GetTradeTargetItemLink(i)
|
||
|
|
local count = select(3, GetTradeTargetItemInfo(i))
|
||
|
|
if link then
|
||
|
|
tinsert(tradeInfo.target, { itemString = TSMAPI:GetItemString(link), count = count })
|
||
|
|
end
|
||
|
|
|
||
|
|
local link = GetTradePlayerItemLink(i)
|
||
|
|
local count = select(3, GetTradePlayerItemInfo(i))
|
||
|
|
if link then
|
||
|
|
tinsert(tradeInfo.player, { itemString = TSMAPI:GetItemString(link), count = count })
|
||
|
|
end
|
||
|
|
end
|
||
|
|
else
|
||
|
|
tradeInfo = nil
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local function onChatMsg(_, msg)
|
||
|
|
if not TSM.db.factionrealm.trackTrades then return
|
||
|
|
end
|
||
|
|
if msg == ERR_TRADE_COMPLETE and tradeInfo then
|
||
|
|
-- trade went through
|
||
|
|
local info
|
||
|
|
if tradeInfo.player.money > 0 and #tradeInfo.player == 0 and tradeInfo.target.money == 0 and #tradeInfo.target > 0 then
|
||
|
|
-- player bought items
|
||
|
|
local itemString, count
|
||
|
|
for i = 1, #tradeInfo.target do
|
||
|
|
local data = tradeInfo.target[i]
|
||
|
|
if not itemString then
|
||
|
|
itemString = data.itemString
|
||
|
|
count = data.count
|
||
|
|
elseif itemString == data.itemString then
|
||
|
|
count = count + data.count
|
||
|
|
else
|
||
|
|
return
|
||
|
|
end
|
||
|
|
end
|
||
|
|
if not itemString or not count then return
|
||
|
|
end
|
||
|
|
info = { type = "buys", itemString = itemString, count = count, price = tradeInfo.player.money / count }
|
||
|
|
info.gotText = select(2, TSMAPI:GetSafeItemInfo(itemString)) .. "x" .. count
|
||
|
|
info.gaveText = TSMAPI:FormatTextMoney(tradeInfo.player.money)
|
||
|
|
elseif tradeInfo.player.money == 0 and #tradeInfo.player > 0 and tradeInfo.target.money > 0 and #tradeInfo.target == 0 then
|
||
|
|
-- player sold items
|
||
|
|
local itemString, count
|
||
|
|
for i = 1, #tradeInfo.player do
|
||
|
|
local data = tradeInfo.player[i]
|
||
|
|
if not itemString then
|
||
|
|
itemString = data.itemString
|
||
|
|
count = data.count
|
||
|
|
elseif itemString == data.itemString then
|
||
|
|
count = count + data.count
|
||
|
|
else
|
||
|
|
return
|
||
|
|
end
|
||
|
|
end
|
||
|
|
if not itemString or not count then return
|
||
|
|
end
|
||
|
|
info = { type = "sales", itemString = itemString, count = count, price = tradeInfo.target.money / count }
|
||
|
|
info.gaveText = select(2, TSMAPI:GetSafeItemInfo(itemString)) .. "x" .. count
|
||
|
|
info.gotText = TSMAPI:FormatTextMoney(tradeInfo.target.money)
|
||
|
|
else
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
local function InsertTradeRecord()
|
||
|
|
if info.type == "sales" then
|
||
|
|
Data:InsertItemSaleRecord(info.itemString, "Trade", info.count, info.price, tradeInfo.target.name)
|
||
|
|
elseif info.type == "buys" then
|
||
|
|
Data:InsertItemBuyRecord(info.itemString, "Trade", info.count, info.price, tradeInfo.target.name)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
if TSM.db.factionrealm.autoTrackTrades then
|
||
|
|
InsertTradeRecord()
|
||
|
|
else
|
||
|
|
StaticPopupDialogs["TSMAccountingOnTrade"] = {
|
||
|
|
text = format(L["TSM_Accounting detected that you just traded %s %s in return for %s. Would you like Accounting to store a record of this trade?"], tradeInfo.target.name, info.gaveText, info.gotText),
|
||
|
|
button1 = YES,
|
||
|
|
button2 = NO,
|
||
|
|
timeout = 0,
|
||
|
|
whileDead = true,
|
||
|
|
hideOnEscape = true,
|
||
|
|
OnAccept = InsertTradeRecord,
|
||
|
|
OnCancel = function() end,
|
||
|
|
}
|
||
|
|
TSMAPI:ShowStaticPopupDialog("TSMAccountingOnTrade")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
Data:RegisterEvent("TRADE_ACCEPT_UPDATE", onAcceptUpdate)
|
||
|
|
Data:RegisterEvent("UI_INFO_MESSAGE", onChatMsg)
|
||
|
|
end
|
||
|
|
|
||
|
|
local lastTrackMinute = 0
|
||
|
|
function Data:LogGold()
|
||
|
|
local currentTime = time()
|
||
|
|
local currentMinute = floor(currentTime / 60)
|
||
|
|
if currentMinute <= lastTrackMinute then return end
|
||
|
|
local player = UnitName("player")
|
||
|
|
if not player then return end
|
||
|
|
lastTrackMinute = currentMinute
|
||
|
|
|
||
|
|
TSM.db.factionrealm.goldLog[player] = TSM.db.factionrealm.goldLog[player] or {}
|
||
|
|
local goldLog = TSM.db.factionrealm.goldLog[player]
|
||
|
|
local currentGold = TSM:Round(GetMoney(), COPPER_PER_GOLD * 1000)
|
||
|
|
if #goldLog > 0 and currentGold == goldLog[#goldLog].copper then
|
||
|
|
goldLog[#goldLog].endMinute = currentMinute
|
||
|
|
else
|
||
|
|
if #goldLog > 0 then
|
||
|
|
goldLog[#goldLog].endMinute = currentMinute - 1
|
||
|
|
end
|
||
|
|
tinsert(goldLog, { startMinute = currentMinute, endMinute = currentMinute, copper = currentGold })
|
||
|
|
end
|
||
|
|
end
|