extends Node3D ## Dev-only capture harness. Loads main.tscn and drives the player through a scripted ## tour so a headless/CI run can record video of the actual game with Godot's ## --write-movie, without a human at the keyboard (and without screen-recording perms). ## ## /Applications/Godot.app/Contents/MacOS/Godot --path game \ ## --resolution 1280x720 --write-movie /tmp/cap.avi --quit-after 900 \ ## dev/demo.tscn ## ## Not shipped: excluded from export presets. Drives the player directly (position / ## yaw / pitch / _queue_attack) rather than faking InputEvents, so it stays immune to ## input-map changes. const MAIN := "res://main.tscn" var _main: Node3D var _player: Player var _t := 0.0 var _step := 0 var _step_t := 0.0 # Each step: {dur, move_to (Vector3 or null), look_at (Vector3 or null), act (String)} # act: "" | "punch" | "kick" | "rain" | "reset" var _script: Array = [] func _ready() -> void: var packed := load(MAIN) as PackedScene _main = packed.instantiate() add_child(_main) await get_tree().process_frame _player = _find_player(_main) if _player == null: push_error("[demo] no Player found") return # the demo drives the camera; don't let the OS grab the pointer Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _build_script() func _find_player(n: Node) -> Player: if n is Player: return n for c in n.get_children(): var r := _find_player(c) if r != null: return r return null func _build_script() -> void: # world layout (from Main._build_rack / _build_office, origin 0,0): # racks (-0.95,-0.7) (0.95,-0.7) # crates y=0.5 and y=1.2 rows, x -0.8..0.8 # desk+printer (2.7,-0.3) cabinet (-2.7,-0.3) cooler (3.3,1.6) # CRT (-2.7,1.3) box (1.9,2.4) turntable (-1.7,-1.3) _script = [] # ---- ACT 1: the loadout. Stand still and hold each weapon so the viewmodel, # ---- the grip and one swing per archetype can all be inspected frame by frame. for slot in range(6): _script.append({"dur": 0.5, "move_to": Vector3(0, 0, 3.2), "look_at": Vector3(0, 1.0, 0), "act": "weapon:%d" % slot}) _script.append({"dur": 1.0, "move_to": null, "look_at": null, "act": ""}) _script.append({"dur": 1.1, "move_to": null, "look_at": null, "act": "swing"}) # ---- ACT 2: the matrix. Box cutter shreds the carton, then clanks off the # ---- filing cabinet; the sledge then removes the cabinet in one go. _script += [ {"dur": 0.6, "move_to": null, "look_at": null, "act": "weapon:1"}, {"dur": 1.2, "move_to": Vector3(1.9, 0, 3.4), "look_at": Vector3(1.9, 0.3, 2.4), "act": ""}, {"dur": 0.45, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 0.45, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 0.9, "move_to": null, "look_at": null, "act": "swing"}, # cutter vs steel — expect the clank + the HUD nudge, and no damage {"dur": 1.3, "move_to": Vector3(-2.7, 0, 1.1), "look_at": Vector3(-2.7, 0.6, -0.3), "act": ""}, {"dur": 0.4, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 0.9, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 0.5, "move_to": null, "look_at": null, "act": "weapon:4"}, {"dur": 1.0, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 1.2, "move_to": null, "look_at": null, "act": "swing"}, ] # ---- ACT 3: the boss + the HUD styles _script += [ {"dur": 1.4, "move_to": Vector3(2.7, 0, 1.3), "look_at": Vector3(2.7, 0.95, -0.3), "act": ""}, {"dur": 1.0, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 1.0, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 1.0, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 1.4, "move_to": null, "look_at": null, "act": "swing"}, {"dur": 1.6, "move_to": Vector3(0.6, 0, 5.2), "look_at": Vector3(0, 0.6, 0), "act": "hud"}, {"dur": 1.6, "move_to": null, "look_at": null, "act": "hud"}, {"dur": 1.6, "move_to": null, "look_at": null, "act": "hud"}, {"dur": 1.6, "move_to": null, "look_at": null, "act": "hud"}, ] func _process(dt: float) -> void: if _player == null or _step >= _script.size(): return _t += dt var s: Dictionary = _script[_step] var dur: float = s["dur"] # fire the action when the step BEGINS, so a capture at step_start + n frames # actually shows the swing rather than the second of stillness before it if not s.get("_fired", false): s["_fired"] = true _do(String(s["act"])) _step_t += dt var mt = s.get("move_to") if mt != null: var to: Vector3 = mt var k: float = clampf(_step_t / maxf(dur, 0.001), 0.0, 1.0) k = k * k * (3.0 - 2.0 * k) # smoothstep so the dolly eases var from: Vector3 = s.get("_from", _player.global_position) if not s.has("_from"): s["_from"] = _player.global_position from = _player.global_position _player.global_position = from.lerp(to, k) var la = s.get("look_at") if la != null: _aim(la, clampf(_step_t / maxf(dur, 0.001), 0.0, 1.0)) if _step_t >= dur: _step += 1 _step_t = 0.0 ## Turn the body (yaw) + head (pitch) toward a world point, eased. func _aim(target: Vector3, k: float) -> void: var eye: Vector3 = _player.global_position + Vector3(0, _player.eye_height, 0) var d: Vector3 = target - eye if d.length() < 0.001: return var want_yaw := atan2(-d.x, -d.z) var want_pitch := atan2(d.y, Vector2(d.x, d.z).length()) var e := clampf(k * 0.14, 0.0, 1.0) + 0.06 _player.rotation.y = lerp_angle(_player.rotation.y, want_yaw, e) var head: Node3D = _player.get_node_or_null("Head") if head != null: head.rotation.x = lerp_angle(head.rotation.x, want_pitch, e) func _do(act: String) -> void: if act.begins_with("weapon:"): _player.select(int(act.substr(7))) return match act: "swing": _player._queued = true # same path a left-click takes "hud": var hud = _main.get("_hud") if hud != null: hud.cycle() "rain": if _main.has_method("_rain"): _main._rain(500) "reset": if _main.has_method("_reset"): _main._reset()