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