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.
@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" );
}
}