Skip to content

Commit

Permalink
feat: #2144 add support for modRate/timeMod
Browse files Browse the repository at this point in the history
  • Loading branch information
ascott18 committed Sep 8, 2024
1 parent 32d2cea commit 17e08ef
Show file tree
Hide file tree
Showing 17 changed files with 127 additions and 84 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## v11.0.7
* #1857 - Add LibCustomGlow animations.
* #2144 - TMW now accounts for adjusted-rate cooldowns and auras (e.g. timer freezes caused by talents or boss mechanics).
* Fix: #2191 - Icon Shown condition ignoring Shown/Hidden checkboxes for disabled icons/groups.
* Fix: #2193 - Icon overlay and border animations starting in the wrong state.
* Fix: #2215 - Spell Charges condition not updating for countable spells without true charges.
Expand Down
1 change: 1 addition & 0 deletions Components/Core/Common/Cooldowns.lua
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ else
maxCharges = maxCharges,
cooldownStartTime = cooldownStartTime,
cooldownDuration = cooldownDuration,
chargeModRate = 1
} or false
CachedCharges[spell] = cached
return cached
Expand Down
31 changes: 21 additions & 10 deletions Components/Core/Conditions/Categories/BuffsDebuffs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ function Env.AuraDur(unit, name, filter)
if not data then
return 0, 0, 0
else
return data.expirationTime == 0 and huge or data.expirationTime - TMW.time, data.duration, data.expirationTime
local expirationTime = instance.expirationTime
local timeMod = instance.timeMod
return
expirationTime == 0 and huge or ((expirationTime - TMW.time) / timeMod),
data.duration,
expirationTime,
timeMod
end
end

Expand All @@ -125,7 +131,12 @@ function Env.AuraDurPacked(unit, name, kindKey, onlyMine)
local instance = instances[auraInstanceID]
if instance[kindKey] then
local expirationTime = instance.expirationTime
return expirationTime == 0 and huge or expirationTime - TMW.time, instance.duration, expirationTime
local timeMod = instance.timeMod
return
expirationTime == 0 and huge or ((expirationTime - TMW.time) / timeMod),
instance.duration,
expirationTime,
timeMod
end
end
end
Expand Down Expand Up @@ -273,10 +284,10 @@ ConditionCategory:RegisterCondition(1, "BUFFDUR", {
and [[AuraDurPacked(c.Unit, c.Spells.First, "isHelpful", ]] .. (tostring(c.Checked)) .. ")"
or [[AuraDur(c.Unit, c.Spells.First, "HELPFUL]] .. (c.Checked and " PLAYER" or "") .. [[")]]

return [[local dur, duration, expirationTime = ]] .. getAura .. [[
return [[local dur, duration, expirationTime, timeMod = ]] .. getAura .. [[
local VALUE
if dur and dur > 0 then
VALUE = expirationTime and expirationTime - c.Level or 0
VALUE = expirationTime and expirationTime - (c.Level * timeMod) or 0
else
VALUE = 0
end]]
Expand Down Expand Up @@ -333,10 +344,10 @@ ConditionCategory:RegisterCondition(2.5, "BUFFPERC", {
and [[AuraDurPacked(c.Unit, c.Spells.First, "isHelpful", ]] .. (tostring(c.Checked)) .. ")"
or [[AuraDur(c.Unit, c.Spells.First, "HELPFUL]] .. (c.Checked and " PLAYER" or "") .. [[")]]

return [[local dur, duration, expirationTime = ]] .. getAura .. [[
return [[local dur, duration, expirationTime, timeMod = ]] .. getAura .. [[
local VALUE
if dur and dur > 0 then
VALUE = expirationTime and (expirationTime - c.Level*duration) or 0
VALUE = expirationTime and (expirationTime - c.Level*duration*timeMod) or 0
else
VALUE = 0
end]]
Expand Down Expand Up @@ -538,10 +549,10 @@ ConditionCategory:RegisterCondition(11, "DEBUFFDUR", {
and [[AuraDurPacked(c.Unit, c.Spells.First, "isHarmful", ]] .. (tostring(c.Checked)) .. ")"
or [[AuraDur(c.Unit, c.Spells.First, "HARMFUL]] .. (c.Checked and " PLAYER" or "") .. [[")]]

return [[local dur, duration, expirationTime = ]] .. getAura .. [[
return [[local dur, duration, expirationTime, timeMod = ]] .. getAura .. [[
local VALUE
if dur and dur > 0 then
VALUE = expirationTime and expirationTime - c.Level or 0
VALUE = expirationTime and expirationTime - (c.Level*timeMod) or 0
else
VALUE = 0
end]]
Expand Down Expand Up @@ -598,10 +609,10 @@ ConditionCategory:RegisterCondition(12.5,"DEBUFFPERC", {
and [[AuraDurPacked(c.Unit, c.Spells.First, "isHarmful", ]] .. (tostring(c.Checked)) .. ")"
or [[AuraDur(c.Unit, c.Spells.First, "HARMFUL]] .. (c.Checked and " PLAYER" or "") .. [[")]]

return [[local dur, duration, expirationTime = ]] .. getAura .. [[
return [[local dur, duration, expirationTime, timeMod = ]] .. getAura .. [[
local VALUE
if dur and dur > 0 then
VALUE = expirationTime and (expirationTime - c.Level*duration) or 0
VALUE = expirationTime and (expirationTime - c.Level*duration*timeMod) or 0
else
VALUE = 0
end]]
Expand Down
12 changes: 7 additions & 5 deletions Components/Core/Conditions/Categories/Spells.lua
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ function Env.CooldownDuration(spell, gcdAsUnusable)
if spell == "gcd" then
local cooldown = GetSpellCooldown(TMW.GCDSpell)
local duration = cooldown.duration
return duration == 0 and 0 or (duration - (TMW.time - cooldown.startTime))
return duration == 0 and 0 or ((duration - (TMW.time - cooldown.startTime)) / cooldown.modRate)
end

local cooldown = GetSpellCooldown(spell)
if cooldown then
local duration = cooldown.duration
return ((duration == 0 or (not gcdAsUnusable and OnGCD(duration))) and 0) or (duration - (TMW.time - cooldown.startTime))
return
((duration == 0 or (not gcdAsUnusable and OnGCD(duration))) and 0) or
((duration - (TMW.time - cooldown.startTime)) / cooldown.modRate)
end
return 0
end
Expand All @@ -65,7 +67,7 @@ function Env.RechargeDuration(spell)
local charges = GetSpellCharges(spell)
if charges and charges.currentCharges ~= charges.maxCharges then
local duration = charges.cooldownDuration
return (duration == 0 and 0) or (duration - (TMW.time - charges.cooldownStartTime))
return (duration == 0 and 0) or ((duration - (TMW.time - charges.cooldownStartTime)) / charges.chargeModRate)
end
return 0
end
Expand Down Expand Up @@ -113,7 +115,7 @@ ConditionCategory:RegisterCondition(1, "SPELLCD", {
anticipate = function(c)
local str = [[
local cooldown = GetSpellCooldown(c.OwnSpells.First)
local VALUE = cooldown and cooldown.startTime + (cooldown.duration - c.Level) or huge
local VALUE = cooldown and cooldown.startTime + (cooldown.duration - (c.Level*cooldown.modRate)) or huge
]]
if TMW:GetSpells(c.Name).First == "gcd" then
str = str:gsub("c.OwnSpells.First", TMW.GCDSpell)
Expand Down Expand Up @@ -229,7 +231,7 @@ if TMW.isRetail then
end,
anticipate = [[
local data = GetSpellCharges(c.OwnSpells.First)
local VALUE = data and data.cooldownDuration and data.cooldownStartTime + (data.cooldownDuration - c.Level) or huge
local VALUE = data and data.cooldownDuration and data.cooldownStartTime + (data.cooldownDuration - (c.Level*data.chargeModRate)) or huge
]],
})
end
Expand Down
47 changes: 26 additions & 21 deletions Components/Core/Icon.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1343,28 +1343,33 @@ local function SetInfo_GenerateFunction(signature, isInternal)
local match
for _, Processor in ipairs(TMW.Classes.IconDataProcessor.instances) do

match = signature:match("^(" .. Processor.attributesStringNoSpaces .. ")$") -- The attribute string is the only one in the signature
or signature:match("^(" .. Processor.attributesStringNoSpaces .. ";)") -- The attribute string is the first one in the signature
or signature:match("(;" .. Processor.attributesStringNoSpaces .. ")$") -- The attribute string is the last one in the signature
or signature:match(";(" .. Processor.attributesStringNoSpaces .. ";)") -- The attribute string is in the middle of the signature

for _, attributeString in pairs(Processor.allAttributesStringNoSpaces) do
match = signature:match("^(" .. attributeString .. ")$") -- The attribute string is the only one in the signature
or signature:match("^(" .. attributeString .. ";)") -- The attribute string is the first one in the signature
or signature:match("(;" .. attributeString .. ")$") -- The attribute string is the last one in the signature
or signature:match(";(" .. attributeString .. ";)") -- The attribute string is in the middle of the signature

if match then
t[#t+1] = "local Processor = "
t[#t+1] = Processor.name
t[#t+1] = "\n"

-- Process any hooks that should go before the main function segment
Processor:CompileFunctionHooks(t, "pre")

Processor:CompileFunctionSegment(t)

-- Process any hooks that should go after the main function segment
Processor:CompileFunctionHooks(t, "post")

t[#t+1] = "\n\n"

signature = signature:gsub(match, "", 1)

break
end
end
if match then
t[#t+1] = "local Processor = "
t[#t+1] = Processor.name
t[#t+1] = "\n"

-- Process any hooks that should go before the main function segment
Processor:CompileFunctionHooks(t, "pre")

Processor:CompileFunctionSegment(t)

-- Process any hooks that should go after the main function segment
Processor:CompileFunctionHooks(t, "post")

t[#t+1] = "\n\n"

signature = signature:gsub(match, "", 1)

break
end
end
Expand Down
26 changes: 22 additions & 4 deletions Components/Core/IconDataProcessor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ IconDataProcessor.ProcessorsByName = {}
-- local Processor = TMW.Classes.IconDataProcessor:New("STATE", "state")
--
-- local Processor = TMW.Classes.IconDataProcessor:New("DURATION", "start, duration")
function IconDataProcessor:OnNewInstance(name, attributes)
TMW:ValidateType("2 (name)", "IconDataProcessor:New(name, attributes)", name, "string")
TMW:ValidateType("3 (attributes)", "IconDataProcessor:New(name, attributes)", attributes, "string")
function IconDataProcessor:OnNewInstance(name, attributes, altAttributes)
TMW:ValidateType("2 (name)", "IconDataProcessor:New(name, attributes, altAttributes?)", name, "string")
TMW:ValidateType("3 (attributes)", "IconDataProcessor:New(name, attributes, altAttributes?)", attributes, "string")
TMW:ValidateType("4 (altAttributes)", "IconDataProcessor:New(name, attributes, altAttributes?)", altAttributes, "table;nil")

self.hooks = {}

Expand All @@ -64,19 +65,36 @@ function IconDataProcessor:OnNewInstance(name, attributes)
self.name = name
self.attributesString = attributes
self.attributesStringNoSpaces = attributes:gsub(" ", "")
self.allAttributesStringNoSpaces = { self.attributesStringNoSpaces }

for _, attribute in TMW:Vararg(strsplit(",", self.attributesStringNoSpaces)) do
if self.UsedTokens[attribute] then
error(("Attribute token %q is already in use by %q!"):format(attribute, self.UsedTokens[attribute].name))
else
self.UsedTokens[attribute] = self
self:DeclareUpValue(attribute) -- do this to prevent accidental leaked global accessing
self.NumAttributes = self.NumAttributes + 1
end
end

for _, altAttribute in pairs(altAttributes or {}) do
altAttribute = altAttribute:gsub(" ", "")
tinsert(self.allAttributesStringNoSpaces, altAttribute)
for i, attribute in TMW:Vararg(strsplit(",", altAttribute)) do
if self.UsedTokens[attribute] == self then
-- Already declared by the default attributes
elseif self.UsedTokens[attribute] then
error(("Attribute token %q is already in use by %q!"):format(attribute, self.UsedTokens[attribute].name))
else
self.UsedTokens[attribute] = self
self:DeclareUpValue(attribute) -- do this to prevent accidental leaked global accessing
end
self.NumAttributes = max(self.NumAttributes, i)
end
end

self.ProcessorsByName[self.name] = self
self:DeclareUpValue(name, self)
self:DeclareUpValue(attributes) -- do this to prevent accidental leaked global accessing

self.changedEvent = "TMW_ICON_DATA_CHANGED_" .. name

Expand Down
29 changes: 16 additions & 13 deletions Components/Core/IconDataProcessors.lua
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,12 @@ end

-- DURATION: "start, duration"
do
local Processor = TMW.Classes.IconDataProcessor:New("DURATION", "start, duration")
local Processor = TMW.Classes.IconDataProcessor:New("DURATION", "start, duration, modRate", {"start, duration"})
Processor:DeclareUpValue("OnGCD", TMW.OnGCD)

TMW.Classes.Icon.attributes.start = 0
TMW.Classes.Icon.attributes.duration = 0
TMW.Classes.Icon.attributes.modRate = 1
TMW.Classes.Icon.__realDuration = 0

Processor:RegisterIconEvent(21, "OnStart", {
Expand Down Expand Up @@ -358,7 +359,7 @@ do
valueName = L["DURATION"],
conditionChecker = function(icon, eventSettings)
local attributes = icon.attributes
local d = attributes.duration - (TMW.time - attributes.start)
local d = (attributes.duration - (TMW.time - attributes.start)) / attributes.modRate
d = d > 0 and d or 0

return TMW.CompareFuncs[eventSettings.Operator](d, eventSettings.Value)
Expand All @@ -370,15 +371,16 @@ do
})

function Processor:CompileFunctionSegment(t)
-- GLOBALS: start, duration
-- GLOBALS: start, duration, modRate
t[#t+1] = [[
duration = duration or 0
start = start or 0
modRate = modRate or 1
if duration == 0.001 then duration = 0 end -- hardcode fix for tricks of the trade. nice hardcoding on your part too, blizzard
if EventHandlersSet.OnDuration then
local d = duration - (TMW.time - start)
local d = (duration - (TMW.time - start)) / modRate
d = d > 0 and d or 0
if d ~= icon.__lastDur then
Expand All @@ -387,7 +389,7 @@ do
end
end
if attributes.start ~= start or attributes.duration ~= duration then
if attributes.start ~= start or attributes.duration ~= duration or attributes.modRate ~= modRate then
local realDuration = icon:OnGCD(duration) and 0 or duration -- the duration of the cooldown, ignoring the GCD
if icon.__realDuration ~= realDuration then
Expand All @@ -406,8 +408,9 @@ do
attributes.start = start
attributes.duration = duration
attributes.modRate = modRate
TMW:Fire(DURATION.changedEvent, icon, start, duration)
TMW:Fire(DURATION.changedEvent, icon, start, duration, modRate)
doFireIconUpdated = true
end
--]]
Expand Down Expand Up @@ -458,7 +461,7 @@ do
if #durations > 0 then
local lastCheckedDuration = durations.last or 0

local currentIconDuration = icon.attributes.duration - (time - icon.attributes.start)
local currentIconDuration = (icon.attributes.duration - (time - icon.attributes.start)) / icon.attributes.modRate
if currentIconDuration < 0 then currentIconDuration = 0 end

-- If the duration didn't change (i.e. it is 0) then don't even try.
Expand Down Expand Up @@ -502,8 +505,8 @@ do
local durationA = attributesA.duration
local durationB = attributesB.duration

durationA = iconA:OnGCD(durationA) and 0 or durationA - (time - attributesA.start)
durationB = iconB:OnGCD(durationB) and 0 or durationB - (time - attributesB.start)
durationA = iconA:OnGCD(durationA) and 0 or ((durationA - (time - attributesA.start)) / attributesA.modRate)
durationB = iconB:OnGCD(durationB) and 0 or ((durationB - (time - attributesB.start)) / attributesA.modRate)

if durationA ~= durationB then
return durationA*order < durationB*order
Expand Down Expand Up @@ -539,7 +542,7 @@ do

TMW:RegisterCallback("TMW_ICON_SETUP_POST", function(event, icon)
if not TMW.Locked then
icon:SetInfo("start, duration", 0, 0)
icon:SetInfo("start, duration, modRate", 0, 0, 1)
end
end)
end
Expand Down Expand Up @@ -702,19 +705,19 @@ do

if icon then
local attributes = icon.attributes
local modRate = attributes.modRate

local chargeDur = attributes.chargeDur
if not ignoreCharges and chargeDur and chargeDur > 0 then

local remaining = chargeDur - (TMW.time - attributes.chargeStart)
local remaining = (chargeDur - (TMW.time - attributes.chargeStart)) / modRate
if remaining > 0 then
return isNumber[format("%.1f", remaining)] or 0
end
end

local duration = attributes.duration

local remaining = duration - (TMW.time - attributes.start)
local remaining = (duration - (TMW.time - attributes.start)) / modRate
if remaining <= 0 or (not gcd and icon:OnGCD(duration)) then
return 0
end
Expand Down
9 changes: 6 additions & 3 deletions Components/Core/IconType.lua
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ function IconType:UsesAttributes(attributesString, uses)

TMW:ValidateType("3 (uses)", "IconView:Register(attributesString, uses)", uses, "boolean;nil")

attributesString = attributesString:gsub(" ", "")
if uses == false then
self.UsedAttributes[attributesString] = nil
else
Expand All @@ -328,9 +329,11 @@ function IconType:UpdateUsedProcessors()
self:AssertSelfIsInstance()

for _, Processor in ipairs(TMW.Classes.IconDataProcessor.instances) do
if self.UsedAttributes[Processor.attributesString] then
self.UsedAttributes[Processor.attributesString] = nil
self.UsedProcessors[Processor] = true
for _, attributeString in pairs(Processor.allAttributesStringNoSpaces) do
if self.UsedAttributes[attributeString] then
self.UsedAttributes[attributeString] = nil
self.UsedProcessors[Processor] = true
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ end)

Hook:DeclareUpValue("STATE_DEFAULT_DURATIONFAILED", STATE)
Hook:RegisterCompileFunctionSegmentHook("post", function(Processor, t)
-- GLOBALS: start, duration
-- GLOBALS: start, duration, modRate
t[#t+1] = [[
if duration > 0 or doFireIconUpdated then
local d = duration - (TMW.time - start)
local d = (duration - (TMW.time - start)) / (modRate or 1)
local state_durationFailed = nil
if
Expand Down
Loading

0 comments on commit 17e08ef

Please sign in to comment.