A city object placed by staff, synced from the host with its kind, label and model source. Each client loads the model itself and the component handles the E press for ATMs, job NPCs and garages.
namespace HorizonRP;
/// <summary>
/// Un objet posé par le staff. Créé et possédé par l'hôte (CityManager), qui
/// seul en écrit les valeurs ; chaque machine charge le modèle elle-même à
/// partir de <see cref="ModelSource"/>.
///
/// Les distributeurs et les PNJ de métier réagissent à la touche E : c'est le
/// PlayerController du joueur qui appelle <see cref="Press"/>, chez lui.
/// </summary>
[Title( "Horizon RP – Objet de la ville" )]
[Icon( "location_on" )]
public sealed class CityObject : Component, Component.IPressable
{
[Sync( SyncFlags.FromHost )] public long PlacementId { get; set; }
[Sync( SyncFlags.FromHost )] public string Kind { get; set; } = CityKinds.Decor;
[Sync( SyncFlags.FromHost )] public string ModelSource { get; set; }
[Sync( SyncFlags.FromHost )] public string Label { get; set; }
/// <summary>Donnée propre à la sorte : l'Id du métier pour un PNJ.</summary>
[Sync( SyncFlags.FromHost )] public string Data { get; set; }
public CityKind KindInfo => CityKinds.Get( Kind );
public static IEnumerable<CityObject> All =>
Game.ActiveScene?.GetAllComponents<CityObject>() ?? Enumerable.Empty<CityObject>();
string appliedSource;
ModelRenderer renderer;
ModelCollider collider;
protected override void OnUpdate()
{
// Pas de rappel « Change » : un simple relevé suffit et reste juste même
// quand la valeur arrive avec l'instantané de connexion.
if ( appliedSource != ModelSource )
{
appliedSource = ModelSource;
_ = ApplyModel( ModelSource );
}
if ( renderer.IsValid() )
{
// Un point d'apparition n'existe que pour le staff.
renderer.Enabled = Kind != CityKinds.Spawn || (RoleplayPlayer.Local?.IsStaff ?? false);
}
}
async Task ApplyModel( string source )
{
var model = await ModelSources.Load( source );
if ( !IsValid || source != ModelSource )
return;
renderer = GetOrAddComponent<ModelRenderer>();
renderer.Model = model;
// Un point d'apparition ne doit pas bloquer les joueurs qui y arrivent.
if ( Kind == CityKinds.Spawn )
return;
collider = GetOrAddComponent<ModelCollider>();
collider.Model = model;
collider.Static = true;
}
// Touche E
public bool CanPress( IPressable.Event e ) => Kind is CityKinds.JobNpc or CityKinds.Garage || Kind == CityKinds.Atm && Atm.InPressReach( GameObject );
public bool Press( IPressable.Event e )
{
if ( Kind == CityKinds.Garage )
{
Scene.GetAllComponents<GarageMenu>().FirstOrDefault()?.Open();
return true;
}
// Le distributeur : l'hôte le donne s'il est libre, puis la vue se pose devant son écran
// (RoleplayPlayer.RequestAtmOpen, UI/AtmScreen.razor).
if ( Kind == CityKinds.Atm )
{
RoleplayPlayer.Local?.RequestAtmOpen( GameObject.Id );
return true;
}
var hud = Scene.GetAllComponents<HorizonHud>().FirstOrDefault();
if ( hud is null )
return false;
if ( Kind == CityKinds.JobNpc )
hud.OpenTab( "metiers" );
return true;
}
public IPressable.Tooltip? GetTooltip( IPressable.Event e ) => Kind switch
{
CityKinds.Atm => Atm.InPressReach( GameObject ) ? new IPressable.Tooltip( Label ?? "Distributeur HZ Bank", "local_atm", "Retrait, dépôt, solde" ) : null,
CityKinds.JobNpc => new IPressable.Tooltip( Label ?? "Pôle emploi", "badge", "Choisir un métier" ),
CityKinds.Garage => new IPressable.Tooltip( Label ?? "Garage", "garage", "Concession et garage" ),
_ => null,
};
}