Server or client? Where should I change Humanoid properties?

I have a combat system that should gradually slow the player upon charging an attack. I use TweenService on the server to control the WalkSpeed, etc. properties of the Humanoid.

My thought process is, since the client has control on Humanoid properties already, wouldn’t it be more efficient to just tween them on the client and just “trust the client”? What is the best way to approach this? I would appreciate any help.

Client:

local function handleAttack(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.Begin and not humanoid:GetAttribute("Cooldown") then		
		attackEvent:FireServer()
		
		attackTrack:Play()
	end
end

ContextActionService:BindAction("Attack", handleAttack, true, Enum.UserInputType.MouseButton1)

Server:

pushEvent.OnServerEvent:Connect(function(player)
	local character = player.Character
	local humanoid = character.Humanoid
	
	if humanoid:GetAttribute("Cooldown") then return end
	
	TweenService:Create(humanoid, TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, 0, true), {WalkSpeed = 0, JumpPower = 50}):Play()
	
	task.spawn(function()
		humanoid:SetAttribute("Cooldown", true)
		
		wait(2)
		
		humanoid:SetAttribute("Cooldown", false)
	end)
end)
2 Likes

Attributes aren’t shared data; meaning you’re going to have to check the server anyway to get validation that the humanoid is being cool-downed.

Either way, there’s really no point - they do the same thing besides some minor performance differentiations with the server validation.

3 Likes

Your comment is well appreciated. Apologies but my concern is whether its best to tween humanoid.WalkSpeed on client or server. I have read somewhere that tweening on the server would not be optimal performance-wise, so I’m asking if I can just tween humanoid properties on the client since the client has access to humanoid properties already so might as well just trust them and break the golden rule. Thank you for your prompt response.

1 Like

well stuff like WS/JP can be done in client sided even if in what i remember tweenservice don’t work for client sided, (i can be wrong too), but for informations like Health/MaxHealth etc it needs to be done in serversided

1 Like

It’d be smoother to tween it on the client.

2 Likes

Yes, I would be risking exploitation, right?

not really since roblox are going to add hyperion to microsoft store and android and linux pretty soon so exploiters will be allot rarer than they are now because microsoft and android are the actual platform with the highest ammount of exploiters since 64bits roblox not exploitable anymore

I think you could mix it a little bit; Tween done on client-side, while waiting the tween’s completion and then setting the player’s speed to that amount on server to avoid desynchronization with other players or the server itself.

If the tween were done entirely on client-side without the server setting their new speed, other parts of the server wouldn’t know that player’s new speed because changes made locally don’t replicate to server or other players.

Putting this mixup in practice:

-- Local Script
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

local Client = Players.LocalPlayer
local Event = ReplicatedStorage.TweenSpeed -- Remote Function
-- It should be a remote function because there could occur some desynchronization between client-server
-- Which means that if we used an remote event, the client could tween animation too late
-- Providing him speed when he shouldn't have.

function Event.OnClientInvoke(NewSpeed: number?, Duration: number)
	print(`Local: Got requested to tween speed to {NewSpeed} in {Duration}s`)
	local Character = Client.Character;
	if not Character then
		-- Client's character probably died or has not been fully loaded yet.
		return false
	end

	local Humanoid = Character.Humanoid
	if not Humanoid then
		-- The previous logic applies to this too
		return false
	end

	local TweenInformation = TweenInfo.new(
		Duration or 1, -- Time: 1 is the default when none is received.
		Enum.EasingStyle.Quint, -- EasingStyle
		Enum.EasingDirection.InOut -- EasingDirection
	)

	local Tween = TweenService:Create(Humanoid, TweenInformation, {
		WalkSpeed = NewSpeed
	})

	Tween:Play()
	Tween.Completed:Wait()
	
	print(`Local: Finished tweening of speed to {NewSpeed}`)
	return true
end
-- ServerScript
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local function TweenSpeed(Player: Player, Speed: number, Duration)
	local Character = Player.Character
	if not Character then
		-- Player died?
		return
	end
	
	local Humanoid = Character:FindFirstChild("Humanoid")
	if not Humanoid then
		-- Same logic
		return
	end
	
	-- Be careful when using InvokeClient in synchronous code, remote functions may fail/be manipulated.
	-- You can protect it using pcall, but since my example is based on asynchronous code, event code, 
	-- I didn't do it.
	
	-- More informations: https://create.roblox.com/docs/scripting/events/remote
	-- Example of using pcall to protect it 
	--[[
		local Success, Result = pcall(function()
			return ReplicatedStorage.TweenSpeed:InvokeClient(Player, Speed, Duration)
		end)
		
		if Success then
			if Result == true then
				Humanoid.WalkSpeed = Speed
			end
		else
			warn(`Could not tween player {Player}'s speed to {Speed}. More details below`)
			warn(Result) -- err
		end	
	]]
	
	ReplicatedStorage.TweenSpeed:InvokeClient(Player, Speed, Duration)
	Humanoid.WalkSpeed = Speed -- Maintains speed synchronization with the server, and then, with all other players.
end

Players.PlayerAdded:Connect(function(Player)
	Player.CharacterAdded:Wait()
	task.wait() 
	
	TweenSpeed(Player, 50, 4) 
	TweenSpeed(Player, 16, 2)
end)
1 Like

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