Menu
CoddyTech

Never Trust the Client

Lesson 7 of 18 in Coddy's Roblox Game: Pet Simulator course.

A player controls their own device, so an exploiter can make it fire any remote, with anything, from anywhere, as fast as they like. Hit:FireServer(pile) from across the map, a hundred times a second, would empty every pile in the game.

So the server checks every request before it acts:

  • it is a real pile (an Instance in workspace.Piles),
  • the player stands within Settings.Reach of it,
  • and they last hit at least Settings.HitCooldown seconds ago.
if typeof(pile) ~= "Instance" or pile.Parent ~= piles then
	return
end

When you press Check, the tests do what an exploiter would. The rule for every remote you write: the client asks, the server decides.

challenge icon

Challenge

Medium

In PetServer, at the start of the Hit handler, return without hitting when:

  1. the pile is not an Instance in piles,
  2. the player has no character, or their HumanoidRootPart is more than Settings.Reach studs from the pile,
  3. they hit less than Settings.HitCooldown seconds ago (keep local lastHit = {} near the top, and record time() when a hit is allowed).

Press Play, then Check: the checks play the exploiter.

Try it yourself

local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Settings = require(game.ReplicatedStorage.Settings)

local remotes = game.ReplicatedStorage.Remotes
local piles = workspace.Piles
local pileSpot = {} -- each pile -> the spot it stands on

print("A Meadow pile gives " .. Settings.PileValue .. " coins")

-- Every player gets Coins on the leaderboard
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local coins = Instance.new("IntValue")
	coins.Name = "Coins"
	coins.Parent = leaderstats
end)

-- A coin pile on a spot: a copy of ServerStorage.CoinPile with its health
-- and value as attributes
local function spawnPile(spot)
	local pile = ServerStorage.CoinPile:Clone()
	pile.Position = spot.Position
	pile:SetAttribute("MaxHealth", Settings.PileHealth)
	pile:SetAttribute("Health", Settings.PileHealth)
	pile:SetAttribute("Value", Settings.PileValue)
	pileSpot[pile] = spot
	pile.Parent = piles
	return pile
end

-- A hit takes 1 off a pile's health;
-- at 0 the pile breaks and pays its value
local function hitPile(player, pile)
	local health = pile:GetAttribute("Health") - 1
	if health > 0 then
		pile:SetAttribute("Health", health)
		return
	end
	player.leaderstats.Coins.Value += pile:GetAttribute("Value")
	local spot = pileSpot[pile]
	pileSpot[pile] = nil
	pile:Destroy()
	-- a new pile on the same spot, a few seconds later
	task.delay(Settings.RespawnTime, spawnPile, spot)
end

-- A pile on every spot in the Meadow
for _, spot in workspace.Zones.Meadow.Spawns:GetChildren() do
	spawnPile(spot)
end

-- The client asks to hit a pile
remotes.Hit.OnServerEvent:Connect(function(player, pile)
	hitPile(player, pile)
end)
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Roblox Game: Pet Simulator