extends SceneTree ## Dev tool: screenshot first-person weapon choreography — reload phases for ## representative weapons (mag / boltmag / tube / break), the knife slash, ## and the fire kick. Run: ## godot --path . --windowed --resolution 1280x720 -s res://debug/fp_weapon_capture.gd -- var _frames := 0 var _out_dir := "." var _player: Node = null var _wman: Node = null var _mode := "boot" var _phase_frame := 0 var _step := 0 # [tag, slot, weapon_id, screenshot fractions of reload_time] # Slots 11+ so the loadout's own weapons get properly hidden by _equip_slot # (re-spawning INTO an occupied slot leaves the old node visible). var _weapons := [ ["m4", 11, "m4", [0.12, 0.25, 0.38, 0.55, 0.72, 0.9]], ["awp", 12, "awp", [0.3, 0.6, 0.85]], ["rocket", 13, "rocket_launcher", [0.25, 0.55, 0.8]], ["shotgun", 14, "double_barrel_shotgun", [0.25, 0.6, 0.9]], ] var _wi := 0 var _shots: Array = [] var _reload_T := 0.0 var _reload_start_ms := 0 func _initialize() -> void: var args := OS.get_cmdline_user_args() if args.size() > 0: _out_dir = args[0] change_scene_to_file("res://ui/main_menu/main_menu.tscn") func _process(_delta: float) -> bool: _frames += 1 if _mode == "boot": if _frames == 40: var nm = root.get_node_or_null("NetworkManager") if nm and nm.has_method("start_singleplayer_match"): nm.start_singleplayer_match(GameMode.DEATHMATCH) change_scene_to_file("res://scenes/maps/test_level/test_level.tscn") elif _frames >= 160: for p in root.find_children("*", "CharacterBody3D", true, false): if p.has_method("get_visual_model") and p.is_multiplayer_authority(): _player = p break if not _player: printerr("fp_capture: no player") return true _wman = _player.get_node_or_null("HeadPivot/Camera3D/WeaponManager") if not _wman: for c in _player.find_children("*", "Node", true, false): if c.name == "WeaponManager": _wman = c break if not _wman: printerr("fp_capture: no WeaponManager") return true _player.set_physics_process(false) _player.global_position = Vector3(0, 1.2, 14) _mode = "spawn" return false if _mode == "spawn": # Force-spawn the test weapons into known slots. for w in _weapons: _wman._spawn_weapon(w[1], w[2]) _mode = "next_weapon" return false if _mode == "next_weapon": if _wi >= _weapons.size(): _mode = "knife_equip" _phase_frame = 0 return false var entry: Array = _weapons[_wi] _wman._equip_slot(entry[1]) _phase_frame = 0 _mode = "reload_start" return false if _mode == "reload_start": _phase_frame += 1 if _phase_frame < 15: return false var entry: Array = _weapons[_wi] var w = _wman.weapons.get(entry[1]) if w == null: _wi += 1 _mode = "next_weapon" return false # Drain ammo state and kick off the reload. w._start_reload() _reload_T = w.reload_time _shots = entry[3].duplicate() _reload_start_ms = Time.get_ticks_msec() _mode = "reload_watch" return false if _mode == "reload_watch": var entry: Array = _weapons[_wi] var elapsed := (Time.get_ticks_msec() - _reload_start_ms) / 1000.0 if _shots.size() > 0: if elapsed >= float(_shots[0]) * _reload_T: _snap("fp_%s_reload_%d" % [entry[0], entry[3].size() - _shots.size() + 1]) _shots.pop_front() elif elapsed >= _reload_T + 0.3: _wi += 1 _mode = "next_weapon" return false if _mode == "knife_equip": _wman._equip_slot(4) _phase_frame = 0 _mode = "knife_swing" return false if _mode == "knife_swing": _phase_frame += 1 if _phase_frame == 20: var knife = _wman.weapons.get(4) if knife and knife.has_method("_swing"): knife._swing() elif _phase_frame == 26: _snap("fp_knife_windup") elif _phase_frame == 32: _snap("fp_knife_slash") elif _phase_frame >= 60: _mode = "kick" _phase_frame = 0 return false if _mode == "kick": _phase_frame += 1 if _phase_frame == 5: _wman._equip_slot(11) elif _phase_frame == 30: var w = _wman.weapons.get(11) if w: w._fire() elif _phase_frame == 33: _snap("fp_m4_kick") return true return false return false func _snap(tag: String) -> void: var img := root.get_viewport().get_texture().get_image() var path := _out_dir + "/" + tag + ".png" img.save_png(path) print("fp_capture: saved ", path)