A Scene GameObjectSystem that applies a "horror" visual mode when the DirectorRitmo reports FaseHorror true. It tints SkyBox2D, modifies DirectionalLight colors, spawns a gradient fog object, and adds camera color adjustments and vignette; it restores originals on exit.
namespace SBH;
using Sandbox;
using System.Collections.Generic;
/// <summary>
/// [SBHBase] La fase 2 del Sprunki original: cuando DirectorRitmo entra
/// en FaseHorror (ConVar sbh.horror), el AMBIENTE se transforma — cielo
/// rojo sangre (#800000, el color exacto del backdrop evil del original),
/// luz de casi-noche, niebla roja y viñeta cerrada. Todo reversible al salir.
/// Los PERSONAJES ya no se tocan acá: los horror son personajes COMPLETOS
/// (sprunki_hrr_*) y el reemplazo base↔hrr lo hace SbhConversionHorror en el
/// juego (necesita Vida/prefabs de SBHBase, que esta librería no conoce).
/// </summary>
public sealed class ModoHorror : GameObjectSystem
{
bool _activo;
readonly Dictionary<SkyBox2D, Color> _tintesOriginales = new();
readonly Dictionary<DirectionalLight, (Color Luz, Color Cielo)> _lucesOriginales = new();
readonly List<Component> _compsAmbiente = new();
GameObject _niebla;
// La paleta del backdrop evil del Scratch: negro + rojo sangre
static readonly Color RojoSangre = new( 0.5f, 0.03f, 0.03f );
public ModoHorror( Scene scene ) : base( scene )
{
Listen( Stage.StartUpdate, 35, Tick, "ModoHorrorSprunki" );
}
void Tick()
{
var director = Scene.GetSystem<DirectorRitmo>();
var horror = director?.FaseHorror ?? false;
if ( horror != _activo )
{
_activo = horror;
if ( _activo ) Entrar();
else Salir();
}
}
// ---------- Entrada ----------
void Entrar()
{
Log.Info( "🩸 FASE HORROR: el mundo se oscurece" );
// Cielo rojo: teñir los skybox que el mapa haya traído
foreach ( var cielo in Scene.GetAllComponents<SkyBox2D>() )
{
if ( _tintesOriginales.ContainsKey( cielo ) ) continue;
_tintesOriginales[cielo] = cielo.Tint;
cielo.Tint = RojoSangre;
}
// Casi de noche: la luz direccional cae a un rescoldo rojizo
foreach ( var luz in Scene.GetAllComponents<DirectionalLight>() )
{
if ( _lucesOriginales.ContainsKey( luz ) ) continue;
_lucesOriginales[luz] = (luz.LightColor, luz.SkyColor);
luz.LightColor = new Color( 0.35f, 0.10f, 0.10f );
luz.SkyColor = new Color( 0.18f, 0.03f, 0.03f );
}
// Niebla roja rastrera (las "nubes" bajas del backdrop evil)
_niebla = Scene.CreateObject();
_niebla.Name = "horror_niebla";
var fog = _niebla.Components.Create<GradientFog>();
fog.Color = new Color( 0.25f, 0.02f, 0.02f );
fog.StartDistance = 150f;
fog.EndDistance = 2200f;
// Sobre la cámara: color apagado y viñeta que cierra el cuadro
var camara = Scene.Camera?.GameObject;
if ( camara.IsValid() )
{
var ajuste = camara.Components.Create<ColorAdjustments>();
ajuste.Saturation = 0.55f;
ajuste.Brightness = 0.8f;
ajuste.Contrast = 1.12f;
_compsAmbiente.Add( ajuste );
var vineta = camara.Components.Create<Vignette>();
vineta.Color = new Color( 0.1f, 0f, 0f );
vineta.Intensity = 0.45f;
_compsAmbiente.Add( vineta );
}
}
// ---------- Salida ----------
void Salir()
{
Log.Info( "🌅 Fase horror terminada: el mundo vuelve" );
foreach ( var (cielo, tinte) in _tintesOriginales )
{
if ( cielo.IsValid() ) cielo.Tint = tinte;
}
_tintesOriginales.Clear();
foreach ( var (luz, colores) in _lucesOriginales )
{
if ( luz.IsValid() )
{
luz.LightColor = colores.Luz;
luz.SkyColor = colores.Cielo;
}
}
_lucesOriginales.Clear();
foreach ( var comp in _compsAmbiente )
{
if ( comp.IsValid() ) comp.Destroy();
}
_compsAmbiente.Clear();
_niebla?.Destroy();
_niebla = null;
}
}