A UI razor component for the in-game Shop panel. It renders tabs for Guns, Charms, Gems, shows coin total, lists shop items filtered and ordered by availability/unlocked/upgradeable, and handles buying/upgrading items and some debug actions.
@namespace Sandbox
@using Sandbox;
@using Sandbox.UI;
@using System;
@using System.Linq;
@inherits Panel
@attribute [StyleSheet("ShopPanel.razor.scss")]
<root>
<div class="hide_button" onclick=@(() => Close())></div>
<div class="title_label">Shop</div>
<div class="coin_header">
<div class="coin_icon"></div>
<label class="coin_amount">@ProgressManager.GetCoins()</label>
</div>
<div class="debug_buttons">
<div class="debug_btn" onclick=@(() => DebugClearCategory(ShopItemCategory.Gun))>Clear Guns</div>
<div class="debug_btn" onclick=@(() => DebugClearCategory(ShopItemCategory.Charm))>Clear Charms</div>
<div class="debug_btn" onclick=@(() => DebugClearCategory(ShopItemCategory.Gem))>Clear Gems</div>
<div class="debug_btn" onclick=@(() => ProgressManager.AddCoins(1000))>+1000 Coins</div>
<div class="debug_btn" onclick=@(() => DebugClearCoins())>Clear Coins</div>
</div>
<div class="tabs">
@for(int t = 0; t < TabNames.Length; t++)
{
var tabIndex = t;
<div class="tab @(_selectedTab == tabIndex ? "active" : "")" onclick=@(() => SelectTab(tabIndex))>
@TabNames[tabIndex]
</div>
}
</div>
<div class="entries_container">
<div class="entries">
@foreach(var item in GetItemsForTab(_selectedTab))
{
var itemCapture = item;
<LoadoutItemPanel Item=@itemCapture IsShopMode=@true OnClick=@(() => OnBuyItem(itemCapture)) />
}
</div>
</div>
</root>
@code
{
static readonly string[] TabNames = { "Guns", "Charms", "Gems" };
// Maps tab index to ShopItemCategory
static readonly ShopItemCategory[] TabCategories = { ShopItemCategory.Gun, ShopItemCategory.Charm, ShopItemCategory.Gem };
static int _selectedTab = 0;
static bool _initialTabSet = false;
// Cache item lists per tab to avoid re-filtering every frame
List<List<ShopItemDef>> _tabItems;
bool _wasVisible = false;
protected override void OnAfterTreeRender(bool firstTime)
{
base.OnAfterTreeRender(firstTime);
var isVisible = Manager.Instance.ShowShopPanel;
if(firstTime || (!_wasVisible && isVisible))
RebuildItemLists();
if(firstTime && !_initialTabSet)
{
_selectedTab = BestStartingTab();
_initialTabSet = true;
}
_wasVisible = isVisible;
}
int BestStartingTab()
{
for(int i = 0; i < TabCategories.Length; i++)
if(GetItemsForTab(i).Count > 0)
return i;
return 0;
}
void RebuildItemLists()
{
_tabItems = TabCategories
.Select( cat =>
{
var all = ProgressManager.ShopItems.Where( x => x.Category == cat );
// Fully done = hide on next open: owned gun/charm, or owned+maxed gem
bool IsFullyDone(ShopItemDef x) =>
ProgressManager.IsItemOwned( x.Id ) &&
(cat != ShopItemCategory.Gem || ProgressManager.IsGemMaxed( x.Id ));
var remaining = all.Where( x => !IsFullyDone( x ) );
var available = remaining
.Where( x => !ProgressManager.IsItemOwned( x.Id ) && ProgressManager.IsItemUnlocked( x.Id ) )
.OrderBy( x => x.RequiredPurchases )
.ThenBy( x => x.Price )
.ToList();
var locked = remaining
.Where( x => !ProgressManager.IsItemOwned( x.Id ) && !ProgressManager.IsItemUnlocked( x.Id ) )
.OrderBy( x => ProgressManager.GetPurchasesNeeded( x.Id ) )
.ToList();
// Gems owned but not yet maxed — still upgradeable
var upgradeable = remaining
.Where( x => ProgressManager.IsItemOwned( x.Id ) )
.ToList();
return available.Concat( locked ).Concat( upgradeable ).ToList();
} )
.ToList();
}
List<ShopItemDef> GetItemsForTab(int tab)
{
if(_tabItems == null || tab < 0 || tab >= _tabItems.Count)
return new List<ShopItemDef>();
return _tabItems[tab];
}
void OnBuyItem(ShopItemDef item)
{
if ( !ProgressManager.IsItemUnlocked( item.Id ) ) return;
if(item.Category == ShopItemCategory.Gem && ProgressManager.IsItemOwned(item.Id))
{
// Owned gem: try to upgrade instead of buy
if(ProgressManager.UpgradeGem(item.Id))
{
Log.Info($"Upgraded {item.Name} to level {ProgressManager.GetGemLevel(item.Id) + 1}");
StateHasChanged();
}
return;
}
if(ProgressManager.IsItemOwned(item.Id)) return;
if(ProgressManager.GetCoins() < item.Price) return;
if(ProgressManager.BuyItem(item.Id))
{
Log.Info($"Purchased {item.Name} for {item.Price} coins");
StateHasChanged();
}
}
void SelectTab(int tabIndex)
{
if(_selectedTab == tabIndex) return;
_selectedTab = tabIndex;
StateHasChanged();
}
void DebugClearCoins()
{
ProgressManager.AddCoins( -ProgressManager.GetCoins() );
StateHasChanged();
}
void DebugClearCategory(ShopItemCategory category)
{
ProgressManager.DebugClearOwnedByCategory(category);
RebuildItemLists();
StateHasChanged();
}
void Close()
{
Manager.Instance.ShowShopPanel = false;
}
protected override int BuildHash()
{
return System.HashCode.Combine(
Manager.Instance.ShowShopPanel,
_selectedTab,
ProgressManager.StateVersion
);
}
}