Roblox Thirst System

Hello!

I am attempting to make a thirst and hunger system for my game. I have had everything working for a while now, but recently decided that I wanted to make your character loose thirst significantly faster when sprinting. This helps add an incentive to not sprint for as long. This being said, I am fairly new to scripting and when I tried to implement a system like this it did not work. I tried two different ways but neither of them worked. For some reason, the game either only grabs the value of the player at the start of the game, or does not recognize when the sprint script changes the player’s walk speed. Could someone who knows what they are doing help me please? I have attached the original script, as well as the attempts I made with highlights. Thanks!



1 Like

A couple things to note here.

  1. If your sprint script is on the client, then the server will keep reading the Humanoid.WalkSpeed as 16. In that case, you can check if the Humanoid.MoveDirection.Magnitude > 0 (to check if the player is moving with WASD/other controls) and if the HumanoidRootPart.AssemblyLinearVelocity > 0 (to check if the player is actually moving).
  2. Don’t assume the Player.Character will always exist.

Putting these 2 in practice, you get something along the lines of:

local function ReduceThirst()
	for _, player in Players:GetPlayers() do
		-- Just to make sure in case the creation of Thirst gets delayed somehow
		local thirst = player:FindFirstChild("Thirst")
		
		if thirst ~= nil then
			local character = player.Character

			-- Get Humanoid and HumanoidRootPart if character exists
			local humanoid = if character ~= nil then character:FindFirstChild("Humanoid") else nil
			local humanoidRootPart = if character ~= nil then character:FindFirstChild("HumanoidRootPart") else nil

			-- Check if Humanoid and HumanoidRootPart exists
			if humanoid ~= nil and humanoidRootPart ~= nil then
				-- Check if the player is moving with WASD/other controls
				local isMoving = humanoid.MoveDirection.Magnitude > 0
				
				-- Check the speed of the HumanoidRootPart (ignoring Y axis)
				local speed = (humanoidRootPart.AssemblyLinearVelocity * Vector3.new(1, 0, 1)).Magnitude
				
				if isMoving == true then
					thirst.Value -= Increment * (if speed > 16 then 3 else 1)
				end
				
				if thirst.Value <= 0 then
					thirst.Value = 0
					humanoid.Health = 0
				end
				
				UpdateThirst:FireClient(player, thirst.Value / MaxThirst)
			end
		end
	end
end

Ideally, you want to be doing thirst/stamina stuff on the client anyways to prevent as much input and network lag as possible. It doesn’t really make sense to do it on the server due to the fact that players have full network ownership of their character anyways, so nothing is stopping them from simply teleporting or overriding the default behaviors.

2 Likes

Thank you very much for helping, I am encountering a slight problem though. It works perfectly fine when sprinting: thirst goes down by 3 every 3 seconds. However, the thirst does not drain unless the player is moving, and when walking the thirst still goes down by 3, then 1, then 3 again.

Also I thought preforming the task server side would prevent it from being messed with by the client.

Change the speed variable into this instead: (speed will read 16.00x without it)

local speed = math.round((humanoidRootPart.AssemblyLinearVelocity * Vector3.new(1, 0, 1)).Magnitude)

This is absolutely true for things such as data, but clients has full control over their character. Nothing is stopping them from simply teleporting x amount of distance. This is why most sprint scripts you see are done on the client as LocalScripts. Generally, things like cooldown and stamina-based systems are done on the client with potentially server-side sanity checks (or anti-exploits by definition) to avoid as much input and network lag.

It’s essentially the same principle as client-side rendering. If you have a gun and you hit something on the client, play the hit sound and show the hitmarker regardless if the server actually hits something or not. This will lead to “ghost hits”, but players with decent/good connections will have a smoother UX experience.

1 Like

Apologies, you wanted the thirst to go down regardless if they are moving or not. Simply remove the isMoving variable and it’s condition.

1 Like

Makes sense, I understand now.

Wow everything works perfectly, thank you so much for your help. Have a great day!

Quick question: What does making the function local do?

2 main things really.

  1. By localizing your function, the function is limited to within that scope and any scopes within it.
--!strict

do -- new scope
	local function temporaryFunction()
		print("do task A")
	end
	
	temporaryFunction() -- this will work
	
	do -- new scope
		temporaryFunction() -- this will also work and will do task A
		
		local function temporaryFunction()
			print("do task B")
		end
		
		temporaryFunction() -- this will do task B
	end
	
	temporaryFunction() -- this will to task A
	
	function globalFunction()
		print("do task C")
	end
end

temporaryFunction() -- this will error since it is defined as a local function
globalFunction() -- this will work since it is defined as a global function
  1. Functions with local on the top-most level is faster to search internally (by ~25% according to this benchmark: Any advantage in declaring a function as local? - #31 by rogeriodec_games) because of the way it traverses through these stacks. You don’t have to memorize this but it’s good to know anyways.

There is no point in not adding local to your functions really, unless you are doing something similar to point 1.

Important thing to note: Global functions are not the same as global functions in the _G global variable. Generally, it’s not good to store stuff in the global variable _G, and ModuleScripts work better (technically both in performance and organization), but you could if you wanted to.

1 Like

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