UI/CashMenu.razor

Razor panel for the cash menu. Shows the local player's wallet, lets them enter or pick an amount, then gives the bills to the player they are looking at or drops them on the ground. Closes on Escape or when cash is no longer held.

@using Sandbox;
@using Sandbox.UI;
@namespace HorizonRP
@inherits PanelComponent

@*
	Les billets en main, clic droit (Money/Cash.cs) : combien, puis donner au joueur qu'on
	regarde ou poser au sol. Créée par le HUD sur chaque machine (pas dans la scène). L'hôte
	revérifie la somme et la distance.
*@

<root>
	@if ( IsOpen && Me is not null )
	{
		<div class="cash-backdrop">
			<div class="cash-panel">
				<div class="cash-head">
					<div class="cash-icon"><i>payments</i></div>
					<div class="cash-titles">
						<div class="cash-title">Billets</div>
						<div class="cash-sub">@($"Sur vous : {(Format.Money( Me.Wallet ))}")</div>
					</div>
				</div>

				<div class="field-label">Somme</div>
				<TextEntry class="cash-field" Value=@AmountText OnTextEdited=@((string v) => { AmountText = Digits( v ); })></TextEntry>
				<div class="cash-chips">
					@foreach ( var preset in Presets )
					{
						<div class="chip @(Amount == preset ? "on" : "")" onclick=@(() => AmountText = preset.ToString())>@Format.Money( preset )</div>
					}
					<div class="chip @(Amount == Me.Wallet && Me.Wallet > 0 ? "on" : "")" onclick=@(() => AmountText = Me.Wallet.ToString())>Tout</div>
				</div>

				<div class="cash-actions">
					@if ( Target.IsValid() )
					{
						<div class="btn primary @(Valid ? "" : "off")" onclick=@(() => Give())><i>volunteer_activism</i> @($"Donner à {Target.RpName}")</div>
					}
					<div class="btn @(Valid ? "" : "off")" onclick=@(() => Drop())><i>south</i> Poser au sol</div>
					<div class="btn ghost" onclick=@(() => Close())><i>close</i> Annuler</div>
				</div>
				@if ( !Target.IsValid() )
				{
					<div class="cash-hint">Pour donner, regardez quelqu'un de près.</div>
				}
			</div>
		</div>
	}
</root>

@code
{
	static CashMenu instance;

	/// <summary>Le clic gauche billets en main (VehicleKeySystem.LocalInput).</summary>
	public static void Open() => instance?.Show();

	public bool IsOpen { get; private set; }
	string AmountText { get; set; } = "";
	RoleplayPlayer Target { get; set; }

	static int[] Presets => new[] { 10, 50, 100, 500 };

	RoleplayPlayer Me => RoleplayPlayer.Local;

	int Amount => int.TryParse( AmountText, out var a ) ? a : 0;
	bool Valid => Amount > 0 && Me is not null && Amount <= Me.Wallet;

	protected override void OnStart() => instance = this;

	void Show()
	{
		if ( Me is null || UiLock.IsLocked )
			return;
		instance = this;
		Target = PlayerInFront();
		AmountText = "";
		IsOpen = true;
		UiLock.Set( "argent", true );
	}

	void Close()
	{
		IsOpen = false;
		UiLock.Set( "argent", false );
	}

	protected override void OnUpdate()
	{
		instance = this;
		if ( !IsOpen )
			return;
		// Échap, ou plus de billets en main (tombé, monté en voiture) : on referme.
		if ( Input.EscapePressed || !Cash.IsHeld( Me ) )
		{
			Input.EscapePressed = false;
			Close();
		}
	}

	protected override void OnDisabled()
	{
		if ( IsOpen )
			Close();
	}

	/// <summary>Le joueur qu'on regarde, de près (à pied, debout).</summary>
	RoleplayPlayer PlayerInFront()
	{
		var me = Me;
		var eye = me.GetComponent<PlayerController>()?.EyePosition ?? me.WorldPosition + Vector3.Up * 64;
		var forward = Scene.Camera?.WorldRotation.Forward ?? me.WorldRotation.Forward;
		return RoleplayPlayer.All
			.Where( p => p != me && !p.OnTheGround && p.GameObject.GetComponentInParent<Vehicle>() is null )
			.Select( p => (p, d: p.WorldPosition.Distance( me.WorldPosition ), aim: Vector3.Dot( (p.WorldPosition + Vector3.Up * 50 - eye).Normal, forward )) )
			.Where( x => x.d < Cash.Reach * 1.3f && x.aim > 0.6f )
			.OrderBy( x => x.d ).Select( x => x.p ).FirstOrDefault();
	}

	void Give()
	{
		if ( !Valid || !Target.IsValid() )
			return;
		Cash.RequestGive( Target.GameObject.Id, Amount );
		HorizonHud.ShowHotbar( HorizonHud.HandsPosition );
		Close();
	}

	void Drop()
	{
		if ( !Valid )
			return;
		Cash.RequestDrop( Amount );
		// Les billets posés, on revient aux mains (l'hôte les retire de la main).
		HorizonHud.ShowHotbar( HorizonHud.HandsPosition );
		Close();
	}

	static string Digits( string v )
	{
		var d = new string( (v ?? "").Where( char.IsDigit ).Take( 9 ).ToArray() );
		return d.TrimStart( '0' );
	}

	protected override int BuildHash() => HashCode.Combine( IsOpen, AmountText, Me?.Wallet, Target?.RpName );
}