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:
@@ -26,6 +26,7 @@ const PIECES := {
|
||||
"bed": preload("res://scripts/building/bed.gd"),
|
||||
}
|
||||
const BuildPiece = preload("res://scripts/building/build_piece.gd")
|
||||
const UITheme = preload("res://scripts/ui/theme.gd")
|
||||
|
||||
const AUTOSAVE_SECONDS := 120.0
|
||||
|
||||
@@ -50,8 +51,13 @@ var _dropped: Node3D ## arrows, spears and other dropped items
|
||||
var den: Node3D ## den.gd: the wolves' cave at the foot of the rim
|
||||
var bobber: Node3D ## bobber.gd: the cast fishing float, if any
|
||||
var wolves: Node3D ## wolf_pack.gd (null if this seed has no den site)
|
||||
var net_sync: Node ## net_sync.gd when playing on a server, else null
|
||||
var has_bed_spawn := false
|
||||
var bed_spawn := Vector3.ZERO ## position of the bed you respawn at
|
||||
|
||||
## Playing on a server (multiplayer) rather than a local world.
|
||||
func is_online() -> bool:
|
||||
return not menu_mode and Net.state == "connected"
|
||||
var _loading := false ## applying a save: defer support recompute
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -157,7 +163,7 @@ func _on_player_died() -> void:
|
||||
player.inventory.load_data(Inventory.new(player.inventory.size()).to_data())
|
||||
if hud != null:
|
||||
hud.close_screens()
|
||||
var bed := spawn_bed()
|
||||
var bed := spawn_bed() if net_sync == null else null # (online: beds aren't shared until M2.4)
|
||||
if bed != null:
|
||||
_respawn_at_bed(bed)
|
||||
else:
|
||||
@@ -258,6 +264,10 @@ func _on_terrain_ready() -> void:
|
||||
player.set_physics_process(true)
|
||||
set_physics_process(true)
|
||||
creatures.setup(self)
|
||||
if is_online():
|
||||
_go_online()
|
||||
_maybe_capture()
|
||||
return
|
||||
if not "--no-creatures" in OS.get_cmdline_user_args():
|
||||
creatures.populate()
|
||||
creatures.enabled = true
|
||||
@@ -301,7 +311,13 @@ func save_now() -> bool:
|
||||
return ok
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_WM_CLOSE_REQUEST and Saves.has_world() and not menu_mode:
|
||||
if what != NOTIFICATION_WM_CLOSE_REQUEST or menu_mode:
|
||||
return
|
||||
if net_sync != null:
|
||||
Net.left.disconnect(_on_net_left)
|
||||
Net.leave()
|
||||
get_tree().quit()
|
||||
elif Saves.has_world():
|
||||
save_now()
|
||||
get_tree().quit()
|
||||
|
||||
@@ -421,6 +437,8 @@ func apply_save(s: Dictionary) -> void:
|
||||
_update_grass_cuts()
|
||||
|
||||
func _respawn() -> void:
|
||||
if net_sync != null:
|
||||
net_sync.mark_teleport() # tell the server this jump back to the spawn is legitimate
|
||||
player.global_position = _spawn + Vector3(0.0, 1.5, 0.0)
|
||||
player.velocity = Vector3.ZERO
|
||||
player.rotation = Vector3(0.0, _spawn_yaw, 0.0)
|
||||
@@ -498,6 +516,34 @@ func water_at(x: float, z: float) -> Dictionary:
|
||||
return {"type": "sea", "surface": terrain.sea_level, "depth": terrain.sea_level - ground}
|
||||
return {}
|
||||
|
||||
# --- multiplayer -----------------------------------------------------------------
|
||||
|
||||
## Joined a server: start where it says, follow its clock, show the others.
|
||||
## Animals stay off until the server runs them (M2.5): otherwise everyone
|
||||
## would see different wildlife.
|
||||
func _go_online() -> void:
|
||||
_spawn = Net.world_info.get("island_spawn", _spawn)
|
||||
var start: Vector3 = Net.world_info.get("spawn", _spawn)
|
||||
player.global_position = start + Vector3(0.0, 0.05, 0.0)
|
||||
net_sync = preload("res://scripts/net/net_sync.gd").new()
|
||||
net_sync.name = "NetSync"
|
||||
add_child(net_sync)
|
||||
net_sync.setup(self)
|
||||
Net.left.connect(_on_net_left)
|
||||
get_tree().auto_accept_quit = false # leave the server cleanly on window close
|
||||
if hud != null:
|
||||
var bar := UITheme.label("Online: %s · you can see each other; building, gathering and animals aren't shared yet" \
|
||||
% Net.server_info.get("server_name", "server"), 14, UITheme.TEXT_DIM)
|
||||
bar.position = Vector2(16, 40)
|
||||
$HUD.add_child(bar)
|
||||
Actions.notify.emit("Connected to %s" % Net.server_info.get("server_name", "the server"))
|
||||
|
||||
func _on_net_left(_reason: String) -> void:
|
||||
if is_inside_tree():
|
||||
get_tree().paused = false
|
||||
get_tree().auto_accept_quit = true
|
||||
get_tree().change_scene_to_file.call_deferred("res://scenes/title.tscn")
|
||||
|
||||
## Leaves `count` of an item lying in the world at `xf` (an arrow stuck in a
|
||||
## tree, a thrown spear, meat that didn't fit). Saved with the world.
|
||||
func drop_item(id: String, count: int, xf: Transform3D) -> Node3D:
|
||||
@@ -692,6 +738,50 @@ func _capture_survival(prefix: String, cam: Camera3D) -> void:
|
||||
await _capture_weather(prefix, cam)
|
||||
await _capture_creatures(prefix, cam)
|
||||
await _capture_wolves_fishing(prefix, cam)
|
||||
await _capture_players(prefix, cam)
|
||||
|
||||
## Two sample players (as other people would see them) at the camp.
|
||||
func _capture_players(prefix: String, cam: Camera3D) -> void:
|
||||
weather.force("clear")
|
||||
weather.snow_cover = 0.0
|
||||
weather.ground_wet = 0.0
|
||||
day_night.time = 10.0 / 24.0
|
||||
day_night.apply(true)
|
||||
_respawn()
|
||||
player.set_physics_process(false)
|
||||
var fwd := -player.global_basis.z
|
||||
var right := player.global_basis.x
|
||||
var root := Node3D.new()
|
||||
add_child(root)
|
||||
var AvatarScript = preload("res://scripts/net/avatar.gd")
|
||||
var made: Array = []
|
||||
for d in [["Freya", {"tunic": 2, "hair": 2, "beard": false}, 3.2, -0.9, "stone_axe", 1],
|
||||
["Old Bjorn", {"tunic": 1, "hair": 4, "beard": true}, 4.4, 1.3, "", 0]]:
|
||||
var av: Node3D = AvatarScript.new()
|
||||
av.setup(made.size() + 2, {"name": d[0], "appearance": d[1]})
|
||||
root.add_child(av)
|
||||
var p: Vector3 = player.global_position + fwd * float(d[2]) + right * float(d[3])
|
||||
p.y = terrain.sample_height(p.x, p.z)
|
||||
var face: float = player.rotation.y + PI + float(d[3]) * 0.3
|
||||
av.push(0.0, p, face, 0.0, d[5], d[4])
|
||||
av.push(1.0, p + fwd * 0.01 * d[5], face, 0.0, d[5], d[4])
|
||||
made.append(av)
|
||||
player.select(0)
|
||||
cam.rotation.x = -0.12
|
||||
for i in range(30):
|
||||
for av in made:
|
||||
av.update_at(0.5 + i * 0.016, 0.016)
|
||||
await get_tree().process_frame
|
||||
await RenderingServer.frame_post_draw
|
||||
get_viewport().get_texture().get_image().save_png("%s_players.png" % prefix)
|
||||
print("[world] saved %s_players.png" % prefix)
|
||||
day_night.time = 19.2 / 24.0
|
||||
day_night.apply(true)
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
await RenderingServer.frame_post_draw
|
||||
get_viewport().get_texture().get_image().save_png("%s_players_dusk.png" % prefix)
|
||||
print("[world] saved %s_players_dusk.png" % prefix)
|
||||
root.queue_free()
|
||||
|
||||
## The den by day with the pack resting, wolves stalking at night, and
|
||||
## fishing from the lake shore.
|
||||
|
||||
Reference in New Issue
Block a user