-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathChatLootBidder.lua
1349 lines (1251 loc) · 47.6 KB
/
ChatLootBidder.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 ChatLootBidder = ChatLootBidderFrame
--if ChatLootBidder == nil then print("XML Error"); return end
local T = ChatLootBidder_i18n
local startSessionButton = getglobal(ChatLootBidder:GetName() .. "StartSession")
local endSessionButton = getglobal(ChatLootBidder:GetName() .. "EndSession")
local clearSessionButton = getglobal(ChatLootBidder:GetName() .. "ClearSession")
local gfind = string.gmatch or string.gfind
math.randomseed(time() * 100000000000)
for i=1,3 do
math.random(10000, 65000)
end
local function Roll()
return math.random(1, 100)
end
local addonName = "ChatLootBidder"
local addonTitle = GetAddOnMetadata(addonName, "Title")
local addonNotes = GetAddOnMetadata(addonName, "Notes")
local addonVersion = GetAddOnMetadata(addonName, "Version")
local addonAuthor = GetAddOnMetadata(addonName, "Author")
local chatPrefix = "<CL> "
local me = UnitName("player")
-- Roll tracking heavily borrowed from RollTracker: http://www.wowace.com/projects/rolltracker/
if GetLocale() == 'deDE' then RANDOM_ROLL_RESULT = "%s w\195\188rfelt. Ergebnis: %d (%d-%d)"
elseif RANDOM_ROLL_RESULT == nil then RANDOM_ROLL_RESULT = "%s rolls %d (%d-%d)" end -- Using english language https://vanilla-wow-archive.fandom.com/wiki/WoW_constants if not set
local rollRegex = string.gsub(string.gsub(string.gsub("%s rolls %d (%d-%d)", "([%(%)%-])", "%%%1"), "%%s", "%(.+%)"), "%%d", "%(%%d+%)")
ChatLootBidder_ChatFrame_OnEvent = ChatFrame_OnEvent
local softReserveSessionName = nil
local softReservesLocked = false
local session = nil
local sessionMode = nil
local stage = nil
local lastWhisper = nil
local function DefaultFalse(prop) return prop == true end
local function DefaultTrue(prop) return prop == nil or DefaultFalse(prop) end
local function LoadVariables()
ChatLootBidder_Store = ChatLootBidder_Store or {}
ChatLootBidder_Store.ItemValidation = DefaultTrue(ChatLootBidder_Store.ItemValidation)
ChatLootBidder_Store.RollAnnounce = DefaultTrue(ChatLootBidder_Store.RollAnnounce)
ChatLootBidder_Store.AutoStage = DefaultTrue(ChatLootBidder_Store.AutoStage)
ChatLootBidder_Store.BidAnnounce = DefaultFalse(ChatLootBidder_Store.BidAnnounce)
ChatLootBidder_Store.BidSummary = DefaultFalse(ChatLootBidder_Store.BidSummary)
ChatLootBidder_Store.BidChannel = ChatLootBidder_Store.BidChannel or "OFFICER"
ChatLootBidder_Store.SessionAnnounceChannel = ChatLootBidder_Store.SessionAnnounceChannel or "RAID"
ChatLootBidder_Store.WinnerAnnounceChannel = ChatLootBidder_Store.WinnerAnnounceChannel or "RAID_WARNING"
ChatLootBidder_Store.DebugLevel = ChatLootBidder_Store.DebugLevel or 0
ChatLootBidder_Store.TimerSeconds = ChatLootBidder_Store.TimerSeconds or 30
ChatLootBidder_Store.MaxBid = ChatLootBidder_Store.MaxBid or 5000
ChatLootBidder_Store.MinBid = ChatLootBidder_Store.MinBid or 1
ChatLootBidder_Store.AltPenalty = ChatLootBidder_Store.AltPenalty or 0
ChatLootBidder_Store.MinRarity = ChatLootBidder_Store.MinRarity or 4
ChatLootBidder_Store.MaxRarity = ChatLootBidder_Store.MaxRarity or 5
ChatLootBidder_Store.DefaultSessionMode = ChatLootBidder_Store.DefaultSessionMode or "MSOS" -- DKP | MSOS
ChatLootBidder_Store.BreakTies = DefaultTrue(ChatLootBidder_Store.BreakTies)
ChatLootBidder_Store.AddonVersion = addonVersion
ChatLootBidder_Store.SoftReserveSessions = ChatLootBidder_Store.SoftReserveSessions or {}
ChatLootBidder_Store.AutoRemoveSrAfterWin = DefaultTrue(ChatLootBidder_Store.AutoRemoveSrAfterWin)
ChatLootBidder_Store.AutoLockSoftReserve = DefaultTrue(ChatLootBidder_Store.AutoLockSoftReserve)
-- TODO: Make this custom per Soft Reserve session and make this the default when a new list is started
ChatLootBidder_Store.DefaultMaxSoftReserves = 1
end
local function Trim(str)
local _start, _end, _match = string.find(str, '^%s*(.-)%s*$')
return _match or ""
end
local function ToWholeNumber(numberString, default)
if default == nil then default = 0 end
if numberString == nil then return default end
local num = math.floor(tonumber(numberString) or default)
if default == num then return default end
return math.max(num, default)
end
local function Error(message)
DEFAULT_CHAT_FRAME:AddMessage("|cffbe5eff" .. chatPrefix .. "|cffff0000 "..message)
end
local function Message(message)
DEFAULT_CHAT_FRAME:AddMessage("|cffbe5eff".. chatPrefix .."|r "..message)
end
local function Debug(message)
if ChatLootBidder_Store.DebugLevel > 0 then
DEFAULT_CHAT_FRAME:AddMessage("|cffbe5eff".. chatPrefix .."|cffffff00 "..message)
end
end
local function Trace(message)
if ChatLootBidder_Store.DebugLevel > 1 then
DEFAULT_CHAT_FRAME:AddMessage("|cffbe5eff".. chatPrefix .."|cffffff00 "..message)
end
end
function ChatLootBidder:SetPropValue(propName, propValue, prefix)
if prefix then
propName = string.sub(propName, strlen(prefix)+1)
end
if ChatLootBidder_Store[propName] ~= nil then
ChatLootBidder_Store[propName] = propValue
local v = propValue
if type(v) == "boolean" then
v = v and "on" or "off"
end
Debug((T[propName] or propName) .. " is " .. tostring(v))
-- Special Handlers for specific properties here
if propName == "DefaultSessionMode" then
ChatLootBidder:RedrawStage()
end
else
Error(propName .. " is not initialized")
end
end
local ShowHelp = function()
Message("/loot - Open GUI Options")
Message("/loot stage [itm1] [itm2] - Stage item(s) for a future session start")
Message("/loot start [itm1] [itm2] [#timer_optional] - Start a session for item(s) + staged items(s)")
Message("/loot end - End a loot session and announce winner(s)")
Message("/loot sr load [name] - Load a SR list (by name, optional)")
Message(addonNotes .. " for detailed instructions, bugs, and suggestions")
Message("Written by " .. addonAuthor)
end
local function GetRaidIndex(unitName)
if UnitInRaid("player") == 1 then
for i = 1, GetNumRaidMembers() do
if UnitName("raid"..i) == unitName then
return i
end
end
end
return 0
end
local function IsInRaid(unitName)
return GetRaidIndex(unitName) ~= 0
end
local function IsRaidAssistant(unitName)
_, rank = GetRaidRosterInfo(GetRaidIndex(unitName));
return rank ~= 0
end
local function GetPlayerClass(unitName)
_, _, _, _, _, playerClass = GetRaidRosterInfo(GetRaidIndex(unitName));
return playerClass
end
local function IsMasterLooterSet()
local method, _ = GetLootMethod()
return method == "master"
end
local function IsStaticChannel(channel)
channel = channel == nil and nil or string.upper(channel)
return channel == "RAID" or channel == "RAID_WARNING" or channel == "SAY" or channel == "EMOTE" or channel == "PARTY" or channel == "GUILD" or channel == "OFFICER" or channel == "YELL"
end
local function IsTableEmpty(tbl)
if tbl == nil then return true end
local next = next
return next(tbl) == nil
end
-- Flatten a Player: [ SR1, SR2 ] structure into: { [Player, SR1], [Player, SR2] }
local function Flatten(tbl)
if tbl == nil then return {} end
local flattened = {}
local k, arr, v
for k, arr in pairs(tbl) do
for _,v in pairs(arr) do
table.insert(flattened, { k, v })
end
end
return flattened
end
-- Take a [[Player, SR1], [Player, SR2]] data structure and Map it: { Player: [ SR1, SR2 ] }
local function UnFlatten(tbl)
if tbl == nil then return {} end
local unflattened = {}
local arr
for _, arr in pairs(tbl) do
if unflattened[arr[1]] == nil then unflattened[arr[1]] = {} end
if arr[2] ~= nil then
table.insert(unflattened[Trim(arr[1])], Trim(arr[2]))
end
end
return unflattened
end
local function TableContains(table, element)
local value
for _,value in pairs(table) do
if value == element then
return true
end
end
return false
end
local function ParseItemNameFromItemLink(i)
local _, _ , n = string.find(i, "|h.(.-)]")
return n
end
local function TableLength(tbl)
if tbl == nil then return 0 end
local count = 0
for _ in pairs(tbl) do count = count + 1 end
return count
end
local function SplitBySpace(str)
local commandlist = { }
local command
for command in gfind(str, "[^ ]+") do
table.insert(commandlist, command)
end
return commandlist
end
local function GetKeysWhere(tbl, fn)
if tbl == nil then return {} end
local keys = {}
for key,value in pairs(tbl) do
if fn == nil or fn(key, value) then
table.insert(keys, key)
end
end
return keys
end
local function GetKeys(tbl)
return GetKeysWhere(tbl)
end
local function GetKeysSortedByValue(tbl)
local keys = GetKeys(tbl)
table.sort(keys, function(a, b)
return tbl[a] > tbl[b]
end)
return keys
end
local function SendToChatChannel(channel, message, prio)
if IsStaticChannel(channel) then
ChatThrottleLib:SendChatMessage(prio or "NORMAL", shortName, message, channel)
else
local channelIndex = GetChannelName(channel)
if channelIndex > 0 then
ChatThrottleLib:SendChatMessage(prio or "NORMAL", shortName, message, "CHANNEL", nil, channelIndex)
else
Error(channel .. " <Not In Channel> " .. message)
end
end
end
local function MessageBidSummaryChannel(message, force)
if ChatLootBidder_Store.BidSummary or force then
SendToChatChannel(ChatLootBidder_Store.BidChannel, message)
Trace("<SUMMARY>" .. message)
else
Debug("<SUMMARY>" .. message)
end
end
local function MessageBidChannel(message)
if ChatLootBidder_Store.BidAnnounce then
SendToChatChannel(ChatLootBidder_Store.BidChannel, message)
Trace("<BID>" .. message)
else
Debug("<BID>" .. message)
end
end
local function MessageWinnerChannel(message)
SendToChatChannel(ChatLootBidder_Store.WinnerAnnounceChannel, message)
Trace("<WIN>" .. message)
end
local function MessageStartChannel(message)
if IsInRaid(me) then
SendToChatChannel(ChatLootBidder_Store.SessionAnnounceChannel, message)
else
Message(message)
end
Trace("<START>" .. message)
end
local function SendResponse(message, bidder)
if bidder == me then
Message(message)
else
ChatThrottleLib:SendChatMessage("ALERT", shortName, message, "WHISPER", nil, bidder)
end
end
local function AppendNote(note)
return (note == nil or note == "") and "" or " [ " .. note .. " ]"
end
local function PlayerWithClassColor(unit)
if RAID_CLASS_COLORS and pfUI then -- pfUI loads class colors
local unitClass = GetPlayerClass(unit)
local colorStr = RAID_CLASS_COLORS[unitClass].colorStr
if colorStr and string.len(colorStr) == 8 then
return "\124c" .. colorStr .. "\124Hplayer:" .. unit .. "\124h" .. unit .. "\124h\124r"
end
end
return unit
end
local function Srs(n)
local n = n or softReserveSessionName
local srs = ChatLootBidder_Store.SoftReserveSessions[n]
if srs ~= nil then return srs end
ChatLootBidder_Store.SoftReserveSessions[n] = {}
return ChatLootBidder_Store.SoftReserveSessions[n];
end
function ChatLootBidder:LoadedSoftReserveSession()
if softReserveSessionName then
return unpack({softReserveSessionName, ChatLootBidder_Store.SoftReserveSessions[softReserveSessionName]})
end
return unpack({nil, nil})
end
local function HandleSrRemove(bidder, item)
local itemName = ParseItemNameFromItemLink(item)
if Srs()[bidder] == nil then
Srs()[bidder] = {}
end
local sr = Srs()[bidder]
local i, v
for i,v in pairs(sr) do
if v == itemName then
table.remove(sr,i)
SendResponse("You are no longer reserving: " .. itemName, bidder)
return
end
end
end
local function realAmt(amt, real)
if real ~= nil and amt ~= real then
return amt .. "(" .. real .. ")"
end
return amt
end
local function BidSummary(announceWinners)
if session == nil then
Error("There is no existing session")
return
end
local summaries = {}
for item,itemSession in pairs(session) do
local sr = itemSession["sr"] or {}
local ms = itemSession["ms"] or {}
local ofs = itemSession["os"] or {}
local roll = itemSession["roll"]
local cancel = itemSession["cancel"] or {}
local notes = itemSession["notes"] or {}
local real = itemSession["real"] or {}
local needsRoll = IsTableEmpty(sr) and IsTableEmpty(ms) and IsTableEmpty(ofs)
if announceWinners and needsRoll then
for bidder,r in roll do
if r == -1 then
r = Roll()
roll[bidder] = r
if ChatLootBidder_Store.RollAnnounce then
MessageStartChannel(PlayerWithClassColor(bidder) .. " rolls " .. r .. " (1-100) for " .. item)
else
SendResponse("You roll " .. r .. " (1-100) for " .. item, bidder)
end
end
end
end
local winner = {}
local winnerBid = nil
local winnerTier = nil
local header = true
local summary = {}
if not IsTableEmpty(sr) then
local sortedMainspecKeys = GetKeysSortedByValue(sr)
for k,bidder in pairs(sortedMainspecKeys) do
if IsTableEmpty(winner) then table.insert(summary, item) end
if header then table.insert(summary, "- Soft Reserve:"); header = false end
local bid = sr[bidder]
if IsTableEmpty(winner) then table.insert(winner, bidder); winnerBid = bid; winnerTier = "sr"
elseif not IsTableEmpty(winner) and winnerTier == "sr" and winnerBid == bid then table.insert(winner, bidder) end
table.insert(summary, "-- " .. PlayerWithClassColor(bidder) .. ": " .. bid)
end
end
header = true
if not IsTableEmpty(ms) then
local sortedMainspecKeys = GetKeysSortedByValue(ms)
for k,bidder in pairs(sortedMainspecKeys) do
if cancel[bidder] == nil then
if IsTableEmpty(winner) then table.insert(summary, item) end
if header then table.insert(summary, "- Main Spec:"); header = false end
local bid = ms[bidder]
if IsTableEmpty(winner) then table.insert(winner, bidder); winnerBid = bid; winnerTier = "ms"
elseif not IsTableEmpty(winner) and winnerTier == "ms" and winnerBid == bid then table.insert(winner, bidder) end
table.insert(summary, "-- " .. PlayerWithClassColor(bidder) .. ": " .. realAmt(bid, real[bidder]) .. AppendNote(notes[bidder]))
end
end
end
header = true
if not IsTableEmpty(ofs) then
local sortedOffspecKeys = GetKeysSortedByValue(ofs)
for k,bidder in pairs(sortedOffspecKeys) do
if cancel[bidder] == nil and ms[bidder] == nil then
if IsTableEmpty(winner) then table.insert(summary, item) end
if header then table.insert(summary, "- Off Spec:"); header = false end
local bid = ofs[bidder]
if IsTableEmpty(winner) then table.insert(winner, bidder); winnerBid = bid; winnerTier = "os"
elseif not IsTableEmpty(winner) and winnerTier == "os" and winnerBid == bid then table.insert(winner, bidder) end
table.insert(summary, "-- " .. PlayerWithClassColor(bidder) .. ": " .. realAmt(bid, real[bidder]) .. AppendNote(notes[bidder]))
end
end
end
header = true
if not IsTableEmpty(roll) then
local sortedRollKeys = GetKeysSortedByValue(roll)
for k,bidder in pairs(sortedRollKeys) do
if cancel[bidder] == nil and ms[bidder] == nil and ofs[bidder] == nil then
if IsTableEmpty(winner) then table.insert(summary, item) end
if header then table.insert(summary, "- Rolls:"); header = false end
local bid = roll[bidder]
if IsTableEmpty(winner) then table.insert(winner, bidder); winnerBid = bid; winnerTier = "roll"
elseif not IsTableEmpty(winner) and winnerTier == "roll" and winnerBid == bid then table.insert(winner, bidder) end
table.insert(summary, "-- " .. PlayerWithClassColor(bidder) .. ": " .. bid .. AppendNote(notes[bidder]))
end
end
end
local breakTies = ChatLootBidder_Store.BreakTies or sessionMode ~= "DKP"
if getn(winner) > 1 then
if sessionMode == "DKP" then
MessageWinnerChannel(table.concat(winner, ", ") .. " tied with a ".. string.upper(winnerTier) .. " bid of " .. winnerBid .. ", rolling it off:")
else
MessageWinnerChannel(table.concat(winner, ", ") .. " bid ".. string.upper(winnerTier) ..", rolling it off:")
end
while getn(winner) > 1 and breakTies do
local winningRoll = 0
for _,bidder in winner do
local r = roll[bidder]
if r == -1 or r == nil then
r = Roll()
roll[bidder] = r
MessageWinnerChannel(PlayerWithClassColor(bidder) .. " rolls " .. r .. " (1-100) for " .. item)
else
r = roll[bidder]
MessageWinnerChannel(PlayerWithClassColor(bidder) .. " already rolled " .. r .. " (1-100) for " .. item)
end
if winningRoll < r then winningRoll = r end
end
local newWinner = {}
for _,bidder in winner do
if roll[bidder] == winningRoll then
table.insert(newWinner, bidder)
end
roll[bidder] = -1
end
winner = newWinner
end
end
if IsTableEmpty(winner) then
if announceWinners then MessageStartChannel("No bids received for " .. item) end
table.insert(summary, item .. ": No Bids")
elseif announceWinners then
local winnerMessage = table.concat(winner, ", ") .. (getn(winner) > 1 and " tie for " or " wins ") .. item
if sessionMode == "DKP" then
winnerMessage = winnerMessage .. " with a " .. (winnerTier == "roll" and "roll of " or (string.upper(winnerTier) .. " bid of "))
if getn(winner) == 1 and winnerTier ~= "roll" then
winnerMessage = winnerMessage .. realAmt(winnerBid, real[winner[1]])
else
winnerMessage = winnerMessage .. winnerBid
end
else
winnerMessage = winnerMessage .. " for " .. string.upper(winnerTier)
end
MessageWinnerChannel(winnerMessage)
end
table.insert(summaries, summary)
if winnerTier == "sr" and ChatLootBidder_Store.AutoRemoveSrAfterWin then
HandleSrRemove(winner[1], item)
end
end
for _,summary in summaries do
for _,line in summary do
MessageBidSummaryChannel(line)
end
end
end
function ChatLootBidder:End()
ChatThrottleLib:SendAddonMessage("BULK", "NotChatLootBidder", "endSession=1", "RAID")
BidSummary(true)
session = nil
sessionMode = nil
stage = nil
endSessionButton:Hide()
ChatLootBidder:Hide()
end
local function GetItemLinks(str)
local itemLinks = {}
local _start, _end, _lastEnd = nil, -1, -1
while true do
_start, _end = string.find(str, "|c.-|H.-|h|r", _end + 1)
if _start == nil then
return itemLinks, _lastEnd
end
_lastEnd = _end
table.insert(itemLinks, string.sub(str, _start, _end))
end
end
function ChatLootBidder:Start(items, timer, mode)
if not IsRaidAssistant(me) then Error("You must be a raid leader or assistant in a raid to start a loot session"); return end
if not IsMasterLooterSet() then Error("Master Looter must be set to start a loot session"); return end
local mode = mode ~= nil and mode or ChatLootBidder_Store.DefaultSessionMode
if session ~= nil then ChatLootBidder:End() end
local stageList = GetKeysWhere(stage, function(k,v) return v == true end)
if items == nil then
items = stageList
else
for _, v in pairs(stageList) do
table.insert(items, v)
end
end
if IsTableEmpty(items) then Error("You must provide at least a single item to bid on"); return end
ChatLootBidder:EndSessionButtonShown()
session = {}
sessionMode = mode
stage = nil
if ChatLootBidder_Store.AutoLockSoftReserve and softReserveSessionName ~= nil and not softReservesLocked then
softReservesLocked = true
MessageStartChannel("Soft Reserves for " .. softReserveSessionName .. " are now LOCKED")
end
local srs = mode == "MSOS" and softReserveSessionName ~= nil and ChatLootBidder_Store.SoftReserveSessions[softReserveSessionName] or {}
local startChannelMessage = {}
table.insert(startChannelMessage, "Bid on the following items")
table.insert(startChannelMessage, "-----------")
local bidAddonMessage = "mode=" .. mode .. ",items="
local exampleItem
for k,i in pairs(items) do
local itemName = ParseItemNameFromItemLink(i)
local srsOnItem = GetKeysWhere(srs, function(player, playerSrs) return IsInRaid(player) and TableContains(playerSrs, itemName) end)
local srLen = TableLength(srsOnItem)
session[i] = {}
session[i]["cancel"] = {}
session[i]["roll"] = {}
session[i]["real"] = {}
if srLen == 0 then
exampleItem = i
table.insert(startChannelMessage, i)
bidAddonMessage = bidAddonMessage .. string.gsub(i, ",", "~~~")
session[i]["ms"] = {}
session[i]["os"] = {}
session[i]["notes"] = {}
else
table.insert(startChannelMessage, i .. " SR (" .. srLen .. ")")
session[i]["sr"] = {}
for _,sr in pairs(srsOnItem) do
session[i]["sr"][sr] = 1
session[i]["roll"][sr] = -1
if srLen > 1 then
SendResponse("Your Soft Reserve for " .. i .. " is contested by " .. (srLen-1) .. " other player" .. (srLen == 2 and "" or "s") .. ". '/random' now to record your own roll or do nothing for the addon to roll for you at the end of the session.", sr)
else
SendResponse("You won " .. i .. " with your Soft Reserve!", srsOnItem[1])
end
end
end
end
table.insert(startChannelMessage, "-----------")
if exampleItem then
table.insert(startChannelMessage, "/w " .. PlayerWithClassColor(me) .. " " .. exampleItem .. " ms/os/roll" .. (mode == "DKP" and " #bid" or "") .. " [optional-note]")
local l
for _, l in pairs(startChannelMessage) do
MessageStartChannel(l)
end
if timer == nil or timer < 0 then timer = ChatLootBidder_Store.TimerSeconds end
if BigWigs and timer > 0 then BWCB(timer, "Bidding Ends") end
ChatThrottleLib:SendAddonMessage("BULK", "NotChatLootBidder", bidAddonMessage, "RAID")
else
-- Everything was SR'd - just end now
ChatLootBidder:End()
end
end
function ChatLootBidder:Clear(stageOnly)
if session == nil or stageOnly then
if IsTableEmpty(stage) then
Message("There is no active session or stage")
else
stage = nil
Message("Cleared the stage")
ChatLootBidder:RedrawStage()
end
else
session = nil
Message("Cleared the current loot session")
end
end
function ChatLootBidder:Unstage(item, redraw)
stage[item] = false
if redraw then ChatLootBidder:RedrawStage() end
end
function ChatLootBidder:HandleSrDelete(providedName)
if softReserveSessionName == nil and providedName == nil then
Error("No Soft Reserve session loaded or provided for deletion")
elseif providedName == nil then
ChatLootBidder_Store.SoftReserveSessions[softReserveSessionName] = nil
Message("Deleted currently loaded Soft Reserve session: " .. softReserveSessionName)
softReserveSessionName = nil
elseif ChatLootBidder_Store.SoftReserveSessions[providedName] == nil then
Error("No Soft Reserve session exists with the label: " .. providedName)
else
ChatLootBidder_Store.SoftReserveSessions[providedName] = nil
Message("Deleted Soft Reserve session: " .. providedName)
end
if providedName == nil or providedName == softReserveSessionName then
SrEditFrame:Hide()
end
end
local function craftName(appender)
return date("%y-%m-%d") .. (appender == 0 and "" or ("-"..appender))
end
function ChatLootBidder:HandleSrAddDefault()
local appender = 0
while ChatLootBidder_Store.SoftReserveSessions[craftName(appender)] ~= nil do
appender = appender + 1
end
softReserveSessionName = craftName(appender)
local srs = Srs()
Message("New Soft Reserve list [" .. softReserveSessionName .. "] loaded")
SrEditFrame:Hide()
ChatLootBidderOptionsFrame_Init(softReserveSessionName)
end
function ChatLootBidder:HandleSrLoad(providedName)
if providedName then
softReserveSessionName = providedName
local srs = Srs()
ValidateFixAndWarn(srs)
Message("Soft Reserve list [" .. softReserveSessionName .. "] loaded with " .. TableLength(srs) .. " players with soft reserves")
SrEditFrame:Hide()
ChatLootBidderOptionsFrame_Init(softReserveSessionName)
else
ChatLootBidder:HandleSrAddDefault()
end
end
function ChatLootBidder:HandleSrUnload()
if softReserveSessionName == nil then
Error("No Soft Reserve session loaded")
else
Message("Unloaded Soft Reserve session: " .. softReserveSessionName)
softReserveSessionName = nil
end
ChatLootBidderOptionsFrame_Reload()
SrEditFrame:Hide()
end
function ChatLootBidder:HandleSrInstructions()
MessageStartChannel("Set your SR: /w " .. PlayerWithClassColor(me) .. " sr [item-link or exact-item-name]")
MessageStartChannel("Get your current SR: /w " .. PlayerWithClassColor(me) .. " sr")
MessageStartChannel("Clear your current SR: /w " .. PlayerWithClassColor(me) .. " sr clear")
end
function ChatLootBidder:HandleSrShow()
if softReserveSessionName == nil then
Error("No Soft Reserve session loaded")
else
local srs = Srs()
if IsTableEmpty(srs) then
Error("No Soft Reserves placed yet")
return
end
MessageStartChannel("Soft Reserve Bids:")
local keys = GetKeys(srs)
table.sort(keys)
local player
for _, player in pairs(keys) do
local sr = srs[player]
if not IsTableEmpty(sr) then
local msg = PlayerWithClassColor(player) .. ": " .. table.concat(sr, ", ")
if IsInRaid(player) then
MessageStartChannel(msg)
else
Message(msg)
end
end
end
end
end
local function EncodeSemicolon()
local encoded = ""
for k,v in pairs(Srs()) do
encoded = encoded .. k
for _, sr in pairs(v) do
encoded = encoded .. " ; " .. sr
end
encoded = encoded .. "\n"
end
return encoded
end
local function EncodeRaidResFly()
local encoded = ""
local flat = Flatten(Srs())
for _,arr in flat do
-- [00:00]Autozhot: Autozhot - Band of Accuria
encoded = (encoded or "") .. "[00:00]"..arr[1]..": "..arr[1].." - "..arr[2].."\n"
end
return encoded
end
-- This is the most simple pretty print function possible applciable to { key : [value, value, value] } structures only
local function PrettyPrintJson(encoded)
-- The default empty structure should be an object, not an array
if encoded == "[]" then return "{}" end
encoded = string.gsub(encoded, "{", "{\n")
encoded = string.gsub(encoded, "}", "\n}")
encoded = string.gsub(encoded, "],", "],\n")
return encoded
end
local function HandleChannel(prop, channel)
if IsStaticChannel(channel) then channel = string.upper(channel) end
ChatLootBidder_Store[prop] = channel
Message(T[prop] .. " announce channel set to " .. channel)
getglobal("ChatLootBidderOptionsFrame" .. prop):SetValue(channel)
end
function ChatLootBidder:HandleEncoding(encodingType)
if softReserveSessionName == nil then
Error("No Soft Reserve list is loaded")
else
local encoded
if encodingType == "csv" then
encoded = csv:toCSV(Flatten(Srs()))
elseif encodingType == "json" then
encoded = PrettyPrintJson(json.encode(Srs()))
elseif encodingType == "semicolon" then
encoded = EncodeSemicolon()
elseif encodingType == "raidresfly" then
encoded = EncodeRaidResFly()
end
if not SrEditFrame:IsVisible() then
SrEditFrame:Show()
elseif SrEditFrameHeaderString:GetText() == encodingType then
SrEditFrame:Hide()
end
SrEditFrameText:SetText(encoded)
SrEditFrameHeaderString:SetText(encodingType)
end
end
function ChatLootBidder:ToggleSrLock(command)
if softReserveSessionName == nil then
Error("No Soft Reserve session loaded")
else
if command then
softReservesLocked = command == "lock"
else
softReservesLocked = not softReservesLocked
end
MessageStartChannel("Soft Reserves for " .. softReserveSessionName .. " are now " .. (softReservesLocked and "LOCKED" or "UNLOCKED"))
end
end
function ChatLootBidder:IsLocked()
return softReservesLocked
end
local InitSlashCommands = function()
SLASH_ChatLootBidder1, SLASH_ChatLootBidder2 = "/l", "/loot"
SlashCmdList["ChatLootBidder"] = function(message)
local commandlist = SplitBySpace(message)
if commandlist[1] == nil then
if ChatLootBidderOptionsFrame:IsVisible() then
ChatLootBidderOptionsFrame:Hide()
else
ChatLootBidderOptionsFrame:Show()
end
elseif commandlist[1] == "help" or commandlist[1] == "info" then
ShowHelp()
elseif commandlist[1] == "sr" then
if ChatLootBidder_Store.DefaultSessionMode ~= "MSOS" then
Error("You need to be in MSOS mode to modify Soft Reserve sessions. `/loot` to change modes.")
return
end
local subcommand = commandlist[2]
if commandlist[2] == "load" then
ChatLootBidder:HandleSrLoad(commandlist[3])
elseif commandlist[2] == "unload" then
HandleSrUnload()
elseif commandlist[2] == "delete" then
ChatLootBidder:HandleSrDelete(commandlist[3])
elseif commandlist[2] == "show" then
ChatLootBidder:HandleSrShow()
elseif commandlist[2] == "csv" or commandlist[2] == "json" or commandlist[2] == "semicolon" or commandlist[2] == "raidresfly" then
ChatLootBidder:HandleEncoding(commandlist[2])
elseif commandlist[2] == "lock" or commandlist[2] == "unlock" then
ChatLootBidder:ToggleSrLock(commandlist[2])
elseif commandlist[2] == "instructions" then
ChatLootBidder:HandleSrInstructions()
else
Error("Unknown 'sr' subcommand: " .. (commandlist[2] == nil and "nil" or commandlist[2]))
Error("Valid values are: load, unload, delete, show, lock, unlock, json, semicolon, raidresfly, csv, instructions")
end
elseif commandlist[1] == "debug" then
ChatLootBidder_Store.DebugLevel = ToWholeNumber(commandlist[2])
Message("Debug level set to " .. ChatLootBidder_Store.DebugLevel)
elseif commandlist[1] == "bid" and commandlist[2] then
HandleChannel("BidChannel", commandlist[2])
elseif commandlist[1] == "session" and commandlist[2] then
HandleChannel("SessionAnnounceChannel", commandlist[2])
elseif commandlist[1] == "win" and commandlist[2] then
HandleChannel("WinnerAnnounceChannel", commandlist[2])
elseif commandlist[1] == "end" then
ChatLootBidder:End()
elseif commandlist[1] == "clear" then
if commandlist[2] == nil then
ChatLootBidder:Clear()
elseif stage == nil then
Error("The stage is empty")
else
local itemLinks = GetItemLinks(message)
for _, item in pairs(itemLinks) do
ChatLootBidder:Unstage(item)
end
end
ChatLootBidder:RedrawStage()
elseif commandlist[1] == "stage" then
local itemLinks = GetItemLinks(message)
for _, item in pairs(itemLinks) do
local item = item
ChatLootBidder:Stage(item, true)
end
ChatLootBidder:RedrawStage()
elseif commandlist[1] == "summary" then
BidSummary()
elseif commandlist[1] == "start" then
local itemLinks = GetItemLinks(message)
local optionalTimer = ToWholeNumber(commandlist[getn(commandlist)], -1)
ChatLootBidder:Start(itemLinks, optionalTimer)
end
end
end
local function LoadText()
local k,v,g
for k,v in pairs(T) do
if type(k) == "string" then
g = getglobal("ChatLootBidderOptionsFrame"..k.."Text")
if g then g:SetText(v) end
end
end
end
local function LoadValues()
local k,v,g,t
for k,v in pairs(ChatLootBidder_Store) do
t = type(v)
g = getglobal("ChatLootBidderOptionsFrame"..k)
if g and g.SetChecked and t == "boolean" then
g:SetChecked(v)
elseif g and k == "DefaultSessionMode" then
g:SetValue(v == "MSOS" and 1 or 0)
elseif g and g.SetValue and (t == "string" or t == "number") then
g:SetValue(v)
else
Trace(k .. " <noGui> " .. tostring(v))
end
end
end
local function IsValidTier(tier)
return tier == "ms" or tier == "os" or tier == "roll" or tier == "cancel"
end
local function InvalidBidSyntax(item)
local bidExample = " " .. (ChatLootBidder_Store.MinBid + 9)
return "Invalid bid syntax for " .. item .. ". The proper format is: '[item-link] ms" .. (sessionMode == "DKP" and bidExample or "") .. "' or '[item-link] os" .. (sessionMode == "DKP" and bidExample or "") .. "' or '[item-link] roll'"
end
local function of(amt, real)
if sessionMode == "DKP" then
return " of " .. realAmt(amt, real)
end
return ""
end
local function HandleSrQuery(bidder)
local sr = Srs(softReserveSessionName)[bidder]
local msg = "Your Soft Reserve is currently " .. (sr == nil and "not set" or ("[ " .. table.concat(sr, ", ") .. " ]"))
if softReservesLocked then
msg = msg .. " LOCKED"
end
SendResponse(msg, bidder)
end
local function AtlasLootLoaded()
return (AtlasLoot_Data and AtlasLoot_Data["AtlasLootItems"]) ~= nil
end
-- Ex/
-- AtlasLoot_Data["AtlasLootItems"]["BWLRazorgore"][1]
-- { 16925, "INV_Belt_22", "=q4=Belt of Transcendence", "=ds=#s10#, #a1# =q9=#c5#", "11%" }
local function ValidateItemName(n)
if not ChatLootBidder_Store.ItemValidation or not AtlasLootLoaded() then return unpack({-1, n, -1, "", ""}) end
for raidBossKey,raidBoss in AtlasLoot_Data["AtlasLootItems"] do
for _,dataSet in raidBoss do
if dataSet then
local itemNumber, icon, nameQuery, _, dropRate = unpack(dataSet)
if nameQuery then
local _start, _end, _quality, _name = string.find(nameQuery, '^=q(%d)=(.-)$')
if _name and string.lower(_name) == string.lower(n) then
return unpack({itemNumber, _name, _quality, raidBossKey, dropRate})
end
end
end
end
end
return nil
end
local function HandleSrAdd(bidder, itemName)
itemName = Trim(itemName)
if Srs(softReserveSessionName)[bidder] == nil then
Srs(softReserveSessionName)[bidder] = {}
end
local sr = Srs(softReserveSessionName)[bidder]
local itemNumber, nameFix, _quality, raidBoss, dropRate = ValidateItemName(itemName)
if itemNumber == nil then
SendResponse(itemName .. " does not appear to be a valid item name (AtlasLoot). If this is incorrect, the Loot Master will need to manually input the item name or disable item validation.", bidder)
else
if nameFix ~= itemName then
SendResponse(itemName .. " fixed to " .. nameFix)
itemName = nameFix
end
table.insert(sr, itemName)
if TableLength(sr) > ChatLootBidder_Store.DefaultMaxSoftReserves then
local pop = table.remove(sr, 1)
if not TableContains(sr, pop) then
SendResponse("You are no longer reserving: " .. pop, bidder)
end
end
end
ChatLootBidderOptionsFrame_Reload()
end
function ChatFrame_OnEvent(event)
-- Non-whispers are ignored; Don't react to duplicate whispers (multiple windows, usually)
if event ~= "CHAT_MSG_WHISPER" or lastWhisper == (arg1 .. arg2) then
ChatLootBidder_ChatFrame_OnEvent(event)
return
end
lastWhisper = arg1 .. arg2
local bidder = arg2
-- Parse string for a item links
local items, itemIndexEnd = GetItemLinks(arg1)
local item = items[1]
-- Handle SR Bids
local commandlist = SplitBySpace(arg1)
if (softReserveSessionName ~= nil and string.lower(commandlist[1] or "") == "sr") then
if not IsInRaid(bidder) then
SendResponse("You must be in the raid to place a Soft Reserve", bidder)
return
end
if softReserveSessionName == nil then
SendResponse("There is no Soft Reserve session loaded", bidder)
return
end
-- If we're manually editing the SRs, treat it like being locked for incoming additions
local softReservesLocked = softReservesLocked or SrEditFrame:IsVisible()
if TableLength(commandlist) == 1 or softReservesLocked then
-- skip, query do the query at the end