A UI Razor component for the in-game shop. It renders lists of upgrades grouped as buyable, locked, or already bought, handles opening/closing via ShopBus events, allows purchasing upgrades, and computes a build hash for UI diffing.
@using System
@using System.Linq
@using System.Collections.Generic
@using Sandbox
@using Sandbox.UI
@using BrickJam.Upgrading
@namespace BrickJam.UI
@inherits Panel
<root class="@(open ? "open" : "")">
<div class="window">
<div class="header">
<div class="image"></div>
</div>
<div class="container">
<div class="container-content">
<div class="items">
@{
var player = Player.Local;
var possible = new List<Upgrade>();
var locked = new List<Upgrade>();
var bought = new List<Upgrade>();
if ( player is not null )
{
foreach ( var u in Upgrade.All )
{
if ( player.HasUpgrade( u.Identifier ) )
bought.Add( u );
else if ( CanBuy( u ) )
possible.Add( u );
else
locked.Add( u );
}
}
}
@foreach ( var upgrade in possible )
{
<div class="item">
<div class="info">
<div class="header">
@upgrade.Title
<div class="icon" style="background-image: url('@upgrade.Texture');"></div>
</div>
<div class="description">
@upgrade.Description
</div>
</div>
<div class="buy-button" @onclick=@( () => Buy( upgrade ) )>
<div class="buy">
BUY!
</div>
<div class="price">
[email protected]
</div>
</div>
</div>
}
@foreach ( var upgrade in locked )
{
<div class="item locked">
<div class="info">
<div class="header">
@upgrade.Title
<div class="icon locked" style="background-image: url('@upgrade.Texture');"></div>
</div>
<div class="description">
@upgrade.Description
</div>
</div>
<div class="buy-button locked">
<div class="price">
[email protected]
</div>
</div>
</div>
}
@foreach ( var upgrade in bought )
{
<div class="item locked">
<div class="info">
<div class="header">
@upgrade.Title
<div class="icon locked" style="background-image: url('@upgrade.Texture');"></div>
</div>
<div class="description">
@upgrade.Description
</div>
</div>
<div class="buy-button locked bought">
<div class="price">
BOUGHT
</div>
</div>
</div>
}
</div>
</div>
<div class="border-overlay"></div>
</div>
</div>
</root>
@code {
private bool open;
public Shop()
{
ShopBus.OnOpen += Show;
ShopBus.OnClose += Hide;
}
public override void OnDeleted()
{
base.OnDeleted();
ShopBus.OnOpen -= Show;
ShopBus.OnClose -= Hide;
}
private void Show() => open = true;
private void Hide() => open = false;
private bool CanBuy( Upgrade upgrade )
{
var player = Player.Local;
if ( player is null || player.HasUpgrade( upgrade.Identifier ) || upgrade.Price > player.Money )
return false;
return upgrade.Dependencies.All( dep => player.HasUpgrade( dep ) );
}
private void Buy( Upgrade upgrade )
{
if ( CanBuy( upgrade ) )
Player.Local.BuyUpgrade( upgrade.Identifier );
}
public override void Tick()
{
if ( open && Input.Pressed( "use" ) )
Hide();
}
protected override int BuildHash() => HashCode.Combine(
open, Player.Local?.Money ?? 0, Player.Local?.Upgrades.Count ?? 0 );
}
<style>
Shop {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
opacity: 0;
z-index: 1;
transition: opacity 0.3s ease;
&.open {
opacity: 1;
pointer-events: all;
}
.window {
width: 800px;
height: 900px;
background-color: #61577C;
border-right: #25213A solid 5px;
border-bottom: #25213A solid 5px;
padding: 8px;
flex-direction: column;
.header {
width: 100%;
height: 155px;
justify-content: space-between;
margin-bottom: 10px;
.image {
width: 100%;
height: 100%;
image-rendering: pixelated;
background-image: url("ui/shop/header.png");
background-size: 100%;
background-repeat: no-repeat;
}
}
.container {
width: 100%;
height: 100%;
image-rendering: pixelated;
background-image: url("ui/shop/background.png");
background-size: 200% 200%;
background-position: center;
.border-overlay {
z-index: 30;
position: absolute;
image-rendering: pixelated;
background-image: url("ui/shop/border.png");
background-size: 100%;
background-position: center;
pointer-events: none;
}
.container-content {
padding: 35px;
.items {
width: 100%;
height: 100%;
pointer-events: all;
justify-content: center;
align-items: center;
flex-wrap: wrap;
overflow: scroll;
.item {
flex-shrink: 0;
margin-bottom: 15px;
padding: 18px;
width: 100%;
height: 120px;
image-rendering: pixelated;
background-size: 100%;
background-position: center;
justify-content: space-between;
&.locked {
background-image: url("ui/shop/upgrade-locked.png");
}
&:not(.locked) {
background-image: url("ui/shop/upgrade.png");
}
.info {
width: 70%;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
.header {
height: 30px;
width: 100%;
font-size: 30px;
justify-content: flex-start;
.icon {
margin-left: 10px;
width: 30px;
height: 30px;
image-rendering: pixelated;
background-size: 100%;
background-position: center;
&.locked {
filter: brightness(0.2);
}
}
}
.description {
font-size: 20px;
}
}
.buy-button {
width: 30%;
height: 100%;
image-rendering: pixelated;
background-size: 100%;
background-position: center;
background-image: url("ui/shop/buy-button.png");
color: white;
justify-content: center;
align-items: center;
.buy {
font-size: 30px;
}
.price {
margin-left: 10px;
font-size: 20px;
}
&.locked {
.price {
color: #381313;
margin-left: 0;
font-size: 30px;
}
}
&.bought {
.price {
color: #000000;
margin-left: 0;
font-size: 30px;
}
}
&:not(.locked):hover {
background-image: url("ui/shop/buy-button.hover.png");
}
}
}
}
}
}
}
}
</style>