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