Game/2D/UI/SurvivalTutorial.razor

A Razor UI component for a survival tutorial overlay. It displays multi-step instructional cards, a small demo grid animation for one step, handles advancing steps and plays a UI sound, and toggles the tutorial active state.

NetworkingFile Access
@using System;
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace Sandbox

<root>
    @if(_active)
    {
        <div class="survival-tut-bg"></div>
        <div class="survival-tut-card">

            <div class="tut-step-count">@(_step + 1) / @_steps.Length</div>
            <div class="survival-tut-title">@_steps[_step].Title</div>
            <div class="survival-tut-text">@_steps[_step].Text</div>

            @if(_steps[_step].ShowGridDemo)
            {
                <div class="survival-demo-grid">
                    ...
                </div>
                <div class="survival-demo-caption">@_demoCaption</div>
            }

            <button class="survival-tut-close" onclick=@NextStep>
                @(_step == _steps.Length - 1 ? "▶ Let's Survive!" : "Got it →")
            </button>
        </div>
    }
</root>

@code {
    public static SurvivalTutorial Instance { get; private set; }
    public bool IsActive => _active;

    bool _active = false;
    int _step = 0;

    struct SurvivalTutStep
    {
        public string Title;
        public string Text;
        public bool ShowGridDemo;
    }

    static readonly SurvivalTutStep[] _steps = new SurvivalTutStep[]
    {
        new SurvivalTutStep {
            Title = "Welcome to Survival! ⚡",
            Text  = "No waves here — chests spawn continuously and slide downward. Your goal: last as long as possible.",
            ShowGridDemo = false
        },
        new SurvivalTutStep {
            Title = "It Gets Faster... 🔥",
            Text  = "Chests spawn every couple seconds at first. After about a minute, spawn rate speeds up and more chests appear at once. Stay sharp — it never really stops.",
            ShowGridDemo = false
        },
        new SurvivalTutStep {
            Title = "Block the Slide! 🛡️",
            Text  = "Here's the key trick: a placed bomb blocks a chest from sliding into its column. Use this to buy time and set up bigger chain reactions before detonating.",
            ShowGridDemo = true
        },
        new SurvivalTutStep {
            Title = "Survive as Long as You Can 💀",
            Text  = "One chest reaching the bottom row ends your run. Place smart, chain big, and see how long you can hold on!",
            ShowGridDemo = false
        },
    };

    // ── Mini grid demo animation state ──
    float _demoTimer = 0f;
    int _demoPhase = 0; // 0=chest falling, 1=bomb placed blocks it, 2=hold
    string _demoCaption = "A chest is about to slide down...";

    protected override void OnStart()
    {
        Instance = this;
        _active = false;
    }

    public void Activate()
    {
        _step = 0;
        _active = true;
        _demoPhase = 0;
        _demoTimer = 0f;
        StateHasChanged();
    }

    protected override void OnUpdate()
    {
        if (!_active || !_steps[_step].ShowGridDemo) return;

        _demoTimer += Time.Delta;

        // Simple 3-phase loop: chest falls (0-1.2s) -> bomb blocks it (1.2-2.4s) -> hold (2.4-3.6s) -> reset
        if (_demoTimer < 1.2f)
        {
            _demoPhase = 0;
            _demoCaption = "A chest slides down each cycle...";
        }
        else if (_demoTimer < 2.4f)
        {
            _demoPhase = 1;
            _demoCaption = "...but a placed bomb blocks it from moving further!";
        }
        else if (_demoTimer < 3.6f)
        {
            _demoPhase = 2;
            _demoCaption = "Now you have time to plan your chain reaction.";
        }
        else
        {
            _demoTimer = 0f;
        }
        StateHasChanged();
    }

    // Simple fixed layout: chest at (0,1) falling toward bomb at (2,1)
    string GetDemoCellClass(int r, int c)
    {
        if (c != 1) return "empty";

        if (_demoPhase == 0)
        {
            // chest animates from row 0 toward row 1
            if (r == 0) return "chest";
            if (r == 2) return "bomb";
            return "empty";
        }
        else
        {
            // blocked — chest sits right above the bomb, doesn't pass
            if (r == 1) return "chest";
            if (r == 2) return "bomb";
            return "empty";
        }
    }

    string GetDemoCellIcon(int r, int c)
    {
        var cls = GetDemoCellClass(r, c);
        return cls switch {
            "chest" => "🎁",
            "bomb"  => "💣",
            _ => ""
        };
    }

    void NextStep()
    {
        _step++;
        Sound.Play("UI_Button");

        if (_step >= _steps.Length)
		{
			_active = false;
			ChainReactionGame.Instance?.UnfreezeSurvivalSpawn(); // new public method
			StateHasChanged();
			return;
		}

        _demoTimer = 0f;
        _demoPhase = 0;
        StateHasChanged();
    }

    protected override int BuildHash() =>
        System.HashCode.Combine(_active, _step, _demoPhase);
}