-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDatabase.lua
More file actions
284 lines (243 loc) · 11.5 KB
/
Database.lua
File metadata and controls
284 lines (243 loc) · 11.5 KB
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
local statusMap = {
discovered = "|cffaaaaaaDiscovered|r",
inProgress = "|cffffff00In Progress|r",
completed = "|cff00ff00Completed|r",
abandoned = "|cffff0000Abandoned|r"
}
function QuestKeeper.ValidateDatabase()
if QuestKeeperConfig.dbVersion ~= QuestKeeper.LATEST_DB_VERSION then
QuestKeeper.DB_STATE = QuestKeeper.DB_STATES.LOCKED
-- Lock the table using a metatable
setmetatable(QuestKeeperDB, {
__newindex = function(t, k, v)
-- This function runs whenever someone tries to write: QuestKeeperDB[key] = value
-- We do nothing, effectively blocking the write
end
})
return
end
QuestKeeper.DB_STATE = QuestKeeper.DB_STATES.READY
end
-- Recover quests that were completed at a time when QuestKeeper was not active
function QuestKeeper.ImportCompletedQuests()
-- Do not import if DB not ready
if QuestKeeper.DB_STATE ~= QuestKeeper.DB_STATES.READY then return end
local allCompleted = C_QuestLog.GetAllCompletedQuestIDs()
local importCount = 0
if allCompleted then
for _, qID in ipairs(allCompleted) do
if not QuestKeeperDB[qID] then
local q = QuestKeeper.GetOrCreateQuest(qID)
if q then
-- 2. Populate and override only key historical markers (omitting any empty values)
q.title = C_QuestLog.GetTitleForQuestID(qID) or "Imported"
q.status = "completed"
q.isImported = true
q.completionCount = 1
importCount = importCount + 1
end
end
end
end
if importCount > 0 then
print("|cff00ff00QuestKeeper:|r Successfully imported |cffffffff" .. importCount .. "|r quests that were completed while the addon was not enabled / installed.")
end
end
function QuestKeeper.UpdateActiveQuests()
if QuestKeeper.DB_STATE ~= QuestKeeper.DB_STATES.READY then return end
local numEntries = C_QuestLog.GetNumQuestLogEntries()
for i = 1, numEntries do
local info = C_QuestLog.GetInfo(i)
if info and info.questID and not info.isHeader and not info.isHidden then
local qID = info.questID
local q = QuestKeeperDB[qID]
-- Only check quests that are tracked as inProgress
if q and q.status == "inProgress" then
local questIndex = C_QuestLog.GetLogIndexForQuestID(qID)
if questIndex then
local intro, objectives = GetQuestLogQuestText(questIndex)
-- Only save if the text actually exists and is not empty
if objectives and objectives ~= "" then q.objectives = objectives end
-- Only save rewards if they are greater than zero
local xp = GetQuestLogRewardXP(qID) or 0
local money = GetQuestLogRewardMoney(qID) or 0
q.xp = (xp > 0) and xp or nil
q.money = (money > 0) and money or nil
-- Setup frequency flags cleanly
q.isDaily = (info.frequency == Enum.QuestFrequency.Daily) and true or nil
q.isRepeatable = (info.frequency == Enum.QuestFrequency.Repeatable) and true or nil
end
end
end
end
end
local function CreateHeader(text, width, xOffset, sortKey)
local h = CreateFrame("Button", nil, QuestListFrame)
h:SetSize(width, 25)
h:SetPoint("TOPLEFT", xOffset, -30)
h.text = h:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
h.text:SetPoint("LEFT", 5, 0)
h.text:SetText(text)
h.sortKey = sortKey
h.baseText = text
h:SetScript("OnClick", function()
if QuestKeeper.currentSort.column == sortKey then
QuestKeeper.currentSort.order = (QuestKeeper.currentSort.order == "asc") and "desc" or "asc"
else
QuestKeeper.currentSort.column = sortKey
QuestKeeper.currentSort.order = "asc"
end
QuestKeeper.UpdateList()
end)
QuestKeeper.headers[sortKey] = h
end
function QuestKeeper.UpdateList()
if not QuestListFrame or not QuestListScrollFrame then return end
if not QuestKeeper.headers["id"] then
local function CreateHeader(text, width, xOffset, sortKey)
local h = CreateFrame("Button", nil, QuestListFrame)
h:SetSize(width, 25); h:SetPoint("TOPLEFT", xOffset, -58)
h.text = h:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
h.text:SetPoint("LEFT", 5, 0); h.text:SetText(text)
h.sortKey, h.baseText = sortKey, text
h:SetScript("OnClick", function()
if QuestKeeper.currentSort.column == sortKey then
QuestKeeper.currentSort.order = (QuestKeeper.currentSort.order == "asc" and "desc" or "asc")
else
QuestKeeper.currentSort.column, QuestKeeper.currentSort.order = sortKey, "asc"
end
QuestKeeper.UpdateList()
end)
QuestKeeper.headers[sortKey] = h
end
CreateHeader("ID", 60, 20, "id")
CreateHeader("Quest Name", 350, 80, "title")
CreateHeader("Status", 100, 430, "status")
CreateHeader("Last Updated", 150, 530, "timestamp")
end
local scrollChild = QuestKeeper.listContent
if not scrollChild then
scrollChild = CreateFrame("Frame", nil, QuestListScrollFrame)
QuestKeeper.listContent = scrollChild
QuestListScrollFrame:SetScrollChild(scrollChild)
end
scrollChild:SetSize(700, 1)
for key, h in pairs(QuestKeeper.headers) do
local arrow = (QuestKeeper.currentSort.column == key) and (QuestKeeper.currentSort.order == "asc" and " [^]" or " [v]") or ""
h.text:SetText(h.baseText .. arrow)
end
for _, b in pairs(QuestKeeper.buttons) do b:Hide() end
local searchText = (QuestKeeper.searchBox and QuestKeeper.searchBox:GetText() or ""):lower()
local dataList = {}
local totalQuests = 0
-- For date, only use numbers for search
-- For quest name unchanged input is used for search
local dateSearchThreshold = searchText:gsub("[%.%-]", "")
for id, data in pairs(QuestKeeperDB) do
totalQuests = totalQuests + 1
local questID = tostring(id)
local questName = (data.title or "Unknown"):lower()
local displayDate = (data.status == "completed" or data.status == "abandoned") and (data.completedDate or "Unknown")
or (data.status == "inProgress" and (data.acceptedDate or "Unknown")) or (data.discoveredDate or "Unknown")
-- Unify dates by removing "." and "-" characters
local dateOnlyNumbers = displayDate:gsub("[%.%-]", "")
-- SEARCH LOGIC:
-- 1. ID Match
-- 2. Quest name includes text?
-- 3. Does the date match original, unmodified data?
-- 4. Does date match unified?
if searchText == "" or
questID:find(searchText, 1, true) or
questName:find(searchText, 1, true) or
displayDate:lower():find(searchText, 1, true) or
(dateSearchThreshold ~= "" and dateOnlyNumbers:find(dateSearchThreshold, 1, true)) then
-- Create a shallow copy to protect the global QuestKeeperDB from sorting corruption
table.insert(dataList, {
id = id,
title = data.title,
status = data.status,
timestamp = data.timestamp,
isImported = data.isImported,
isDaily = data.isDaily,
isRepeatable = data.isRepeatable,
isEdited = data.isEdited,
displayDate = displayDate
})
end
end
table.sort(dataList, function(a, b)
if not a or not b then return false end
local col = QuestKeeper.currentSort.column
local order = QuestKeeper.currentSort.order
-- For timestamps: Unknow should always be at the bottom of the list, no matter if asc or desc ordering
if col == "timestamp" then
-- 1. Always put imported(recovered) below a properly tracked quest
if a.isImported and not b.isImported then return false end
if not a.isImported and b.isImported then return true end
-- 2. For same type (both recovered or both tracked)
local tA, tB = tonumber(a.timestamp) or 0, tonumber(b.timestamp) or 0
if tA ~= tB then
if order == "asc" then return tA < tB else return tA > tB end
end
-- 3. Use ID if dates match.
return tostring(a.id) < tostring(b.id)
end
-- General sorting logic for all other column
local valA, valB = a[col] or "", b[col] or ""
-- Numeric ordering for IDs
if col == "id" then
local nA, nB = tonumber(valA) or 0, tonumber(valB) or 0
if nA ~= nB then
if order == "asc" then return nA < nB else return nA > nB end
end
end
-- For textual values
local strA, strB = tostring(valA):lower(), tostring(valB):lower()
if strA ~= strB then
if order == "asc" then return strA < strB else return strA > strB end
end
-- Fallback: ID
return tostring(a.id) < tostring(b.id)
end)
if QuestKeeper.searchCount then
if searchText == "" then
QuestKeeper.searchCount:SetText(string.format("%d/%d", totalQuests, totalQuests))
else
QuestKeeper.searchCount:SetText(string.format("%d/%d", #dataList, totalQuests))
end
end
for i, data in ipairs(dataList) do
local b = QuestKeeper.buttons[i] or CreateFrame("Button", nil, scrollChild, "UIPanelButtonTemplate")
b:SetSize(690, 25); b:SetPoint("TOPLEFT", 5, -(i-1)*26); b:Show()
QuestKeeper.buttons[i] = b
if not b.colID then
b.colID = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); b.colID:SetPoint("LEFT", 10, 0)
b.colTitle = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); b.colTitle:SetPoint("LEFT", 75, 0)
b.colStatus = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); b.colStatus:SetPoint("LEFT", 425, 0)
b.colDate = b:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall"); b.colDate:SetPoint("LEFT", 525, 0)
end
local displayStatus = statusMap[data.status] or "Unknown"
b.colID:SetText(data.id)
local titleText = data.title or "Unknown"
if data.isDaily then
titleText = "|cff00ccff[D] |r" .. titleText
elseif data.isRepeatable then
titleText = "|cff00ff00[R] |r" .. titleText
end
b.colTitle:SetText(titleText .. (data.isEdited and " |cff00ff00 (Edited import)|r" or ""))
b.colStatus:SetText(displayStatus)
b.colDate:SetText(data.displayDate or "Unknown")
b:SetScript("OnClick", function()
-- 1. State update
QuestKeeper.selectedQuestID = data.id
QuestKeeper.currentGossipIndex = 1
-- 2. Call the new formatter
if QuestKeeper.UpdateDetailDisplay then
QuestKeeper.UpdateDetailDisplay()
end
-- 3. Show the frame
QuestDetailDisplay:Show()
end)
end
scrollChild:SetHeight(#dataList * 26 + 10)
end