Menu
CoddyTech

Tiempos de espera del servidor

Lección 7 de 20 del curso Juego de Roblox: Combates en la arena de Coddy.

Quien explota vulnerabilidades controla su propio dispositivo, así que puede activar Remotes.Ability cien veces por segundo, con cualquier nombre. Un tiempo de recarga en el LocalScript solo detendría a los jugadores honestos: el servidor tiene que aplicarlo.

El servidor guarda, para cada jugador y habilidad, cuándo vuelve a estar lista, como un atributo del jugador (la próxima lección lo muestra en pantalla). workspace:GetServerTimeNow() es un reloj con el que coinciden el servidor y todos los clientes:

local now = workspace:GetServerTimeNow()
if now < (player:GetAttribute(name .. "ReadyAt") or 0) then
	return
end
player:SetAttribute(name .. "ReadyAt", now + Settings.Cooldowns[name])

Y comprueba qué recibió: una cadena que nombre una habilidad real, enviada por un jugador que esté vivo.

challenge icon

Desafío

Intermedio

En el manejador de Ability, antes de ejecutar la habilidad, retorna cuando:

  1. el nombre no es una cadena que nombre una habilidad,
  2. el jugador no tiene una raíz de personaje, o la salud de su Humanoid es 0,
  3. GetServerTimeNow() es anterior al tiempo en que el jugador estará listo para esa habilidad, el atributo name .. "ReadyAt".

De lo contrario, establece ese atributo en el tiempo actual más Settings.Cooldowns[name] y luego ejecútala. Pulsa Play y después Check: las comprobaciones disparan el remoto como lo haría un explotador.

Pruébalo tú mismo

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

local remotes = game.ReplicatedStorage.Remotes
local lastSwing = {} -- player -> cuándo blandió la espada por última vez

-- Las animaciones de espada propias de Roblox
local slash = Instance.new("Animation")
slash.AnimationId = "rbxassetid://522635514"

print("Swords deal " .. Settings.SwordDamage .. " damage")

-- Un espadazo: primero la animación; luego reciben daño todos los que estén en la zona frente a ti
local function swing(player)
	local character = player.Character
	local root = character and character:FindFirstChild("HumanoidRootPart")
	if not root or character.Humanoid.Health <= 0 then
		return
	end
	local now = time()
	if lastSwing[player] and now - lastSwing[player] < Settings.SwingCooldown then
		return
	end
	lastSwing[player] = now
	character.Humanoid.Animator:LoadAnimation(slash):Play()
	-- la zona frente a ti, nunca tu propio cuerpo
	local params = OverlapParams.new()
	params.FilterDescendantsInstances = { character }
	local box = root.CFrame * CFrame.new(0, 0, -3)
	local hit = {} -- cada humanoid una sola vez por espadazo
	for _, part in workspace:GetPartBoundsInBox(box, Settings.HitboxSize, params) do
		local humanoid = part.Parent:FindFirstChildOfClass("Humanoid")
		if humanoid and not hit[humanoid] then
			hit[humanoid] = true
			humanoid:TakeDamage(Settings.SwordDamage)
		end
	end
end

-- Las habilidades, por nombre: qué hace cada una
local abilities = {}

function abilities.Dash(player, character, root)
	root.AssemblyLinearVelocity = root.CFrame.LookVector * Settings.DashSpeed + Vector3.new(0, 15, 0)
end

-- El cliente solicita una habilidad por su nombre; el servidor la ejecuta
remotes.Ability.OnServerEvent:Connect(function(player, name)
	local character = player.Character
	local root = character and character:FindFirstChild("HumanoidRootPart")
	if abilities[name] and root then
		abilities[name](player, character, root)
	end
end)

-- Cada jugador recibe Kills en la tabla de clasificación y una espada en cada vida
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local kills = Instance.new("IntValue")
	kills.Name = "Kills"
	kills.Parent = leaderstats

	player.CharacterAdded:Connect(function()
		local sword = ServerStorage.Sword:Clone()
		sword.Parent = player.Backpack
		sword.Activated:Connect(function()
			swing(player)
		end)
	end)
end)

-- El muñeco de entrenamiento: su barra de salud refleja su salud y, cuando lo derrotan,
-- el muñeco reaparece al cabo de un momento como una copia nueva en el mismo lugar
local function setupDummy(dummy)
	local humanoid = dummy.Humanoid
	humanoid.HealthChanged:Connect(function(health)
		dummy.HealthBar.Back.Fill.Size = UDim2.fromScale(health / humanoid.MaxHealth, 1)
	end)
	humanoid.Died:Connect(function()
		local spot = dummy:GetPivot()
		task.wait(0.5)
		dummy:Destroy()
		task.wait(Settings.DummyRespawn)
		local fresh = ServerStorage.Dummy:Clone()
		fresh:PivotTo(spot)
		fresh.Parent = workspace
		setupDummy(fresh)
	end)
end
setupDummy(workspace.Dummy)
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Juego de Roblox: Combates en la arena