Demos/TarkovInventory/Loadout.cs
using System.Collections.Generic;
namespace Sandbox.TarkovInventory;
// A typed equip slot: id, display label, and the kinds it accepts.
public sealed record EquipSlot( string Id, string Label, IReadOnlySet<ItemKind> Accepts )
{
public bool CanHold( ItemKind kind ) => Accepts.Contains( kind );
}
// Distinguishes a rejected equip (Equipped false) from a successful one that
// displaced nothing (Equipped true, Displaced null).
public readonly record struct EquipResult( bool Equipped, string? Displaced );
// Pure equip model: a fixed set of typed slots, each holding at most one item key.
// Keys are item ids (string), matching Grid<string>; kind is supplied per call so
// the model stays decoupled from StashItem.
public sealed class Loadout
{
readonly Dictionary<string, EquipSlot> _slots = new();
readonly List<EquipSlot> _order = new();
readonly Dictionary<string, string> _occ = new(); // slotId -> item key; SlotOf reverse-scans (fine for small loadouts)
public Loadout( IEnumerable<EquipSlot> slots )
{
foreach ( var s in slots )
{
if ( !_slots.TryAdd( s.Id, s ) )
throw new System.ArgumentException( $"duplicate slot id '{s.Id}'", nameof( slots ) );
_order.Add( s );
}
}
public IReadOnlyList<EquipSlot> Slots => _order;
public EquipSlot? Slot( string slotId ) => _slots.TryGetValue( slotId, out var s ) ? s : null;
public string? Occupant( string slotId ) => _occ.TryGetValue( slotId, out var id ) ? id : null;
public string? SlotOf( string itemKey )
{
foreach ( var kv in _occ )
if ( kv.Value == itemKey ) return kv.Key;
return null;
}
public bool CanEquip( string slotId, ItemKind kind ) =>
_slots.TryGetValue( slotId, out var s ) && s.CanHold( kind );
// Swap-on-occupied. Rejected (unknown slot or type mismatch) leaves the slot unchanged.
public EquipResult Equip( string slotId, string itemKey, ItemKind kind )
{
if ( !CanEquip( slotId, kind ) ) return new EquipResult( false, null );
if ( _occ.TryGetValue( slotId, out var cur ) )
{
if ( cur == itemKey ) return new EquipResult( true, null ); // already there: no-op
_occ[slotId] = itemKey;
return new EquipResult( true, cur ); // swap: evict cur
}
_occ[slotId] = itemKey;
return new EquipResult( true, null );
}
public string? Unequip( string slotId )
{
if ( _occ.TryGetValue( slotId, out var id ) ) { _occ.Remove( slotId ); return id; }
return null;
}
}