-
Notifications
You must be signed in to change notification settings - Fork 15
/
Functions.lua
576 lines (491 loc) · 20.8 KB
/
Functions.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
--[=[
Dumping logical functions here, make the code of the main file smaller
--]=]
if (not LIB_OPEN_RAID_CAN_LOAD) then
return
end
local openRaidLib = LibStub:GetLibrary("LibOpenRaid-1.0")
-- TWW compat
-- TODO: Remove when TWW is released
local GetItemInfo = GetItemInfo or C_Item.GetItemInfo
local CONST_FRACTION_OF_A_SECOND = 0.01
local CONST_COOLDOWN_TYPE_OFFENSIVE = 1
local CONST_COOLDOWN_TYPE_DEFENSIVE_PERSONAL = 2
local CONST_COOLDOWN_TYPE_DEFENSIVE_TARGET = 3
local CONST_COOLDOWN_TYPE_DEFENSIVE_RAID = 4
local CONST_COOLDOWN_TYPE_UTILITY = 5
local CONST_COOLDOWN_TYPE_INTERRUPT = 6
local CONST_COOLDOWN_TYPE_ITEMHEAL = 10
local CONST_COOLDOWN_TYPE_ITEMPOWER = 11
local CONST_COOLDOWN_TYPE_ITEMUTIL = 12
local CONST_COOLDOWN_TYPE_CROWDCONTROL = 8
--hold spellIds and which custom caches the spell is in
--map[spellId] = map[filterName] = true
local spellsWithCustomFiltersCache = {}
--simple non recursive table copy
function openRaidLib.TCopy(tableToReceive, tableToCopy)
if (not tableToCopy) then
print(debugstack())
end
for key, value in pairs(tableToCopy) do
tableToReceive[key] = value
end
end
--find the normalized percent of the value in the range. e.g range of 200-400 and a value of 250 result in 0.25
--from details! framework
function openRaidLib.GetRangePercent(minValue, maxValue, value)
return (value - minValue) / max((maxValue - minValue), 0.0000001)
end
--transform a table index into a string dividing values with a comma
--@table: an indexed table with unknown size
function openRaidLib.PackTable(table)
local tableSize = #table
local newString = "" .. tableSize .. ","
for i = 1, tableSize do
newString = newString .. table[i] .. ","
end
newString = newString:gsub(",$", "")
return newString
end
function openRaidLib.PackTableAndSubTables(table)
local totalSize = 0
local subTablesAmount = #table
for i = 1, subTablesAmount do
totalSize = totalSize + #table[i]
end
local newString = "" .. totalSize .. ","
for i = 1, subTablesAmount do
local subTable = table[i]
for subIndex = 1, #subTable do
newString = newString .. subTable[subIndex] .. ","
end
end
newString = newString:gsub(",$", "")
return newString
end
--return is a number is almost equal to another within a tolerance range
function openRaidLib.isNearlyEqual(value1, value2, tolerance)
tolerance = tolerance or CONST_FRACTION_OF_A_SECOND
return abs(value1 - value2) <= tolerance
end
--return true if the lib is allowed to receive comms from other players
function openRaidLib.IsCommAllowed()
return IsInGroup() or IsInRaid()
end
--stract some indexes of a table
local selectIndexes = function(table, startIndex, amountIndexes, zeroIfNil)
local values = {}
for i = startIndex, startIndex+amountIndexes do
values[#values+1] = tonumber(table[i]) or (zeroIfNil and 0) or table[i]
end
return values
end
--transform a string table into a regular table
--@table: a table with unknown values
--@index: where in the table is the information we want
--@isPair: if true treat the table as pairs(), ipairs() otherwise
--@valueAsTable: return {value1, value2, value3}
--@amountOfValues: for the parameter above
function openRaidLib.UnpackTable(table, index, isPair, valueIsTable, amountOfValues)
local result = {}
local reservedIndexes = table[index]
if (not reservedIndexes) then
return result
end
local indexStart = index+1
local indexEnd = reservedIndexes+index
if (isPair) then
amountOfValues = amountOfValues or 2
for i = indexStart, indexEnd, amountOfValues do
if (valueIsTable) then
local key = tonumber(table[i])
local values = selectIndexes(table, i+1, max(amountOfValues-2, 1), true)
result[key] = values
else
local key = tonumber(table[i])
local value = tonumber(table[i+1])
result[key] = value
end
end
else
if (valueIsTable) then
for i = indexStart, indexEnd, amountOfValues do
local values = selectIndexes(table, i, amountOfValues - 1)
tinsert(result, values)
end
else
for i = indexStart, indexEnd do
local value = tonumber(table[i])
result[#result+1] = value
end
end
end
return result
end
--returns if the player is in group
function openRaidLib.IsInGroup()
local inParty = IsInGroup()
local inRaid = IsInRaid()
return inParty or inRaid
end
---return a table with unitName as keys and true as value
---@return table<string, boolean>
function openRaidLib.GetPlayersInTheGroup()
local playersInTheGroup = {}
if (IsInRaid()) then
for i = 1, GetNumGroupMembers() do
local unitName = GetUnitName("raid"..i, true)
if (unitName) then
playersInTheGroup[unitName] = true
end
end
elseif (IsInGroup()) then
for i = 1, GetNumGroupMembers() - 1 do
local unitName = GetUnitName("party"..i, true)
if (unitName) then
playersInTheGroup[unitName] = true
end
end
playersInTheGroup[UnitName("player")] = true
end
return playersInTheGroup
end
function openRaidLib.UpdateUnitIDCache()
openRaidLib.UnitIDCache = {}
if (IsInRaid()) then
for i = 1, GetNumGroupMembers() do
local unitName = GetUnitName("raid"..i, true)
if (unitName) then
openRaidLib.UnitIDCache[unitName] = "raid"..i
end
end
elseif (IsInGroup()) then
for i = 1, GetNumGroupMembers() - 1 do
local unitName = GetUnitName("party"..i, true)
if (unitName) then
openRaidLib.UnitIDCache[unitName] = "party"..i
end
end
end
openRaidLib.UnitIDCache[UnitName("player")] = "player"
end
function openRaidLib.GetUnitID(playerName)
return openRaidLib.UnitIDCache[playerName] or playerName
end
--report: "filterStringToCooldownType doesn't include the new filters."
--answer: custom filter does not have a cooldown type, it is a mesh of spells
local filterStringToCooldownType = {
["defensive-raid"] = CONST_COOLDOWN_TYPE_DEFENSIVE_RAID,
["defensive-target"] = CONST_COOLDOWN_TYPE_DEFENSIVE_TARGET,
["defensive-personal"] = CONST_COOLDOWN_TYPE_DEFENSIVE_PERSONAL,
["ofensive"] = CONST_COOLDOWN_TYPE_OFFENSIVE,
["utility"] = CONST_COOLDOWN_TYPE_UTILITY,
["interrupt"] = CONST_COOLDOWN_TYPE_INTERRUPT,
["itemutil"] = CONST_COOLDOWN_TYPE_ITEMUTIL,
["itemheal"] = CONST_COOLDOWN_TYPE_ITEMHEAL,
["itempower"] = CONST_COOLDOWN_TYPE_ITEMPOWER,
["crowdcontrol"] = CONST_COOLDOWN_TYPE_CROWDCONTROL,
}
local filterStringToCooldownTypeReverse = {
[CONST_COOLDOWN_TYPE_DEFENSIVE_RAID] = "defensive-raid",
[CONST_COOLDOWN_TYPE_DEFENSIVE_TARGET] = "defensive-target",
[CONST_COOLDOWN_TYPE_DEFENSIVE_PERSONAL] = "defensive-personal",
[CONST_COOLDOWN_TYPE_OFFENSIVE] = "ofensive",
[CONST_COOLDOWN_TYPE_UTILITY] = "utility",
[CONST_COOLDOWN_TYPE_INTERRUPT] = "interrupt",
[CONST_COOLDOWN_TYPE_ITEMUTIL] = "itemutil",
[CONST_COOLDOWN_TYPE_ITEMHEAL] = "itemheal",
[CONST_COOLDOWN_TYPE_ITEMPOWER] = "itempower",
[CONST_COOLDOWN_TYPE_CROWDCONTROL] = "crowdcontrol",
}
local removeSpellFromCustomFilterCache = function(spellId, filterName)
local spellFilterCache = spellsWithCustomFiltersCache[spellId]
if (spellFilterCache) then
spellFilterCache[filterName] = nil
end
end
local addSpellToCustomFilterCache = function(spellId, filterName)
local spellFilterCache = spellsWithCustomFiltersCache[spellId]
if (not spellFilterCache) then
spellFilterCache = {}
spellsWithCustomFiltersCache[spellId] = spellFilterCache
end
spellFilterCache[filterName] = true
end
local getSpellCustomFiltersFromCache = function(spellId)
local spellFilterCache = spellsWithCustomFiltersCache[spellId]
local result = {}
if (spellFilterCache) then
for filterName in pairs(spellFilterCache) do
result[filterName] = true
end
end
return result
end
--LIB_OPEN_RAID_COOLDOWNS_INFO store all registered cooldowns in the file ThingsToMantain_<game version>
function openRaidLib.CooldownManager.GetAllRegisteredCooldowns()
return LIB_OPEN_RAID_COOLDOWNS_INFO
end
function openRaidLib.CooldownManager.GetCooldownInfo(spellId)
return openRaidLib.CooldownManager.GetAllRegisteredCooldowns()[spellId]
end
--return a map of filter names which the spell is in, map: {[filterName] = true}
--API Call documented in the docs.txt as openRaidLib.GetSpellFilters() the declaration is on the main file of the lib
function openRaidLib.CooldownManager.GetSpellFilters(spellId, defaultFilterOnly, customFiltersOnly)
local result = {}
if (not customFiltersOnly) then
local thisCooldownInfo = openRaidLib.CooldownManager.GetCooldownInfo(spellId)
local cooldownTypeFilter = filterStringToCooldownTypeReverse[thisCooldownInfo.type]
if (cooldownTypeFilter) then
result[cooldownTypeFilter] = true
end
end
if (defaultFilterOnly) then
return result
end
local customFilters = getSpellCustomFiltersFromCache(spellId)
for filterName in pairs(customFilters) do
result[filterName] = true
end
return result
end
function openRaidLib.CooldownManager.DoesSpellPassFilters(spellId, filters)
--table with information about a single cooldown
local thisCooldownInfo = openRaidLib.CooldownManager.GetCooldownInfo(spellId)
--check if this spell is registered as a cooldown
if (thisCooldownInfo) then
for filter in filters:gmatch("([^,%s]+)") do
--filterStringToCooldownType is a map where the key is the filter name and value is the cooldown type
local cooldownType = filterStringToCooldownType[filter]
--cooldown type is a number from 1 to 8 telling its type
if (cooldownType == thisCooldownInfo.type) then
return true
--check for custom filter, the custom filter name is set as a key in the cooldownInfo: cooldownInfo[filterName] = true
elseif (thisCooldownInfo[filter]) then
return true
end
end
end
return false
end
local getCooldownsForFilter = function(unitName, allCooldowns, unitDataFilteredCache, filter)
local allCooldownsData = openRaidLib.CooldownManager.GetAllRegisteredCooldowns()
local filterTable = unitDataFilteredCache[filter]
--if the unit already sent its full list of cooldowns, the cache can be built
--when NeedRebuildFilters is true, HasFullCooldownList is always true
--bug: filterTable is nil and HasFullCooldownList is also nil, happening after leaving a group internal callback
--November 06, 2022 note: is this bug still happening?
local doesNotHaveFilterYet = not filterTable and openRaidLib.CooldownManager.HasFullCooldownList[unitName]
local isDirty = openRaidLib.CooldownManager.NeedRebuildFilters[unitName]
if (doesNotHaveFilterYet or isDirty) then
--reset the filterTable
filterTable = {}
unitDataFilteredCache[filter] = filterTable
--
for spellId, cooldownInfo in pairs(allCooldowns) do
local cooldownData = allCooldownsData[spellId]
if (cooldownData) then
if (cooldownData.type == filterStringToCooldownType[filter]) then
filterTable[spellId] = cooldownInfo
elseif (cooldownData[filter]) then --custom filter
filterTable[spellId] = cooldownInfo
end
end
end
end
return filterTable
end
--API Call
--@filterName: a string representing a name of the filter
--@spells: an array of spellIds
--important: a spell can be part of any amount of custom filters,
--declaring a spell on a new filter does NOT remove it from other filters where it was previously added
function openRaidLib.AddCooldownFilter(filterName, spells)
--integrity check
if (type(filterName) ~= "string") then
openRaidLib.DiagnosticError("Usage: openRaidLib.AddFilter(string: filterName, table: spells)", debugstack())
return false
elseif (type(spells) ~= "table") then
openRaidLib.DiagnosticError("Usage: openRaidLib.AddFilter(string: filterName, table: spells)", debugstack())
return false
end
local allCooldownsData = openRaidLib.CooldownManager.GetAllRegisteredCooldowns()
--iterate among the all cooldowns table and erase the filterName from all spells
for spellId, cooldownData in pairs(allCooldownsData) do
cooldownData[filterName] = nil
removeSpellFromCustomFilterCache(spellId, filterName)
end
--iterate among spells passed within the spells table and set the new filter on them
--problem: the filter is set directly into the global cooldown table
--this could in rare cases make an addon to override settings of another addon
for spellIndex, spellId in ipairs(spells) do
local cooldownData = allCooldownsData[spellId]
if (cooldownData) then
cooldownData[filterName] = true
addSpellToCustomFilterCache(spellId, filterName)
else
openRaidLib.DiagnosticError("A spellId on your spell list for openRaidLib.AddFilter isn't registered as cooldown:", spellId, debugstack())
end
end
--tag all cache filters as dirt
local allUnitsCooldowns = openRaidLib.GetAllUnitsCooldown()
for unitName in pairs(allUnitsCooldowns) do
openRaidLib.CooldownManager.NeedRebuildFilters[unitName] = true
end
return true
end
--API Call
--@allCooldowns: all cooldowns sent by a unit, map{[spellId] = cooldownInfo}
--@filters: string with filter names: array{"defensive-raid, "defensive-personal"}
function openRaidLib.FilterCooldowns(unitName, allCooldowns, filters)
local allDataFiltered = openRaidLib.CooldownManager.UnitDataFilterCache --["unitName"] = {defensive-raid = {[spellId = cooldownInfo]}}
local unitDataFilteredCache = allDataFiltered[unitName]
if (not unitDataFilteredCache) then
unitDataFilteredCache = {}
allDataFiltered[unitName] = unitDataFilteredCache
end
--before break the string into parts and build the filters, attempt to get cooldowns from the cache using the whole filter string
local filterAlreadyInCache = unitDataFilteredCache[filters]
if (filterAlreadyInCache and not openRaidLib.CooldownManager.NeedRebuildFilters[unitName]) then
return filterAlreadyInCache
end
local resultFilters = {}
--break the string into pieces and filter cooldowns
for filter in filters:gmatch("([^,%s]+)") do
local filterTable = getCooldownsForFilter(unitName, allCooldowns, unitDataFilteredCache, filter)
if (filterTable) then
openRaidLib.TCopy(resultFilters, filterTable) --filter table is nil
end
end
--cache the whole filter string
if (next(resultFilters)) then
unitDataFilteredCache[filters] = resultFilters
end
return resultFilters
end
--use to check if a spell is a flask buff, return a table containing .tier{}
function openRaidLib.GetFlaskInfoBySpellId(spellId)
return LIB_OPEN_RAID_FLASK_BUFF[spellId]
end
--return a number indicating the flask tier, if the aura isn't a flask return nil
function openRaidLib.GetFlaskTierFromAura(auraInfo)
local flaskTable = openRaidLib.GetFlaskInfoBySpellId(auraInfo.spellId)
if (flaskTable) then
local points = auraInfo.points
if (points) then
for i = 1, #points do
local flaskTier = flaskTable.tier[points[i]]
if (flaskTier) then
return flaskTier
end
end
end
end
return nil
end
--use to check if a spell is a food buff, return a table containing .tier{} .status{} .localized{}
function openRaidLib.GetFoodInfoBySpellId(spellId)
return LIB_OPEN_RAID_FOOD_BUFF[spellId]
end
--return a number indicating the food tier, if the aura isn't a food return nil
function openRaidLib.GetFoodTierFromAura(auraInfo)
local foodTable = openRaidLib.GetFoodInfoBySpellId(auraInfo.spellId)
if (foodTable) then
local points = auraInfo.points
if (points) then
for i = 1, #points do
local foodTier = foodTable.tier[points[i]]
if (foodTier) then
return foodTier
end
end
end
end
return nil
end
local isTierPiece = function(itemLink)
local tooltipData = C_TooltipInfo.GetHyperlink(itemLink)
if (tooltipData) then
local lines = tooltipData.lines
if (lines and #lines > 0) then
for i = 1, #lines do
local thisLine = lines[i]
local leftText = thisLine.leftText
if (type(leftText) == "string") then
if (leftText:match( "%s%(%d%/5%)$" )) then
return true
end
end
end
end
end
return false
end
--called from AddUnitGearList() on LibOpenRaid file
function openRaidLib.GearManager.BuildEquipmentItemLinks(equippedGearList)
equippedGearList = equippedGearList or {} --nil table for older versions
for i = 1, #equippedGearList do
local equipmentTable = equippedGearList[i]
--equippedGearList is a indexed table with 4 indexes:
local slotId = equipmentTable[1]
local numGemSlots = equipmentTable[2]
local itemLevel = equipmentTable[3]
local partialItemLink = equipmentTable[4]
if (partialItemLink and type(partialItemLink) == "string") then
--get the itemId from the partial link to query the itemName with GetItemInfo
local itemId = partialItemLink:match("^%:(%d+)%:")
itemId = tonumber(itemId)
if (itemId) then
local itemName = GetItemInfo(itemId)
if (itemName) then
--build the full item link
local itemLink = "|cFFEEEEEE|Hitem" .. partialItemLink .. "|h[" .. itemName .. "]|r"
--use GetItemInfo again with the now completed itemLink to query the item color
local _, _, itemQuality = GetItemInfo(itemLink)
itemQuality = itemQuality or 1
local qualityColor = ITEM_QUALITY_COLORS[itemQuality]
--replace the item color
--local r, g, b, hex = GetItemQualityColor(qualityColor)
itemLink = itemLink:gsub("FFEEEEEE", qualityColor.color:GenerateHexColor())
wipe(equipmentTable)
equipmentTable.slotId = slotId
equipmentTable.gemSlots = numGemSlots
equipmentTable.itemLevel = itemLevel
equipmentTable.itemLink = itemLink
equipmentTable.itemQuality = itemQuality
equipmentTable.itemId = itemId
equipmentTable.itemName = itemName
equipmentTable.isTier = isTierPiece(itemLink)
local _, _, enchantId, gemId1, gemId2, gemId3, gemId4, suffixId, uniqueId, levelOfTheItem, specId, upgradeInfo, instanceDifficultyId, numBonusIds, restLink = strsplit(":", itemLink)
local enchantAttribute = LIB_OPEN_RAID_ENCHANT_SLOTS[slotId]
local nEnchantId = 0
if (enchantAttribute) then --this slot can receive an enchat
if (enchantId and enchantId ~= "") then
enchantId = tonumber(enchantId)
nEnchantId = enchantId
end
--6400 and above is dragonflight enchantId number space
if (nEnchantId < 6300 and not LIB_OPEN_RAID_DEATHKNIGHT_RUNEFORGING_ENCHANT_IDS[nEnchantId]) then
nEnchantId = 0
end
end
equipmentTable.enchantId = nEnchantId
local nGemId = 0
local gemsIds = {gemId1, gemId2, gemId3, gemId4}
--check if the item has a socket
if (numGemSlots) then
--check if the socket is empty
for gemSlotId = 1, numGemSlots do
local gemId = tonumber(gemsIds[gemSlotId])
if (gemId and gemId >= 180000) then
nGemId = gemId
break
end
end
end
equipmentTable.gemId = nGemId
end
end
end
end
end