VendorMenu.cs
using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;
using SWB.Base;
using SWB.Player;
// The loadout board itself. Shows the local player's cash and two columns
// of stock (primary / secondary); clicking a line buys and equips it.
//
// Follows the same no-Razor PanelComponent pattern as swb_hud. Put this on
// the same GameObject as the Vendor and assign Vendor; it creates its own
// ScreenPanel, so no extra editor wiring.
public sealed class VendorMenu : PanelComponent
{
[Property]
public Vendor Vendor { get; set; }
Label _cashLabel;
Panel _primaryColumn;
Panel _secondaryColumn;
bool _wasOpen = false;
protected override void OnStart()
{
Components.GetOrCreate<ScreenPanel>();
Panel.StyleSheet.Load( "/VendorMenu.cs.scss" );
var header = Panel.Add.Panel( "header" );
header.Add.Label( "Loadout", "title" );
_cashLabel = header.Add.Label( "", "cash" );
// Holding Use again also closes it, but a visible way out matters
// when the cursor is already up and the menu is covering the
// screen - nothing on screen tells you the hold-to-close trick.
var close = header.Add.Label( "✕", "close" );
close.AddEventListener( "onmousedown", () => Vendor?.Close() );
var columns = Panel.Add.Panel( "columns" );
_primaryColumn = columns.Add.Panel( "column" );
_primaryColumn.Add.Label( "Primary", "columnTitle" );
_secondaryColumn = columns.Add.Panel( "column" );
_secondaryColumn.Add.Label( "Secondary", "columnTitle" );
}
protected override void OnUpdate()
{
if ( Vendor is null )
return;
Panel.SetClass( "show", Vendor.IsOpen );
// Rebuild only on open so prices/ownership reflect whatever the
// player walked up with, without churning panels every frame.
if ( Vendor.IsOpen && !_wasOpen )
Rebuild();
// Release the cursor so the menu can be clicked - see MenuInput for
// why this works on mouse-look rather than on Mouse.Visible.
if ( Vendor.IsOpen != _wasOpen )
MenuInput.SetMenuOpen( Vendor.IsOpen );
_wasOpen = Vendor.IsOpen;
if ( Vendor.IsOpen )
UpdateCash();
}
void UpdateCash()
{
var loadout = LocalLoadout();
_cashLabel.Text = loadout is null ? "" : $"${loadout.Cash}";
}
void Rebuild()
{
ClearEntries( _primaryColumn );
ClearEntries( _secondaryColumn );
foreach ( var entry in Vendor.Stock )
{
if ( entry is null || string.IsNullOrWhiteSpace( entry.ClassName ) )
continue;
var column = entry.Slot == LoadoutSlot.Primary ? _primaryColumn : _secondaryColumn;
AddEntry( column, entry );
}
}
void AddEntry( Panel column, VendorEntry entry )
{
var weapon = WeaponRegistry.Instance?.Get( entry.ClassName );
var displayName = weapon?.DisplayName ?? entry.ClassName;
var row = column.Add.Panel( "entry" );
if ( !string.IsNullOrWhiteSpace( weapon?.Icon ) )
row.Add.Image( weapon.Icon, "icon" );
row.Add.Label( displayName, "name" );
row.Add.Label( $"${entry.Cost}", "cost" );
row.AddEventListener( "onmousedown", () =>
{
var loadout = LocalLoadout();
if ( loadout is null )
return;
if ( !loadout.TryBuy( entry.ClassName, entry.Cost, entry.Slot ) )
return;
Rebuild();
UpdateCash();
} );
// Reflect current state: what you already have equipped, and what
// you simply can't afford right now.
var loadout = LocalLoadout();
if ( loadout is not null )
{
var equipped = entry.Slot == LoadoutSlot.Primary ? loadout.Primary : loadout.Secondary;
row.SetClass( "equipped", equipped == entry.ClassName );
row.SetClass( "unaffordable", !loadout.CanAfford( entry.Cost ) );
}
}
void ClearEntries( Panel column )
{
foreach ( var child in column.Children )
{
if ( child.HasClass( "entry" ) )
child.Delete();
}
}
PlayerLoadout LocalLoadout()
{
return PlayerBase.Local?.Components.Get<PlayerLoadout>();
}
}