ModoHorror.cs

A Scene GameObjectSystem that toggles a horror visual mode. When DirectorRitmo.FaseHorror is true it tints skyboxes red, adjusts directional light colors, creates a red gradient fog object, and adds camera color adjustments and vignette components; it restores originals when exiting.

Native Interop
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;
	}
}