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