Zombies/WalkerAnimations.cs

Static data table for walker (zombie) animation clip groups and speed-tier thresholds. Provides lists of clip names for Walk/Run/Sprint/Supersprint, attack/mantle/slip/death/pain/spawn sets, and helper methods to pick a clip list or tier name from a speed rating.

File Access
using System.Collections.Generic;

namespace NZombies;

/// <summary>
/// GENERATED by Tools/build_walker_vmdl.py - do not hand-edit.
///
/// The walker's clips, grouped the way the AI consumes them. These are the
/// clips that actually compiled into walker_honorguard_dmx.vmdl, so this table
/// and the model cannot drift apart.
///
/// Locomotion naming: a_nz_&lt;tier&gt;_(ad|au)N. Thresholds mirror
/// ZombieVariant.SpeedTiers; a ZombieVariant asset overrides this if assigned.
/// </summary>
public static class WalkerAnimations
{
	/// <summary>
	/// The tier boundaries, as speed RATINGS (not velocities).
	///
	/// ⛔️ NAMED BECAUSE THEY ARE READ FROM OUTSIDE NOW. The four methods below each spelled
	/// 36/71/155 out again, and RoundManager's last-few sprint needs the Sprint boundary too --
	/// a fifth copy of a number is a number that will eventually disagree with itself. Anything
	/// wanting "make this zombie sprint" should say SprintRating, not 71.
	///
	/// ⚠️ A RATING IS NOT A SPEED. It is matched against these thresholds only to pick a tier;
	/// how fast the zombie then moves comes back out of the chosen clip. See ForSpeed.
	/// </summary>
	public const float RunRating = 36f;

	/// <inheritdoc cref="RunRating"/>
	public const float SprintRating = 71f;

	/// <inheritdoc cref="RunRating"/>
	public const float SuperSprintRating = 155f;

	/// <summary>
	/// Locomotion clips for a speed rating, taking the highest tier at or below it.
	///
	/// ⚠️ The argument is the ROUND SPEED RATING (curve + jitter), not how fast
	/// the zombie moves. In the original these thresholds are matched against
	/// the value from nz_zombie_walker_derriese:772, whose only job is choosing
	/// a tier; actual movement speed then comes back out of the chosen clip.
	/// See WalkerGroundSpeeds.
	/// </summary>
	public static List<string> ForSpeed( float speed )
	{
		if ( speed >= SuperSprintRating ) return SuperSprint;
		if ( speed >= SprintRating ) return Sprint;
		if ( speed >= RunRating ) return Run;
		return Walk;
	}

	/// <summary>Tier name for a speed rating — the key WalkerGroundSpeeds falls
	/// back to when a clip isn't in the measured table (a ZombieVariant can name
	/// clips we never measured).</summary>
	public static string TierName( float speed )
	{
		if ( speed >= SuperSprintRating ) return "SuperSprint";
		if ( speed >= SprintRating ) return "Sprint";
		if ( speed >= RunRating ) return "Run";
		return "Walk";
	}

	/// <summary>
	/// Attack clips for a speed. Attacks are speed-tiered in the source data
	/// the same way locomotion is — a sprinting zombie has its own swing, and
	/// falling back to the standing one makes it snap on contact. Empty tiers
	/// fall back to the standing set.
	/// </summary>
	public static List<string> AttackForSpeed( float speed )
	{
		if ( speed >= SuperSprintRating && AttackSupersprint.Count > 0 ) return AttackSupersprint;
		if ( speed >= SprintRating && AttackSprint.Count > 0 ) return AttackSprint;
		if ( speed >= RunRating && AttackRun.Count > 0 ) return AttackRun;

		// No lower speed gate. This is only reached for a MOVING attack — the
		// standing case is decided by the target's motion in AttackClipsForNow,
		// not here — and a round-1 zombie's rating is legitimately 0 while it is
		// still walking at ~46 u/s. Gating on speed >= 1 sent those to the
		// standing swing mid-stride.
		if ( AttackWalk.Count > 0 ) return AttackWalk;
		return AttackStand;
	}

	/// <summary>
	/// The vault over an open barricade, picked by the same speed tiers as
	/// movement and attacks.
	///
	/// ⛔ THE HEIGHT IS IN THE CLIP NAME AND IT IS NOT DECORATIVE. The set ships
	/// `nz_mantle_over_48 / _72 / _96 / _128` — one per obstacle height — and a
	/// barricade's sill is 44u, so the 48 clips are the ones that land on it. If
	/// the sill ever becomes mapper-editable these must be CHOSEN from that
	/// height, or a zombie vaults 128u of air over a 44u wall.
	///
	/// ⚠️ Only two basic variants exist at this height: a standing mantle and a
	/// running one. Every tier above Run therefore shares the run clip — that is
	/// the animation set's own limit, not a shortcut.
	/// </summary>
	public static List<string> MantleForSpeed( float speed )
	{
		if ( speed >= RunRating && MantleRun.Count > 0 ) return MantleRun;
		return Mantle;
	}

	/// <summary>Standing/walking vault over a ~48u obstacle.</summary>
	/// <summary>
	/// Going over on a Banana Colada slick bar.
	///
	/// ⚠️ TWO OF THE ORIGINAL'S THREE. `SlipGunSequences` in nz_zombiebase_moo.lua:7463 lists
	/// `nz_slipslide_collapse`, `nz_sprint_slipslide` and `nz_sprint_slipslide_a`, and the entity
	/// picks one at random. The collapse clip is decompiled and staged but does NOT survive
	/// conversion — `smd_to_fbx.py` reports its root Z as 68719476736 (2^36) and its lean as NaN,
	/// from source data that is provably clean, so it is held back rather than shipped broken.
	/// Add it here the moment that converts; the caller already picks at random over whatever is
	/// in this list.
	///
	/// ⚠️ THE OTHER THREE SLIP CLIPS IN THE SET — `nz_walk_slipslide`, `nz_run_slipslide`,
	/// `nz_run_slipslide_a` — are deliberately NOT here. The original does not pick by gait, and
	/// matching it matters more than covering every staged file.
	/// </summary>
	public static readonly List<string> Slip = new()
	{
		"nz_sprint_slipslide",
		"nz_sprint_slipslide_a",
	};

	public static readonly List<string> Mantle = new()
	{
		"nz_mantle_over_48",
	};

	/// <summary>Vault taken at a run. 36 and 48 are both authored; 48 matches the
	/// sill, 36 is kept as the fallback for a shorter one.</summary>
	public static readonly List<string> MantleRun = new()
	{
		"nz_base_zombie_run_mantle_over_48",
		"nz_base_zombie_run_mantle_over_36",
	};

	public static readonly List<string> Walk = new()
	{
		"a_nz_walk_ad1",
		"a_nz_walk_ad20",
		"a_nz_walk_ad22",
		"a_nz_walk_ad23",
		"a_nz_walk_ad24",
		"a_nz_walk_ad25",
		"a_nz_walk_ad3",
		"a_nz_walk_ad7",
		"a_nz_walk_au1",
		"a_nz_walk_au12",
		"a_nz_walk_au20",
		"a_nz_walk_au21",
		"a_nz_walk_au3",
		"a_nz_walk_au7",
		"a_nz_walk_au8",
	};

	public static readonly List<string> Run = new()
	{
		"a_nz_run_ad11",
		"a_nz_run_ad14",
		"a_nz_run_ad2",
		"a_nz_run_ad21",
		"a_nz_run_ad8",
		"a_nz_run_au1",
		"a_nz_run_au2",
		"a_nz_run_au20",
		"a_nz_run_au21",
		"a_nz_run_au23",
		"a_nz_run_au4",
		"a_nz_run_au5",
	};

	public static readonly List<string> Sprint = new()
	{
		"a_nz_sprint_ad21",
		"a_nz_sprint_ad22",
		"a_nz_sprint_ad24",
		"a_nz_sprint_ad3",
		"a_nz_sprint_ad4",
		"a_nz_sprint_ad5",
		"a_nz_sprint_au3",
		"a_nz_sprint_au4",
	};

	public static readonly List<string> SuperSprint = new()
	{
		"a_nz_supersprint_ad10",
		"a_nz_supersprint_ad12",
		"a_nz_supersprint_ad13",
		"a_nz_supersprint_ad3",
		"a_nz_supersprint_ad4",
		"a_nz_supersprint_ad5",
		"a_nz_supersprint_ad6",
		"a_nz_supersprint_au12",
		"a_nz_supersprint_au3",
		"a_nz_supersprint_au4",
	};

	public static readonly List<string> AttackStand = new()
	{
		"nz_attack_stand_ad_1",
		"nz_attack_stand_au_1",
		"nz_attack_stand_long",
		"nz_base_attack_ranged_01",
		"nz_base_attack_ranged_left_blend_01",
		"nz_base_attack_ranged_react_left_01",
		"nz_base_attack_ranged_right_blend_01",
		"nz_base_attack_ranged_throw_left_01",
		"nz_base_zombie_cellbreaker_attack_01",
		"nz_base_zombie_cellbreaker_attack_02",
		"nz_base_zombie_cellbreaker_attack_03",
		"nz_fwd_ad_attack_v1",
		"nz_fwd_ad_attack_v2",
		"nz_legacy_attack_superwindmill",
		"nz_legacy_attack_v11",
		"nz_legacy_attack_v12",
		"nz_legacy_attack_v3",
		"nz_legacy_attack_v4",
		"nz_legacy_attack_v6",
		"nz_legacy_jap_attack_v1",
		"nz_legacy_jap_attack_v2",
		"nz_legacy_jap_attack_v3",
		"nz_legacy_jap_attack_v4",
		"nz_legacy_jap_attack_v5",
		"nz_t8_attack_stand_larm_1",
		"nz_t8_attack_stand_larm_2",
		"nz_t8_attack_stand_larm_3",
		"nz_t8_attack_stand_larm_4",
		"nz_t8_attack_stand_larm_5",
		"nz_t8_attack_stand_larm_6",
		"nz_t8_attack_stand_rarm_1",
		"nz_t8_attack_stand_rarm_2",
		"nz_t8_attack_stand_rarm_3",
		"nz_t8_attack_stand_rarm_4",
		"nz_t8_attack_stand_rarm_5",
		"nz_t8_attack_stand_rarm_6",
		"nz_win_attack_larm",
		"nz_win_attack_lbody",
		"nz_win_attack_rarm",
		"nz_win_attack_rbody",
	};

	public static readonly List<string> AttackWalk = new()
	{
		"nz_t8_attack_walk_larm_1",
		"nz_t8_attack_walk_larm_2",
		"nz_t8_attack_walk_larm_3",
		"nz_t8_attack_walk_larm_4",
		"nz_t8_attack_walk_larm_5",
		"nz_t8_attack_walk_larm_6",
		"nz_t8_attack_walk_rarm_1",
		"nz_t8_attack_walk_rarm_2",
		"nz_t8_attack_walk_rarm_3",
		"nz_t8_attack_walk_rarm_4",
		"nz_t8_attack_walk_rarm_5",
		"nz_t8_attack_walk_rarm_6",
		"nz_walk_ad_attack_v1",
		"nz_walk_ad_attack_v2",
		"nz_walk_ad_attack_v3",
		"nz_walk_ad_attack_v4",
		"nz_walk_ad_attack_v5",
		"nz_walk_ad_attack_v6",
		"nz_walk_ad_attack_v7",
		"nz_walk_ad_attack_v8",
		"nz_walk_ad_attack_v9",
	};

	public static readonly List<string> AttackRun = new()
	{
		"nz_legacy_jap_run_attack_v1",
		"nz_legacy_jap_run_attack_v2",
		"nz_legacy_run_attack_v1",
		"nz_legacy_run_attack_v2",
		"nz_legacy_run_attack_v3",
		"nz_legacy_run_attack_v4",
		"nz_run_ad_attack_v1",
		"nz_run_ad_attack_v2",
		"nz_run_ad_attack_v3",
		"nz_run_ad_attack_v4",
		"nz_run_au_attack_v1",
		"nz_run_au_attack_v2",
		"nz_run_au_attack_v3",
		"nz_run_au_attack_v4",
		"nz_t8_attack_run_larm_1",
		"nz_t8_attack_run_larm_2",
		"nz_t8_attack_run_larm_3",
		"nz_t8_attack_run_larm_4",
		"nz_t8_attack_run_rarm_1",
		"nz_t8_attack_run_rarm_2",
		"nz_t8_attack_run_rarm_3",
		"nz_t8_attack_run_rarm_4",
	};

	public static readonly List<string> AttackSprint = new()
	{
		"nz_t8_attack_sprint_larm_1",
		"nz_t8_attack_sprint_larm_2",
		"nz_t8_attack_sprint_larm_3",
		"nz_t8_attack_sprint_larm_4",
		"nz_t8_attack_sprint_rarm_1",
		"nz_t8_attack_sprint_rarm_2",
		"nz_t8_attack_sprint_rarm_3",
		"nz_t8_attack_sprint_rarm_4",
	};

	public static readonly List<string> AttackSupersprint = new()
	{
		"nz_t8_attack_supersprint_larm_1",
		"nz_t8_attack_supersprint_larm_2",
		"nz_t8_attack_supersprint_rarm_1",
		"nz_t8_attack_supersprint_rarm_2",
	};

	public static readonly List<string> Death = new()
	{
		"nz_base_zombie_cellbreaker_death_01",
		"nz_base_zombie_cellbreaker_death_02",
		"nz_base_zombie_cellbreaker_death_explode",
		"nz_base_zombie_cellbreaker_death_mg",
		"nz_bayonet_thrust_death",
		"nz_death_1",
		"nz_death_2",
		"nz_death_3",
		"nz_death_blast_1",
		"nz_death_blast_2",
		"nz_death_blast_from_back",
		"nz_death_blast_from_left",
		"nz_death_blast_from_right",
		"nz_death_elec_1",
		"nz_death_elec_2",
		"nz_death_elec_3",
		"nz_death_elec_4",
		"nz_death_explosion_run_b_v2",
		"nz_death_explosion_stand_b_v3",
		"nz_death_f_1",
		"nz_death_f_10",
		"nz_death_f_11",
		"nz_death_f_12",
		"nz_death_f_13",
		"nz_death_f_2",
		"nz_death_f_3",
		"nz_death_f_4",
		"nz_death_f_5",
		"nz_death_f_6",
		"nz_death_f_7",
		"nz_death_f_8",
		"nz_death_f_9",
		"nz_death_fallback",
		"nz_death_falltoknees_1",
		"nz_death_falltoknees_2",
		"nz_death_neckgrab",
		"nz_death_nerve",
		"nz_death_run_forward_crumple",
		"nz_dth_freeze_1",
		"nz_dth_freeze_2",
		"nz_dth_freeze_3",
		"nz_dth_freeze_4",
		"nz_dth_freeze_5",
		"nz_exposed_death_twist",
		"nz_firestaff_death_collapse_a",
		"nz_firestaff_death_collapse_b",
		"nz_flame_death_a",
		"nz_flame_death_e",
		"nz_flame_death_f",
		"nz_flame_death_g",
		"nz_flame_death_h",
		"nz_icestaff_death_a",
		"nz_icestaff_death_b",
		"nz_icestaff_death_c",
		"nz_icestaff_death_d",
		"nz_icestaff_death_e",
		"nz_t9_dth_f_chest_hvy_00",
		"nz_t9_dth_f_chest_hvy_01",
		"nz_t9_dth_f_chest_hvy_02",
		"nz_t9_dth_f_chest_lt_00",
		"nz_t9_dth_f_chest_lt_01",
		"nz_t9_dth_f_chest_lt_02",
		"nz_t9_dth_f_chest_lt_03",
		"nz_t9_dth_f_chest_lt_04",
		"nz_t9_dth_f_chest_lt_05",
		"nz_t9_dth_f_chest_lt_06",
		"nz_t9_dth_f_chest_lt_07",
		"nz_t9_dth_f_chest_lt_08",
		"nz_t9_dth_f_chest_lt_09",
		"nz_t9_dth_f_head_hvy_01",
		"nz_t9_dth_f_head_hvy_02",
		"nz_t9_dth_f_head_hvy_03",
		"nz_t9_dth_f_head_lt_00",
		"nz_t9_dth_f_head_lt_01",
		"nz_t9_dth_f_head_lt_02",
		"nz_t9_dth_f_larm_hvy_00",
		"nz_t9_dth_f_larm_hvy_01",
		"nz_zombie_tesla_death_e",
	};

	public static readonly List<string> Pain = new()
	{
		"nz_pain_dismem_larm_1",
		"nz_pain_dismem_larm_2",
		"nz_pain_dismem_larm_3",
		"nz_pain_dismem_lleg_1",
		"nz_pain_dismem_lleg_2",
		"nz_pain_dismem_lleg_3",
		"nz_pain_dismem_lleg_4",
		"nz_pain_dismem_rarm_1",
		"nz_pain_dismem_rarm_2",
		"nz_pain_dismem_rarm_3",
		"nz_pain_dismem_rleg_1",
		"nz_pain_dismem_rleg_2",
		"nz_pain_dismem_rleg_3",
		"nz_pain_dismem_rleg_4",
		"nz_pain_head_v1",
		"nz_pain_head_v2",
		"nz_pain_highdam_dismem_lleg_1",
		"nz_pain_highdam_dismem_lleg_2",
		"nz_pain_highdam_dismem_lleg_3",
		"nz_pain_highdam_dismem_rleg_1",
		"nz_pain_highdam_dismem_rleg_2",
		"nz_pain_highdam_dismem_rleg_3",
		"nz_pain_left_v1",
		"nz_pain_left_v2",
		"nz_pain_right_v1",
		"nz_pain_right_v2",
		"nz_pain_rleg",
	};

	/// <summary>
	/// The entrances cleared for actual play.
	///
	/// ⚠️ ONE CLIP ON PURPOSE, NOT AN OVERSIGHT. Of the twenty below, this is the
	/// only one confirmed working three independent ways rather than one:
	///
	///   1. nz_spawn_verify — starts 42.9 below the floor, lands +2.8, never rises
	///      more than 7.6 above it mid-clip.
	///   2. nz_spawn_trace — the POSE articulates (pelvis 34→26→32, feet plant
	///      forward then pull under the body), so it is a real climb-out and not
	///      the object being dragged up a hole.
	///   3. The source SMD's own root travel is +64.57 z; the game reports +64.6,
	///      so nothing was lost in conversion.
	///
	/// The other nineteen still load and still work with nz_spawn_clip — eight of
	/// them end 11 to 16 units high, which is probably invisible but has never
	/// been checked by eye. Widen this list as each one earns it.
	/// </summary>
	public static readonly List<string> SpawnVerified = new()
	{
		"nz_ent_ground_02",
	};

	public static readonly List<string> Spawn = new()
	{
		"nz_base_riser_elevator_from_ceiling",
		"nz_base_riser_elevator_from_floor",
		"nz_base_zombie_cellbreaker_spawn",
		"nz_ent_ceiling_112",
		"nz_ent_ceiling_144",
		"nz_ent_ground_01",
		"nz_ent_ground_02",
		"nz_ent_ground_wall_01",
		"nz_ent_ground_wall_03",
		"nz_spawn_ground_ad_v2",
		"nz_spawn_ground_climbout_fast",
		"nz_spawn_ground_jumpout",
		"nz_spawn_ground_v1",
		"nz_spawn_ground_v1_deathinside",
		"nz_spawn_ground_v1_deathoutside",
		"nz_spawn_ground_v1_run",
		"nz_spawn_ground_v2",
		"nz_spawn_ground_v2_altb",
		"nz_spawn_ground_v2_deathinside",
		"nz_spawn_ground_v2_deathoutside",
	};
}