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 --name --address host:port ## [--password p] [--stay seconds] [--result ] [--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()