-
Notifications
You must be signed in to change notification settings - Fork 1
/
QueueTools.lua
1349 lines (1074 loc) · 47.9 KB
/
QueueTools.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 _G, ModuleName, Private, AddonName, Namespace = _G, 'QueueTools', {}, ...
local Addon = Namespace.Addon
local Module = Addon:NewModule(ModuleName, 'AceEvent-3.0', 'AceTimer-3.0', 'AceComm-3.0')
local L = Namespace.Libs.AceLocale:GetLocale(AddonName)
local ACD = Namespace.Libs.AceConfigDialog
local LibDD = Namespace.Libs.LibDropDown
local ScrollingTable = Namespace.Libs.ScrollingTable
Namespace.QueueTools = Module
local Faction = Namespace.PlayerData.Faction
local GetPlayerDataByUnit = Namespace.PlayerData.GetPlayerDataByUnit
local GetPlayerDataByName = Namespace.PlayerData.GetPlayerDataByName
local GetGroupLeaderData = Namespace.PlayerData.GetGroupLeaderData
local RebuildPlayerData = Namespace.PlayerData.RebuildPlayerData
local ForEachPlayerData = Namespace.PlayerData.ForEachPlayerData
local RefreshMissingData = Namespace.PlayerData.RefreshMissingData
local ForEachUnitData = Namespace.PlayerData.ForEachUnitData
local Role = Namespace.PlayerData.Role
local EntryButtonShowPopTime = Namespace.Utils.EntryButtonShowPopTime
local ReadyCheckState = Namespace.Utils.ReadyCheckState
local BattlegroundStatus = Namespace.Utils.BattlegroundStatus
local RoleCheckStatus = Namespace.Utils.RoleCheckStatus
local IsLeaderOrAssistant = Namespace.Utils.IsLeaderOrAssistant
local GetPlayerAuraExpiration = Namespace.Utils.GetPlayerAuraExpiration
local RaidIconChatStyle = Namespace.Utils.RaidIconChatStyle
local IsModifierButtonDown = Namespace.Utils.IsModifierButtonDown
local PackData = Namespace.Communication.PackData
local UnpackData = Namespace.Communication.UnpackData
local GetMessageDestination = Namespace.Communication.GetMessageDestination
local InActiveBattleground = Namespace.Battleground.InActiveBattleground
local AllowQueuePause = Namespace.Battleground.AllowQueuePause
local QueueStatus = Namespace.Battleground.QueueStatus
local ColorList = Namespace.Utils.ColorList
local CreateAtlasMarkup = CreateAtlasMarkup
local DoReadyCheck = DoReadyCheck
local GetInstanceInfo = GetInstanceInfo
local CreateFrame = CreateFrame
local PlaySound = PlaySound
local CharacterPanelOpenSound = SOUNDKIT.IG_CHARACTER_INFO_OPEN
local CharacterPanelCloseSound = SOUNDKIT.IG_CHARACTER_INFO_CLOSE
local GetNumGroupMembers = GetNumGroupMembers
local GetLFGRoleUpdate = GetLFGRoleUpdate
local CombatLogGetCurrentEventInfo = CombatLogGetCurrentEventInfo
local GetDebuffDataByIndex = C_UnitAuras.GetDebuffDataByIndex
local UnitInOtherParty = UnitInOtherParty
local GetBattlefieldPortExpiration = GetBattlefieldPortExpiration
local GetTime = GetTime
local SendChatMessage = SendChatMessage
local DEBUFF_MAX_DISPLAY = DEBUFF_MAX_DISPLAY
local UNKNOWNOBJECT = UNKNOWNOBJECT
local TimeDiff = Namespace.Utils.TimeDiff
local GetServerTime = GetServerTime
local max = math.max
local ceil = math.ceil
local format = string.format
local pairs = pairs
local concat = table.concat
local sort = table.sort
local date = date
local locale = GetLocale()
local SpellIds = {
DeserterDebuff = 26013,
MercenaryContractAlliance = 193472,
MercenaryContractHorde = 193475,
}
local tableStructure = {
{
name = '',
width = 20,
},
{
name = '',
width = 110,
align = 'LEFT',
},
{
name = L['Auto Queue'],
width = 45,
align = 'CENTER',
},
{
name = ' ' .. L['Merc'],
width = 50,
align = 'CENTER',
},
{
name = ' ' .. L['Status'],
width = 75,
align = 'CENTER',
},
{
name = '',
width = 25,
align = 'LEFT',
}
}
local Memory = {
-- seems like you can't do a second ready check for about 5~6 seconds, even if the "finished" event is faster
readyCheckGracePeriod = 6,
-- keep track of last ready check and when it finished to update the button and icons properly
lastReadyCheckTime = 0,
lastReadyCheckDuration = 0,
readyCheckButtonTicker = nil,
readyCheckClearTimeout = nil,
readyCheckHeartbeatTimout = nil,
disableEntryButtonTicker = nil,
playerTableCache = {},
-- the data that should be send next data sync event
syncDataPayloadBuffer = nil,
InCombat = false,
InGossip = false,
queueExpiryTimer = nil,
queueTicker = nil,
}
local NameFormat = {
RealmWhenPlayer = 1,
RealmAlways = 2,
RealmNever = 3,
}
Namespace.QueueTools.NameFormat = NameFormat
local CommunicationEvent = {
SyncData = 'Bgc:syncData',
ReadyCheckHeartbeat = 'Bgc:rchb',
EnterBattleground = 'Bgc:enterBg',
DeclineBattleground = 'Bgc:declineBg',
}
function Private.SendSyncData()
if Memory.syncDataPayloadBuffer == nil then return end
local channel, player = GetMessageDestination()
local payload = PackData(Memory.syncDataPayloadBuffer)
Module:SendCommMessage(CommunicationEvent.SyncData, payload, channel, player)
Memory.syncDataPayloadBuffer = nil
end
function Module:ScheduleSendSyncData()
local shouldSchedule = Memory.syncDataPayloadBuffer == nil
local remainingMercenary = Private.GetRemainingAuraTime(SpellIds.MercenaryContractHorde)
if remainingMercenary == -1 then
remainingMercenary = Private.GetRemainingAuraTime(SpellIds.MercenaryContractAlliance)
end
Memory.syncDataPayloadBuffer = {
addonVersion = Namespace.Meta.version,
remainingMercenary = remainingMercenary,
remainingDeserter = Private.GetRemainingAuraTime(SpellIds.DeserterDebuff),
autoAcceptRole = Namespace.Database.profile.QueueTools.Automation.acceptRoleSelection,
wantLead = Namespace.Database.profile.BattlegroundTools.WantBattlegroundLead.wantLead,
}
if not shouldSchedule then return end
self:ScheduleTimer(Private.SendSyncData, ceil(GetNumGroupMembers() * 0.1) + 1)
end
function Private.CanDoReadyCheck()
if Memory.lastReadyCheckTime + Memory.lastReadyCheckDuration > GetTime() then
return false
end
if not IsLeaderOrAssistant('player') then
return false
end
local _, instanceType = GetInstanceInfo()
return instanceType ~= 'pvp' and instanceType ~= 'arena'
end
function Private.TriggerDeserterUpdate(data)
if data.deserterExpiry > 0 and data.deserterExpiry <= GetTime() then
data.deserterExpiry = -1
end
if data.deserterExpiry > -1 then
-- only re-check if the player doesn't have it already
-- this ensures the "guess" here is a fallback vs what
-- the addon comms say.
return
end
if not data.units.primary then
return
end
for i = 1, DEBUFF_MAX_DISPLAY do
local aura = GetDebuffDataByIndex(data.units.primary, i)
if aura and aura.spellId == SpellIds.DeserterDebuff then
data.deserterExpiry = aura.expirationTime
return
end
end
end
function Private.CreateTableRow(data)
local indexColumn = {
value = function ()
if data.role == Role.Leader then
return [[|TInterface\GroupFrame\UI-Group-LeaderIcon:17|t]]
elseif data.role == Role.Assist then
return [[|TInterface\GroupFrame\UI-Group-AssistantIcon:17|t]]
end
return ''
end,
}
local nameColumn = {
value = function(tableData, _, realRow, column)
local columnData = tableData[realRow].cols[column]
RefreshMissingData(data)
local name = Module:GetPlayerNameForDisplay(data)
local color = data.classColor or ColorList.UnknownClass
columnData.color = { r = color.r, g = color.g, b = color.b, a = color.a }
return name
end,
}
local autoAcceptRoleColumn = {
value = function(tableData, _, realRow, column)
local columnData = tableData[realRow].cols[column]
if data.autoAcceptRole == nil then
columnData.color = ColorList.Warning
return '?'
end
columnData.color = nil
return data.autoAcceptRole and L['yes'] or L['no']
end,
}
local mercenaryColumn = {
value = function(tableData, _, realRow, column)
local columnData = tableData[realRow].cols[column]
local currentTime = GetTime()
local leader = GetGroupLeaderData()
local timeDiff = TimeDiff(data.mercenaryExpiry, currentTime)
local shouldCheckStatus = data ~= leader and data.faction == (leader or data).faction
local leaderHasMercenary = (leader or data).mercenaryExpiry > currentTime
columnData.color = nil
if timeDiff.fullSeconds < 1 then
if shouldCheckStatus and leaderHasMercenary then
columnData.color = ColorList.Bad
end
return L['no']
end
-- it's a guesstimate
if timeDiff.fullMinutes > 60 then
if shouldCheckStatus and not leaderHasMercenary and leader.addonVersion then
columnData.color = ColorList.Bad
else
columnData.color = ColorList.UnknownClass
end
return L['yes']
end
if shouldCheckStatus and not leaderHasMercenary then
columnData.color = leader.addonVersion and ColorList.Bad or ColorList.Warning
return L['yes']
end
if timeDiff.fullMinutes < 4 then
columnData.color = ColorList.Bad
elseif timeDiff.fullMinutes < 9 then
columnData.color = ColorList.Warning
end
return format('%dm', timeDiff.fullMinutes)
end,
}
local readyCheckColumn = {
value = function(tableData, _, realRow, column)
local columnData = tableData[realRow].cols[column]
if not data.isConnected then
columnData.color = ColorList.Bad
return L['Offline']
end
Private.TriggerDeserterUpdate(data)
local timeDiff = TimeDiff(data.deserterExpiry, GetTime())
if timeDiff.fullSeconds > 0 then
columnData.color = ColorList.Bad
return format('%dm', timeDiff.fullMinutes) .. ' ' .. L['Deserter']
end
local battlegroundStatus = data.battlegroundStatus
if battlegroundStatus == BattlegroundStatus.Entered then
columnData.color = nil
return L['Entered']
end
local roleCheckStatus = data.roleCheckStatus
if roleCheckStatus == RoleCheckStatus.Waiting then
columnData.color = ColorList.Warning
return L['Role Check']
end
if roleCheckStatus == RoleCheckStatus.Accepted then
columnData.color = ColorList.Good
return L['Accepted']
end
local readyState = data.readyState
if readyState == ReadyCheckState.Declined then
columnData.color = ColorList.Bad
return L['Not Ready']
end
if readyState == ReadyCheckState.Ready then
columnData.color = ColorList.Good
return L['Ready']
end
if battlegroundStatus == BattlegroundStatus.Declined then
columnData.color = nil
return L['Declined']
end
columnData.color = nil
if readyState == ReadyCheckState.Waiting then
return L['Ready Check']
end
if battlegroundStatus == BattlegroundStatus.Waiting then
return L['Queue Pop']
end
return L['OK']
end,
}
local factionColumn = {
value = function ()
local faction = data.faction
if faction == Faction.Horde then
return CreateAtlasMarkup('Warfronts-FieldMapIcons-Horde-Banner-Minimap', 20, 20)
end
if faction == Faction.Alliance then
return CreateAtlasMarkup('Warfronts-FieldMapIcons-Alliance-Banner-Minimap', 20, 20)
end
return CreateAtlasMarkup('Warfronts-FieldMapIcons-Empty-Banner-Minimap', 20, 20)
end,
}
return { cols = {
indexColumn,
nameColumn,
autoAcceptRoleColumn,
mercenaryColumn,
readyCheckColumn,
factionColumn,
}, originalData = data }
end
function Private.RefreshGroupInfoFrame()
local queueFrame = _G.BgcQueueFrame
if not queueFrame then return end
queueFrame:UpdateReadyCheckButtonState()
queueFrame:UpdateLeaderCounter()
queueFrame:UpdateAddonUsersLabel()
queueFrame.PlayerTable:Refresh()
end
function Private.GetRemainingAuraTime(spellId)
local expirationTime = GetPlayerAuraExpiration(spellId)
return expirationTime and expirationTime - GetTime() or -1
end
function Private.RebuildGroupInformationTable(unitPlayerData)
local tableCache, count = {}, 0
for _, playerData in pairs(unitPlayerData) do
count = count + 1
tableCache[count] = Private.CreateTableRow(playerData)
end
Memory.playerTableCache = tableCache
local queueFrame = _G.BgcQueueFrame
if queueFrame and Namespace.Database.profile.QueueTools.showGroupQueueFrame then
queueFrame.PlayerTable:SetData(tableCache)
queueFrame:UpdateReadyCheckButtonState()
queueFrame:UpdateAddonUsersLabel()
end
Module:ScheduleSendSyncData()
end
function Private.UpdateGroupInfoVisibility(newVisibility)
RebuildPlayerData()
if not _G.BgcQueueFrame then return end
_G.BgcQueueFrame:SetShown(newVisibility)
end
function Private.InitializeBattlegroundOptions()
local dropdown = LibDD:Create_UIDropDownMenu('BgcBattlegroundOptions', _G.HonorFrame)
dropdown:SetPoint('LEFT', _G.HonorFrameQueueButton, 'RIGHT', -2, -2)
LibDD:UIDropDownMenu_SetWidth(dropdown, 160)
LibDD:UIDropDownMenu_SetText(dropdown, L['Battleground Options'])
LibDD:UIDropDownMenu_Initialize(dropdown, function ()
do
local info = LibDD:UIDropDownMenu_CreateInfo()
info.text = L['Automatically accept role when queuing']
info.checked = Module:GetAutomationSetting('acceptRoleSelection')
info.isNotRadio = true
info.keepShownOnClick = true
info.func = function (_, _, _, checked)
Module:SetAutomationSetting('acceptRoleSelection', checked)
end
LibDD:UIDropDownMenu_AddButton(info)
end
do
local info = LibDD:UIDropDownMenu_CreateInfo()
info.text = L['Request lead inside battleground']
info.checked = Namespace.BattlegroundTools:GetWantLeadSetting('wantLead')
info.isNotRadio = true
info.keepShownOnClick = true
info.func = function (_, _, _, checked)
Namespace.BattlegroundTools:SetWantLeadSetting('wantLead', checked)
Namespace.BattlegroundTools:RequestRaidLead()
end
LibDD:UIDropDownMenu_AddButton(info)
end
end)
end
function Private.InitializeBattlegroundModeCheckbox()
local checkbox = CreateFrame('CheckButton', 'BgcBattlegroundModeCheckbox', _G.PVPUIFrame, 'UICheckButtonTemplate')
checkbox:SetPoint('BOTTOMRIGHT', _G.PVEFrame, 'BOTTOMRIGHT', -2, 2)
checkbox:SetSize(24, 24)
checkbox:SetChecked(Namespace.Database.profile.QueueTools.showGroupQueueFrame)
checkbox:SetScript('OnEnter', function (self)
local tooltip = _G.GameTooltip
tooltip:SetOwner(self, 'ANCHOR_RIGHT')
tooltip:SetText(L['Show or hide the Battleground Commander group information window'], nil, nil, nil, nil, true)
tooltip:Show()
end)
checkbox:SetScript('OnLeave', function () _G.GameTooltip:Hide() end)
checkbox:SetScript('OnClick', function (self)
local newVisibility = self:GetChecked()
Namespace.Database.profile.QueueTools.showGroupQueueFrame = newVisibility
Private.UpdateGroupInfoVisibility(newVisibility)
PlaySound(newVisibility and CharacterPanelOpenSound or CharacterPanelCloseSound)
end)
checkbox:Show()
_G.PVPUIFrame.BattlegroundModeCheckbox = checkbox
local text = checkbox:CreateFontString(nil, 'ARTWORK', 'GameFontNormal')
text:SetText(L['Group Info'])
text:SetPoint('RIGHT', checkbox, 'LEFT')
text:SetWordWrap(false)
checkbox.Text = text
end
function Private.ProcessSyncData(payload, data)
local time = GetTime()
data.mercenaryExpiry = payload.remainingMercenary + time
data.deserterExpiry = payload.remainingDeserter + time
data.addonVersion = payload.addonVersion
data.autoAcceptRole = payload.autoAcceptRole
if payload.wantLead ~= nil then
data.wantLead = payload.wantLead
if data.wantLead then
Namespace.BattlegroundTools:WantBattlegroundLead(data)
end
end
Private.RefreshGroupInfoFrame()
end
function Private.OnSyncData(_, text, _, sender)
local payload = UnpackData(text)
if not payload then return end
local data = GetPlayerDataByName(payload.sender or sender)
if not data then return end
Private.ProcessSyncData(payload, data)
end
function Private.OnReadyCheckHeartbeat(_, text, _, sender)
local payload = UnpackData(text)
text = payload and payload.message or text
sender = payload and payload.sender or sender
local playerData = GetPlayerDataByName(sender)
if not playerData or playerData.units.player then return end
local acceptReadyCheck = function (skipVisibility)
if not skipVisibility and not _G.ReadyCheckFrameYesButton:IsVisible() then return end
_G.ReadyCheckFrameYesButton:Click()
Addon:Print(format(L['Accepted automated ready check with message: "%s"'], text))
end
-- due to the async nature, the ready check might come later than the
-- message to click the button, postpone click if not visible yet
if _G.ReadyCheckFrameYesButton:IsVisible() then
return acceptReadyCheck(true)
end
Module:ScheduleTimer(acceptReadyCheck, 0.055)
end
function Module:OnInitialize()
self:RegisterEvent('ADDON_LOADED')
end
function Private.OnClickEnterBattleground()
local channel, player = GetMessageDestination()
Module:SendCommMessage(CommunicationEvent.EnterBattleground, PackData({}), channel, player)
if not IsLeaderOrAssistant('player') then return end
local config = Namespace.Database.profile.QueueTools.InspectQueue
if config.sendMessageOnBattlegroundEntry then
local message = concat({RaidIconChatStyle.GreenTriangle, Private.TwoLanguages('Enter'), RaidIconChatStyle.GreenTriangle}, ' ')
SendChatMessage(Addon:PrependChatTemplate(message), channel)
end
end
function Private.OnEnterBattleground(_, text, _, sender)
local payload = UnpackData(text)
sender = payload and payload.sender or sender
local data = GetPlayerDataByName(sender)
if not data then return end
if data.battlegroundStatus == BattlegroundStatus.Waiting then
data.battlegroundStatus = BattlegroundStatus.Entered
end
local units = data.units
if not units.player
and units.primary
and Namespace.Database.profile.QueueTools.Automation.disableEntryButtonOnQueuePop
and IsLeaderOrAssistant(units.primary)
then
Private.RestoreEntryButton()
end
Private.RefreshGroupInfoFrame()
end
function Private.RestoreEntryButton()
local button = _G.PVPReadyDialogEnterBattleButton
button:SetEnabled(true)
button:SetText(Memory.disableEntryButtonOriginalText)
if Memory.disableEntryButtonTicker == nil then return end
Module:CancelTimer(Memory.disableEntryButtonTicker)
Memory.disableEntryButtonTicker = nil
end
function Private.DisableEntryButton(text)
local button = _G.PVPReadyDialogEnterBattleButton
button:SetEnabled(false)
button:SetText(text)
if Memory.disableEntryButtonTicker then return end
Memory.disableEntryButtonTicker = Module:ScheduleRepeatingTimer(function ()
if IsModifierButtonDown(Module:GetAutomationSetting('enableEntryButtonModifier')) then
Private.RestoreEntryButton()
end
end, 0.5)
end
function Private.SetEntryButtonTime(timeFormat)
local text = Memory.disableEntryButtonOriginalText
if timeFormat then
text = format('%s (%s)', text, date(timeFormat, GetServerTime()))
end
_G.PVPReadyDialogEnterBattleButton:SetText(text)
end
function Private.OnDeclineBattleground(_, text, _, sender)
local payload = UnpackData(text)
sender = payload and payload.sender or sender
local data = GetPlayerDataByName(sender)
if not data then return end
if data.battlegroundStatus == BattlegroundStatus.Waiting then
data.battlegroundStatus = BattlegroundStatus.Declined
end
local units = data.units
if not units.player
and units.primary
and Namespace.Database.profile.QueueTools.Automation.disableEntryButtonOnCancel
and IsLeaderOrAssistant(units.primary)
then
Private.DisableEntryButton([[|TInterface\RaidFrame\ReadyCheck-NotReady:15|t ]] .. format(L['Cancel (%s)'], Module:GetAutomationSetting('enableEntryButtonModifier')))
end
Private.RefreshGroupInfoFrame()
end
function Module:OnEnable()
self:RegisterEvent('READY_CHECK')
self:RegisterEvent('READY_CHECK_CONFIRM')
self:RegisterEvent('READY_CHECK_FINISHED')
self:RegisterEvent('PLAYER_REGEN_ENABLED')
self:RegisterEvent('PLAYER_REGEN_DISABLED')
self:RegisterEvent('GOSSIP_CLOSED')
self:RegisterEvent('GOSSIP_SHOW')
self:RegisterEvent('LFG_ROLE_CHECK_SHOW')
self:RegisterEvent('LFG_ROLE_CHECK_ROLE_CHOSEN')
self:RegisterEvent('LFG_ROLE_CHECK_DECLINED')
self:RegisterEvent('LFG_ROLE_CHECK_UPDATE')
self:RegisterEvent('UNIT_CONNECTION')
self:RegisterComm(CommunicationEvent.SyncData, Private.OnSyncData)
self:RegisterComm(CommunicationEvent.ReadyCheckHeartbeat, Private.OnReadyCheckHeartbeat)
self:RegisterComm(CommunicationEvent.EnterBattleground, Private.OnEnterBattleground)
self:RegisterComm(CommunicationEvent.DeclineBattleground, Private.OnDeclineBattleground)
Namespace.PlayerData.RegisterOnUpdate('rebuild_group_information', Private.RebuildGroupInformationTable)
Namespace.Database.RegisterCallback(self, 'OnProfileChanged', 'RefreshConfig')
Namespace.Database.RegisterCallback(self, 'OnProfileCopied', 'RefreshConfig')
Namespace.Database.RegisterCallback(self, 'OnProfileReset', 'RefreshConfig')
Namespace.Battleground.RegisterQueueStateListener('update_group_information', Private.DetectQueueEntry)
Namespace.Battleground.RegisterQueueStateListener('protect_entry_button', Private.DetectQueuePop)
Namespace.Battleground.RegisterQueueStateListener('rebuild_post_bg_group_info', Private.DetectBattlegroundExit)
Namespace.Battleground.RegisterQueueStateListener('alert_queue_paused', Private.DetectQueuePause)
Namespace.Battleground.RegisterQueueStateListener('notify_queue_resume', Private.DetectQueueResume)
Namespace.Battleground.RegisterQueueStateListener('ensure_nobody_entered', Private.DetectQueueCancelAfterConfirm)
Namespace.Battleground.RegisterQueueStateListener('clean_pre_bg_group_info', Private.DetectBattlegroundEntryAfterConfirm)
Namespace.Battleground.RegisterQueueStateListener('update_mercenary_aura_tracking', Private.UpdateAuraTracking)
Private.UpdateAuraTracking()
self:RefreshConfig()
local entryButton = _G.PVPReadyDialogEnterBattleButton
entryButton:HookScript('OnClick', Private.OnClickEnterBattleground)
Memory.disableEntryButtonOriginalText = entryButton:GetText()
end
function Module:UNIT_CONNECTION(_, unitTarget, isConnected)
local playerData = GetPlayerDataByUnit(unitTarget)
if not playerData then return end
playerData.isConnected = isConnected
end
function Module:LFG_ROLE_CHECK_ROLE_CHOSEN(_, sender)
local data = GetPlayerDataByName(sender)
if not data then return end
data.roleCheckStatus = RoleCheckStatus.Accepted
if data.role == Role.Leader then
-- The leader does not get a LFG_ROLE_CHECK_SHOW event so sync the info
-- here for just the leader
self:ScheduleSendSyncData()
end
Private.RefreshGroupInfoFrame()
end
function Module:LFG_ROLE_CHECK_DECLINED()
ForEachPlayerData(function(data) data.roleCheckStatus = RoleCheckStatus.Nothing end)
Private.RefreshGroupInfoFrame()
end
function Module:LFG_ROLE_CHECK_UPDATE()
local doRefresh = false
ForEachUnitData(function(data)
if data.roleCheckStatus == RoleCheckStatus.Nothing then
data.roleCheckStatus = RoleCheckStatus.Waiting
doRefresh = true
end
end)
if not doRefresh then return end
Private.RefreshGroupInfoFrame()
end
function Module:LFG_ROLE_CHECK_SHOW()
local _, _, _, _, _, bgQueue = GetLFGRoleUpdate()
if not bgQueue then return end
-- always schedule sending your data when the queue popup happens
-- this means the leader has more accurate information
self:ScheduleSendSyncData()
if not Namespace.Database.profile.QueueTools.Automation.acceptRoleSelection then return end
local button = _G.LFDRoleCheckPopupAcceptButton
if not button then return end
button:Click()
end
function Private.SendReadyCheckHeartbeat(message)
if not Private.CanDoReadyCheck() then return end
DoReadyCheck()
Addon:Print(format(L['Sending automated ready check with message: "%s"'], message))
Module:SendCommMessage(CommunicationEvent.ReadyCheckHeartbeat, PackData({ message = message }), GetMessageDestination())
end
function Private.ScheduleReadyCheckHeartbeat(message, delay, preventReadyCheckCallback)
if delay == nil then delay = 0 end
if Memory.readyCheckHeartbeatTimout ~= nil then return end
Memory.readyCheckHeartbeatTimout = Module:ScheduleTimer(function ()
if preventReadyCheckCallback and preventReadyCheckCallback() then return end
Private.SendReadyCheckHeartbeat(message)
Memory.readyCheckHeartbeatTimout = nil
end, delay)
end
function Private.GuessPartyMemberWithoutAddonEntered()
local refreshData = false
ForEachUnitData(function(data)
if data.addonVersion then return end
if data.battlegroundStatus ~= BattlegroundStatus.Waiting then return end
if not UnitInOtherParty(data.units.primary) then return end
data.battlegroundStatus = BattlegroundStatus.Entered
if data.role == Role.Leader then
Private.RestoreEntryButton()
end
refreshData = true
end)
if refreshData then Private.RefreshGroupInfoFrame() end
end
function Private.CleanUpQueueTicker()
if Memory.queueTicker then
Module:CancelTimer(Memory.queueTicker)
Memory.queueTicker = nil
end
end
function Private.CleanUpQueueExpiry()
Memory.queueExpiryTimer = nil
if InActiveBattleground() then return end
ForEachUnitData(function(data)
data.battlegroundStatus = UnitInOtherParty(data.units.primary) and BattlegroundStatus.Entered or BattlegroundStatus.Nothing
end)
Private.RefreshGroupInfoFrame()
end
function Private.DetectQueuePop(previousState, newState)
if previousState.status ~= QueueStatus.Queued then return end
if newState.status ~= QueueStatus.Confirm then return end
if Memory.queueExpiryTimer then
Module:CancelTimer(Memory.queueExpiryTimer)
end
Memory.queueExpiryTimer = Module:ScheduleTimer(Private.CleanUpQueueExpiry, GetBattlefieldPortExpiration(newState.queueId) + 1)
Memory.queueTicker = Module:ScheduleRepeatingTimer(Private.GuessPartyMemberWithoutAddonEntered, 2)
ForEachUnitData(function(data) data.battlegroundStatus = BattlegroundStatus.Waiting end)
Private.RefreshGroupInfoFrame()
local config = Namespace.Database.profile.QueueTools.Automation
if config.disableEntryButtonOnQueuePop and GetNumGroupMembers() > 1 and not IsLeaderOrAssistant('player') then
Private.DisableEntryButton([[|TInterface\RaidFrame\ReadyCheck-Waiting:15|t ]] .. format(L['Waiting (%s)'], Module:GetAutomationSetting('enableEntryButtonModifier')))
return;
end
if (config.showTimeOnEntryButton == EntryButtonShowPopTime.OnlyGroupLead and IsLeaderOrAssistant('player'))
or (config.showTimeOnEntryButton == EntryButtonShowPopTime.Always and (IsLeaderOrAssistant('player') or GetNumGroupMembers() == 1))
then
Private.SetEntryButtonTime(config.entryButtonTimeFormat)
else
Private.SetEntryButtonTime(nil)
end
end
function Private.DetectQueueEntry(previousState, newState)
if previousState.status ~= QueueStatus.None then return end
if newState.status ~= QueueStatus.Queued then return end
ForEachPlayerData(function(data)
data.battlegroundStatus = BattlegroundStatus.Nothing
data.roleCheckStatus = RoleCheckStatus.Nothing
end)
Private.RestoreEntryButton()
Private.RefreshGroupInfoFrame()
end
function Private.DetectBattlegroundExit(previousState, newState)
if previousState.status ~= QueueStatus.Active then return end
if newState.status ~= QueueStatus.None then return end
RebuildPlayerData()
ForEachPlayerData(function(data) data.battlegroundStatus = BattlegroundStatus.Nothing end)
end
function Private.DetectQueuePause(previousState, newState, mapName)
if previousState.status ~= QueueStatus.Queued then return end
if newState.status ~= QueueStatus.Queued then return end
if previousState.suspendedQueue == true then return end
if newState.suspendedQueue == false then return end
local config = Namespace.Database.profile.QueueTools.InspectQueue
local isLeader = IsLeaderOrAssistant('player')
if config.onlyAsLeader and not isLeader then return end
if AllowQueuePause() then return end
if config.sendPausedMessage then
local message = Private.TwoLanguages('Queue paused for %s', mapName)
if GetNumGroupMembers() == 0 then
-- just return, you got no friends to do a ready check with anyway
return Addon:Print(message)
end
local channel = GetMessageDestination()
SendChatMessage(Addon:PrependChatTemplate(message), channel)
end
if isLeader and config.doReadyCheckOnQueuePause then
Private.ScheduleReadyCheckHeartbeat('Detected queue pause')
end
end
function Private.DetectQueueResume(previousState, newState, mapName)
if previousState.status ~= QueueStatus.Queued then return end
if newState.status ~= QueueStatus.Queued then return end
if previousState.suspendedQueue == false then return end
if newState.suspendedQueue == true then return end
local config = Namespace.Database.profile.QueueTools.InspectQueue
if config.onlyAsLeader and not IsLeaderOrAssistant('player') then return end
if not config.sendResumedMessage then return end
local message = Private.TwoLanguages('Queue resumed for %s', mapName)
if GetNumGroupMembers() == 0 then
return Addon:Print(message)
end
local channel = GetMessageDestination()
SendChatMessage(Addon:PrependChatTemplate(message), channel)
end
function Private.DetectQueueCancelAfterConfirm(previousState, newState)
if previousState.status ~= QueueStatus.Confirm then return end
if newState.status ~= QueueStatus.None then return end
Module:SendCommMessage(CommunicationEvent.DeclineBattleground, PackData({}), GetMessageDestination())
Private.RestoreEntryButton()
if not IsLeaderOrAssistant('player') then return end
local config = Namespace.Database.profile.QueueTools.InspectQueue
if config.doReadyCheckOnQueueCancelAfterConfirm then
-- wait a few seconds as not everyone will have cancelled as fast
Private.ScheduleReadyCheckHeartbeat('Confirm nobody entered', 4, function ()
local preventReadyCheck = true
ForEachUnitData(function (data)
if data.battlegroundStatus == BattlegroundStatus.Waiting then
preventReadyCheck = false
return false
end
end)
return preventReadyCheck
end)
end
if config.sendMessageOnQueueCancelAfterConfirm then
local channel = GetMessageDestination()
local message = concat({RaidIconChatStyle.RedCross, Private.TwoLanguages('Cancel'), RaidIconChatStyle.RedCross}, ' ')
SendChatMessage(Addon:PrependChatTemplate(message), channel)
end
end
function Private.DetectBattlegroundEntryAfterConfirm(previousState, newState)
if previousState.status ~= QueueStatus.Confirm then return end
if newState.status ~= QueueStatus.Active then return end
Private.CleanUpQueueTicker()
ForEachPlayerData(function(data) data.battlegroundStatus = BattlegroundStatus.Nothing end)
end
function Module:RefreshConfig()
Private.UpdateGroupInfoVisibility(Namespace.Database.profile.QueueTools.showGroupQueueFrame)
self:ScheduleSendSyncData()
end
function Private.UpdateAuraTracking()
if not Memory.InGossip and (Memory.InCombat or InActiveBattleground()) then
return Module:UnregisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
end
-- only track outside of combat and outside of battlegrounds
Module:RegisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
end
function Module:PLAYER_REGEN_DISABLED()
Memory.InCombat = true
Private.UpdateAuraTracking()
end
function Module:PLAYER_REGEN_ENABLED()
Memory.InCombat = false
Private.UpdateAuraTracking()
end
function Module:GOSSIP_SHOW()
Memory.InGossip = true
Private.UpdateAuraTracking()
end
function Module:GOSSIP_CLOSED()
Memory.InGossip = false
Private.UpdateAuraTracking()
end
function Module:COMBAT_LOG_EVENT_UNFILTERED()
local _, _, _, _, _, _, _, _, _, _, _, spellId = CombatLogGetCurrentEventInfo()
if spellId ~= SpellIds.MercenaryContractHorde and spellId ~= SpellIds.MercenaryContractAlliance then return end
self:ScheduleSendSyncData()
end
function Module:READY_CHECK(_, initiatedByName, duration)
Memory.lastReadyCheckTime = GetTime()
Memory.lastReadyCheckDuration = duration
-- occupy the heartbeat timeout so it won't send anything shortly after this ready check
Memory.readyCheckHeartbeatTimout = self:ScheduleTimer(function() Memory.readyCheckHeartbeatTimout = nil end, duration)
ForEachUnitData(function(data) data.readyState = ReadyCheckState.Waiting end)
local initiatedByData = GetPlayerDataByName(initiatedByName)
if initiatedByData then
initiatedByData.readyState = ReadyCheckState.Ready
end
Memory.readyCheckButtonTicker = self:ScheduleRepeatingTimer(function ()
local queueFrame = _G.BgcQueueFrame
if not queueFrame then return end
local timeLeft = max(0, ceil(Memory.lastReadyCheckTime + Memory.lastReadyCheckDuration - GetTime()))
queueFrame:UpdateReadyCheckButtonText(timeLeft)