Bastion aims to serve as a highly performant, simplisitic, and expandable World of Warcraft data visualization framework.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Bastion/src/TimeToDie/TimeToDie.lua

285 lines
10 KiB

---@type Tinkr, Bastion
local Tinkr, Bastion = ...
local Player = Bastion.UnitManager:Get("player")
local Target = Bastion.UnitManager:Get("target")
if not Bastion.Globals.UnitInfo then
Bastion.Globals.UnitInfo = Bastion.Cache:New()
end
local Cache = Bastion.Globals.UnitInfo
--- An attempt to integrate HeroLib TTD timers.
---@class Bastion.TimeToDie
local TimeToDie = {
Settings = {
-- Refresh time (seconds) : min=0.1, max=2, default = 0.1
Refresh = 0.5,
-- History time (seconds) : min=5, max=120, default = 10+0.4
HistoryTime = 10 + 0.4,
-- Max history count : min=20, max=500, default = 100
HistoryCount = 100
},
Cache = {}, -- A cache of unused { time, value } tables to reduce garbage due to table creation
---@type table<string, {[1]: {[1]: {[1]: number, [2]: number}, [2]: number}, [2]: number }>
Units = {}, -- Used to track units,
---@type table<string, boolean>
ExistingUnits = {}, -- Used to track GUIDs of currently existing units (to be compared with tracked units)
Throttle = 0
}
function TimeToDie:IterableUnits()
return Bastion.ObjectManager.enemies
end
function TimeToDie:Refresh()
local currentTime = GetTime()
local historyCount = TimeToDie.Settings.HistoryCount
local historyTime = TimeToDie.Settings.HistoryTime
local ttdCache = TimeToDie.Cache
local iterableUnits = Bastion.ObjectManager.enemies
local units = TimeToDie.Units
local existingUnits = TimeToDie.ExistingUnits
wipe(existingUnits)
---@param unit Bastion.Unit
iterableUnits:each(function(unit)
if unit:Exists() then
local unitGUID = unit:GetGUID()
-- Check if we didn't already scanned this unit.
if unitGUID and not existingUnits[unitGUID] then
existingUnits[unitGUID] = true
local healthPercentage = unit:GetHealthPercent()
-- Check if it's a valid unit
if healthPercentage < 100 then
local unitTable = units[unitGUID]
-- Check if we have seen one time this unit, if we don't then initialize it.
if not unitTable or healthPercentage > unitTable[1][1][2] then
unitTable = { {}, currentTime }
units[unitGUID] = unitTable
end
local values = unitTable[1]
local time = currentTime - unitTable[2]
-- Check if the % HP changed since the last check (or if there were none)
if not values or healthPercentage ~= values[2] then
local value
local lastIndex = #ttdCache
-- Check if we can re-use a table from the cache
if lastIndex == 0 then
value = { time, healthPercentage }
else
value = ttdCache[lastIndex]
ttdCache[lastIndex] = nil
value[1] = time
value[2] = healthPercentage
end
table.insert(values, 1, value)
local n = #values
-- Delete values that are no longer valid
while (n > historyCount) or (time - values[n][1] > historyTime) do
ttdCache[#Cache + 1] = values[n]
values[n] = nil
n = n - 1
end
end
end
end
end
return false
end)
-- Not sure if it's even worth it to do this here
-- Ideally this should be event driven or done at least once a second if not less
for key in pairs(units) do
if not existingUnits[key] then
units[key] = nil
end
end
end
TimeToDie.specialTTDPercentageData = {
--- Dragonflight
----- Dungeons -----
--- Brackenhide Hollow
-- Decatriarch Wratheye
[186121] = 5,
--- Shadowlands
----- Dungeons -----
--- De Other Side
-- Mueh'zala leaves the fight at 10%.
[166608] = 10,
--- Mists of Tirna Scithe
-- Tirnenns leaves the fight at 20%.
[164929] = 20, -- Tirnenn Villager
[164804] = 20, -- Droman Oulfarran
--- Sanguine Depths
-- General Kaal leaves the fight at 50%.
[162099] = 50,
----- Castle Nathria -----
--- Stone Legion Generals
-- General Kaal leaves the fight at 50% if General Grashaal has not fight yet. We take 49% as check value since it get -95% dmg reduction at 50% until intermission is over.
---@param self Bastion.Unit
[168112] = function(self) return (not self:CheckHPFromBossList(168113, 99) and 49) or 0 end,
--- Sun King's Salvation
-- Shade of Kael'thas fight is 60% -> 45% and then 10% -> 0%.
---@param self Bastion.Unit
[165805] = function(self) return (self:GetHealthPercent() > 20 and 45) or 0 end,
----- Sanctum of Domination -----
--- Eye of the Jailer leaves at 66% and 33%
---@param self Bastion.Unit
[180018] = function(self)
return (self:GetHealthPercent() > 66 and 66) or
(self:GetHealthPercent() <= 66 and self:GetHealthPercent() > 33 and 33) or 0
end,
--- Painsmith Raznal leaves at 70% and 40%
---@param self Bastion.Unit
[176523] = function(self)
return (self:GetHealthPercent() > 70 and 70) or
(self:GetHealthPercent() <= 70 and self:GetHealthPercent() > 40 and 40) or 0
end,
--- Fatescribe Roh-Kalo phases at 70% and 40%
---@param self Bastion.Unit
[179390] = function(self)
return (self:GetHealthPercent() > 70 and 70) or
(self:GetHealthPercent() <= 70 and self:GetHealthPercent() > 40 and 40) or 0
end,
--- Sylvanas Windrunner intermission at 83% and "dies" at 50% (45% in MM)
---@param self Bastion.Unit
[180828] = function(self)
local _, _, difficultyId = GetInstanceInfo()
return (self:GetHealthPercent() > 83 and 83) or
((difficultyId == 16 and 45) or 50)
end,
--- Legion
----- Open World -----
--- Stormheim Invasion
-- Lord Commander Alexius
[118566] = 85,
----- Dungeons -----
--- Halls of Valor
-- Hymdall leaves the fight at 10%.
[94960] = 10,
-- Fenryr leaves the fight at 60%. We take 50% as check value since it doesn't get immune at 60%.
---@param self Bastion.Unit
[95674] = function(self) return (self:GetHealthPercent() > 50 and 60) or 0 end,
-- Odyn leaves the fight at 80%.
[95676] = 80,
--- Maw of Souls
-- Helya leaves the fight at 70%.
[96759] = 70,
----- Trial of Valor -----
--- Odyn
-- Hyrja & Hymdall leaves the fight at 25% during first stage and 85%/90% during second stage (HM/MM).
---@param self Bastion.Unit
[114360] = function(self)
local _, _, difficultyId = GetInstanceInfo()
return (not self:CheckHPFromBossList(114263, 99) and 25) or
(difficultyId == 16 and 85) or 90
end,
---@param self Bastion.Unit
[114361] = function(self)
local _, _, difficultyId = GetInstanceInfo()
return (not self:CheckHPFromBossList(114263, 99) and 25) or
(difficultyId == 16 and 85) or 90
end,
-- Odyn leaves the fight at 10%.
[114263] = 10,
----- Nighthold -----
--- Elisande leaves the fight two times at 10% then normally dies. She looses 50% power per stage (100 -> 50 -> 0).
---@param self Bastion.Unit
[106643] = function(self) return (self:GetPower() > 0 and 10) or 0 end,
--- Warlord of Draenor (WoD)
----- Dungeons -----
--- Shadowmoon Burial Grounds
-- Carrion Worm doesn't die but leave the area at 10%.
[88769] = 10,
[76057] = 10,
----- HellFire Citadel -----
--- Hellfire Assault
-- Mar'Tak doesn't die and leave fight at 50% (blocked at 1hp anyway).
[93023] = 50,
--- Classic
----- Dungeons -----
--- Uldaman
-- Dwarves
[184580] = 5,
[184581] = 5,
[184582] = 5,
}
-- Returns the max fight length of boss units, or the current selected target if no boss units
---@param enemies? Bastion.List
---@param bossOnly? boolean
function TimeToDie.FightRemains(enemies, bossOnly)
local bossExists, maxTimeToDie
for i = 1, 4 do
local bossUnit = Bastion.UnitManager:Get(string.format("boss%d", i))
if bossUnit:Exists() then
bossExists = true
if not bossUnit:TimeToDieIsNotValid() then
maxTimeToDie = math.max(maxTimeToDie or 0, bossUnit:TimeToDie2())
end
end
end
if bossExists or bossOnly then
-- If we have a boss list but no valid boss time, return invalid
return maxTimeToDie or 11111
end
-- If we specify an AoE range, iterate through all the targets in the specified range
if enemies then
---@param enemy Bastion.Unit
enemies:each(function(enemy)
if (enemy:InCombatOdds() > 80 or enemy:IsDummy()) and enemy:TimeToDieIsNotValid() then
maxTimeToDie = math.max(maxTimeToDie or 0, enemy:TimeToDie2())
end
return false
end)
if maxTimeToDie then
return maxTimeToDie
end
end
return Target:TimeToDie2()
end
-- Returns the max fight length of boss units, 11111 if not a boss fight
function TimeToDie.BossFightRemains()
return TimeToDie.FightRemains(nil, true)
end
-- Get if the Time To Die is Valid for a boss fight remains
function TimeToDie.BossFightRemainsIsNotValid()
return TimeToDie.BossFightRemains() >= 7777
end
-- Returns if the current fight length meets the requirements.
---@param enemies? Bastion.List
---@param operator CompareThisTable
---@param value number
---@param checkIfValid boolean
---@param bossOnly boolean
function TimeToDie.FilteredFightRemains(enemies, operator, value, checkIfValid, bossOnly)
local fightRemains = TimeToDie.FightRemains(enemies, bossOnly)
if checkIfValid and fightRemains >= 7777 then
return false
end
return Bastion.Utils.CompareThis(operator, fightRemains, value) or false
end
-- Returns if the current boss fight length meets the requirements, 11111 if not a boss fight.
---@param operator CompareThisTable
---@param value number
---@param checkIfValid boolean
function TimeToDie.BossFilteredFightRemains(operator, value, checkIfValid)
return TimeToDie.FilteredFightRemains(nil, operator, value, checkIfValid, true)
end
return TimeToDie