I re-scripted the entire Roblox health regen system using RunService

As some of us in the Roblox developer community may know, the default Roblox health regeneration script uses a while true do loop to regenerate players’ health. Upon seeing this, I asked myself: “Why does Roblox Corporation, a corporate entity that makes billions of dollars a year while totally caring about their player community (/j) still use this unoptimized garbage for their default regen script that’s used in the majority of games on the platform?”

So, I took it upon myself to re-script the health regen script. After about 20+ hours of scripting, testing and implementing brand-new features (not all in a row), I’m confident enough to publicly disclose that code for people to use in their own StarterCharacterScripts. So here you all go!

The script, collapsed for your reading convenience

WARNING: This script requires Attributes named “Multiplier” and “DeepWounded” to work.

-- == Catz's Custom Health Regen Script v1.0 == --
-- Because Roblox's default script is unoptimized.

-- Services
local RunService = game:GetService("RunService")

-- Values
local Character = script.Parent
local Humanoid:Humanoid = Character:WaitForChild("Humanoid")
local RegenTick = 0

local RegenConnection:RBXScriptConnection = nil
local WoundConnection:RBXScriptConnection = nil
local DeathConnection:RBXScriptConnection = nil

-- Functions
function RegenerationLoop(total,dt)
	RegenTick += dt
	if RegenTick >= (1/script:GetAttribute("Multiplier")) then
		RegenTick = 0
		Humanoid.Health += (Humanoid.MaxHealth/100)
	end
end

function DeepWounded(Status)
	pcall(function() RegenConnection:Disconnect() end)
	if Status == true then
		RegenTick = 0
	else
		RegenConnection = RunService.Stepped:Connect(RegenerationLoop)
	end
end

function Death()
	pcall(function() RegenConnection:Disconnect() end)
	pcall(function() WoundConnection:Disconnect() end)
	pcall(function() DeathConnection:Disconnect() end)
end

-- Connections
RegenConnection = RunService.Stepped:Connect(RegenerationLoop)
WoundConnection = script:GetAttributeChangedSignal("DeepWounded"):Connect(DeepWounded)
DeathConnection = Humanoid.Died:Connect(Death)

The new health regen script (which is by no means official) uses a mix of RunService connections and RBXScriptConnection:Disconnect() to protect against unnecessary code execution and memory leaks. By “unnecessary execution” I primarily mean that we don’t want to attempt to regenerate health when the Humanoid is already dead. Additionally, via the use of Attributes (through :SetAttribute()) one can customize the speed of the health regeneration in real-time, or stop it completely (via adjusting “Multiplier” and “DeepWounded” respectively).

Anyway here’s a brief Q&A section (also collapsed), and please let me know your thoughts on my work in the replies.

A brief Q&A section

Q: Why did you call it “DeepWounded”?
A: I got the name of this attribute from Dead by Daylight, actually.

Q: Does this work on NPCs?
A: Yes, just change Character:WaitForChild("Humanoid") to Character:WaitForChild("<whatever you named your NPC's humanoid>") and you’re good to go.

Q: But why go through any of this?
A: Yes.

Q: Can I use your script?
A: Yes, all I ask is that you credit me (although this isn’t required). I might release it on the Roblox Developer Library if this post gets enough attention.

Q: Are you a furry, as your avatar implies?
A: Yes, and please keep your distance with the flame throwers.

More questions may be added to this section if they get asked frequently enough

5 Likes

Is this script exploitable? I hear hackers can access localscripts and modify them, or does the disconnection prevent that?

exploiters can only see ModuleScripts & LocalScripts. they cannot ‘modify’ a localscript but can delete it or stop it from working unless it’s serverside.

2 Likes

Weird, irrelevant and unnecessary commentary about Roblox aside, the issues with the current Health script are largely trivial, from relying on legacy wait and no check for the Dead state.

While loops are not evident of lack of optimisation. The outer while loop is intervaled by an explicit condition which is that a new iteration doesn’t begin until HealthChanged fires which then it runs an inner loop until Health < MaxHealth… this is how health regeneration should be done. The outer loop is purely to allow the inner loop’s condition to check every time health changes.

Changing to RunService doesn’t necessarily make your code better or optimised. In this case, you remove explicit determination of when the loop should run and instead just make health regen a constant background process. There’s also an unnecessarily high number of pcalls with lambdas as if there’s no predictability for these connections. As for calculating how much health to restore, your code could take a page out of the default script’s book - it combines waiting and deltaTime to figure out how much to restore in the current step instead of divorcing the processes in a manner that’s less customisable and more unneeded work.

This is not the improvement you think it is. Small issues like the while loop continuing to execute when the humanoid is dead are quick fixes (e.g. check for Dead state or Health <= 0).

5 Likes

This can/should be a server script, original was too, so dont have to worry about it

It’s a server script. If it was local, the health regen would only occur on the client (not both client and server).

Yes I understand it could’ve been done differently. Am I going to do this again? No, actually, I literally just did this as a random side project. The improvement to performance is probably trivial.

Also, about the pcall() functions; I’ve made a habit of always pcalling a :Disconnect() because trying to do that to an RBXScriptConnection that either doesn’t exist or has already been disconnected throws an error. The former case I can understand, but the latter I’m simply annoyed by.

Finally, “improvements” are ultimately subjective, as your comment proves.

You’re fine not to redo this but you probably shouldn’t word the thread like you made a moderate or major improvement over the “unoptimised garbage” and then turn around saying that the potential performance increase is “probably trivial”. I think that’s something that should be made apparent without another’s reply if the intention is to provide this as a resource.

Ergo, “as if there’s no predictability for these connections”. That’s a problem. You shouldn’t need to guess when a connection is active or not enough in a system without too many moving parts. Using a light connection manager or being predictable about when connections are established can remove the need to unnecessarily pcall disconnections. Predictability is not just about the pcalls strewn here but it’s also a good habit to have for other types of systems you create as well.

I wouldn’t deflect advice as “subjective” because that’s not necessarily true depending on the nature of the advice and in this case, according to your desire of not having code execute when it shouldn’t, having a constant background process bound to the game step is not an improvement over only having it done when health regeneration needs to happen at Health is less than MaxHealth. Same goes for removing (be it intentionally or not) predictability *. Not particularly sure how my comment “proves” subjectivity - I’m not trying to be opinionated, there’s things that are really true in there.

* To be honest, with a re-read through of the code, the only unpredictable connection is RegenConnection. Every time the attribute is changed it always disconnects and then it’ll reconnect based on the attribute’s value. Using a flag to stop regeneration is better than constantly reconnecting an event. You can guard clause out the regeneration.

2 Likes

I thought RunService was only accessible from the client, my bad

1 Like

Do pardon my exaggeration then, it’s a bit of a habit of mine. Maybe Google what ADHD is, I understand not everyone’s heard of it and respect your opinions regarding this topic either way.

EDIT:

While ultimately it is up to the developer to decide how they want to program something I can’t help but agree with you here, there are some objective truths regarding Luau as a scripting language. But that’s more of a “purple category” topic, so I’ll leave it there.

EDIT2: Edited the first edit because DevForum rules.

1 Like