- 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)
128 lines
4.9 KiB
GDScript
128 lines
4.9 KiB
GDScript
extends SceneTree
|
|
## A scripted headless player for the network tests (tests/test_net_lobby.gd
|
|
## starts several of these as separate processes).
|
|
##
|
|
## Args (after --): --identity <dir> --name <name> --address host:port
|
|
## [--password p] [--stay seconds] [--result <json path>] [--forge] [--protocol n]
|
|
## Joins, stays a while noting who it saw, leaves, writes the outcome to the
|
|
## result file and quits. Gives up after 20 s whatever happens.
|
|
|
|
var _outcome := ""
|
|
var _reason := ""
|
|
var _pos := Vector3.ZERO
|
|
var _corrections := 0
|
|
var _others := {} ## name -> last position seen in a snapshot
|
|
var _terrain: Node3D
|
|
|
|
## Walks (and optionally cheats) on the server's island, sending positions
|
|
## 20 times a second like a real client. Corrections from the server are
|
|
## obeyed, as the real client does.
|
|
## --walk line --speed 4 --walk-time 3 walk along +X, then stand still
|
|
## --cheat teleport after 1 s, claim to be 60 m away
|
|
## --cheat outside walk straight out toward the world's edge at 7 m/s
|
|
## --cheat respawn a flagged teleport to the spawn (legitimate)
|
|
func _move(net: Node, args: Dictionary, res: Dictionary) -> void:
|
|
_terrain = load("res://scripts/world/terrain.gd").new()
|
|
root.add_child(_terrain) # takes its seed from the server (net.world_info)
|
|
_pos = net.world_info.get("spawn", Vector3.ZERO)
|
|
net.corrected.connect(func(p): _corrections += 1; _pos = p)
|
|
net.snapshot.connect(func(_t, states):
|
|
for s in states:
|
|
if net.players.has(int(s[0])):
|
|
_others[net.players[int(s[0])].name] = s[1])
|
|
var mode := String(args.get("walk", args.get("cheat", "")))
|
|
var speed := float(args.get("speed", 4.0))
|
|
var dur := float(args.get("walk-time", 3.0))
|
|
res.walk_start = [_pos.x, _pos.y, _pos.z]
|
|
var t := 0.0
|
|
var dir := Vector3(1, 0, 0)
|
|
if mode == "outside":
|
|
dir = Vector3(_pos.x, 0, _pos.z).normalized() if Vector2(_pos.x, _pos.z).length() > 1.0 else Vector3(1, 0, 0)
|
|
speed = 7.0
|
|
dur = 40.0
|
|
var step := 0.05
|
|
var prev_us := Time.get_ticks_usec()
|
|
while t < dur and net.state == "connected":
|
|
var now_us := Time.get_ticks_usec()
|
|
var real_dt := (now_us - prev_us) / 1000000.0
|
|
prev_us = now_us
|
|
var teleported := false
|
|
var anim := 1
|
|
match mode:
|
|
"teleport":
|
|
if absf(t - 1.0) < step * 0.5:
|
|
_pos += Vector3(60, 0, 0)
|
|
anim = 0
|
|
"respawn":
|
|
if absf(t - 1.0) < step * 0.5:
|
|
var sp: Vector3 = net.world_info.get("island_spawn", _pos)
|
|
_pos = sp + Vector3(0.5, 0, 0.5)
|
|
teleported = true
|
|
anim = 0
|
|
_:
|
|
_pos += dir * speed * real_dt # real elapsed time, like the game's physics
|
|
_pos.y = _terrain.sample_height(_pos.x, _pos.z)
|
|
net.send_state(_pos, 0.0, 0.0, anim, "", teleported)
|
|
await create_timer(step).timeout
|
|
t += step
|
|
if mode == "outside" and _corrections > 0 and t > 1.0:
|
|
break
|
|
res.walk_end = [_pos.x, _pos.y, _pos.z]
|
|
# Stand still for a moment, still sending, so the last position settles.
|
|
for _i in range(10):
|
|
net.send_state(_pos, 0.0, 0.0, 0, "", false)
|
|
await create_timer(step).timeout
|
|
|
|
func _initialize() -> void:
|
|
_run.call_deferred()
|
|
|
|
func _run() -> void:
|
|
var net: Node = root.get_node("Net")
|
|
var args: Dictionary = net.parse_args(OS.get_cmdline_user_args())
|
|
var Identity = load("res://scripts/net/identity.gd")
|
|
var me = Identity.open(String(args.get("identity", "user://test_net/bot")))
|
|
if args.has("name"):
|
|
me.name = String(args.name)
|
|
net.identity = me
|
|
net.debug_forge = args.has("forge")
|
|
net.debug_protocol = int(args.get("protocol", 0))
|
|
net.debug_skip_name_check = args.has("raw-name")
|
|
net.joined.connect(func(): _outcome = "joined")
|
|
net.join_failed.connect(func(r): _outcome = "rejected"; _reason = r)
|
|
var res := {"joined": false, "reason": "", "saw": [], "admin": false, "server_name": "", "id": me.player_id}
|
|
net.join(String(args.get("address", "127.0.0.1")), String(args.get("password", "")))
|
|
var start := Time.get_ticks_msec()
|
|
while _outcome.is_empty() and Time.get_ticks_msec() - start < 20000:
|
|
await process_frame
|
|
if _outcome == "joined":
|
|
res.joined = true
|
|
res.server_name = net.server_info.get("server_name", "")
|
|
res.admin = bool(net.players.get(net.local_peer, {}).get("admin", false))
|
|
if args.has("walk") or args.has("cheat"):
|
|
await _move(net, args, res)
|
|
var saw := {}
|
|
var stay := float(args.get("stay", 2.0))
|
|
var s := 0.0
|
|
while s < stay and net.state == "connected":
|
|
for p in net.players.values():
|
|
saw[p.name] = true
|
|
await create_timer(0.1).timeout
|
|
s += 0.1
|
|
res.saw = saw.keys()
|
|
res.corrections = _corrections
|
|
res.final_pos = [_pos.x, _pos.y, _pos.z]
|
|
if _others.has("Alice"):
|
|
var ap: Vector3 = _others.Alice
|
|
res.alice_pos = [ap.x, ap.y, ap.z]
|
|
net.leave()
|
|
for _i in range(10):
|
|
await process_frame
|
|
else:
|
|
res.reason = _reason if not _reason.is_empty() else "timed out"
|
|
if args.has("result"):
|
|
var f := FileAccess.open(String(args.result), FileAccess.WRITE)
|
|
f.store_string(JSON.stringify(res))
|
|
f.close()
|
|
print("[bot %s] %s" % [me.name, JSON.stringify(res)])
|
|
quit()
|