You know what, I actually just thought of something you can do. This is in regards to after Tomarty posted about event-based systems and I re-read the OP.
You’re using animations, yes? Make use of the AnimationTrack API then. Get rid of all that wait nonsense. Set up either keyframe names or keyframe markers. For keyframe names, use AnimationTrack.KeyframeReached. For markers, use AnimationTrack.GetMarkerReachedSignal.
For keyframe names, make three: Block (the very first keyframe, insert a blank one if there is no keyframe at 0:00 of the animation), StunStart (when true should be passed to the remote) and StunEnd (ditto, but false). For markers, make two events: Block and Stun. Have the first keyframe use the block marker and pass true as a parameter in the marker edit view, have the first stun keyframe use the stun marker pass true and the second one pass false. Regardless of what you choose, connect to AnimationTrack.Stopped and have it pass Block and false to the remote.
Easily a better solution than anything currently suggested. Event-based systems should always be your go-to rather than relying on wait which should scarcely be used in production code. If you need a wait, look to a Heartbeat-based wait from RunService (though I don’t know how to do that myself). You’d probably also be able to code cancellation from it or make a Timer API to further implement events.
Here’s some code samples of how to set this up with each item respectively.
NOTE NOTE NOTE: If you call LoadAnimation on your animations beforehand, don’t connect these events within your ButtonDown functions otherwise it’ll spawn up several connections and cause potential memory leaks or inflations. Connect events beforehand outside of the ButtonDown events, either from ModuleScripts containing the functions or just somewhere in the main script.
With named keyframes:
remoteEvent:FireServer("Block", true)
-- NOTE: If you ever use math.randomseed, consider using Random.new
-- For your random generator instead of math.randomseed + math.random
CurrentBlockAnimation = block_animations[math.random(1, #block_Animations)]
CurrentBlockAnimation.KeyframeReached:Connect(function (keyframeName)
-- We don't use ternary otherwise it'd fire for every keyframe
if keyframeName == "StunStart" then
remoteEvent:FireServer("Stun", true)
elseif keyframeName == "StunEnd" then
remoteEvent:FireServer("Stun", false)
end
end)
CurrentBlockAnimation.Stopped:Connect(function ()
remoteEvent:FireServer("Block", false)
end)
CurrentBlockAnimation:Play()
With keyframe markers:
remoteEvent:FireServer("Block", true)
CurrentBlockAnimation = block_animations[math.random(1, #block_Animations)]
CurrentBlockAnimation:GetMarkerReachedSignal("Block"):Connect(function ()
remoteEvent:FireServer("Block", true)
end)
CurrentBlockAnimation:GetMarkerReachedSignal("Stun"):Connect(function (boolean)
remoteEvent:FireServer("Stun", boolean)
end)
CurrentBlockAnimation.Stopped:Connect(function ()
remoteEvent:FireServer("Block", false)
end)
CurrentBlockAnimation:Play()
This should get rid of your worries of having to cancel functions entirely and you can have something both event-based and highly accurate for your case. I definitely recommend relying on events from the AnimationTrack keyframes rather than arbitrary waits (which in themselves can be desynchronised due to the extra time on wait spent searching for an open slot on the thread scheduler). 