Made a custom health regeneration code that heal linearly, after set delay. Please review

I used to code with roblox Lua 2 year ago, not an expert, still an amateur. Returned to scripting, learned my mistake to always ask review of working code to attain improvement.

So. just recently made a code, title says it all but i say it again, a custom health regeneration that heal in an exponential amount after set cooldown, reset cooldown if another damage is taken. Tested the code and it work smoothly, perfectly fine, but im looking if there’s a way to improve or optimize this code someway. Feel free to look

-- Gradually regenerates the Humanoid's Health over time.

local RunService = game:GetService("RunService")

local REGEN_RATE = 0.01 -- Exponential rate of health gain increment
local REGEN_AMOUNT = 0 -- How many health point gained
local REGEN_DELAY = 5 -- Wait this long to start regenerate health
local REGEN_TIMER = 0 -- Time after last damage

local lastHealth = 0

--------------------------------------------------------------------------------

local Character = script.Parent
local Humanoid = Character:WaitForChild'Humanoid'

--------------------------------------------------------------------------------

while true do
	repeat task.wait() until Humanoid.Health < Humanoid.MaxHealth
	
	lastHealth = Humanoid. Health -- Take note of current health after damage
	
	-- Cooldown session
	while REGEN_TIMER < REGEN_DELAY do
		REGEN_TIMER += RunService.Heartbeat:Wait()

		if Humanoid.Health < lastHealth then -- If player take damage during regen cooldown. Break cooldown
			REGEN_TIMER = 0
			lastHealth = Humanoid.Health
		end
	end
	
	-- Regen session
	while Humanoid.Health < Humanoid.MaxHealth do
		
		REGEN_AMOUNT += REGEN_RATE * RunService.Heartbeat:Wait()
		Humanoid.Health += REGEN_AMOUNT
		
		lastHealth = Humanoid.Health
		
		if Humanoid.Health < lastHealth then break end -- Break regen if damage is taken
	end
	
	REGEN_AMOUNT = 0
	REGEN_TIMER = 0
end

I fixed it up as best as I could but something about this function is a little bit wrong. I don’t think this code is drastically horrible in terms of performance due to the limited scope of when it runs, but I think it’s a little bit of a code smell.

Changing this function’s trigger to be a GetPropertyChangedSignal("Health"), you remove all of the infinite waiting and checking done by these two lines: while true do repeat task.wait() until Humanoid.Health < Humanoid.MaxHealth but you introduce another issue of creating multiple loops and race conditions at the same time.

Secondly, your healing function is not exponential, it’s linear. I can understand why you think it may be exponential as it’s very quick, but an exponential function is defined as: f(x) == a^x whereas you are doing f(x) == a * t, a being your regen rate, t being Heartbeat which is just deltaTime. I do want to point out, you’re using delta time, which is very good.

Here is my crappy version that I made, but I’m going to come back to this thread and try again. Note that this code I’m attaching is problematic.

-- Gradually regenerates the Humanoid's Health over time.

local RunService = game:GetService("RunService")

local REGEN_RATE = 0.01 -- Exponential rate of health gain increment
local REGEN_AMOUNT = 0 -- How many health point gained
local REGEN_DELAY = 5 -- Wait this long to start regenerate health
local REGEN_TIMER = 0 -- Time after last damage

local lastHealth = 0

--------------------------------------------------------------------------------

local Character: Model = script.Parent
local Humanoid: Humanoid = Character:WaitForChild("Humanoid")

--------------------------------------------------------------------------------

Humanoid:GetPropertyChangedSignal("Health"):Connect(function()
    if Humanoid.Health <= Humanoid.MaxHealth then
        lastHealth = Humanoid.Health -- Take note of current health after damage
	
        -- Cooldown session
        while REGEN_TIMER < REGEN_DELAY do
            REGEN_TIMER += RunService.Heartbeat:Wait()
    
            if Humanoid.Health < lastHealth then -- If player take damage during regen cooldown. Break cooldown
                REGEN_TIMER = 0
            end
        end
        
        -- Regen session
        while Humanoid.Health < Humanoid.MaxHealth do
            
            REGEN_AMOUNT += REGEN_RATE * RunService.Heartbeat:Wait() -- this isn't exponential, this is linear btw
            Humanoid.Health += REGEN_AMOUNT
            
            lastHealth = Humanoid.Health --this might result in a race condition, can be fixed by using a local variable

            if Humanoid.Health < lastHealth then break end -- Break regen if damage is taken
        end
        
        REGEN_AMOUNT = 0
        REGEN_TIMER = 0
    end
end)
2 Likes

Ah, thank you for pointing out. I know something was off with the constant wait() checking but can’t quite put a finger on it. Still, i have two question.

  1. The reason im avoiding using event is, last time im trying to make linear regeneration (Thanks for pointing out that this isnt exponent, still a satisfying regen style that i was aiming, i just called it wrong). Im stuck at figuring how to use event to detect damage for regen breaking without freezing the code and making the result haywire, especially GetPropertChangedSignal also detect health increase. Is there any better way i can check damage during a loop using event?

  2. Mind explaining me what is race condition? That is currently the best method i could think of to detect damage during healing process?

EDIT : After writing this reply. I reviewed my own code and noticed i put [lastHealth = Humanoid.Health] before the condition check, essentialy failing to do a proper job. Shoot, is this what you mean by race condition?

Revised the code. Im gonna post the finished code here just in case

-- Gradually regenerates the Humanoid's Health over time.

local RunService = game:GetService("RunService")

local REGEN_RATE = 0.01 -- Exponential rate of health gain increment
local REGEN_AMOUNT = 0 -- How many health point gained
local REGEN_DELAY = 5 -- Wait this long to start regenerate health
local REGEN_TIMER = 0 -- Time after last damage

local isRegen = false
local debounce = false

--------------------------------------------------------------------------------

local Character = script.Parent
local Humanoid = Character:WaitForChild'Humanoid'

--------------------------------------------------------------------------------
Humanoid:SetAttribute("isRegenerating", false)

local function attributeChange(bool)
	isRegen = bool
	Humanoid:SetAttribute("isRegenerating", bool)
end

local function waitForCooldown()
	local lastHealth = Humanoid.Health -- Take note of current health after damage
	
	while REGEN_TIMER < REGEN_DELAY do
		REGEN_TIMER += RunService.Heartbeat:Wait()

		if Humanoid.Health < lastHealth then -- If player take damage during regen cooldown. Break cooldown
			REGEN_TIMER = 0
			lastHealth = Humanoid.Health
		end
	end
end

Humanoid:GetPropertyChangedSignal("Health"):Connect(function()
	if Humanoid.Health < Humanoid.MaxHealth and not isRegen and not debounce then
		
		debounce = true 
		--[[]
		This debounce prevent multiple regeneration running at the same time because multiple damage is taken at quick succession.
		Hard to explain but uh, imagine that everytime you got damaged, a single instance of this code is made, making regeneration stack
		]]

		-- Cooldown session
		waitForCooldown()

		-- Regen session
		local lastHealth = Humanoid.Health
		
		attributeChange(true)
		
		while Humanoid.Health < Humanoid.MaxHealth do

			REGEN_AMOUNT += REGEN_RATE * RunService.Heartbeat:Wait()
			Humanoid.Health += REGEN_AMOUNT

			if Humanoid.Health < lastHealth then 
				attributeChange(false)

				REGEN_AMOUNT = 0
				REGEN_TIMER = 0
				
				waitForCooldown()
				
				attributeChange(true)
			end -- Break regen if damage is taken
			lastHealth = Humanoid.Health
			
		end
		
		attributeChange(false)
		
		REGEN_AMOUNT = 0
		REGEN_TIMER = 0
		debounce = false
	end
end)
1 Like

I just realized that the function that I put in there would instantly crash your game, because every time it edited your health, it would spawn another loop. Woops.

That being said, the best way to do it would be to save the time the user’s health and then save that lastHealth. If the health is smaller the lastHealth variable then you can set a variable the breaks the loop to a true condition. This is less important as long as you find a performant alternative.

A race condition, that I can best describe is when two functions interfere with each other because they are working on the same thing. Take a look at this stackoverflow post, or feel free to google it as I’m sure they can describe it better than I can.

Your race condition is not as horrible as it possibly could be, it just wouldn’t be functional.

Roblox is multi-threaded, meaning it runs several processes on the core CPU. This means multiple events and threads can run at the same time. For example, each event connecting to a function is a thread.

It’s possible you may have an instance where the health changes after this line of code:

if Humanoid.Health < lastHealth then break end

Then, because your script doesn’t detect that health change, it just ups the health again and continues healing. To fix this issue, I’d recommend using GetPropertyChangedSignal("Health"), and checking whether the health has changed is actually different from your function. Just be careful as everytime you change the users health within the script, it’ll fire this function. (Don’t cause an infinitely recursive function!) That being said, your function will work 99.98% of the time, because race conditions are so rare as it depends upon circumstance and timing. I’d do a bit of testing and see if it shows up, but I think you’ll be fine for the most part.

1 Like

I’ve already fixed my code and it work 100% of the time now without any noticeable issue, thanks for the advice. I already posted the said code above your reply. Feel free to review it again but i think its already perfectly functional enough. Though in a niche situation, i just have to make it adaptable to max health modification

3 Likes

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