Static host-side RPC handler for the EMS (paramedic) job. Validates the caller is an on-duty medic of the right rank and within reach, then runs diagnose, heal, revive, stabilize, escort, vehicle loading, billing, roster, duty, locker/kit and hospital desk requests, writing audit log entries.
namespace HorizonRP;
/// <summary>
/// Ce que le menu des secours (F6 en service, vue « Secours » de UI/PoliceMenu.razor), le vestiaire
/// et l'accueil de l'hôpital demandent à l'hôte, sur le modèle exact de Police/PoliceActions.cs.
/// Chaque demande est revérifiée ici : l'appelant est un secouriste en service
/// (<see cref="RoleplayPlayer.IsEms"/>), en état d'agir, du grade voulu (EmsRules.Rank*), à portée
/// du patient (~2,8 m) ; le client ne désigne qu'un objet et des ids de tarifs, jamais un montant
/// (l'API le recalcule), ni une quantité de soins. Tout est journalisé et notifié aux deux parties.
///
/// - <b>Diagnostiquer</b> : vie, faim, conscience et temps qui reste avant la mort, lésions.
/// - <b>Soigner</b> un conscient : une trousse (sinon un bandage) du sac, la vie remonte peu à peu
/// (EmsRules.HealSeconds) tant que le secouriste reste près de lui.
/// - <b>Réanimer</b> un inconscient : la réanimation existante (HorizonGame.Revive), réservée aux
/// secouristes en service ; aussi par E maintenu sur le corps (RoleplayPlayer.Death.cs).
/// - <b>Stabiliser</b> un inconscient : un bandage, et EmsRules.StabilizeSeconds de plus avant la
/// mort (deux fois au plus par chute).
/// - <b>Escorter</b> un blessé debout, le <b>mettre dans un véhicule</b> (un inconscient aussi :
/// installé à l'arrière, il ne descend pas seul), l'en <b>sortir</b> : les mécanismes de la police.
/// - <b>Facture de soins</b> (EmsRules.Tariffs), payable dans l'appli Factures du téléphone ou à l'accueil.
/// </summary>
public static class EmsActions
{
static RoleplayPlayer Caller() =>
RoleplayPlayer.All.FirstOrDefault( x => Rpc.Caller is not null && x.Network.OwnerId == Rpc.Caller.Id && x.ProfileLoaded );
/// <summary>Le secouriste en service qui appelle, en état d'agir et du grade voulu ; sinon null (refus notifié).</summary>
static RoleplayPlayer Medic( int minRank = 0, string what = null )
{
var player = Caller();
if ( player is null )
return null;
if ( !player.IsEms )
{
player.Notify( $"Réservé aux secouristes de l'{EmsRules.Short} en service.", "erreur" );
return null;
}
if ( player.OnTheGround || player.Cuffed || player.Jailed || player.NeedsCharacter )
return null;
if ( player.EmsGrade < minRank )
{
player.Notify( $"{what ?? "Cette action"} : grade de {EmsRules.GradeOf( minRank ).Name} au moins.", "erreur" );
return null;
}
return player;
}
/// <summary>Où l'on mesure un joueur : son corps s'il est à terre.</summary>
static Vector3 At( RoleplayPlayer p ) => p.OnTheGround ? p.BodyPosition : p.WorldPosition;
/// <summary>Le patient visé, à portée ; sinon null (refus notifié). Un peu plus loin s'il est assis dans un véhicule.</summary>
static RoleplayPlayer Patient( RoleplayPlayer medic, Guid targetId )
{
var target = Game.ActiveScene?.Directory.FindByGuid( targetId )?.GetComponent<RoleplayPlayer>();
if ( target is null || target == medic || !target.ProfileLoaded || target.NeedsCharacter )
return null;
if ( At( target ).Distance( medic.WorldPosition ) > (target.Seated ? PoliceActions.Reach * 1.6f : PoliceActions.Reach) )
{
medic.Notify( "Approchez-vous du patient.", "erreur" );
return null;
}
return target;
}
static bool StillThere( RoleplayPlayer medic, RoleplayPlayer target ) =>
medic.IsValid() && target.IsValid() && medic.IsEms && !medic.OnTheGround
&& At( target ).Distance( medic.WorldPosition ) <= (target.Seated ? PoliceActions.Reach * 1.8f : PoliceActions.Reach * 1.3f);
static string Who( RoleplayPlayer p ) => $"{p.RpName} ({p.SteamId})";
static void Journal( string action, RoleplayPlayer subject, string details ) =>
HorizonGame.Current?.AddLog( action, subject.SteamId, details );
/// <summary>Un soin à la fois par secouriste (un double clic ne lance pas deux trousses).</summary>
static readonly HashSet<ulong> busy = new();
/// <summary>Le premier objet de ce type dans les poches, ou null.</summary>
static InventorySlot SlotOf( RoleplayPlayer player, string itemId ) =>
player.Inventory.Where( x => x.Item == itemId && x.Count > 0 ).OrderBy( x => x.Slot ).FirstOrDefault();
/// <summary>Consomme un objet du sac (et le retire de la dotation reprise en fin de service).</summary>
static void Consume( RoleplayPlayer medic, InventorySlot slot )
{
var itemId = slot.Item;
if ( medic.HeldKey == itemId )
medic.HeldKey = null;
medic.RemoveFrom( slot, 1 );
PoliceService.Current?.NoteKitUsed( medic, itemId );
}
// ------------------------------------------------------------------ examen et soins
/// <summary>Diagnostiquer : l'état du patient s'affiche chez le secouriste (vue « Secours »).</summary>
[Rpc.Host]
public static void RequestDiagnose( Guid targetId )
{
var medic = Medic( EmsRules.RankCare );
if ( medic is null || Patient( medic, targetId ) is not { } target )
return;
medic.EmsDiagnosis( target.GameObject.Id, Diagnosis( target ) );
if ( !target.Downed )
target.Notify( "Un secouriste vous examine.", "info" );
medic.PoliceKnow( target.SteamId );
Journal( "ems_soins", target, $"{PoliceService.EmsSignature( medic )} examine {Who( target )}" );
}
static string Diagnosis( RoleplayPlayer p )
{
var lines = new List<string>();
if ( p.Downed )
{
lines.Add( $"Inconscient · décès dans {PoliceService.FormatJail( p.DownedLeft )} sans réanimation" );
var done = PoliceService.Current?.StabilizedCount( p ) ?? 0;
lines.Add( $"Pouls filant, ne répond pas · stabilisé {done}/{EmsRules.StabilizeMax}" );
}
else
lines.Add( p.Health < 30 ? "Conscient, en détresse" : "Conscient, orienté" );
lines.Add( $"Vie : {MathF.Round( p.Health ):0} %" );
lines.Add( $"Faim : {MathF.Round( p.Hunger ):0} %{(p.Hunger < 15 ? " (dénutrition)" : "")}" );
lines.Add( $"Lésions : {Injury( p )}" );
return string.Join( "\n", lines );
}
/// <summary>Ce que voit un secouriste : la nature des blessures, jamais l'auteur ni l'arme (le journal les a).</summary>
static string Injury( RoleplayPlayer p )
{
var cause = (p.DownCause ?? "").ToLowerInvariant();
if ( p.Downed )
{
if ( cause.StartsWith( "abattu" ) )
return cause.Contains( "tête" ) ? "plaie par balle à la tête" : "plaies par balle";
if ( cause.StartsWith( "renversé" ) )
return "polytraumatisme (renversé par un véhicule)";
if ( cause.StartsWith( "faim" ) )
return "dénutrition sévère";
if ( cause.Contains( "explos" ) || cause.Contains( "grenade" ) || cause.Contains( "feu" ) || cause.Contains( "brûl" ) )
return "brûlures et blessures par explosion";
return "traumatisme, cause indéterminée";
}
return p.Health < 40 ? "blessures graves" : p.Health < 85 ? "blessures légères" : "aucune lésion visible";
}
/// <summary>Soigner un patient conscient : une trousse (sinon un bandage) du sac, la vie remonte peu à peu.</summary>
[Rpc.Host]
public static void RequestHeal( Guid targetId )
{
var medic = Medic( EmsRules.RankCare, "Soigner" );
if ( medic is null || Patient( medic, targetId ) is not { } target )
return;
if ( target.Downed )
{
medic.Notify( "Le patient est inconscient : réanimez-le (ou stabilisez-le d'abord).", "erreur" );
return;
}
if ( target.Health >= 100 )
{
medic.Notify( "Le patient n'a besoin d'aucun soin.", "info" );
return;
}
var slot = SlotOf( medic, "trousse" ) ?? SlotOf( medic, "bandage" );
if ( slot is null )
{
medic.Notify( "Plus de trousse ni de bandage : réapprovisionnez-vous au vestiaire de l'hôpital.", "erreur" );
return;
}
if ( !busy.Add( medic.SteamId ) )
return;
var trousse = slot.Item == "trousse";
var name = slot.Definition?.Name ?? slot.Item;
Consume( medic, slot );
_ = HealAsync( medic, target, trousse ? EmsRules.HealTrousse : EmsRules.HealBandage, name );
}
static async Task HealAsync( RoleplayPlayer medic, RoleplayPlayer target, float amount, string itemName )
{
try
{
medic.Notify( $"Soins en cours ({itemName}, {EmsRules.HealSeconds:0} s) : restez près du patient.", "info" );
target.Notify( "Un secouriste vous soigne.", "info" );
const int Steps = 12;
var given = 0f;
for ( var i = 0; i < Steps; i++ )
{
await GameTask.DelaySeconds( EmsRules.HealSeconds / Steps );
if ( !StillThere( medic, target ) || target.Downed )
{
if ( medic.IsValid() )
medic.Notify( "Soins interrompus.", "erreur" );
break;
}
var step = MathF.Min( amount / Steps, 100 - target.Health );
target.Health = MathF.Min( 100, target.Health + step );
given += step;
if ( target.Health >= 100 )
break;
}
if ( !target.IsValid() || !medic.IsValid() || given <= 0 )
return;
medic.Notify( $"Patient soigné : +{given:0} points de vie ({target.Health:0} %).", "info" );
target.Notify( $"Vous êtes soigné : {target.Health:0} % de vie.", "info" );
Journal( "ems_soins", target, $"{PoliceService.EmsSignature( medic )} soigne {Who( target )} ({itemName}, +{given:0} PV)" );
}
finally
{
busy.Remove( medic.SteamId );
}
}
/// <summary>Réanimer un inconscient (la réanimation existante, réservée aux secouristes en service).</summary>
[Rpc.Host]
public static void RequestRevive( Guid targetId )
{
var medic = Medic( EmsRules.RankRevive, "Réanimer" );
if ( medic is null || Patient( medic, targetId ) is not { } target )
return;
if ( !target.Downed )
{
medic.Notify( "Le patient est conscient : soignez-le.", "info" );
return;
}
if ( !busy.Add( medic.SteamId ) )
return;
_ = ReviveAsync( medic, target );
}
static async Task ReviveAsync( RoleplayPlayer medic, RoleplayPlayer target )
{
try
{
var seconds = MathF.Max( 1f, ServerSettings.ReviveSeconds );
medic.Notify( $"Réanimation en cours ({seconds:0} s) : massage cardiaque, restez près du patient.", "info" );
await GameTask.DelaySeconds( seconds );
if ( !StillThere( medic, target ) || !target.Downed )
{
if ( medic.IsValid() && target.IsValid() && target.Downed )
medic.Notify( "Réanimation interrompue.", "erreur" );
return;
}
Journal( "ems_soins", target, $"{PoliceService.EmsSignature( medic )} réanime {Who( target )}" );
HorizonGame.Current?.Revive( target, medic );
}
finally
{
busy.Remove( medic.SteamId );
}
}
/// <summary>Stabiliser un inconscient : un bandage, et du temps en plus avant la mort.</summary>
[Rpc.Host]
public static void RequestStabilize( Guid targetId )
{
var medic = Medic( EmsRules.RankCare, "Stabiliser" );
if ( medic is null || Patient( medic, targetId ) is not { } target || PoliceService.Current is not { } service )
return;
if ( !target.Downed )
{
medic.Notify( "On ne stabilise qu'un patient inconscient.", "info" );
return;
}
if ( service.StabilizedCount( target ) >= EmsRules.StabilizeMax )
{
medic.Notify( "Le patient ne peut plus être stabilisé : il faut le réanimer.", "erreur" );
return;
}
var slot = SlotOf( medic, "bandage" ) ?? SlotOf( medic, "trousse" );
if ( slot is null )
{
medic.Notify( "Il faut un bandage (ou une trousse) pour stabiliser : vestiaire de l'hôpital.", "erreur" );
return;
}
if ( !busy.Add( medic.SteamId ) )
return;
var name = slot.Definition?.Name ?? slot.Item;
Consume( medic, slot );
_ = StabilizeAsync( medic, target, service, name );
}
static async Task StabilizeAsync( RoleplayPlayer medic, RoleplayPlayer target, PoliceService service, string itemName )
{
try
{
medic.Notify( $"Stabilisation ({EmsRules.StabilizeDuration:0} s) : compression, position latérale…", "info" );
await GameTask.DelaySeconds( EmsRules.StabilizeDuration );
if ( !StillThere( medic, target ) || !target.Downed )
{
if ( medic.IsValid() )
medic.Notify( "Stabilisation interrompue.", "erreur" );
return;
}
target.DownedLeft += EmsRules.StabilizeSeconds;
service.NoteStabilized( target );
medic.Notify( $"Patient stabilisé : {PoliceService.FormatJail( target.DownedLeft )} avant le décès.", "info" );
Journal( "ems_soins", target, $"{PoliceService.EmsSignature( medic )} stabilise {Who( target )} ({itemName}, +{EmsRules.StabilizeSeconds} s)" );
}
finally
{
busy.Remove( medic.SteamId );
}
}
// ------------------------------------------------------------------ escorte et véhicule
/// <summary>Escorter un blessé debout (il suit le secouriste, posé devant lui), ou le lâcher.</summary>
[Rpc.Host]
public static void RequestEscort( Guid targetId, bool escort )
{
var medic = Medic( EmsRules.RankCare );
if ( medic is null || Patient( medic, targetId ) is not { } target )
return;
if ( !escort )
{
if ( target.EscortedBy != medic.GameObject.Id )
return;
target.EscortedBy = Guid.Empty;
medic.Notify( "Vous lâchez le patient.", "info" );
return;
}
if ( target.OnTheGround || target.Seated || medic.Seated )
{
medic.Notify( target.Downed ? "Un inconscient ne s'escorte pas : installez-le dans un véhicule." : "Le patient doit être debout.", "erreur" );
return;
}
if ( target.EscortedBy != Guid.Empty )
{
medic.Notify( "Quelqu'un escorte déjà ce patient.", "erreur" );
return;
}
foreach ( var other in RoleplayPlayer.All.Where( x => x.EscortedBy == medic.GameObject.Id && x != target ).ToList() )
other.EscortedBy = Guid.Empty;
target.EscortedBy = medic.GameObject.Id;
target.Notify( "Un secouriste vous soutient et vous accompagne.", "info" );
medic.Notify( "Vous escortez le patient.", "info" );
Journal( "ems_soins", target, $"{PoliceService.EmsSignature( medic )} escorte {Who( target )}" );
}
/// <summary>Installer le patient (debout, escorté ou inconscient) à l'arrière du véhicule le plus proche.</summary>
[Rpc.Host]
public static void RequestPutInVehicle( Guid targetId )
{
var medic = Medic( EmsRules.RankCare );
if ( medic is null || Patient( medic, targetId ) is not { } target )
return;
if ( target.Seated || target.Knocked )
return;
if ( target.EscortedBy != Guid.Empty && target.EscortedBy != medic.GameObject.Id )
{
medic.Notify( "Ce patient est escorté par quelqu'un d'autre.", "erreur" );
return;
}
if ( PoliceActions.NearestVehicle( medic.WorldPosition, PoliceActions.VehicleRange ) is not { } vehicle )
{
medic.Notify( "Aucun véhicule à côté de vous.", "erreur" );
return;
}
if ( vehicle.Locked || !vehicle.Usable )
{
medic.Notify( vehicle.Locked ? "Le véhicule est verrouillé." : "Ce véhicule est inutilisable.", "erreur" );
return;
}
var seat = vehicle.Seats.Where( x => !x.IsOccupied && x.Index >= 2 ).OrderBy( x => x.Index ).FirstOrDefault()
?? vehicle.Seats.FirstOrDefault( x => !x.IsOccupied && x.Index == 1 );
if ( seat is null || target.GetComponent<PlayerController>() is not { } pc || target.Network.Owner is not { } owner )
{
medic.Notify( "Plus de place dans ce véhicule.", "erreur" );
return;
}
target.EscortedBy = Guid.Empty;
// Comme la police (BaseChair.EnterChair) : le siège est pris chez le joueur, qui simule son
// personnage. Un inconscient y perd son ragdoll (RoleplayPlayer.Death.cs) et n'en descend
// pas seul (VehicleSeat.CanLeave).
using ( Rpc.FilterInclude( owner ) )
seat.Sit( pc );
if ( !target.Downed )
target.Notify( "On vous installe dans le véhicule.", "info" );
medic.Notify( $"Patient installé dans {vehicle.Plate}.", "info" );
Journal( "ems_soins", target, $"{PoliceService.EmsSignature( medic )} installe {Who( target )}{(target.Downed ? " (inconscient)" : "")} dans {vehicle.Plate}" );
}
/// <summary>Faire descendre un patient d'un véhicule (un inconscient y retombe au sol, à la portière).</summary>
[Rpc.Host]
public static void RequestTakeOutOfVehicle( Guid targetId )
{
var medic = Medic( EmsRules.RankCare );
var target = Game.ActiveScene?.Directory.FindByGuid( targetId )?.GetComponent<RoleplayPlayer>();
if ( medic is null || target is null || target == medic || PoliceService.SeatOf( target ) is not { } seat )
return;
if ( PoliceActions.BodyDistance( seat.Vehicle, medic.WorldPosition ) > PoliceActions.VehicleRange )
{
medic.Notify( "Approchez-vous du véhicule.", "erreur" );
return;
}
if ( seat.Vehicle.Locked )
{
medic.Notify( "Le véhicule est verrouillé.", "erreur" );
return;
}
PoliceService.Unseat( target );
if ( !target.Downed )
target.Notify( "On vous fait descendre du véhicule.", "info" );
medic.Notify( "Patient sorti du véhicule.", "info" );
Journal( "ems_soins", target, $"{PoliceService.EmsSignature( medic )} fait descendre {Who( target )} de {seat.Vehicle.Plate}" );
}
// ------------------------------------------------------------------ facture
/// <summary>Une facture à la fois par secouriste (un double clic n'en dresse pas deux).</summary>
static readonly HashSet<ulong> billing = new();
/// <summary>Établir une facture de soins : ids de EmsRules.Tariffs séparés par des virgules (un même soin peut revenir).</summary>
[Rpc.Host]
public static void RequestBill( Guid targetId, string tariffIds, string notes )
{
var medic = Medic( EmsRules.RankBill, "Facturer des soins" );
if ( medic is null || Patient( medic, targetId ) is not { } target )
return;
var items = new List<string>();
foreach ( var id in (tariffIds ?? "").Split( ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ) )
{
if ( EmsRules.TariffOf( id ) is null || id == EmsRules.DeskTariff )
{
medic.Notify( "Soin inconnu sur la facture.", "erreur" );
return;
}
items.Add( id );
}
if ( items.Count == 0 || items.Count > EmsRules.BillLinesMax )
{
medic.Notify( $"Choisissez de 1 à {EmsRules.BillLinesMax} soins.", "erreur" );
return;
}
if ( !Backend.IsOnline )
{
medic.Notify( "Partie locale : les factures sont en base, facture impossible ici.", "erreur" );
return;
}
if ( !billing.Add( medic.SteamId ) )
return;
notes = (notes ?? "").Trim();
if ( notes.Length > PoliceActions.NotesMax )
notes = notes[..PoliceActions.NotesMax];
_ = BillAsync( medic, target, items, notes );
}
static async Task BillAsync( RoleplayPlayer medic, RoleplayPlayer target, List<string> items, string notes )
{
try
{
var response = await Backend.EmsBillCreate( target.SteamId, items, notes, PoliceService.EmsSignature( medic ), medic.SteamId );
if ( !medic.IsValid() )
return;
if ( response?.Ok != true )
{
medic.Notify( response?.Error ?? "Le secrétariat ne répond pas : facture non enregistrée.", "erreur" );
return;
}
var total = response.Amount > 0 ? response.Amount : items.Sum( x => EmsRules.TariffOf( x )?.Price ?? 0 );
if ( target.IsValid() )
{
target.Notify( $"Facture de soins de {Format.Money( total )} ({response.Title}). Payez-la sur votre téléphone (appli Factures) ou à l'accueil de l'hôpital.", "argent" );
PhoneSystem.Current?.RefreshInvoices( target );
}
medic.Notify( $"Facture de {Format.Money( total )} établie (n° {response.BillId}).", "info" );
Journal( "ems_facture", target, $"{PoliceService.EmsSignature( medic )} facture {Format.Money( total )} de soins à {Who( target )} : {string.Join( ", ", items )}" );
}
finally
{
billing.Remove( medic.SteamId );
}
}
// ------------------------------------------------------------------ effectifs (gradé)
/// <summary>
/// Un gradé (EmsRules.RankRoster) agit sur le joueur visé : « recruter » (stagiaire),
/// « promouvoir », « retrograder », « renvoyer ». L'API refait les vérifications.
/// </summary>
[Rpc.Host]
public static void RequestRoster( Guid targetId, string action )
{
var chief = Medic( EmsRules.RankRoster, "Gérer les effectifs" );
if ( chief is null || Patient( chief, targetId ) is not { } target || PoliceService.Current is not { } service )
return;
if ( !Backend.IsOnline )
{
chief.Notify( "Partie locale : les effectifs sont en base.", "erreur" );
return;
}
var current = service.MedicOf( target.SteamId );
if ( current is not null && current.Grade >= chief.EmsGrade )
{
chief.Notify( "On ne touche ni à un égal, ni à un supérieur.", "erreur" );
return;
}
int? grade = action switch
{
"recruter" when current is null => 0,
"promouvoir" when current is not null && current.Grade + 1 < chief.EmsGrade => current.Grade + 1,
"retrograder" when current is not null && current.Grade > 0 => current.Grade - 1,
_ => -1,
};
if ( action == "renvoyer" && current is not null )
grade = null;
else if ( grade == -1 )
{
chief.Notify( "Action impossible sur ce joueur.", "erreur" );
return;
}
_ = service.EmsRosterWrite( chief, target, grade );
}
// ------------------------------------------------------------------ vestiaire et accueil
static bool AtLocker( RoleplayPlayer player )
{
if ( PoliceService.Near( player, ServerSettings.EmsLocker, EmsRules.PointRange * 1.5f ) )
return true;
player.Notify( "Rendez-vous au vestiaire de l'hôpital.", "erreur" );
return false;
}
/// <summary>Prendre ou finir son service (au vestiaire de l'hôpital).</summary>
[Rpc.Host]
public static void RequestDuty( bool on )
{
var player = Caller();
if ( player is null || PoliceService.Current is not { } service || !AtLocker( player ) )
return;
if ( on )
service.StartEmsService( player );
else if ( player.EmsOnDuty )
service.EndEmsService( player, "fin de service au vestiaire" );
}
/// <summary>Le matériel du vestiaire (EmsRules.Kit), complété jusqu'à la dotation.</summary>
[Rpc.Host]
public static void RequestKit()
{
var medic = Medic();
if ( medic is null || !AtLocker( medic ) )
return;
PoliceService.Current?.IssueKit( medic );
}
/// <summary>Rendre le matériel, sans quitter le service.</summary>
[Rpc.Host]
public static void RequestReturnKit()
{
var player = Caller();
if ( player is null || player.EmsGrade < 0 || !AtLocker( player ) )
return;
PoliceService.Current?.ReturnKit( player );
}
static bool AtDesk( RoleplayPlayer player ) =>
PoliceService.Near( player, ServerSettings.EmsDesk, EmsRules.PointRange * 1.5f );
/// <summary>L'accueil de l'hôpital : la liste de ses factures impayées.</summary>
[Rpc.Host]
public static void RequestDeskBills()
{
var player = Caller();
if ( player is null || !AtDesk( player ) )
return;
_ = PoliceService.Current?.SendEmsDesk( player );
}
/// <summary>L'accueil de l'hôpital : payer une facture depuis HZ BANK.</summary>
[Rpc.Host]
public static void RequestDeskPay( long billId )
{
var player = Caller();
if ( player is null || billId <= 0 || !AtDesk( player ) )
return;
_ = PoliceService.Current?.PayEmsBillAtDesk( player, billId );
}
/// <summary>L'accueil de l'hôpital : se faire soigner contre paiement (sans secouriste en service).</summary>
[Rpc.Host]
public static void RequestDeskCare()
{
var player = Caller();
if ( player is null || player.OnTheGround || player.Cuffed || !AtDesk( player ) )
return;
_ = PoliceService.Current?.DeskCare( player );
}
// ------------------------------------------------------------------ points de l'hôpital (staff)
/// <summary>Les points uniques, et leur nom.</summary>
public static (string Key, string Label, string Icon)[] Points => new (string, string, string)[]
{
(EmsRules.PointLocker, "Vestiaire des secours (service, matériel)", "checkroom"),
(EmsRules.PointGarage, "Garage des secours (menu des ambulances)", "garage"),
(EmsRules.PointDesk, "Accueil de l'hôpital (soins, factures)", "local_hospital"),
};
/// <summary>
/// Le staff pose un point de l'hôpital là où il se tient : « poser » pour un point unique ; pour
/// les sorties du garage, « ajouter » ou « retirer:N ». Comme PoliceActions.RequestStaffPoint.
/// </summary>
[Rpc.Host]
public static void RequestStaffPoint( string key, string mode )
{
var staff = Caller();
if ( staff is null || !staff.IsStaff )
return;
var yaw = staff.GetComponent<PlayerController>()?.EyeAngles.yaw ?? staff.WorldRotation.Yaw();
var here = new ServerSettings.Point( staff.WorldPosition.x, staff.WorldPosition.y, staff.WorldPosition.z, yaw );
if ( Points.FirstOrDefault( x => x.Key == key ) is { Key: not null } single )
{
if ( mode != "poser" )
return;
_ = SaveSetting( staff, key, new { x = here.X, y = here.Y, z = here.Z, yaw = here.Yaw }, ServerSettings.PointJson( here ), $"Point posé ici : {single.Label}." );
return;
}
if ( key != EmsRules.PointsGarageSpawn )
return;
var points = ServerSettings.EmsGarageSpawns;
if ( mode == "ajouter" )
{
if ( points.Count >= 10 )
{
staff.Notify( "Dix sorties de garage au plus.", "erreur" );
return;
}
points.Add( here );
}
else if ( mode?.StartsWith( "retirer:" ) == true && int.TryParse( mode[8..], out var i ) && i >= 0 && i < points.Count )
points.RemoveAt( i );
else
return;
var list = points.Select( p => new { x = p.X, y = p.Y, z = p.Z, yaw = p.Yaw } ).ToList();
_ = SaveSetting( staff, key, list, ServerSettings.PointsJson( points ), mode == "ajouter" ? "Sortie du garage des secours ajoutée ici." : "Sortie du garage des secours retirée." );
}
static async Task SaveSetting( RoleplayPlayer staff, string key, object value, string json, string done )
{
if ( !Backend.IsOnline )
{
ServerSettings.SetLocal( key, json );
staff.Notify( done + " (partie locale : non enregistré)", "info" );
return;
}
var response = await Backend.SaveSetting( key, value, $"{staff.RpName} ({staff.SteamId})" );
if ( response?.Ok != true )
{
staff.Notify( response?.Error ?? "API injoignable : point non enregistré.", "erreur" );
return;
}
ServerSettings.SetLocal( key, json );
staff.Notify( done, "info" );
HorizonGame.Current?.AddLog( "ems_points", staff.SteamId, $"{Who( staff )} : {key} — {done}" );
}
}