Multiplayer¶
The rule that makes multiplayer simple: the host decides. One machine (the host, or a dedicated server) owns the truth: where things are, who scored. The others send requests and show what they're told. In KKE that rule is spelled with a file name.
Host scripts and player scripts¶
A script whose name starts with sv_ runs only where the truth lives:
on the host, or when you're playing alone. Every other script runs on
every machine. So a feature is usually two files: the host's sv_ script
that decides, and a plain script that asks and shows.
This scoreboard is the smallest complete example. J asks for a point; the host counts it (no more than two a second per player, so a modified client can't cheat) and sends the totals to everyone.
-- sv_scores.lua: the scoreboard's truth. sv_ scripts run only where the
-- game's truth lives: on the host, or when playing alone. Players ask for
-- points; this decides, keeps the totals and tells everyone.
-- (docs/cookbook/networking.md)
local scores = {} -- player id -> points
local function publish()
-- To every player's machine (does nothing when playing alone)...
if net and net.connected() then net.send("scores", scores) end
-- ...and to the scripts on this one.
hook.Run("ScoresChanged", scores)
end
-- The host decides: a point is only counted when it's allowed. Here, one
-- per player per half second, so a hacked client can't flood it.
local last = {}
local function addPoint(player)
local now = kke.time()
if last[player] and now - last[player] < 0.5 then return end
last[player] = now
scores[player] = (scores[player] or 0) + 1
publish()
end
hook.Add("NetMessage", "sv_scores.point", function(name, data, from)
if name == "point" then addPoint(from) end
end)
-- The host's own player (or you, playing alone) asks without the network.
hook.Add("WantPoint", "sv_scores.local", function(player) addPoint(player) end)
-- scores.lua: every player's side. J asks for a point; the HUD shows the
-- scoreboard the host sends. Runs on every machine.
-- (docs/cookbook/networking.md)
input.define("scores.point", "Score a point", "J")
local hud = ui and ui.open([[
<rml><head><style>
body { width: 100%; height: 100%; font-family: Noto Sans; color: #ffffff; pointer-events: none; }
#board { position: absolute; right: 20dp; top: 20dp; padding: 8dp 16dp; background-color: #10131ecc;
border-radius: 8dp; font-size: 18dp; }
</style></head>
<body><div id="board">Press J to score</div></body></rml>
]])
local function me() return net and net.playerId() or 0 end
local function show(scores)
if not hud then return end
local lines = {}
for player, points in pairs(scores) do
local who = player == me() and "You" or ("Player " .. player)
table.insert(lines, string.format("%s: %d", who, points))
end
table.sort(lines)
ui.text(hud, "board", table.concat(lines, " ยท "))
end
hook.Add("Think", "scores.keys", function()
if not input.pressed("scores.point") then return end
if net and net.role() == "client" then
net.send("point", true) -- ask the host
else
hook.Run("WantPoint", me()) -- we are the host (or alone): sv_scores.lua hears this
end
end)
-- The host's copy, on the host; the network's copy, on a client.
hook.Add("ScoresChanged", "scores.show", show)
hook.Add("NetMessage", "scores.receive", function(name, data)
if name == "scores" and type(data) == "table" then show(data) end
end)
Download sv_scores.lua Download scores.lua
net.send(name, data)goes from a player to the host, or from the host to every player.datais a number, text, true/false, or a table of those (up to 1 KB).hook.Add("NetMessage", ...)receives, with the message's name, its data and who sent it (from, a player id).net.role()is"host","client"or"offline". Playing alone, thesv_script is right there, so the scores go throughhook.Runinstead of the network; the same code works in both cases.- Never trust a message: the host checks it makes sense (here: not too often) before acting. What arrives damaged or oversized is dropped before a script sees it.
Both files go in the same scripts/ folder. Hosting, joining and leaving
is in the game's menu (the starter game has it); loading and unloading the
sv_ scripts as your role changes is automatic.
What replicates by itself¶
What an sv_ script spawns (physics.box, breakable.box, thrown
balls) appears on every player's machine and moves as it does on the
host, so a level built by a host script is shared without a line of
network code. Plain scripts spawn locally: each machine makes its own.
The character's movement is server-authoritative (the host checks each
player's moves), and voice chat, LAN discovery and join codes come with
NetModule. Networking has the model in full;
Server hosting runs the same scripts on a
dedicated server, where server.* adds say, kick and a PlayerJoin
hook; Anti-cheat is why the host decides.
Next: play to make.