Menu
CoddyTech

Le verrou à code

Leçon 16 sur 20 du cours Jeu Roblox : Échapper aux ténèbres de Coddy.

La porte d'une salle Vault n'a pas de clé : elle est protégée par un code à quatre chiffres, inscrit sur une note accrochée au mur de la salle. Le serveur le choisit lors de la création de la salle et le conserve dans une table, par porte.

L'invite ouvre le Keypad du HUD sur le client, qui recueille les chiffres. En appuyant sur Entrée, le client interroge le serveur via une RemoteFunction qui, contrairement à un RemoteEvent, renvoie une réponse :

-- client
local ok = remotes.TryCode:InvokeServer(door, typed)
-- serveur
remotes.TryCode.OnServerInvoke = function(player, door, code)
	return code == codes[door]
end

Quatre chiffres donnent 9 000 essais ; un exploiteur pourrait tous les envoyer en une seconde. Le serveur autorise donc un essai par Settings.CodeWait, et uniquement de la part d'un joueur se trouvant devant un vrai coffre-fort.

challenge icon

Défi

Difficile
  1. Ajoute local remotes, local codes = {} et local lastTry = {}. Dans addRoom, une porte de chambre forte reçoit tostring(math.random(1000, 9999)) dans codes et sur sa note (Code 4821).
  2. Dans setupDoor, une porte de chambre forte verrouillée ne fait rien : le clavier numérique l’ouvre.
  3. Écris remotes.TryCode.OnServerInvoke : renvoie false pour tout sauf une porte de chambre forte présente dans codes avec une chaîne de caractères, si un joueur se trouve à plus de 12 studs de son panneau, si le délai CodeWait depuis son dernier essai n’est pas écoulé, ou si le code est incorrect ; sinon, supprime le code, déverrouille la porte, ajoute la pièce suivante si nécessaire, ouvre la porte et renvoie true.
  4. Dans DarkClient : lors de ProximityPromptService.PromptTriggered pour une porte de chambre forte verrouillée, affiche le clavier numérique ; les boutons de chiffres ajoutent leur chiffre à typed (jusqu’à 4 chiffres), Clear l’efface, et Enter appelle TryCode et ferme le clavier si la porte s’ouvre, ou affiche Wrong.

La première pièce de cette leçon est une chambre forte. Appuie sur Play, puis sur Check.

Essayez vous-même

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

local rooms = workspace.Rooms
local lastRoom = rooms.Lobby -- la pièce la plus récente
local count = 0 -- pièces créées jusqu’ici
local monster = nil

-- La nuit tombe : plus de soleil, un peu de lumière partout et du brouillard
Lighting.ClockTime = Settings.ClockTime
Lighting.Ambient = Settings.Ambient
Lighting.OutdoorAmbient = Settings.Ambient
Lighting.FogColor = Color3.new(0, 0, 0)
Lighting.FogEnd = Settings.FogEnd
print("Lights out")

-- Un outil que porte un joueur : dans sa main ou dans son Backpack
local function findTool(player, name)
	return (player.Character and player.Character:FindFirstChild(name)) or player.Backpack:FindFirstChild(name)
end

-- Les lampes de poche se déchargent quand elles éclairent et s’éteignent une fois vides
task.spawn(function()
	while true do
		task.wait(0.25)
		for _, player in Players:GetPlayers() do
			local flashlight = player.Character and player.Character:FindFirstChild("Flashlight")
			if flashlight and flashlight.Handle.Beam.Enabled then
				local battery = math.max(0, flashlight:GetAttribute("Battery") - Settings.BatteryDrain * 0.25)
				flashlight:SetAttribute("Battery", battery)
				if battery == 0 then
					flashlight.Handle.Beam.Enabled = false
				end
			end
		end
	end
end)

-- Faire pivoter une porte sur ses gonds pour l’ouvrir
local function openDoor(door)
	door:SetAttribute("Open", true)
	door.Panel.Prompt.Enabled = false
	local hinge, panel = door.Hinge, door.Panel
	local goal = hinge.CFrame * CFrame.Angles(0, math.rad(90), 0) * hinge.CFrame:ToObjectSpace(panel.CFrame)
	TweenService:Create(panel, TweenInfo.new(0.6), { CFrame = goal }):Play()
end

-- Le joueur vivant le plus proche qu’il pourrait attraper (non caché), à portée de MonsterSight, et sa distance
local function nearestCharacter(position)
	local best, bestDistance = nil, Settings.MonsterSight
	for _, player in Players:GetPlayers() do
		local character = player.Character
		local root = character and character:FindFirstChild("HumanoidRootPart")
		if root and character.Humanoid.Health > 0 and not character:GetAttribute("Hidden") then
			local distance = (root.Position - position).Magnitude
			if distance < bestDistance then
				best, bestDistance = character, distance
			end
		end
	end
	return best, bestDistance
end

-- Le monstre peut-il voir ce personnage ? Un rayon partant de sa tête l’atteint-il ?
local function canSee(shadow, character)
	local from = shadow.Head.Position
	local params = RaycastParams.new()
	params.FilterDescendantsInstances = { shadow }
	local hit = workspace:Raycast(from, character.HumanoidRootPart.Position - from, params)
	return hit ~= nil and hit.Instance:IsDescendantOf(character)
end

-- Marcher vers un point : tout droit si le passage est dégagé, sinon en suivant un chemin
local function walkToward(shadow, goal, path)
	local root = shadow.HumanoidRootPart
	local params = RaycastParams.new()
	params.FilterDescendantsInstances = { shadow }
	local hit = workspace:Raycast(root.Position, goal - root.Position, params)
	if not hit or hit.Instance.Parent:FindFirstChildOfClass("Humanoid") then
		shadow.Humanoid:MoveTo(goal)
		return
	end
	path:ComputeAsync(root.Position, goal)
	local points = path:GetWaypoints()
	if path.Status == Enum.PathStatus.Success and points[2] then
		shadow.Humanoid:MoveTo(points[2].Position)
	end
end

-- Le centre d’une pièce située `back` pièces avant celle où se trouvent les joueurs (celle
-- juste avant la plus récente), pour la patrouille du monstre
local function patrolPoint(back)
	for _, room in rooms:GetChildren() do
		if room:GetAttribute("Number") == count - 1 - back then
			return (room.Entrance.CFrame * CFrame.new(0, 3.1, -12)).Position
		end
	end
	return nil
end

-- Le comportement du monstre : toutes les quarts de seconde, à la poursuite de quelqu’un
local function runMonster(shadow)
	local humanoid = shadow.Humanoid
	local root = shadow.HumanoidRootPart
	local path = PathfindingService:CreatePath()
	local lastSeen = nil -- l’endroit où il a vu quelqu’un pour la dernière fois
	local back = 0 -- sa patrouille : 0 correspond à la salle des joueurs, 2 à celle située deux salles en arrière
	while shadow.Parent do
		local character = nearestCharacter(root.Position)
		if character and canSee(shadow, character) then
			lastSeen = character.HumanoidRootPart.Position
		else
			character = nil
		end
		if character and (character.HumanoidRootPart.Position - root.Position).Magnitude <= Settings.CatchDistance then
			-- attrapé : le joueur est vaincu, et le monstre disparaît jusqu’à la prochaine porte
			character.Humanoid.Health = 0
			shadow:Destroy()
			monster = nil
			return
		end
		local goal = character and character.HumanoidRootPart.Position or lastSeen
		if goal then
			walkToward(shadow, goal, path)
			-- arrivé là où il t’a vu pour la dernière fois, si tu n’y es pas, il abandonne
			if not character and ((goal - root.Position) * Vector3.new(1, 0, 1)).Magnitude < 1.5 then
				lastSeen = nil
			end
		else
			-- personne en vue : il patrouille en faisant des allers-retours
			local point = patrolPoint(back) or patrolPoint(0)
			if point then
				walkToward(shadow, point, path)
				if ((point - root.Position) * Vector3.new(1, 0, 1)).Magnitude < 2 then
					back = back == 0 and 2 or 0
				end
			end
		end
		task.wait(0.25)
	end
end

-- Le monstre apparaît derrière toi : à l’entrée de la salle située deux salles avant la plus récente
local function spawnMonster()
	local behind = nil
	for _, room in rooms:GetChildren() do
		if room:GetAttribute("Number") == count - 2 then
			behind = room
		end
	end
	if not behind then
		return
	end
	monster = ServerStorage.Shadow:Clone()
	monster:PivotTo(behind.Entrance.CFrame + Vector3.new(0, 3.1, 0))
	monster.Humanoid.WalkSpeed = Settings.MonsterSpeed
	monster.Parent = workspace
	task.spawn(runMonster, monster)
end

local addRoom -- défini plus bas : une porte ajoute une salle, et une salle possède une porte

-- Ce qui se passe lorsqu’un joueur ouvre une porte
local function setupDoor(door)
	door.Panel.Prompt.Triggered:Connect(function(player)
		if door:GetAttribute("Open") then
			return
		end
		if door:GetAttribute("Locked") then
			local key = findTool(player, "Key")
			if not key then
				return
			end
			key:Destroy()
			door:SetAttribute("Locked", nil)
		end
		if door.Parent == lastRoom then
			addRoom()
		end
		openDoor(door)
	end)
end
setupDoor(workspace.Rooms.Lobby.Door)

-- Entrer dans une salle : elle devient la Room du joueur et son point de réapparition
local function setupEntry(room)
	room.Entry.Touched:Connect(function(hit)
		local player = Players:GetPlayerFromCharacter(hit.Parent)
		if player then
			local number = room:GetAttribute("Number")
			player.leaderstats.Room.Value = number
			player.RespawnLocation = room.Checkpoint
		end
	end)
end
setupEntry(workspace.Rooms.Lobby)

-- Un placard : E pour s’y cacher, E à nouveau pour en sortir
local function setupCloset(closet)
	closet.Front.Prompt.Triggered:Connect(function(player)
		local character = player.Character
		if character:GetAttribute("Hidden") then
			character:PivotTo(closet.Front.CFrame * CFrame.new(-3, -1, 0) * CFrame.Angles(0, math.rad(90), 0))
			character:SetAttribute("Hidden", nil)
		else
			character:PivotTo(closet.Inside.CFrame + Vector3.new(0, 2.5, 0))
			character:SetAttribute("Hidden", true)
		end
	end)
end

-- Une nouvelle salle à la sortie de la plus récente
function addRoom()
	count += 1
	local name = Settings.Order[(count - 1) % #Settings.Order + 1]
	if count >= Settings.RoomsToEscape then
		name = "Exit"
	end
	local room = ServerStorage.Rooms[name]:Clone()
	room:PivotTo(lastRoom.Exit.CFrame)
	room:SetAttribute("Number", count)
	room.Sign.SurfaceGui.Number.Text = "Room " .. count
	room.Parent = rooms
	lastRoom = room
	setupDoor(room.Door)
	setupEntry(room)
	-- une salle verrouillée contient sa clé
	local keySpot = room:FindFirstChild("KeySpot")
	if keySpot then
		local key = ServerStorage.Key:Clone()
		key.Handle.CFrame = keySpot.CFrame
		key.Parent = room
		key.Handle.Prompt.Triggered:Connect(function(player)
			key.Handle.Prompt.Enabled = false
			key.Parent = player.Backpack
		end)
	end
	local closet = room:FindFirstChild("Closet")
	if closet then
		setupCloset(closet)
	end
	-- certaines salles contiennent une batterie
	local batterySpot = room:FindFirstChild("BatterySpot")
	if batterySpot then
		local battery = ServerStorage.Battery:Clone()
		battery.CFrame = batterySpot.CFrame
		battery.Parent = room
		battery.Prompt.Triggered:Connect(function(player)
			local flashlight = findTool(player, "Flashlight")
			if flashlight and battery.Parent then
				flashlight:SetAttribute("Battery", math.min(Settings.BatteryMax, flashlight:GetAttribute("Battery") + Settings.BatteryRefill))
				battery:Destroy()
			end
		end)
	end
	-- conserver la salle la plus récente et KeepRooms salles précédentes
	for _, old in rooms:GetChildren() do
		if old:GetAttribute("Number") < count - Settings.KeepRooms then
			old:Destroy()
		end
	end
	if count >= Settings.MonsterFrom and not monster then
		spawnMonster()
	end
	return room
end

-- Une fois sorti du placard, vous n’êtes plus caché
task.spawn(function()
	while true do
		task.wait(0.25)
		for _, player in Players:GetPlayers() do
			local character = player.Character
			if character and character:GetAttribute("Hidden") then
				local closest = math.huge
				for _, inside in workspace.Rooms:GetDescendants() do
					if inside.Name == "Inside" then
						closest = math.min(closest, (inside.Position - character.HumanoidRootPart.Position).Magnitude)
					end
				end
				if closest > 4 then
					character:SetAttribute("Hidden", nil)
				end
			end
		end
	end
end)

-- Pour chaque joueur : Room dans le classement et une lampe de poche à chaque vie
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local room = Instance.new("IntValue")
	room.Name = "Room"
	room.Parent = leaderstats

	player.CharacterAdded:Connect(function()
		local flashlight = ServerStorage.Flashlight:Clone()
		flashlight:SetAttribute("Battery", Settings.BatteryMax)
		flashlight.Parent = player.Backpack
		local beam = flashlight.Handle.Beam
		flashlight.Activated:Connect(function()
			if beam.Enabled then
				beam.Enabled = false
			elseif flashlight:GetAttribute("Battery") > 0 then
				beam.Enabled = true
			end
		end)
	end)
end)
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Jeu Roblox : Échapper aux ténèbres