World/MapProps.cs

Static helper that runs once per map load to separate moveable props (bins, benches, chairs) from the locally-loaded map and network them from the host. The host unparents each Rigidbody prop, clears NotNetworked, tags it prop_carte and spawns it; clients destroy their local duplicate copies.

Networking
namespace HorizonRP;

/// <summary>
/// Les props mobiles de la carte (412 sur Union City : poubelles, bancs, cartons, pots de fleurs,
/// chaises, bennes…) partagés en réseau (Julien, 29/10/2026).
///
/// La carte est chargée par chaque machine de son côté : ses objets n'existent pas en réseau. Un
/// prop mobile bousculé ne bougeait donc que chez celui qui le poussait (chacun voyait la poubelle
/// ailleurs), et le physgun du staff les refusait (« Décor de la carte »). Une fois la carte
/// chargée, l'hôte sort chaque prop mobile de la carte et le partage en réseau ; chaque client
/// détruit sa propre copie et ne garde que celle de l'hôte. Les props fixes restent du décor.
///
/// Appelé à chaque image fixe par HorizonGame, sur toutes les machines ; ne fait son travail
/// qu'une fois par chargement de carte. Les positions ne sont pas sauvegardées : un redémarrage
/// remet tout en place.
/// </summary>
public static class MapProps
{
	/// <summary>L'étiquette des props de la carte partagés (un prop posé par le staff ne l'a pas).</summary>
	public const string Tag = "prop_carte";

	static GameObject handledMap;

	public static void Update( Scene scene )
	{
		if ( scene is null || scene.IsEditor )
			return;

		var map = scene.GetAllComponents<MapInstance>().FirstOrDefault();
		if ( map is null || !map.IsLoaded || map.GameObject == handledMap )
			return;
		handledMap = map.GameObject;

		// Mobiles : un corps physique qui bouge, encore rangé sous la carte. Pas de test sur
		// Network.Active : un enfant de la carte le donne vrai alors que la carte n'est pas en
		// réseau (le premier essai n'en a trouvé aucun).
		var props = map.GameObject.GetComponentsInChildren<Rigidbody>( true )
			.Where( x => x.MotionEnabled && x.GameObject.Root == map.GameObject )
			.Select( x => x.GameObject )
			.Distinct()
			.ToList();

		if ( Networking.IsHost )
		{
			foreach ( var go in props )
			{
				// Hors de la carte (un objet partagé ne peut pas pendre sous un objet local),
				// à la même place.
				go.SetParent( null, true );
				go.Flags &= ~GameObjectFlags.NotNetworked;
				go.Tags.Add( Tag );
				go.NetworkMode = NetworkMode.Object;
				go.NetworkSpawn();
			}
			Log.Info( $"[Horizon] {props.Count} prop(s) mobile(s) de la carte partagé(s) en réseau" );
		}
		else
		{
			// Ceux de l'hôte arrivent par le réseau, hors de la carte : ils ne sont pas dans la liste.
			foreach ( var go in props )
				go.Destroy();
			Log.Info( $"[Horizon] {props.Count} copie(s) locale(s) de props de la carte retirée(s)" );
		}
	}
}