How to stop code with a task.wait from executing after character reset

I have this pseudo health system using attributes, where if their pseudo health reaches 0, they get knocked out and ragdolled, and other vfx to go along with it. You can see my comments detailing my issue in the module script

Server Script
local players = game:GetService("Players")
local pseudoHealthModule = require(script.pseudoHealthModule)

players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		pseudoHealthModule:Create(player)
	end)
end)
Module Script
local pseudoHealth = {}

local RS = game:GetService("ReplicatedStorage")
local ragdollModule = require(RS.helperModules.ragdollModule)
local knockedOutEvent = RS.remoteEvents.phbEvents.knockedOutEvent

local ATTRIBUTE_NAME = "PsuedoHealth"
local MAX_HEALTH = 100
local MIN_HEALTH = 0
local REGEN_RATE = .1
local REGEN_STEP = .1
local KNOCKOUT_DURATION_SECONDS = 10

function pseudoHealth:Create(player)
	local character = player.Character
	local humanoid: Humanoid = character.Humanoid
	humanoid:SetAttribute(ATTRIBUTE_NAME, MAX_HEALTH)
	
	local _knockedOut = false
	
	humanoid:GetAttributeChangedSignal(ATTRIBUTE_NAME):Connect(function()
		local health = humanoid:GetAttribute(ATTRIBUTE_NAME)
		
		
		if (health == 0 and not _knockedOut) then
			warn("KNOCKED OUT")
			_knockedOut = true 
                         --[vfx stuff]--
			ragdollModule:RagdollCharacter(character)
			knockedOutEvent:FireClient(player, true)
			
			task.wait(KNOCKOUT_DURATION_SECONDS)

			--[for testing, I commented the actual 3 lines of code below and used a print statement]
			--[[
				how do I keep the code below the task.wait from executing when the character resets?
				Is there like a way to cancel or stop the execution or delete a thread (is that the right word)?
				Because if the actual code below, that I commented out, runs after the character resets,
				the ragdoll function, for example, where I get the player from the character that was passed, will result in
				player = nil because it's like the character doesn't exist anymore since they reset; it's like the old pre-reset reference
				to the character was passed instead of the reference to the actual current after-reset character
			]]
			
			print("HELLO") -- why this still print even after character reset? :[

			--	_knockedOut = false
			--	ragdollModule:UnragdollCharacter(character)
			--	knockedOutEvent:FireClient(player, false)
		end
		
		health = math.clamp(health, MIN_HEALTH, MAX_HEALTH)
		humanoid:SetAttribute(ATTRIBUTE_NAME, health)
	end)
	
	
	--[health regeneration]--
	while true do
		if not _knockedOut then
			local health = humanoid:GetAttribute(ATTRIBUTE_NAME)
			
			if health < MAX_HEALTH then
				health = math.clamp(health + REGEN_RATE, MIN_HEALTH, MAX_HEALTH)
				humanoid:SetAttribute(ATTRIBUTE_NAME, health)
			end
		end
		task.wait(REGEN_STEP)
	end
	
	
end

return pseudoHealth

When the character resets while the countdown for the knockout duration is still ticking, the code below the task.wait will still run, and I don’t want it to run; I only want the code to execute when the task.wait waited for the full knockout duration, while their character was still alive and didn’t reset. So it makes sense for the code to not execute–for the countdown to “reset”–when the character resets as well. I’m planning on adding an executing/gripping system to this, and it won’t work with this problem.

Also, If there are other ways of making an executing/gripping system that you may know of, that are better than what I am doing rn, plz link some stuff as well :D.

1 Like

You need a new function:

function waitConditional(conditionCheckFunction, duration, callback, ...)
local t = duration
while t > 0 do if not conditionCheckFunction() then return end t-=task.wait() end
callback(...)
end

Written on DevForum. Contains formatting issues.

Then, replace

with:

waitConditional(function()return humanoid.Parent and humanoid.Parent.Parent and humanoid.Parent == player.Character end, KNOCKOUT_DURATION_SECONDS, print, 'HELLO')

This will wait 5 seconds. If the character exists, it will run callback, but it will also stop yielding if the condition is not met before duration seconds have passed.


Alternatively, you can use coroutine.close if I remember correctly. This will “kill” a thread but will put “cannot resume dead coroutine” in the output.


Another method is just to reference the player’s character again, or maybe just prevent them from resetting.

1 Like

Additionally to the solution already suggested, you did mention you are reseting the character, so the character spawns again after being destroyed?

Each time the player spawns by first time or respawn after reset, this function will be called again pseudoHealth:Create(player), causing a double while loop (health regeneration)

You are stacking multiple while loop threads each time the player is reseted

Take a look at coroutine.

Not sure if it is what you want, but you can try this:

local thread = task.spawn(function()
	print("stuff happens before")
	
	task.wait(5)
	
	print("stuff happens afterwards")
end)

if somecondition then
	task.cancel(thread) -- Cancel the thread early
end

“stuff happens afterwards” will never print. But there is probably a much better solution to this problem.

1 Like

how would I avoid the double white loops (health regeneration)?

This didn’t work. I used a print statement to track if the timer was still counting, and it was still counting even after I reset. It only stopped the moment I respawned, though.

This is what my module script code looked like

local pseudoHealth = {}

local RS = game:GetService("ReplicatedStorage")
local ragdollModule = require(RS.helperModules.ragdollModule)
local knockedOutEvent = RS.remoteEvents.phbEvents.knockedOutEvent
local ragdollEvent = RS.remoteEvents.ragdollEvents.ragdollEvent

local ATTRIBUTE_NAME = "PsuedoHealth"
local MAX_HEALTH = 100
local MIN_HEALTH = 0
local REGEN_RATE = .1
local REGEN_STEP = .1
local KNOCKOUT_DURATION_SECONDS = 10


function waitConditional(conditionCheckFunction, duration, callback, ...)
	local t = duration
	while t > 0 do 
		if not conditionCheckFunction() then return end 
		t-=task.wait() 
	end
	callback(...)
end



function pseudoHealth:Create(player)
	local character = player.Character
	local humanoid: Humanoid = character.Humanoid
	humanoid:SetAttribute(ATTRIBUTE_NAME, MAX_HEALTH)
	
	local _knockedOut = false
	
	humanoid:GetAttributeChangedSignal(ATTRIBUTE_NAME):Connect(function()
		local health = humanoid:GetAttribute(ATTRIBUTE_NAME)
		
		
		if (health == 0 and not _knockedOut) then
			warn("KNOCKED OUT")
			_knockedOut = true 
			--ragdollModule:RagdollCharacter(character)
			--knockedOutEvent:FireClient(player, true)
			
			waitConditional(function()
				print(humanoid.Parent and humanoid.Parent.Parent and humanoid.Parent == player.Character)
				return humanoid.Parent and humanoid.Parent.Parent and humanoid.Parent == player.Character
			end, KNOCKOUT_DURATION_SECONDS, print, "hello")
			
			--_knockedOut = false
			--ragdollModule:UnragdollCharacter(character)
			--knockedOutEvent:FireClient(player, false)
		end
		
		health = math.clamp(health, MIN_HEALTH, MAX_HEALTH)
		humanoid:SetAttribute(ATTRIBUTE_NAME, health)
	end)
	
	
	--[health regeneration]--
	while true do
		if not _knockedOut then
			local health = humanoid:GetAttribute(ATTRIBUTE_NAME)
			
			if health < MAX_HEALTH then
				health = math.clamp(health + REGEN_RATE, MIN_HEALTH, MAX_HEALTH)
				humanoid:SetAttribute(ATTRIBUTE_NAME, health)
			end
		end
		task.wait(REGEN_STEP)
	end
	
	
end

return pseudoHealth


return pseudoHealth

So far this seems to be the only solution that works, but I wanna see what other responses I get before marking a solution

task.delay(KNOCKOUT_DURATION_SECONDS, function()
    if Player.Character.Humanoid.Health > 0 then
       -- ...
    end
end)

Have you tried this?

this works as well. I also noticed I would need to use

if humanoid.Health > 0 then
   --
end)

Instead of

if player.Character.Humanoid.Health > 0 then
   --
end)

bc humanoid seems to be referencing the instance of the humanoid of the character as it reset, while player.Character.Humanoid references the humanoid of the character after reset–the current character

In whatever way you prefer, you could break the while loop when _knockedOut variable becomes true, because if player is knockedout theres no reason to keep checking if its allowed to regen health or not, the next time the character respawns will run the function again including a new loop and the old one would be disabled.

I tested your script and yes, everytime the player respawn a new while loop is stacked, which is pretty bad and will cause regeneration to be faster and faster the more times the player dies.

1 Like

I will mark this as the solution as it essentially achieves what I want–to stop code from executing after a certain duration

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.