fix lag of casting and channeling conditionals.

This commit is contained in:
Jrc13245
2025-12-02 15:24:48 -05:00
parent d436aa8f38
commit 442eeb9cbc
3 changed files with 287 additions and 82 deletions
+156 -65
View File
@@ -351,18 +351,71 @@ function CleveRoids.GetSpammableConditional(name)
return CleveRoids.spamConditions[name] or "nomybuff"
end
-- Checks whether or not we're currently casting a spell with cast time
-- Returns TRUE if we should allow the cast (not casting, or not casting the specified spell)
-- Returns FALSE if we should block the cast (currently casting)
function CleveRoids.CheckCasting(castingSpell)
-- No parameter: check if we're casting ANYTHING
if not castingSpell or castingSpell == "" then
-- Time-based prediction: if we know cast duration, check if it should be done
if CleveRoids.CurrentSpell.type == "cast" and CleveRoids.castStartTime and CleveRoids.castDuration then
local elapsed = GetTime() - CleveRoids.castStartTime
local remaining = CleveRoids.castDuration - elapsed
-- If cast should be done (with 0.1s grace period), treat as not casting
if remaining <= 0.1 then
CleveRoids.CurrentSpell.type = ""
return true
end
end
return CleveRoids.CurrentSpell.type ~= "cast"
end
-- With parameter: check if we're casting a specific spell
local spellName = string.gsub(CleveRoids.CurrentSpell.spellName or "", "%(.-%)%s*", "")
local casting = string.gsub(castingSpell, "%(.-%)%s*", "")
-- If we're casting this specific spell, block the recast
if CleveRoids.CurrentSpell.type == "cast" and spellName == casting then
return false
end
-- Not casting the specified spell, allow the cast
return true
end
-- Checks whether or not we're currently casting a channeled spell
-- Returns TRUE if we should allow the cast (not channeling, or not channeling the specified spell)
-- Returns FALSE if we should block the cast (currently channeling)
function CleveRoids.CheckChanneled(channeledSpell)
if not channeledSpell then return false end
-- No parameter: check if we're channeling ANYTHING
if not channeledSpell or channeledSpell == "" then
-- Time-based prediction: if we know channel duration, check if it should be done
if CleveRoids.CurrentSpell.type == "channeled" and CleveRoids.channelStartTime and CleveRoids.channelDuration then
local elapsed = GetTime() - CleveRoids.channelStartTime
local remaining = CleveRoids.channelDuration - elapsed
-- If channel should be done (with 0.1s grace period), treat as not channeling
if remaining <= 0.1 then
CleveRoids.CurrentSpell.type = ""
return true
end
end
return CleveRoids.CurrentSpell.type ~= "channeled"
end
-- Remove the "(Rank X)" part from the spells name in order to allow downranking
local spellName = string.gsub(CleveRoids.CurrentSpell.spellName, "%(.-%)%s*", "")
local spellName = string.gsub(CleveRoids.CurrentSpell.spellName or "", "%(.-%)%s*", "")
local channeled = string.gsub(channeledSpell, "%(.-%)%s*", "")
-- If we're channeling this specific spell, block the recast
if CleveRoids.CurrentSpell.type == "channeled" and spellName == channeled then
return false
end
-- Special cases for auto-attacks
if channeled == CleveRoids.Localized.Attack then
return not CleveRoids.CurrentSpell.autoAttack
end
@@ -375,7 +428,7 @@ function CleveRoids.CheckChanneled(channeledSpell)
return not CleveRoids.CurrentSpell.wand
end
CleveRoids.CurrentSpell.spellName = channeled
-- If none of the special cases matched, allow the cast (not channeling the specified spell)
return true
end
@@ -1786,29 +1839,45 @@ CleveRoids.Keywords = {
end, conditionals, "nocasting")
end,
-- NEW: Direct player casting check using Nampower's GetCurrentCastingInfo
-- More reliable than [nocasting @player] for checking if YOU are casting
-- NEW: Direct player casting check with time-based prediction
-- Uses our accurate state tracking instead of GetCurrentCastingInfo polling
selfcasting = function(conditionals)
if not GetCurrentCastingInfo then return false end
local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo()
-- Explicit check: casting or channeling must equal 1 (active)
if casting and casting == 1 then return true end
if channeling and channeling == 1 then return true end
return false
-- Check for cast with time-based prediction
if CleveRoids.CurrentSpell.type == "cast" and CleveRoids.castStartTime and CleveRoids.castDuration then
local remaining = CleveRoids.castDuration - (GetTime() - CleveRoids.castStartTime)
if remaining <= 0.1 then
return false -- Cast is done
end
end
-- Check for channel with time-based prediction
if CleveRoids.CurrentSpell.type == "channeled" and CleveRoids.channelStartTime and CleveRoids.channelDuration then
local remaining = CleveRoids.channelDuration - (GetTime() - CleveRoids.channelStartTime)
if remaining <= 0.1 then
return false -- Channel is done
end
end
return CleveRoids.CurrentSpell.type == "cast" or CleveRoids.CurrentSpell.type == "channeled"
end,
noselfcasting = function(conditionals)
-- If GetCurrentCastingInfo not available (shouldn't happen with Nampower), assume not casting
if not GetCurrentCastingInfo then return true end
-- Inverse of selfcasting with same prediction logic
if CleveRoids.CurrentSpell.type == "cast" and CleveRoids.castStartTime and CleveRoids.castDuration then
local remaining = CleveRoids.castDuration - (GetTime() - CleveRoids.castStartTime)
if remaining <= 0.1 then
return true -- Cast is done
end
end
local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo()
if CleveRoids.CurrentSpell.type == "channeled" and CleveRoids.channelStartTime and CleveRoids.channelDuration then
local remaining = CleveRoids.channelDuration - (GetTime() - CleveRoids.channelStartTime)
if remaining <= 0.1 then
return true -- Channel is done
end
end
-- Return TRUE if player is NOT casting AND NOT channeling
-- Explicit checks for safety
local isCasting = (casting and casting == 1) or false
local isChanneling = (channeling and channeling == 1) or false
return not isCasting and not isChanneling
return CleveRoids.CurrentSpell.type ~= "cast" and CleveRoids.CurrentSpell.type ~= "channeled"
end,
zone = function(conditionals)
@@ -2017,9 +2086,27 @@ CleveRoids.Keywords = {
end,
checkchanneled = function(conditionals)
return Or(conditionals.checkchanneled, function(channeledSpells)
return CleveRoids.CheckChanneled(channeledSpells)
end)
if conditionals.checkchanneled == true then
-- Boolean form [checkchanneled] - check if NOT channeling anything
return CleveRoids.CheckChanneled(nil)
else
-- String form [checkchanneled:SpellName] - check if NOT channeling that spell
return Or(conditionals.checkchanneled, function(channeledSpells)
return CleveRoids.CheckChanneled(channeledSpells)
end)
end
end,
checkcasting = function(conditionals)
if conditionals.checkcasting == true then
-- Boolean form [checkcasting] - check if NOT casting anything
return CleveRoids.CheckCasting(nil)
else
-- String form [checkcasting:SpellName] - check if NOT casting that spell
return Or(conditionals.checkcasting, function(castingSpells)
return CleveRoids.CheckCasting(castingSpells)
end)
end
end,
buff = function(conditionals)
@@ -2361,45 +2448,38 @@ CleveRoids.Keywords = {
end,
channeled = function(conditionals)
if GetCurrentCastingInfo then
local _, _, _, _, channeling = GetCurrentCastingInfo()
return channeling == 1
-- Use time-based prediction for accuracy
if CleveRoids.CurrentSpell.type == "channeled" and CleveRoids.channelStartTime and CleveRoids.channelDuration then
local remaining = CleveRoids.channelDuration - (GetTime() - CleveRoids.channelStartTime)
if remaining <= 0.1 then
return false -- Channel is done
end
end
return CleveRoids.CurrentSpell.type == "channeled"
end,
nochanneled = function(conditionals)
if GetCurrentCastingInfo then
local _, _, _, _, channeling = GetCurrentCastingInfo()
return channeling ~= 1
-- Use time-based prediction for accuracy
if CleveRoids.CurrentSpell.type == "channeled" and CleveRoids.channelStartTime and CleveRoids.channelDuration then
local remaining = CleveRoids.channelDuration - (GetTime() - CleveRoids.channelStartTime)
if remaining <= 0.1 then
return true -- Channel is done
end
end
return CleveRoids.CurrentSpell.type ~= "channeled"
end,
channeltime = function(conditionals)
-- Try both "player" literal and playerGuid for tracking lookup
local playerCast = CleveRoids.spell_tracking["player"] or CleveRoids.spell_tracking[CleveRoids.playerGuid]
if not playerCast or playerCast.type ~= "CHANNEL" then
if CleveRoids.ChannelTimeDebug then
if not playerCast then
DEFAULT_CHAT_FRAME:AddMessage("|cffffff00[channeltime]|r No tracking data for player or playerGuid: " .. tostring(CleveRoids.playerGuid))
-- Show what keys exist
local keys = ""
for k,v in pairs(CleveRoids.spell_tracking) do
keys = keys .. tostring(k) .. " "
end
if keys ~= "" then
DEFAULT_CHAT_FRAME:AddMessage("|cffffff00[channeltime]|r Tracking keys: " .. keys)
end
else
DEFAULT_CHAT_FRAME:AddMessage("|cffffff00[channeltime]|r Wrong type: " .. tostring(playerCast.type))
end
end
return false
-- Calculate remaining time (0 if not channeling)
local timeLeft = 0
if CleveRoids.CurrentSpell.type == "channeled" and CleveRoids.channelStartTime and CleveRoids.channelDuration then
local elapsed = GetTime() - CleveRoids.channelStartTime
timeLeft = CleveRoids.channelDuration - elapsed
-- Don't allow negative time
if timeLeft < 0 then timeLeft = 0 end
end
-- FOUND TRACKING - Debug this path too!
local timeLeft = playerCast.expires - GetTime()
local check = conditionals.channeltime
-- channeltime is stored as an array by the parser, get the first element
@@ -2407,26 +2487,37 @@ CleveRoids.Keywords = {
check = check[1]
end
if CleveRoids.ChannelTimeDebug then
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[channeltime FOUND]|r expires=%.2f, timeLeft=%.2f", playerCast.expires, timeLeft))
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[channeltime FOUND]|r check type=" .. type(check) .. ", value=" .. tostring(check))
if type(check) == "table" then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00[channeltime FOUND]|r operator=" .. tostring(check.operator) .. ", amount=" .. tostring(check.amount))
end
if type(check) == "table" and check.operator and check.amount then
-- Now compare: if not channeling, timeLeft is 0, so [channeltime:<0.5] returns true
return CleveRoids.comparators[check.operator](timeLeft, check.amount)
end
return false
end,
casttime = function(conditionals)
-- Calculate remaining time (0 if not casting)
local timeLeft = 0
if CleveRoids.CurrentSpell.type == "cast" and CleveRoids.castStartTime and CleveRoids.castDuration then
local elapsed = GetTime() - CleveRoids.castStartTime
timeLeft = CleveRoids.castDuration - elapsed
-- Don't allow negative time
if timeLeft < 0 then timeLeft = 0 end
end
local check = conditionals.casttime
-- casttime is stored as an array by the parser, get the first element
if type(check) == "table" and type(check[1]) == "table" then
check = check[1]
end
if type(check) == "table" and check.operator and check.amount then
local result = CleveRoids.comparators[check.operator](timeLeft, check.amount)
if CleveRoids.ChannelTimeDebug then
DEFAULT_CHAT_FRAME:AddMessage(string.format("|cff00ff00[channeltime RESULT]|r %.2fs %s %.2f = %s",
timeLeft, check.operator, check.amount, result and "TRUE" or "FALSE"))
end
return result
-- Now compare: if not casting, timeLeft is 0, so [casttime:<0.5] returns true
return CleveRoids.comparators[check.operator](timeLeft, check.amount)
end
if CleveRoids.ChannelTimeDebug then
DEFAULT_CHAT_FRAME:AddMessage("|cffff0000[channeltime ERROR]|r check structure invalid, returning false")
end
return false
end,
+125 -13
View File
@@ -1332,6 +1332,8 @@ function CleveRoids.ParseMsg(msg)
nostealth = true,
channeled = true,
nochanneled = true,
checkchanneled = true,
checkcasting = true,
dead = true,
alive = true,
help = true,
@@ -3058,6 +3060,15 @@ CleveRoids.Frame:RegisterEvent("START_AUTOREPEAT_SPELL")
CleveRoids.Frame:RegisterEvent("STOP_AUTOREPEAT_SPELL")
CleveRoids.Frame:RegisterEvent("SPELLCAST_CHANNEL_START")
CleveRoids.Frame:RegisterEvent("SPELLCAST_CHANNEL_STOP")
CleveRoids.Frame:RegisterEvent("SPELLCAST_START")
CleveRoids.Frame:RegisterEvent("SPELLCAST_STOP")
CleveRoids.Frame:RegisterEvent("SPELLCAST_FAILED")
CleveRoids.Frame:RegisterEvent("SPELLCAST_INTERRUPTED")
-- Nampower SPELL_CAST_EVENT for reliable channel tracking
if GetCurrentCastingInfo then
CleveRoids.Frame:RegisterEvent("SPELL_CAST_EVENT")
end
@@ -3170,6 +3181,18 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time
-- ALSO store under "player" literal for easier lookup
if caster == CleveRoids.playerGuid then
CleveRoids.spell_tracking["player"] = CleveRoids.spell_tracking[caster]
-- For CHANNEL events, capture duration for checkchanneled conditional
if action == "CHANNEL" then
CleveRoids.channelStartTime = GetTime()
CleveRoids.channelDuration = cast_time / 1000 -- Convert ms to seconds
end
-- For START events, capture cast time for checkcasting conditional
if action == "START" then
CleveRoids.castStartTime = GetTime()
CleveRoids.castDuration = cast_time / 1000 -- Convert ms to seconds
end
end
if CleveRoids.ChannelTimeDebug and caster == CleveRoids.playerGuid then
@@ -3237,26 +3260,115 @@ function CleveRoids.Frame:UNIT_CASTEVENT(caster,target,action,spell_id,cast_time
end
end
function CleveRoids.Frame:SPELLCAST_CHANNEL_START()
CleveRoids.CurrentSpell.type = "channeled"
-- BUGFIX: Update full casting state (for [casting] conditional)
if CleveRoids.UpdateCastingState then
CleveRoids.UpdateCastingState()
end
if CleveRoidMacros.realtime == 0 then
CleveRoids.QueueActionUpdate()
-- Nampower SPELL_CAST_EVENT handler for reliable channel tracking
-- This is the PRIMARY source of truth for channel state (not GetCurrentCastingInfo polling)
function CleveRoids.Frame:SPELL_CAST_EVENT(success, spellId, castType, targetGuid, itemId)
DEFAULT_CHAT_FRAME:AddMessage(string.format("[SPELL_CAST_EVENT] success=%s, spellId=%s, castType=%s",
tostring(success), tostring(spellId), tostring(castType)))
local CHANNEL = 4
if castType == CHANNEL and success == 1 then
-- Channel started successfully
CleveRoids.CurrentSpell.type = "channeled"
CleveRoids.CurrentSpell.castingSpellId = spellId
local spellName = GetSpellInfo(spellId)
if spellName then
CleveRoids.CurrentSpell.spellName = spellName
DEFAULT_CHAT_FRAME:AddMessage(string.format("[SPELL_CAST_EVENT] Channel START: %s", spellName))
end
-- Force immediate action update
CleveRoids.TestForAllActiveActions()
end
end
function CleveRoids.Frame:SPELLCAST_CHANNEL_START()
-- Set channel state immediately when event fires
-- Duration is captured by UNIT_CASTEVENT which fires earlier
CleveRoids.CurrentSpell.type = "channeled"
-- Try to get spell name from GetCurrentCastingInfo if available
if GetCurrentCastingInfo then
local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo()
if visId and visId > 0 then
CleveRoids.CurrentSpell.castingSpellId = visId
local spellName = GetSpellInfo(visId)
if spellName then
CleveRoids.CurrentSpell.spellName = spellName
end
end
end
-- Force immediate action update
CleveRoids.TestForAllActiveActions()
end
function CleveRoids.Frame:SPELLCAST_CHANNEL_STOP()
-- Channel ended - clear state immediately
CleveRoids.CurrentSpell.type = ""
CleveRoids.CurrentSpell.spellName = ""
-- BUGFIX: Update full casting state (for [casting] conditional)
if CleveRoids.UpdateCastingState then
CleveRoids.UpdateCastingState()
CleveRoids.CurrentSpell.castingSpellId = nil
-- Force immediate action update
CleveRoids.TestForAllActiveActions()
end
function CleveRoids.Frame:SPELLCAST_START()
-- Cast-time spell started
-- Duration is captured by UNIT_CASTEVENT which fires earlier
CleveRoids.CurrentSpell.type = "cast"
-- Try to get spell name from GetCurrentCastingInfo if available
if GetCurrentCastingInfo then
local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo()
if castId and castId > 0 then
CleveRoids.CurrentSpell.castingSpellId = castId
local spellName = GetSpellInfo(castId)
if spellName then
CleveRoids.CurrentSpell.spellName = spellName
end
end
end
if CleveRoidMacros.realtime == 0 then
CleveRoids.QueueActionUpdate()
-- Force immediate action update
CleveRoids.TestForAllActiveActions()
end
function CleveRoids.Frame:SPELLCAST_STOP()
-- Cast finished - clear state immediately
if CleveRoids.CurrentSpell.type == "cast" then
CleveRoids.CurrentSpell.type = ""
CleveRoids.CurrentSpell.spellName = ""
CleveRoids.CurrentSpell.castingSpellId = nil
-- Force immediate action update
CleveRoids.TestForAllActiveActions()
end
end
function CleveRoids.Frame:SPELLCAST_FAILED()
-- Cast failed - clear state immediately
if CleveRoids.CurrentSpell.type == "cast" then
CleveRoids.CurrentSpell.type = ""
CleveRoids.CurrentSpell.spellName = ""
CleveRoids.CurrentSpell.castingSpellId = nil
-- Force immediate action update
CleveRoids.TestForAllActiveActions()
end
end
function CleveRoids.Frame:SPELLCAST_INTERRUPTED()
-- Cast interrupted - clear state immediately
if CleveRoids.CurrentSpell.type == "cast" then
CleveRoids.CurrentSpell.type = ""
CleveRoids.CurrentSpell.spellName = ""
CleveRoids.CurrentSpell.castingSpellId = nil
-- Force immediate action update
CleveRoids.TestForAllActiveActions()
end
end
+6 -4
View File
@@ -63,17 +63,19 @@ CleveRoids.UpdateCastingState = function()
local castId, visId, autoId, casting, channeling, onswing, autoattack = GetCurrentCastingInfo()
-- Update CurrentSpell based on actual cast state
-- NOTE: Channel state is EXCLUSIVELY managed by SPELLCAST_CHANNEL_START/STOP events
-- This function NEVER touches channel state, only regular casts
if casting == 1 then
CleveRoids.CurrentSpell.type = "cast"
CleveRoids.CurrentSpell.castingSpellId = castId
elseif channeling == 1 then
CleveRoids.CurrentSpell.type = "channeled"
CleveRoids.CurrentSpell.castingSpellId = visId
else
elseif CleveRoids.CurrentSpell.type == "cast" then
-- Only clear if we were in a regular cast (not channel)
CleveRoids.CurrentSpell.type = ""
CleveRoids.CurrentSpell.castingSpellId = nil
end
-- DO NOT touch channel state here - events handle it
-- Always update metadata
CleveRoids.CurrentSpell.autoAttack = (autoattack == 1)
CleveRoids.CurrentSpell.onSwingPending = (onswing == 1)
CleveRoids.CurrentSpell.visualSpellId = visId