Police/PoliceService.cs

Partial component for the police system on the Horizon RP scene object. Syncs officer rosters, penal code and flagged plates as JSON from the host, and lazily deserializes them into typed lists exposed as read-only properties. Holds a static Current reference and an officer lookup by SteamID.

namespace HorizonRP;

/// <summary>
/// Le système de police (Police/PoliceRules.cs pour les règles) : effectifs, code pénal et véhicules
/// signalés, reçus de l'API par l'hôte et publiés à tous ; prise et fin de service ; paie par grade ;
/// armurerie ; prison. Posé sur l'objet « Horizon RP » de la scène, comme les autres systèmes.
/// (Coquille du 29/10/2026 : le chantier « Justice et service » la remplit.)
/// </summary>
[Title( "Horizon RP – Police" )]
[Icon( "local_police" )]
public sealed partial class PoliceService : Component
{
	public static PoliceService Current { get; private set; }

	/// <summary>Un agent inscrit (hz_police_officers).</summary>
	public sealed record Officer( string SteamId, int Grade, string Badge, string Callsign );

	/// <summary>Une ligne du code pénal (hz_penal_code).</summary>
	public sealed record PenalEntry( string Id, string Category, string Title, string Description, string Class, int Fine, int JailMinutes );

	/// <summary>Un véhicule signalé (hz_vehicle_flags).</summary>
	public sealed record PlateFlag( string Plate, string Reason );

	/// <summary>Publiés par l'hôte (JSON des listes ci-dessus).</summary>
	[Sync( SyncFlags.FromHost )] public string OfficersJson { get; set; }
	[Sync( SyncFlags.FromHost )] public string PenalJson { get; set; }
	[Sync( SyncFlags.FromHost )] public string FlagsJson { get; set; }

	string parsedOfficers, parsedPenal, parsedFlags;
	List<Officer> officers = new();
	List<PenalEntry> penal = new();
	List<PlateFlag> flags = new();

	public IReadOnlyList<Officer> Officers
	{
		get
		{
			if ( parsedOfficers != OfficersJson )
			{
				parsedOfficers = OfficersJson;
				officers = string.IsNullOrEmpty( OfficersJson ) ? new() : Json.Deserialize<List<Officer>>( OfficersJson ) ?? new();
			}
			return officers;
		}
	}

	public IReadOnlyList<PenalEntry> PenalCode
	{
		get
		{
			if ( parsedPenal != PenalJson )
			{
				parsedPenal = PenalJson;
				penal = string.IsNullOrEmpty( PenalJson ) ? new() : Json.Deserialize<List<PenalEntry>>( PenalJson ) ?? new();
			}
			return penal;
		}
	}

	public IReadOnlyList<PlateFlag> Flags
	{
		get
		{
			if ( parsedFlags != FlagsJson )
			{
				parsedFlags = FlagsJson;
				flags = string.IsNullOrEmpty( FlagsJson ) ? new() : Json.Deserialize<List<PlateFlag>>( FlagsJson ) ?? new();
			}
			return flags;
		}
	}

	/// <summary>L'agent inscrit sous ce SteamID, ou null : la liste blanche du métier.</summary>
	public Officer OfficerOf( ulong steamId ) => Officers.FirstOrDefault( x => x.SteamId == steamId.ToString() );

	protected override void OnAwake() => Current = this;

	protected override void OnDestroy()
	{
		if ( Current == this )
			Current = null;
	}
}