M2.4: the server decides online (characters, inventory, stats, death, owned graves, per-player saves); death screen with Respawn after 3 s

This commit is contained in:
Paul Bailey
2026-11-04 19:18:31 +00:00
parent 8e8f10eb96
commit 35a656e996
16 changed files with 1124 additions and 477 deletions

View File

@@ -32,7 +32,7 @@ signal clock(day: int, time: float) ## the server's time of day
const Identity = preload("res://scripts/net/identity.gd")
const ServerConfig = preload("res://scripts/net/server_config.gd")
const PROTOCOL := 3 ## 3: the shared world (M2.3)
const PROTOCOL := 4 ## 3: the shared world (M2.3); 4: the server decides (M2.4)
const DEFAULT_PORT := 24242
const HANDSHAKE_TIMEOUT := 10.0
const JOIN_TIMEOUT := 20.0 ## includes the server building its terrain on first join
@@ -238,6 +238,7 @@ func _on_peer_disconnected(id: int) -> void:
players.erase(id)
print("[server] %s left (%d online)" % [p.name, players.size()])
_s2c_player_left.rpc(id)
PlayerNet.server_leave(id) # saves their character
WorldNet.peer_left(id)
players_changed.emit()
@@ -275,6 +276,15 @@ func _tick_clock(delta: float) -> void:
func player_pos(peer: int) -> Vector3:
return _states[peer].pos if _states.has(peer) else Vector3.INF
func island_spawn() -> Vector3:
return _spawn
## The server moved a player (respawning): their next moves are checked from here.
func teleport(peer: int, pos: Vector3) -> void:
if _states.has(peer):
_states[peer].pos = pos
_states[peer].t = server_time
func _broadcast_snapshot() -> void:
if _states.is_empty():
return
@@ -318,11 +328,13 @@ func _c2s_state(pos: Vector3, yaw: float, pitch: float, anim: int, held: String,
print("[server] moved %s back (%s)" % [players[peer].name, why])
_s2c_correct.rpc_id(peer, s.pos)
return
var from: Vector3 = s.pos
s.pos = pos
s.yaw = wrapf(yaw, -PI, PI)
s.pitch = clampf(pitch, -1.5, 1.5)
s.anim = clampi(anim, 0, 4)
s.held = held if held.length() < 40 and (held.is_empty() or not ItemDB.get_item(held).is_empty()) else ""
s.held = PlayerNet.held(peer) # (what's really in their hand, not what they say)
PlayerNet.server_moved(peer, from, pos, s.yaw, s.anim, server_time - s.t)
s.t = server_time
s.stamp = stamp
@@ -422,14 +434,19 @@ func _c2s_proof(sig: PackedByteArray) -> void:
print("[server] %s is the first player, so they're now an admin" % name)
var p := {"id": id, "name": name, "appearance": Identity.sanitize_appearance(hello.get("appearance", {})),
"admin": config.is_admin(id)}
var start := _spawn_for(players.size())
# Their character (player_net.gd): back where they left off, or new.
var start: Vector3 = PlayerNet.server_join(peer, id, name)
var yaw: float = PlayerNet.yaw_of(peer)
if start == Vector3.INF:
start = _spawn_for(players.size())
PlayerNet.place_at(peer, start)
players[peer] = p
_states[peer] = {"pos": start, "yaw": 0.0, "pitch": 0.0, "anim": 0, "held": "", "t": server_time, "corr_t": -10.0,
_states[peer] = {"pos": start, "yaw": yaw, "pitch": 0.0, "anim": 0, "held": "", "t": server_time, "corr_t": -10.0,
"stamp": server_time, "off": INF}
print("[server] %s joined (id %s%s, %d/%d online)" % [name, id, ", admin" if p.admin else "",
players.size(), int(config.v("max_players"))])
# (The world's own seed: an existing world keeps the seed it was made with.)
var world := {"seed": int(_terrain.noise_seed), "worldlet": String(config.v("worldlet")), "spawn": start,
var world := {"seed": int(_terrain.noise_seed), "worldlet": String(config.v("worldlet")), "spawn": start, "yaw": yaw,
"island_spawn": _spawn, "day": _world.day_night.day, "time": _world.day_night.time, "server_time": server_time}
_s2c_welcome.rpc_id(peer, {"you": peer, "server": config.public_info(), "players": players, "world": world})
for other in players: