The door interaction UI for a roleplay gamemode. Draws a label on the door being looked at and a P-key menu to buy, lock, sell or share keys, plus staff controls for title, price, buyable flag and job access. All actions are sent as requests to DoorSystem.
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.Mapping;
@namespace HorizonRP
@inherits PanelComponent
@*
Les portes RP. En regardant une porte : une étiquette (titre, propriétaire,
prix, verrou). P ouvre le menu de cette porte : acheter, verrouiller, vendre,
donner la clé ; le staff y règle aussi le titre, le prix et les métiers.
Rien n'est décidé ici : chaque bouton est une demande à l'hôte (DoorSystem).
*@
<root>
@if ( !MenuOpen && Looked is not null )
{
<div class="door-hint">
<i>@(Looked.Locked ? "lock" : "door_front")</i>
<div class="hint-text">
<div class="hint-title">@Looked.DisplayTitle</div>
<div class="hint-sub">@StatusLine( Looked )</div>
</div>
<div class="hint-key">P</div>
</div>
}
@if ( MenuOpen && Current is not null )
{
<div class="door-backdrop">
<div class="door-menu">
<div class="door-head">
<div class="door-icon @(Current.Locked ? "locked" : "")"><i>@(Current.Locked ? "lock" : "door_front")</i></div>
<div class="door-title">
<div class="door-name">@Current.DisplayTitle</div>
<div class="door-sub">@StatusLine( Current )</div>
</div>
<div class="door-close" onclick=@(() => SetMenu( false ))><i>close</i></div>
</div>
<div class="door-body">
<div class="actions">
@if ( Current.CanBuy )
{
<div class="action primary @(Me.Wallet < Current.Price ? "disabled" : "")" onclick=@(() => Doors.RequestBuy( Key ))>
<i>key</i> @($"Acheter · {(Format.Money( Current.Price ))}")
</div>
}
@if ( Current.CanUse( Me ) || Me.IsStaff )
{
<div class="action" onclick=@(() => Doors.RequestSetLocked( Key, !Current.Locked ))>
<i>@(Current.Locked ? "lock_open" : "lock")</i> @(Current.Locked ? "Déverrouiller" : "Verrouiller")
</div>
}
@if ( Current.OwnerId == Me.SteamId )
{
<div class="action danger" onclick=@(() => Doors.RequestSell( Key ))>
<i>sell</i> @($"Vendre · +{(Current.Price / 2)} $")
</div>
}
</div>
@if ( Current.OwnerId == Me.SteamId )
{
<div class="section-label">Ont la clé</div>
<div class="chips">
@if ( !Current.CoOwners.Any() )
{
<div class="muted">Personne d'autre que vous.</div>
}
@foreach ( var id in Current.CoOwners )
{
<div class="chip on" onclick=@(() => Doors.RequestRemoveCoOwner( Key, id ))>@NameOf( id ) <i>close</i></div>
}
</div>
<div class="section-label">Donner la clé à</div>
<div class="chips">
@foreach ( var player in RoleplayPlayer.All.Where( x => x != Me && !Current.CoOwners.Contains( x.SteamId ) ).OrderBy( x => x.RpName ) )
{
<div class="chip" onclick=@(() => Doors.RequestAddCoOwner( Key, player.SteamId ))><i>add</i> @player.RpName</div>
}
</div>
}
@if ( Me.IsStaff )
{
<div class="staff">
<div class="staff-title"><i>shield</i> Réglage staff (enregistré)</div>
<div class="section-label">Nom de la porte</div>
<TextEntry class="field" Value=@EditTitle OnTextEdited=@((string v) => { EditTitle = v; })></TextEntry>
<div class="section-label">Prix</div>
<div class="chips">
@foreach ( var price in Prices )
{
<div class="chip @(EditPrice == price ? "on" : "")" onclick=@(() => EditPrice = price)>@Format.Money( price )</div>
}
</div>
<div class="section-label">Accès</div>
<div class="chips">
<div class="chip @(EditBuyable ? "on" : "")" onclick=@(() => EditBuyable = true)>Achetable</div>
<div class="chip @(!EditBuyable ? "on" : "")" onclick=@(() => EditBuyable = false)>Non achetable</div>
</div>
<div class="section-label">Réservée aux métiers (clé automatique)</div>
<div class="chips">
@foreach ( var job in Jobs.All )
{
<div class="chip @(EditJobs.Contains( job.Id ) ? "on" : "")" onclick=@(() => ToggleJob( job.Id ))>@job.Name</div>
}
</div>
<div class="actions">
<div class="action primary" onclick=@(() => SaveStaff())><i>save</i> Enregistrer</div>
@if ( Current.IsOwned )
{
<div class="action danger" onclick=@(() => Doors.StaffRelease( Key ))><i>person_remove</i> @($"Retirer à {(Current.OwnerName)}")</div>
}
</div>
</div>
}
</div>
</div>
</div>
}
</root>
@code
{
static readonly int[] Prices = { 0, 50, 100, 250, 500, 1000, 2500, 5000 };
public bool MenuOpen { get; private set; }
/// <summary>La porte ouverte dans le menu.</summary>
string Key { get; set; }
/// <summary>La porte regardée (pour l'étiquette), rafraîchie à chaque image.</summary>
DoorState Looked { get; set; }
string EditTitle { get; set; }
int EditPrice { get; set; }
bool EditBuyable { get; set; }
HashSet<string> EditJobs { get; set; } = new();
DoorSystem Doors => DoorSystem.Current;
RoleplayPlayer Me => RoleplayPlayer.Local;
DoorState Current => Key is null ? null : Doors?.StateOf( Key );
protected override void OnUpdate()
{
if ( Me is null || Doors is null )
{
if ( MenuOpen ) SetMenu( false );
Looked = null;
return;
}
// Échap referme ce menu (et ne laisse pas s'ouvrir le menu pause en même temps).
if ( MenuOpen && Input.EscapePressed )
{
Input.EscapePressed = false;
SetMenu( false );
return;
}
var (door, key) = Doors.LookedAt();
Looked = key is null ? null : Doors.StateOf( key );
if ( Input.Pressed( "DoorMenu" ) )
{
if ( MenuOpen )
SetMenu( false );
else if ( key is not null && Looked is not null )
OpenFor( key );
else
HudNotifications.Push( "Regardez une porte pour ouvrir son menu.", "info" );
}
// On ferme si l'on s'éloigne : les demandes seraient refusées de toute façon.
if ( MenuOpen && Current is null )
SetMenu( false );
}
void OpenFor( string key )
{
Key = key;
var state = Doors.StateOf( key );
EditTitle = state.Title;
EditPrice = state.Price;
EditBuyable = state.Buyable;
EditJobs = state.JobList.ToHashSet();
SetMenu( true );
}
void SetMenu( bool open )
{
MenuOpen = open;
UiLock.Set( "porte", open );
}
protected override void OnDisabled() => UiLock.Set( "porte", false );
void ToggleJob( string id )
{
if ( !EditJobs.Remove( id ) )
EditJobs.Add( id );
}
void SaveStaff()
{
Doors.StaffConfigure( Key, EditTitle, EditPrice, EditBuyable, string.Join( ",", EditJobs ) );
}
string NameOf( ulong steamId ) => RoleplayPlayer.All.FirstOrDefault( x => x.SteamId == steamId )?.RpName ?? "Joueur parti";
static string StatusLine( DoorState state )
{
if ( state.IsJobDoor )
return "Réservée : " + string.Join( ", ", state.JobList.Select( x => Jobs.Get( x ).Name ) ) + (state.Locked ? " · verrouillée" : "");
if ( state.IsOwned )
return $"Propriété de {state.OwnerName}" + (state.Locked ? " · verrouillée" : "");
if ( !state.Buyable )
return "Ne s'achète pas";
return $"Libre · {Format.Money( state.Price )}";
}
protected override int BuildHash()
{
var state = Current;
return HashCode.Combine(
HashCode.Combine( MenuOpen, Key, Looked?.Key, Looked?.Locked, Looked?.OwnerId ),
HashCode.Combine( state?.OwnerId, state?.Locked, state?.CoOwners.Count, state?.Title, state?.Price, state?.Buyable, state?.Jobs ),
HashCode.Combine( EditTitle, EditPrice, EditBuyable, string.Join( ",", EditJobs ) ),
HashCode.Combine( Me?.Wallet, RoleplayPlayer.All.Count(), Doors?.StatesJson?.Length ) );
}
}