-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathQuest.lua
657 lines (553 loc) · 20 KB
/
Quest.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
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
-----------------------------------------
-- INFORMATION
-----------------------------------------
--[[
Each quest has many different Main steps
Each Main step can have many steps
each step can have many conditions
Main steps are not actually created by ZOS
--]]
-----------------------------------------
-- LOCALIZED GLOBAL VARIABLES
-----------------------------------------
local ZGV = _G.ZGV
local tinsert,type, ipairs = table.insert, type, ipairs
local Data = ZGV.Data
local MakeExcerpt = ZGV.Utils.MakeExcerpt
local MatchExcerpt = ZGV.Utils.MatchExcerpt
local d = _G.d
local GetJournalQuestNumSteps = _G.GetJournalQuestNumSteps
local GetJournalQuestStepInfo = _G.GetJournalQuestStepInfo
local EMPTY_STRING = _G.EMPTY_STRING
local GetJournalQuestNumConditions = _G.GetJournalQuestNumConditions
local GetJournalQuestConditionInfo = _G.GetJournalQuestConditionInfo
local GetJournalQuestInfo = _G.GetJournalQuestInfo
-- Saved Variables initialized at startup
local svchardata, savedquests, svcompletedquests
-- Classes
local Quest = ZGV.Class:New("Quest")
local Quest_mt = { __index = Quest }
local Quests = ZGV.Class:New("Quests")
local Quests_mt = { __index = Quest }
local QuestStep = ZGV.Class:New("QuestStep")
local QuestStep_mt = { __index = QuestStep }
local QuestCondition = ZGV.Class:New("QuestCondition")
local QuestCondition_mt = { __index = QuestCondition }
local QuestReward = ZGV.Class:New("QuestReward")
local QuestReward_mt = { __index = QuestReward }
local PrefixPairs = _G.PrefixPairs
local GetByPrefix = _G.GetByPrefix
local MAX_JOURNAL_QUESTS = _G.MAX_JOURNAL_QUESTS
local IsValidQuestIndex = _G.IsValidQuestIndex
local GetJournalQuestName = _G.GetJournalQuestName
-- Enums
local questTypes = {}
for k,v in PrefixPairs("QUEST_TYPE") do
questTypes[v] = k:gsub("QUEST_TYPE_","")
end
local questStepTypes = {}
for k,v in PrefixPairs("QUEST_STEP_TYPE") do
questStepTypes[v] = k:gsub("QUEST_STEP_TYPE_","")
end
local questConditionTypes = {}
for k,v in PrefixPairs("QUEST_CONDITION_TYPE") do
questConditionTypes[v] = k:gsub("QUEST_CONDITION_TYPE_","")
end
local questRewardTypes = {}
for k,v in PrefixPairs("REWARD_TYPE") do
questRewardTypes[v] = k:gsub("REWARD_TYPE_","")
end
local stepVisibilityTypes = {}
for k,v in PrefixPairs("QUEST_STEP_VISIBILITY") do
stepVisibilityTypes[v] = k:gsub("QUEST_STEP_VISIBILITY_","")
end
-----------------------------------------
-- SAVED REFERENCES
-----------------------------------------
ZGV.Quests = Quests
ZGV.QuestProto = Quest
ZGV.completedQuests = {}
Quests.questTypes = questTypes
Quests.questStepTypes = questStepTypes
Quests.questConditionTypes = questConditionTypes
Quests.questRewardTypes = questRewardTypes
Quests.stepVisibilityTypes = stepVisibilityTypes
_G['ZQuest']=Quest
_G['ZQuestStep']=QuestStep
_G['ZQuestCondition']=QuestCondition
-----------------------------------------
-- LOAD TIME SETUP
-----------------------------------------
-- Setup a metatable for easier access to completed quests.
setmetatable(ZGV.completedQuests,{
__index = function(self,ind) return Quests:IsQuestComplete(ind) end,
__call = function(self,...) return Quests:IsQuestComplete(...) end
})
-----------------------------------------
-- QUESTS FUNCTIONS
-----------------------------------------
-- These are supposed to be the ONLY public functions, since there's virtually zero need to interface
-- with all the Step/Condition mechanics from outside.
Quests.journal = {}
function Quests:FindQuest(questName)
if not questName then return end
for i = 1,MAX_JOURNAL_QUESTS do
if IsValidQuestIndex(i) then
local name = GetJournalQuestName(i)
if name == questName then
return i
end
end
end
end
function Quests:HasQuest(id)
return not not self:FindQuest(id)
end
function Quests:RemoveQuest(journalIndex)
if type(journalIndex)=="string" then
journalIndex = self:FindQuest(journalIndex)
end
self.journal[journalIndex]=nil
end
function Quests:Clear()
ZGV.Utils.table_wipe_keys(Quests.journal)
end
-- Load quest from journal into Quests, so that we can update and check its progress and shit.
-- Used on startup and in "quest added" events.
function Quests:GetQuest(journalIndex,is_retry) -- or questname
local questname
if type(journalIndex) == "string" then
questname = journalIndex
journalIndex = self:FindQuest(questname)
end
if not journalIndex then return end
local quest = self.journal[journalIndex]
if quest and questname and quest.name~=questname then -- Whoa, journal changed, title mismatch!
if is_retry then
return nil
end -- failed to retry
self:UpdateJournal()
return self:GetQuest(questname,"retry")
end
if not quest then
-- Quest isn't in our table for this session, lets try to get it from our data first. If not in data (include SV) then create new.
quest = Quest:New(journalIndex)
self.journal[journalIndex] = quest
end
return quest
end
function Quests:UpdateQuest(journalIndex)
local quest = Quests:GetQuest(journalIndex)
if quest then
quest:FillFromJournal()
end
-- TODO... or will this suffice?
end
--PUBLIC
function Quests:SetConditionCoords(journalIndex,stepnum,condnum, typ,m,x,y,r,b1,b2)
local GetByPrefix = _G.GetByPrefix
if not journalIndex or not stepnum or not condnum then
error("Quests:SetConditionCoords without journalIndex, stepnum or condnum")
end
local quest = Quests:GetQuest(journalIndex)
if not quest or not quest.steps then return end -- shouldn't happen!
if quest.steps[stepnum] ~= nil then
local step = quest.steps[stepnum]
if not step then return end
local cond = step.conditions[condnum]
if not cond then return end
cond.coords = { pinType = GetByPrefix("MAP_PIN_TYPE",typ) or typ, map = m, x = x, y = y, r = r, b1 = b1, b2 = b2 }
--ZGV:Debug(("&quest Got coords for quest |cffffff%s|r step |cffffff%d|r cond |cffffff%d|r: %d %.3f %.3f %.3f %s %s"):format(quest.name,step.num,cond.num,typ,x,y,r, tostring(b1),tostring(b2)))
end
end
--PUBLIC though legacy
function Quests:IsQuestComplete(questid)
return ZGV.QuestTracker:IsQuestComplete(questid)
end
--PUBLIC
-- This is the powerhorse. Returns: complete,possible,explanation,...
-- v1.1 stripped
function Quests:GetCompletionStatus(qname,condtxt)
ZGV:Debug("&quest GetCompletionS |cffeeaa%s|r / |cffaaee%s|r",qname or "", condtxt or "")
-- QUEST: COMPLETE? Perhaps it's all done?
local isComplete = self:IsQuestComplete(qname)
if isComplete then
return true,true,"quest complete"
end -- Whole quest is complete. Cheers.
-- ... if not, keep checking.
-- Check quest in journal.
local quest = self:GetQuest(qname)
if not quest then
return false,false,"not in journal"
end
if quest:HasRecentlyCompletedCondition(condtxt) then
return true,true,"cond recently completed"
end
--
-- STEP: PINPOINT. Use condtxt if need be.
local stepnum,condnum = quest:FindStepCond(condtxt)
if not stepnum then
return false,false,"no step matched",condtxt
end -- no step? too bad.
local step = quest.steps[stepnum]
if not step then
return false,false,"no step found"
end
-- ... if we have a step, carry on!
local complete,possible = step:IsComplete()
if complete then
return complete,possible,"step completion"
end
-- COND: PINPOINT (if we haven't already, when trying to find the step.)
local cond = condnum and step.conditions and step.conditions[condnum]
if not cond then
local complete,possible = step:IsComplete()
return complete,possible,"step overrides cond"
end -- no condition? too bad.
-- Hallelujah, we have the condition nailed!
local complete,possible,curv,maxv = cond:GetCompletion()
return complete,possible,"cond completion",curv,maxv, ("|c00aaff%s|cffffff step |c00ffaa%d|cffffff cond |c00ffaa%d |c00aaff%s|r"):format(qname, stepnum,condnum, condtxt or "?")
end
function Quests:UpdateJournal()
self:Clear()
for ji = 1,MAX_JOURNAL_QUESTS do
if IsValidQuestIndex(ji) then
self:GetQuest(ji) -- This loads all our current quests into ZGV.Quests
end
end
end
-----------------------------------------
-- QUEST DATA FUNCTIONS
-----------------------------------------
function Quest:New(journalIndex)
if not journalIndex then
error("Quest:New(nil) !?")
end
local name = GetJournalQuestName(journalIndex)
local quest={
name=name,
id=Data:GetQuestIdByName(name),
steps={},
}
setmetatable(quest,Quest_mt)
quest:FillFromJournal(journalIndex)
return quest
end
-- /dump ZGV.Quests:GetCompletionStatus("Finding the Family",nil,1)
local ShowFloatingMessage = ZGV.Utils.ShowFloatingMessage
-- Create a new Quest object (from current journal data) to put in ZGV.Quests.
function Quest:FillFromJournal(journalIndex)
if not journalIndex then
journalIndex = self:GetJournalIndex()
if not journalIndex then
self.steps = nil
return
end
end
local questName, backgroundText, activeStepText, activeStepType, activeStepTrackerOverrideText, completed, tracked, questLevel, pushed, questType = GetJournalQuestInfo(journalIndex)
if questName == "" then
return false
end
if questName ~= self.name then
if ZGV.DEV then
d("What..? Quest journalIndex="..journalIndex.." has name "..questName..", expected "..(self.name or "?"))
end
return
end
self.name = questName
self.level = questLevel
self.bgtext = backgroundText
self.questType = questTypes[questType] or questType
if self.steps and self.activeStepText ~= activeStepText then -- make "new" step
self.oldsteps = self.steps
end
-- fill
self.steps = {}
for stepnum=1,GetJournalQuestNumSteps(journalIndex) do
local step = self.steps[stepnum] or QuestStep:New()
local ok = step:FillFromJournal(journalIndex,stepnum)
if ok and (not self.steps[stepnum] or self.steps[stepnum].text~=step.text) then
step.parentQuest = self
self.steps[stepnum]=step
end
end
self.activeStepText = activeStepText
return self
end
local function textmatch(subject,test)
if not subject or not test then
return false
end
if subject == test then
return true
end
local zo_plainstrfind = _G.zo_plainstrfind
if zo_plainstrfind(test,"*") and subject:match(test) then
return true
end
return false
end
-- Check which CURRENT step/condition this textual objective belongs to.
function Quest:FindStepCond(condtxt)
for snum,s in ipairs(self.steps) do
if textmatch(s.trackerText,condtxt)
then return snum,0
end -- trackerText matched?
if s.conditions then
for cnum,c in ipairs(s.conditions) do
if textmatch(c.text,condtxt) then
return snum,cnum
end
end
end
end
return nil,nil
end
-- Check which CURRENT step/condition this textual objective belongs to.
function Quest:HasRecentlyCompletedCondition(condtxt)
if not self.oldsteps then
return false
end
for snum,s in ipairs(self.oldsteps) do
if textmatch(s.trackerText,condtxt) then
return true
end -- trackerText matched?
if s.conditions then
for cnum,c in ipairs(s.conditions) do
if textmatch(c.text,condtxt) then
return true
end
end
end
end
return false
end
---------------- QUEST DATA DUMPS --------------------------
function Quest:Dump_OLD_StageSnapshot(strict)
local snap = {}
tinsert(snap,("Q1 %s"):format(MakeExcerpt(self.bgtext)))
local ji = self:GetJournalIndex()
local trackered
for si = 1,GetJournalQuestNumSteps(ji) do
local steptext,visibility,steptype,tracker,numcond = GetJournalQuestStepInfo(ji,si)
if steptext == EMPTY_STRING then
steptext = "NO TEXT"
end
tinsert(snap,("S%d %s%s"):format(si,strict and "== " or "",MakeExcerpt(steptext)))
if tracker and tracker ~= EMPTY_STRING then
tinsert(snap,("S%dC0 %s%s"):format(si,"== ",MakeExcerpt(tracker)))
trackered = true
end
for ci = 1,GetJournalQuestNumConditions(ji,si) do
local conditionText,current,maxv,isFailCondition,isComplete,isCreditShared = GetJournalQuestConditionInfo(ji,si,ci)
conditionText = conditionText:gsub(ZGV.Utils.quest_cond_counts,"")
tinsert(snap,("S%dC%d %s%s"):format(si,ci,(strict or ((si == 1 and ci == 1) and not trackered)) and "== " or "", MakeExcerpt(conditionText)))
end
end
return snap
end
function Quest:DumpQuestStructure()
local qi=self:GetJournalIndex()
local title,bgtext,asteptxt,asteptype,astepoverride,_,_,_ = GetJournalQuestInfo(qi)
local ret = {}
tinsert(ret,(("%d. |cffffff%s"):format(qi,title)))
for si=1,GetJournalQuestNumSteps(qi) do
local steptext,visibility,steptype,tracker,numcond = GetJournalQuestStepInfo(qi,si)
if steptext == EMPTY_STRING then
steptext = "NO TEXT"
end
tinsert(ret,("- Step %d. |cffeedd%s|r |c008800(|c00aa00%s|c008800)|r%s"):format(
si,steptext,
GetByPrefix("QUEST_STEP_TYPE",steptype,true),
visibility and (" |c0088ff[|c33aaff%s|c0088ff]|r"):format(GetByPrefix("QUEST_STEP_VISIBILITY",visibility,true) or visibility) or ""
))
if tracker ~= EMPTY_STRING then
tinsert(ret,("' = tracker: |cffaa00%s|r"):format(tracker))
end
for ci = 1,GetJournalQuestNumConditions(qi,si) do
local conditionText,current,max,isFailCondition,isComplete,isCreditShared = GetJournalQuestConditionInfo(qi,si,ci)
conditionText = conditionText:gsub(ZGV.Utils.quest_cond_counts,"")
tinsert(ret,("- - Cond %d. |cffeebb%s|r |c888888(%d/%d)|r%s"):format(ci,conditionText,current,max,(isFailCondition and " |cff0000(FAIL)" or "")..(isComplete and " |c00ff00(COMPLETE)" or "")))
end
end
return ret
end
function Quest:DumpQuestStructure_Print()
d(self:DumpQuestStructure())
end
function Quest:_TODO_DumpReport_TODO_rework_into_oldsteps()
self.recentStages = self.recentStages or {}
local recent = #self.recentStages>0 and table.concat(self.recentStages,",") or "unknown"
local s = "--- QUEST STAGE REPORT ---\n"
s = s .. ("QUEST: %s ##%d\n"):format(self.name,self.id)
local currentStage = _G.currentStage
if currentStage then
s = s .. "CURRENT STAGE:\n"
else
s = s .. ([[RECENT STAGES: %s
CURRENT QUEST STATE:
]]):format(recent)
end
local lastrecent = self.recentStages[#self.recentStages]
s = s .. (" [%d] = {\n"):format(tonumber(lastrecent) and lastrecent+1 or 0)
for i,r in ipairs(self:DumpStageSnapshot()) do
s = s .. (" [[%s]],"):format(r)
--if r:match("STAGE %d") then s = s .. " --MAYBE" end
s = s .. "\n";
end
s = s .. " },"
return s
end
-----------------------------------------
-- QUEST CLASS FUNCTIONS
-----------------------------------------
function Quest:GetJournalIndex()
return Quests:FindQuest(self.name or "") or (ZGV:Debug("Journal for quest "..(self.name or "?").." unknown!?") and nil)
end
function Quest:GetText()
return self.steps and self.steps[1] and self.steps[1].text or "NO TEXT??"
end
function Quest:tostring()
return "Quest: "..(self.name or "")
end
-----------------------------------------
-- QUESTSTEP CLASS FUNCTIONS
-----------------------------------------
function QuestStep:New()
local step = {
conditions = {},
}
setmetatable(step,QuestStep_mt)
return step
end
function QuestStep:FillFromJournal(journalIndex, stepIndex) -- MAKE SURE WE'RE ON THAT STAGE!
journalIndex = journalIndex or self.parentQuest:GetJournalIndex()
stepIndex = stepIndex or self.num
if not journalIndex or not stepIndex then
error("Step:FillFromJournal() no journalindex or stepnum given or found")
end
local steptext, visibility, stepType, trackerOverrideText, numConditions = GetJournalQuestStepInfo(journalIndex, stepIndex)
if steptext == EMPTY_STRING then
steptext = "NO TEXT"
end
if trackerOverrideText == EMPTY_STRING then
trackerOverrideText = nil
end
self.num = stepIndex
self.text = steptext
self.stepType = questStepTypes[stepType] or stepType
self.visibility = stepVisibilityTypes[visibility] or visibility
self.trackerText = trackerOverrideText
self.num = stepIndex
self:FillConditionsFromJournal(journalIndex,self.num)
return self
end
function QuestStep:FillConditionsFromJournal(journalIndex, stepIndex) -- indexes optional, step may already know them
if (not journalIndex or not stepIndex) then
if not self.parentQuest then return end
journalIndex = self.parentQuest:GetJournalIndex() -- REGARDLESS whether the stage is current or not!!!
if not journalIndex then
error("We don't have the quest!?")
end -- we don't have that quest?? WTF?
end
stepIndex = stepIndex or self.num
self.num = stepIndex
for condNum=1,GetJournalQuestNumConditions(journalIndex, stepIndex) do
local condition = self.conditions[condNum] or QuestCondition:New()
local ok = condition:FillFromJournal(journalIndex, stepIndex, condNum)
if ok then
condition.parentStep = self
self.conditions[condNum]=condition
end
end
return self
end
function QuestStep:GetQuestJournalIndex()
return self.parentQuest and self.parentQuest:GetJournalIndex()
end
function QuestStep:IsComplete()
if self.stepType=="END" and self.visibility=="HIDDEN" and #self.conditions==0 then
return true,true
end
if self.stepType=="END" then
return false,true
end
for ci,cond in ipairs(self.conditions) do
local complete,possible=cond:GetCompletion()
if complete and self.stepType=="OR" then
return true,true
end
if not complete and self.stepType=="AND" then
return false,true
end
end
return self.stepType == "AND",true
end
function QuestStep:tostring()
return "Step: "..(self.text or "")
end
-----------------------------------------
-- QUESTCONDITION CLASS FUNCTIONS
-----------------------------------------
function QuestCondition:New()
local condition = {}
setmetatable(condition,QuestCondition_mt)
return condition
end
function QuestCondition:FillFromJournal(journalIndex, stepIndex, conditionIndex) -- MAY NOT BE CURRENT STAGE. Use with caution.
if not journalIndex or not stepIndex and self.num and self.parentStep then
journalIndex = self:GetQuestJournalIndex()
if not journalIndex then return end -- we don't have that quest...
conditionIndex = self.num
stepIndex = self.parentStep.num
end
self.num=conditionIndex
local conditionText, current, maxval, isFailCondition, isComplete, isCreditShared = GetJournalQuestConditionInfo(journalIndex, stepIndex, conditionIndex)
conditionText = conditionText:gsub(ZGV.Utils.quest_cond_counts,"")
local condType = _G.GetJournalQuestConditionType(journalIndex, stepIndex, conditionIndex)
if conditionText=="" and current==0 and maxval==0 then
return false
end
self.text = conditionText
self.condType = questConditionTypes[condType] or condType
self.current = current
self.maxval = maxval
return self
end
function QuestCondition:GetCompletion() -- returns: isComplete,isCurrent,curVal,maxVal
local journalIndex = self:GetQuestJournalIndex()
if not journalIndex then
return false,false,"WTF #1"
end -- strange...
local conditionText, current, maxval, isFailCondition, isComplete, isCreditShared = GetJournalQuestConditionInfo(journalIndex,self.parentStep.num,self.num)
return isComplete,true,current,maxval
end
function QuestCondition:GetQuestJournalIndex()
return self.parentStep and self.parentStep:GetQuestJournalIndex()
end
function QuestCondition:RequestCoords()
local complete,possible,curr,need = self:GetCompletion()
if not possible then return end
local journalIndex = self:GetQuestJournalIndex()
if not journalIndex then
return false,"WTF #2"
end -- strange...
local RequestJournalQuestConditionAssistance = _G.RequestJournalQuestConditionAssistance
RequestJournalQuestConditionAssistance(self:GetQuestJournalIndex(),self.parentStep.num,self.num)
-- the event is handled in QuestTracker
end
function QuestCondition:tostring()
return "Cond: " .. (self.text or "")
end
-----------------------------------------
-- DEBUG
-----------------------------------------
function Quests:Debug(...)
local str = ...
ZGV:Debug("&quest "..str, select(2,...) )
end
-----------------------------------------
-- STARTUP
-----------------------------------------
tinsert(ZGV.startups,function(self)
end)