Money/CashDrop.cs

A cash pickup lying on the ground for the Horizon RP gamemode. Spawned by the host, syncs its amount, builds a scaled model with a trigger hitbox, can be pressed with E to request pickup, and destroys itself after 10 minutes.

namespace HorizonRP;

/// <summary>
/// Une liasse au sol : de l'argent posé (Cash.RequestDrop) ou laissé par un mort. Créée par
/// l'hôte ; n'importe qui la ramasse avec E, et la somme va droit au portefeuille. Disparaît au
/// bout de 10 minutes, comme les objets au sol.
/// </summary>
[Title( "Horizon RP – Argent au sol" )]
[Icon( "payments" )]
public sealed class CashDrop : Component, Component.IPressable
{
	public const float Lifetime = 600f;

	/// <summary>L'échelle de la liasse au sol (en main, elle garde sa vraie taille).</summary>
	public const float GroundScale = 1.8f;

	[Sync( SyncFlags.FromHost )] public int Amount { get; set; }

	/// <summary>Hôte : d'où vient l'argent (« posé par … », « laissé par … mort »), pour le journal.</summary>
	public string Origin { get; set; }

	RealTimeSince sinceSpawn;
	bool built;

	/// <summary>Hôte : une liasse posée sur le sol sous <paramref name="near"/>.</summary>
	public static void Spawn( int amount, Vector3 near, string origin )
	{
		var tr = Game.ActiveScene.Trace.Ray( near + Vector3.Up * 30, near + Vector3.Down * 120 ).WithoutTags( "player", "trigger", "ragdoll" ).Run();
		var go = new GameObject( true, $"Argent au sol : {Format.Money( amount )}" );
		go.WorldPosition = (tr.Hit ? tr.HitPosition : near) + Vector3.Up * 0.2f;
		go.WorldRotation = Rotation.FromYaw( Random.Shared.Float( 0, 360 ) );
		var drop = go.Components.Create<CashDrop>();
		drop.Amount = amount;
		drop.Origin = origin;
		go.NetworkSpawn();
	}

	protected override void OnStart() => sinceSpawn = 0;

	protected override void OnUpdate()
	{
		if ( !built )
		{
			built = true;
			// Le modèle et sa zone de visée dans un enfant local (rien à synchroniser).
			var holder = new GameObject( GameObject, true, "Liasse" );
			holder.NetworkMode = NetworkMode.Never;
			// Grossie au sol : à sa vraie taille (15,6 cm) elle se voyait à peine debout (Julien).
			// La zone de visée, posée sur le même enfant, grandit avec.
			holder.LocalScale = GroundScale;
			var model = Model.Load( Cash.ModelPath );
			holder.Components.Create<ModelRenderer>().Model = model;
			// Une zone de visée un peu plus grande que la liasse (qui ne fait qu'un centimètre
			// d'épaisseur), en déclencheur : on la vise sans buter dedans.
			var box = holder.Components.Create<BoxCollider>();
			box.Center = model.Bounds.Center + Vector3.Up * 2;
			box.Scale = model.Bounds.Size + new Vector3( 4, 4, 4 );
			box.IsTrigger = true;
		}
		if ( Networking.IsHost && sinceSpawn > Lifetime )
			GameObject.Destroy();
	}

	public bool Press( IPressable.Event e )
	{
		Cash.RequestPickup( GameObject.Id );
		return true;
	}

	public IPressable.Tooltip? GetTooltip( IPressable.Event e ) =>
		Amount > 0 ? new IPressable.Tooltip( $"{Format.Money( Amount )} en liquide", "payments", "Ramasser" ) : null;
}