Zombies/DirectorBoss.cs

A game component implementing the Director boss NPC for a Zombies mode. It handles spawning visuals, rage mechanics, attacks (slam, roar), buffing nearby zombies (speed or electric), damage scaling, state transitions, networking of effects/clips, and console commands for debugging and tuning.

NetworkingFile Access
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;

namespace NZombies;

/// <summary>
/// The Director — George Romero, Call of the Dead's boss, ported 2026-10-06 from GMod nZombies' `nz_zombie_boss_director`
/// (GhostlyMoo's port). The user picked him with Avogadro, the Astronaut and the Panzer Soldat.
///
/// | | everything |
/// |---|---|
/// | **damage** | **×0.095** (upstream's `OnTakeDamage`) |
///
/// CALM, he walks (30.8 u/s), swings his stage lamp, and taunts. HIT HIM BY A PLAYER and he RAGES:
///   • `nz_boss_enrage_start_coast` (5.9 s) — his body turns zombified, a roar that dazes everyone within 575 for 1 s, and at
///     frame 116 a slam: a heavy hit to anyone within 105, a 3 s daze within 175, and the zombies round him charged.
///   • then he RUNS YOU DOWN at FIVE TIMES his sprint (~985 u/s against your 340; the user, 2026-10-06: *"yes its suposed to be
///     a death sentence, this is not an enemy you should try to kill but avoid instead"*), and calms down
///     (`nz_boss_sprint_to_walk`) the moment he DOWNS SOMEONE (a player going down within <see cref="DownReach"/> of him), or
///     after 30 s. Five seconds later he can rage again. A hit before then makes him stagger (`nz_boss_nuke_react`, once
///     every 7 s) — upstream's only flinch. The 5.9 s it takes him to rage is the time you have to get away.
///   ⚠️ Upstream also ends his rage in water, the Call of the Dead way to beat him; this project's maps have no water to stand in.
/// HE BUFFS THE HORDE with a scream (`nz_boss_enrage_start_scream`), when at least two zombies are within 200:
///   • 3 times in 4 the SPEED buff (every 10 s): the zombies within 175 sprint at the top speed tier;
///   • else the ELECTRIC buff (every 20 s): the zombies within 175 get three times a zombie's health this round (it heals them
///     too, as upstream's does) and electricity crawling over them.
/// HIS LAMP is blue, and orange once he is under half health. HE ARRIVES out of a beam and slams the ground; HE DIES and at
/// frame 131 is gone in a flash (upstream's `boss_remove`) — and his killer gets a free perk (upstream drops a perk bottle;
/// this project has no bottle power-up, so the perk goes straight to whoever landed the kill, through the slot cap).
///
/// ⚠️ HOST AUTHORITY, EVERY MACHINE DRAWS: `NZNet.DirectorBody` / `DirectorCharged` / `DirectorRoar`, and `DirectorFx` /
/// `DirectorLamp` on each machine. His special clips are relayed (`PlayShared`).
/// ⛔ NO ATTACK OF HIS DOWNS ANYONE FROM FULL HEALTH (<see cref="HitCap"/>), Oberon's rule.
/// </summary>
public sealed class DirectorBoss : Component
{
	// ══ tuning ═══════════════════════════════════════════════════════════════
	//
	// ⛔ NULLABLE-BACKED, INSTRUCTIONS.md §1.

	static float? _damageScale, _rageSeconds, _rageCooldown, _reactGap, _sprintSpeed, _walkSpeed, _hitCap;
	/// <summary>What every hit on him does: ×0.095, upstream's `ScaleDamage(0.095)`.</summary>
	public static float DamageScale { get => _damageScale ?? 0.095f; set => _damageScale = value; }
	/// <summary>How long a rage lasts when he downs nobody: 30 s (the user's, 2026-10-06; upstream's 45).</summary>
	public static float RageSeconds { get => _rageSeconds ?? 30f; set => _rageSeconds = value; }
	/// <summary>Seconds after calming before he can rage again: 5, upstream's `NextEnrageTime`.</summary>
	public static float RageCooldown { get => _rageCooldown ?? 5f; set => _rageCooldown = value; }
	/// <summary>Seconds between staggers: 7, upstream's.</summary>
	public static float ReactGap { get => _reactGap ?? 7f; set => _reactGap = value; }
	/// <summary>His rage sprint: 197.1 u/s, `nz_boss_sprint_01`'s own. Calm he walks at 30.8 (`FixedSpeed`).</summary>
	public static float SprintSpeed { get => _sprintSpeed ?? 197.1f; set => _sprintSpeed = value; }
	public static float WalkSpeed { get => _walkSpeed ?? 30.8f; set => _walkSpeed = value; }

	static float? _rageSpeedScale, _rageAnimRate, _rageAccel, _downReach;
	/// <summary>His rage against his sprint: ×5 — ~985 u/s, the `rage` tier (`nz_boss_sprint_02`, 240.5 u/s of its own).</summary>
	public static float RageSpeedScale { get => _rageSpeedScale ?? 5f; set => _rageSpeedScale = value; }
	/// <summary>How fast his legs may cycle while raging, over the match's 2.5 (`ZombieAI.MaxAnimRateOverride`): 4.5.</summary>
	public static float RageAnimRate { get => _rageAnimRate ?? 4.5f; set => _rageAnimRate = value; }
	/// <summary>His acceleration while raging, units a second a second (a zombie's is 900): 5000, so the corners do not fling him.</summary>
	public static float RageAccel { get => _rageAccel ?? 5000f; set => _rageAccel = value; }
	/// <summary>A player going down this near him while he rages is his doing, and calms him: 200 at size 1 (his swing is 105).</summary>
	public static float DownReach { get => _downReach ?? 200f; set => _downReach = value; }

	/// <summary>His rage speed now.</summary>
	public static float RageSpeed => SprintSpeed * MathF.Max( 0.1f, RageSpeedScale );
	/// <summary>The most one hit of his takes, as a share of the match's base health. Two-thirds, Oberon's.</summary>
	public static float HitCap { get => _hitCap ?? (2f / 3f); set => _hitCap = value; }

	static float? _roarRadius, _roarDaze, _slamRadius, _slamWeight, _slamDazeRadius, _slamDaze;
	/// <summary>His roar dazes everyone this near: 575, for 1 s (upstream's `NZSonicBlind(1)`).</summary>
	public static float RoarRadius { get => _roarRadius ?? 575f; set => _roarRadius = value; }
	public static float RoarDaze { get => _roarDaze ?? 1f; set => _roarDaze = value; }
	/// <summary>The rage slam: a hit within 105 (upstream's `DamageRange`), 150 against his 90 swing.</summary>
	public static float SlamRadius { get => _slamRadius ?? 105f; set => _slamRadius = value; }
	public static float SlamWeight { get => _slamWeight ?? (150f / 90f); set => _slamWeight = value; }
	/// <summary>…and a 3 s daze within 175, upstream's `NZSonicBlind(3)`.</summary>
	public static float SlamDazeRadius { get => _slamDazeRadius ?? 175f; set => _slamDazeRadius = value; }
	public static float SlamDaze { get => _slamDaze ?? 3f; set => _slamDaze = value; }

	static float? _speedBuffGap, _elecBuffGap, _buffQualify, _buffReach, _elecHealth;
	/// <summary>Seconds between speed buffs: 10; electric: 20 — upstream's.</summary>
	public static float SpeedBuffGap { get => _speedBuffGap ?? 10f; set => _speedBuffGap = value; }
	public static float ElecBuffGap { get => _elecBuffGap ?? 20f; set => _elecBuffGap = value; }
	/// <summary>Zombies this near count toward a buff (two needed): 200; the buff reaches 175 — upstream's.</summary>
	public static float BuffQualify { get => _buffQualify ?? 200f; set => _buffQualify = value; }
	public static float BuffReach { get => _buffReach ?? 175f; set => _buffReach = value; }
	/// <summary>A charged zombie's health, in zombies of this round: 3, upstream's `GetZombieHealth() * 3`.</summary>
	public static float ElecHealth { get => _elecHealth ?? 3f; set => _elecHealth = value; }

	public static float MaxHit => MathF.Max( 1f, Difficulty.MaxHealth * Math.Clamp( HitCap, 0.1f, 0.95f ) );

	// clip lengths, frames at 30
	const float RageClip = 176f / 30f;
	const float CalmClip = 53f / 30f;
	const float ReactClip = 115f / 30f;
	const float ScreamClip = 51f / 30f;
	const float RemoveAt = 131f / 30f;
	/// <summary>Hidden while the beam comes down, shown just before his entrance clip's slam (frame 39).</summary>
	const float RevealAt = 1.0f;

	// ══ state ════════════════════════════════════════════════════════════════

	ZombieAI _ai;
	Health _hp;
	SkinnedModelRenderer _body;
	DirectorLamp _lamp;

	bool _raging, _hitPending, _shielded, _deadHandled, _revealed, _removed, _rewarded;

	/// <summary>Who was down last frame (`IsDown` on the host's own player, `DownedNet` on a client's) — a NEW down near him calms him.</summary>
	readonly Dictionary<NZPlayer, bool> _wasDown = new();
	/// <summary>He downed someone this rage; he calms the moment his current move lets him.</summary>
	bool _gotOne;
	float _rageUntil, _nextRage, _nextReact, _nextSpeedBuff, _nextElecBuff, _nextThink, _born, _deathAt, _nextBehind;
	bool _start0, _start2;

	enum Beat { Roar, Fall, Slam, React, Boom, Weak, Yell, SpeedYell, ElecYell, EntranceSlam }
	readonly List<(float At, Beat What)> _pending = new();

	public bool Raging => _raging;

	Vector3 Facing => _ai.IsValid() ? (WorldRotation * _ai.ModelTurn.Inverse).Forward.WithZ( 0 ).Normal : WorldRotation.Forward;
	float Size => MathF.Max( 0.1f, WorldScale.x );
	Vector3 Voice => WorldPosition + Vector3.Up * 66f * Size;

	protected override void OnStart()
	{
		_ai = Components.Get<ZombieAI>( FindMode.EverythingInSelfAndAncestors );
		_hp = Components.Get<Health>( FindMode.EverythingInSelfAndAncestors );
		_body = Components.Get<SkinnedModelRenderer>( FindMode.EverythingInSelfAndDescendants );
		_lamp = Components.GetOrCreate<DirectorLamp>();

		_born = Time.Now;

		// ⚠️ EVERY MACHINE: hidden while the summoning beam comes down (upstream's `NoDraw` for 1.5 s), each machine its own copy
		MargwaFx.SetHidden( GameObject, true );
		DirectorFx.Beam( WorldPosition );

		if ( NZGame.IsClient || !_ai.IsValid() ) return;

		_nextRage = _born + 7f;          // upstream's `NextEnrageTime = CurTime() + 7`
		_nextReact = _born + 5f;
		_nextSpeedBuff = _born + 2f;
		_nextElecBuff = _born + 7f;
		_ai.MaxHitDamage = MaxHit;

		// ⛔ NO CLIMB CLIP OF THE WALKER'S ON HIS MODEL (Oberon's note): his jump-slam stands in, in place
		_ai.ClimbClipOverride = "nz_boss_slamjump";

		Log.Info( $"[nz-director] spawned — {_hp?.Max ?? 0f:0} hp at x{DamageScale:0.###} · rage {RageSeconds:0}s at {SprintSpeed:0} u/s"
			+ $" · hitboxes {_ai.HitboxCount}" );
	}

	protected override void OnUpdate()
	{
		if ( !_ai.IsValid() ) return;

		TickReveal();

		if ( _ai.State == ZombieState.Dead )
		{
			OnDeath();
			TickRemove();
			return;
		}

		if ( _ai.IsPuppet ) return;

		_ai.MaxHitDamage = MaxHit;
		TickShield();
		TickPending();
		TickHit();
		TickDowns();
		TickRage();
		TickBuffs();
		TickVoice();
	}

	// ══ every machine ════════════════════════════════════════════════════════

	/// <summary>Out of the beam, on every machine, just before his entrance slams the ground.</summary>
	void TickReveal()
	{
		if ( _revealed || Time.Now < _born + RevealAt ) return;
		_revealed = true;

		MargwaFx.SetHidden( GameObject, false );
		AvogadroFx.Flash( WorldPosition + Vector3.Up * 50f * Size, 1.4f );

		if ( NZGame.IsClient ) return;

		// upstream's reveal: the zap and the rubble, the electric water, the beam
		NZSound.PlayShared( "nz.director.zap", WorldPosition );
		NZSound.PlayShared( "nz.director.electricwater", WorldPosition );
		NZSound.PlayShared( "nz.director.beam", WorldPosition );
		Queue( 39f / 30f - RevealAt, Beat.EntranceSlam );
	}

	/// <summary>Dead, on every machine: the lamp stays lit until he goes; the host owes his killer a perk.</summary>
	void OnDeath()
	{
		if ( _deadHandled ) return;
		_deadHandled = true;
		_deathAt = Time.Now;
		_pending.Clear();

		if ( _ai.IsPuppet ) return;

		_ai.CorpseLinger = 0.2f;
		Reward();
	}

	/// <summary>Gone in a flash at frame 131 of his death, upstream's `boss_remove` — on every machine.</summary>
	void TickRemove()
	{
		if ( _removed || Time.Now < _deathAt + RemoveAt ) return;
		_removed = true;

		AvogadroFx.Flash( WorldPosition + Vector3.Up * 40f * Size, 1.4f );
		MargwaFx.SetHidden( GameObject, true );
		if ( _lamp.IsValid() ) _lamp.TurnOff();

		if ( !NZGame.IsClient ) NZSound.PlayShared( "nz.director.zap", WorldPosition );
	}

	// ══ the host ═════════════════════════════════════════════════════════════

	/// <summary>Untouchable while he arrives (upstream's 1.5 s, here his entrance).</summary>
	void TickShield()
	{
		var want = _ai.State == ZombieState.Spawning;
		if ( want == _shielded || !_hp.IsValid() ) return;
		_shielded = want;
		_hp.Invulnerable = want;
	}

	/// <summary>A player's hit asked for a rage or a stagger (`ScaleFor`); decided here, outside the damage call.</summary>
	void TickHit()
	{
		if ( !_hitPending ) return;
		_hitPending = false;
		if ( _raging || _ai.State is not (ZombieState.Chasing or ZombieState.Attacking) ) return;

		var now = Time.Now;
		if ( now >= _nextRage ) StartRage();
		else if ( now >= _nextReact ) React();
	}

	void TickRage()
	{
		if ( !_raging || (!_gotOne && Time.Now < _rageUntil) ) return;
		if ( _ai.State is not (ZombieState.Chasing or ZombieState.Attacking) ) return;
		CalmDown();
	}

	/// <summary>
	/// A player going down near him while he rages: his kill, and the end of his rage (the user: *"when it kills a player it calms
	/// down"*). ⚠️ A CLIENT'S DOWN REACHES THE HOST AS `DownedNet` (its owner publishes it); `IsDown` on the host's copy of a client
	/// is always false. THE HOST.
	/// </summary>
	void TickDowns()
	{
		foreach ( var p in Scene.GetAllComponents<NZPlayer>() )
		{
			if ( !p.IsValid() ) continue;
			var down = p.IsDown || p.DownedNet;
			var was = _wasDown.TryGetValue( p, out var w ) && w;
			_wasDown[p] = down;

			if ( !down || was || !_raging || _gotOne ) continue;
			if ( p.WorldPosition.Distance( WorldPosition ) > DownReach * Size ) continue;

			_gotOne = true;
			Log.Info( $"[nz-director] he DOWNED {p.GameObject.Name} — calming down" );
		}
	}

	/// <summary>The rage: the zombified body, the roar, the slam, then the sprint for 45 s. THE HOST.</summary>
	void StartRage()
	{
		if ( !PlayShared( "nz_boss_enrage_start_coast", RageClip ) ) return;

		_raging = true;
		_gotOne = false;
		_rageUntil = Time.Now + RageClip + RageSeconds;
		NZNet.DirectorBody( GameObject.Id, 1 );

		_ai.SpeedOverride = MathF.Max( 1f, RageSpeed * Size );
		_ai.RepickAnimations();
		SetAccel( RageAccel );

		Queue( 2f / 30f, Beat.Roar );
		Queue( 107f / 30f, Beat.Fall );
		Queue( 116f / 30f, Beat.Slam );
		Queue( 150f / 30f, Beat.React );
		Log.Info( $"[nz-director] RAGE — {RageSeconds:0}s at {_ai.SpeedOverride:0} u/s" );
	}

	/// <summary>Calm again: `nz_boss_sprint_to_walk`, the calm body, the walk; a rage again in 5 s. THE HOST.</summary>
	void CalmDown()
	{
		_raging = false;
		_gotOne = false;
		_nextRage = Time.Now + RageCooldown;
		NZNet.DirectorBody( GameObject.Id, 0 );

		_ai.SpeedOverride = MathF.Max( 1f, WalkSpeed * Size );
		_ai.RepickAnimations();
		SetAccel( _ai.Acceleration );

		if ( PlayShared( "nz_boss_sprint_to_walk", CalmClip ) )
		{
			Queue( 4f / 30f, Beat.Boom );
			Queue( 41f / 30f, Beat.Weak );
		}
		Log.Info( "[nz-director] calm" );
	}

	/// <summary>His nav agent's acceleration (the rage's, or back to the zombie's own). THE HOST.</summary>
	void SetAccel( float accel )
	{
		var agent = Components.Get<NavMeshAgent>( FindMode.EverythingInSelfAndDescendants );
		if ( agent.IsValid() ) agent.Acceleration = MathF.Max( 100f, accel );
	}

	/// <summary>A stagger — upstream's only flinch, for a hit while he cannot rage yet. THE HOST.</summary>
	void React()
	{
		_nextReact = Time.Now + ReactGap;
		if ( PlayShared( "nz_boss_nuke_react", ReactClip ) ) Queue( 88f / 30f, Beat.Yell );
	}

	void TickBuffs()
	{
		if ( _ai.State != ZombieState.Chasing || Time.Now < _nextThink ) return;
		_nextThink = Time.Now + 0.5f;

		var now = Time.Now;
		// upstream's roll: 3 in 4 the speed buff, else the electric one
		if ( Game.Random.Float( 0f, 1f ) < 0.75f )
		{
			if ( now >= _nextSpeedBuff && Near( BuffQualify, z => z.SpeedRating > 70f && z.SpeedRating < 200f ).Count >= 2 )
			{
				_nextSpeedBuff = now + SpeedBuffGap;
				if ( PlayShared( "nz_boss_enrage_start_scream", ScreamClip ) ) Queue( 2f / 30f, Beat.SpeedYell );
			}
		}
		else if ( now >= _nextElecBuff && Near( BuffQualify, z => !Charged( z ) ).Count >= 2 )
		{
			_nextElecBuff = now + ElecBuffGap;
			if ( PlayShared( "nz_boss_enrage_start_scream", ScreamClip ) ) Queue( 2f / 30f, Beat.ElecYell );
		}
	}

	/// <summary>Ordinary zombies within <paramref name="radius"/> passing <paramref name="test"/> — never a boss.</summary>
	List<ZombieAI> Near( float radius, Func<ZombieAI, bool> test )
	{
		var r = radius * Size;
		var list = new List<ZombieAI>();
		foreach ( var z in Scene.GetAllComponents<ZombieAI>() )
		{
			if ( !z.IsValid() || z == _ai || z.State is ZombieState.Dead or ZombieState.Spawning ) continue;
			if ( (z.Variant?.IsBoss ?? false) || z.WorldPosition.Distance( WorldPosition ) > r ) continue;
			if ( test( z ) ) list.Add( z );
		}
		return list;
	}

	static bool Charged( ZombieAI z ) => z.Components.Get<DirectorCharge>( FindMode.EverythingInSelf ).IsValid();

	/// <summary>The speed buff: everyone within reach sprints at the top tier (upstream's `SetRunSpeed(300)`). THE HOST.</summary>
	void SpeedBuff()
	{
		var n = 0;
		foreach ( var z in Near( BuffReach, _ => true ) )
			if ( z.RaiseSpeedRating( 300f ) ) n++;
		Log.Info( $"[nz-director] SPEED BUFF — {n} zombie(s) sprinting" );
	}

	/// <summary>
	/// The electric buff: everyone within reach not yet charged gets <see cref="ElecHealth"/> zombies' health of this round,
	/// and electricity on every machine (upstream's `SetHealth(GetZombieHealth() * 3)` + `SetWaterBuff`). THE HOST.
	/// </summary>
	void ElectricBuff( float reach )
	{
		int round = Math.Max( 1, RoundManager.Instance?.Round ?? 1 );
		var health = MathF.Max( 1f, ZombieStats.HealthForRound( round ) * ElecHealth * Difficulty.ZombieHealth );

		var n = 0;
		foreach ( var z in Near( reach, z => !Charged( z ) ) )
		{
			var hp = z.Components.Get<Health>( FindMode.EverythingInSelfAndAncestors );
			if ( !hp.IsValid() || hp.IsDead ) continue;

			hp.Reset( MathF.Max( health, hp.Max ) );
			z.Components.GetOrCreate<DirectorCharge>();
			NZNet.DirectorCharged( z.GameObject.Id );
			n++;
		}
		Log.Info( $"[nz-director] ELECTRIC BUFF — {n} zombie(s) charged to {health:0} hp" );
	}

	/// <summary>
	/// His lines that the AI's passive voice does not cover: the entrance (upstream's `start_0` at 7 s, `start_2` at 10 s), and
	/// "behind you" when his target is close and looking away (upstream's first priority, within 300).
	/// </summary>
	void TickVoice()
	{
		var now = Time.Now;
		if ( !_start0 && now >= _born + 7f ) { _start0 = true; NZSound.PlayShared( "nz.director.start", Voice ); return; }
		if ( !_start2 && now >= _born + 10f ) { _start2 = true; NZSound.PlayShared( "nz.director.start", Voice ); return; }

		if ( now < _nextBehind || _ai.State != ZombieState.Chasing ) return;
		var p = _ai.Target.IsValid() ? _ai.Target.Components.Get<NZPlayer>( FindMode.EverythingInSelfAndAncestors ) : null;
		if ( !p.IsValid() || (p.IsDown || p.DownedNet) || p.WorldPosition.Distance( WorldPosition ) > 300f * Size ) return;

		var pc = p.Components.Get<PlayerController>( FindMode.EverythingInSelfAndDescendants );
		var look = (pc.IsValid() ? pc.EyeAngles.ToRotation() : p.WorldRotation).Forward.WithZ( 0 ).Normal;
		var toHim = (WorldPosition - p.WorldPosition).WithZ( 0 ).Normal;
		if ( look.Dot( toHim ) > 0f ) return;   // looking at him

		_nextBehind = now + Game.Random.Float( 8f, 15f );
		NZSound.PlayShared( "nz.director.behind", Voice );
	}

	void Queue( float after, Beat what ) => _pending.Add( (Time.Now + after, what) );

	void TickPending()
	{
		for ( int i = _pending.Count - 1; i >= 0; i-- )
		{
			if ( i >= _pending.Count ) continue;
			var p = _pending[i];
			if ( Time.Now < p.At ) continue;
			_pending.RemoveAt( i );
			Run( p.What );
		}
	}

	void Run( Beat what )
	{
		switch ( what )
		{
			case Beat.Roar:
				// upstream's `boss_vox_enrage`: the biggest shake it has, the angered vox, everyone within 575 dazed 1 s
				NZSound.PlayShared( "nz.director.angered", Voice );
				NZNet.DirectorRoar( WorldPosition, RoarRadius * Size, RoarDaze, 0.8f );
				break;

			case Beat.Fall:
				NZSound.PlayShared( "nz.director.rubble", WorldPosition );
				break;

			case Beat.Slam:
				Slam( true );
				break;

			case Beat.EntranceSlam:
				Slam( false );
				break;

			case Beat.React:
				NZSound.PlayShared( "nz.director.react", Voice );
				break;

			case Beat.Boom:
				NZSound.PlayShared( "nz.director.beam", WorldPosition );
				AvogadroFx.Flash( WorldPosition + Vector3.Up * 50f * Size, 1f );
				break;

			case Beat.Weak:
				NZSound.PlayShared( "nz.director.weak", Voice );
				break;

			case Beat.Yell:
				// upstream's `boss_vox_yell` with no buff pending: the pain yell, the shake, everyone within 575 dazed 1 s
				NZSound.PlayShared( "nz.director.painyell", Voice );
				NZNet.DirectorRoar( WorldPosition, RoarRadius * Size, RoarDaze, 0.8f );
				break;

			case Beat.SpeedYell:
				NZSound.PlayShared( "nz.director.speedbuff", Voice );
				SpeedBuff();
				break;

			case Beat.ElecYell:
				NZSound.PlayShared( "nz.director.elecbuff", Voice );
				ElectricBuff( BuffReach );
				break;
		}
	}

	/// <summary>
	/// His slam, upstream's `boss_slam`: the zap and the rubble, a ring and a shake; in a rage also the heavy hit within 105, the
	/// 3 s daze within 175 and the zombies within 100 charged. His entrance slams for show. THE HOST.
	/// </summary>
	void Slam( bool hurts )
	{
		var at = WorldPosition + Facing * 60f * Size;
		NZSound.PlayShared( "nz.director.zap", at );
		NZSound.PlayShared( "nz.director.rubble", at );
		ShockRing.FireShared( at, 220f * Size, DirectorFx.LampBlue );
		NZNet.ShakeAt( at, 0.6f, 1000f );

		if ( !hurts ) return;

		NZNet.DirectorRoar( WorldPosition, SlamDazeRadius * Size, SlamDaze, 0f );

		int round = Math.Max( 1, RoundManager.Instance?.Round ?? 1 );
		var swing = ZombieStats.AttackDamageForRound( round ) * (_ai.Variant?.DamageMultiplier ?? 1.8f) * Difficulty.ZombieDamage;
		var damage = MathF.Min( swing * SlamWeight, MaxHit );

		foreach ( var p in Scene.GetAllComponents<NZPlayer>().ToList() )
		{
			if ( !p.IsValid() || (p.IsDown || p.DownedNet) ) continue;
			var hp = p.Components.Get<Health>( FindMode.EverythingInSelfAndDescendants );
			if ( !hp.IsValid() || hp.IsDead ) continue;
			if ( p.WorldPosition.Distance( WorldPosition ) > SlamRadius * Size ) continue;
			hp.Apply( damage, false, GameObject, blast: true, blastAt: at );
		}

		ElectricBuff( 100f );
	}

	/// <summary>
	/// His killer gets a perk they do not have yet, at random, through the slot cap — upstream's perk bottle, which this
	/// project has no power-up for. Nobody to credit (a nuke): the nearest player. THE HOST picks; that player's machine
	/// grants it (`DirectorReward.Grant`).
	/// </summary>
	void Reward()
	{
		if ( _rewarded ) return;
		_rewarded = true;

		var killer = _hp.IsValid() && _hp.LastAttacker.IsValid()
			? _hp.LastAttacker.Components.Get<NZPlayer>( FindMode.EverythingInSelfAndAncestors )
			: null;

		var who = killer.IsValid()
			? killer
			: Scene.GetAllComponents<NZPlayer>().Where( p => p.IsValid() && !(p.IsDown || p.DownedNet) )
				.OrderBy( p => p.WorldPosition.Distance( WorldPosition ) ).FirstOrDefault();

		if ( !who.IsValid() ) { Log.Info( "[nz-director] dead — nobody to take his perk" ); return; }

		// ⛔ ON THE KILLER'S OWN MACHINE (2026-10-06 fix): perks are bought, kept and lost there and synced to no one, so only it
		// knows its slots and what it owns — granted here, a client's perk landed in a copy nobody reads
		NZNet.DirectorPerk( who.GameObject.Id );
		Log.Info( $"[nz-director] dead — his perk goes to {who.GameObject.Name}" );
	}

	// ── clips ──

	/// <summary>`ZombieAI.PlaySpecial`, and the same clip on every watching machine — `PlaySpecial` does not relay.</summary>
	bool PlayShared( string clip, float seconds )
	{
		if ( !_ai.PlaySpecial( clip, seconds ) ) return false;
		if ( Networking.IsActive ) NZNet.ZombieClip( GameObject.Id, clip, 1f );
		return true;
	}

	// ══ the damage table ═════════════════════════════════════════════════════

	/// <summary>
	/// The multiplier for one hit: ×0.095. A player's hit also asks for his rage or a stagger (decided in `TickHit`). THE HOST.
	/// </summary>
	public float ScaleFor( GameObject attacker )
	{
		if ( _deadHandled ) return 1f;
		if ( _shielded ) return 0f;

		if ( attacker.IsValid() && attacker.Components.Get<NZPlayer>( FindMode.EverythingInSelfAndAncestors ).IsValid() )
			_hitPending = true;

		return MathF.Max( 0f, DamageScale );
	}

	/// <summary>The scale for a hit on this object: the Director's, or 1 when it is not him (`Health.OnDamage`).</summary>
	public static float ScaleOn( GameObject victim, GameObject attacker )
	{
		if ( !victim.IsValid() ) return 1f;
		var d = victim.Components.Get<DirectorBoss>( FindMode.EverythingInSelfAndAncestors );
		return d.IsValid() ? d.ScaleFor( attacker ) : 1f;
	}

	// ══ diagnostics ══════════════════════════════════════════════════════════

	static IEnumerable<DirectorBoss> All
		=> Game.ActiveScene?.GetAllComponents<DirectorBoss>() ?? Enumerable.Empty<DirectorBoss>();

	/// <summary>`nz_director` — every Director alive.</summary>
	[ConCmd( "nz_director" )]
	public static void Report()
	{
		Log.Info( $"[nz-director] x{DamageScale:0.###} · rage {RageSeconds:0}s (or until he downs someone within {DownReach:0}u)"
			+ $" at {RageSpeed:0} u/s (x{RageSpeedScale:0.#} his sprint), again after {RageCooldown:0}s"
			+ $" · roar {RoarRadius:0}u/{RoarDaze:0.#}s · slam {SlamRadius:0}u x{SlamWeight:0.##}, daze {SlamDazeRadius:0}u/{SlamDaze:0.#}s"
			+ $" · buffs: speed every {SpeedBuffGap:0}s, electric every {ElecBuffGap:0}s (x{ElecHealth:0.#} hp) · hit cap {MaxHit:0}" );

		var list = All.ToList();
		Log.Info( $"[nz-director] {list.Count} alive" );
		foreach ( var d in list )
			Log.Info( $"[nz-director]   hp {d._hp?.Current ?? 0f:0}/{d._hp?.Max ?? 0f:0} · state {d._ai?.State} · speed {d._ai?.MoveSpeed ?? 0f:0}"
				+ $" u/s{(d._raging ? $" · RAGING {MathF.Max( 0f, d._rageUntil - Time.Now ):0}s left" : "")} · hitboxes {d._ai?.HitboxCount ?? 0}" );
	}

	/// <summary>`nz_director_rage` — every Director rages now.</summary>
	[ConCmd( "nz_director_rage" )]
	public static void RageCmd()
	{
		if ( NZGame.IsClient ) { Log.Info( "[nz-director] the host decides" ); return; }
		foreach ( var d in All.Where( d => d._ai.IsValid() && !d._raging ).ToList() ) d.StartRage();
	}

	/// <summary>`nz_director_calm` — every Director calms down now.</summary>
	[ConCmd( "nz_director_calm" )]
	public static void CalmCmd()
	{
		if ( NZGame.IsClient ) { Log.Info( "[nz-director] the host decides" ); return; }
		foreach ( var d in All.Where( d => d._ai.IsValid() && d._raging ).ToList() ) d.CalmDown();
	}

	/// <summary>`nz_director_buff &lt;speed|electric&gt;` — every Director screams that buff now, at whoever is in reach.</summary>
	[ConCmd( "nz_director_buff" )]
	public static void BuffCmd( string kind = "speed" )
	{
		if ( NZGame.IsClient ) { Log.Info( "[nz-director] the host decides" ); return; }
		var electric = kind.StartsWith( "e", StringComparison.OrdinalIgnoreCase );
		foreach ( var d in All.Where( d => d._ai.IsValid() ).ToList() )
			if ( d.PlayShared( "nz_boss_enrage_start_scream", ScreamClip ) )
				d.Queue( 2f / 30f, electric ? Beat.ElecYell : Beat.SpeedYell );
	}

	/// <summary>`nz_director_set &lt;key&gt; &lt;value&gt;` — retune him live.</summary>
	[ConCmd( "nz_director_set" )]
	public static void SetCmd( string key = "", float value = 0f )
	{
		switch ( key.ToLowerInvariant() )
		{
			case "damage": DamageScale = value; break;
			case "rage": RageSeconds = value; break;
			case "cooldown": RageCooldown = value; break;
			case "react": ReactGap = value; break;
			case "sprint": SprintSpeed = value; break;
			case "ragespeed": RageSpeedScale = value; break;
			case "rageanim": RageAnimRate = value; break;
			case "rageaccel": RageAccel = value; break;
			case "downreach": DownReach = value; break;
			case "roar": RoarRadius = value; break;
			case "roardaze": RoarDaze = value; break;
			case "slam": SlamWeight = value; break;
			case "slamdaze": SlamDaze = value; break;
			case "speedgap": SpeedBuffGap = value; break;
			case "elecgap": ElecBuffGap = value; break;
			case "elechp": ElecHealth = value; break;
			case "hitcap": HitCap = value; break;
			default:
				Log.Info( "[nz-director] nz_director_set <damage|rage|cooldown|react|sprint|ragespeed|rageanim|rageaccel|downreach|roar"
					+ "|roardaze|slam|slamdaze|speedgap|elecgap"
					+ "|elechp|hitcap> <value>" );
				return;
		}

		Log.Info( $"[nz-director] {key} = {value:0.###}" );
		Report();
	}
}