Vendor.cs
using Sandbox;
using SWB.Shared;
using System.Collections.Generic;
// What sort of thing a vendor line sells. Weapons go into a loadout slot
// and come from the WeaponRegistry; gear doesn't, so it needs its own
// handling on both the buying and the displaying side.
public enum VendorItemKind
{
Weapon,
Armor
}
// One buyable line on the vendor's board. Configure these in the editor.
//
// For a weapon, ClassName has to match one registered in the WeaponRegistry
// (swb_colt, swb_scarh, swb_remington, swb_veresk, swb_l96a1, ...) and Slot
// decides which hand it goes in.
//
// For armour, ClassName and Slot are both ignored - there's only one vest
// and it isn't a weapon - so set DisplayName instead.
public sealed class VendorEntry
{
[Property] public VendorItemKind Kind { get; set; } = VendorItemKind.Weapon;
[Property] public string ClassName { get; set; }
[Property] public int Cost { get; set; } = 500;
[Property] public LoadoutSlot Slot { get; set; } = LoadoutSlot.Primary;
/// <summary>
/// What the shop calls this. Weapons fall back to their registry name,
/// so this only has to be filled in for gear.
/// </summary>
[Property] public string DisplayName { get; set; }
/// <summary>Is this line configured well enough to show at all?</summary>
public bool IsValid => Kind == VendorItemKind.Armor
|| !string.IsNullOrWhiteSpace( ClassName );
}
// The Hideout loadout vendor. Put this on a GameObject with a trigger
// Collider covering the counter area; walk in and hold Use to open the
// board, hold Use again (or walk away) to close it.
public sealed class Vendor : Component, Component.ITriggerListener, IInteractable
{
[Property]
public List<VendorEntry> Stock { get; set; } = new();
[Property]
public float HoldDuration { get; set; } = 0.4f;
bool _playerInRange = false;
float _holdProgress = 0f;
bool _wasHolding = false;
public bool IsOpen { get; private set; }
// Drives the on-screen prompt - see IInteractable.
public bool PlayerInRange => _playerInRange;
public float HoldFraction => HoldDuration <= 0f ? 0f : _holdProgress / HoldDuration;
public string PromptText => "Hold E to shop";
public bool SuppressPrompt => IsOpen;
public void OnTriggerEnter( Collider other )
{
if ( other.GameObject.Root.Tags.Has( "player" ) )
_playerInRange = true;
}
public void OnTriggerExit( Collider other )
{
if ( !other.GameObject.Root.Tags.Has( "player" ) )
return;
_playerInRange = false;
_holdProgress = 0f;
Close();
}
protected override void OnUpdate()
{
if ( !_playerInRange )
return;
var holding = Input.Down( InputButtonHelper.Use );
if ( holding )
{
_holdProgress += Time.Delta;
// Only toggle on the hold completing, then wait for the
// button to come back up - otherwise one long press would
// flap the menu open and shut every HoldDuration seconds.
if ( _holdProgress >= HoldDuration && !_wasHolding )
{
_wasHolding = true;
if ( IsOpen )
Close();
else
Open();
}
}
else
{
_holdProgress = 0f;
_wasHolding = false;
}
}
void Open()
{
IsOpen = true;
}
public void Close()
{
IsOpen = false;
}
}