Police and EMS menu UI. Renders the F6 and E prompts, officer actions, penal charge selection, lockers, front desk fines, staff placement points, and EMS treatment and billing views, then sends requests through PoliceActions and EmsActions.
@using Sandbox;
@using Sandbox.UI;
@namespace HorizonRP
@inherits PanelComponent
@*
La police, côté écran (Police/PoliceActions.cs, PoliceService.Service.cs) :
- F6 en service : les actions sur le citoyen le plus proche (son nom s'il est connu de l'agent,
sinon « Citoyen » ; menotté, escorté, en véhicule), puis l'écran des charges (code pénal par
catégorie, recherche, total de l'amende et de la peine, notes) pour une amende ou un écrou ;
les actions sur le véhicule le plus proche (fourrière, crochetage).
- E au vestiaire : prise et fin de service, tenue de service (PoliceUniform), armurerie selon le grade.
- E à l'accueil : ses amendes impayées, payées depuis HZ BANK.
- F6 pour le staff hors service : les points du commissariat et de l'hôpital, posés là où il se tient.
- Au HUD : « En service · grade · indicatif » chez l'agent, le minuteur de prison chez le détenu,
« Menotté » chez le menotté, et les bandeaux E près des points.
Les secours (UCFD EMS, Ems/EmsActions.cs, Police/PoliceService.Ems.cs), dans le même composant
(déjà dans la scène) :
- F6 en service : la vue « Secours » sur le patient le plus proche (diagnostiquer, soigner,
réanimer, stabiliser, escorter, véhicule, facture de soins ; effectifs pour un gradé).
- E au vestiaire de l'hôpital : prise et fin de service, matériel ; E à l'accueil : se faire
soigner (sans secouriste en service), payer ses factures.
Le menu ne décide de rien : l'hôte revérifie chaque demande.
*@
<root>
@if ( Me is not null && !Me.NeedsCharacter )
{
@if ( PoliceShowroom.Framing )
{
}
else if ( Me.IsCop )
{
<div class="duty-chip">
<i>@PoliceRules.GradeOf( Me.PoliceGrade ).Insignia</i>
<div class="duty-text">@DutyText</div>
</div>
}
else if ( Me.IsEms )
{
<div class="duty-chip ems">
<i>@EmsRules.GradeOf( Me.EmsGrade ).Insignia</i>
<div class="duty-text">@EmsDutyText</div>
</div>
}
@if ( Me.Jailed )
{
<div class="jail-row">
<div class="jail-card">
<div class="jail-head"><i>lock</i><div>PRISON D'UNION CITY</div></div>
<div class="jail-time">@Clock( Me.JailSecondsLeft )</div>
<div class="jail-bar"><div class="jail-fill" style="width: @JailPercent"></div></div>
<div class="jail-sub">Temps de jeu en ligne : la peine ne s'écoule que connecté.</div>
</div>
</div>
}
@if ( Me.Cuffed )
{
<div class="cuff-row">
<div class="cuff-chip">
<i>link</i>
<div>@(Me.EscortedBy != Guid.Empty ? "Menotté · escorté par un agent" : "Menotté")</div>
</div>
</div>
}
@if ( View is null && Prompt is { } prompt )
{
<div class="prompt-row">
<div class="prompt">
<div class="keycap">@HorizonHud.KeyName( "use" )</div>
<i>@prompt.Icon</i>
<div class="prompt-text">@prompt.Text</div>
</div>
</div>
}
@if ( View is not null )
{
<div class="police-backdrop">
@if ( View == "actions" )
{
<div class="panel">
<div class="head">
<div class="head-icon"><i>local_police</i></div>
<div class="head-titles">
<div class="head-title">@(Target is { } t ? NameOf( t ) : "Aucun citoyen à portée")</div>
<div class="head-sub">@TargetSub</div>
</div>
<div class="icon-btn" onclick=@(() => Repick())><i>person_search</i></div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
@if ( Target is { } citizen )
{
<div class="chips">
@foreach ( var (icon, text) in StateChips( citizen ) )
{
<div class="chip"><i>@icon</i><div>@text</div></div>
}
</div>
<div class="section-label">Citoyen</div>
<div class="grid">
<div class="action" onclick=@(() => PoliceActions.RequestCuff( citizen.GameObject.Id, !citizen.Cuffed ))>
<i>@(citizen.Cuffed ? "link_off" : "link")</i><div>@(citizen.Cuffed ? "Démenotter" : "Menotter")</div>
</div>
<div class="action @(citizen.Cuffed && !Seated( citizen ) ? "" : "off")" onclick=@(() => PoliceActions.RequestEscort( citizen.GameObject.Id, citizen.EscortedBy == Guid.Empty ))>
<i>@(citizen.EscortedBy == Guid.Empty ? "directions_walk" : "front_hand")</i><div>@(citizen.EscortedBy == Guid.Empty ? "Escorter" : "Lâcher")</div>
</div>
@if ( Seated( citizen ) )
{
<div class="action" onclick=@(() => PoliceActions.RequestTakeOutOfVehicle( citizen.GameObject.Id ))>
<i>logout</i><div>Sortir du véhicule</div>
</div>
}
else
{
<div class="action @(citizen.Cuffed && NearCar is not null ? "" : "off")" onclick=@(() => PoliceActions.RequestPutInVehicle( citizen.GameObject.Id ))>
<i>airline_seat_recline_normal</i><div>Mettre dans le véhicule</div>
</div>
}
<div class="action @(citizen.Cuffed || citizen.Downed ? "" : "off")" onclick=@(() => Frisk( citizen ))>
<i>search</i><div>Fouiller</div>
</div>
<div class="action" onclick=@(() => Identity( citizen ))>
<i>badge</i><div>Identité</div>
</div>
<div class="action @(Me.PoliceGrade >= PoliceRules.RankFine ? "" : "off")" onclick=@(() => OpenCharges( "amende" ))>
<i>receipt_long</i><div>Amende</div>
</div>
<div class="action wide @(CanBook( citizen ) ? "" : "off")" onclick=@(() => OpenCharges( "ecrou" ))>
<i>gavel</i><div>@BookLabel( citizen )</div>
</div>
</div>
}
else
{
<div class="empty">Approchez-vous à deux mètres d'un citoyen, puis rouvrez le menu (ou la loupe en haut).</div>
}
<div class="section-label">Véhicule</div>
@if ( NearCar is { } car )
{
<div class="car-row">
<i>directions_car</i>
<div class="car-name">@CarLabel( car )</div>
</div>
<div class="grid">
<div class="action @(Me.PoliceGrade >= PoliceRules.RankImpound && car.OwnerId != 0 && !car.IsPolice ? "" : "off")" onclick=@(() => PoliceActions.RequestImpound( car.GameObject.Id ))>
<i>car_crash</i><div>Fourrière</div>
</div>
<div class="action @(car.Locked ? "" : "off")" onclick=@(() => PoliceActions.RequestLockpick( car.GameObject.Id ))>
<i>key</i><div>Crocheter</div>
</div>
</div>
}
else
{
<div class="empty">Aucun véhicule à côté de vous.</div>
}
@if ( Me.IsStaff )
{
<div class="btn ghost" onclick=@(() => View = "points")><i>add_location_alt</i> Points du commissariat (staff)</div>
}
</div>
}
else if ( View == "charges" )
{
<div class="panel wide">
<div class="head">
<div class="head-icon @(ChargeMode == "ecrou" ? "red" : "")"><i>@(ChargeMode == "ecrou" ? "gavel" : "receipt_long")</i></div>
<div class="head-titles">
<div class="head-title">@(ChargeMode == "ecrou" ? $"Écrouer · {NameOf( Target )}" : $"Amende · {NameOf( Target )}")</div>
<div class="head-sub">@(ChargeMode == "ecrou" ? "Toutes les charges : amende et prison s'additionnent (peine plafonnée)." : "Contraventions et délits seulement : un crime s'écroue.")</div>
</div>
<div class="icon-btn" onclick=@(() => View = "actions")><i>arrow_back</i></div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
<div class="charges">
<div class="code">
<TextEntry class="field" Placeholder="Rechercher : n°, intitulé, catégorie…" Value=@Search OnTextEdited=@((string v) => { Search = v ?? ""; })></TextEntry>
<div class="code-list">
@if ( PenalCount == 0 )
{
<div class="empty">Le code pénal n'est pas encore chargé (fichier de la police injoignable ?).</div>
}
@foreach ( var group in PenalGroups )
{
<div class="cat">@group.Key</div>
@foreach ( var entry in group )
{
var id = entry.Id;
<div class="charge @(Selected.Contains( id ) ? "on" : "")" onclick=@(() => Toggle( id ))>
<i class="check">@(Selected.Contains( id ) ? "check_box" : "check_box_outline_blank")</i>
<div class="charge-main">
<div class="charge-title">@($"{entry.Id} · {entry.Title}")</div>
<div class="charge-sub">@ChargeSub( entry )</div>
</div>
<div class="class-pill" style="background-color: @ClassColor( entry.Class )">@ClassName( entry.Class )</div>
</div>
}
}
</div>
</div>
<div class="summary">
<div class="section-label">@($"Charges retenues ({Selected.Count})")</div>
<div class="chosen">
@if ( Selected.Count == 0 )
{
<div class="empty">Cochez les charges dans la liste.</div>
}
@foreach ( var entry in Chosen )
{
var id = entry.Id;
<div class="chosen-row">
<div class="chosen-name">@($"{entry.Id} · {entry.Title}")</div>
<div class="icon-btn small" onclick=@(() => Toggle( id ))><i>close</i></div>
</div>
}
</div>
<div class="totals">
<div class="total"><div class="field-label">Amende</div><div class="total-value">@Format.Money( FineTotal )</div></div>
@if ( ChargeMode == "ecrou" )
{
<div class="total"><div class="field-label">Prison</div><div class="total-value red">@($"{JailTotal} min")</div></div>
}
</div>
<div class="field-label">Notes (casier)</div>
<TextEntry class="field notes" Placeholder="Circonstances, lieu, témoins…" Value=@Notes OnTextEdited=@((string v) => { Notes = v ?? ""; })></TextEntry>
<div class="btn primary @(CanSubmit ? "" : "off")" onclick=@(() => Submit())>
<i>@(ChargeMode == "ecrou" ? "gavel" : "receipt_long")</i>
<div>@(ChargeMode == "ecrou" ? $"Écrouer · {JailTotal} min" : $"Dresser l'amende · {Format.Money( FineTotal )}")</div>
</div>
@if ( ChargeMode == "ecrou" && JailTotal == 0 && Selected.Count > 0 )
{
<div class="hint">Ces charges ne prévoient pas de prison : dressez plutôt une amende.</div>
}
</div>
</div>
</div>
}
else if ( View == "vestiaire" )
{
var grade = PoliceRules.GradeOf( Me.PoliceGrade );
<div class="panel">
<div class="head">
<div class="head-icon"><i>@grade.Insignia</i></div>
<div class="head-titles">
<div class="head-title">@($"Vestiaire · {PoliceRules.Short}")</div>
<div class="head-sub">@($"{grade.Name} ({grade.English}) · {Me.RpName}")</div>
</div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
<div class="chips">
<div class="chip"><i>tag</i><div>@($"Matricule {(string.IsNullOrEmpty( Me.PoliceBadge ) ? "—" : Me.PoliceBadge)}")</div></div>
<div class="chip"><i>cell_tower</i><div>@($"Indicatif {(string.IsNullOrEmpty( Me.PoliceCallsign ) ? "—" : Me.PoliceCallsign)}")</div></div>
<div class="chip"><i>payments</i><div>@($"Paie en service {Format.Money( grade.Salary )}")</div></div>
</div>
<div class="btn @(Me.OnDuty ? "danger" : "primary")" onclick=@(() => PoliceActions.RequestDuty( !Me.OnDuty ))>
<i>@(Me.OnDuty ? "logout" : "login")</i><div>@(Me.OnDuty ? "Finir mon service" : "Prendre mon service")</div>
</div>
@if ( Me.OnDuty && PoliceUniform.Enabled )
{
var inUniform = PoliceUniform.IsWorn( Me.AppearanceJson );
<div class="btn ghost" onclick=@(() => PoliceActions.RequestUniform( !inUniform ))>
<i>checkroom</i><div>@(inUniform ? "Retirer la tenue de service (civil)" : "Mettre la tenue de service")</div>
</div>
}
else
{
<div class="btn off">
<i>checkroom</i><div>@(PoliceUniform.Enabled ? "Tenue de service à la prise de service" : "Tenue de service · à venir")</div>
</div>
}
<div class="section-label">Armurerie</div>
@if ( !Me.OnDuty )
{
<div class="empty">Prenez votre service pour accéder à l'armurerie.</div>
}
else
{
<div class="armory">
@foreach ( var item in PoliceRules.Armory )
{
var weaponId = item.WeaponId;
var allowed = Me.PoliceGrade >= item.MinRank;
var taken = HasService( weaponId );
<div class="arm-row @(allowed ? "" : "locked")">
<i>@(allowed ? "my_location" : "lock")</i>
<div class="arm-main">
<div class="arm-name">@item.Label</div>
<div class="arm-sub">@ArmorySub( item )</div>
</div>
<div class="btn small @(allowed && !taken ? "" : "off")" onclick=@(() => PoliceActions.RequestArmory( weaponId ))>@(taken ? "Pris" : "Prendre")</div>
</div>
}
</div>
<div class="btn ghost" onclick=@(() => PoliceActions.RequestReturnWeapons())><i>assignment_return</i> Rendre l'armement</div>
}
<div class="hint">Les armes de service ne se jettent pas, ne se donnent pas, et sont reprises à la fin du service.</div>
</div>
}
else if ( View == "accueil" )
{
<div class="panel">
<div class="head">
<div class="head-icon"><i>support_agent</i></div>
<div class="head-titles">
<div class="head-title">@($"Accueil · {PoliceRules.Department}")</div>
<div class="head-sub">@($"Vos amendes impayées · HZ BANK {Format.Money( Me.Bank )}")</div>
</div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
@if ( DeskError is { } error )
{
<div class="empty">@error</div>
}
else if ( DeskFines is null )
{
<div class="empty">Lecture du fichier…</div>
}
else if ( DeskFines.Count == 0 )
{
<div class="empty">Aucune amende impayée. Bonne journée !</div>
}
else
{
<div class="armory">
@foreach ( var fine in DeskFines )
{
var fineId = fine.Id;
<div class="arm-row">
<i>receipt_long</i>
<div class="arm-main">
<div class="arm-name">@(string.IsNullOrEmpty( fine.Title ) ? $"Amende n° {fine.Id}" : fine.Title)</div>
<div class="arm-sub">@($"N° {fine.Id} · {Format.Money( fine.Amount )}")</div>
</div>
<div class="btn small @(Me.Bank >= fine.Amount ? "" : "off")" onclick=@(() => PoliceActions.RequestDeskPay( fineId ))>Payer</div>
</div>
}
</div>
<div class="hint">@($"Total dû : {Format.Money( DeskFines.Sum( x => x.Amount ) )}. Payable aussi depuis le téléphone (HZ BANK, Amendes).")</div>
}
</div>
}
else if ( View == "points" )
{
<div class="panel">
<div class="head">
<div class="head-icon"><i>add_location_alt</i></div>
<div class="head-titles">
<div class="head-title">Points du commissariat</div>
<div class="head-sub">Staff : placez-vous, regardez dans le sens voulu, puis posez.</div>
</div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
<div class="armory">
@foreach ( var (key, label, icon) in PoliceActions.Points )
{
var pointKey = key;
var point = ServerSettings.PolicePoint( key );
<div class="arm-row">
<i>@icon</i>
<div class="arm-main">
<div class="arm-name">@label</div>
<div class="arm-sub">@PointSub( point )</div>
</div>
<div class="btn small" onclick=@(() => PoliceActions.RequestStaffPoint( pointKey, "poser" ))>Poser ici</div>
</div>
}
@foreach ( var (listKey, listLabel, listIcon, _) in PoliceActions.PointLists )
{
var spawns = ServerSettings.PolicePointList( listKey );
for ( var i = 0; i < spawns.Count; i++ )
{
var index = i;
var removeKey = listKey;
<div class="arm-row">
<i>@listIcon</i>
<div class="arm-main">
<div class="arm-name">@($"{listLabel} {index + 1}")</div>
<div class="arm-sub">@PointSub( spawns[index] )</div>
</div>
<div class="btn small danger" onclick=@(() => PoliceActions.RequestStaffPoint( removeKey, $"retirer:{index}" ))>Retirer</div>
</div>
}
}
</div>
@foreach ( var (listKey, listLabel, _, _) in PoliceActions.PointLists )
{
var addKey = listKey;
<div class="btn primary" onclick=@(() => PoliceActions.RequestStaffPoint( addKey, "ajouter" ))><i>add_location_alt</i> @($"Ajouter ici : {listLabel.ToLowerInvariant()}")</div>
}
<div class="section-label">Hôpital (secours)</div>
<div class="armory">
@foreach ( var (key, label, icon) in EmsActions.Points )
{
var pointKey = key;
var point = ServerSettings.PolicePoint( key );
<div class="arm-row">
<i>@icon</i>
<div class="arm-main">
<div class="arm-name">@label</div>
<div class="arm-sub">@PointSub( point )</div>
</div>
<div class="btn small" onclick=@(() => EmsActions.RequestStaffPoint( pointKey, "poser" ))>Poser ici</div>
</div>
}
@{ var emsSpawns = ServerSettings.EmsGarageSpawns; }
@for ( var i = 0; i < emsSpawns.Count; i++ )
{
var index = i;
<div class="arm-row">
<i>local_parking</i>
<div class="arm-main">
<div class="arm-name">@($"Sortie des ambulances {index + 1}")</div>
<div class="arm-sub">@PointSub( emsSpawns[index] )</div>
</div>
<div class="btn small danger" onclick=@(() => EmsActions.RequestStaffPoint( EmsRules.PointsGarageSpawn, $"retirer:{index}" ))>Retirer</div>
</div>
}
</div>
<div class="btn primary" onclick=@(() => EmsActions.RequestStaffPoint( EmsRules.PointsGarageSpawn, "ajouter" ))><i>add_location_alt</i> Ajouter une sortie des ambulances ici</div>
@if ( !Me.IsCop && BookingTarget is { } stuck )
{
@* Un menotté oublié (agent parti) : le staff peut lui retirer ses menottes. *@
<div class="btn ghost" onclick=@(() => PoliceActions.RequestCuff( stuck.GameObject.Id, false ))><i>link_off</i> @($"Retirer les menottes de {stuck.RpName} (staff)")</div>
}
<div class="hint">La prison est aussi le centre de l'enceinte : un détenu qui s'en éloigne de plus de 40 m y est ramené.</div>
</div>
}
else if ( View == "secours" )
{
<div class="panel ems">
<div class="head">
<div class="head-icon ems"><i>medical_services</i></div>
<div class="head-titles">
<div class="head-title">@(Target is { } t ? NameOf( t ) : "Aucun patient à portée")</div>
<div class="head-sub">@PatientSub</div>
</div>
<div class="icon-btn" onclick=@(() => Repick())><i>person_search</i></div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
@if ( Target is { } patient )
{
<div class="chips">
@foreach ( var (icon, text) in PatientChips( patient ) )
{
<div class="chip"><i>@icon</i><div>@text</div></div>
}
</div>
@if ( DiagnosisFor( patient ) is { } diagnosis )
{
<div class="diag">
@foreach ( var line in diagnosis )
{
<div class="diag-line">@line</div>
}
</div>
}
<div class="section-label">Soins</div>
<div class="grid">
<div class="action" onclick=@(() => EmsActions.RequestDiagnose( patient.GameObject.Id ))>
<i>stethoscope</i><div>Diagnostiquer</div>
</div>
<div class="action @(!patient.Downed && patient.Health < 100 && HasCare ? "" : "off")" onclick=@(() => EmsActions.RequestHeal( patient.GameObject.Id ))>
<i>healing</i><div>Soigner</div>
</div>
<div class="action @(patient.Downed && Me.EmsGrade >= EmsRules.RankRevive ? "" : "off")" onclick=@(() => EmsActions.RequestRevive( patient.GameObject.Id ))>
<i>ecg_heart</i><div>Réanimer</div>
</div>
<div class="action @(patient.Downed && HasCare ? "" : "off")" onclick=@(() => EmsActions.RequestStabilize( patient.GameObject.Id ))>
<i>monitor_heart</i><div>Stabiliser</div>
</div>
</div>
<div class="section-label">Transport</div>
<div class="grid">
<div class="action @(!patient.OnTheGround && !Seated( patient ) && (patient.EscortedBy == Guid.Empty || patient.EscortedBy == Me.GameObject.Id) ? "" : "off")" onclick=@(() => EmsActions.RequestEscort( patient.GameObject.Id, patient.EscortedBy == Guid.Empty ))>
<i>@(patient.EscortedBy == Guid.Empty ? "directions_walk" : "front_hand")</i><div>@(patient.EscortedBy == Guid.Empty ? "Escorter" : "Lâcher")</div>
</div>
@if ( Seated( patient ) )
{
<div class="action" onclick=@(() => EmsActions.RequestTakeOutOfVehicle( patient.GameObject.Id ))>
<i>logout</i><div>Sortir du véhicule</div>
</div>
}
else
{
<div class="action @(NearCar is not null && !patient.Knocked ? "" : "off")" onclick=@(() => EmsActions.RequestPutInVehicle( patient.GameObject.Id ))>
<i>airline_seat_flat</i><div>Mettre dans le véhicule</div>
</div>
}
</div>
<div class="section-label">Facturation</div>
<div class="grid">
<div class="action wide @(Me.EmsGrade >= EmsRules.RankBill ? "" : "off")" onclick=@(() => OpenBill())>
<i>receipt_long</i><div>@BillLabel</div>
</div>
</div>
@if ( Me.EmsGrade >= EmsRules.RankRoster )
{
<div class="section-label">@RosterLabel( patient )</div>
<div class="grid">
@foreach ( var (action, icon, label) in RosterActions( patient ) )
{
var what = action;
<div class="action" onclick=@(() => EmsActions.RequestRoster( patient.GameObject.Id, what ))>
<i>@icon</i><div>@label</div>
</div>
}
</div>
}
}
else
{
<div class="empty">Approchez-vous à deux mètres d'un patient (debout, à terre ou dans un véhicule), puis rouvrez le menu (ou la loupe en haut).</div>
}
<div class="hint">@KitHint</div>
@if ( Me.IsStaff )
{
<div class="btn ghost" onclick=@(() => View = "points")><i>add_location_alt</i> Points du commissariat et de l'hôpital (staff)</div>
}
</div>
}
else if ( View == "facture" )
{
<div class="panel ems">
<div class="head">
<div class="head-icon ems"><i>receipt_long</i></div>
<div class="head-titles">
<div class="head-title">@($"Facture de soins · {NameOf( Target )}")</div>
<div class="head-sub">Ajoutez les soins donnés : le total est recalculé par le secrétariat.</div>
</div>
<div class="icon-btn" onclick=@(() => View = "secours")><i>arrow_back</i></div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
<div class="armory">
@foreach ( var tariff in BillTariffs )
{
var tariffId = tariff.Id;
<div class="arm-row">
<i>@tariff.Icon</i>
<div class="arm-main">
<div class="arm-name">@tariff.Label</div>
<div class="arm-sub">@TariffSub( tariff )</div>
</div>
@if ( BillCount( tariffId ) > 0 )
{
<div class="btn small ghost" onclick=@(() => BillRemove( tariffId ))>−</div>
}
<div class="btn small @(BillLines.Count < EmsRules.BillLinesMax ? "" : "off")" onclick=@(() => BillAdd( tariffId ))>+</div>
</div>
}
</div>
<div class="totals">
<div class="total"><div class="field-label">Total</div><div class="total-value">@Format.Money( BillTotal )</div></div>
</div>
<div class="field-label">Notes (facture)</div>
<TextEntry class="field notes" Placeholder="Circonstances, soins donnés…" Value=@Notes OnTextEdited=@((string v) => { Notes = v ?? ""; })></TextEntry>
<div class="btn primary ems @(BillLines.Count > 0 && Target is not null ? "" : "off")" onclick=@(() => SubmitBill())>
<i>receipt_long</i><div>@($"Établir la facture · {Format.Money( BillTotal )}")</div>
</div>
<div class="hint">@BillHint</div>
</div>
}
else if ( View == "ems_vestiaire" )
{
var emsGrade = EmsRules.GradeOf( Me.EmsGrade );
<div class="panel ems">
<div class="head">
<div class="head-icon ems"><i>@emsGrade.Insignia</i></div>
<div class="head-titles">
<div class="head-title">@($"Vestiaire · {EmsRules.Short} {EmsRules.Service}")</div>
<div class="head-sub">@($"{emsGrade.Name} ({emsGrade.English}) · {Me.RpName}")</div>
</div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
<div class="chips">
<div class="chip"><i>tag</i><div>@($"Matricule {(string.IsNullOrEmpty( Me.EmsBadge ) ? "—" : Me.EmsBadge)}")</div></div>
<div class="chip"><i>cell_tower</i><div>@($"Indicatif {(string.IsNullOrEmpty( Me.EmsCallsign ) ? "—" : Me.EmsCallsign)}")</div></div>
<div class="chip"><i>payments</i><div>@($"Paie en service {Format.Money( emsGrade.Salary )}")</div></div>
</div>
<div class="btn @(Me.EmsOnDuty ? "danger" : "primary ems")" onclick=@(() => EmsActions.RequestDuty( !Me.EmsOnDuty ))>
<i>@(Me.EmsOnDuty ? "logout" : "login")</i><div>@(Me.EmsOnDuty ? "Finir mon service" : "Prendre mon service")</div>
</div>
<div class="btn off">
<i>checkroom</i><div>Tenue de service · tenues à venir</div>
</div>
<div class="section-label">Matériel</div>
@if ( !Me.EmsOnDuty )
{
<div class="empty">Prenez votre service pour accéder au matériel.</div>
}
else
{
<div class="armory">
@foreach ( var kit in EmsRules.Kit )
{
<div class="arm-row">
<i>medical_services</i>
<div class="arm-main">
<div class="arm-name">@kit.Label</div>
<div class="arm-sub">@KitSub( kit )</div>
</div>
</div>
}
</div>
<div class="btn primary ems" onclick=@(() => EmsActions.RequestKit())><i>medical_services</i> Compléter mon sac</div>
<div class="btn ghost" onclick=@(() => EmsActions.RequestReturnKit())><i>assignment_return</i> Rendre le matériel</div>
}
<div class="hint">Le matériel non utilisé est repris à la fin du service. Soigner consomme une trousse (sinon un bandage), stabiliser un bandage.</div>
</div>
}
else if ( View == "ems_accueil" )
{
<div class="panel ems">
<div class="head">
<div class="head-icon ems"><i>local_hospital</i></div>
<div class="head-titles">
<div class="head-title">Accueil · Hôpital d'Union City</div>
<div class="head-sub">@($"{EmsRules.Short} {EmsRules.Service} · HZ BANK {Format.Money( Me.Bank )}")</div>
</div>
<div class="icon-btn" onclick=@(() => Close())><i>close</i></div>
</div>
<div class="section-label">Soins</div>
@if ( EmsDeskMedics )
{
<div class="empty">Des secouristes sont en service : appelez le 911 (secours), ils viennent vous soigner.</div>
}
else
{
<div class="btn primary ems @(Me.Health < 100 && Me.Bank >= EmsDeskPrice ? "" : "off")" onclick=@(() => EmsActions.RequestDeskCare())>
<i>healing</i><div>@DeskCareLabel</div>
</div>
}
<div class="section-label">Vos factures de soins</div>
@if ( EmsDeskError is { } emsError )
{
<div class="empty">@emsError</div>
}
else if ( EmsDeskBills is null )
{
<div class="empty">Lecture du dossier…</div>
}
else if ( EmsDeskBills.Count == 0 )
{
<div class="empty">Aucune facture impayée. Prenez soin de vous !</div>
}
else
{
<div class="armory">
@foreach ( var bill in EmsDeskBills )
{
var billId = bill.Id;
<div class="arm-row">
<i>receipt_long</i>
<div class="arm-main">
<div class="arm-name">@(string.IsNullOrEmpty( bill.Title ) ? $"Facture n° {bill.Id}" : bill.Title)</div>
<div class="arm-sub">@($"N° {bill.Id} · {Format.Money( bill.Amount )}")</div>
</div>
<div class="btn small @(Me.Bank >= bill.Amount ? "" : "off")" onclick=@(() => EmsActions.RequestDeskPay( billId ))>Payer</div>
</div>
}
</div>
<div class="hint">@($"Total dû : {Format.Money( EmsDeskBills.Sum( x => x.Amount ) )}. Payable aussi depuis le téléphone (appli Factures).")</div>
}
</div>
}
</div>
}
}
</root>
@code
{
static PoliceMenu instance;
RoleplayPlayer Me => RoleplayPlayer.Local;
/// <summary>
/// L'écran ouvert : null, « actions », « charges », « vestiaire », « accueil », « points » ; pour
/// les secours, « secours », « facture », « ems_vestiaire », « ems_accueil ».
/// </summary>
string View { get; set; }
Guid TargetId { get; set; }
/// <summary>« amende » ou « ecrou ».</summary>
string ChargeMode { get; set; } = "amende";
readonly HashSet<string> Selected = new();
string Search { get; set; } = "";
string Notes { get; set; } = "";
// ------------------------------------------------------------------ les citoyens connus de l'agent
static readonly HashSet<ulong> known = new();
/// <summary>Change quand l'agent apprend un nom : les fenêtres se redessinent.</summary>
public static int KnownVersion { get; private set; }
/// <summary>Chez l'agent : il a vu les papiers de ce citoyen (identité, fouille, amende, écrou).</summary>
public static void MarkKnown( ulong steamId )
{
if ( known.Add( steamId ) )
KnownVersion++;
}
/// <summary>Le nom RP s'il est connu de l'agent, sinon « Citoyen ».</summary>
public static string NameOf( RoleplayPlayer player ) =>
player is null ? "Citoyen" : player == RoleplayPlayer.Local || known.Contains( player.SteamId ) ? player.RpName : "Citoyen";
// ------------------------------------------------------------------ l'accueil
static List<Backend.JusticeFine> DeskFines;
static string DeskError;
static int deskVersion;
/// <summary>Chez le citoyen : la réponse de l'hôte (PoliceService.SendDeskFines).</summary>
public static void ReceiveDesk( string json, string error )
{
DeskError = error;
try { DeskFines = string.IsNullOrEmpty( json ) ? new() : Json.Deserialize<List<Backend.JusticeFine>>( json ) ?? new(); }
catch { DeskFines = new(); }
deskVersion++;
}
// ------------------------------------------------------------------ ouverture et fermeture
void Open( string view )
{
View = view;
UiLock.Set( "police", true );
}
void Close()
{
View = null;
TargetId = Guid.Empty;
Selected.Clear();
Search = "";
Notes = "";
BillLines.Clear();
UiLock.Set( "police", false );
}
void OpenMain()
{
var me = Me;
if ( me.IsCop )
{
TargetId = PickTarget()?.GameObject.Id ?? Guid.Empty;
Open( "actions" );
}
else if ( me.IsEms )
{
TargetId = PickTarget()?.GameObject.Id ?? Guid.Empty;
Open( "secours" );
}
else if ( me.IsStaff )
Open( "points" );
else if ( me.JobId == PoliceRules.JobId )
HudNotifications.Push( "Prenez votre service au vestiaire du commissariat pour utiliser le menu de police.", "info" );
else if ( me.JobId == EmsRules.JobId )
HudNotifications.Push( "Prenez votre service au vestiaire de l'hôpital pour utiliser le menu des secours.", "info" );
}
void Repick() => TargetId = PickTarget()?.GameObject.Id ?? Guid.Empty;
void OpenCharges( string mode )
{
if ( Target is null )
return;
ChargeMode = mode;
Selected.Clear();
Search = "";
Notes = "";
View = "charges";
}
void Frisk( RoleplayPlayer citizen )
{
// La fenêtre de fouille s'ouvre à la réponse de l'hôte.
Close();
PoliceActions.RequestFrisk( citizen.GameObject.Id );
}
void Identity( RoleplayPlayer citizen )
{
Close();
PoliceActions.RequestIdentity( citizen.GameObject.Id );
}
protected override void OnStart() => instance = this;
protected override void OnDisabled()
{
if ( View is not null )
Close();
}
protected override void OnUpdate()
{
instance = this;
var me = Me;
if ( me is null || me.NeedsCharacter )
{
if ( View is not null )
Close();
return;
}
if ( View is not null && (me.Cuffed || me.OnTheGround) )
{
Close();
return;
}
if ( View is not null && Input.EscapePressed )
{
Input.EscapePressed = false;
if ( View == "charges" )
View = "actions";
else if ( View == "facture" )
View = "secours";
else
Close();
return;
}
var typing = InputFocus.Current is not null;
if ( !typing && Input.Pressed( "PoliceMenu" ) )
{
if ( View is not null )
Close();
else if ( !UiLock.IsLocked )
OpenMain();
return;
}
// E près d'un point du commissariat.
if ( View is null && !typing && Input.Pressed( "use" ) && Prompt is { } prompt )
{
switch ( prompt.Kind )
{
case "vestiaire":
Open( "vestiaire" );
break;
case "accueil":
DeskFines = null;
DeskError = null;
Open( "accueil" );
PoliceActions.RequestDeskFines();
break;
case "ecrou":
if ( BookingTarget is { } suspect )
{
TargetId = suspect.GameObject.Id;
Open( "actions" );
OpenCharges( "ecrou" );
}
break;
case "ems_vestiaire":
Open( "ems_vestiaire" );
break;
case "ems_accueil":
EmsDeskBills = null;
EmsDeskError = null;
Open( "ems_accueil" );
EmsActions.RequestDeskBills();
break;
}
return;
}
switch ( View )
{
case "actions":
if ( !me.IsCop )
Close();
else if ( Target is { } t && Pos( t ).Distance( me.WorldPosition ) > PoliceActions.Reach * 2 )
TargetId = Guid.Empty;
break;
case "charges":
if ( !me.IsCop || Target is not { } c || Pos( c ).Distance( me.WorldPosition ) > PoliceActions.Reach * 2 )
Close();
break;
case "vestiaire":
if ( !Near( ServerSettings.PoliceLocker, PoliceRules.PointRange * 2 ) || me.PoliceGrade < 0 )
Close();
break;
case "accueil":
if ( !Near( ServerSettings.PoliceFrontDesk, PoliceRules.PointRange * 2 ) )
Close();
break;
case "points":
if ( !me.IsStaff )
Close();
break;
case "secours":
if ( !me.IsEms )
Close();
else if ( Target is { } p && Pos( p ).Distance( me.WorldPosition ) > PoliceActions.Reach * 2 )
TargetId = Guid.Empty;
break;
case "facture":
if ( !me.IsEms || Target is not { } billed || Pos( billed ).Distance( me.WorldPosition ) > PoliceActions.Reach * 2 )
Close();
break;
case "ems_vestiaire":
if ( !Near( ServerSettings.EmsLocker, EmsRules.PointRange * 2 ) || me.EmsGrade < 0 )
Close();
break;
case "ems_accueil":
if ( !Near( ServerSettings.EmsDesk, EmsRules.PointRange * 2 ) )
Close();
break;
}
}
// ------------------------------------------------------------------ cibles
static Vector3 Pos( RoleplayPlayer p ) => p.OnTheGround ? p.BodyPosition : p.WorldPosition;
RoleplayPlayer Target => TargetId == Guid.Empty ? null : Scene.Directory.FindByGuid( TargetId )?.GetComponent<RoleplayPlayer>();
static bool Seated( RoleplayPlayer p ) => PoliceService.SeatOf( p ) is not null;
/// <summary>Le citoyen le plus proche (un peu plus loin s'il est assis dans un véhicule), de préférence celui qu'on regarde.</summary>
RoleplayPlayer PickTarget()
{
var me = Me;
var eye = me.GetComponent<PlayerController>()?.EyePosition ?? me.WorldPosition;
var forward = Scene.Camera?.WorldRotation.Forward ?? me.WorldRotation.Forward;
return RoleplayPlayer.All
.Where( p => p != me && !p.NeedsCharacter && !string.IsNullOrEmpty( p.RpName ) )
.Select( p => (Player: p, Distance: Pos( p ).Distance( me.WorldPosition ), Aim: Vector3.Dot( (Pos( p ) + Vector3.Up * 40 - eye).Normal, forward )) )
.Where( x => x.Distance <= (Seated( x.Player ) ? 150 : PoliceActions.MenuReach) )
.OrderBy( x => x.Distance - x.Aim * 30 )
.Select( x => x.Player )
.FirstOrDefault();
}
/// <summary>Le menotté à écrouer : le plus proche de l'agent.</summary>
RoleplayPlayer BookingTarget => Me is not { } me ? null : RoleplayPlayer.All
.Where( p => p != me && p.Cuffed && !p.Jailed && Pos( p ).Distance( me.WorldPosition ) <= PoliceActions.MenuReach * 1.5f )
.OrderBy( p => Pos( p ).Distance( me.WorldPosition ) )
.FirstOrDefault();
Vehicle NearCar => Me is null ? null : PoliceActions.NearestVehicle( Me.WorldPosition, PoliceActions.VehicleRange );
bool Near( ServerSettings.Point point, float range = PoliceRules.PointRange ) =>
point is not null && Me is not null && Me.WorldPosition.Distance( point.Position ) <= range;
bool NearBooking => Near( ServerSettings.PoliceBooking, PoliceActions.BookingRange );
bool CanBook( RoleplayPlayer citizen ) => citizen.Cuffed && !citizen.Jailed && NearBooking && Me.PoliceGrade >= PoliceRules.RankArrest;
string BookLabel( RoleplayPlayer citizen ) =>
Me.PoliceGrade < PoliceRules.RankArrest ? $"Écrouer (grade {PoliceRules.GradeOf( PoliceRules.RankArrest ).Name})"
: !NearBooking ? "Écrouer (au point d'écrou)"
: !citizen.Cuffed ? "Écrouer (menottez d'abord)"
: "Écrouer";
/// <summary>Le bandeau E d'un point proche : (sorte, texte, icône), ou null.</summary>
(string Kind, string Text, string Icon)? Prompt
{
get
{
var me = Me;
if ( me is null || UiLock.IsLocked || me.Cuffed || me.OnTheGround || Vehicle.OfLocalPlayer is not null )
return null;
if ( me.JobId == PoliceRules.JobId && me.PoliceGrade >= 0 && Near( ServerSettings.PoliceLocker ) )
return ("vestiaire", "Vestiaire", "checkroom");
if ( me.IsCop && me.PoliceGrade >= PoliceRules.RankArrest && Near( ServerSettings.PoliceBooking ) && BookingTarget is not null )
return ("ecrou", "Écrouer", "gavel");
if ( Near( ServerSettings.PoliceFrontDesk ) )
return ("accueil", "Accueil · payer ses amendes", "support_agent");
if ( me.JobId == EmsRules.JobId && me.EmsGrade >= 0 && Near( ServerSettings.EmsLocker ) )
return ("ems_vestiaire", "Vestiaire des secours", "checkroom");
if ( Near( ServerSettings.EmsDesk ) )
return ("ems_accueil", "Accueil de l'hôpital · soins et factures", "local_hospital");
return null;
}
}
// ------------------------------------------------------------------ textes
string DutyText
{
get
{
var grade = PoliceRules.GradeOf( Me.PoliceGrade ).Name;
return string.IsNullOrEmpty( Me.PoliceCallsign ) ? $"En service · {grade}" : $"En service · {grade} · {Me.PoliceCallsign}";
}
}
static string Clock( int seconds ) => $"{Math.Max( 0, seconds ) / 60}:{Math.Max( 0, seconds ) % 60:00}";
string JailPercent
{
get
{
var total = Math.Max( 1, Me.JailSecondsTotal );
var left = Math.Clamp( Me.JailSecondsLeft, 0, total );
return $"{(100f * left / total).ToString( "0.#", System.Globalization.CultureInfo.InvariantCulture )}%";
}
}
string TargetSub => Target is { } t
? (known.Contains( t.SteamId ) ? "Identité vérifiée" : "Identité non vérifiée · « Identité » pour voir ses papiers")
: "Le citoyen le plus proche (~2 m)";
static IEnumerable<(string Icon, string Text)> StateChips( RoleplayPlayer p )
{
if ( p.Cuffed )
yield return ("link", "Menotté");
if ( p.EscortedBy != Guid.Empty )
yield return ("directions_walk", "Escorté");
if ( Seated( p ) )
yield return ("directions_car", "En véhicule");
if ( p.Downed )
yield return ("emergency", "Inconscient");
if ( p.IsCop )
yield return ("local_police", "Agent en service");
if ( !p.Cuffed && p.EscortedBy == Guid.Empty && !Seated( p ) && !p.Downed && !p.IsCop )
yield return ("person", "Libre");
}
static string CarLabel( Vehicle car )
{
var name = car.Info?.Name ?? "Véhicule";
var state = car.Locked ? "verrouillé" : "ouvert";
var owner = car.IsEms ? " · ambulance de l'UCFD" : car.IsPolice ? " · flotte de l'UCPD" : car.OwnerId == 0 ? " · sans propriétaire" : "";
return $"{name} · {car.Plate} · {state}{owner}";
}
string ArmorySub( PoliceRules.ArmoryItem item )
{
if ( Me.PoliceGrade < item.MinRank )
return $"Grade {PoliceRules.GradeOf( item.MinRank ).Name} au moins";
return string.IsNullOrEmpty( item.Ammo ) || item.AmmoCount <= 0
? "Arme de service"
: $"Arme de service · {item.AmmoCount} munitions";
}
bool HasService( string weaponId )
{
var itemId = WeaponItems.WeaponItemId( weaponId );
return Me.PublishedInventory.Any( x => x.Item == itemId && WeaponData.Read( x.Data )?.Service == true );
}
string PointSub( ServerSettings.Point point )
{
if ( point is null )
return "Non posé";
var meters = Me is null ? 0 : Me.WorldPosition.Distance( point.Position ) * 0.0254f;
return $"{point.X:0}, {point.Y:0}, {point.Z:0} — cap {point.Yaw:0}° · à {meters:0} m";
}
// ------------------------------------------------------------------ code pénal
IReadOnlyList<PoliceService.PenalEntry> Penal => PoliceService.Current?.PenalCode ?? new List<PoliceService.PenalEntry>();
int PenalCount => Penal.Count;
bool Matches( PoliceService.PenalEntry entry )
{
if ( ChargeMode == "amende" && entry.Class == "crime" )
return false;
var q = Search?.Trim().ToLowerInvariant();
return string.IsNullOrEmpty( q ) || $"{entry.Id} {entry.Title} {entry.Category} {entry.Description}".ToLowerInvariant().Contains( q );
}
IEnumerable<IGrouping<string, PoliceService.PenalEntry>> PenalGroups => Penal.Where( Matches ).GroupBy( x => x.Category );
List<PoliceService.PenalEntry> Chosen => Penal.Where( x => Selected.Contains( x.Id ) ).ToList();
int FineTotal => Chosen.Sum( x => x.Fine );
int JailTotal => Math.Min( PoliceActions.MaxJailMinutes, Chosen.Sum( x => x.JailMinutes ) );
bool CanSubmit => Target is not null && Selected.Count > 0 && Selected.Count <= PoliceActions.MaxCharges
&& (ChargeMode != "ecrou" || (JailTotal > 0 && CanBook( Target )));
void Toggle( string id )
{
if ( !Selected.Remove( id ) && Selected.Count < PoliceActions.MaxCharges )
Selected.Add( id );
}
static string ClassName( string cls ) => PoliceRules.Classes.FirstOrDefault( x => x.Id == cls ).Name ?? cls;
static string ClassColor( string cls ) => PoliceRules.Classes.FirstOrDefault( x => x.Id == cls ).Color ?? "#6b7280";
string ChargeSub( PoliceService.PenalEntry entry )
{
var jail = entry.JailMinutes > 0 ? $" · {entry.JailMinutes} min de prison" : "";
return $"{Format.Money( entry.Fine )}{jail}";
}
void Submit()
{
if ( !CanSubmit )
return;
var ids = string.Join( ",", Selected );
if ( ChargeMode == "ecrou" )
PoliceActions.RequestArrest( TargetId, ids, Notes );
else
PoliceActions.RequestFine( TargetId, ids, Notes );
Close();
}
// ------------------------------------------------------------------ les secours (UCFD EMS)
string EmsDutyText
{
get
{
var grade = EmsRules.GradeOf( Me.EmsGrade ).Name;
return string.IsNullOrEmpty( Me.EmsCallsign ) ? $"En service · {EmsRules.Short} · {grade}" : $"En service · {grade} · {Me.EmsCallsign}";
}
}
string PatientSub => Target is { } t
? (t.Downed ? $"Inconscient · {PoliceService.FormatJail( t.DownedLeft )} avant le décès" : $"Conscient · vie {MathF.Round( t.Health ):0} %")
: "Le patient le plus proche (~2 m)";
static IEnumerable<(string Icon, string Text)> PatientChips( RoleplayPlayer p )
{
if ( p.Downed )
yield return ("emergency", "Inconscient");
else
yield return ("favorite", $"Vie {MathF.Round( p.Health ):0} %");
if ( p.EscortedBy != Guid.Empty )
yield return ("directions_walk", "Escorté");
if ( Seated( p ) )
yield return ("directions_car", "En véhicule");
if ( p.Cuffed )
yield return ("link", "Menotté");
if ( p.IsEms )
yield return ("medical_services", "Secouriste en service");
}
// Le diagnostic reçu de l'hôte (EmsActions.RequestDiagnose), pour un patient.
static Guid diagnosisOf;
static List<string> diagnosisLines;
static RealTimeSince diagnosisSince;
static int diagnosisVersion;
/// <summary>Chez le secouriste : le diagnostic d'un patient (lignes séparées par « \n »).</summary>
public static void ReceiveDiagnosis( Guid patient, string text )
{
diagnosisOf = patient;
diagnosisLines = (text ?? "").Split( '\n', StringSplitOptions.RemoveEmptyEntries ).ToList();
diagnosisSince = 0;
diagnosisVersion++;
}
/// <summary>Le dernier diagnostic de ce patient, s'il a moins d'une minute.</summary>
static List<string> DiagnosisFor( RoleplayPlayer patient ) =>
patient is not null && diagnosisOf == patient.GameObject.Id && diagnosisSince < 60f ? diagnosisLines : null;
int KitCount( string itemId ) => Me?.PublishedInventory.Where( x => x.Item == itemId ).Sum( x => x.Count ) ?? 0;
/// <summary>Une trousse ou un bandage dans le sac : de quoi soigner ou stabiliser.</summary>
bool HasCare => KitCount( "trousse" ) + KitCount( "bandage" ) > 0;
string KitHint => $"Sac : {KitCount( "trousse" )} trousse(s), {KitCount( "bandage" )} bandage(s). Réanimer : aussi par {HorizonHud.KeyName( "use" )} maintenu sur le corps.";
string KitSub( EmsRules.KitItem kit ) => $"Dotation {kit.Count} · dans le sac : {KitCount( kit.ItemId )}";
string BillLabel => Me.EmsGrade >= EmsRules.RankBill ? "Facture de soins" : $"Facture de soins (grade {EmsRules.GradeOf( EmsRules.RankBill ).Name})";
// La facture en cours : les soins choisis (un même soin peut revenir).
readonly List<string> BillLines = new();
IEnumerable<EmsRules.Tariff> BillTariffs => EmsRules.Tariffs.Where( x => x.Id != EmsRules.DeskTariff );
int BillCount( string id ) => BillLines.Count( x => x == id );
int BillTotal => BillLines.Sum( x => EmsRules.TariffOf( x )?.Price ?? 0 );
string TariffSub( EmsRules.Tariff tariff ) =>
BillCount( tariff.Id ) is var n and > 0 ? $"{Format.Money( tariff.Price )} · ×{n} sur la facture" : Format.Money( tariff.Price );
string BillHint => "Payable par le patient sur son téléphone (appli Factures) ou à l'accueil de l'hôpital.";
void BillAdd( string id )
{
if ( BillLines.Count < EmsRules.BillLinesMax )
BillLines.Add( id );
}
void BillRemove( string id ) => BillLines.Remove( id );
void OpenBill()
{
if ( Target is null || Me.EmsGrade < EmsRules.RankBill )
return;
BillLines.Clear();
Notes = "";
View = "facture";
}
void SubmitBill()
{
if ( BillLines.Count == 0 || Target is null )
return;
EmsActions.RequestBill( TargetId, string.Join( ",", BillLines ), Notes );
Close();
}
/// <summary>Le titre de la section des effectifs : le grade du joueur visé aux secours, s'il y figure.</summary>
string RosterLabel( RoleplayPlayer target ) =>
PoliceService.Current?.MedicOf( target.SteamId ) is { } medic
? $"Effectifs · {EmsRules.GradeOf( medic.Grade ).Name}"
: "Effectifs · pas inscrit aux secours";
/// <summary>Ce qu'un gradé peut faire du joueur visé (l'hôte et l'API revérifient).</summary>
IEnumerable<(string Action, string Icon, string Label)> RosterActions( RoleplayPlayer target )
{
var medic = PoliceService.Current?.MedicOf( target.SteamId );
if ( medic is null )
{
yield return ("recruter", "person_add", $"Recruter ({EmsRules.GradeOf( 0 ).Name})");
yield break;
}
if ( medic.Grade >= Me.EmsGrade )
yield break;
if ( medic.Grade + 1 < Me.EmsGrade )
yield return ("promouvoir", "arrow_upward", $"Promouvoir {EmsRules.GradeOf( medic.Grade + 1 ).Name}");
if ( medic.Grade > 0 )
yield return ("retrograder", "arrow_downward", $"Rétrograder {EmsRules.GradeOf( medic.Grade - 1 ).Name}");
yield return ("renvoyer", "person_remove", "Renvoyer des secours");
}
// L'accueil de l'hôpital : la réponse de l'hôte (PoliceService.SendEmsDesk).
static List<Backend.EmsBill> EmsDeskBills;
static string EmsDeskError;
static int EmsDeskPrice;
static bool EmsDeskMedics;
static int emsDeskVersion;
/// <summary>Chez le patient : ses factures impayées, le prix des soins à l'accueil, et si des secouristes sont en service.</summary>
public static void ReceiveEmsDesk( string json, string error, int deskPrice, bool medicsOnDuty )
{
EmsDeskError = error;
EmsDeskPrice = deskPrice;
EmsDeskMedics = medicsOnDuty;
try { EmsDeskBills = string.IsNullOrEmpty( json ) ? new() : Json.Deserialize<List<Backend.EmsBill>>( json ) ?? new(); }
catch { EmsDeskBills = new(); }
emsDeskVersion++;
}
string DeskCareLabel =>
Me.Health >= 100 ? "Vous n'avez besoin d'aucun soin"
: Me.Bank < EmsDeskPrice ? $"Se faire soigner · {Format.Money( EmsDeskPrice )} (solde insuffisant)"
: $"Se faire soigner · {Format.Money( EmsDeskPrice )} (HZ BANK)";
protected override int BuildHash()
{
var me = Me;
var target = Target;
var car = View is "actions" or "secours" ? NearCar : null;
return HashCode.Combine(
HashCode.Combine( View, TargetId, ChargeMode, Search, Notes, string.Join( ",", Selected ), string.Join( ",", BillLines ), me?.AppearanceJson ),
HashCode.Combine( me?.IsCop, me?.PoliceGrade, me?.PoliceCallsign, me?.JailSecondsLeft, me?.Cuffed, me?.EscortedBy, me?.Bank, me?.InventoryJson ),
HashCode.Combine( target?.Cuffed, target?.EscortedBy, target is not null && Seated( target ), target?.Downed, KnownVersion, deskVersion, target?.DownedLeft, MathF.Ceiling( target?.Health ?? 0 ) ),
HashCode.Combine( car?.Plate, car?.Locked, Prompt?.Kind, ServerSettings.Version, PoliceService.Current?.PenalJson, me?.IsStaff, PoliceService.Current?.EmsStaffJson ),
HashCode.Combine( me?.IsEms, me?.EmsGrade, me?.EmsCallsign, MathF.Ceiling( me?.Health ?? 0 ), diagnosisVersion, emsDeskVersion, diagnosisSince > 60f, PoliceShowroom.Framing ),
View == "points" && me is not null ? (int)(me.WorldPosition.Length / 40) : 0 );
}
}