Partial class HexPlatforms, UI/game logic for showing egg (Easter egg) progress to a machine. It reads mirrored/internal state fields and exposes StepDoneShown, StepNowShown, grouping of 16 steps into 9 main steps, MainDoneShown/MainNowShown, and builds localized little-step entries with counts for UI display.
using Sandbox;
namespace NZombies;
/// <summary>
/// WHICH STEPS OF THE EGG THIS MACHINE IS SHOWN DONE, for the scoreboard's footer (`EggProgress.Footer`, 2026-09-29).
///
/// ⛔ THE MIRRORS, NEVER THE HOST'S OWN FIELDS. Every step is decided on the host and mirrored to each machine (`NZNet`'s
/// `HexPlatformsDone`, `HexBonfire`, `ShieldLockState`, … and `NZNet.PushState` replays them all to a joiner), so a `…Shown`
/// field reads the same on every screen, the host's included. The host-only fields (`_done`, `_bonfire`, …) would leave a
/// client's footer on step 1 for the whole game.
///
/// ⚠️ A PARTIAL OF `HexPlatforms`, so it reads the private mirrors without a public getter for each.
/// </summary>
public sealed partial class HexPlatforms
{
/// <summary>
/// Is step <paramref name="step"/> done, as this machine is shown it? 1-16, numbered as `NZNet.EggStepDone` numbers them
/// (`EggFanfare.For`'s table), not as `nz_hex_skipto` does.
///
/// ⚠️ STEP 6, THE FLAME ON THE ALTAR, ALSO COUNTS AS DONE ONCE THE DEFENSE IS WON. Its mirror goes false again when the
/// light-blue flame is lifted off for the twin shield (`HexPlatforms.Torch`). A failed defense takes the flame off too, and
/// that is the one time it should read as not done: the team has to carry it back.
/// </summary>
public static bool StepDoneShown( int step )
{
var m = Instance;
if ( !m.IsValid() ) return false;
return step switch
{
1 => m._doneShown,
2 => m._bonfireShown >= BonfireDone,
3 => m.RingsShown.Done,
4 => m._bonfireShown >= BonfireOut,
5 => m._lockOpenShown,
6 => m._flamePlacedShown || m._defenseShown == Defense.Won,
7 => m._defenseShown == Defense.Won,
8 => m._twinOpenShown,
9 => m.ShriekersDoneShown,
10 => m.MastermindDoneShown,
11 => m.LavaDoneShown,
12 => m.JunctionsDoneShown,
13 => m.ButtonsDoneShown,
14 => m._fightShown >= FightIntro,
15 => m._fightShown >= FightWon,
16 => m._fightShown == FightDone,
_ => false,
};
}
/// <summary>
/// The step on now, as this machine is shown it: the first one not done, 1-16, or 17 once all sixteen are.
///
/// ⚠️ THE FIRST NOT DONE, NOT ONE PAST THE LAST DONE. The Shield Lock takes its code whenever the code is found, so it can
/// open before the Torch Carry is done, and the step on is still the Torch Carry then.
/// </summary>
public static int StepNowShown
{
get
{
for ( var s = 1; s <= EggProgress.Steps; s++ )
if ( !StepDoneShown( s ) ) return s;
return EggProgress.Steps + 1;
}
}
// ══ THE NINE MAIN STEPS AND THEIR LITTLE STEPS (2026-09-29) ═════════════════════════════════════════════════════════════
//
// The user regrouped the sixteen into nine, named them (`EggProgress.MainName`), and wrote each one's little steps: *"Ok
// those are the main steps, now let's write the minor steps inside each one"*. The sixteen stay what the host decides and the
// fanfares ride (`NZNet.EggStepDone`); these only read them, and a few finer mirrors this machine already has: the power,
// the napalm's fire, the pests, each ring, the light blue flame in hand, the Shriekers, the stones.
/// <summary>How many main steps the egg has: nine.</summary>
public const int EggMains = 9;
/// <summary>A little step as this machine is shown it: its line, done or not, and a count where it keeps one (`Need` 0: none).</summary>
public readonly record struct EggLittle( string Text, bool Done, int Have = 0, int Need = 0 );
/// <summary>The first and the last of the sixteen steps that make main step <paramref name="main"/> (1-9).</summary>
public static (int First, int Last) MainSteps( int main ) => main switch
{
1 => (1, 1), 2 => (2, 2), 3 => (3, 3), 4 => (4, 7), 5 => (8, 9), 6 => (10, 11), 7 => (12, 12), 8 => (13, 14),
_ => (15, 16),
};
/// <summary>The main step (1-9) one of the sixteen belongs to.</summary>
public static int MainOf( int step ) => step switch
{
<= 1 => 1, 2 => 2, 3 => 3, <= 7 => 4, <= 9 => 5, <= 11 => 6, 12 => 7, <= 14 => 8, _ => 9,
};
/// <summary>Is main step <paramref name="main"/> done, as this machine is shown it? Every one of its sixteen-steps done.</summary>
public static bool MainDoneShown( int main )
{
if ( main < 1 || main > EggMains ) return false;
var (first, last) = MainSteps( main );
for ( var s = first; s <= last; s++ )
if ( !StepDoneShown( s ) ) return false;
return true;
}
/// <summary>
/// The main step on now, as this machine is shown it: the first not done, 1-9, or 10 once all nine are.
/// ⚠️ THE FIRST NOT DONE, as `StepNowShown` is: the Shield Lock can open before the Torch Carry.
/// </summary>
public static int MainNowShown
{
get
{
for ( var m = 1; m <= EggMains; m++ )
if ( !MainDoneShown( m ) ) return m;
return EggMains + 1;
}
}
/// <summary>
/// Main step <paramref name="main"/>'s little steps, in the user's words, as this machine is shown them. Counts where the
/// step keeps one: the pests (of `BonfireTarget`), the Shriekers on the dais (of three), the stones light blue (of six).
/// </summary>
public static EggLittle[] LittleStepsShown( int main )
{
var m = Instance;
if ( !m.IsValid() ) return System.Array.Empty<EggLittle>();
var rings = m.RingsShown;
bool Ring( int colour ) => rings.Done || (rings.Active && rings.PositionOf( colour ) == rings.TargetOf( colour ));
return main switch
{
// ⚠️ THE POWER COUNTS AS ON ONCE THE SLAMS ARE DONE: the slams need it, and a power that went off after is not undone
1 => new[]
{
new EggLittle( "Turn on the power", Power.IsOn || m._doneShown ),
new EggLittle( "Slam the tile slots", m._doneShown ),
},
2 => new[]
{
new EggLittle( "Kill a napalm zombie on the crucible", m._bonfireShown >= BonfireLit ),
new EggLittle( "Kill pests in the fire", m._bonfireShown >= BonfireDone,
m._bonfireShown >= BonfireDone ? BonfireTarget : System.Math.Clamp( m._pestsShown, 0, BonfireTarget ), BonfireTarget ),
},
// the user's order, blue, green, yellow, red (`ColourName`: 0 blue, 1 yellow, 2 green, 3 red)
3 => new[]
{
new EggLittle( "Set the blue ring", Ring( 0 ) ),
new EggLittle( "Set the green ring", Ring( 2 ) ),
new EggLittle( "Set the yellow ring", Ring( 1 ) ),
new EggLittle( "Set the red ring", Ring( 3 ) ),
},
4 => new[]
{
new EggLittle( "Kill a Shrieker in the bonfire", StepDoneShown( 4 ) ),
new EggLittle( "Unlock the altar", StepDoneShown( 5 ) ),
new EggLittle( "Place the flame on the altar", StepDoneShown( 6 ) ),
new EggLittle( "Defend the altar", StepDoneShown( 7 ) ),
},
5 => new[]
{
// ⚠️ IN HAND, OR SPENT ON THE TWIN SHIELD: a carrier who loses it unticks it, since the flame is back to be taken
new EggLittle( "Pick up the blue flame", m._twinOpenShown || (m.FlameBlueShown && m.FlameCarried) ),
new EggLittle( "Unlock the gateway", StepDoneShown( 8 ) ),
new EggLittle( "Kill 3 Shriekers in the gateway", StepDoneShown( 9 ),
System.Math.Clamp( m._shriekerKillsShown, 0, ShriekersTarget ), ShriekersTarget ),
},
6 => new[]
{
new EggLittle( "Power up the magma generator", StepDoneShown( 10 ) ),
new EggLittle( "Survive the lava", StepDoneShown( 11 ) ),
},
7 => new[]
{
new EggLittle( "Correct the junction's path", StepDoneShown( 12 ) ),
},
8 => new[]
{
new EggLittle( "Turn the stones blue in the gateway", StepDoneShown( 13 ), StonesBlueShown( m ), 6 ),
new EggLittle( "Interact with the gateway", StepDoneShown( 14 ) ),
},
9 => new[]
{
new EggLittle( "Defeat Oberon", StepDoneShown( 15 ) ),
new EggLittle( "Pick up the orb", StepDoneShown( 16 ) ),
},
_ => System.Array.Empty<EggLittle>(),
};
}
/// <summary>How many of the teleporter's six stones are light blue, as this machine is shown them: 0 asleep, 6 once set.</summary>
static int StonesBlueShown( HexPlatforms m )
{
if ( m.ButtonsDoneShown ) return 6;
if ( (m._btnFlagsShown & BtnActive) == 0 ) return 0;
var n = 0;
for ( var b = 0; b < 6; b++ )
if ( BtnColour( m._btnColoursShown, b ) == 2 ) n++;
return n;
}
}