UI/SonicDazeOverlay.razor

A UI Razor component that implements a 'sonic daze' overlay for a player. It reads the local player's SonicDaze component to compute a blur strength, writes CSS backdrop-filter and opacity to two Panel layers each frame, and includes a console command to test the effect by applying a SonicDaze to the local player.

File Access
@using Sandbox;
@using Sandbox.UI;
@using System;
@using NZombies;
@inherits PanelComponent

@*
    SONIC DAZE — the screen goes soft and cold after a sonic wave catches you.

    ⛔ IT IS A BLUR, NOT A WHITEOUT, AND THE FIRST VERSION OF THIS FILE WAS A WHITEOUT. I read
    upstream's `NZSonicBlind` as "blind" and built a sheet of white light; the entity behind
    that name is a bare timer that nothing in the addon reads, so the name was the only
    evidence and it was the wrong evidence. The effect is slowed legs and soft vision — you
    can still see, you just cannot pick anything out or get away from it.

    ⚠️ `backdrop-filter` IS THE MECHANISM AND IT IS ALREADY PROVEN HERE. The engine's own menus
    use `backdrop-filter: brightness() blur()`, and SWB's customisation menu uses it in this
    project. It blurs what is rendered BEHIND the panel, which is the whole world — no post
    process component, no camera work, nothing to put back when it ends.

    ⚠️ IT READS THE PLAYER, NOT A STATIC. The napalm's heat is a static because several zombies
    have to reduce to one number; a daze belongs to one player and lives on them as a
    `SonicDaze` component, so in a four-player game only the one who was hit goes soft.

    # MAPPORT: sonic daze
*@

<root class="daze">
    <div @ref="Soft" class="layer soft"></div>
    <div @ref="Edge" class="layer edge"></div>
</root>

@code
{
    /// <summary>⛔ CONSTANT, like every other overlay here — the strength is written to Style every
    /// frame and a hash that tracked it would rebuild the tree constantly.</summary>
    protected override int BuildHash() => 0;

    Panel Soft;
    Panel Edge;

    /// <summary>
    /// Master switch: `nz_daze_overlay 0`.
    ///
    /// ⚠️ NOT called `Enabled`. `PanelComponent` inherits `Component.Enabled` and a static of that
    /// name SHADOWS it — this project has hit that collision at least six times.
    /// </summary>
    [ConVar( "nz_daze_overlay" )] public static bool ShowOverlay { get; set; } = true;

    /// <summary>
    /// Ceiling on the blur, as a multiplier on what `SonicDaze` asks for.
    ///
    /// ⚠️ A SCALE RATHER THAN AN ABSOLUTE, so the component stays the one place that decides how
    /// strong a daze is and this stays a comfort setting. Somebody who finds it nauseating can turn
    /// it down without changing how long the enemy slows them for.
    /// </summary>
    [ConVar( "nz_daze_overlay_scale" )] public static float BlurScale { get; set; } = 1f;

    float _shown;

    protected override void OnUpdate()
    {
        var want = ShowOverlay ? SonicDaze.BlurFor( NZPlayer.Local ) * BlurScale : 0f;

        // ⚠️ SNAPS ON, EASES OFF. Being hit is instant; easing INTO the blur would soften the
        // moment the player most needs to understand. Coming out of it slowly is what makes the
        // recovery feel like your eyes clearing rather than a filter being switched off.
        //
        // ⚠️ AND THE LERP IS ON THE PIXEL VALUE, not on the component's strength — `SonicDaze`
        // already holds then fades, so lerping its output too would fade twice and the tail would
        // outlast the slow it is supposed to be describing.
        _shown = want > _shown ? want : _shown.LerpTo( want, Time.Delta * 3.5f );

        var px = MathF.Max( 0f, _shown );

        if ( Soft is not null )
        {
            // ⚠️ THE FILTER IS WRITTEN AS A STRING because there is no typed backdrop-filter on
            // Styles — this is how the engine's own panels set it too.
            Soft.Style.Set( "backdrop-filter", px < 0.05f ? "none"
                : $"blur( {px:0.##}px ) saturate( {( 1f - ( px / 20f ).Clamp( 0f, 0.45f ) ):0.##} )" );

            Soft.Style.Opacity = px < 0.05f ? 0f : 1f;
        }

        // A cold ring at the edges, so the blur reads as something done TO you rather than as the
        // game losing focus. Weaker than the blur and arrives with it.
        if ( Edge is not null )
            Edge.Style.Opacity = ( px / 14f ).Clamp( 0f, 1f ) * 0.55f;
    }

    /// <summary>
    /// `nz_daze_overlay_test [px] [seconds]` — hold the blur so it can be looked at.
    ///
    /// ⚠️ IT GOES THROUGH `SonicDaze`, NOT STRAIGHT TO THE PANEL, so the test exercises the thing
    /// the game actually uses — including the slow. A command that only moved the picture would
    /// pass while the half that matters was broken.
    /// </summary>
    [ConCmd( "nz_daze_overlay_test" )]
    public static void Test( float px = 11f, float seconds = 5f )
    {
        var p = NZPlayer.Local;
        if ( !p.IsValid() ) { Log.Warning( "[nz-daze] no local player" ); return; }

        SonicDaze.Apply( p, seconds );

        var d = p.Components.Get<SonicDaze>();
        if ( d.IsValid() ) d.BlurPixels = px;

        Log.Info( $"[nz-daze] holding {px:0.#}px for {seconds:0.#}s" );
    }
}