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 _skin_id := "" var _quick := false 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] if args.size() > 1: _skin_id = args[1] if args.size() > 2: _quick = args[2] == "quick" 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 skin_manager = root.get_node_or_null("SkinManager") if not _skin_id.is_empty() and skin_manager \ and skin_manager.skins.has(_skin_id): # Capture-only override: do not persist over the player's actual # skin selection in user://. skin_manager.active_skin_id = _skin_id 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 = "quick" if _quick else "spawn" _phase_frame = 0 return false if _mode == "quick": _phase_frame += 1 if _phase_frame == 15: if not _skin_id.is_empty(): _verify_skin(_skin_id) _snap("fp_quick_idle") var quick_weapon = _wman.weapons.get(_wman.active_slot) if quick_weapon and quick_weapon.has_method("_start_reload"): quick_weapon._start_reload() elif _phase_frame == 32: _snap("fp_quick_reload") elif _phase_frame == 38: var skin_manager = root.get_node_or_null("SkinManager") if skin_manager: skin_manager.skin_changed.emit("aria") elif _phase_frame == 48: _verify_skin("aria") _snap("fp_quick_skin_swap") elif _phase_frame >= 55: return true 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 == 10: _snap("fp_knife_guard") elif _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) func _verify_skin(skin_id: String) -> void: var skin_manager = root.get_node_or_null("SkinManager") var weapon = _wman.weapons.get(_wman.active_slot) if _wman else null var sleeve = weapon.get_node_or_null("ArmR/Sleeve") if weapon else null var hand = weapon.get_node_or_null("ArmR/Hand") if weapon else null if skin_manager == null or sleeve == null or hand == null: printerr("fp_capture: skin swap verification missing arm") return var material = sleeve.get_active_material(0) var actual = material.get_shader_parameter("albedo_color") \ if material is ShaderMaterial else null var expected: Color = skin_manager.get_skin(skin_id).viewmodel_sleeve_color var selected_skin: PlayerSkin = skin_manager.get_skin(skin_id) var palm := hand.get_node_or_null("Palm") as MeshInstance3D var palm_material := palm.get_active_material(0) if palm else null var palm_actual = palm_material.get_shader_parameter("albedo_color") \ if palm_material is ShaderMaterial else null var palm_expected: Color match selected_skin.viewmodel_hand_style: "bare": palm_expected = selected_skin.viewmodel_skin_color "android": palm_expected = selected_skin.viewmodel_accent_color _: palm_expected = selected_skin.viewmodel_glove_color var finger_segments: int = hand.find_children( "Finger*", "MeshInstance3D", true, false).size() var thumb_segments: int = hand.find_children( "Thumb*", "MeshInstance3D", true, false).size() if not (actual is Color and actual.is_equal_approx(expected)): printerr("fp_capture: skin swap mismatch actual=%s expected=%s" % [ actual, expected]) elif not (palm_actual is Color and palm_actual.is_equal_approx(palm_expected)): printerr("fp_capture: hand palette mismatch actual=%s expected=%s" % [ palm_actual, palm_expected]) elif finger_segments != 8 or thumb_segments != 2: printerr("fp_capture: incomplete anime hand fingers=%d thumb=%d" % [ finger_segments, thumb_segments]) else: print("fp_capture: live skin swap verified %s style=%s fingers=%d thumb=%d" % [ skin_id, selected_skin.viewmodel_hand_style, finger_segments, thumb_segments])