forked from jeffi/Bastion
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.
285 lines
10 KiB
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
|
|
|