UI/ShipSelect/ShipList.razor
@using Sandbox.UI
@inherits Panel
<style>
ShipList {
position: absolute;
width: 100%;
height: 100%;
z-index: 500;
justify-content: center;
align-content: center;
align-items: stretch;
backdrop-filter-blur: 20px;
background-color: rgba(0, 0, 0, 0.75);
opacity: 0;
transition: all 0.2s ease;
&.open {
opacity: 1;
pointer-events: all;
}
/* ── Left info panel ─────────────────────────────── */
.infopanel {
flex-direction: column;
width: 700px;
min-width: 700px;
padding: 60px 48px;
background-color: rgba(0,0,0,0.3);
border-right: 2px solid rgba(255,255,255,0.07);
justify-content: flex-start;
.fp4label {
font-family: "Wallpoet";
font-size: 34px;
color: #35ae8f;
margin-bottom: 4px;
}
.screentitle {
font-family: "Wallpoet";
font-size: 64px;
color: white;
margin-bottom: 32px;
}
.shiplogo {
width: 130px;
height: 130px;
margin-bottom: 20px;
}
.shipname {
font-family: "Rubik";
font-size: 52px;
color: white;
font-weight: 700;
margin-bottom: 4px;
white-space: nowrap;
}
.shipclass {
font-family: "Rubik";
font-size: 26px;
color: rgba(0,255,255,0.7);
margin-bottom: 20px;
text-transform: uppercase;
}
.sectionlabel {
font-family: "Wallpoet";
font-size: 22px;
color: rgba(255,255,255,0.4);
margin-top: 24px;
margin-bottom: 10px;
text-transform: uppercase;
}
.description {
font-family: "Rubik";
font-size: 22px;
color: rgba(255,255,255,0.65);
margin-bottom: 24px;
white-space: normal;
}
.statsblock {
flex-direction: column;
margin-bottom: 24px;
}
.weaponrow {
flex-direction: column;
margin-bottom: 14px;
.weplabel {
font-family: "Wallpoet";
font-size: 20px;
color: rgba(255,255,255,0.4);
}
.wepname {
font-family: "Rubik";
font-size: 28px;
color: white;
}
}
}
/* ── Centre portrait ─────────────────────────────── */
.portrait {
flex-grow: 1;
align-items: center;
justify-content: center;
ShipPortrait3d {
width: 100%;
height: 100%;
}
}
/* ── Right class selector ────────────────────────── */
.classpanel {
flex-direction: column;
width: 380px;
padding: 60px 28px;
background-color: rgba(0,0,0,0.3);
border-left: 2px solid rgba(255,255,255,0.07);
justify-content: center;
ShipEntries {
flex-direction: column;
gap: 48px;
}
}
}
</style>
<root class="@(_open ? "open" : "")">
<div class="infopanel">
<label class="fp4label">FP4</label>
<label class="screentitle">ship select</label>
@if ( _hovered != null )
{
@if ( !string.IsNullOrEmpty( _hovered.ShipLogo ) )
{
<img class="shiplogo" src="@_hovered.ShipLogo" />
}
<label class="shipname">@_hovered.ShipName</label>
<label class="shipclass">@_hovered.shipType</label>
@if ( !string.IsNullOrEmpty( _hovered.Description ) )
{
<label class="sectionlabel">about</label>
<label class="description">@_hovered.Description</label>
}
<label class="sectionlabel">stats</label>
<div class="statsblock">
<StatBars Type="HEALTH" Value="@(_hovered.Health / 200f)" />
<StatBars Type="SHIELD" Value="@(_hovered.Shield / 100f)" />
<StatBars Type="SPEED" Value="@(_hovered.MaxSpeed / 25f)" />
<StatBars Type="BOOST" Value="@((_hovered.BoostAmount * _hovered.BoostRegenRate) / 2000f)" />
</div>
@if ( !string.IsNullOrEmpty( _hovered.PrimaryWeaponName ) || !string.IsNullOrEmpty( _hovered.SecondaryWeaponName ) )
{
<label class="sectionlabel">weapons</label>
@if ( !string.IsNullOrEmpty( _hovered.PrimaryWeaponName ) )
{
<div class="weaponrow">
<label class="weplabel">PRIMARY</label>
<label class="wepname">@_hovered.PrimaryWeaponName</label>
</div>
}
@if ( !string.IsNullOrEmpty( _hovered.SecondaryWeaponName ) )
{
<div class="weaponrow">
<label class="weplabel">SECONDARY</label>
<label class="wepname">@_hovered.SecondaryWeaponName</label>
</div>
}
}
}
</div>
<div class="portrait">
@if ( _hovered != null && !string.IsNullOrEmpty( _hovered.ShipPrefab ) )
{
<ShipPortrait3d PrefabPath="@_hovered.ShipPrefab" />
}
</div>
<div class="classpanel">
<ShipEntries Ships="@_shipsByType" CurrentShip="@_currentShip"
OnSelected="@SelectShip" OnHovered="@HoverShip" />
</div>
</root>
@code
{
private bool _open;
private ShipData _hovered;
private ShipData _currentShip;
private Dictionary<ShipData.ShipType, List<ShipData>> _shipsByType = new();
protected override void OnAfterTreeRender( bool firstTime )
{
if ( !firstTime ) return;
var all = ResourceLibrary.GetAll<ShipData>().ToList();
_shipsByType = all
.GroupBy( s => s.shipType )
.ToDictionary( g => g.Key, g => g.ToList() );
_hovered = all.FirstOrDefault();
}
private void HoverShip( ShipData ship )
{
if ( ship == null || ship == _hovered ) return;
_hovered = ship;
}
private void SelectShip( ShipData ship )
{
if ( ship == null ) return;
_hovered = ship;
var pawn = LocalPlayer.Pawn;
pawn?.RequestNewShip( ship.ResourcePath );
_currentShip = ship;
_open = false;
StateHasChanged();
}
public override void Tick()
{
base.Tick();
if ( PilotGame.Gamemode == FPGameMode.Instagib ) return;
var pawn = LocalPlayer.Pawn;
// Keep current ship in sync
if ( pawn?.Data != null ) _currentShip = pawn.Data;
// Show only on first join (never selected a ship) or while menu key toggled
var firstJoin = pawn != null && !pawn.IsAlive && !pawn.HasSelectedShip;
if ( Input.Pressed( "menu" ) ) _open = !_open;
if ( firstJoin ) _open = true;
SetClass( "open", _open );
}
protected override int BuildHash() => HashCode.Combine( _open, _hovered, _currentShip );
}