Player/Player.Upgrades.cs

Player partial class methods for managing player upgrades. Declares a networked NetList<string> Upgrades, checks ownership, grants upgrades on the host, and handles host-validated purchase RPCs with dependency, price, and achievement checks.

Networking
using System.Linq;
using Sandbox;
using BrickJam.Upgrading;

namespace BrickJam;

public sealed partial class Player
{
	/// <summary>
	/// Owned upgrade identifiers. Host-authoritative and replicated. Replaces the legacy
	/// <c>[Net, Change] IList&lt;string&gt; Upgrades</c>.
	/// </summary>
	[Sync( SyncFlags.FromHost )] public NetList<string> Upgrades { get; set; } = new();

	public bool HasUpgrade( string identifier ) => Upgrades.Contains( identifier );

	/// <summary>Grant an upgrade unconditionally (host-side).</summary>
	public void GiveUpgrade( string identifier )
	{
		if ( Networking.IsHost && Upgrade.Exists( identifier ) && !Upgrades.Contains( identifier ) )
			Upgrades.Add( identifier );
	}

	/// <summary>
	/// Purchase request from the owning client's shop UI; validated and applied on the host.
	/// Replaces the legacy <c>[ConCmd.Server] BuyUpgrade</c>.
	/// </summary>
	[Rpc.Host]
	public void BuyUpgrade( string identifier )
	{
		var upgrade = Upgrade.Find( identifier );
		if ( upgrade is null )
		{
			Log.Warning( $"Tried to buy unknown upgrade {identifier}" );
			return;
		}

		if ( upgrade.Dependencies.Any( dep => !HasUpgrade( dep ) ) )
		{
			Log.Warning( $"Missing dependency for upgrade {identifier}" );
			return;
		}

		if ( upgrade.Price > Money || HasUpgrade( identifier ) )
			return;

		SetMoney( Money - upgrade.Price );
		Upgrades.Add( identifier );

		// Fully Loaded: this purchase completed the full shop catalogue.
		if ( Upgrade.All.Any() && Upgrade.All.All( u => HasUpgrade( u.Identifier ) ) )
			TrackAchievement( GameStats.AchFullyLoaded );
	}
}