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.
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.
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
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.
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
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.