M2.2: player bodies, name tags, shared island and checked movement

- Joining loads the server's island (seed, spawn, clock); the server
  generates the same terrain from its seed and keeps a world clock that
  pauses when empty
- Other players are procedural low-poly characters in their chosen colours
  with name tags (hidden by walls, fading with distance), walk/run/swim
  animation and held items
- Movement: local and instant, sent at 20 Hz stamped with the sender's
  clock; the server refuses impossible moves (speed, rising, leaving the
  island, underground) and sends the player back; flagged respawns allowed
- Avatars drawn 100 ms behind and interpolated, with brief extrapolation
- Online: no pausing, player list in the Esc menu, Disconnect back to the
  title (with the reason); animals off and world changes local-only until
  M2.3-M2.5, shown on the HUD
- test_net_world: real server + walking and cheating bots at 100 ms +/-
  30 ms simulated latency (27 checks)
This commit is contained in:
Paul Bailey
2026-11-04 15:08:14 +00:00
parent f62f71658f
commit d0f473e57b
14 changed files with 1093 additions and 29 deletions

View File

@@ -30,6 +30,7 @@ var _lobby_motd: Label
var _lobby_rules: Label
var _lobby_count: Label
var _lobby_list: VBoxContainer
var _lobby_note: Label
func _ready() -> void:
if Net.dedicated:
@@ -88,15 +89,31 @@ func _ready() -> void:
_pages["lobby"] = _lobby_page()
for p in _pages.values():
col.add_child(p)
Net.joined.connect(func(): _show("lobby"))
Net.joined.connect(_enter_server_world)
Net.join_failed.connect(_on_join_failed)
Net.left.connect(_on_left)
Net.players_changed.connect(func():
if _pages["lobby"].visible:
_fill_lobby())
_show("main")
# Back from a server (left, kicked, or it went away): say why.
if Net.state == "offline" and not Net.last_reason.is_empty():
_show("join")
_join_msg.text = Net.last_reason
Net.last_reason = ""
_maybe_capture()
## The server let us in: show who's there for a moment, then load the island.
func _enter_server_world() -> void:
_show("lobby")
_lobby_note.text = "Loading the island..."
for _i in range(3):
await get_tree().process_frame
if Net.state != "connected":
return
Saves.current_id = "" # a server's world isn't saved locally
get_tree().change_scene_to_file(WORLD_SCENE)
## Dev hook, same as world.gd: `-- --shot=/abs/prefix` saves the title pages.
func _maybe_capture() -> void:
var prefix := ""
@@ -370,10 +387,10 @@ func _lobby_page() -> Control:
_lobby_list = VBoxContainer.new()
_lobby_list.add_theme_constant_override("separation", 4)
v.add_child(_lobby_list)
var note := UITheme.label("Playing together in the world comes next (M2.2-M2.5). For now, this is who's here.", 13, UITheme.TEXT_DIM)
note.autowrap_mode = TextServer.AUTOWRAP_WORD
note.custom_minimum_size = Vector2(420, 0)
v.add_child(note)
_lobby_note = UITheme.label("", 13, UITheme.TEXT_DIM)
_lobby_note.autowrap_mode = TextServer.AUTOWRAP_WORD
_lobby_note.custom_minimum_size = Vector2(420, 0)
v.add_child(_lobby_note)
v.add_child(_button("Disconnect", Net.leave, 180))
return p