using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 6 — "The imagery of the world changes to fit the choices you made."
/// (Jax's note on Andraya's pitch, made literal.) Watches the flags and swaps sprites,
/// colours, objects, or whole scene decorations when the story state changes.
///
/// SETUP
/// 1. Empty GameObject + this script (one per scene is plenty).
/// 2. Add a State per variation. Each has:
/// Require Flags – when this passes, the state is applied
/// Sprite Swaps – SpriteRenderer + the sprite to show
/// Colour Tints – SpriteRenderer + colour (fades over Transition Time)
/// Show Objects / Hide Objects – whole GameObjects
/// On Applied – anything else (music change, particle burst)
/// 3. Order matters like EndingSelector: the LAST matching state wins, so put the
/// most advanced/corrupted version at the bottom.
/// 4. Check Every Frame is off by default (it re-checks whenever a flag changes, which
/// is enough). Turn it on only if something else changes state without flags.
///
/// For Anabasis this is how the mansion gets darker as the ghost remembers more: one
/// state per memory recovered, each tinting the walls and swapping a few key props.
/// </summary>
public class WorldStateSwapper : MonoBehaviour
{
[System.Serializable]
public class SpriteSwap
{
public SpriteRenderer target;
public Sprite sprite;
}
[System.Serializable]
public class ColourTint
{
public SpriteRenderer target;
public Color colour = Color.white;
}
[System.Serializable]
public class State
{
public string name = "State";
[Tooltip("Flags required for this state (comma separated; ! to negate). Empty = base state.")]
public string requireFlags = "";
public List<SpriteSwap> spriteSwaps = new List<SpriteSwap>();
public List<ColourTint> colourTints = new List<ColourTint>();
public List<GameObject> showObjects = new List<GameObject>();
public List<GameObject> hideObjects = new List<GameObject>();
public UnityEvent onApplied;
}
[Tooltip("Checked top to bottom — the LAST match is applied. Base state first.")]
public List<State> states = new List<State>();
public float transitionTime = 0.5f;
public bool applyOnStart = true;
public bool checkEveryFrame = false;
public UnityEvent<string> OnStateChanged;
public State CurrentState { get; private set; }
private readonly Dictionary<SpriteRenderer, Color> tintFrom = new Dictionary<SpriteRenderer, Color>();
private readonly Dictionary<SpriteRenderer, Color> tintTo = new Dictionary<SpriteRenderer, Color>();
private float tintProgress = 1f;
void Start()
{
if (ChoiceFlagManager.Instance != null)
{
ChoiceFlagManager.Instance.OnFlagSet.AddListener(_ => Refresh());
ChoiceFlagManager.Instance.OnFlagCleared.AddListener(_ => Refresh());
}
if (applyOnStart) Refresh();
}
void Update()
{
if (checkEveryFrame) Refresh();
if (tintProgress < 1f && transitionTime > 0f)
{
tintProgress += Time.deltaTime / transitionTime;
foreach (var kv in tintTo)
{
if (kv.Key == null) continue;
Color from = tintFrom.TryGetValue(kv.Key, out var f) ? f : kv.Key.color;
kv.Key.color = Color.Lerp(from, kv.Value, Mathf.Clamp01(tintProgress));
}
}
}
/// <summary>Re-evaluate which state should be active. Called automatically on flag changes.</summary>
public void Refresh()
{
State match = null;
foreach (var s in states)
if (FlagsOk(s.requireFlags)) match = s; // last match wins
if (match == null || match == CurrentState) return;
Apply(match);
}
void Apply(State s)
{
CurrentState = s;
foreach (var swap in s.spriteSwaps)
if (swap.target != null && swap.sprite != null) swap.target.sprite = swap.sprite;
tintFrom.Clear();
tintTo.Clear();
foreach (var tint in s.colourTints)
{
if (tint.target == null) continue;
tintFrom[tint.target] = tint.target.color;
tintTo[tint.target] = tint.colour;
}
tintProgress = transitionTime > 0f ? 0f : 1f;
if (transitionTime <= 0f)
foreach (var kv in tintTo) if (kv.Key != null) kv.Key.color = kv.Value;
foreach (var o in s.showObjects) if (o != null) o.SetActive(true);
foreach (var o in s.hideObjects) if (o != null) o.SetActive(false);
s.onApplied?.Invoke();
OnStateChanged?.Invoke(s.name);
}
bool FlagsOk(string flags)
{
if (string.IsNullOrWhiteSpace(flags)) return true;
return ChoiceFlagManager.Instance == null || ChoiceFlagManager.Instance.HasAll(flags);
}
}