extends Node ## In a multiplayer world: sends where you are, shows where everyone else is. ## Added by world.gd when the game is connected to a server. ## ## - Your own movement stays instant (you move locally and tell the server). ## The server checks each move; if it refuses one it sends you back to your ## last good position. ## - Other players are avatars (avatar.gd), drawn INTERP_DELAY behind the ## server's clock and interpolated between its snapshots. ## - The time of day follows the server, so everyone shares the same sky ## (weather follows from the seed and the time). const AvatarScript = preload("res://scripts/net/avatar.gd") const INTERP_DELAY := 0.1 var world: Node3D var player: CharacterBody3D var avatars: Dictionary = {} ## peer -> avatar.gd var corrections := 0 var _send_left := 0.0 var _offset := INF ## server time minus local time (smoothed) var _teleport_next := false var _root: Node3D func setup(w: Node3D) -> void: world = w player = w.player _root = Node3D.new() _root.name = "Avatars" w.add_child(_root) Net.snapshot.connect(_on_snapshot) Net.corrected.connect(_on_corrected) Net.clock.connect(_on_clock) Net.players_changed.connect(_sync_avatars) Net.player_joined.connect(func(n): Actions.notify.emit("%s joined" % n)) Net.player_left.connect(func(n): Actions.notify.emit("%s left" % n)) if Net.world_info.has("day"): _on_clock(int(Net.world_info.day), float(Net.world_info.time)) func _exit_tree() -> void: for sig in [[Net.snapshot, _on_snapshot], [Net.corrected, _on_corrected], [Net.clock, _on_clock], [Net.players_changed, _sync_avatars]]: if (sig[0] as Signal).is_connected(sig[1]): (sig[0] as Signal).disconnect(sig[1]) static func _now() -> float: return Time.get_ticks_msec() / 1000.0 ## Server time we draw other players at. func render_time() -> float: return _now() + _offset - INTERP_DELAY if _offset != INF else -INF ## Flags the next update as a legitimate jump (respawning), so the server ## checks it against the spawn instead of against walking speed. func mark_teleport() -> void: _teleport_next = true func _physics_process(delta: float) -> void: _send_left -= delta if _send_left > 0.0: return _send_left = Net.SEND_INTERVAL var cam: Camera3D = player.get_node("Camera3D") Net.send_state(player.global_position, player.rotation.y, cam.rotation.x, anim_state(), player.held_item_id(), _teleport_next) _teleport_next = false ## What the local player is doing, for other people's view of them. func anim_state() -> int: if player.global_position.y < player.water_level - player.FLOAT_DEPTH + 0.1 and not player.is_on_floor(): return AvatarScript.Anim.SWIM if not player.is_on_floor(): return AvatarScript.Anim.AIR var flat := Vector2(player.velocity.x, player.velocity.z).length() if flat > 6.0: return AvatarScript.Anim.RUN if flat > 0.5: return AvatarScript.Anim.WALK return AvatarScript.Anim.IDLE func _process(delta: float) -> void: var rt := render_time() if rt == -INF: return for a in avatars.values(): a.update_at(rt, delta) func _on_snapshot(t: float, states: Array) -> void: var sample := t - _now() # Smooth the clock offset so jitter doesn't make avatars judder. Jump # straight to it at first, or if it's way off (a long stall). if _offset == INF or absf(sample - _offset) > 1.0: _offset = sample else: _offset = lerpf(_offset, sample, 0.05) for s in states: var peer := int(s[0]) if peer == Net.local_peer: continue var a: Node3D = avatars.get(peer) if a == null: a = _add_avatar(peer) if a == null: continue a.push(float(s[6]), s[1], s[2], s[3], s[4], s[5]) # stamped with when they moved, not when it was sent func _add_avatar(peer: int) -> Node3D: if not Net.players.has(peer): return null var a: Node3D = AvatarScript.new() a.setup(peer, Net.players[peer]) _root.add_child(a) avatars[peer] = a return a func _sync_avatars() -> void: for peer in avatars.keys(): if not Net.players.has(peer): avatars[peer].queue_free() avatars.erase(peer) func _on_corrected(pos: Vector3) -> void: corrections += 1 player.global_position = pos player.velocity = Vector3.ZERO func _on_clock(day: int, time: float) -> void: world.day_night.day = day # Small drift: ease the clock across. A big gap (joining, a stall): jump. var diff := wrapf(time - world.day_night.time, -0.5, 0.5) if absf(diff) > 0.01: world.day_night.time = time world.day_night.apply(true) world.weather.snap() # (a night slept through: the weather jumps too)