Regenerating Health in Coroutine Conflicts with Normal Roblox Regeneration

So, I have this coroutine that regenerates health based on a value inside the player’s “stats” folder. However, whenever this executes, I found that the player’s health is only regenerated by the “Health” script in the Character model. I created a “Health” script in StarterPlayerScripts, disabling the default Health script. This fixed the issue, and my coroutine ran fine.

So, my question is, how can I enable the Health script (or use a custom one) to have both a regeneration script and my coroutine? I realize that this may sound like a weird ask, but the idea is that the user’s health regenerates over time, but when they use an ability, it restores some amount of health to the player over some duration in ticks. i.e. The player’s health is at 50/100. Standard regeneration restores some amount every second. The player uses an ability and heals 10 health per second for five seconds ON TOP of the standard regeneration.

Coroutine:

for _, v in pairs(workspace:GetDescendants()) do
	if v.ClassName == "Highlight" and v.Parent == player.Character then
						
		-- heal function
		coroutine.wrap(function()
			-- heal some amount of health over some duration of time		
			for i = 1, tool.Stats.Duration.Value, 1 do
				v.Parent.Humanoid.Health += heal
				task.wait(1)
			end
		end)()
		
		debounce = true
		break
	else
		debounce = false
	end
end

Custom Health script:

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

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

local player = nil

for _, v in game:GetService("Players"):GetChildren() do
	if v.Name == Character.Name then
		player = v
	end
end


local REGEN_RATE = player:WaitForChild("stats"):FindFirstChild("regen").Value -- Regenerate this fraction of MaxHealth per second.
local REGEN_STEP = 3 -- Wait this long between each regeneration step.
--------------------------------------------------------------------------------

while true do
	while Humanoid.Health < Humanoid.MaxHealth do
		local dt = wait(REGEN_STEP)
		print("REGEN: ", REGEN_RATE)
		print("Before: ", Humanoid.Health)
		Humanoid.Health += REGEN_RATE
		print("After: ", Humanoid.Health)
	end
	Humanoid.HealthChanged:Wait()
end

Here is the Roblox default health regen for reference:

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

local REGEN_RATE = 1/100 -- Regenerate this fraction of MaxHealth per second.
local REGEN_STEP = 1 -- Wait this long between each regeneration step.

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

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

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

while true do
	while Humanoid.Health < Humanoid.MaxHealth do
		local dt = wait(REGEN_STEP)
		local dh = dt*REGEN_RATE*Humanoid.MaxHealth
		Humanoid.Health = math.min(Humanoid.Health + dh, Humanoid.MaxHealth)
	end
	Humanoid.HealthChanged:Wait()
end

I think you mean to multiply REGEN_RATE by dt, otherwise the units don’t make sense. You’re basically saying that a heal rate of “5% per second” means the health will go from 50/100 to 50.05/100 after one second.

That’s not really my problem. Both my script and the custom script work fine independently of each other. The problem is that the custom Health script overwrites the changes to Humanoid.Health from my regeneration script. I think the reason is because I’m writing to Humanoid.Health at the same time from two different scripts.

Let’s say that Humanoid.Health is accessed from the Health script when it is at 50/100. It adds some amount to the health.

At the same time, I activated my regeneration script, which adds some amount of health on top of that. Health at this point was also 50/100. Well, when this script adds health to Humanoid.Health, that change gets reversed by the Health script above. Instead of adding 20 health in the regen script, I take an amount of damage equivalent to the health restored by the regen script and then add 10 health from the Health script.

This renders my regeneration script effectively useless. The only time it works is when I disable the Health script entirely. But I want both.

I tested this and couldn’t figure out why it wouldn’t stack the way you want. You could try printing the before and after health values in each script to see if it is indeed a concurrent issue.

1 Like

I feel like this Part in the Roblox Provided is the issue

Humanoid.Health = math.min(Humanoid.Health + dh, Humanoid.MaxHealth)

Could be wrong tho

(oops, accidental reply, but nah, i didnt read)

When I print, I see that the regen script changes, then the screen flashes red as I take damage and the Health script kicks in. Here’s the results. Keep in mind that the Regen script occurs every 1 second, whereas my custom Health script runs once ever 3 seconds. Could it be that the changes aren’t being synced across the client and server?

  10:49:07.563  Regen Script - Health Before:  822  -  Client - Script:54
  10:49:07.563  Regen Script - Health After:  920  -  Client - Script:56
  10:49:08.226  Health Script - Health Before:  822  -  Server - Health:22
  10:49:08.226  Health Script - Health After:  850  -  Server - Health:24
  10:49:08.578  Regen Script - Health Before:  850  -  Client - Script:54
  10:49:08.578  Regen Script - Health After:  920  -  Client - Script:56
  10:49:09.594  Regen Script - Health Before:  920  -  Client - Script:54
  10:49:09.594  Regen Script - Health After:  920  -  Client - Script:56
  10:49:10.595  Regen Script - Health Before:  920  -  Client - Script:54
  10:49:10.595  Regen Script - Health After:  920  -  Client - Script:56
  10:49:11.243  Health Script - Health Before:  850  -  Server - Health:22
  10:49:11.243  Health Script - Health After:  878  -  Server - Health:24
  10:49:11.595  Regen Script - Health Before:  878  -  Client - Script:54
  10:49:11.595  Regen Script - Health After:  920  -  Client - Script:56
  10:49:14.243  Health Script - Health Before:  878  -  Server - Health:22
  10:49:14.243  Health Script - Health After:  906  -  Server - Health:24

Its possible that if Health is being set from the client and from the server at the same time that it wont check which one happened first and could cause your problem. I’m not sure of a good way to check this.

1 Like

Let me try rewriting the LocalScript into a normal server-side script. I’ll check in when I’m done.

Personally I would take an approach by modifying the default “Health” script to respect some boosted regen stat, and then directly modify this boosted regen stat. In this example I have set BoostedRegen to 0.1 so they will regenerate (0.01 + 0.1)% Health every second.


Example Script (Named Health and in StarterCharacterScripts to override the default)

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

local REGEN_RATE = 1
local REGEN_PRECENTAGE = 1/100

while true do
	while Humanoid.Health < Humanoid.MaxHealth do
		task.wait(REGEN_RATE)
		
		local BoostedRegen = Humanoid:GetAttribute("BoostedRegen")
		if BoostedRegen and BoostedRegen ~= 0 then 
			Humanoid.Health = math.min(Humanoid.Health + Humanoid.MaxHealth * (REGEN_PRECENTAGE + BoostedRegen), Humanoid.MaxHealth)
		else
			Humanoid.Health = math.min(Humanoid.Health + Humanoid.MaxHealth * REGEN_PRECENTAGE, Humanoid.MaxHealth)
		end
	end
	Humanoid.HealthChanged:Wait()
end
1 Like

I resolved the issue. As I suspected before, the issue was that the changes to the health were made on the client side in one script and on the server side in the other. I resolved this issue by creating a RemoteEvent and firing it from the regeneration local script to the server to update the health. No update needed to be made to the custom Health script.

LocalScript:

for _, v in pairs(workspace:GetDescendants()) do
	if v.ClassName == "Highlight" and v.Parent == player.Character then
		local duration = tool.Stats.Duration.Value
		coroutine.wrap(function()
			HealedEvent:FireServer(tool, heal, duration)
		end)()
		
		debounce = true
		break
	else
		debounce = false
	end
end

SeverScript:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local HealedEvent = ReplicatedStorage:WaitForChild("Healed")

local function regen(player, tool, heal, duration)
	for i = 1, duration, 1 do
		print("Regen Script - Health Before: ", player.Character.Humanoid.Health)
		player.Character.Humanoid.Health += heal
		print("Regen Script - Health After: ", player.Character.Humanoid.Health)
		task.wait(1)
	end
end

HealedEvent.OnServerEvent:Connect(regen)
1 Like

Hi, just letting you know this is a terrible idea! You are letting the client have control over the player’s health - meaning an exploiter could simply fire the remote and regenerate their health as they wish.
I suggest to either add some sort of sanity check or, better, remake your system from scratch by making it completely server-sided and leaving no access to the client.

1 Like

Thanks for the heads-up. I don’t know a lot about secure scripting, so that just made my job a lot more difficult, lol. But it needs to be done! There are a few places where I’m firing remote events from the client to the server to change some stat (damage to enemies, stamina cost, health regen, etc). I’ll have to look deep into my code to figure out how I can move all of this server-side. Especially considering some of these things are stored on the localPlayer - just to make things more difficult.

But really, I’m grateful for your input. I’ll look into this. Thanks!

1 Like

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