Game/2D/Gameplay/MilestoneOverlay.razor

UI component (Razor) for milestone and event overlays in a 2D game. Displays an animated icon, title, subtitle, description, progress bar, plays sounds, queues multiple messages, pauses/resumes the game timer, and auto-dismisses after a duration.

File Access
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace Sandbox

<root>
    @* Milestone messages *@
    @if (_active && _milestone != null)
    {
        <div class="ms-backdrop" onclick=@Dismiss>
            <div class="ms-box">

                @* Animated icon *@
                <div class="ms-icon" style="color:@_milestone.AccentColor;">
                    @_milestone.Icon
                </div>

                @* Subtitle — wave number *@
                <div class="ms-subtitle" style="color:@_milestone.AccentColor;">
                    @_milestone.Subtitle
                </div>

                @* Big title *@
                <div class="ms-title">@_milestone.Title</div>

                @* Description — what changes *@
                <div class="ms-desc">@_milestone.Description</div>

                @* Auto-dismiss progress bar *@
                <div class="ms-progress-track">
                    <div class="ms-progress-fill" 
                         style="animation-duration:@_duration.ToString("F1")s;">
                    </div>
                </div>

                <div class="ms-hint">TAP TO CONTINUE</div>
            </div>
        </div>
    }
</root>

@code {
    public static MilestoneOverlay Instance { get; private set; }

    bool _active = false;
    WaveMilestone _milestone = null;
    float _duration = 4f;
    float _shownAt = 0f;
	public bool IsActive => _active;
    protected override void OnStart() => Instance = this;

    protected override void OnUpdate()
    {
        if (!_active) return;

        // Auto-dismiss after duration
        if (Time.Now - _shownAt >= _duration)
            Dismiss();
    }

    // Called from ChainReactionGame when wave changes
    public void ShowIfMilestone(int wave)
    {
        var milestone = MilestoneCatalog.GetForWave(wave);
        if (milestone == null) return;

        Sound.Play("replaybutton_ha");
        ShowMilestone(milestone, 4f);
    }

    void Dismiss()
    {
         if (!_active) return;
        _active    = false;
        _milestone = null;

        if (_queue.Count > 0)
        {
            // Show next queued milestone
            var (next, dur) = _queue.Dequeue();
            _milestone = next;
            _duration  = dur;
            _active    = true;
            _shownAt   = Time.Now;
            // Timer stays paused
            StateHasChanged();
            return;
        }

        // Nothing queued — resume timer
        ChainReactionGame.Instance?.ResumeTimer();
        StateHasChanged();
    }


    // Another type of message (i.e : lost a heart -> message card if we have a heart consumable or pet's heart)
    public void ShowMessage(string message, int wave, float duration = 2.5f)
    {
        ShowMilestone(new WaveMilestone {
            Icon        = "💔",
            Title       = message,
            Subtitle    = wave > 0 ? $"Wave {wave}" : "",
            Description = "",
            AccentColor = "rgba(248,113,113,0.90)"
        }, duration);
    }
    string _customMessage = "";

    protected override int BuildHash() =>
        System.HashCode.Combine(_active, _milestone?.Title ?? "", _queue.Count);


	// ------------------------------------------------------------------- //
	// --------------------------- RANDOM EVENTS ------------------------- //
	// ------------------------------------------------------------------- //
	public void ShowEvent(RandomEventType type, int wave)
	{
		ShowMilestone(BuildEventMilestone(type, wave), 3.5f);
	}

    // Queue for multiple overlays
    public Queue<(WaveMilestone milestone, float duration)> _queue = new();
    void ShowMilestone(WaveMilestone milestone, float duration)
    {

        if (_active)
        {
            // Queue it instead of showing immediately
            _queue.Enqueue((milestone, duration));
            ChainReactionGame.Instance?.PauseTimer(); 
            Log.Info($"Queued: {milestone.Title}");

            return;
        }

        ChainReactionGame.Instance?.PauseTimer();

        _milestone = milestone;
        _duration  = duration;
        _active    = true;
        _shownAt   = Time.Now;

        Sound.Play("UI_Button");
        StateHasChanged();
    }
    public void ShowBoss(int wave, int hp, int moveCooldown)
    {
        string moveText = moveCooldown == 1 ? "moves every wave"
            : $"moves every {moveCooldown} waves";

        ShowMilestone(new WaveMilestone {
            Icon = "👑",
            Title = "BOSS INCOMING!",
            Subtitle = $"Wave {wave}",
            Description = $"{hp} HP · {moveText} · Multipliers capped while alive.",
            AccentColor = "rgba(251,191,36,0.90)"
        }, 4.5f);
        // PauseTimer is handled inside ShowMilestone now
        Sound.Play("boss_chest_spawn");
    }

	WaveMilestone BuildEventMilestone(RandomEventType type, int wave) => type switch
	{
		RandomEventType.BonusWave    => new WaveMilestone { Icon = "🎁", Title = "BONUS WAVE!",
			Subtitle = $"Wave {wave}", Description = "This wave: 3× score, no lives can be lost.",
			AccentColor = "rgba(52,211,153,0.90)" },

		RandomEventType.FragmentRain => new WaveMilestone { Icon = "💠", Title = "FRAGMENT RAIN!",
			Subtitle = $"Wave {wave}", Description = "+15 fragments added to your total instantly.",
			AccentColor = "rgba(96,165,250,0.90)" },

		RandomEventType.TimerFreeze  => new WaveMilestone { Icon = "❄️", Title = "TIME FREEZE!",
			Subtitle = $"Wave {wave}", Description = "Timer is frozen for 8 seconds this wave.",
			AccentColor = "rgba(147,197,253,0.90)" },

		RandomEventType.DoubleDrop   => new WaveMilestone { Icon = "✨", Title = "DOUBLE DROP!",
			Subtitle = $"Wave {wave}", Description = "All chests reward double coins this wave.",
			AccentColor = "rgba(251,191,36,0.90)" },

		RandomEventType.ChestBonanza => new WaveMilestone { Icon = "🎁", Title = "CHEST BONANZA!",
			Subtitle = $"Wave {wave}", Description = "3 extra chests spawned this wave!",
			AccentColor = "rgba(52,211,153,0.90)" },

		RandomEventType.GoldRush     => new WaveMilestone { Icon = "💎", Title = "GOLD RUSH!",
			Subtitle = $"Wave {wave}", Description = "Only gem chests this wave. All worth 5× score.",
			AccentColor = "rgba(251,191,36,0.90)" },

		RandomEventType.CursedWave   => new WaveMilestone { Icon = "💀", Title = "CURSED WAVE",
			Subtitle = $"Wave {wave}", Description = "All chests require one extra hit to destroy.",
			AccentColor = "rgba(239,68,68,0.90)" },

		RandomEventType.SpeedRound   => new WaveMilestone { Icon = "⚡", Title = "SPEED ROUND!",
			Subtitle = $"Wave {wave}", Description = "Timer is halved this wave. Move fast!",
			AccentColor = "rgba(251,191,36,0.90)" },

		RandomEventType.GhostBombs   => new WaveMilestone { Icon = "👻", Title = "GHOST BOMBS!",
			Subtitle = $"Wave {wave}", Description = "Your bombs fire twice this wave.",
			AccentColor = "rgba(167,139,250,0.90)" },

		RandomEventType.BossRush     => new WaveMilestone { Icon = "👑", Title = "BOSS RUSH!",
			Subtitle = $"Wave {wave}", Description = "Three mini-bosses instead of normal chests.",
			AccentColor = "rgba(239,68,68,0.90)" },

		RandomEventType.MysteryWave  => new WaveMilestone { Icon = "❓", Title = "MYSTERY WAVE",
			Subtitle = $"Wave {wave}", Description = "Chest types are hidden until you hit them.",
			AccentColor = "rgba(139,92,246,0.90)" },

		RandomEventType.BombJam      => new WaveMilestone { Icon = "🔧", Title = "BOMB JAM",
			Subtitle = $"Wave {wave}", Description = "One of your bomb slots is locked this wave.",
			AccentColor = "rgba(239,68,68,0.90)" },

		RandomEventType.MirrorWave => new WaveMilestone { 
			Icon = "🪞", Title = "MIRROR WAVE!",
			Subtitle = $"Wave {wave}", 
			Description = "The grid is mirrored horizontally this wave. Left is right, right is left!",
			AccentColor = "rgba(147,197,253,0.90)" 
		},
        RandomEventType.Wave100 => new WaveMilestone {
            Wave = 100,
            Title = "CENTURY",
            Description = "Wave 100 — you're in the top 1% of players. Legendary.",
        },
        RandomEventType.Wave200 => new WaveMilestone {
        Wave = 200,
            Title = "UNSTOPPABLE",  
            Description = "Wave 200. Nobody gets here. You're something else.",
        },
		

		_ => new WaveMilestone { Icon = "⚡", Title = "SPECIAL WAVE!",
			Subtitle = $"Wave {wave}", Description = "Something unexpected is happening...",
			AccentColor = "rgba(167,139,250,0.90)" }
	};
}