extends SceneTree ## Prop-collision smoke: proves the guard and an NPC cannot walk through street ## furniture, and that a guest slides around a bench instead of passing through ## it. Windowed (the guard reads captured-mouse input): ## ## godot --path . --script res://src/test/smoke_props.gd ## ## Exit code 0 = pass, 1 = failure. const MAX_FRAMES := 1800 const GUARD_PUSH_FRAMES := 100 var frame := 0 var gm: Node = null var started := false var step := 0 var step_frame := 0 var failures: Array[String] = [] var bench: Node3D = null var bench_center := Vector3.ZERO var bench_body: StaticBody3D = null var route_center := Vector3.ZERO var guard_start := Vector3.ZERO var npc: Node3D = null var npc_start := Vector3.ZERO func _initialize() -> void: root.size = Vector2i(1280, 720) root.add_child(load("res://scenes/main.tscn").instantiate()) func _process(_delta: float) -> bool: frame += 1 if gm == null: gm = root.find_child("GameManager", true, false) return false if not started: gm.call("_on_venue_selected", "hollywood_building") Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) started = true return false if frame > MAX_FRAMES: _fail("timeout before the collision checks finished") return true step_frame += 1 match step: 0: _find_bench() if bench == null: return false # Stand on the door side of the bench and walk into it (+z). The # street side is crowded with trees/planters now. var start := bench_center - Vector3(0, 0, 2.0) start.y = 0.1 gm.guard.global_position = start gm.guard.rotation.y = PI guard_start = start Input.action_press("move_forward") step = 1 step_frame = 0 1: if step_frame >= GUARD_PUSH_FRAMES: Input.action_release("move_forward") _check(gm.guard.global_position.z - guard_start.z > 0.5, "guard walked toward the bench") _check(gm.guard.global_position.z < bench_center.z - 0.25, "guard stopped at the bench instead of passing through it") _check(not _inside_bench(gm.guard.global_position), "guard is not inside the bench collider") # Step the guard aside so the guest test hits the bench, not the player. gm.guard.global_position = bench_center + Vector3(4.0, -0.08, 2.2) _stage_npc() step = 2 step_frame = 0 2: if npc == null or not is_instance_valid(npc): _stage_npc() return false if _npc_finished(): _check(npc.global_position.z > route_center.z + 0.35, "guest reached the far side of the stanchion") _check(npc.global_position.distance_to(route_center) > 0.4, "guest is not inside the stanchion collider") _check(npc_start.distance_to(npc.global_position) > 1.0, "guest moved while routing around the prop") _finish() return true return false func _find_bench() -> void: var detail := gm.get_node_or_null("World/StreetDetail") if detail == null: # The world root name differs between scene builds; search instead. detail = root.find_child("StreetDetail", true, false) if detail == null: _fail("StreetDetail not found") return for child in detail.get_children(): if str(child.name).begins_with("Prop_bench"): bench = child as Node3D break if bench == null: _fail("no bench prop placed") return bench_center = bench.global_position bench_body = bench.get_node_or_null("Collision") as StaticBody3D if bench_body == null: _fail("bench has no static collider") func _stage_npc() -> void: if gm.npcs.is_empty(): step = 0 step_frame = 0 return var detail := root.find_child("StreetDetail", true, false) route_center = bench_center if detail != null: for child in detail.get_children(): if str(child.name).begins_with("Prop_stanchion"): route_center = (child as Node3D).global_position break npc = gm.npcs[0] npc_start = route_center + Vector3(0, 0.5, -2.0) npc.global_position = npc_start npc.velocity = Vector3.ZERO npc.arrived = false npc.awaiting_interaction = false npc.lifetime_s = 999.0 npc.reaction_mode = "" if npc.npc != null: # "enter" would send the guest to the door once the path ends; keep this # test focused on routing around the prop. npc.npc.intent = "roam" # A stanchion sits in open sidewalk, so the route is not squeezed by trees # or bins; the target forces a straight line through it. npc.target_position = Vector3(route_center.x, 0.5, route_center.z + 5.5) func _npc_finished() -> bool: if npc == null or not is_instance_valid(npc): return false return npc.global_position.z > route_center.z + 0.35 \ or npc.global_position.distance_to(npc.target_position) < 0.9 func _inside_bench(point: Vector3) -> bool: if bench_body == null: return false var collider := bench_body.get_node_or_null("Shape") as CollisionShape3D if collider == null or not (collider.shape is BoxShape3D): return false var box := collider.shape as BoxShape3D var half := box.size * 0.5 var center := collider.global_position half.x += 0.1 half.z += 0.1 return absf(point.x - center.x) < half.x and absf(point.z - center.z) < half.z func _check(condition: bool, message: String) -> void: if condition: print("ok: " + message) else: _fail(message) func _fail(message: String) -> void: failures.append(message) _finish() func _finish() -> void: Input.action_release("move_forward") if failures.is_empty(): print("SMOKE OK: props block the guard and guests route around them") quit(0) else: for message in failures: printerr("SMOKE FAIL: " + message) quit(1)