forked from Bastion/Bastion
parent
3d203c280c
commit
4d0719b869
@ -0,0 +1,82 @@ |
||||
---@type Tinkr, Bastion |
||||
local Tinkr, Bastion = ... |
||||
|
||||
---@class Config |
||||
---@field instantiated boolean |
||||
---@field config Tinkr.Util.Config.Instance |
||||
---@field defaults nil | table |
||||
local Config = { |
||||
instantiated = false, |
||||
} |
||||
|
||||
function Config:__index(k) |
||||
local response = Config[k] |
||||
|
||||
if response == nil then |
||||
response = rawget(self, k) |
||||
end |
||||
|
||||
if response == nil and self.instantiated then |
||||
response = self:Read(k) |
||||
end |
||||
|
||||
return response |
||||
end |
||||
|
||||
function Config:__newindex(key, value) |
||||
if self.instantiated then |
||||
self:Write(key, value) |
||||
else |
||||
rawset(self, key, value) |
||||
end |
||||
end |
||||
|
||||
---@generic D |
||||
---@param name string |
||||
---@param defaults? D |
||||
function Config:New(name, defaults) |
||||
local self = setmetatable({}, Config) |
||||
self.config = Tinkr.Util.Config:New(name) |
||||
self.defaults = type(defaults) == "table" and defaults or {} |
||||
self.instantiated = true |
||||
return self |
||||
end |
||||
|
||||
---@generic D |
||||
---@param key string |
||||
---@param default? D |
||||
---@return D |
||||
function Config:Read(key, default) |
||||
if type(default) == "nil" then |
||||
default = self.defaults[key] |
||||
end |
||||
return self.config:Read(key, default) |
||||
end |
||||
|
||||
function Config:Reset() |
||||
if type(self.defaults) == "table" then |
||||
-- Clear all values currently in the config. |
||||
for key, _ in pairs(self.config.data) do |
||||
self:Write(key, nil) |
||||
end |
||||
-- Use default table to write new defaults. |
||||
for key, value in pairs(self.defaults) do |
||||
self:Write(key, value) |
||||
end |
||||
return true |
||||
end |
||||
return false |
||||
end |
||||
|
||||
---@param key string |
||||
---@param value any |
||||
function Config:Write(key, value) |
||||
self.config:Write(key, value) |
||||
end |
||||
|
||||
---@param key string |
||||
function Config:Sync(key) |
||||
self.config:Sync(key) |
||||
end |
||||
|
||||
return Config |
@ -0,0 +1,282 @@ |
||||
---@type Tinkr, Bastion |
||||
local Tinkr, Bastion = ... |
||||
local Cache = Bastion.Caches |
||||
local Player = Bastion.UnitManager:Get("player") |
||||
local Target = Bastion.UnitManager:Get("target") |
||||
|
||||
--- An attempt to integrate HeroLib TTD timers. |
||||
---@class TimeToDie |
||||
local TimeToDie = { |
||||
Settings = { |
||||
-- Refresh time (seconds) : min=0.1, max=2, default = 0.1 |
||||
Refresh = 0.1, |
||||
-- 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]: 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.List:New():concat(Bastion.ObjectManager.enemies):concat(Bastion.ObjectManager.explosives):concat( |
||||
Bastion.ObjectManager.incorporeal) |
||||
end |
||||
|
||||
function TimeToDie:Refresh() |
||||
local currentTime = GetTime() |
||||
local historyCount = self.Settings.HistoryCount |
||||
local historyTime = self.Settings.HistoryTime |
||||
local ttdCache = self.Cache |
||||
local iterableUnits = self:IterableUnits() |
||||
local units = self.Units |
||||
local existingUnits = self.ExistingUnits |
||||
|
||||
wipe(existingUnits) |
||||
|
||||
local thisUnit |
||||
---@param unit Unit |
||||
iterableUnits:each(function(unit) |
||||
thisUnit = unit |
||||
if thisUnit:Exists() then |
||||
local unitGUID = thisUnit:GetGUID() |
||||
-- Check if we didn't already scanned this unit. |
||||
if unitGUID and not existingUnits[unitGUID] then |
||||
existingUnits[unitGUID] = true |
||||
local healthPercentage = thisUnit:GetHealthPercent() |
||||
-- Check if it's a valid unit |
||||
if Player:CanAttack(thisUnit) and 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 |
||||
tableinsert(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 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 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 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 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 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 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 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 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 Unit |
||||
[114361] = function(self) |
||||
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 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? 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 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? 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 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 |
Loading…
Reference in new issue