Player/PlayerPawn.Inventory.cs
public sealed partial class PlayerPawn
{
	// -------------------------------------------------------------------------
	// Inventory
	// -------------------------------------------------------------------------

	[Sync( SyncFlags.FromHost )] public NetList<Weapon> Weapons { get; private set; } = new();
	// ActiveWeapon is the last-used weapon, used for UI display only (owner-written)
	[Sync, Change( nameof( OnActiveWeaponChanged ) )] public Weapon ActiveWeapon { get; private set; }

	private int _activeWeaponInput;

	private void SetUpWeapons()
	{
		ClearWeapons();

		AddWeapon( ResourceLibrary.Get<PrefabFile>( _primaryWeapon ), makeActive: true );
		if ( PilotGame.Gamemode != FPGameMode.Instagib )
			AddWeapon( ResourceLibrary.Get<PrefabFile>( _secondaryWeapon ) );
	}

	public void AddWeapon( PrefabFile prefab, bool makeActive = false )
	{
		if ( prefab == null ) return;
		var weaponGo = SceneUtility.GetPrefabScene( prefab )?.Clone();
		if ( weaponGo == null ) return;

		weaponGo.SetParent( GameObject, false );
		weaponGo.Enabled = true; // set enabled before NetworkSpawn so clients receive correct initial state
		weaponGo.NetworkSpawn(); // host-owned so host can always call [Rpc.Broadcast] from weapon components

		var weapon = weaponGo.Components.Get<Weapon>( FindMode.EverythingInSelfAndDescendants );
		if ( weapon == null ) { weaponGo.Destroy(); return; }

		AddWeapon( weapon, makeActive );
	}

	public void AddWeapon( Weapon weapon, bool makeActive = false )
	{
		Weapons.Add( weapon );
		weapon.Player = this;
		weapon.GameObject.Enabled = true; // ensure weapon is always active

		if ( makeActive )
			SetActiveWeapon( weapon );
	}

	public void RemoveWeapon( Weapon weapon )
	{
		if ( ActiveWeapon == weapon )
			SetActiveWeapon( null );

		Weapons.Remove( weapon );
		weapon.GameObject.Destroy();
	}

	public void SetActiveWeapon( Weapon weapon )
	{
		if ( ActiveWeapon == weapon ) return;
		ActiveWeapon = weapon;
	}

	private void OnActiveWeaponChanged( Weapon before, Weapon after ) { }

	public Weapon GetSlot( int slot )
	{
		return slot >= 0 && slot < Weapons.Count ? Weapons[slot] : null;
	}

	public void SwitchActiveSlot( int direction, bool loop = false )
	{
		if ( Weapons.Count == 0 ) return;

		var index = Weapons.IndexOf( ActiveWeapon );
		index += direction;

		if ( loop )
		{
			index = ((index % Weapons.Count) + Weapons.Count) % Weapons.Count;
		}
		else
		{
			index = index.Clamp( 0, Weapons.Count - 1 );
		}

		SetActiveWeapon( GetSlot( index ) );
	}

	public void SimulateInventory()
	{
		// Auto-select first weapon once weapons have replicated, or if current weapon was destroyed
		if ( (ActiveWeapon == null || !ActiveWeapon.IsValid()) && Weapons.Count > 0 )
			SetActiveWeapon( GetSlot( 0 ) );

		// Use child component iteration rather than Weapons NetList to avoid
		// race conditions where NetList updates arrive before weapon GOs on clients
		foreach ( var w in Components.GetAll<Weapon>( FindMode.InChildren ) )
			w.Simulate();
	}

	public void FrameSimulateInventory()
	{
		foreach ( var w in Components.GetAll<Weapon>( FindMode.InChildren ) )
			w.FrameSimulate();
	}

	private void BuildInventoryInput()
	{
		if ( Input.Pressed( "Slot1" ) ) SetActiveWeapon( GetSlot( 0 ) );
		if ( Input.Pressed( "Slot2" ) ) SetActiveWeapon( GetSlot( 1 ) );
	}

	public void ClearWeapons()
	{
		SetActiveWeapon( null );
		foreach ( var w in Weapons.ToList() )
			w?.GameObject?.Destroy();
		Weapons.Clear();
	}
}