UI/WalletMenu.razor

The wallet UI panel, opened with the K key. Shows the local player's ID card, driving license, job badge, bank card and cash in a two-leaf leather wallet, with a detail pane of actions such as showing the ID to nearby players or flipping the card. Also renders another player's ID card for 10 seconds when they present it.

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

@*
	Le portefeuille (touche K) : un portefeuille de cuir ouvert sur deux volets.
	Documents à gauche (carte d'identité, permis, badge de métier), cartes à droite
	(carte HZ BANK, espèces). On clique une pièce : elle passe au premier plan, avec
	ses actions à droite (montrer aux joueurs proches, retourner, ranger).
	Quand un autre joueur vous montre sa carte, elle s'affiche quelques secondes.
*@

<root>
	@if ( Presented is { } other && sincePresented < PresentSeconds )
	{
		<div class="presented">
			<div class="presented-title"><i>badge</i> @($"{(other.RpName)} vous montre sa carte d'identité")</div>
			<IdCard Player=@other></IdCard>
		</div>
	}

	@if ( IsOpen && Me is not null )
	{
		<div class="wallet-backdrop">
			<div class="wallet">
				@* Les billets dépassent du compartiment, derrière les deux volets. *@
				<div class="bills-peek" onclick=@(() => Select( "especes" ))>
					<div class="bill b1"><span>100</span></div>
					<div class="bill b2"><span>50</span></div>
					<div class="bill b3"><span>20</span></div>
				</div>

				<div class="leaf left">
					<div class="stitch"></div>
					<div class="leaf-title">DOCUMENTS</div>

					@* La carte d'identité derrière sa fenêtre transparente. *@
					<div class="window @(Selected == "id" ? "selected" : "")" onclick=@(() => Select( "id" ))>
						<IdCard Player=@Me Flipped=@(Selected == "id" && Flipped)></IdCard>
						<div class="glare"></div>
					</div>

					@* Les autres papiers, glissés dans des fentes : on n'en voit que le haut. *@
					<div class="slot paper">
						<div class="peek license">
							<div class="peek-left">@Documents.State</div>
							<div class="peek-right">PERMIS DE CONDUIRE</div>
						</div>
						<div class="lip"></div>
					</div>
					@if ( Documents.BadgeOf( Me.JobId ) is { } badge )
					{
						<div class="slot paper">
							<div class="peek badge">
								<div class="peek-left">@badge.Agency</div>
								<div class="peek-right">@badge.Title</div>
							</div>
							<div class="lip"></div>
						</div>
					}
				</div>

				<div class="spine"></div>

				<div class="leaf right">
					<div class="stitch"></div>
					<div class="leaf-title">CARTES</div>

					@* La carte HZ BANK dans sa fente : choisie, elle en sort. *@
					<div class="card-slot @(Selected == "carte" ? "out" : "")">
						<div class="bank-card @(Selected == "carte" && Flipped ? "flipped" : "")" onclick=@(() => Select( "carte" ))>
							@if ( Selected == "carte" && Flipped )
							{
								<div class="stripe"></div>
								<div class="signature-row">
									<div class="signature">@Me.RpName</div>
									<div class="cvv">@Documents.Cvv( Me.SteamId )</div>
								</div>
								<div class="balance">@($"Solde : {(Format.Money( Me.Bank ))}")</div>
								<div class="card-legal">@($"Cette carte reste la propriété de {(Documents.BankName)}.")</div>
							}
							else
							{
								<div class="card-top">
									<div class="bank-name">@Documents.BankName</div>
									<div class="default">PAR DÉFAUT</div>
								</div>
								<div class="card-sub">COMPTE PERSONNEL</div>
								<div class="chip-row"><div class="chip"></div><i>contactless</i></div>
								<div class="card-number">@Documents.CardNumber( Me.SteamId )</div>
								<div class="card-bottom">
									<div class="cell"><div class="card-label">TITULAIRE</div><div class="card-value">@Me.RpName.ToUpperInvariant()</div></div>
									<div class="cell"><div class="card-label">EXPIRE FIN</div><div class="card-value">@Documents.ValidThru( Me.IdIssued )</div></div>
									<div class="network"><div class="circle a"></div><div class="circle b"></div></div>
								</div>
							}
						</div>
						<div class="lip"></div>
					</div>

					@* Deux fentes vides, pour l'allure d'un vrai porte-cartes. *@
					<div class="slot empty"><div class="lip"></div></div>
					<div class="slot empty"><div class="lip"></div></div>

					<div class="cash" onclick=@(() => Select( "especes" ))>
						<i>payments</i>
						<div class="cash-amount">
							<div class="card-label">ESPÈCES</div>
							<div class="cash-value">@Format.Money( Me.Wallet )</div>
						</div>
					</div>
				</div>

				<div class="detail">
					@if ( Selected == "id" )
					{
						<div class="detail-kind">DOCUMENT</div>
						<div class="detail-title">Carte d'identité</div>
						<div class="detail-line"><span>Né(e) le</span><b>@Format.Date( Me.BirthDate )</b></div>
						<div class="detail-line"><span>N° de citoyen</span><b>@Documents.CitizenId( Me.SteamId )</b></div>
						<div class="detail-line"><span>Expire le</span><b>@Documents.PlusYears( Me.IdIssued, Documents.IdValidYears )</b></div>
						<div class="action gold" onclick=@(() => Me.RequestShowId())><i>visibility</i> Montrer aux joueurs proches</div>
						<div class="action" onclick=@(() => Flipped = !Flipped)><i>flip</i> Retourner</div>
						<div class="action" onclick=@(() => Select( null ))><i>undo</i> Ranger</div>
					}
					else if ( Selected == "carte" )
					{
						<div class="detail-kind">COMPTE PERSONNEL</div>
						<div class="detail-title">@($"{(Documents.BankName)} Classic")</div>
						<div class="detail-line"><span>Solde</span><b class="money">@Format.Money( Me.Bank )</b></div>
						<div class="detail-line"><span>Titulaire</span><b>@Me.RpName</b></div>
						<div class="detail-line"><span>Valide jusqu'au</span><b>@Documents.ValidThru( Me.IdIssued )</b></div>
						<div class="action" onclick=@(() => Flipped = !Flipped)><i>flip</i> Retourner</div>
						<div class="action" onclick=@(() => Select( null ))><i>undo</i> Ranger</div>
					}
					else if ( Selected == "especes" )
					{
						<div class="detail-kind">ESPÈCES</div>
						<div class="detail-title">@Format.Money( Me.Wallet )</div>
						<div class="detail-text">L'argent liquide part avec vous. Déposez-le à la banque (F4, ou un distributeur) pour le mettre à l'abri.</div>
						<div class="action" onclick=@(() => Select( null ))><i>undo</i> Ranger</div>
					}
					else
					{
						<div class="detail-kind">PORTEFEUILLE</div>
						<div class="detail-title">@Me.RpName</div>
						<div class="detail-text">Cliquez une carte ou un document pour le sortir.</div>
					}
					<div class="hint-keys"><span class="key">K</span> fermer</div>
				</div>
			</div>
		</div>
	}
</root>

@code
{
	public const float PresentSeconds = 10f;

	static RoleplayPlayer Presented;
	static RealTimeSince sincePresented;

	/// <summary>Un autre joueur nous montre sa carte d'identité (RPC de l'hôte).</summary>
	public static void Present( RoleplayPlayer player )
	{
		Presented = player;
		sincePresented = 0;
	}

	public bool IsOpen { get; private set; }
	string Selected { get; set; }
	bool Flipped { get; set; }

	RoleplayPlayer Me => RoleplayPlayer.Local;

	protected override void OnUpdate()
	{
		if ( Me is null || Me.NeedsCharacter )
		{
			if ( IsOpen ) SetOpen( false );
			return;
		}


		// Échap referme ce menu (et ne laisse pas s'ouvrir le menu pause en même temps).
		if ( IsOpen && Input.EscapePressed )
		{
			Input.EscapePressed = false;
			SetOpen( false );
			return;
		}

		if ( Input.Pressed( "Wallet" ) )
			SetOpen( !IsOpen );
	}

	void SetOpen( bool open )
	{
		IsOpen = open;
		Selected = null;
		Flipped = false;
		UiLock.Set( "portefeuille", open );
	}

	protected override void OnDisabled() => UiLock.Set( "portefeuille", false );

	void Select( string what )
	{
		if ( Selected != what )
			Flipped = false;
		Selected = what;
	}

	protected override int BuildHash() => HashCode.Combine(
		HashCode.Combine( IsOpen, Selected, Flipped ),
		HashCode.Combine( Me?.Bank, Me?.Wallet, Me?.JobId, Me?.RpName ),
		Presented?.SteamId, sincePresented < PresentSeconds );
}