This repository has been archived by the owner on Nov 6, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDB.lua
339 lines (257 loc) · 17.7 KB
/
DB.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
----------------------------------------------------------------------------------------------------------------------
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
----------------------------------------------------------------------------------------------------------------------
local addonName, CLL = ...
if not CLL then return end
-- Initialise environment
local DB = {}
-- Locals and constants
local MODULE = "DB"
local DB_VERSION = 1
-- DB Versioning
local versions = {
[1] = { changelog = "Initial version", changes = {} }
}
-- Upvalues
local type = type
local format = format
local tonumber = tonumber
local tostring = tostring
local pairs = pairs
local date = date
local math_abs = math.abs
local str_match = string.match
local max = max
local UnitName = UnitName
local GetRealmName = GetRealmName
local GetLocale = GetLocale
local ChatMsg = CLL.Output.Print
local DebugMsg = CLL.Debug.Print
local GetFQCN = CLL.GetFQCN
-- Helper function (TODO: Upvalue)
local function IsFQCN(str)
if not str or not str:match(".*%s-%s.*") then return false end
return true
end
-- Initialise DB in SavedVariables
function DB.Init()
-- Create DB tables if necessary
ContainerLootLoggerDB = ContainerLootLoggerDB or {}
local fqcn = GetFQCN()
ContainerLootLoggerDB[fqcn] = ContainerLootLoggerDB[fqcn] or {}
-- Update to current version (if outdated)
local currentVersion = ContainerLootLoggerDB["DatabaseVersion"]
DebugMsg(MODULE, "Initialising DB with currentVersion = " .. tostring(currentVersion))
if not currentVersion or type(currentVersion) ~= "number" or (tonumber(currentVersion) < DB_VERSION) then -- DB needs upgrade to newer version
DebugMsg(MODULE, "DatabaseVersion was found to be outdated (is " .. tostring(currentVersion) .. ", needs upgrading to " .. DB_VERSION .. ")")
-- TODO: Apply changes where necessary
ContainerLootLoggerDB["DatabaseVersion"] = DB_VERSION
end
end
-- Validates a given entry (must contain container name and loot info)
function DB.ValidateEntry(entry)
-- A valid entry looks like this (for the current DB_VERSION; may be subject to change):
-- <link> = { amount = <total amount>, type = <currency, item, etc.>, locale = <client locale>, count = <no. of opened containers> }
local template = {
amount = "number",
type = "string", -- TODO: Temp workaround for GOLD_TOTAL not having it
--locale = "string", -- Not needed, as it will be added by DB.AddEntry automatically if omitted
count = "number", -- Will also be increased or initialised with 1 automatically
}
for k, v in pairs(template) do -- Compare entry with the template and make sure the fields exist
if not entry[k] or type(entry[k]) ~= v then -- This part of the template doesn't exist or is invalid -> reject entry
DebugMsg(MODULE, "Failed to validate entry " .. tostring(entry[k]) .. " for key '" .. k .. "' (should be " .. v .. ", but is " .. type(entry[k]) .. ")")
return false
end
end
return true
end
-- Add one instance of opened container
function DB.AddOpening(container, fqcn)
fqcn = fqcn or GetFQCN()
container = container or "UNKNOWN_SOURCE" -- TODO
DebugMsg(MODULE, "Adding opening for fqcn = " .. tostring(fqcn) .. ", container = " .. tostring(container))
ContainerLootLoggerDB[fqcn][container] = ContainerLootLoggerDB[fqcn][container] or {} -- Init table if this container hasn't been added before
ContainerLootLoggerDB[fqcn][container].numContainersOpened = ContainerLootLoggerDB[fqcn][container].numContainersOpened and (ContainerLootLoggerDB[fqcn][container].numContainersOpened + 1 ) or 1 -- TODO: Wrong for missions, should consider each mission instead of the "table opening"?
end
-- Adds a loot entry for the given fqcn (or the current player if none was given)
-- @param entry
-- @param fqcn
function DB.AddEntry(key, entry, container, fqcn)
container = container or "UNKNOWN_CONTAINER"
fqcn = fqcn or GetFQCN()
local isValid = DB.ValidateEntry(entry)
if not isValid then -- Can't add this to the DB
DebugMsg(MODULE, "Failed to add entry to the DB because it was invalid")
return
end
-- Update existing entry or create anew with default values to add the given loot info
DebugMsg(MODULE, "Adding entry for fqcn = " .. tostring(fqcn) .. ", container = " .. tostring(container))
ContainerLootLoggerDB[fqcn][container] = ContainerLootLoggerDB[fqcn][container] or {} -- Init table if this container hasn't been added before
ContainerLootLoggerDB[fqcn][container][key] = ContainerLootLoggerDB[fqcn][container][key] or {}
ContainerLootLoggerDB[fqcn][container][key].count = ContainerLootLoggerDB[fqcn][container][key].count or 0
DebugMsg(MODULE, "Count was " .. ContainerLootLoggerDB[fqcn][container][key].count .. ", is now " .. (ContainerLootLoggerDB[fqcn][container][key].count and (ContainerLootLoggerDB[fqcn][container][key].count + entry.count) or entry.count))
ContainerLootLoggerDB[fqcn][container][key].count = ContainerLootLoggerDB[fqcn][container][key].count and (ContainerLootLoggerDB[fqcn][container][key].count + entry.count) or entry.count
--ContainerLootLoggerDB[fqcn][container][key].count = (ContainerLootLoggerDB[fqcn][container][key].count + 1)
ContainerLootLoggerDB[fqcn][container][key].amount = ContainerLootLoggerDB[fqcn][container][key].amount and (ContainerLootLoggerDB[fqcn][container][key].amount + entry.amount) or entry.amount
ContainerLootLoggerDB[fqcn][container][key].type = ContainerLootLoggerDB[fqcn][container][key].type or entry.type -- Types shouldn't be able to change, no need to check this
ContainerLootLoggerDB[fqcn][container][key].locale = ContainerLootLoggerDB[fqcn][container][key].locale or GetLocale() -- ditto
--ContainerLootLoggerDB[fqcn][container].numContainersOpened = ContainerLootLoggerDB[fqcn][container].numContainersOpened + numOpenings
-- Add entry for the current day (to allow statistical analysis later on)
local today = date("%d-%m-%Y") -- e.g., 09-11-2001 -> to be used as key
ContainerLootLoggerDB[fqcn][container][key][today] = ContainerLootLoggerDB[fqcn][container][key][today] or {} -- Create new entry if none exists
ContainerLootLoggerDB[fqcn][container][key][today].count = (ContainerLootLoggerDB[fqcn][container][key][today].count or 0) + (entry.count or 0)
ContainerLootLoggerDB[fqcn][container][key][today].amount = (ContainerLootLoggerDB[fqcn][container][key][today].amount or 0) + (entry.amount or 0)
local countToday = ContainerLootLoggerDB[fqcn][container][key][today].count
local amountToday = ContainerLootLoggerDB[fqcn][container][key][today].amount
DebugMsg(MODULE, "Updated entry for " .. container .. " with date [" .. today .. "]: count = " .. countToday .. ", amount = " .. amountToday)
end
-- TODO: Format numbers according to locale (use Blizzard functions)
-- Sums up the daily entries for a given character and container and returns some statistics
function DB.GetTotalAmount(container, fqcn)
-- Parameter validation (well, sort of)
if not container then return end
fqcn = fqcn or GetFQCN()
-- Sum up all entries
local numEntries, totalAmount = 0, 0
local today = date("%d-%m-%Y") -- e.g., 09-11-2001 -> to be used as key
local datePattern = "%d+-%d+-%d+" -- all other entries are actual words, so it doesn't need to be overly restrictive
local entry = ContainerLootLoggerDB[fqcn] -- Check if this toon has an entry that needs to be printed
if type(entry) == "table" and IsFQCN(fqcn) then -- is potential character DB
local orderHallLog = entry.LEGION_ORDER_HALL or {}
local containerLog = orderHallLog[container] or {}
--print(container, fqcn)
for k, v in pairs(containerLog) do -- Check if entry is a daily log
--print(1, k, v)
if str_match(k, datePattern) then -- Is a daily log entry
--print(2)
numEntries = numEntries + (((v.amount ~= nil) and 1) or 0) -- If the daily log exists but is empty, don't count this day
totalAmount = totalAmount + (v.amount or 0)
end
end
end
-- DebugMsg(MODULE, "GetTotalAmount for toon = " .. fqcn .. " and container = " .. container .. " found numEntries = " .. numEntries .. ", totalAmount = " .. totalAmount)
return totalAmount, numEntries
end
-- TODO: Ordering
local ABC_DESC, ABC_ASC, GOLD_DESC, GOLD_ASC, OR_DESC, GOLD_ASC, CUSTOM = 1, 2, 3, 4, 5, 6, 7
-- TODO: Settings
local settings = {
showEmptyAll = false, -- Display characters that haven't earned any gold since last reset
showEmptyToday = false, -- Display characters that haven't earned any gold ever
sortType = ABC_DESC, -- TODO: Possible types = abc, custom ordering, by gold earned, by OR spent?
showCurrentPlayerOnly = true, -- Only display summary for the logged-in character
}
-- Checkout the current and total gold logged (read-only, so this can be used deliberately)
function DB.Checkout()
-- Print summary of the gold earnings since last reset
local showEmpty = settings and settings.showEmptyAll or false -- TODO: all/today
local showCurrentPlayerOnly = settings and settings.showCurrentPlayerOnly or false
local player = GetFQCN()
local numDays, totalGold, totalOR, totalGoldToday, totalOrderResourcesToday = 0, 0, 0, 0, 0
DebugMsg(MODULE, "Checking out characters...")
for toon, entry in pairs(ContainerLootLoggerDB) do -- Check if this toon has an entry that needs to be printed
-- Some basic info can be looked up directly
local today = date("%d-%m-%Y") -- e.g., 09-11-2001 -> to be used as key
local goldToday = type(entry) == "table" and entry["LEGION_ORDER_HALL"] and entry["LEGION_ORDER_HALL"]["GOLD"] and entry["LEGION_ORDER_HALL"]["GOLD"].amount and entry["LEGION_ORDER_HALL"]["GOLD"][today] and entry["LEGION_ORDER_HALL"]["GOLD"][today].amount or 0
local orderResourcesToday = type(entry) == "table" and entry["LEGION_ORDER_HALL"] and entry["LEGION_ORDER_HALL"]["ORDER_RESOURCES"] and entry["LEGION_ORDER_HALL"]["ORDER_RESOURCES"]["amount"] and entry["LEGION_ORDER_HALL"]["ORDER_RESOURCES"][today] and entry["LEGION_ORDER_HALL"]["ORDER_RESOURCES"][today].amount or 0
-- Add this character's gold and OR to the grand total
local gold, numGold = DB.GetTotalAmount("GOLD", toon)
totalGold = totalGold + gold
totalGoldToday = totalGoldToday + goldToday
local OR, numOR = DB.GetTotalAmount("ORDER_RESOURCES", toon)
totalOR = totalOR + OR
totalOrderResourcesToday = totalOrderResourcesToday + orderResourcesToday
numDays = max(numDays, max(numGold, numOR)) -- If one entry is empty but the other exists, it is likely is that there simply was no gold mission for that day (and it is also assumed that all characters will be logged in for a given day, if any)
--print(toon, gold, numGold, OR, numOR, numDays, totalGold, totalOR)
--DebugMsg(MODULE, )
-- Format numbers (TODO: thousands separator) for readability
local formattedGoldToday = GetCoinTextureString(goldToday)
local formattedGoldTotal = GetCoinTextureString(gold)
local formattedOrderResourcesToday = math_abs(orderResourcesToday) .. " |TInterface\\Icons\\inv_orderhall_orderresources:12|t"
local formattedOrderResourcesTotal = math_abs(OR) .. " |TInterface\\Icons\\inv_orderhall_orderresources:12|t"
-- Show debug summary regardless of the user's settings (but not if there is no data of interest)
if (goldToday > 0 or orderResourcesToday > 0 or gold > 0 or OR > 0) then -- Character has at least some data, even if it may not be current
DebugMsg(MODULE, "[" .. tostring(toon) .. "] Gold earned today: " .. formattedGoldToday .. " (Total: " .. formattedGoldTotal .. ")")
DebugMsg(MODULE, "[" .. tostring(toon) .. "] OR spent today: " .. formattedOrderResourcesToday .. " (Total: " .. formattedOrderResourcesTotal .. ")")
end
if (goldToday > 0) or orderResourcesToday or showEmpty then -- Display character in the summary
if not showCurrentPlayerOnly or (showCurrentPlayerOnly and toon == player) then -- Display data for this toon
ChatMsg("-----------------------------------------------------------------------------------------------------------")
ChatMsg("Showing data for [" .. tostring(toon) .. "]")
ChatMsg("Gold earned (today): " .. formattedGoldToday)
ChatMsg("Gold earned (total): " .. formattedGoldTotal)
ChatMsg("OR spent (today): " .. formattedOrderResourcesToday)
ChatMsg("OR spent (total): " .. formattedOrderResourcesTotal)
-- Display detailed character statistics (but only if there are some entries)
local STATISTICAL_SIGNIFICANT_THRESHOLD = 1 -- TODO - This is just to avoid Division by Zero errors, for now, but could become a setting later
local totalGoldAmount, numGoldEntries = DB.GetTotalAmount("GOLD", toon)
local totalOrderResourcesAmount, numOrderResourcesEntries = DB.GetTotalAmount("ORDER_RESOURCES", toon)
-- Calculate statistics
local goldPerDay = (numGoldEntries >= STATISTICAL_SIGNIFICANT_THRESHOLD) and (totalGoldAmount / numGoldEntries) or 0
local orderResourcesPerDay = (numOrderResourcesEntries >= STATISTICAL_SIGNIFICANT_THRESHOLD) and (totalOrderResourcesAmount / numOrderResourcesEntries) or 0
-- Format strings for output
local formattedTotalGoldAmount = GetCoinTextureString(totalGoldAmount)
local formattedGoldPerDay = GetCoinTextureString(goldPerDay)
local formattedTotalOrderResourcesAmount = math_abs(totalOrderResourcesAmount) .. " |TInterface\\Icons\\inv_orderhall_orderresources:12|t"
local formattedTotalOrderResourcesPerDay = math_abs(orderResourcesPerDay) .. " |TInterface\\Icons\\inv_orderhall_orderresources:12|t"
local goldBaseString = format("Total gold earned: %s", formattedTotalGoldAmount)
local goldNumEntriesString = (numGoldEntries > 1) and format(" over %d days", numGoldEntries) or "" -- Show if there is data for several days
local goldPerDayString = (numGoldEntries > 0) and format(" (%s per day)", formattedGoldPerDay) or "" -- Show if there is any data
local goldSummaryString = goldBaseString .. goldNumEntriesString .. goldPerDayString
ChatMsg(goldSummaryString)
local orderResourcesBaseString = format("Total OR spent: %s", formattedTotalOrderResourcesAmount)
local orderResourcesNumEntriesString = (numOrderResourcesEntries > 1) and format(" over %d days", numOrderResourcesEntries) or "" -- Show if there is data for several days
local orderResourcesPerDayString = (numOrderResourcesEntries > 0) and format(" (%s per day)", formattedTotalOrderResourcesPerDay) or "" -- Show if there is any data
local orderResourcesSummaryString = orderResourcesBaseString .. orderResourcesNumEntriesString .. orderResourcesPerDayString
ChatMsg(orderResourcesSummaryString)
--- TODO: More stats?
-- Gold per OR
end
end
end
-- Format numbers (TODO: thousands separator for large numbers) for readability
local formattedGoldToday = GetCoinTextureString(totalGoldToday) -- TODO: sum up today gold/OR for ALL chars
local formattedGoldTotal = GetCoinTextureString(totalGold)
local formattedOrderResourcesToday = math_abs(totalOrderResourcesToday) .. " |TInterface\\Icons\\inv_orderhall_orderresources:12|t"
local formattedOrderResourcesTotal = math_abs(totalOR) .. " |TInterface\\Icons\\inv_orderhall_orderresources:12|t"
-- Print summary
ChatMsg("-----------------------------------------------------------------------------------------------------------")
ChatMsg("Printing Order Hall summary for all characters...")
ChatMsg("Gold earned today: " .. formattedGoldToday .. " (Total: " .. formattedGoldTotal .. ")")
ChatMsg("OR spent today: " .. formattedOrderResourcesToday .. " (Total: " .. formattedOrderResourcesTotal .. ")")
-- TODO: More stats - G/OR, OR/Day, G/Day, week, month, per char, etc. (see Excel sheet)
ChatMsg("-----------------------------------------------------------------------------------------------------------")
end
function DB.Reset() -- TODO: Reset other parts, too?
for toon, entry in pairs(ContainerLootLoggerDB) do
if IsFQCN(toon) then -- Is a an entry for a character, and not a general DB setting (TODO: DB structured into toons/settings part for v2?)
local goldToday = type(entry) == "table" and entry["LEGION_ORDER_HALL"] and entry["LEGION_ORDER_HALL"]["GOLD"] and entry["LEGION_ORDER_HALL"]["GOLD"]["amount"] or 0
if goldToday > 0 then
local goldTotal = entry["LEGION_ORDER_HALL"]["GOLD_TOTAL"] and entry["LEGION_ORDER_HALL"]["GOLD_TOTAL"]["amount"] or 0
local newGoldTotal = goldTotal + goldToday
DebugMsg(MODULE, "Resetting entry for character " .. toon .. " - " .. GetCoinTextureString(entry["LEGION_ORDER_HALL"]["GOLD"]["amount"]) .. " earned today ( " .. GetCoinTextureString(newGoldTotal) .. " in total, was " .. GetCoinTextureString(goldTotal) .. " at the time of the last reset)")
-- Copy entry from GOLD to GOLD_TOTAL and add counts
entry["LEGION_ORDER_HALL"]["GOLD_TOTAL"] = entry["LEGION_ORDER_HALL"]["GOLD_TOTAL"] or {}
entry["LEGION_ORDER_HALL"]["GOLD_TOTAL"]["amount"] = (entry["LEGION_ORDER_HALL"]["GOLD_TOTAL"]["amount"] or 0) + entry["LEGION_ORDER_HALL"]["GOLD"]["amount"]
entry["LEGION_ORDER_HALL"]["GOLD_TOTAL"]["count"] = (entry["LEGION_ORDER_HALL"]["GOLD_TOTAL"]["count"] or 0) + entry["LEGION_ORDER_HALL"]["GOLD"]["count"]
-- Reset the current entry
entry["LEGION_ORDER_HALL"]["GOLD"]["amount"] = 0
entry["LEGION_ORDER_HALL"]["GOLD"]["count"] = 0
else
DebugMsg(MODULE, "Nothing to reset for character " .. toon)
end
end
end
end
-- Add module to shared environment
CLL.DB = DB