Files
Cinderwild/scripts/net/net_sync.gd

134 lines
4.4 KiB
GDScript

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)