Data model and rules for the gamemode's prop catalog. PropEntry holds one catalog row (model, name, category, price, job restriction, staff-only, active), PropRules stores placement and inventory constants plus a default catalog from CityKinds, and PropItems turns a catalog entry into a cached ItemDefinition with the "prop:<id>" id prefix.
namespace HorizonRP;
/// <summary>
/// Une entrée du catalogue des props, réglée par le staff (panneau web ou menu
/// d'administration) et rangée en base (hz_props). Les noms de champs suivent le
/// JSON de l'API (camelCase).
/// </summary>
public sealed class PropEntry
{
public long Id { get; set; }
public string Model { get; set; }
public string Name { get; set; }
public string Category { get; set; } = "Divers";
public int Price { get; set; }
/// <summary>Id de métiers séparés par des virgules ; vide = tout le monde.</summary>
public string Jobs { get; set; } = "";
public bool StaffOnly { get; set; }
public bool Active { get; set; } = true;
public IEnumerable<string> JobList => (Jobs ?? "").Split( ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries );
/// <summary>
/// Du matériel de service pour ce joueur : réservé à son métier, il le pose
/// sans l'avoir en poche, comme on le sort du casier (cônes de la police…).
/// </summary>
public bool IsServiceFor( RoleplayPlayer player ) =>
player is not null && Active && !StaffOnly && JobList.Contains( player.JobId );
}
/// <summary>Les règles des props, communes à l'hôte et à l'interface.</summary>
public static class PropRules
{
/// <summary>Props posés en même temps par un joueur ; le staff n'a pas de limite.</summary>
public const int MaxPerPlayer = 15;
/// <summary>Distance maximale entre le joueur et l'endroit où il pose (unités, ~10 m).</summary>
public const float Reach = 400f;
/// <summary>Un prop en poche : son poids et combien par case.</summary>
public const float ItemWeight = 1.5f;
public const int ItemStack = 5;
/// <summary>
/// Le catalogue de départ, sans l'API (partie lancée sans base) : les modèles
/// livrés avec s&box du menu de construction, à petit prix.
/// </summary>
public static List<PropEntry> Defaults() => CityKinds.Catalog
.Select( ( x, i ) => new PropEntry { Id = i + 1, Model = x.Model, Name = x.Name, Category = x.Category, Price = 10 } )
.ToList();
}
/// <summary>
/// Les props portés en poche, façon FiveM : un prop du catalogue devient un objet
/// d'inventaire « prop:<id> », qu'on pose depuis l'inventaire ou le menu P et
/// qu'on ramasse pour le récupérer. Le staff, lui, fait apparaître ce qu'il veut.
/// </summary>
public static class PropItems
{
public const string Prefix = "prop:";
public static string IdOf( long entryId ) => $"{Prefix}{entryId}";
public static long EntryOf( string itemId ) =>
itemId is not null && itemId.StartsWith( Prefix ) && long.TryParse( itemId[Prefix.Length..], out var id ) ? id : 0;
static readonly Dictionary<string, ItemDefinition> cache = new();
/// <summary>
/// La définition d'un prop en poche. Elle existe même quand le catalogue n'est
/// pas encore chargé (ou que le prop en a été retiré) : sans elle, le
/// chargement du profil jetterait l'objet de l'inventaire.
/// </summary>
public static ItemDefinition Get( string itemId )
{
var entryId = EntryOf( itemId );
if ( entryId <= 0 )
return null;
var entry = PropSystem.Current?.Get( entryId );
var key = $"{itemId}|{entry?.Name}|{entry?.Model}|{entry?.Price}";
if ( cache.TryGetValue( key, out var known ) )
return known;
var item = new ItemDefinition( itemId, entry?.Name ?? "Objet à poser",
entry is null ? "Un objet à poser, absent du catalogue pour l'instant." : "À poser : bouton Poser, ou le menu des props. Ramassé, il revient dans vos poches.",
"chair", "Props", PropRules.ItemWeight, PropRules.ItemStack, entry?.Price ?? 0,
entry?.Model ?? "models/citizen_props/cardboardbox01.vmdl" );
cache[key] = item;
return item;
}
/// <summary>Les props du catalogue, en objets (pour les dons du staff).</summary>
public static IEnumerable<ItemDefinition> All =>
(PropSystem.Current?.Catalog ?? (IReadOnlyList<PropEntry>)Array.Empty<PropEntry>()).OrderBy( x => x.Name ).Select( x => Get( IdOf( x.Id ) ) );
}