-
Notifications
You must be signed in to change notification settings - Fork 11
/
Goal.lua
1058 lines (884 loc) · 30 KB
/
Goal.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
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-----------------------------------------
-- INFORMATION
-- Guide events and actions (e.g. "goto")
-----------------------------------------
-----------------------------------------
-- LOCALIZED GLOBAL VARIABLES
-----------------------------------------
local ZGV = _G.ZGV
local GetCurrentMapIndex = _G.GetCurrentMapIndex
local GuideViewer = _G.GuideViewer
local GetNumPOIs = _G.GetNumPOIs
local IsPOIWayshrine = _G.IsPOIWayshrine
local GetPOIInfo = _G.GetPOIInfo
local zo_plainstrfind = _G.zo_plainstrfind
local GetPOIMapInfo = _G.GetPOIMapInfo
local MAP_PIN_TYPE_POI_COMPLETE = _G.MAP_PIN_TYPE_POI_COMPLETE
local GetLoreBookInfo = _G.GetLoreBookInfo
local GetAchievementInfo = _G.GetAchievementInfo
local GetAchievementCriterion = _G.GetAchievementCriterion
local zo_loadstring = _G.zo_loadstring
local zo_max = _G.zo_max
local GoalProto = {}
local Goal = ZGV.Class:New("Goal")
local GoalProto_mt = { __index=Goal }
local GOALTYPES = {}
local empty_table = {}
local tinsert, min, type, ipairs = table.insert, math.min, type, ipairs
local L = ZGV.L
local split = _G.zo_strsplit
-- Parser functions
local ParseMapXYDist = ZGV.Parser.ParseMapXYDist
local MakeCondition = ZGV.Parser.MakeCondition
local ParseQuest = ZGV.Parser.ParseQuest
local ParseId = ZGV.Parser.ParseId
local INDENT = ""
-----------------------------------------
-- SAVED REFERENCES
-----------------------------------------
ZGV.GoalProto = GoalProto
ZGV.GOALTYPES = GOALTYPES
-----------------------------------------
-- LOAD TIME SETUP
-----------------------------------------
setmetatable( GOALTYPES, { __index = function() return empty_table end } )
-----------------------------------------
-- LOCAL FUNCTIONS
-----------------------------------------
local function COLOR_LOC(s) return "|cffee77"..tostring(s).."|r" end
local function COLOR_COUNT(s) return "|cffffcc"..tostring(s).."|r" end
local function COLOR_ITEM(s) return "|caaeeff"..tostring(s).."|r" end
local function COLOR_QUEST(s) return "|ceebbff"..tostring(s).."|r" end
local function COLOR_NPC(s) return "|caaffaa"..tostring(s).."|r" end
local function COLOR_TIP(s) return "|ceeeecc"..tostring(s).."|r" end
-----------------------------------------
-- GOALHANDLERS
-----------------------------------------
-- Pretty much Goal Types, but not exactly.
GOALTYPES['only'] = { -- |only, |only if
parse = function(self,params,step,data)
local chunkcount = data.chunkcount
local cond = params:match("^if%s+(.*)$")
if cond then
-- condition match and is a |only if
local subject = chunkcount == 1 and step or self -- If this is the first chunk for this line, then it is a |only if for a step.
local fun,err = MakeCondition(cond,true)
if not fun then return err end
subject.condition_visible_raw = cond
subject.condition_visible = fun
return false,"cancel goal"
else
self.requirement = params
end
end,
}
GOALTYPES['complete'] = {
parse = function(self,params)
--local chunkcount = data.chunkcount
local cond = params:match("^if%s+(.*)$")
if cond then
-- condition match and is a |only if
local subject = self -- If this is the first chunk for this line, then it is a |only if for a step.
local fun,err = MakeCondition(cond,true)
if not fun then return err end
subject.condition_complete_raw = cond
subject.condition_complete = fun
else
ZGV:Error("||complete needs 'if'. because.")
self.action = nil -- rip in peace goal. wipe out because this is a command for steps.
end
end,
}
GOALTYPES['next'] = {
parse = function(self,params)
params = params:gsub("^\"(.-)\"$","%1")
if params == "" then
params = "+1"
end
self.next = params
end,
}
GOALTYPES['nextreload'] = {
parse = function(self,params)
params = params:gsub("^\"(.-)\"$","%1")
if params == "" then
params = "+1"
end
self.nextreload = params
end,
}
GOALTYPES['sticky'] = {
parse = function(self)
self.sticky = true
end,
}
GOALTYPES['n'] = {
parse = function(self)
self.force_nocomplete = true
end,
}
GOALTYPES['c'] = {
parse = function(self)
self.force_complete = true
end,
}
GOALTYPES['sub'] = {
parse = function(self)
self.subgoal = true -- obsolete! do not use!
end,
}
GOALTYPES['opt'] = {
parse = function(self)
self.optional = true
end,
}
GOALTYPES['future'] = {
parse = function(self)
self.future = true
end,
}
GOALTYPES['noway'] = {
parse = function(self)
self.force_noway = true
end,
}
GOALTYPES['or'] = {
parse = function(self,params)
self.orlogic = params and tonumber(params) or 1
end,
}
GOALTYPES['override'] = {
parse = function(self)
self.override = true
end,
}
GOALTYPES['tip'] = {
parse = function(self,params)
self.tooltip = params
end,
}
GOALTYPES['q'] = {
parse = function(self,params)
self.quest, self.questcondtxt = ParseQuest(params)
if not self.quest then return "no quest in parameter" end
end,
}
GOALTYPES['_item'] = {
parse = function(self,params)
local count,objinfo,objid
local obj = ""
-- 4 Itemname##id
count,objinfo = params:match("^([0-9]*)%s*(.*)$")
if not count then
objinfo = params
end
local function parse(str)
-- check for plural
local name,plural = str:match("^(.+)(%+)$")
if plural then
str = name
end
local tar, tarid = ParseId(str)
if plural and tar then
tar = GuideViewer("Specials").plural(tar)
end
return tar,tarid
end
self.count = tonumber(count) or 0
local mult = {split(",",objinfo)}
if #mult > 1 then
local targets = {}
self.targets = targets
for i,info in ipairs(mult) do
-- Name##Id are split, parse each individually and then put it in the targets table.
local tar,tarid = parse(info)
tinsert(targets,{tar,tarid})
objid = objid or tarid -- TODO only the first targetid is returned. This isn't an issue 3/1/14 but if we strip out english names then will have to use multiple ids to create the obj.
if tar then
obj = obj .. tar .. (i<#mult and ", " or "") -- Append the target name to the obj, if not the last one then include a ,
end
end
else
obj, objid = parse(objinfo)
end
-- now object##id
self.target,self.targetid = obj,objid
-- something missing?
if not self.targetid and not self.target then
return "no parameter"
end
end
}
GOALTYPES['count'] = {
parse = function(self,params)
self.count = tonumber(params)
end
}
-----------------------------------------
-- GOALTYPES
-----------------------------------------
GOALTYPES['goto'] = {
parse = function(self,params,step,data) -- Called on the game's initial load
local prevmap = data.prevmap
local params2,title = params:match('^(.-)%s*"(.*)"')
if title then
params = params2
end
local map,x,y,dist,err = ParseMapXYDist(params)
if err then
return err
end
self.map = (map or self.map or step.map or prevmap)
if not self.map then
return "'".. (self.action or "?") .."' has no map parameter, neither has one been given before."
end
self.x = x or self.x
self.y = y or self.y
-- Adjusting the speed between zone maps and non-zone maps
self.dist = ZGV.Utils.DistanceOffsetForGoto(dist,self.dist)
self.waytitle = title
end,
iscompletable = function(self) -- Called repeatedly
local step = self.parentStep
local all_gotos = true
for _,goal in ipairs(step.goals) do
if goal.action ~= "goto" -- A non-goto step or a goto step that is force_complete
or goal.force_complete then
all_gotos = false
break
end
end
return (self.force_complete or all_gotos) -- If the goto has a |c then it is completable. Or if there are only gotos present in this step.
end,
iscomplete = function(self) -- Called repeatedly
-- if the player isn't in the zone map then adjust the distance -- GetGameTimeMilliseconds
self.dist = ZGV.Utils.DistanceOffsetForIsComplete()
local dist = ZGV.Pointer:GetDistToCoords(self.map,self.x,self.y)
if self.dist and (
( self.dist > 0 and dist < self.dist ) or
( self.dist < 0 and dist > self.dist )
) then
return true,true
end
return false,true
end,
}
GOALTYPES['buy'] = GOALTYPES['collect']
GOALTYPES['gather'] = GOALTYPES['collect']
GOALTYPES['collect'] = { parse = GOALTYPES['_item'].parse, }
GOALTYPES['kill'] = { parse = GOALTYPES['_item'].parse, }
GOALTYPES['wayshrine'] = { parse = function(self,params,_,_)
local mapid, map = string.match(params,"([^/]+)/([^/]+)")
if tonumber(mapid) then
self.wayshrine_zoneid = tonumber(mapid)
self.wayshrine = map
elseif mapid then -- string
self.wayshrine_zoneid = ZGV.Pointer.Zones[mapid] and ZGV.Pointer.Zones[mapid].id
self.wayshrine = map
else
self.wayshrine = params
end
end,
iscomplete = function(self)
if not self.wayshrine_POIIndex then
local zoneid = self.wayshrine_zoneid
-- screw it, search them all if there's none given.
for zid = (zoneid or 1),(zoneid or 999) do -- oh whatever, less code is good.
for i = 1,GetNumPOIs(zid) do
if IsPOIWayshrine(zid,i) or (zid == 488 and i == 9) then -- West Gash Wayshrine is not a wayshrine. Go figure.
local text,_,_,_ = GetPOIInfo(zid,i)
local fb,fi = zo_plainstrfind(text,self.wayshrine)
if fb and fi == 1 then
self.wayshrine_zoneid = zid
self.wayshrine_POIIndex = i
if ZGV.db.profile.debug_wayshrines then
ZGV:Debug("Found wayshrine '%s' on map id %d. POI %d.", self.wayshrine, self.wayshrine_zoneid, self.wayshrine_POIIndex)
end
break -- out of 2 loops
end
end
end
if self.wayshrine_POIIndex then break end
end
-- Index wasn't found... okay well we don't want to spam looking through POI's because it isn't efficient. Assign the index to 0 which returns an empty POI (no match) then reset it every 10s to try to match again.
if not self.wayshrine_POIIndex then
--ZGV:Debug("Wayshrine '%s' not found, will retry in 10s.", self.wayshrine)
ZGV:Debug("Wayshrine '%s' not found.", self.wayshrine)
self.wayshrine_POIIndex = 0
-- ZGV:ScheduleTimer(function() self.wayshrine_POIIndex = nil end, 10)
end
end
local _,_,typ,_ = GetPOIMapInfo( self.wayshrine_zoneid, self.wayshrine_POIIndex, true ) --true=truthful call! don't be fooled by our own Pointer.lua and its foglight!
if typ == MAP_PIN_TYPE_POI_COMPLETE then
return true,true
else
return false,self.wayshrine_POIIndex > 0
end
end,
}
GOALTYPES['learnskill'] = {
parse = function(self,params)
self.skillname = params
end,
}
GOALTYPES['click'] = {
parse = GOALTYPES['_item'].parse,
}
GOALTYPES['accept'] = {
parse = function(self,params)
if not params then
return "no quest parameter"
end
self.quest = ParseId(params) -- legacy. Get rid of the ID.
if not self.quest then
return "no quest parameter"
end
end,
iscomplete = function(self)
return (ZGV.Quests:HasQuest(self.quest) or ZGV.Quests:IsQuestComplete(self.quest)) , true
end,
}
GOALTYPES['turnin'] = {
parse = GOALTYPES['accept'].parse,
iscomplete = function(self)
local completed = ZGV.Quests:IsQuestComplete(self.quest)
local hasQuest = ZGV.Quests:HasQuest(self.quest)
return completed,hasQuest
end,
}
GOALTYPES['talk'] = {
parse = function(self,params)
self.npc,self.npcid = ParseId(params)
if not self.npc and not self.npcid then
return "no npc"
end
end,
}
GOALTYPES['equip'] = {
parse = GOALTYPES['_item'].parse,
}
GOALTYPES['confirm'] = {
parse = function(self,params)
self.action = self.action or "confirm"
self.always = (params == "always")
end,
iscomplete = function(self)
local complete = not not self.clicked
return complete,true
end,
onclick = function(self)
-- damn, special functionality HERE of all places...
self.clicked = true
if self.nextreload then
ZGV.sv.char.guidename = self.nextreload
ZGV.sv.char.step = 1
_G.ReloadUI()
return true -- oh wait...
end
end,
}
GOALTYPES['lorebook'] = {
parse = function(self,params,_,_)
if not params then
return "no lorebook parameter"
end
local _,cat,col,book = params:match("^(.-)(%d+)/(%d+)/(%d+)$")
self.lorebook_cat = tonumber(cat)
self.lorebook_col = tonumber(col)
self.lorebook_book = tonumber(book)
if not self.lorebook_book then
return "no lorebook cat/col/book parameter"
end
end,
iscomplete = function(self)
local _,_,known = GetLoreBookInfo(self.lorebook_cat,self.lorebook_col,self.lorebook_book)
return known , true
end,
gettext = function(self)
local title = GetLoreBookInfo(self.lorebook_cat,self.lorebook_col,self.lorebook_book)
return L['stepgoal_lorebook']:format(title)
end
}
GOALTYPES['achieve'] = {
parse = function(self,params)
if not params then
return "no achieve parameter"
end
self.achieve_id,self.achieve_crit = params:match("(%d+)/(%d+)$")
if not self.achieve_crit then
self.achieve_id = params:match("(%d+)$")
end
self.achieve_id = tonumber(self.achieve_id)
self.achieve_crit = tonumber(self.achieve_crit)
if not self.achieve_id then
return "no achieve id"
end
end,
iscomplete = function(self,override_achieve_id,override_achieve_crit)
local _,_,_,_,isCompleted,_,_ = GetAchievementInfo(override_achieve_id or self.achieve_id)
if isCompleted or not (override_achieve_crit or self.achieve_crit) then
return isCompleted , true
else
local _,numcom,numreq = GetAchievementCriterion(override_achieve_id or self.achieve_id,override_achieve_crit or self.achieve_crit)
return numcom == numreq, true, (numcom/zo_max(1,(numreq or 1)))
end
end,
gettext = function(self)
local name
if not self.achieve_crit then
name = GetAchievementInfo(self.achieve_id)
else
name = GetAchievementCriterion(self.achieve_id,self.achieve_crit)
end
return L['stepgoal_achieve']:format(name)
end
}
GOALTYPES['ding'] = {
parse = function(self,params)
self.dinglevel = tonumber(params)
end,
iscomplete = function(self)
return ZGV.Utils.GetPlayerPreciseLevel()>=self.dinglevel,true
end,
}
GOALTYPES['text'] = {
parse = function(self,params)
-- highlight _text_
params = params:gsub("_(.-)_","|cffee88%1|r")
self.text = params
end,
}
-----------------------------------------
-- GUIDEPROTO FUNCTIONS
-----------------------------------------
function GoalProto:New()
local goal = {}
setmetatable(goal,GoalProto_mt)
return goal
end
-----------------------------------------
-- GOAL CLASS FUNCTIONS
-----------------------------------------
function Goal:GetQuest()
if not self.quest then return end
return ZGV.Quests:GetQuest(self.quest)
end
function Goal:GetQuestGoalStatus()
if not self.quest then
return false,"no quest"
end
return ZGV.Quests:GetCompletionStatus(self.quest,self.questcondtxt)
end
function Goal:GetQuestGoalCounts()
local _,_,expl,curv,maxv,_ = self:GetQuestGoalStatus()
if expl ~= "cond completion" then return end
if not curv then return end
local goalcountnow,goalcountneeded,remaining
goalcountnow = curv or 0
goalcountneeded = min(self.count or 9999,maxv or 9999) -- If limit is < maxvalue then prefer that to allow guide to dictate only collecting a certain number in a single area.
remaining = goalcountneeded-goalcountnow
if remaining <= 0 then
remaining = goalcountneeded
end
if goalcountneeded == 1 then
remaining = nil
end -- If we only need 1 then don't need to explictly show a number. Nil this out to not show a num
return goalcountnow,goalcountneeded,remaining
end
function Goal:GetText()
local GOALTYPE = GOALTYPES[self.action]
local text = "?"
local progtext
local _done
local complete,ext
local goalcountnow,goalcountneeded,remaining
local base, data
if self.quest then -- Is there a quest to go with this step?
goalcountnow,goalcountneeded,remaining = self:GetQuestGoalCounts()
end
complete,ext = self:IsComplete()
_done = complete and "_done" or ""
if self.text then --TODO expand on this if straight self.text doesn't cut it. And what is it doing?
--~~ This handles {scriptable text entries} in goal texts.
local nsub = 1
-- Generates a parser proc with said behaviour, to evade calling loadstring too much
local function make_parser(parser) -- function to generate code
return function(s)
if not self.textsubs then
self.textsubs = {}
end
local f = self.textsubs[nsub]
if not f then
f = parser(s)
self.textsubs[nsub] = f
end
nsub = nsub + 1
if type(f)=="function" then
ZGV.Parser.ConditionEnv._SetLocal(self.parentStep.parentGuide,self.parentStep,self)
return tostring(f())
else
return tostring(f)
end
end
end
local function parser_simple(s)
local fun,err = zo_loadstring(s:find("return") and s or "return "..s)
if fun then
setfenv(fun,ZGV.Parser.ConditionEnv)
return fun
else
return "("..err..")"
end
end
local function parser_ternary(s)
local condcode,a,b = s:match("(.*)%?%?(.*)::(.*)")
if condcode and a and b then
local condfun,err = zo_loadstring(condcode:find("return") and condcode or "return "..condcode)
if condfun then
local fun = function() -- Generating a real worker function
return condfun() and a or b
end
setfenv(fun,ZGV.Parser.ConditionEnv)
return fun
else
return "("..err..")"
end
else
return "(Wrong conditional syntax)"
end
end
-- TODO support nesting of conditionals
text = self.text
:gsub("{([^}]-%?%?[^}]-::[^}]-)}",make_parser(parser_ternary))
:gsub("{(.-)}",make_parser(parser_simple))
:gsub("#(%d+)#",COLOR_COUNT(remaining))
elseif self.action == "tip" then
text = self.tooltip
elseif self.action == "accept" then
base = L["stepgoal_accept".._done]
data = COLOR_QUEST(L["questtitle"]:format(self.quest or "?quest?"))
elseif self.action == "turnin" then
base = L["stepgoal_turnin".._done]
data = COLOR_QUEST(L["questtitle"]:format(self.quest or "?quest?"))
elseif self.action == "talk" then
base = L["stepgoal_talk".._done]
data = COLOR_NPC(self.npc)
elseif self.action == "kill" then
base = L["stepgoal_kill".._done]
data = COLOR_NPC(self.target)
elseif self.action == "equip" then
base = L["stepgoal_equip".._done]
data = COLOR_NPC(self.target)
elseif self.action == "collect" then
base = L["stepgoal_collect".._done]
data = COLOR_NPC(self.target)
elseif self.action == "buy" then
base = L["stepgoal_buy".._done]
data = COLOR_NPC(self.target)
elseif self.action == "gather" then
base = L["stepgoal_gather".._done]
data = COLOR_NPC(self.target)
elseif self.action == "learnskill" then
base = L["stepgoal_learnskill".._done]
data = COLOR_NPC(self.target)
elseif self.action == "confirm" then
text = L["stepgoal_confirm"]
elseif self.action == "click" then
base = L["stepgoal_click".._done]
data = COLOR_ITEM(self.target)
elseif self.action == "wayshrine" then
base = L["stepgoal_wayshrine".._done]
data = COLOR_NPC(self.wayshrine)
elseif self.action == "goto" then
local curZone = _G.GetMapName()
local mapname = ZGV.Pointer.Zones[self.map] and ZGV.Pointer.Zones[self.map].name or self.map or curZone.."(?)"
if mapname ~= curZone then
if self.x and self.y then -- different map
text = COLOR_LOC(L['map_coords']:format(ZGV.Utils.Delocalize(mapname),self.x * 100,self.y * 100)) -- and coords
else
text = COLOR_LOC(ZGV.Utils.Delocalize(mapname)) -- just the map
end
else
if self.x and self.y then
text = COLOR_LOC(L['coords']:format(self.x*100,self.y * 100)) -- same map
else
text = COLOR_LOC(ZGV.Utils.Delocalize(mapname)) -- just the map
end
end
if self.waytitle then
text = self.waytitle.." ("..text..")"
end
text = ( L["stepgoal_goto"]):format( text )
end
if base and data then
local num = remaining or self.count
if num and num > 1 then
data = num .. " " .. data
end
text = base:format(data)
end
if text == "?" and GOALTYPE.gettext then
text = GOALTYPE.gettext(self)
end
if text == "?" and L["stepgoal_"..self.action] then -- fallback: just plain text in L
text = L["stepgoal_"..self.action]
end
-- Add the indent!
local indent = INDENT:rep(self.indent or 0)
text = text and indent..text
-- apply the (2/4) totals now, or not
if goalcountnow and goalcountneeded and goalcountneeded>0 then
progtext=L["completion_goal"]:format(goalcountnow,goalcountneeded)
end
if progtext then
local col1,col2
if complete then
col1,col2 = "",""
elseif ext then
col1,col2 = " |cffbbbb","|r"
else
col1,col2 = " |caaaaaa","|r"
end
text = text .. col1 .. progtext .. col2
end
return text
end
function Goal:tostring()
return self:GetText()
end
function Goal:GetTipText()
if not self.tooltip then return end
local indent = INDENT:rep(self.indent or 0)
local text = indent..COLOR_TIP(self.tooltip)
return text
end
function Goal:IsVisible()
if self.hidden then return false end
if self.condition_visible then
if self.condition_visible_raw == "default" then
-- oo, special case: show this only if no others are visible!
for _,goal in ipairs(self.parentStep.goals) do
if goal ~= self and goal.condition_visible and goal:IsVisible() then return false end
end
return true
else
ZGV.Parser.ConditionEnv._SetLocal(self.parentStep.parentGuide,self.parentStep,self)
local ok,ret = pcall(self.condition_visible)
if ok then
return ret
else
ZGV:Error("Error in step %s, goal %s, only if %s: %s", self.parentStep.num, self.num, self.condition_visible_raw or "", ret:gsub("\n.*",""))
end
end
end
if self.requirement then
return ZGV.Utils.RaceClassMatch(self.requirement)
end
return true
end
-- returns: true = complete, false = incomplete
-- second return: true = completable, false = incompletable
function Goal:IsComplete()
-- is now a wrapper for sticky reasons.
if self.sticky_complete then
return true,true
end
local iscomplete,ispossible,v1,v2,v3 = self:IsCompleteCheck()
if iscomplete and self.sticky then
self.sticky_complete=true
end
if self:IsCompletable() then
if iscomplete and not self.was_complete then
self:OnComplete()
elseif not iscomplete and self.was_complete then
self:OnDiscomplete()
end
self.was_complete=iscomplete
end
if self.map then
self:CheckVisited()
end -- TODO: this is a bad place to call other checks.
return iscomplete,ispossible,v1,v2,v3
end
function Goal:OnComplete()
if self.parentStep.current_waypoint_goal == self.num then
ZGV.Pointer:CycleWaypoint(1,"no cycle")
end
end
function Goal:OnDiscomplete()
end
function Goal:CheckVisited()
if self.map then
if self.status == "incomplete" then return end
local isvisited = GOALTYPES['goto'].iscomplete(self) -- "test as if it's a goto step"
if isvisited and not self.was_visited then
self:OnVisited()
elseif not isvisited and self.was_visited then
self:OnDevisited()
end
self.was_visited=isvisited
end
end
function Goal:OnVisited()
self.parentStep.current_waypoint_goal = self.num
ZGV.Pointer:CycleWaypoint(1,"no cycle")
end
function Goal:OnDevisited()
end
function Goal:IsCompleteCheck()
-- If the quest is complete then all related goals are complete.
local iscomplete,ispossible,explanation,curv,maxv,debugs
if self.condition_complete then
ZGV.Parser.ConditionEnv._SetLocal(self.parentStep.parentGuide, self.parentStep, self)
local ok,iscomplete = pcall(self.condition_complete)
if ok then
if iscomplete then
return true,true
else
-- fall through!
end
else
ZGV:Error("Error in step %s, goal %s, complete if %s: %s", self.parentStep.num, self.num, self.condition_complete_raw or "", iscomplete:gsub("\n.*",""))
end
end
if self.quest and self.action~="accept" and self.action~="turnin" then -- let accept goals complete on their own
repeat
ZGV:Debug("&goal completing..............")
iscomplete,ispossible,explanation,curv,maxv,debugs = ZGV.Quests:GetCompletionStatus(self.quest, self.questcondtxt)
ZGV:Debug("&goal completion: complete:|cffffff%s|r, possible:|cffffff%s|r, why:|cffffff%s|r ... match: |cffaaee%s|r",tostring(iscomplete),tostring(ispossible),tostring(explanation),tostring(debugs))
local _ = ZGV.Quests:GetQuest(self.quest)
if iscomplete then
-- complete means complete, leave it at that!
if explanation == "quest complete"
or explanation == "quest POI complete"
or explanation == "past stage"
or explanation == "cond recently completed" then
return "past",true
end
return true,true,self.count and 1
elseif false and explanation == "step END" and self.optstep then
return true,true
elseif false and explanation == "future stage" then
if self.future then break end -- fall through
return false,false
elseif explanation == "not in journal" then
if self.future and GOALTYPES[self.action].iscomplete then break end -- fall through if can complete
if self.future and not GOALTYPES[self.action].iscomplete then -- pretend completable. EVIL. SHAME.
return false,true, "future step? pretend completable. shame!"
end
return false,false
elseif false and explanation == "no stagenum" or explanation == "stage completion" then
-- quest in journal, but no stage mentioned, and surely not complete
-- or, stage mentioned, but it's current, so
if self:IsCompletable("by type") then break end -- fall through
return iscomplete,ispossible -- always false,true
elseif explanation == "step completion" or explanation == "step overrides cond" then
if ispossible and self:IsCompletable("by type") then break end -- fall through
return iscomplete,ispossible
elseif explanation == "cond completion" then -- possible, countable
if self.count and self.count > 0 and curv then -- If we only want a specific amount of items at this point then allow them to collect 1/5 and complete this step.
if curv >= self.count then
iscomplete = true
end
end
if iscomplete then
return true, ispossible, (curv and curv / (self.count or maxv or 1))
end
if self:IsCompletable("by type") then break end -- let the goto complete it!
return false, ispossible, (curv and curv / (self.count or maxv or 1))
elseif false and explanation == "current stage unknown" then -- RAISE ALARM?
return false,false
elseif self.future and not GOALTYPES[self.action].iscomplete then -- simulate completability. EVIL.
return false,true, "future step? pretend completable"
elseif self.future then -- how did we end up here?
break
else
return iscomplete,ispossible
end
-- letting possibles through. They'll be either current-stages-only, or incomplete vague objectives.
until false
end
if self.achieve_id then
iscomplete,ispossible = GOALTYPES['achieve'].iscomplete(self)
if iscomplete then
return true,true
end
end
if self.lorebook_book then -- it's lore-based, then?
iscomplete,ispossible = GOALTYPES['lorebook'].iscomplete(self)
if iscomplete then
return true,true
end
end
local giscomplete,gispossible
local GOALTYPE = GOALTYPES[self.action]
if GOALTYPE and GOALTYPE.iscomplete then
giscomplete,gispossible = GOALTYPE.iscomplete(self)
end
return giscomplete or iscomplete,gispossible or ispossible
end
function Goal:IsCompletable(by_type)
local GOALTYPE = GOALTYPES[self.action] -- All goals have goaltypes
if self.force_nocomplete then return false end -- the almighty |n
if self.condition_complete then return true end -- we have a script, so obey
if not by_type and self.action ~= "goto" then
if self.quest or self.lorebook or self.achieve_id then
return true
end -- there is a quest/lore/achieve associated with this goal so can be completed. Unless it's a goto. These are only completed by |c.
end
if GOALTYPE.iscompletable then
return GOALTYPE.iscompletable(self)
end -- This may or maynot be there if it is only sometimes completable.
if GOALTYPE.iscomplete then return true end -- There is a way to complete this goal
-- Nothing above? Okay can't complete.
return false
end