A Razor UI component for the BombWheel HUD in a 2D Chain Reaction game. It renders the player's bomb slots, selection badges, jam overlay, a detonate button, handles keyboard slot shortcuts, listens for hand and game state changes, and updates a screen-shake CSS class.
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits PanelComponent
<root>
@* Screen Shake the whole root*@
@if ( Hand != null && Game?.IsRunning == true )
{
<div class="mini-hand @_currentShake">
@for ( int i = 0; i < Hand.Hand.Count; i++ )
{
int slot = i;
bool placed = Hand.IsSlotPlaced(slot);
bool sel = Hand.SelectedSlot == slot && !placed;
var bomb = Hand.BombAt(slot);
<div class="mini-card @(placed?"used":sel?"active":"")"
onclick=@(()=>Hand.SelectSlot(slot))>
@* Keyboard hint badge top-left *@
@if(!placed)
{
<div class="kb-hint">@(slot + 1)</div>
}
<div class="mini-icon">@Icon(bomb)</div>
<div class="mini-name">@ShortName(bomb)</div>
@* Jammed slot overlay *@
@if (Hand?.IsSlotJammed(slot) == true)
{
<div class="mini-jam-overlay">🔒</div>
}
@if(placed)
{
int n = GetNum(slot);
<div class="mini-badge">@n</div>
}
</div>
}
@* Detonate button *@
<div class="mini-det-wrap @(CanDet?"ready":"off")" onclick=@OnDet>
<div class="mini-det">
<div class="mini-det-icon">💥</div>
<div class="mini-det-label">SPACE</div>
</div>
</div>
</div>
}
</root>
@code {
BombHandSystem Hand => ChainReactionGame.Instance?.Components.Get<BombHandSystem>();
ChainReactionGame Game => ChainReactionGame.Instance;
bool CanDet => Game?.IsRunning == true && Hand?.AnyPlaced == true && !(Hand?.IsDetonating ?? true);
bool _sub = false;
protected override void OnTreeFirstBuilt() => TrySub();
protected override void OnUpdate()
{
if (!_sub) TrySub();
// Keyboard shortcuts: 1/2/3 to select bomb slots
if (Game?.IsRunning == true && !(Hand?.IsDetonating ?? true))
{
if (ChainReactionGame.Instance?.IsBombJamActive == true)
{
// Allow all slots EXCEPT the jammed one
for (int s = 0; s < (Hand?.HandSize ?? 0); s++)
{
if (Hand?.IsSlotJammed(s) == true) continue;
if (s == 0 && Input.Pressed("slot1")) Hand?.SelectSlot(0);
if (s == 1 && Input.Pressed("slot2")) Hand?.SelectSlot(1);
if (s == 2 && Input.Pressed("slot3")) Hand?.SelectSlot(2);
if (s == 3 && Input.Pressed("Slot4")) Hand?.SelectSlot(3);
}
}
else
{
if(Input.Pressed("slot1")) Hand?.SelectSlot(0);
if(Input.Pressed("slot2")) Hand?.SelectSlot(1);
if(Input.Pressed("slot3")) Hand?.SelectSlot(2);
if(Input.Pressed("Slot4")) Hand?.SelectSlot(3);
//if(Input.Pressed("Slot5")) Hand?.SelectSlot(4);
}
}
// Screen Shake
var shakeClass = ScreenShaker.Instance?.ShakeClass ?? "";
if (shakeClass != _currentShake)
{
_currentShake = shakeClass;
StateHasChanged();
}
}
string _currentShake = "";
void OnDet()
{
if (!CanDet) return;
// Fire tutorial advance before detonating
TutorialOverlay.Instance?.TryAdvance(TutorialOverlay.TutorialTrigger.Detonated);
Game?.Detonate();
}
void TrySub()
{
var h = Hand; var g = Game;
if (h is null || g is null) return;
if (_sub) return;
h.OnHandChanged += StateHasChanged;
g.OnStateChanged += StateHasChanged;
_sub = true;
StateHasChanged();
}
int GetNum(int slot)
{
var o = Hand?.PlacementOrder;
if (o is null) return 0;
int idx = o.IndexOf(slot);
return idx >= 0 ? idx + 1 : 0;
}
protected override int BuildHash() => System.HashCode.Combine(
Hand?.SelectedSlot, Hand?.PlacedSlots.Count, Hand?.HasSwapped, Hand?.Hand.Count, CanDet, _currentShake);
static string Icon(GridManager.BombType t) => t switch {
GridManager.BombType.Cross => "✛", GridManager.BombType.Sniper => "↑",
GridManager.BombType.Diagonal => "✕", GridManager.BombType.Square => "■",
GridManager.BombType.Chain => "⛓", _ => "?" };
static string ShortName(GridManager.BombType t) => t switch {
GridManager.BombType.Cross => "Cross", GridManager.BombType.Sniper => "Sniper",
GridManager.BombType.Diagonal => "Diag", GridManager.BombType.Square => "Square",
GridManager.BombType.Chain => "Chain", _ => "?" };
}