UI/SearchMenu.razor

The loot/search panel for a roleplay gamemode, opened when searching an unconscious or cuffed player. Lists their cash and pocket items with per-item take buttons and a take-all button, and switches between civilian take requests and police seizure requests. Closes on Escape or when the target moves out of reach.

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

@*
	La fouille d'un inconscient (E maintenu 3 s, RoleplayPlayer.Death.cs) : son argent liquide et
	ses poches, à prendre une par une ou tout d'un coup. Créée par le HUD sur chaque machine.
	L'hôte revérifie tout (Money/Cash.cs : toujours inconscient, à côté, la place dans nos poches).

	En mode police (menu F6 d'un agent en service, Police/PoliceActions.cs) : la fouille d'un
	menotté (ou d'un inconscient), « Saisir » au lieu de « Prendre », chaque saisie journalisée.
*@

<root>
	@if ( IsOpen && Target.IsValid() && Me is not null )
	{
		<div class="search-backdrop">
			<div class="search-panel">
				<div class="search-head">
					<div class="search-icon"><i>search</i></div>
					<div class="search-titles">
						<div class="search-title">@Title</div>
						<div class="search-sub">@Sub</div>
					</div>
					<div class="search-close" onclick=@(() => Close())><i>close</i></div>
				</div>

				<div class="search-row cash">
					<div class="row-pic" style="background-image: url( ui/billets.png )"></div>
					<div class="row-name">@($"Argent liquide · {(Format.Money( Target.Wallet ))}")</div>
					<div class="btn small @(Target.Wallet > 0 ? "" : "off")" onclick=@(() => TakeCash())>@TakeLabel</div>
				</div>

				@if ( Pockets.Count == 0 )
				{
					<div class="search-empty">Rien dans ses poches.</div>
				}
				@foreach ( var slot in Pockets )
				{
					var index = slot.Slot;
					<div class="search-row">
						@if ( slot.Definition?.Picture is { } picture )
						{
							<div class="row-pic" style="background-image: url( @picture )"></div>
						}
						else
						{
							<div class="row-icon"><i>@(slot.Definition?.Icon ?? "inventory_2")</i></div>
						}
						<div class="row-name">@(slot.Count > 1 ? $"{slot.Definition?.Name} ×{slot.Count}" : slot.Definition?.Name)</div>
						<div class="btn small" onclick=@(() => TakeItem( index ))>@TakeLabel</div>
					</div>
				}

				<div class="btn primary @(Target.Wallet > 0 || Pockets.Count > 0 ? "" : "off")" onclick=@(() => TakeAll())><i>@(PoliceMode ? "gavel" : "back_hand")</i> @(PoliceMode ? "Tout saisir" : "Tout prendre")</div>
			</div>
		</div>
	}
</root>

@code
{
	static SearchMenu instance;

	/// <summary>La fouille finie (E maintenu) : la fenêtre s'ouvre sur ce corps.</summary>
	public static void Open( RoleplayPlayer target ) => instance?.Show( target, false );

	/// <summary>La fouille de police (menu F6, l'hôte a vérifié) : sur un menotté ou un inconscient.</summary>
	public static void OpenPolice( RoleplayPlayer target ) => instance?.Show( target, true );

	/// <summary>Referme la fouille (un menotté ne fouille plus personne).</summary>
	public static void CloseAll()
	{
		if ( instance is { IsOpen: true } menu )
			menu.Close();
	}

	/// <summary>Fouille de police : saisies (PoliceActions) au lieu de prises (Cash).</summary>
	bool PoliceMode { get; set; }

	string TakeLabel => PoliceMode ? "Saisir" : "Prendre";

	/// <summary>Un agent ne voit le nom que de ceux dont il a vu les papiers.</summary>
	string Title => PoliceMode ? $"Fouille de police · {PoliceMenu.NameOf( Target )}" : $"Fouiller {Target.RpName}";

	string Sub => PoliceMode ? (Target.Cuffed ? "Menotté · saisies journalisées" : "Inconscient · saisies journalisées") : "Inconscient";

	void TakeCash()
	{
		if ( PoliceMode )
			PoliceActions.RequestSeizeCash( Target.GameObject.Id );
		else
			Cash.RequestSearch( Target.GameObject.Id );
	}

	void TakeItem( int index )
	{
		if ( PoliceMode )
			PoliceActions.RequestSeizeItem( Target.GameObject.Id, index );
		else
			Cash.RequestTakeItem( Target.GameObject.Id, index );
	}

	void TakeAll()
	{
		if ( PoliceMode )
			PoliceActions.RequestSeizeAll( Target.GameObject.Id );
		else
			Cash.RequestTakeAll( Target.GameObject.Id );
	}

	public bool IsOpen { get; private set; }
	RoleplayPlayer Target { get; set; }

	RoleplayPlayer Me => RoleplayPlayer.Local;

	List<InventorySlot> Pockets => Target?.PublishedInventory?.Where( x => x.Definition is not null ).OrderBy( x => x.Slot ).ToList() ?? new();

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

	void Show( RoleplayPlayer target, bool police )
	{
		// Un menotté ne fouille personne. La fouille de police s'ouvre depuis le menu F6 (verrou « police »).
		if ( Me is null || target is null || Me.Cuffed || (UiLock.IsLocked && !police) )
			return;
		instance = this;
		Target = target;
		PoliceMode = police;
		IsOpen = true;
		UiLock.Set( "fouille", true );
	}

	void Close()
	{
		IsOpen = false;
		Target = null;
		UiLock.Set( "fouille", false );
	}

	protected override void OnUpdate()
	{
		instance = this;
		if ( !IsOpen )
			return;
		// Échap, ou le corps n'est plus là pour être fouillé (réanimé, mort, trop loin).
		var gone = !Target.IsValid() || Me is null || Me.OnTheGround || Me.Cuffed
			|| (PoliceMode
				? (!Target.Cuffed && !Target.Downed) || !Me.IsCop || (Target.Downed ? Target.BodyPosition : Target.WorldPosition).Distance( Me.WorldPosition ) > PoliceActions.Reach * 1.4f
				: !Target.Downed || Target.BodyPosition.Distance( Me.WorldPosition ) > Cash.Reach * 1.6f);
		if ( Input.EscapePressed || gone )
		{
			Input.EscapePressed = false;
			Close();
		}
	}

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

	protected override int BuildHash() => HashCode.Combine( IsOpen, Target?.Wallet, Target?.InventoryJson, Target?.RpName, PoliceMode, Target?.Cuffed, PoliceMenu.KnownVersion );
}