Police/PoliceRules.cs

Static rule table for the UCPD police job. Holds the grade ladder with salaries, fleet vehicles, armory items, penal code classes, station point ids and range constants, and is read by both server and client.

namespace HorizonRP;

/// <summary>
/// Les règles de l'Union City Police Department (UCPD), lues à l'identique par le serveur et par les
/// clients (Julien, 29/10/2026). Inspiré des systèmes de police FiveM les plus repris (qb-policejob,
/// esx_policejob, ps-mdt, wk_wars2x) et de l'organisation des polices américaines (NYPD / LAPD).
///
/// - Métier sur liste blanche : seuls les agents inscrits par l'administration (panneau web, page
///   Police : hz_police_officers) prennent le métier « policier » ; leur grade y est fixé.
/// - On est policier « hors service » tant qu'on n'a pas pris son service au vestiaire : la paie
///   de grade, l'armurerie, le garage, les actions, la tablette et les gyrophares demandent d'être
///   en service.
/// - Le code pénal (amendes, peines) est en base (hz_penal_code), éditable depuis le panneau.
///
/// GRADES et FLEET sont déclarés en double dans api.php (POLICE_GRADES, POLICE_FLEET) : les
/// changer ensemble. Une propriété, pas un champ : un tableau statique garderait son ancienne
/// valeur après un rechargement à chaud.
/// </summary>
public static class PoliceRules
{
	public const string JobId = "policier";
	public const string Department = "Union City Police Department";
	public const string Short = "UCPD";

	/// <summary>
	/// Un grade : son rang (0 le plus bas), son nom français et l'américain, sa paie par paie, et ce
	/// qu'il permet (les droits s'accumulent en montant).
	/// </summary>
	public sealed record Grade( int Rank, string Name, string English, int Salary, string Insignia );

	public static Grade[] Grades => new Grade[]
	{
		new( 0, "Recrue", "Police Cadet", 90, "school" ),
		new( 1, "Agent", "Police Officer", 120, "shield" ),
		new( 2, "Agent confirmé", "Senior Police Officer", 140, "verified_user" ),
		new( 3, "Inspecteur", "Detective", 165, "search" ),
		new( 4, "Sergent", "Sergeant", 190, "military_tech" ),
		new( 5, "Lieutenant", "Lieutenant", 220, "star" ),
		new( 6, "Capitaine", "Captain", 260, "stars" ),
		new( 7, "Commandant", "Deputy Chief", 300, "workspace_premium" ),
		new( 8, "Chef de la police", "Chief of Police", 350, "local_police" ),
	};

	public static Grade GradeOf( int rank ) => Grades.FirstOrDefault( x => x.Rank == rank ) ?? Grades[0];

	// Droits : le grade minimum pour chaque chose. Une seule table, lue partout (jeu, tablette).
	public const int RankFine = 0;            // dresser une amende
	public const int RankArrest = 1;          // écrouer (la recrue est accompagnée)
	public const int RankImpound = 1;         // mettre un véhicule en fourrière
	public const int RankWarrant = 3;         // émettre un avis de recherche
	public const int RankCloseWarrant = 3;    // clore un avis de recherche
	public const int RankCancelFine = 5;      // annuler une amende
	public const int RankDeleteRecord = 6;    // effacer une ligne du casier
	public const int RankRoster = 7;          // recruter, promouvoir, rétrograder, renvoyer (en jeu)

	/// <summary>
	/// Un véhicule de la flotte : modèle du catalogue (VehicleCatalog), sa couleur, grade minimum, et
	/// s'il porte une rampe de gyrophares sur le toit (sinon : banalisé, feux derrière la calandre et
	/// le pare-brise). Les véhicules de service n'appartiennent à personne et disparaissent à la fin
	/// du service ; ils ne sont pas sauvegardés.
	/// </summary>
	public sealed record FleetVehicle( string Id, string Label, string ModelId, string Color, int MinRank, bool Lightbar );

	public static FleetVehicle[] Fleet => new FleetVehicle[]
	{
		// La Crown Victoria noir et blanc (outils/vehicules/livree_crownvic.py, fiche « crownvic_police »
		// hors vente, rampe Vector du modèle) ; elle a remplacé la Polo de police le 30/10/2026.
		// Livrée non teintée : la couleur de la flotte ne s'y applique pas.
		new( "patrouille", "Patrouille (Crown Victoria, livrée UCPD)", "crownvic_police", "#111317", 0, true ),
		// Livrée équipée : rampe Code 3, feux de pare-buffle et visière du modèle (VehicleRig.Beacons),
		// livrée UCPD retouchée par outils/vehicules/livree_charger.py (fiche « charger_police », hors vente).
		new( "charger", "Patrouille (Charger, livrée UCPD)", "charger_police", "#111317", 2, true ),
		new( "interception", "Interception (Classe S)", "w221", "#111317", 4, true ),
		new( "banalisee", "Banalisée (Classe S)", "w221", "#3b3f47", 3, false ),
		new( "intervention", "Unité d'intervention (G 63)", "g63", "#111317", 5, true ),
	};

	public static FleetVehicle FleetOf( string id ) => Fleet.FirstOrDefault( x => x.Id == id );

	/// <summary>
	/// L'armurerie : une arme du catalogue (hz_weapons, Id sans « arme: »), le grade minimum, les
	/// munitions données avec. Les armes de service sont marquées dans leur donnée (WeaponData
	/// « Service ») et reprises au vestiaire en fin de service.
	/// </summary>
	public sealed record ArmoryItem( string WeaponId, string Label, int MinRank, string Ammo, int AmmoCount );

	public static ArmoryItem[] Armory => new ArmoryItem[]
	{
		new( "usp", "Pistolet de service (HK USP)", 0, "munitions_45", 60 ),
		new( "glock20", "Glock-20", 1, "munitions_9mm", 60 ),
		new( "rem870", "Fusil à pompe (Remington 870)", 1, "munitions_12", 24 ),
		new( "mp5a5", "Pistolet-mitrailleur (MP5)", 4, "munitions_9mm", 120 ),
		new( "m4a1", "Fusil d'assaut (M4A1)", 5, "munitions_556", 120 ),
		new( "nade_m84", "Grenade aveuglante (M84)", 5, null, 0 ),
	};

	/// <summary>Les classes d'infraction du code pénal (hz_penal_code.class), à l'américaine.</summary>
	public static readonly (string Id, string Name, string English, string Color)[] Classes =
	{
		("infraction", "Contravention", "Infraction", "#22c55e"),
		("delit", "Délit", "Misdemeanor", "#f59e0b"),
		("crime", "Crime", "Felony", "#ef4444"),
	};

	/// <summary>Les points du commissariat, posés en jeu par le staff (hz_settings, type « point »).</summary>
	public const string PointLocker = "police_vestiaire";        // prise de service, tenue, armurerie
	public const string PointGarage = "police_garage";           // menu du garage
	public const string PointsGarageSpawn = "police_garage_sorties"; // où les véhicules apparaissent (plusieurs)
	public const string PointBooking = "police_ecrou";            // où l'on écroue (près des cellules)
	public const string PointJail = "police_prison";             // dans la prison
	public const string PointRelease = "police_liberation";      // la sortie, peine purgée
	public const string PointFrontDesk = "police_accueil";       // l'accueil : payer ses amendes

	/// <summary>Distance pour utiliser un point du commissariat (pouces, ~2,5 m).</summary>
	public const float PointRange = 100f;

	/// <summary>Rayon de la prison : au-delà, le détenu y est ramené (pouces, ~40 m).</summary>
	public const float JailRadius = 1600f;

	/// <summary>Le numéro d'urgence du téléphone (police et secours).</summary>
	public const string EmergencyNumber = "911";
}