Zombies/ZombieVariant.cs

Asset class defining zombie variant data for the game, including appearance, animation speed tiers, tuning (health, speed, scale, damage), body hitbox overrides, sounds, and special-case gameplay multipliers. It provides helpers to pick cue strings and to select an animation tier by speed, plus small supporting types and an enum.

File Access
using Sandbox;
using System.Collections.Generic;

namespace NZombies;

/// <summary>
/// ZOMBIE/VARIANT — everything that distinguishes one walker from another.
///
/// The research is unambiguous that this belongs in data, not code: of 132
/// walker files in the GMod gamemode, 40 define ZERO functions and the average
/// is 1.9 — and 136 of the 257 total functions are two boilerplate functions
/// pasted verbatim 68 times. The base walker itself is 1,282 lines defining
/// three functions, one of which is dead.
///
/// So ~92% of walker variants are pure data. Get this asset right and the other
/// 131 walkers cost almost nothing.
///
/// Do NOT extend this to bosses/specials — those average 9-12 real functions
/// each with no empty files. They're genuine per-entity mechanics.
///
/// See Docs/NZOMBIES_REFERENCE.md §9.6.
/// </summary>
// TODO: GameResource is obsolete — s&box wants [AssetType] now. Left as-is
// deliberately: it's a warning, not an error, and I don't know AssetType's
// parameter shape well enough to change it without risking a build break.
// Swap it when we can verify the signature.
#pragma warning disable CS0618
[GameResource( "Zombie Variant", "zvar", "A walker variant — models, animations and tuning." )]
#pragma warning restore CS0618
public class ZombieVariant : GameResource
{
	// ── VARIANT/APPEARANCE ───────────────────────────────────────────────────
	// In the original a variant IS its model list — UpdateModel() picks a random
	// {Model, Skin} and randomises every bodygroup (moo:175-184).
	[Property] public List<ZombieModelEntry> Models { get; set; } = new();

	// ── VARIANT/ANIMATION TIERS ──────────────────────────────────────────────
	// The cleverest structure in the original (walker:301, resolved moo:6359).
	// Animation set is chosen by the zombie's SPEED, not by round directly:
	//
	//     walk 0 · run 36 · sprint 71 · supersprint 155
	//
	// Chained with the round speed curve (ZombieStats.SpeedForRound) plus per-zombie jitter
	// rand(0,35) — and that jitter width exactly matches the first tier gap of
	// 36, so tiers smear across rounds instead of the whole horde switching
	// animation on one round boundary.
	//
	//     R1 walk · R15 run · R29 sprint · R60 supersprint   (the whole horde; 2026-10-05)
	//     R1 walk · R10 run · R19 sprint · R40 supersprint   (the original's round*4-4, before)
	//
	// Selection is a ONE-SHOT FREEZE: each list collapses to a single random
	// sequence cached for the zombie's lifetime, not re-rolled per frame.
	[Property] public List<ZombieSpeedTier> SpeedTiers { get; set; } = new();

	// ── VARIANT/TUNING ───────────────────────────────────────────────────────
	// Multipliers over the round curves in ZombieStats.
	/// <summary>
	/// Multiplies the round's normal-zombie health. A boss is expressed here and nowhere else.
	///
	/// ⚠️ RANGE WIDENED TO 50 — it was 0.1-10, and Brutus is 15. A `Range` attribute clamps the
	/// inspector slider, so a boss authored past the old ceiling would have been silently
	/// unadjustable in the editor while the `.zvar` said otherwise.
	/// ⚠️ AND TO 500 (2026-10-07): Shrek is 150, the Sawrunner 100, the Panzermorder 323 — the same trap again.
	/// </summary>
	[Property, Range( 0.1f, 500f )] public float HealthMultiplier { get; set; } = 1f;

[Property, Range( 0.1f, 5f )] public float SpeedMultiplier { get; set; } = 1f;

	/// <summary>
	/// An ABSOLUTE ground speed in units/sec, ignoring the round curve entirely. 0 = use the curve.
	///
	/// ⛔ A BOSS MUST NOT SPEED UP WITH THE ROUND, WHICH `SpeedMultiplier` CANNOT EXPRESS. That field
	/// scales the walker clip's own ground speed, which is chosen from the round's speed rating - so
	/// the same multiplier means a different speed every few rounds. A boss is a fixed quantity: he
	/// moves the same on round 11 and round 71, and only his own state changes it.
	///
	/// ⚠️ IT BYPASSES `MinMoveSpeed` TOO, deliberately. That floor exists so an ordinary zombie is
	/// never slower than its walk cycle looks; a boss is allowed to be slower than a walker, which is
	/// most of what makes him feel heavy.
	///
	/// ⛔ BUT IT CANNOT GO BELOW ROUGHLY 42. `ZombieAI.AgentSpeed` clamps any non-zero speed up to
	/// `MinAgentSpeed`, because the navmesh agent does not move AT ALL below about 35 u/s - measured,
	/// not assumed. Upstream Brutus walks at 36, which is inside that dead band, so his authored
	/// number cannot be used here. See `BrutusHelmet.HelmetOnSpeed`.
	/// </summary>
	[Property] public float FixedSpeed { get; set; } = 0f;

	/// <summary>
	/// Uniform scale for the rendered body. 1 = the model at its authored size.
	///
	/// ⛔ UPSTREAM HAS NO EQUIVALENT AND THIS IS A DELIBERATE DEPARTURE. `nz_zombie_boss_brutus.lua`
	/// never calls `SetModelScale` — Brutus is simply a taller model, 80 units against a walker's 72,
	/// which is 11% and does not read as "boss" next to a 72-unit player. Asked for, not assumed.
	///
	/// ⚠️ IT SCALES `HitRadius`, `BodyHeight` AND `AttackRange` WITH THE BODY, because all three
	/// describe the visible creature. A boss twice the size with a walker's hitbox is one you shoot
	/// past, and one with a walker's reach swings from a body-length away.
	///
	/// ⛔ IT DOES **NOT** SCALE `BodyRadius`, AND THAT EXCLUSION IS THE WHOLE REASON THIS IS SAFE.
	/// `BodyRadius` is the NAV AGENT's radius, not a body dimension — a Brutus at 22 could not path at
	/// all and stood still while the speed chain read perfectly correct. Scaling it would walk
	/// straight back into that bug, silently, the moment someone raised this number.
	/// </summary>
	[Property, Range( 0.25f, 4f )] public float ModelScale { get; set; } = 1f;
	[Property, Range( 0.1f, 5f )] public float DamageMultiplier { get; set; } = 1f;

	/// <summary>Per-zombie speed jitter. 35 is the original's value and is
	/// deliberately sized to the first tier gap — see SpeedTiers.</summary>
	[Property] public int SpeedJitter { get; set; } = 35;

	// ── VARIANT/DEATH ────────────────────────────────────────────────────────
	[Property] public List<string> DeathSequences { get; set; } = new();
	[Property] public bool CanGib { get; set; } = true;
	[Property] public bool CanBecomeCrawler { get; set; } = true;

	// ── VARIANT/BEHAVIOUR ────────────────────────────────────────────────────
	// Only a handful of walkers need real code — 6 of 132 have a leap attack,
	// and ~8 override a small hook. Everything else is covered by the data
	// above. Keep this enum tiny; if it grows past a few entries, that's a
	// signal the thing being added is really a special, not a walker.
	[Property] public ZombieBehaviour Behaviour { get; set; } = ZombieBehaviour.Standard;

	/// <summary>Skip barricades entirely and head straight for a player. The
	/// original's dogs spawn INSIDE the play space from their own effect rather
	/// than walking in from outside, so they never meet a window (dog logic in
	/// nz_zombiebase_moo). A hound stopping to tear planks reads as a reskinned
	/// walker more than anything else does.</summary>
	[Property] public bool IgnoresBarricades { get; set; } = false;

	/// <summary>
	/// Is this a BOSS. Death Perception's M1 and M4 key off it. Off.
	///
	/// ⛔ AN EXPLICIT FLAG, BECAUSE `SpecialEnemies.IsSpecial` TOLD ME TO MAKE ONE. That method
	/// derives specialness from the asset FILENAME and its own note says: "Drop rates are the only
	/// caller today. If anything gameplay-critical ever depends on this, give ZombieVariant an
	/// explicit flag instead - deriving specialness from an asset filename is fine for a loot roll
	/// and too fragile for a rule." Death Perception M1 is a x3 damage multiplier. That is a rule.
	///
	/// ⚠️ BOSS AND SPECIAL ARE NOT THE SAME THING. A hellhound is a special and not a boss; it
	/// should keep its loot rates and grant no Bounty Hunter points. Anything that wants "unusual
	/// enemy" for a DROP still asks `SpecialEnemies.IsSpecial`; anything that wants "boss" for a
	/// RULE asks this.
	/// </summary>
	[Property] public bool IsBoss { get; set; } = false;

	// ── VARIANT/BODY ─────────────────────────────────────────────────────────
	// ⛔ WITHOUT THESE, A QUADRUPED GETS A STANDING HUMAN'S CAPSULE — 72 tall and
	// 9 wide around a body that is 23 x 71 x 60. Shots pass through the visible
	// dog and hit nothing, which is exactly what "cannot be hit" looked like.
	// Null means "leave the walker default", so no existing variant changes.

	/// <summary>Yaw correction, degrees, when the art is not authored facing +X.
	///
	/// ⛔ MEASURE THIS, NEVER EYEBALL IT. The hound's run clip translates its root
	/// by dx=0.00, dy=-735.87 — pure -Y — so the model faces -Y and needs +90 to
	/// line up with forward. Its bounds say the same thing: 23 x 71 x 60, long
	/// axis on Y. Both come free from files already on disk, and either one beats
	/// nudging the number until a screenshot looks right.</summary>
	[Property] public float ModelYawOffset { get; set; } = 0f;

	/// <summary>
	/// The same correction about the other two axes, for a rig that is not merely turned.
	/// </summary>
	///
	/// ⚠️ ALMOST ALWAYS ZERO, AND THAT IS NOT AN OVERSIGHT. A model authored facing the wrong way
	/// needs a yaw and nothing else; pitch and roll exist for a rig exported lying down or rolled
	/// onto its side, which is a conversion fault worth fixing in the DMX rather than here. They are
	/// on the variant so a boss can be dialled in live with `nz_model_turn` and the answer written
	/// down — not so that every model carries three numbers where one would do.
	[Property] public float ModelPitchOffset { get; set; } = 0f;

	/// <summary>See <see cref="ModelPitchOffset"/>.</summary>
	[Property] public float ModelRollOffset { get; set; } = 0f;

	/// <summary>Floor under the round's speed roll, so a variant can be pinned to
	/// a tier regardless of round. Null leaves the round curve alone.
	///
	/// Hellhounds ALWAYS run in this version of the gamemode — they do not have a
	/// walk to scale up from, so letting round 1 pick the walk tier gives you a
	/// plodding dog rather than an early-round one.</summary>
	[Property] public int? MinSpeedRating { get; set; } = null;

	/// <summary>Capsule height. Walker default 72.</summary>
	[Property] public float? BodyHeight { get; set; } = null;

	/// <summary>Capsule radius. Walker default 9.</summary>
	[Property] public float? BodyRadius { get; set; } = null;

	/// <summary>Melee reach. Walker default 40.</summary>
	[Property] public float? AttackRange { get; set; } = null;

	/// <summary>Sphere used for bullet hits when a model has no hitboxes.
	/// Walker default 16.</summary>
	[Property] public float? HitRadius { get; set; } = null;

	// ── VARIANT/SOUND ────────────────────────────────────────────────────────
	// ⛔ THE SEVEN CUES IN ZombieAI WERE HARDCODED TO `nz.zombie.*`. A variant
	// could change the body, the clips, the speed and the health and STILL sound
	// exactly like a walker — user, on the first hellhound: "its using normal
	// zombie AI and sounds and hitbox".
	// Empty means "use the walker cue", so no existing variant changes.

	/// <summary>Ambient voice while moving.</summary>
	[Property] public string IdleSound { get; set; } = "";

	/// <summary>Ambient voice while at the fastest tier. Falls back to
	/// <see cref="IdleSound"/> when blank.</summary>
	[Property] public string SprintSound { get; set; } = "";

	/// <summary>Ambient voice within <see cref="CloseSoundRange"/> of its target.
	/// Blank disables the close/far split entirely.</summary>
	[Property] public string CloseSound { get; set; } = "";

	/// <summary>Range at which <see cref="CloseSound"/> takes over.</summary>
	[Property] public float CloseSoundRange { get; set; } = 400f;

	/// <summary>The swing/lunge, played whether or not it connects.</summary>
	[Property] public string AttackSound { get; set; } = "";

	/// <summary>The blow LANDING on a player.</summary>
	[Property] public string HitSound { get; set; } = "";

	[Property] public string DeathSound { get; set; } = "";
	[Property] public string SpawnSound { get; set; } = "";

	/// <summary>Footfall at walking tiers.</summary>
	[Property] public string StepSound { get; set; } = "";

	/// <summary>Footfall at running tiers. Falls back to
	/// <see cref="StepSound"/> when blank.</summary>
	[Property] public string StepRunSound { get; set; } = "";

	/// <summary>
	/// Trauma added to the camera each time this zombie's foot lands. 0 = none, which is every
	/// ordinary zombie.
	///
	/// ⛔ OPT-IN, AND IT HAS TO BE. Footsteps fire for the whole horde — twenty walkers stepping
	/// would be a permanent tremor, and a shake that never stops is just a broken camera. Weight is
	/// the point: only something heavy enough to feel should register.
	///
	/// ⚠️ IT FALLS OFF WITH THE SQUARE OF DISTANCE over <see cref="StepShakeRange"/>, so this is
	/// the value at zero distance and nothing like the average.
	/// </summary>
	/// <summary>
	/// Seconds between idle/sprint voice cues for this variant. 0 = the shared default.
	///
	/// ⛔ THE SHARED DEFAULT IS SPEED-BASED AND A BOSS BREAKS IT. It picks 1.2-2.6s above 180 u/s and
	/// 2.5-6s below, which suits a walker whose voice is a one-second groan. Brutus is above that
	/// threshold at BOTH his speeds (110 and 220), so he was taunting every ~2s in full sentences.
	///
	/// ⚠️ UPSTREAM HAS NO SPEED SPLIT AT ALL — `nz_zombiebase_moo.lua:861` is a flat
	/// `SoundDelayMin 5` / `SoundDelayMax 6` for everything. Ours is a departure that works for the
	/// horde and not for a boss, so this is the per-variant escape hatch rather than a change to the
	/// walker cadence, which is tuned and play-tested.
	/// </summary>
	[Property] public float VoiceIntervalMin { get; set; } = 0f;

	/// <summary>Upper end of the gap. See <see cref="VoiceIntervalMin"/>.</summary>
	[Property] public float VoiceIntervalMax { get; set; } = 0f;

	[Property] public float StepShake { get; set; } = 0f;

	/// <summary>How far a step can be felt. Ignored when <see cref="StepShake"/> is 0.</summary>
	[Property] public float StepShakeRange { get; set; } = 600f;

	/// <summary>
	/// The Prisma build part the FIRST of these killed drops. 0 = none, which is every variant
	/// that has not been told otherwise.
	/// </summary>
	///
	/// ⛔ THE FIRST ONE ONLY, AND "FIRST" MEANS THE PART IS NOT ALREADY OUT THERE. The check is
	/// `BuildPartManager.AlreadyAwarded`, which covers both a part lying uncollected on the floor
	/// and one the team has already taken — a counter of napalms killed would drop a second piece
	/// to a squad that walked past the first.
	///
	/// ⚠️ ON THE VARIANT RATHER THAN HARDCODED IN THE DEATH PATH, so which enemy owes which piece
	/// is a line in a `.zvar` and not a recompile. `napalm.zvar` carries 2.
	///
	/// ⚠️ IT DOES NOT MAKE THE PART OBTAINABLE BY ITSELF. If this variant never spawns on a map,
	/// the piece is unreachable there — `nz_buildpart_list` says which of the three have a source
	/// and which have none.
	[Property] public int DropsBuildPart { get; set; } = 0;

	/// <summary>
	/// How fast this variant travels as a multiple of a NORMAL zombie on the same round.
	/// 1 = the horde's pace. Pests and shriekers run 1.5.
	/// </summary>
	///
	/// ⛔ THIS IS NOT `SpeedMultiplier`, AND USING THAT ONE FOR THIS WOULD MAKE THEM SKATE.
	/// `SpeedMultiplier` scales `_clipGroundSpeed` — it says "this variant's animation travels
	/// further per cycle than the table thinks", which is a CORRECTION to a measurement. The
	/// playback rate is `velocity / _clipGroundSpeed`, so scaling both sides together leaves that
	/// ratio at 1.0: the zombie crosses the room faster with its legs cycling at the authored pace.
	/// This one scales the MOVE speed alone, so the legs speed up to match — exactly what
	/// `TierSpeedScale` does, and it is applied in the same place for the same reason.
	///
	/// ⛔ AND IT IS APPLIED *AFTER* `MinMoveSpeed`, WHICH IS THE WHOLE DIFFERENCE BETWEEN "1.5x" AND
	/// "1.5x except when it matters". That floor decides the speed for most of the early game — 14
	/// of the walk tier's 15 clips are floored up to it — so a multiplier applied before it is
	/// swallowed whole, and a pest on round 3 would move at exactly a walker's pace. After the
	/// floor, "1.5x a normal zombie on this round" is true on every round, including the ones where
	/// the floor is what a normal zombie is doing.
	///
	/// ⚠️ IT DOES NOT TOUCH A BOSS. `FixedSpeed`/`SpeedOverride` short-circuit the whole chain by
	/// design — a Brutus moves at his authored pace on round 11 and on round 71 — so this sits in
	/// the non-fixed branch with the tier scale.
	///
	/// ⚠️ THE ANIMATION CEILING IS `MaxAnimRate` (2.5) AND 1.5 ONLY JUST FITS UNDER IT. The top
	/// tier already runs at 1.6x, so 1.6 x 1.5 = 2.4 — past the clamp the legs stop keeping up and
	/// the thing skates after all. Anything above about 1.55 on a SuperSprint-tier variant needs
	/// `MaxAnimRate` raised with it.
	[Property] public float MoveSpeedScale { get; set; } = 1f;

	/// <summary>
	/// The share of this variant's MAX health a NUKE takes off. 1 = killed outright, which is
	/// every variant that has not been told otherwise. 0 = the nuke does nothing to it at all.
	/// </summary>
	///
	/// ⛔ A SHARE OF MAX HEALTH, NOT A DAMAGE FIGURE, because what it has to survive scales with
	/// the round and a flat number does not. A napalm at 0.3 is four nukes on round 11 and four
	/// nukes on round 51; the same rule written as "900 damage" is an instant kill early and a
	/// scratch late, and nothing in the file would say which round it stopped making sense on.
	///
	/// ⛔ AND IT IS DATA BECAUSE THE SET CUTS ACROSS EVERY FLAG WE ALREADY HAVE. Asked for as
	/// *"nukes do not insta kill napalms, shriekers, brutus and oberon / instead they deal 30% of
	/// max hp, and do nothing to oberon"* — and `IsBoss` is false on the shrieker while
	/// `SpecialEnemies.IsSpecial` is true of the pest and the hellhound, both of which still die.
	/// Either test would have been right about three enemies and wrong about three others; the
	/// list of names it would have taken instead belongs in the `.zvar`s it describes.
	///
	/// ⚠️ IT IS NOT A DAMAGE RESISTANCE. Nothing else reads it — bullets, fire and the shrieker's
	/// own death are unchanged — so a value here cannot make anything tougher by accident.
	[Property, Range( 0f, 1f )] public float NukeDamageFraction { get; set; } = 1f;

	/// <summary>
	/// What INSTA-KILL does to this variant. Empty = killed by any hit, which is every variant that
	/// has not been told otherwise. A number = it is NOT killed, and every hit lands that many times
	/// harder while the powerup runs.
	/// </summary>
	///
	/// ⛔ ASKED FOR AS *"insta kill should not insta kill napalm, brutus, shriekers and oberon / with
	/// insta kill the player should deal 3x damage to them"* — the same four the nuke now spares,
	/// and for the same reason the nuke's rule is data rather than a list of names:
	/// `NukeDamageFraction` records why no flag the project already had could express the set.
	///
	/// ⚠️ NULLABLE, AND THE NULL IS THE MEANING. "Killed outright" is not a multiplier — there is no
	/// number that means it — so the default is the absence of one rather than a sentinel like 0 or
	/// -1 that a later edit could mistake for a real value. The variant already speaks this way for
	/// `BodyHeight` and `AttackRange`: empty means "the ordinary rule".
	///
	/// ⚠️ IT MULTIPLIES THE HIT, NOT THE HEALTH BAR, so everything else still applies on top: a
	/// helmeted Brutus still takes his helmet's cut, and a headshot still pays its 2.5. Insta-Kill
	/// makes the player three times as dangerous to him; it does not change how he is fought.
	[Property] public float? InstaKillMultiplier { get; set; } = null;

	/// <summary>
	/// What the Prisma's RESONANCE does to this variant. Empty = the ordinary fuse, 9% of max health
	/// every quarter second. A number = that share of the WEAPON'S damage instead, once a second, for
	/// as long as the fuse burns.
	/// </summary>
	///
	/// ⛔ ASKED FOR AS *"the wonder weapon DoT should not affect napalms, shriekers, brutus and oberon
	/// the same way, instead it deals 10% of the weapon's damage once per second"* — the same four the
	/// nuke and Insta-Kill spare, and data for the reason `NukeDamageFraction` gives.
	///
	/// ⚠️ A SHARE OF THE WEAPON, NOT OF THE VICTIM, and that is the whole change. The ordinary fuse is
	/// proportional, so it killed a Brutus in the same three seconds it kills a walker. This scales
	/// with the gun instead — 400 a second from an unpacked Prisma, 1,080 from MK1 — and leaves his
	/// health to decide how long he lasts.
	///
	/// ⚠️ ONLY THE TICK CHANGES. The fuse still lights, still glows and still bursts when he dies, so
	/// a special caught in a crowd passes the chain on like anything else. See `PrismaChain.Infect`.
	[Property] public float? ResonanceShare { get; set; } = null;

	/// <summary>Pick a variant cue, or the walker's when the variant is silent on
	/// it. Static and null-tolerant so ZombieAI can call it with no variant
	/// assigned at all — that is the common case, and it must stay free.</summary>
	public static string Cue( string variantCue, string fallback )
		=> string.IsNullOrWhiteSpace( variantCue ) ? fallback : variantCue;

	/// <summary>Resolve the animation tier for a given speed. Highest threshold
	/// that the speed meets or exceeds wins.</summary>
	public ZombieSpeedTier TierForSpeed( float speed )
	{
		ZombieSpeedTier best = null;
		foreach ( var tier in SpeedTiers )
		{
			if ( speed < tier.MinSpeed ) continue;
			if ( best is null || tier.MinSpeed > best.MinSpeed ) best = tier;
		}
		return best;
	}
}

public enum ZombieBehaviour
{
	Standard,
	LeapAttack,     // the 6 exo/greenflu/headcrab walkers
}

public class ZombieModelEntry
{
	[Property] public Model Model { get; set; }
	[Property] public int Skin { get; set; } = 0;
	/// <summary>Original randomises every bodygroup on spawn (moo:181-183).</summary>
	[Property] public bool RandomiseBodygroups { get; set; } = true;
}

/// <summary>
/// VARIANT/TIER — one animation set, selected by speed.
/// Thresholds from walker:302/492/685/854.
/// </summary>
public class ZombieSpeedTier
{
	[Property] public string Name { get; set; } = "walk";

	/// <summary>Speed at or above which this tier applies. 0 / 36 / 71 / 155.</summary>
	[Property] public float MinSpeed { get; set; } = 0f;

	/// <summary>Ground speed the movement clips were AUTHORED at, u/s. 0 means
	/// "look it up in the walker table", which is right for walkers and useless
	/// for anything else.
	///
	/// ⛔ THE WALKER TABLE IS KEYED BY CLIP NAME AND KNOWS NOTHING ABOUT
	/// `a_nz_dog_run`. It returned 0, so MoveSpeed fell to the MinMoveSpeed floor
	/// (55) and every hound plodded — while PlaybackRate, being speed/authored,
	/// collapsed to its 0.05 clamp and the legs barely moved. One missing number
	/// produced both "it walks slowly" and "the animation is frozen".
	///
	/// Measure it, don't guess: the root bone's travel over the clip in the
	/// decompiled SMD. For this dog set that is run 367.9, trot 138.3, walk 53.8.</summary>
	[Property] public float GroundSpeed { get; set; } = 0f;

	[Property] public List<string> MovementSequences { get; set; } = new();
	[Property] public List<string> AttackSequences { get; set; } = new();
	[Property] public List<string> SpawnSequences { get; set; } = new();
	[Property] public List<string> PassiveSounds { get; set; } = new();
}