Hello, how can I improve?

I’m learning scripting and I’ve made a somewhat jump/dash which uses applyimpulse to push a character forward. The problem is I don’t know how I should overall structure my code. I’m coming across posts saying changes to the player should be on the client but I don’t know how that would work with other posts saying they only use 1 remote event for their games. Below is me using a fireallclients which I imagine is what needs to be used with some sanity checks by the server. I’m still not sure if this is right and I’m looking for more improvements.

Server script:

local RS = game:GetService("ReplicatedStorage")
local debris = game:GetService("Debris")
RS.Dash.OnServerEvent:Connect(function(plr)
	local function boost()
		RS.Move:FireAllClients()
	end
	local function animate()
		local animator = plr.Character.Humanoid.Animator
		local anim = Instance.new("Animation")
		anim.AnimationId = "http://www.roblox.com/asset/?id=15881777718"
		local animtrack = animator:LoadAnimation(anim)
		animtrack:Play()
	end
	task.spawn(animate)
	task.spawn(boost)
end)

Client-Side script:

local CAS = game:GetService("ContextActionService")
local RS = game:GetService("ReplicatedStorage")
local name = "PressedE"

local plr = game.Players.LocalPlayer
local HRP = plr.Character.PrimaryPart
local hum = plr.Character.Humanoid

local lasttime = 0

local function bind(name,inputstate)
	if name == name and inputstate == Enum.UserInputState.Begin then
		if os.time() - lasttime >= 2 then
			lasttime = os.time()
			RS.Dash:FireServer()
		end
	end
end

CAS:BindAction(name,bind,true,Enum.KeyCode.E)

RS.Move.OnClientEvent:Connect(function()
	if plr then
		if hum:GetState() == Enum.HumanoidStateType.Freefall or hum:GetState() == Enum.HumanoidStateType.Jumping then
			print("Success")
			HRP:ApplyImpulse(HRP.CFrame.LookVector * 100 * HRP.AssemblyMass)
		else
			HRP:ApplyImpulse(HRP.CFrame.LookVector * 150 * HRP.AssemblyMass)
		end
	end
end)

dash game

Theres many correct answers to your question, but in my opinion, you want the least amount of stress on your server while still having tight security. The thing is, if you want tight security, you gotta to have some stress.

In the context of your dash/jump mechanic, it’s generally recommended to handle critical aspects, such as movement and physics, on the server. This helps prevent cheating and ensures fair gameplay. However, for responsive controls, you may also want to incorporate predictive movement on the client for a smoother player experience, as long as the calculations are verified and enforced on the server.

There are some things you can improve on:

Use Debounce for input: Instead of checking os.time() for cooldown, consider using a debounce mechanism to prevent rapid firing. This is especially important in a networked environment where input delays can vary.

Use RemoteEvent for Movement: Instead of firing a RemoteEvent (RS.Move:FireAllClients() ) and handling movement on the client, you can directly call a RemoteFunction to the server, and then apply the impulse on the server. This ensures that the server has control over player movement, preventing potential exploits. Example:

--server
local RS = game:GetService("ReplicatedStorage")
local MoveFunction = RS:WaitForChild("MoveFunction")

MoveFunction.OnServerInvoke = function(player)
    -- Your movement code here
end

Client:

--local
RS.MoveFunction:InvokeServer()

  1. Client-Side:
  • User Interface: Rendering and updating the player interface (UI) can be handled on the client.
  • Non-Critical Visual Effects: Cosmetic changes, particle effects, or animations that don’t affect gameplay significantly can be handled on the client.
  • Predictive Movement: If you want a more responsive feel for movements, you might perform some actions on the client and then replicate them to the server for verification.
  1. Server-Side:
  • Critical Game Logic: Important game mechanics, calculations, and decisions should be handled on the server to prevent exploitation and cheating.
  • Player Movement and Physics: To ensure fair and consistent gameplay, essential movement and physics calculations should be performed on the server. Client input can be used for prediction, but the authoritative version should be computed on the server.
  • Data Validation: Always validate and sanitize data on the server to prevent exploits.
  1. Remote Events/Functions:
  • Use RemoteEvents to signal actions from the client to the server, and from the server to the client.
  • RemoteFunctions can be used for two-way communication but should be used cautiously, especially when involving client-to-server calls, as they can introduce security vulnerabilities if not handled correctly.
1 Like

Wouldn’t the best option for this to have sanity checks and fire every client from the server?

How would I render particles for every player on the client without firing every client?

I don’t think I should improve the security of my game because I haven’t found what structure I want to use between the client and server. My main issue stems from not knowing how to balance performance with work on the server. Is the structure of a code just a preference because exploiters can be “unavoidable”?

I’ve made a combat system to upgrade my skills with the knowledge of the devforum and made a new combat system. What improvements can be made?
Game:

Combat game 2

Punch Module
local SS = game:GetService("ServerStorage")
local debris = game:GetService("Debris")
local runservice = game:GetService("RunService")
local module = {
	punch = function(plr, serveritems, debouncetable,punchsound)
		local char = plr.Character or plr.CharacterAdded:Wait()--PLayer values
		local HRP = char.PrimaryPart
		local cd = 0.25
		local newhitbox = serveritems.HitBox:Clone()--Created
		newhitbox.Parent = workspace
		newhitbox.CFrame = HRP.CFrame + HRP.CFrame.LookVector * 3
		local filter = OverlapParams.new()
		filter.FilterType = Enum.RaycastFilterType.Exclude
		filter.FilterDescendantsInstances = {newhitbox, plr.Character}
		local region = workspace:GetPartsInPart(newhitbox, filter)
		for i,v in pairs(region) do
			local enemyhum = v.Parent:FindFirstChild("Humanoid")
			if enemyhum and v.Parent ~= plr.Character and not debouncetable[enemyhum] then
				local core = coroutine.wrap(function()
					punchsound:Play()
					debouncetable[enemyhum] = enemyhum
					enemyhum:TakeDamage(15)
					task.wait(cd)
					debouncetable[enemyhum] = nil
				end)
				core()
			end
		end
		debris:AddItem(newhitbox,cd)--Destroyed
	end,
	animate = function(plr, punchanim, punchtable)
		local char = plr.Character or plr.CharacterAdded:Wait() -- getting player values
		local hum = char.Humanoid
		
		local animation = Instance.new("Animation") -- creating animation
		animation.Parent = char
		animation.AnimationId = punchtable[punchanim]
		
		local animtrack = hum:LoadAnimation(animation) --Playing and loading animation track
		animtrack:Play()
	end,
	renderparticles = function(punchparticle, plr)
		local part = Instance.new("Part")
		local newpunchparticle = punchparticle:Clone()
		
		local cd = 0.25
		
		--plr values
		local char = plr.Character or plr.CharacterAdded:Wait()
		local HRP = char.PrimaryPart
		
		local core = coroutine.wrap(function()
			task.wait(0.1)
			newpunchparticle:Emit(20)
		end)
		
		part.Parent = workspace
		newpunchparticle.Parent = part
		
		part.Transparency = 1
		part.CanCollide = false
		part.Anchored = true
		part.CFrame = HRP.CFrame + HRP.CFrame.LookVector * 3
		
		core()
		
		debris:AddItem(part,cd)
		debris:AddItem(newpunchparticle,cd)
	end,
}

return module

Server Script
local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local debris = game:GetService("Debris")

local modules = RS:WaitForChild("Modules")
local events = RS:WaitForChild("Events")
--Server etc
local serverItems = SS.Items
--Modules
local punchmodule = require(modules.PunchModule)


--Effects
local effects = RS.Effects
local punch = effects.PunchSound
--Debouncing
local debouncetable = {}


events.Punch.OnServerEvent:Connect(function(plr)
	punchmodule.punch(plr, serverItems, debouncetable,punch)
	events.Animate:FireAllClients(plr)
end)
Client-Side Script

local RS = game:GetService(“ReplicatedStorage”)

–Below services
local punchmodule = require(RS.Modules.PunchModule)
local events = RS:WaitForChild(“Events”)

–Player things
local plr = game.Players.LocalPlayer
local mouse = plr:GetMouse()

–Animations
local punchanim = 1
local punchtable = { – Punch table
http://www.roblox.com/asset/?id=15886118703”,
http://www.roblox.com/asset/?id=15886190964
}

–Debouncing
local debounce = false

–Effects
local effects = RS:WaitForChild(“Effects”)
local puncheffect = effects.PunchParticle
local function click()
if plr then
local core = coroutine.wrap(function()
if not debounce then
debounce = true
events.Punch:FireServer()
task.wait(0.25)
debounce = false
end
end)
core()
end
end

mouse.Button1Down:Connect(click)

events.Animate.OnClientEvent:Connect(function(plrtochange)
if plrtochange then
punchmodule.animate(plrtochange, punchanim, punchtable)
punchmodule.renderparticles(puncheffect,plrtochange)
punchanim += 1
if punchanim == 3 then
punchanim = 1
end
end
end)

Note: I’m looking for improvements scripting-wise.
Note 2: I also noticed the hitbox moves because of the animation, is this major?
Note 3: When I use fireallclients I’m giving the server less control, but I don’t know how else I would load things such as animations, particles, etc.

I tested my dash mechanic with remote functions and it worked! It showed everything the client did on the server and on other clients! How did that happen?

Update: Nevermind, particles don’t load for everyone on remote functions, I guess there are no shortcuts lol.