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.
Desafío
IntermedioEn el manejador de Ability, antes de ejecutar la habilidad, retorna cuando:
- el nombre no es una cadena que nombre una habilidad,
- el jugador no tiene una raíz de personaje, o la salud de su Humanoid es 0,
GetServerTimeNow()es anterior al tiempo en que el jugador estará listo para esa habilidad, el atributoname .. "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)
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.