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.
@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);
}