VendingMachine.cs

A vending machine component for the local player. Tracks the nearest machine within InteractionRange, binds slot1 to buy a speed drink and slot2/use to buy a medkit, spends MetaProgression credits, plays a sound and camera shake.

Rough Code
using Sandbox;
using System;
using System.Linq;

public sealed class VendingMachine : Component
{
	public static VendingMachine NearestMachine { get; private set; }

	[Property] public float InteractionRange { get; set; } = 140f;
	[Property] public int SpeedDrinkCost { get; set; } = 25;
	[Property] public int MedkitCost { get; set; } = 40;
	[Property] public string BuySound { get; set; } = "music/buttonhover.sound";

	[Sync] private TimeSince _timeSincePurchase { get; set; } = 1f;

	protected override void OnUpdate()
	{
		var localPlayer = PlayerHealth.LocalInstance;
		if ( !localPlayer.IsValid() || localPlayer.IsDead || localPlayer.IsDowned )
		{
			if ( NearestMachine == this ) NearestMachine = null;
			return;
		}

		float dist = Vector3.DistanceBetween( WorldPosition, localPlayer.WorldPosition );
		if ( dist <= InteractionRange )
		{
			NearestMachine = this;

			// Quick hotkeys when near vending machine: Key 1 for Drink, Key 2 for Medkit
			if ( Input.Pressed( "slot1" ) && _timeSincePurchase > 0.6f )
			{
				TryBuySpeedDrink();
			}
			else if ( ( Input.Pressed( "slot2" ) || Input.Pressed( "use" ) ) && _timeSincePurchase > 0.6f )
			{
				TryBuyMedkit();
			}
		}
		else if ( NearestMachine == this )
		{
			NearestMachine = null;
		}
	}

	public bool TryBuySpeedDrink()
	{
		var meta = MetaProgression.Instance;
		if ( meta == null || meta.Credits < SpeedDrinkCost )
		{
			Sound.Play( "music/buttonhover.sound", WorldPosition );
			Log.Info( "[VendingMachine] Not enough coins for Speed Drink!" );
			return false;
		}

		meta.AddCredits( -SpeedDrinkCost );
		_timeSincePurchase = 0f;

		var abilities = PlayerAbilities.LocalInstance;
		if ( abilities.IsValid() )
		{
			abilities.TryActivateSpeedJuice();
		}

		PlayPurchaseFeedback();
		Log.Info( "[VendingMachine] Purchased Speed Drink!" );
		return true;
	}

	public bool TryBuyMedkit()
	{
		var meta = MetaProgression.Instance;
		if ( meta == null || meta.Credits < MedkitCost )
		{
			Sound.Play( "music/buttonhover.sound", WorldPosition );
			Log.Info( "[VendingMachine] Not enough coins for Medkit!" );
			return false;
		}

		var health = PlayerHealth.LocalInstance;
		if ( !health.IsValid() || health.IsDead ) return false;

		meta.AddCredits( -MedkitCost );
		_timeSincePurchase = 0f;

		health.Heal( 50f );

		PlayPurchaseFeedback();
		Log.Info( "[VendingMachine] Purchased Medkit (+50 HP)!" );
		return true;
	}

	private void PlayPurchaseFeedback()
	{
		if ( !string.IsNullOrEmpty( BuySound ) )
		{
			Sound.Play( BuySound, WorldPosition );
		}
		CameraJuice.Instance?.Shake( 10f, 3f );
	}

	protected override void OnDestroy()
	{
		if ( NearestMachine == this ) NearestMachine = null;
		base.OnDestroy();
	}
}