238 lines
7.4 KiB
GDScript
238 lines
7.4 KiB
GDScript
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 -- <out_dir>
|
|
|
|
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])
|