/
playertoplayer.lua
executable file
·2371 lines (2063 loc) · 110 KB
/
playertoplayer.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
local P2P_UNIT_TAG = "reticleoverplayer"
local P2C_UNIT_TAG = "reticleovercompanion"
local INTERACT_TYPE_AGENT_CHAT_REQUEST = 1
local INTERACT_TYPE_RITUAL_OF_MARA = 2
local INTERACT_TYPE_TRADE_INVITE = 3
local INTERACT_TYPE_GROUP_INVITE = 4
local INTERACT_TYPE_QUEST_SHARE = 5
local INTERACT_TYPE_FRIEND_REQUEST = 6
local INTERACT_TYPE_GUILD_INVITE = 7
local INTERACT_TYPE_CAMPAIGN_QUEUE = 8
local INTERACT_TYPE_WORLD_EVENT_INVITE = 9
local INTERACT_TYPE_LFG_FIND_REPLACEMENT = 10
local INTERACT_TYPE_GROUP_ELECTION = 11
local INTERACT_TYPE_DUEL_INVITE = 12
local INTERACT_TYPE_LFG_READY_CHECK = 13
local INTERACT_TYPE_CLAIM_LEVEL_UP_REWARDS = 14
local INTERACT_TYPE_GIFT_RECEIVED = 15
local INTERACT_TYPE_TRACK_ZONE_STORY = 16
local INTERACT_TYPE_CAMPAIGN_QUEUE_JOINED = 17
local INTERACT_TYPE_CAMPAIGN_LOCK_PENDING = 18
local INTERACT_TYPE_TRAVEL_TO_LEADER = 19
local INTERACT_TYPE_TRIBUTE_INVITE = 20
local INTERACT_TYPE_GROUP_FINDER_APPLICATION = 21
local TIMED_PROMPTS =
{
[INTERACT_TYPE_LFG_READY_CHECK] = true,
[INTERACT_TYPE_CAMPAIGN_QUEUE] = true,
[INTERACT_TYPE_WORLD_EVENT_INVITE] = true,
[INTERACT_TYPE_GROUP_ELECTION] = true,
[INTERACT_TYPE_CAMPAIGN_LOCK_PENDING] = true,
[INTERACT_TYPE_DUEL_INVITE] = true,
[INTERACT_TYPE_TRIBUTE_INVITE] = true,
-- Campaign Queue is the only timed prompt without a fixed expiration time; instead it's manually removed when the queue it's a part of pops.
-- This means it does not define expiresAtS or expirationCallback, and it refreshes every second without necessarily needing to; it doesn't show a timer.
[INTERACT_TYPE_CAMPAIGN_QUEUE_JOINED] = true,
}
-- Prompts that are NOT in the TIMED_PROMPTS table but we still want to have flashing behavior on the task bar.
local FLASHING_PROMPTS =
{
[INTERACT_TYPE_GROUP_FINDER_APPLICATION] = true,
}
ZO_PlayerToPlayer = ZO_Object:Subclass()
local function ShouldUseGamepadResponseMenu(data)
return IsInGamepadPreferredMode() and data.pendingResponse
end
function ZO_PlayerToPlayer:New(...)
local playerToPlayer = ZO_Object.New(self)
playerToPlayer:Initialize(...)
return playerToPlayer
end
do
local KEYBOARD_STYLE =
{
targetFont = "ZoInteractionPrompt",
additionalInfoFont = "ZoInteractionPrompt",
pendingResurrectInfoFont = "ZoFontKeybindStripDescription",
keybindButtonStyle = KEYBIND_STRIP_STANDARD_STYLE,
}
local GAMEPAD_STYLE =
{
targetFont = "ZoFontGamepad36",
additionalInfoFont = "ZoFontGamepad36",
pendingResurrectInfoFont = "ZoFontGamepad27",
keybindButtonStyle = KEYBIND_STRIP_GAMEPAD_STYLE,
}
function ZO_PlayerToPlayer:ApplyPlatformStyle(style)
self.targetLabel:SetFont(style.targetFont)
self.additionalInfo:SetFont(style.additionalInfoFont)
self.pendingResurrectInfo:SetFont(style.pendingResurrectInfoFont)
self.actionKeybindButton:SetupStyle(style.keybindButtonStyle)
self.promptKeybindButton1:SetupStyle(style.keybindButtonStyle)
self.promptKeybindButton2:SetupStyle(style.keybindButtonStyle)
end
function ZO_PlayerToPlayer:Initialize(control)
self.control = control
control.owner = self
self.container = control:GetNamedChild("PromptContainer")
self.targetLabel = self.container:GetNamedChild("Target")
self.actionArea = self.container:GetNamedChild("ActionArea")
self.actionKeybindButton = self.actionArea:GetNamedChild("ActionKeybindButton")
self.additionalInfo = self.actionArea:GetNamedChild("AdditionalInfo")
self.pendingResurrectInfo = self.actionArea:GetNamedChild("PendingResurrectInfo")
self.gamerID = self.actionArea:GetNamedChild("GamerID")
self.promptKeybindButton1 = self.actionArea:GetNamedChild("PromptKeybindButton1")
self.promptKeybindButton1:SetKeybind("PLAYER_TO_PLAYER_INTERACT_ACCEPT")
self.promptKeybindButton1:SetCallback(function() self:OnPromptAccepted() end)
self.promptKeybindButton2 = self.actionArea:GetNamedChild("PromptKeybindButton2")
self.promptKeybindButton2:SetKeybind("PLAYER_TO_PLAYER_INTERACT_DECLINE")
self.promptKeybindButton2:SetCallback(function() self:OnPromptDeclined() end)
self.resurrectProgress = ZO_PlayerToPlayerResurrectProgress
self.resurrectProgressAnimation = ANIMATION_MANAGER:CreateTimelineFromVirtual("PlayerToPlayerResurrectAnimation", self.resurrectProgress)
self:InitializeKeybinds()
self:InitializeSoulGemResurrectionEvents()
self:InitializeIncomingEvents()
self.msToDelayToShowPrompt = 500
self.lastFailedPromptTime = GetFrameTimeMilliseconds()
self.hotkeyBeginHolds = {}
EVENT_MANAGER:RegisterForUpdate(control:GetName() .. "OnUpdate", 0, function() self:OnUpdate() end)
control:RegisterForEvent(EVENT_DUEL_STARTED, function() self:OnDuelStarted() end)
SHARED_INFORMATION_AREA:AddPlayerToPlayer(self.container)
ZO_PlatformStyle:New(function(style) self:ApplyPlatformStyle(style) end, KEYBOARD_STYLE, GAMEPAD_STYLE)
--Set up narration for the player to player prompts
local narrationInfo =
{
canNarrate = function()
return not self:IsHidden() and not SHARED_INFORMATION_AREA:IsSuppressed()
end,
selectedNarrationFunction = function()
local narrations = {}
if not self.targetLabel:IsHidden() then
ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(self.targetTextNarration))
end
if not self.pendingResurrectInfo:IsHidden() then
ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(self.pendingResurrectText))
end
return narrations
end,
additionalInputNarrationFunction = function()
local narrationData = {}
if not self.actionKeybindButton:IsHidden() then
table.insert(narrationData, self.actionKeybindButton:GetKeybindButtonNarrationData())
end
return narrationData
end,
narrationType = NARRATION_TYPE_HUD,
}
SCREEN_NARRATION_MANAGER:RegisterCustomObject("PlayerToPlayerPrompt", narrationInfo)
end
end
function ZO_PlayerToPlayer:OnDuelStarted()
self:StopInteraction()
end
function ZO_PlayerToPlayer:OnTributeStarted()
self:StopInteraction()
end
function ZO_PlayerToPlayer:CreateGamepadRadialMenu()
local USE_DEFAULT_DIRECTIONAL_INPUTS = nil
local DEFAULT_ENABLE_MOUSE = nil
local DEFAULT_SELECT_IF_CENTERED = nil
self.gamepadMenu = ZO_RadialMenu:New(ZO_PlayerToPlayerMenu_Gamepad, "ZO_RadialMenuHUDEntryTemplate_Gamepad", "DefaultRadialMenuAnimation", "DefaultRadialMenuEntryAnimation", "RadialMenu", USE_DEFAULT_DIRECTIONAL_INPUTS, DEFAULT_ENABLE_MOUSE, DEFAULT_SELECT_IF_CENTERED, ZO_AreTogglableWheelsEnabled)
self.gamepadMenu:SetOnClearCallback(function()
self:StopInteraction()
end)
self.gamepadMenu:SetOnSelectionChangedCallback(function(selectedEntry)
--Re-narrate when the selection changes
if selectedEntry then
SCREEN_NARRATION_MANAGER:QueueCustomEntry("PlayerToPlayerWheel")
end
end)
self.gamepadMenu:SetKeybindActionLayer("PlayerToPlayerAccessibleLayer")
--Set up narration for the player interact wheels
local narrationInfo =
{
canNarrate = function()
return self.showingPlayerInteractMenu or self.showingGamepadResponseMenu
end,
selectedNarrationFunction = function()
local narrations = {}
local selectedEntry = self.gamepadMenu.selectedEntry
if selectedEntry then
ZO_AppendNarration(narrations, SCREEN_NARRATION_MANAGER:CreateNarratableObject(selectedEntry.name))
end
return narrations
end,
headerNarrationFunction = function()
if self.showingPlayerInteractMenu then
return SCREEN_NARRATION_MANAGER:CreateNarratableObject(GetString(SI_PLAYER_TO_PLAYER_INTERACT_WHEEL_NARRATION))
elseif self.showingGamepadResponseMenu then
return SCREEN_NARRATION_MANAGER:CreateNarratableObject(GetString(SI_PLAYER_TO_PLAYER_RESPONSE_WHEEL_NARRATION))
end
end,
additionalInputNarrationFunction = function()
local narrationData = {}
if self.gamepadMenu:ShouldShowKeybinds() then
self.gamepadMenu:ForEachOrdinalEntry(function(ordinalIndex, entry)
local actionName = ZO_GetRadialMenuActionNameForOrdinalIndex(ordinalIndex)
local entryNarrationData =
{
name = entry.name,
keybindName = ZO_Keybindings_GetHighestPriorityNarrationStringFromAction(actionName) or GetString(SI_ACTION_IS_NOT_BOUND),
enabled = true,
}
table.insert(narrationData, entryNarrationData)
end)
end
return narrationData
end,
narrationType = NARRATION_TYPE_HUD,
}
SCREEN_NARRATION_MANAGER:RegisterCustomObject("PlayerToPlayerWheel", narrationInfo)
end
function ZO_PlayerToPlayer:CreateKeyboardRadialMenu()
local USE_DEFAULT_DIRECTIONAL_INPUTS = nil
local DEFAULT_ENABLE_MOUSE = nil
local DEFAULT_SELECT_IF_CENTERED = nil
self.keyboardMenu = ZO_RadialMenu:New(ZO_PlayerToPlayerMenu_Keyboard, "ZO_PlayerToPlayerMenuEntryTemplate_Keyboard", "DefaultRadialMenuAnimation", "DefaultRadialMenuEntryAnimation", "RadialMenu", USE_DEFAULT_DIRECTIONAL_INPUTS, DEFAULT_ENABLE_MOUSE, DEFAULT_SELECT_IF_CENTERED, ZO_AreTogglableWheelsEnabled)
self.keyboardMenu:SetOnClearCallback(function()
self:StopInteraction()
end)
self.keyboardMenu:SetKeybindActionLayer("PlayerToPlayerAccessibleLayer")
end
--Gets or creates the radial menu for the current keyboard/gamepad mode
function ZO_PlayerToPlayer:GetRadialMenu()
if IsInGamepadPreferredMode() then
if not self.gamepadMenu then
self:CreateGamepadRadialMenu()
end
return self.gamepadMenu
else
if not self.keyboardMenu then
self:CreateKeyboardRadialMenu()
end
return self.keyboardMenu
end
end
--Gets the radial menu that was most recently interacted with. This resolves issues where switching platform type during an interaction sent the close events to the wrong menu
function ZO_PlayerToPlayer:GetLastActiveRadialMenu()
internalassert(self.isLastRadialMenuGamepad ~= nil, "GetLastActiveRadialMenu() called without a previous active menu")
if self.isLastRadialMenuGamepad then
return internalassert(self.gamepadMenu)
else
return internalassert(self.keyboardMenu)
end
end
function ZO_PlayerToPlayer:InitializeKeybinds()
self.actionKeybindButton:SetKeybind("PLAYER_TO_PLAYER_INTERACT")
end
function ZO_PlayerToPlayer:InitializeSoulGemResurrectionEvents()
self.control:RegisterForEvent(EVENT_START_SOUL_GEM_RESURRECTION, function(eventCode, ...) self:OnStartSoulGemResurrection(...) end)
self.control:RegisterForEvent(EVENT_END_SOUL_GEM_RESURRECTION, function(eventCode, ...) self:OnEndSoulGemResurrection(...) end)
end
local function GetCampaignConfirmQueueData(campaignId, isGroup)
local campaignRulesetTypeString = GetString("SI_CAMPAIGNRULESETTYPE", GetCampaignRulesetType(GetCampaignRulesetId(campaignId)))
local campaignName = GetCampaignName(campaignId)
local remainingSeconds = GetCampaignQueueRemainingConfirmationSeconds(campaignId, isGroup)
local campaignData =
{
campaignId = campaignId,
isGroup = isGroup,
campaignName = campaignName,
messageFormat = SI_CAMPAIGN_QUEUE_MESSAGE,
messageParams = { campaignRulesetTypeString, campaignName },
expiresAtS = GetFrameTimeSeconds() + remainingSeconds,
dialogTitle = GetString("SI_NOTIFICATIONTYPE", NOTIFICATION_TYPE_CAMPAIGN_QUEUE),
}
return campaignData
end
local function GetCampaignQueueJoinedData(campaignId, isAboutToAllianceLock)
local campaignRulesetTypeString = GetString("SI_CAMPAIGNRULESETTYPE", GetCampaignRulesetType(GetCampaignRulesetId(campaignId)))
local campaignName = GetCampaignName(campaignId)
local campaignData =
{
campaignId = campaignId,
campaignName = campaignName,
messageFormat = isAboutToAllianceLock and SI_CAMPAIGN_QUEUE_JOINED_AS_GROUP_WITH_ALLIANCE_LOCK_MESSAGE or SI_CAMPAIGN_QUEUE_JOINED_AS_GROUP_MESSAGE,
messageParams = { campaignRulesetTypeString, ZO_SELECTED_TEXT:Colorize(campaignName) },
dialogTitle = SI_CAMPAIGN_QUEUE_JOINED_AS_GROUP_TITLE,
}
return campaignData
end
function ZO_PlayerToPlayer:InitializeIncomingEvents()
self.incomingQueue = {}
local function OnDuelInviteReceived(eventCode, inviterCharacterName, inviterDisplayName, timeRemainingMS)
PlaySound(SOUNDS.DUEL_INVITE_RECEIVED)
local function DeferDecisionCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_DUEL_INVITE)
end
local NO_TARGET_LABEL = nil
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_DUEL_INVITE, inviterCharacterName, inviterDisplayName, NO_TARGET_LABEL, AcceptDuel, DeclineDuel, DeferDecisionCallback)
data.messageFormat = GetString(SI_PLAYER_TO_PLAYER_INCOMING_DUEL)
-- the time left is added automatically to messageParams in position <<2>>
data.messageParams = { ZO_SELECTED_TEXT:Colorize(data.inviterName) }
data.dialogTitle = GetString("SI_NOTIFICATIONTYPE", NOTIFICATION_TYPE_DUEL)
data.expiresAtS = GetFrameTimeSeconds() + (timeRemainingMS / ZO_ONE_SECOND_IN_MILLISECONDS)
data.expirationCallback = DeferDecisionCallback
data.noDeclineConfirmation = true
end
local function OnDuelInviteRemoved()
self:RemoveFromIncomingQueue(INTERACT_TYPE_DUEL_INVITE)
end
local function OnTributeInviteReceived(eventCode, inviterCharacterName, inviterDisplayName, timeRemainingMS)
PlaySound(SOUNDS.TRIBUTE_INVITE_RECEIVED)
local function DeferDecisionCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRIBUTE_INVITE)
end
local NO_TARGET_LABEL = nil
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_TRIBUTE_INVITE, inviterCharacterName, inviterDisplayName, NO_TARGET_LABEL, AcceptTribute, DeclineTribute, DeferDecisionCallback)
data.messageFormat = GetString(SI_PLAYER_TO_PLAYER_INCOMING_TRIBUTE)
-- the time left is added automatically to messageParams in position <<2>>
data.messageParams = { ZO_SELECTED_TEXT:Colorize(data.inviterName) }
data.dialogTitle = GetString("SI_NOTIFICATIONTYPE", NOTIFICATION_TYPE_TRIBUTE_INVITE)
data.expiresAtS = GetFrameTimeSeconds() + (timeRemainingMS / ZO_ONE_SECOND_IN_MILLISECONDS)
data.expirationCallback = DeferDecisionCallback
data.noDeclineConfirmation = true
end
local function OnTributeInviteRemoved()
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRIBUTE_INVITE)
end
local function OnGroupInviteReceived(eventCode, inviterCharacterName, inviterDisplayName)
if not self:ExistsInQueue(INTERACT_TYPE_GROUP_INVITE, inviterCharacterName, inviterDisplayName) then
local userFacingName = ZO_GetPrimaryPlayerNameWithSecondary(inviterDisplayName, inviterCharacterName)
PlaySound(SOUNDS.GROUP_INVITE)
self:RemoveFromIncomingQueue(INTERACT_TYPE_GROUP_INVITE)
self:AddPromptToIncomingQueue(INTERACT_TYPE_GROUP_INVITE, inviterCharacterName, inviterDisplayName, zo_strformat(SI_PLAYER_TO_PLAYER_INCOMING_GROUP, ZO_SELECTED_TEXT:Colorize(userFacingName)),
function()
AcceptGroupInvite()
end,
function()
DeclineGroupInvite()
end,
function()
self:RemoveFromIncomingQueue(INTERACT_TYPE_GROUP_INVITE, inviterCharacterName, inviterDisplayName)
end)
end
end
local function OnGroupInviteRemoved()
self:RemoveFromIncomingQueue(INTERACT_TYPE_GROUP_INVITE)
end
local function OnTradeWindowInviteConsidering(eventCode, inviterCharacterName, inviterDisplayName)
PlaySound(SOUNDS.TRADE_INVITE_RECEIVED)
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRADE_INVITE)
local userFacingName = ZO_GetPrimaryPlayerNameWithSecondary(inviterDisplayName, inviterCharacterName)
-- There is server message received when trade is cancelled/accepted/declined, which sends a Lua event which will play a sound in AlertHandlers.lua
self:AddPromptToIncomingQueue(INTERACT_TYPE_TRADE_INVITE, inviterCharacterName, inviterDisplayName, zo_strformat(SI_PLAYER_TO_PLAYER_INCOMING_TRADE, ZO_SELECTED_TEXT:Colorize(userFacingName)),
function()
TradeInviteAccept()
end,
function()
TradeInviteDecline()
end,
function()
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRADE_INVITE, inviterCharacterName, inviterDisplayName)
end)
end
local function OnTradeWindowInviteRemoved()
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRADE_INVITE)
end
local function OnQuestShared(eventCode, questId)
PlaySound(SOUNDS.QUEST_SHARED)
local questName, characterName, _, displayName = GetOfferedQuestShareInfo(questId)
local name = ZO_GetPrimaryPlayerNameWithSecondary(displayName, characterName)
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_QUEST_SHARE, characterName, displayName, zo_strformat(SI_PLAYER_TO_PLAYER_INCOMING_QUEST_SHARE, ZO_SELECTED_TEXT:Colorize(name), ZO_SELECTED_TEXT:Colorize(questName)),
function()
AcceptSharedQuest(questId)
end,
function()
DeclineSharedQuest(questId)
end,
function()
self:RemoveFromIncomingQueue(INTERACT_TYPE_QUEST_SHARE, characterName, displayName)
end)
data.questId = questId
data.uniqueSounds = {
accept = SOUNDS.QUEST_SHARE_ACCEPTED,
decline = SOUNDS.QUEST_SHARE_DECLINED,
}
end
local function OnQuestShareRemoved(eventCode, questId)
-- No need to play a sound for this now, the client might automatically remove stale quest shares.
self:RemoveQuestShareFromIncomingQueue(questId)
end
local function OnPledgeOfMaraOffer(eventCode, targetCharacterName, isSender, targetDisplayName)
PlaySound(SOUNDS.MARA_INVITE_RECEIVED)
self:RemoveFromIncomingQueue(INTERACT_TYPE_RITUAL_OF_MARA)
local userFacingTargetName = ZO_GetPrimaryPlayerNameWithSecondary(targetDisplayName, targetCharacterName)
local ritualPromptStringId = isSender and SI_PLAYER_TO_PLAYER_OUTGOING_RITUAL_OF_MARA or SI_PLAYER_TO_PLAYER_INCOMING_RITUAL_OF_MARA
local ritualPromptText = zo_strformat(ritualPromptStringId, ZO_SELECTED_TEXT:Colorize(userFacingTargetName))
local function AcceptRitualOfMara()
ZO_Dialogs_ShowPlatformDialog("RITUAL_OF_MARA_PROMPT", nil,
{
mainTextParams =
{
ZO_SELECTED_TEXT:Colorize(ZO_FormatUserFacingDisplayName(targetDisplayName)),
zo_floor(GetRingOfMaraExperienceBonus() * 100),
ZO_SELECTED_TEXT:Colorize(targetCharacterName),
}
})
end
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_RITUAL_OF_MARA, targetCharacterName, targetDisplayName, ritualPromptText, AcceptRitualOfMara)
data.acceptText = GetString(SI_PLEDGE_OF_MARA_BEGIN_RITUAL_PROMPT)
end
local function OnPledgeOfMaraOfferRemoved()
self:RemoveFromIncomingQueue(INTERACT_TYPE_RITUAL_OF_MARA)
end
local function OnIncomingFriendInviteAdded(eventCode, inviterName)
PlaySound(SOUNDS.FRIEND_INVITE_RECEIVED)
local displayName = ZO_FormatUserFacingDisplayName(inviterName)
self:AddPromptToIncomingQueue(INTERACT_TYPE_FRIEND_REQUEST, inviterName, nil, zo_strformat(SI_PLAYER_TO_PLAYER_INCOMING_FRIEND_REQUEST, ZO_SELECTED_TEXT:Colorize(displayName)),
function()
AcceptFriendRequest(inviterName)
end,
function()
RejectFriendRequest(inviterName)
end,
function()
self:RemoveFromIncomingQueue(INTERACT_TYPE_FRIEND_REQUEST, inviterName)
end)
end
local function OnIncomingFriendRequestRemoved(eventCode, inviterName)
self:RemoveFromIncomingQueue(INTERACT_TYPE_FRIEND_REQUEST, inviterName)
end
local function OnGuildInviteAdded(eventCode, guildId, guildName, guildAlliance, inviterName)
local allianceIconSize = 24
if IsInGamepadPreferredMode() then
allianceIconSize = 36
end
local formattedInviterName = ZO_FormatUserFacingDisplayName(inviterName)
local guildNameAlliance = zo_iconTextFormat(ZO_GetPlatformAllianceSymbolIcon(guildAlliance), allianceIconSize, allianceIconSize, ZO_SELECTED_TEXT:Colorize(guildName))
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_GUILD_INVITE, nil, formattedInviterName, zo_strformat(SI_PLAYER_TO_PLAYER_INCOMING_GUILD_REQUEST, ZO_SELECTED_TEXT:Colorize(formattedInviterName), guildNameAlliance),
function()
AcceptGuildInvite(guildId)
end,
function()
RejectGuildInvite(guildId)
end,
function()
self:RemoveFromIncomingQueue(INTERACT_TYPE_GUILD_INVITE, formattedInviterName)
end)
data.guildId = guildId
end
local function OnGuildInviteRemoved(eventCode, guildId)
self:RemoveGuildInviteFromIncomingQueue(guildId)
end
local function OnAgentChatRequested()
self:AddPromptToIncomingQueue(INTERACT_TYPE_AGENT_CHAT_REQUEST, nil, nil, GetString(SI_PLAYER_TO_PLAYER_INCOMING_AGENT_CHAT_REQUEST),
function()
AcceptAgentChat()
end,
function()
DeclineAgentChat()
end,
function()
self:RemoveFromIncomingQueue(INTERACT_TYPE_AGENT_CHAT_REQUEST)
end)
end
local function OnAgentChatAccepted()
self:RemoveFromIncomingQueue(INTERACT_TYPE_AGENT_CHAT_REQUEST)
end
local function OnAgentChatDeclined()
self:RemoveFromIncomingQueue(INTERACT_TYPE_AGENT_CHAT_REQUEST)
end
local function OnCampaignQueueStateChanged(_, campaignId, isGroup, state)
if state == CAMPAIGN_QUEUE_REQUEST_STATE_CONFIRMING then
local campaignQueueData = GetCampaignConfirmQueueData(campaignId, isGroup)
local function AcceptCampaignEntry()
ConfirmCampaignEntry(campaignId, isGroup, true)
end
local function DeclineCampaignEntry()
ConfirmCampaignEntry(campaignId, isGroup, false)
end
local function DeferDecisionCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_CAMPAIGN_QUEUE, campaignId)
end
--Campaign is super hacky and uses the campaignId in the name field. It works because it only uses that field to do comparisons for removing the entry.
local promptData = self:AddPromptToIncomingQueue(INTERACT_TYPE_CAMPAIGN_QUEUE, campaignId, campaignId, nil, AcceptCampaignEntry, DeclineCampaignEntry, DeferDecisionCallback)
promptData.messageFormat = campaignQueueData.messageFormat
promptData.messageParams = campaignQueueData.messageParams
promptData.expiresAtS = campaignQueueData.expiresAtS
promptData.dialogTitle = campaignQueueData.dialogTitle
promptData.expirationCallback = DeferDecisionCallback
PlaySound(SOUNDS.CAMPAIGN_READY_CHECK)
else
--Campaign is super hacky and uses the campaignId in the name field. It works because it only uses that field to do comparisons for removing the entry.
self:RemoveFromIncomingQueue(INTERACT_TYPE_CAMPAIGN_QUEUE, campaignId, campaignId)
end
end
local function OnCampaignQueueJoined(_, campaignId, isMemberOfGroup, willBeLockedToAlliance)
if not isMemberOfGroup then
return
end
local showAllianceLockWarning = willBeLockedToAlliance ~= ALLIANCE_NONE
local campaignQueueData = GetCampaignQueueJoinedData(campaignId, showAllianceLockWarning)
local function AcceptCampaignEntry()
if IsInGamepadPreferredMode() then
SCENE_MANAGER:Show(GAMEPAD_AVA_ROOT_SCENE:GetName())
else
SCENE_MANAGER:Show(CAMPAIGN_BROWSER_SCENE:GetName())
end
end
local function DeclineCampaignEntry()
-- Dismiss prompt automatically
end
--Campaign is super hacky and uses the campaignId in the name field. It works because it only uses that field to do comparisons for removing the entry.
local NO_TARGET_LABEL = nil
local promptData = self:AddPromptToIncomingQueue(INTERACT_TYPE_CAMPAIGN_QUEUE_JOINED, campaignId, campaignId, NO_TARGET_LABEL, AcceptCampaignEntry, DeclineCampaignEntry)
promptData.messageFormat = campaignQueueData.messageFormat
promptData.messageParams = campaignQueueData.messageParams
promptData.dialogTitle = campaignQueueData.dialogTitle
promptData.acceptText = GetString(SI_CAMPAIGN_QUEUE_JOINED_AS_GROUP_OPEN_CAMPAIGNS_BUTTON)
promptData.declineText = GetString(SI_CAMPAIGN_QUEUE_JOINED_AS_GROUP_DISMISS_BUTTON)
promptData.noDeclineConfirmation = true
end
local function OnCampaignQueueLeft(_, campaignId, group)
--Campaign is super hacky and uses the campaignId in the name field. It works because it only uses that field to do comparisons for removing the entry.
self:RemoveFromIncomingQueue(INTERACT_TYPE_CAMPAIGN_QUEUE, campaignId, campaignId)
self:RemoveFromIncomingQueue(INTERACT_TYPE_CAMPAIGN_QUEUE_JOINED, campaignId, campaignId)
end
local function OnCampaignLockPending(_, campaignId, alliance, timeLeftS)
local colorizedCampaignName = ZO_SELECTED_TEXT:Colorize(GetCampaignName(campaignId))
local allianceString = ZO_SELECTED_TEXT:Colorize(ZO_CampaignBrowser_FormatPlatformAllianceIconAndName(alliance))
local function AcceptCallback()
MarkAllianceLockPendingNotificationSeen()
end
local function NotificationExpiredCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_CAMPAIGN_LOCK_PENDING)
MarkAllianceLockPendingNotificationSeen()
end
--Campaign is super hacky and uses the campaignId in the name field. It works because it only uses that field to do comparisons for removing the entry.
local NO_TARGET_LABEL = nil
local NO_DECLINE_CALLBACK = nil
self:RemoveFromIncomingQueue(INTERACT_TYPE_CAMPAIGN_LOCK_PENDING)
local promptData = self:AddPromptToIncomingQueue(INTERACT_TYPE_CAMPAIGN_LOCK_PENDING, campaignId, campaignId, NO_TARGET_LABEL, AcceptCallback, NO_DECLINE_CALLBACK)
promptData.messageFormat = GetString(SI_CAMPAIGN_ALLIANCE_LOCK_PENDING_MESSAGE)
-- the time left is added automatically to messageParams in position <<3>>
promptData.messageParams = {colorizedCampaignName, allianceString}
promptData.dialogTitle = GetString(SI_CAMPAIGN_ALLIANCE_LOCK_PENDING_TITLE)
promptData.expiresAtS = GetFrameTimeSeconds() + timeLeftS
promptData.expirationCallback = NotificationExpiredCallback
promptData.acceptText = GetString(SI_CAMPAIGN_ALLIANCE_LOCK_PENDING_DISMISS_BUTTON)
end
local function OnCampaignLockActivated(_, campaignId)
self:RemoveFromIncomingQueue(INTERACT_TYPE_CAMPAIGN_LOCK_PENDING)
MarkAllianceLockPendingNotificationSeen()
end
local function OnCurrentCampaignChanged(_)
self:RemoveFromIncomingQueue(INTERACT_TYPE_CAMPAIGN_LOCK_PENDING)
MarkAllianceLockPendingNotificationSeen()
end
local function OnScriptedWorldEventInvite(eventCode, eventId, eventName, inviterName, questName)
PlaySound(SOUNDS.SCRIPTED_WORLD_EVENT_INVITED)
self:RemoveScriptedWorldEventFromIncomingQueue(eventId)
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_WORLD_EVENT_INVITE, eventId, eventId, nil,
function()
AcceptWorldEventInvite(eventId)
end,
function()
DeclineWorldEventInvite(eventId)
end)
local function DeferDecisionCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_WORLD_EVENT_INVITE)
end
local timeRemainingMS = GetScriptedEventInviteTimeRemainingMS(eventId)
data.expiresAtS = GetFrameTimeSeconds() + (timeRemainingMS / ZO_ONE_SECOND_IN_MILLISECONDS)
data.expirationCallback = DeferDecisionCallback
data.dialogTitle = GetString("SI_NOTIFICATIONTYPE", NOTIFICATION_TYPE_SCRIPTED_WORLD_EVENT)
if inviterName == "" then
if questName == "" then
data.messageFormat = SI_EVENT_INVITE
data.messageParams = { eventName }
else
data.messageFormat = SI_EVENT_INVITE_QUEST
data.messageParams = { eventName, questName }
end
else
if questName == "" then
data.messageFormat = SI_EVENT_INVITE_NAMED
data.messageParams = { inviterName, eventName}
else
data.messageFormat = SI_EVENT_INVITE_NAMED_QUEST
data.messageParams = { inviterName, eventName, questName }
end
end
data.eventId = eventId
data.questName = questName
data.uniqueSounds = {
accept = SOUNDS.SCRIPTED_WORLD_EVENT_ACCEPTED,
decline = SOUNDS.SCRIPTED_WORLD_EVENT_DECLINED,
}
end
local function OnScriptedWorldEventInviteRemoved(eventCode, eventId)
self:RemoveScriptedWorldEventFromIncomingQueue(eventId)
end
local function OnLevelUpRewardUpdated()
self:RemoveFromIncomingQueue(INTERACT_TYPE_CLAIM_LEVEL_UP_REWARDS)
local pendingRewardLevel = GetPendingLevelUpRewardLevel()
if pendingRewardLevel then
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_CLAIM_LEVEL_UP_REWARDS, nil, nil, zo_strformat(SI_LEVEL_UP_REWARDS_AVAILABLE_NOTIFICATION, pendingRewardLevel),
function()
if IsInGamepadPreferredMode() then
SCENE_MANAGER:Show("LevelUpRewardsClaimGamepad")
else
SYSTEMS:GetObject("mainMenu"):ToggleCategory(MENU_CATEGORY_CHARACTER)
end
end)
data.dontRemoveOnAccept = true
data.acceptText = GetString(SI_LEVEL_UP_REWARDS_OPEN_CLAIM_SCREEN_TEXT)
data.declineText = GetString(SI_LEVEL_UP_REWARDS_DISMISS_NOTIFICATION)
end
end
local function OnGiftsUpdated()
self:RemoveAllFromIncomingQueue(INTERACT_TYPE_GIFT_RECEIVED)
local giftList = GIFT_INVENTORY_MANAGER:GetGiftList(GIFT_STATE_RECEIVED)
for _, gift in ipairs(giftList) do
if not gift:HasBeenSeen() then
local NO_CHARACTER_NAME = nil
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_GIFT_RECEIVED, NO_CHARACTER_NAME, gift:GetPlayerName(), zo_strformat(SI_PLAYER_TO_PLAYER_GIFT_RECEIVED, ZO_SELECTED_TEXT:Colorize(gift:GetUserFacingPlayerName())),
function()
local giftInventoryView = SYSTEMS:GetObject("giftInventoryView")
giftInventoryView:SetupAndShowGift(gift)
end,
function()
gift:View()
end)
data.acceptText = GetString(SI_GIFT_INVENTORY_OPEN_CLAIM_SCREEN_TEXT)
data.declineText = GetString(SI_GIFT_INVENTORY_DISMISS_NOTIFICATION)
TriggerTutorial(TUTORIAL_TRIGGER_GIFT_RECEIVED)
end
end
end
local function CompareGroupFinderApplications(entry1, entry2)
--Sort by oldest to newest
return ZO_TableOrderingFunction(entry1, entry2, "GetEndTimeSeconds", GROUP_FINDER_APPLICATIONS_LIST_ENTRY_SORT_KEYS, ZO_SORT_ORDER_UP)
end
local function OnGroupFinderApplicationsUpdated()
self:RemoveAllFromIncomingQueue(INTERACT_TYPE_GROUP_FINDER_APPLICATION)
local applicationsData = GROUP_FINDER_APPLICATIONS_LIST_MANAGER:GetApplicationsData(CompareGroupFinderApplications)
if #applicationsData > 0 then
--Find the current oldest application and add that one to the queue
local oldestApplication = applicationsData[1]
local function AcceptCallback()
RequestResolveGroupListingApplication(RESOLVE_GROUP_LISTING_APPLICATION_REQUEST_APPROVE, oldestApplication:GetCharacterId())
end
local function DeclineCallback()
RequestResolveGroupListingApplication(RESOLVE_GROUP_LISTING_APPLICATION_REQUEST_REJECT, oldestApplication:GetCharacterId())
end
PlaySound(SOUNDS.GROUP_FINDER_APPLICATION_NOTIFICATION)
local characterName = oldestApplication:GetCharacterName()
local displayName = oldestApplication:GetDisplayName()
local NO_TARGET_LABEL = nil
local data = self:AddPromptToIncomingQueue(INTERACT_TYPE_GROUP_FINDER_APPLICATION, characterName, displayName, NO_TARGET_LABEL, AcceptCallback, DeclineCallback)
local championPoints = oldestApplication:GetChampionPoints()
data.messageFormat = championPoints > 0 and SI_PLAYER_TO_PLAYER_INCOMING_GROUP_FINDER_APPLICATION_CHAMPION or SI_PLAYER_TO_PLAYER_INCOMING_GROUP_FINDER_APPLICATION
local userFacingName = ZO_GetPrimaryPlayerNameWithSecondary(displayName, characterName)
local role = oldestApplication:GetRole()
local roleText = GetString("SI_LFGROLE", role)
local roleIconFormat = zo_iconFormat(ZO_GetRoleIcon(role), "100%", "100%")
local ICON_SIZE = 24
local level = oldestApplication:GetLevel()
local levelText = ZO_GetLevelOrChampionPointsString(level, championPoints, ICON_SIZE)
--Use a special string for narrating the level text, so it narrates the champion icon properly
local levelTextNarration = ZO_GetLevelOrChampionPointsNarrationString(level, championPoints)
data.messageParams = { userFacingName, levelText, roleIconFormat, roleText }
--The return value of this function will be narrated instead of the normal target text
data.targetTextNarrationFunction = function(incomingEntry)
return zo_strformat(incomingEntry.messageFormat, userFacingName, levelTextNarration, roleIconFormat, roleText)
end
end
end
self.control:RegisterForEvent(EVENT_DUEL_INVITE_RECEIVED, OnDuelInviteReceived)
self.control:RegisterForEvent(EVENT_DUEL_INVITE_REMOVED, OnDuelInviteRemoved)
self.control:RegisterForEvent(EVENT_TRIBUTE_INVITE_RECEIVED, OnTributeInviteReceived)
self.control:RegisterForEvent(EVENT_TRIBUTE_INVITE_REMOVED, OnTributeInviteRemoved)
self.control:RegisterForEvent(EVENT_GROUP_INVITE_RECEIVED, OnGroupInviteReceived)
self.control:RegisterForEvent(EVENT_GROUP_INVITE_REMOVED, OnGroupInviteRemoved)
self.control:RegisterForEvent(EVENT_TRADE_INVITE_CONSIDERING, OnTradeWindowInviteConsidering)
self.control:RegisterForEvent(EVENT_TRADE_INVITE_REMOVED, OnTradeWindowInviteRemoved)
self.control:RegisterForEvent(EVENT_QUEST_SHARED, OnQuestShared)
self.control:RegisterForEvent(EVENT_QUEST_SHARE_REMOVED, OnQuestShareRemoved)
self.control:RegisterForEvent(EVENT_PLEDGE_OF_MARA_OFFER, OnPledgeOfMaraOffer)
self.control:RegisterForEvent(EVENT_PLEDGE_OF_MARA_OFFER_REMOVED, OnPledgeOfMaraOfferRemoved)
self.control:RegisterForEvent(EVENT_INCOMING_FRIEND_INVITE_ADDED, OnIncomingFriendInviteAdded)
self.control:RegisterForEvent(EVENT_INCOMING_FRIEND_INVITE_REMOVED, OnIncomingFriendRequestRemoved)
self.control:RegisterForEvent(EVENT_GUILD_INVITE_ADDED, OnGuildInviteAdded)
self.control:RegisterForEvent(EVENT_GUILD_INVITE_REMOVED, OnGuildInviteRemoved)
self.control:RegisterForEvent(EVENT_AGENT_CHAT_REQUESTED, OnAgentChatRequested)
self.control:RegisterForEvent(EVENT_AGENT_CHAT_ACCEPTED, OnAgentChatAccepted)
self.control:RegisterForEvent(EVENT_AGENT_CHAT_DECLINED, OnAgentChatDeclined)
self.control:RegisterForEvent(EVENT_CAMPAIGN_QUEUE_STATE_CHANGED, OnCampaignQueueStateChanged)
self.control:RegisterForEvent(EVENT_CAMPAIGN_QUEUE_JOINED, OnCampaignQueueJoined)
self.control:RegisterForEvent(EVENT_CAMPAIGN_QUEUE_LEFT, OnCampaignQueueLeft)
self.control:RegisterForEvent(EVENT_CAMPAIGN_ALLIANCE_LOCK_PENDING, OnCampaignLockPending)
self.control:RegisterForEvent(EVENT_CAMPAIGN_ALLIANCE_LOCK_ACTIVATED, OnCampaignLockActivated)
self.control:RegisterForEvent(EVENT_CURRENT_CAMPAIGN_CHANGED, OnCurrentCampaignChanged)
self.control:RegisterForEvent(EVENT_SCRIPTED_WORLD_EVENT_INVITE, OnScriptedWorldEventInvite)
self.control:RegisterForEvent(EVENT_SCRIPTED_WORLD_EVENT_INVITE_REMOVED, OnScriptedWorldEventInviteRemoved)
self.control:RegisterForEvent(EVENT_GROUPING_TOOLS_READY_CHECK_UPDATED, function(event, ...) self:OnGroupingToolsReadyCheckUpdated(...) end)
self.control:RegisterForEvent(EVENT_GROUPING_TOOLS_READY_CHECK_CANCELLED, function(event, ...) self:OnGroupingToolsReadyCheckCancelled(...) end)
self.control:RegisterForEvent(EVENT_LEVEL_UP_REWARD_UPDATED, OnLevelUpRewardUpdated)
GIFT_INVENTORY_MANAGER:RegisterCallback("GiftListsChanged", OnGiftsUpdated)
GROUP_FINDER_APPLICATIONS_LIST_MANAGER:RegisterCallback("ApplicationsListUpdated", OnGroupFinderApplicationsUpdated)
--Find member replacement prompt on a member leaving
local function OnGroupingToolsFindReplacementNotificationNew()
local activityId = GetActivityFindReplacementNotificationInfo()
local function DeferDecisionCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_LFG_FIND_REPLACEMENT)
end
local dungeonName = GetActivityName(activityId)
PlaySound(SOUNDS.LFG_FIND_REPLACEMENT)
self:RemoveFromIncomingQueue(INTERACT_TYPE_LFG_FIND_REPLACEMENT)
local text = zo_strformat(SI_LFG_FIND_REPLACEMENT_TEXT, dungeonName)
local promptData = self:AddPromptToIncomingQueue(INTERACT_TYPE_LFG_FIND_REPLACEMENT, nil, nil, text, AcceptActivityFindReplacementNotification, DeclineActivityFindReplacementNotification, DeferDecisionCallback)
promptData.acceptText = GetString(SI_LFG_FIND_REPLACEMENT_ACCEPT)
end
local function OnGroupingToolsFindReplacementNotificationRemoved()
self:RemoveFromIncomingQueue(INTERACT_TYPE_LFG_FIND_REPLACEMENT)
end
self.control:RegisterForEvent(EVENT_GROUPING_TOOLS_FIND_REPLACEMENT_NOTIFICATION_NEW, OnGroupingToolsFindReplacementNotificationNew)
self.control:RegisterForEvent(EVENT_GROUPING_TOOLS_FIND_REPLACEMENT_NOTIFICATION_REMOVED, OnGroupingToolsFindReplacementNotificationRemoved)
local function OnGroupElectionNotificationAdded()
local electionType, timeRemainingSeconds, descriptor, targetUnitTag = GetGroupElectionInfo()
local function AcceptCallback()
CastGroupVote(GROUP_VOTE_CHOICE_FOR)
end
local function DeclineCallback()
CastGroupVote(GROUP_VOTE_CHOICE_AGAINST)
end
local function DeferDecisionCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_GROUP_ELECTION)
end
local messageFormat, messageParams
if ZO_IsGroupElectionTypeCustom(electionType) then
if descriptor == ZO_GROUP_ELECTION_DESCRIPTORS.READY_CHECK then
messageFormat = GetString(SI_GROUP_ELECTION_READY_CHECK_MESSAGE)
else
messageFormat = descriptor
end
messageParams = {}
else
if electionType == GROUP_ELECTION_TYPE_KICK_MEMBER then
messageFormat = SI_GROUP_ELECTION_KICK_MESSAGE
elseif electionType == GROUP_ELECTION_TYPE_NEW_LEADER then
messageFormat = SI_GROUP_ELECTION_PROMOTE_MESSAGE
end
local primaryName = ZO_GetPrimaryPlayerNameFromUnitTag(targetUnitTag)
local secondaryName = ZO_GetSecondaryPlayerNameFromUnitTag(targetUnitTag)
messageParams = { primaryName, secondaryName }
end
PlaySound(SOUNDS.NEW_TIMED_NOTIFICATION)
self:RemoveFromIncomingQueue(INTERACT_TYPE_GROUP_ELECTION)
local promptData = self:AddPromptToIncomingQueue(INTERACT_TYPE_GROUP_ELECTION, nil, nil, nil, AcceptCallback, DeclineCallback, DeferDecisionCallback)
promptData.acceptText = GetString(SI_YES)
promptData.declineText = GetString(SI_NO)
promptData.expiresAtS = GetFrameTimeSeconds() + timeRemainingSeconds
promptData.messageFormat = messageFormat
promptData.messageParams = messageParams
promptData.expirationCallback = DeferDecisionCallback
promptData.dialogTitle = GetString("SI_NOTIFICATIONTYPE", NOTIFICATION_TYPE_GROUP_ELECTION)
promptData.uniqueSounds = {
accept = SOUNDS.GROUP_ELECTION_VOTE_SUBMITTED,
decline = SOUNDS.GROUP_ELECTION_VOTE_SUBMITTED,
}
end
local function OnGroupElectionNotificationRemoved()
self:RemoveFromIncomingQueue(INTERACT_TYPE_GROUP_ELECTION)
end
self.control:RegisterForEvent(EVENT_GROUP_ELECTION_NOTIFICATION_ADDED, function(event, ...) OnGroupElectionNotificationAdded(...) end)
self.control:RegisterForEvent(EVENT_GROUP_ELECTION_NOTIFICATION_REMOVED, function(event, ...) OnGroupElectionNotificationRemoved(...) end)
local function OnTrackedZoneStoryActivityCompleted(zoneId, zoneCompletionType, activityId)
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRACK_ZONE_STORY)
local numCompletedActivities, totalActivities, _, _, progressText = ZO_ZoneStories_Manager.GetActivityCompletionProgressValuesAndText(zoneId, zoneCompletionType)
if numCompletedActivities == totalActivities and not CanZoneStoryContinueTrackingActivities(zoneId) then
return
end
local function AcceptCallback()
local SET_AUTO_MAP_NAVIGATION_TARGET = true
local COMPLETION_TYPE_ALL = nil
TrackNextActivityForZoneStory(zoneId, COMPLETION_TYPE_ALL, SET_AUTO_MAP_NAVIGATION_TARGET)
end
local function DeclineCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRACK_ZONE_STORY)
end
PlaySound(SOUNDS.NEW_TIMED_NOTIFICATION)
local promptData = self:AddPromptToIncomingQueue(INTERACT_TYPE_TRACK_ZONE_STORY, nil, nil, nil, AcceptCallback, DeclineCallback)
promptData.acceptText = GetString(SI_ZONE_STORY_CONTINUE_EXPLORING_ACTION)
promptData.declineText = GetString(SI_DIALOG_DISMISS)
promptData.messageFormat = GetString("SI_ZONECOMPLETIONTYPE_PROGRESSDESCRIPTION", zoneCompletionType)
promptData.messageParams = { ZO_SELECTED_TEXT:Colorize(progressText), ZO_SELECTED_TEXT:Colorize(GetZoneNameById(zoneId)) }
promptData.dialogTitle = GetString("SI_ZONE_STORY_INFO_HEADER")
promptData.uniqueSounds =
{
accept = SOUNDS.ZONE_STORIES_TRACK_ACTIVITY,
}
end
self.control:RegisterForEvent(EVENT_TRACKED_ZONE_STORY_ACTIVITY_COMPLETED, function(event, ...) OnTrackedZoneStoryActivityCompleted(...) end)
local function OnZoneStoryActivityTracked()
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRACK_ZONE_STORY)
end
self.control:RegisterForEvent(EVENT_ZONE_STORY_ACTIVITY_TRACKED, function(event, ...) OnZoneStoryActivityTracked(...) end)
local pendingJumpToGroupLeaderPrompt = nil
local function OnTravelToLeaderPromptReceived()
-- LFG groups use their own jump notification
if IsInLFGGroup() then
return
end
-- The location of the group leader may not be available immediately
if pendingJumpToGroupLeaderPrompt then
local groupLeaderUnitTag = GetGroupLeaderUnitTag()
local groupLeaderZoneName = GetUnitZone(groupLeaderUnitTag)
if groupLeaderZoneName ~= "" then
pendingJumpToGroupLeaderPrompt = nil
if IsGroupMemberInRemoteRegion(groupLeaderUnitTag) then
local canJump, result = CanJumpToGroupMember(groupLeaderUnitTag)
if canJump then
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRAVEL_TO_LEADER)
local function AcceptCallback()
local leaderUnitTag = GetGroupLeaderUnitTag()
JumpToGroupMember(GetUnitName(leaderUnitTag))
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRAVEL_TO_LEADER)
end
local function DeclineCallback()
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRAVEL_TO_LEADER)
end
local promptData = self:AddPromptToIncomingQueue(INTERACT_TYPE_TRAVEL_TO_LEADER, nil, nil, nil, AcceptCallback, DeclineCallback)
promptData.acceptText = GetString(SI_DIALOG_ACCEPT)
promptData.declineText = GetString(SI_DIALOG_DECLINE)
promptData.messageFormat = GetString(GetUnitZone("player") == groupLeaderZoneName and SI_JUMP_TO_GROUP_LEADER_OCCURANCE_PROMPT or SI_JUMP_TO_GROUP_LEADER_WORLD_PROMPT)
promptData.messageParams = { groupLeaderZoneName }
promptData.dialogTitle = GetString("SI_JUMP_TO_GROUP_LEADER_TITLE")
elseif result == JUMP_TO_PLAYER_RESULT_ZONE_COLLECTIBLE_LOCKED then
local zoneIndex = GetUnitZoneIndex(groupLeaderUnitTag)
local collectibleId = GetCollectibleIdForZone(zoneIndex)
local collectibleData = ZO_COLLECTIBLE_DATA_MANAGER:GetCollectibleDataById(collectibleId)
local collectibleName = collectibleData:GetName()
local categoryName = collectibleData:GetCategoryData():GetName()
local message = zo_strformat(SI_COLLECTIBLE_LOCKED_FAILURE_CAUSED_BY_JUMP_TO_GROUP_LEADER, groupLeaderZoneName)
local marketOperation = MARKET_OPEN_OPERATION_DLC_FAILURE_TELEPORT_TO_GROUP
ZO_Dialogs_ShowPlatformDialog("COLLECTIBLE_REQUIREMENT_FAILED", { collectibleData = collectibleData, marketOpenOperation = marketOperation }, { mainTextParams = { message, collectibleName, categoryName } })
end
end
end
end
end
local function OnUnitCreated(unitTag)
if ZO_Group_IsGroupUnitTag(unitTag) then
OnTravelToLeaderPromptReceived()
end
end
local function OnZoneUpdate(unitTag, newZone)
if ZO_Group_IsGroupUnitTag(unitTag) then
OnTravelToLeaderPromptReceived()
end
end
local function OnGroupMemberJoined(characterName, displayName, isLocalPlayer)
if isLocalPlayer then
local groupLeaderUnitTag = GetGroupLeaderUnitTag()
if not AreUnitsEqual(groupLeaderUnitTag, "player") then
pendingJumpToGroupLeaderPrompt = true
OnTravelToLeaderPromptReceived()
end
end
end
local function OnPlayerActivateOrLeaderUpdate()
if pendingJumpToGroupLeaderPrompt then
OnTravelToLeaderPromptReceived()
end
end
local function OnGroupMemberLeft(eventCode, characterName, reason, isLocalPlayer, amLeader)
if isLocalPlayer then
self:RemoveFromIncomingQueue(INTERACT_TYPE_TRAVEL_TO_LEADER)
end
end
self.control:RegisterForEvent(EVENT_UNIT_CREATED, function(event, ...) OnUnitCreated(...) end)
self.control:RegisterForEvent(EVENT_ZONE_UPDATE, function(event, ...) OnZoneUpdate(...) end)
self.control:RegisterForEvent(EVENT_GROUP_MEMBER_JOINED, function(event, ...) OnGroupMemberJoined(...) end)