Static catalog for the vehicle dealership. Defines the VehicleModel record (id, name, price, model path, speed, handling and seat tuning) and holds a Defaults list of eight cars plus the paint color list. All returns the models from VehicleManager when the database has some, otherwise the defaults. Get looks a model up by id.
namespace HorizonRP;
/// <summary>
/// Un modèle de la concession. Les Id sont déclarés à l'identique dans
/// VEHICLE_MODELS de l'API (api/api.php), qui refuse un Id inconnu.
/// </summary>
public sealed record VehicleModel(
string Id,
string Name,
string Description,
int Price,
string Model,
float MaxSpeedKmh,
float Acceleration, // km/h gagnés par seconde en moyenne de 0 à 100 : 100 / valeur = le 0 à 100 en secondes
float Mass = 1400 )
{
// Conduite : chaque valeur a un sens physique, et toutes se règlent depuis le panneau.
/// <summary>Freinage maximal, en g (1 = une bonne voiture, 0,6 = un camion).</summary>
public float BrakeG { get; init; } = 1.0f;
/// <summary>Adhérence des pneus (coefficient de frottement : 1 = pneu de route sur sec).</summary>
public float Grip { get; init; } = 1.1f;
/// <summary>Braquage maximal des roues avant à l'arrêt, en degrés (il se réduit avec la vitesse).</summary>
public float SteerAngle { get; init; } = 34f;
/// <summary>Débattement de la suspension, en pouces.</summary>
public float SuspensionTravel { get; init; } = 7f;
/// <summary>Raideur et amortissement de la suspension (1 = réglage d'usine).</summary>
public float Stiffness { get; init; } = 1f;
public float Damping { get; init; } = 1f;
/// <summary>Barre antiroulis (0 = aucune, 1 = d'usine) : plus haut, la voiture penche moins.</summary>
public float AntiRoll { get; init; } = 1f;
/// <summary>Roues motrices : « AR » (propulsion), « AV » (traction), « 4x4 ».</summary>
public string Drive { get; init; } = "AR";
/// <summary>Ce qu'il reste d'adhérence aux roues arrière, frein à main serré : bas, ça dérape.</summary>
public float HandbrakeGrip { get; init; } = 0.45f;
/// <summary>Le modèle n'avance pas selon +X : sa rotation, en degrés, pour qu'il le fasse.</summary>
public float YawOffset { get; init; } = 0f;
/// <summary>
/// Taille du modèle (1 = telle que livrée). Les voitures reprises de GTA V sont à leurs
/// vraies dimensions, mais Union City est un peu plus grande que nature (bancs ×1,3,
/// poubelles ×1,1) : à 1, elles paraissaient petites.
/// </summary>
public float Scale { get; init; } = 1.15f;
public bool Shop { get; init; } = true;
/// <summary>Nombre de places, conducteur compris (2 à 8).</summary>
public int Seats { get; init; } = 2;
/// <summary>
/// Le son du moteur : un fichier du projet (« sounds/vehicules/… .mp3 »), joué en boucle
/// et accordé au régime ; vide, le moteur synthétisé commun, à la hauteur <see cref="EnginePitch"/>.
/// </summary>
public string EngineSound { get; init; } = "";
/// <summary>Hauteur du moteur : 0,75 un camion, 1 une berline, 1,2 une sportive.</summary>
public float EnginePitch { get; init; } = 1f;
/// <summary>Décalage vertical des sièges, en pouces (négatif : plus bas, pour un toit bas).</summary>
public float SeatHeight { get; init; } = 0f;
/// <summary>Décalage des sièges vers l'avant, en pouces (négatif : plus en arrière).</summary>
public float SeatForward { get; init; } = 0f;
/// <summary>Ce que le coffre porte, en kilos (cinq emplacements dans cette limite).</summary>
public float TrunkKg { get; init; } = 80f;
}
public static class VehicleCatalog
{
/// <summary>
/// Modèles : uniquement des modèles importés (Assets/models/horizon/vehicules/<id>/, préparés par
/// outils/vehicules/preparer_vehicule.py). Les voitures GTA V de TwinsD du Workshop, qui
/// servaient d'essai, sont retirées depuis le 29/10/2026. Pour changer de modèle, changer
/// « Model » de la fiche, rien d'autre.
/// </summary>
// Construite à chaque lecture : une liste gardée dans un champ statique survit au
// rechargement à chaud avec l'ancienne forme de la fiche (les réglages ajoutés depuis y
// valaient 0, suspension comprise : la voiture s'enfonçait dans la route).
public static IReadOnlyList<VehicleModel> All => VehicleManager.Current?.Models is { Count: > 0 } fromDb ? fromDb : Defaults;
/// <summary>Les fiches d'origine, quand la base n'en a pas (partie hors ligne).</summary>
static IReadOnlyList<VehicleModel> Defaults => new List<VehicleModel>
{
new( "e34", "BMW E34", "Berline des années 90, rabaissée façon stance.", 9000, "models/horizon/vehicules/e34/e34.vmdl", 230, 13, 1500 )
{
BrakeG = 0.95f, Grip = 1.05f, SuspensionTravel = 5, Stiffness = 1.1f, AntiRoll = 1.1f, Seats = 4, TrunkKg = 140,
EnginePitch = 0.95f, SeatForward = 13,
},
new( "gt3", "Porsche 911 GT3", "La sportive de circuit, homologuée pour la route.", 55000, "models/horizon/vehicules/gt3/gt3.vmdl", 318, 29, 1435 )
{
BrakeG = 1.35f, Grip = 1.35f, SteerAngle = 32, SuspensionTravel = 5, Stiffness = 1.3f, Damping = 1.2f, AntiRoll = 1.3f,
TrunkKg = 40, EnginePitch = 1.3f,
},
new( "model3", "Tesla Model 3", "Électrique, silencieuse et vive.", 42000, "models/horizon/vehicules/model3/model3.vmdl", 233, 23, 1850 )
{
BrakeG = 1.1f, Grip = 1.2f, SuspensionTravel = 6, Stiffness = 1.1f, Damping = 1.1f, AntiRoll = 1.2f, Drive = "4x4",
Seats = 4, TrunkKg = 140, EnginePitch = 1.5f, SeatForward = 12,
},
// Les cinq du 29/10/2026 (la Clio est retirée) : réglages d'après les vraies fiches techniques.
new( "g63", "Mercedes-AMG G 63", "Le 4×4 de luxe, V8 biturbo, haut sur pattes.", 85000, "models/horizon/vehicules/g63/g63.vmdl", 210, 18.5f, 2550 )
{
BrakeG = 0.9f, Grip = 0.95f, SteerAngle = 34, SuspensionTravel = 9, Damping = 1.05f, AntiRoll = 0.9f, Drive = "4x4",
// Haut sur son châssis : le plancher calculé d'après les roues mettait le conducteur dans
// le bas de caisse, la tête sous l'appui des vitres et le volant au-dessus de lui.
// Relevé et avancé d'après les photos (tête à mi-vitre, mains au volant).
Seats = 4, TrunkKg = 220, EnginePitch = 0.85f, SeatForward = 12, SeatHeight = 18,
},
new( "polo", "Volkswagen Polo", "La citadine moderne, sobre et facile.", 11000, "models/horizon/vehicules/polo/polo.vmdl", 187, 9.3f, 1150 )
{
SteerAngle = 36, SuspensionTravel = 6, Drive = "AV", Seats = 4, TrunkKg = 120, EnginePitch = 1.15f, SeatForward = 6,
},
new( "abarth", "Fiat 500 Abarth", "La petite teigne turbo, ferme et vive.", 16000, "models/horizon/vehicules/abarth/abarth.vmdl", 205, 12.7f, 1110 )
{
BrakeG = 1.05f, Grip = 1.15f, SteerAngle = 36, SuspensionTravel = 4.5f, Stiffness = 1.3f, Damping = 1.2f, AntiRoll = 1.3f,
Drive = "AV", Seats = 4, TrunkKg = 70, EnginePitch = 1.35f, SeatForward = 4,
},
new( "w221", "Mercedes Classe S (W221)", "La grande berline de luxe, V8 AMG et cuir partout.", 48000, "models/horizon/vehicules/w221/w221.vmdl", 250, 21.7f, 2050 )
{
BrakeG = 1.05f, Grip = 1.1f, SteerAngle = 33, SuspensionTravel = 7, Stiffness = 0.95f, Damping = 1.15f, AntiRoll = 1.15f,
Seats = 4, TrunkKg = 200, EnginePitch = 0.9f, SeatForward = 14,
},
new( "r9", "Renault 9 Broadway", "La berline des années 80, increvable et pas chère.", 2500, "models/horizon/vehicules/r9/r9.vmdl", 152, 6.5f, 870 )
{
BrakeG = 0.8f, Grip = 0.9f, SteerAngle = 38, SuspensionTravel = 7, Stiffness = 0.85f, Damping = 0.85f, AntiRoll = 0.8f,
Drive = "AV", Seats = 4, TrunkKg = 120, EnginePitch = 1.05f, SeatForward = 6,
},
};
public static VehicleModel Get( string id ) => All.FirstOrDefault( x => x.Id == id );
/// <summary>Les teintes proposées à l'achat.</summary>
public static readonly IReadOnlyList<(string Name, string Hex)> Colors = new List<(string, string)>
{
("Blanc", "#f4f5fb"), ("Noir", "#1b1d26"), ("Gris", "#8a8f9c"), ("Rouge", "#d7263d"),
("Bleu", "#2563eb"), ("Vert", "#16a34a"), ("Jaune", "#facc15"), ("Orange", "#ff8a3d"),
("Rose", "#ff3d7f"), ("Violet", "#8b5cf6"),
};
}