diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/boot.config b/Dedicated_Server_Linux/DatacenterSim_Data/boot.config
index 701967a..a6da18b 100644
--- a/Dedicated_Server_Linux/DatacenterSim_Data/boot.config
+++ b/Dedicated_Server_Linux/DatacenterSim_Data/boot.config
@@ -3,4 +3,4 @@ gfx-enable-native-gfx-jobs=1
wait-for-native-debugger=0
hdr-display-enabled=0
gc-max-time-slice=3
-build-guid=7c178961783345d483f76d6f1fad2a38
+build-guid=e778e91ac99847908610029fa6e532a7
diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.assets b/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.assets
index dc59ac8..116992e 100644
Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.assets and b/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.assets differ
diff --git a/Patchs/fix_portage/PlayerInteraction.cs b/Patchs/fix_portage/PlayerInteraction.cs
new file mode 100644
index 0000000..1bc337a
--- /dev/null
+++ b/Patchs/fix_portage/PlayerInteraction.cs
@@ -0,0 +1,1438 @@
+using UnityEngine;
+using System.Collections.Generic;
+using Mirror;
+
+///
+/// PlayerInteraction.cs - v6.13 Multijoueur
+///
+/// v6.13 : Fix des syncs manquantes en multijoueur
+/// BUG 1 : La pile de ZoneLivraison ne se reorganise pas cote autres clients
+/// quand un joueur ramasse un article (le client qui ramasse reorganise
+/// en local, les autres ne voient rien bouger).
+/// Fix : CmdReorganiserZoneLivraison envoie la reorganisation au serveur,
+/// qui modifie les positions des articles -> Mirror sync via NetworkTransform.
+///
+/// BUG 2 : Les equipements poses sur le chariot "volent" quand l'autre
+/// joueur pilote. Mirror ne sync pas le SetParent, donc seul le client qui
+/// a pose voit les equipements parentes au chariot. Les autres les voient
+/// a leur position monde figee au moment de la pose.
+/// Fix : CmdParenterAuChariot + RpcParenterAuChariot pour broadcaster le
+/// SetParent a tous les clients (chacun fait SetParent local). Idem pour
+/// le detachement au ramassage (CmdDetacherDuChariot + RpcDetacherDuChariot).
+///
+/// v6.12 : Transfert d'autorite pour le pilotage du chariot en multijoueur
+/// v6.11 : Pose/retrait des torons sur les axes lateraux du chariot
+/// v6.10 : Fix critique - objets qui flottent apres avoir ete poses sur le chariot
+/// v6.9 : Integration du systeme de pose plateau / etagere basse du Chariot v7.x
+/// - Detection des zones de pose via RaycastAll + tri par distance
+/// - Distinction automatique zonePosePlateau vs zonePoseEtagere
+/// - Affichage du fantome a la bonne position via AfficherFantomePose(tailleU, surEtagereBasse)
+/// - Cachage des fantomes quand on quitte le chariot du regard ou qu'on pose
+/// - Pose sur etagere basse via PoserEquipementSurEtagere
+/// - Ramassage depuis etagere basse (via equipementsSurEtagere.Contains)
+///
+/// v6.8 : Systeme d'assise sur les chaises
+/// v6.7 : Placement local client pour supports muraux
+/// v6.5 : Remise de PDU sur SupportPDU
+/// v6.4 : Portage toron "dans les mains"
+/// v6.3 : Placement precis toron sur axe cible
+/// v6.2 : Remise de toron sur SupportTorons
+///
+public class PlayerInteraction : NetworkBehaviour
+{
+ [Header("Interaction")]
+ public float porteeInteraction = 4f;
+ public KeyCode toucheInteraction = KeyCode.E;
+ public KeyCode toucheConfiguration = KeyCode.F;
+
+ [Header("Performance")]
+ [Range(1, 5)]
+ public int detectInterval = 2;
+ public bool detecterTriggers = false;
+
+ [Header("Transport")]
+ public float distanceTransport = 1.5f;
+ public float hauteurTransport = 0.6f;
+ public float decalageHorizontal = 0f;
+ public float vitesseSuivi = 15f;
+
+ [Header("Transport — Toron (v6.4)")]
+ public float distanceTransportToron = 0.85f;
+ public float hauteurTransportToron = 0.15f;
+ public float decalageHorizontalToron = 0.35f;
+ public Vector3 rotationEulerToron = new Vector3(0f, 90f, 0f);
+
+ [Header("Remise sur supports muraux (v6.2/6.5)")]
+ public float distanceDetectionSupport = 3.5f;
+ public float angleDetectionSupport = 35f;
+
+ private Camera _camera;
+ private Interactable _objetSurvole;
+ private Interactable _objetEnMain;
+ private Rigidbody _rbEnMain;
+ private Vector3 _cibleTransport;
+ private Quaternion _rotationOffsetTransport = Quaternion.identity;
+
+ private RackSlot _slotSurvole;
+ private Chariot _chariotPilote;
+ private Chariot _chariotVise;
+ private bool _visePoignee = false;
+ // v6.9 : distinction zone plateau/etagere du chariot vise
+ private bool _chariotZoneEtagereVisee = false;
+ // v6.11 : index de l'axe toron cible sur le chariot (0=Gauche, 1=Droit, -1=aucun)
+ private int _chariotAxeToronCibleIndex = -1;
+
+ private EmplacementBaie _emplacementVise;
+ private EmplacementPDU _emplacementPDUVise;
+ private PorteBaie _porteVisee;
+
+ private PriseC13 _priseSurvolee;
+ private BoutonPower _boutonPowerVise;
+
+ // v6.8 : système d'assise
+ private ChaiseSiege _chaiseSiegeVisee;
+ private ChaiseSiege _chaiseSiegeActuelle;
+
+ private List _collisionsIgnorees = new List();
+ private EmplacementBaie[] _tousEmplacements;
+ private bool _emplacementsVisibles = false;
+ private bool _emplacementsPDUVisibles = false;
+
+ private int _detectFrame = 0;
+
+ private MenuPause _cachedPause;
+ private MenuPrincipal _cachedMenu;
+ private bool _cacheInitialized = false;
+
+ // v6.2 : axe cible courant sur SupportTorons
+ private int _axeSupportCibleLigne = -1;
+ private int _axeSupportCibleColonne = -1;
+
+ // v6.5 : emplacement cible courant sur SupportPDU
+ private int _emplacementSupportPDUCibleLigne = -1;
+ private int _emplacementSupportPDUCibleColonne = -1;
+
+ [SyncVar(hook = nameof(OnObjetPorteChange))]
+ private uint _objetPorteNetId = 0;
+
+ private GameObject _objetPorteDistant;
+
+ public Interactable ObjetEnMain => _objetEnMain;
+
+ void Start()
+ {
+ _camera = GetComponentInChildren();
+ if (_camera == null) _camera = Camera.main;
+ }
+
+ private bool _popupEtaitOuvert = false;
+
+ void Update()
+ {
+ if (!isLocalPlayer) return;
+
+ // v6.8 : mode assis — seul [E] pour se lever est actif
+ if (_chaiseSiegeActuelle != null)
+ {
+ if (Input.GetKeyDown(toucheInteraction))
+ {
+ _chaiseSiegeActuelle.SeLever();
+ _chaiseSiegeActuelle = null;
+ HUDManager hud = GetComponent();
+ if (hud != null) hud.SetInfoEquipement("");
+ }
+ return;
+ }
+
+ if (_camera == null) _camera = Camera.main;
+
+ if (UIConfigurationEquipement.Instance != null && UIConfigurationEquipement.Instance.EstOuvert())
+ {
+ _popupEtaitOuvert = true;
+ return;
+ }
+
+ if (_popupEtaitOuvert)
+ {
+ _popupEtaitOuvert = false;
+ Cursor.lockState = CursorLockMode.Locked;
+ Cursor.visible = false;
+ var playerInput = GetComponent();
+ if (playerInput != null) playerInput.enabled = true;
+ var fpc = GetComponent();
+ if (fpc != null) fpc.enabled = true;
+ }
+
+ if (Cursor.visible && _objetEnMain == null)
+ {
+ if (!_cacheInitialized || _cachedPause == null) { _cachedPause = FindObjectOfType(); _cachedMenu = FindObjectOfType(); if (_cachedPause != null) _cacheInitialized = true; }
+ bool pauseOuverte = (_cachedPause != null && _cachedPause.estEnPause);
+ bool menuOuvert = (_cachedMenu != null && _cachedMenu.EstAffiche());
+ MonitoringDatacenter monitoring = FindObjectOfType();
+ bool monitoringOuvert = (monitoring != null && monitoring.EstAffiche());
+ PlanSalle plan = FindObjectOfType();
+ bool planOuvert = (plan != null && plan.EstOuvert());
+ UIBoutique boutique = FindObjectOfType();
+ bool boutiqueOuverte = (boutique != null && boutique.panneauPrincipal != null && boutique.panneauPrincipal.activeSelf);
+ UIConfigurationEquipement configPopup = UIConfigurationEquipement.Instance;
+ bool configOuverte = (configPopup != null && configPopup.EstOuvert());
+ UITickets tickets = UITickets.Instance;
+ bool ticketsOuverts = (tickets != null && tickets.EstAffiche());
+
+ if (!pauseOuverte && !menuOuvert && !monitoringOuvert && !planOuvert && !boutiqueOuverte && !configOuverte && !ticketsOuverts)
+ {
+ Cursor.lockState = CursorLockMode.Locked;
+ Cursor.visible = false;
+ var fpc2 = GetComponent();
+ if (fpc2 != null) fpc2.enabled = true;
+ var pi2 = GetComponent();
+ if (pi2 != null) pi2.enabled = true;
+ }
+ }
+
+ if (_objetEnMain == null)
+ {
+ if (Time.frameCount % detectInterval == 0)
+ DetecterObjet();
+ if (_emplacementsVisibles) { SetEmplacementsVisibles(false); _emplacementsVisibles = false; }
+ if (_emplacementsPDUVisibles) { SetEmplacementsPDUVisibles(false); _emplacementsPDUVisibles = false; }
+ if (_axeSupportCibleLigne >= 0) QuitterSurvolSupport();
+ if (_emplacementSupportPDUCibleLigne >= 0) QuitterSurvolSupportPDU();
+ // v6.9 : cacher les fantomes du chariot quand on n'a plus rien en main
+ CacherFantomesChariotTous();
+ }
+ else
+ {
+ DetecterCiblePose();
+ bool portePDU = _objetEnMain.GetComponent() != null;
+ if (portePDU && !_emplacementsPDUVisibles) { SetEmplacementsPDUVisibles(true); _emplacementsPDUVisibles = true; }
+ if (!portePDU && _emplacementsPDUVisibles) { SetEmplacementsPDUVisibles(false); _emplacementsPDUVisibles = false; }
+ if (_emplacementsVisibles) { SetEmplacementsVisibles(false); _emplacementsVisibles = false; }
+ }
+
+ if (Input.GetKeyDown(toucheInteraction))
+ {
+ // v6.8 : s'asseoir si on vise une chaise et pas d'objet en main
+ if (_chaiseSiegeVisee != null && _objetEnMain == null)
+ {
+ if (_chaiseSiegeVisee.Asseoir(gameObject))
+ {
+ _chaiseSiegeActuelle = _chaiseSiegeVisee;
+ _chaiseSiegeVisee = null;
+ HUDManager hud = GetComponent();
+ if (hud != null) hud.SetInfoEquipement("[E] Se lever");
+ }
+ return;
+ }
+
+ if (_boutonPowerVise != null && _objetEnMain == null) { CmdAppuyerBoutonPower(_boutonPowerVise.GetComponentInParent().netId); return; }
+ if (_priseSurvolee != null && _priseSurvolee.estConnectee && _objetEnMain == null) { CmdBasculerInterrupteur(_priseSurvolee.GetComponentInParent().netId); return; }
+ if (_porteVisee != null && _objetEnMain == null)
+ {
+ NetworkIdentity porteNetId = _porteVisee.GetComponentInParent();
+ if (porteNetId != null) CmdTogglePorte(porteNetId.netId, _porteVisee.gameObject.name);
+ return;
+ }
+
+ if (_chariotPilote != null && _objetEnMain == null && _visePoignee)
+ {
+ LacherChariot();
+ QuitterSurvol();
+ return;
+ }
+
+ if (_objetEnMain == null && _objetSurvole != null) Ramasser();
+ else if (_objetEnMain != null) Poser();
+ }
+
+ if (Input.GetKeyDown(toucheConfiguration) && _objetEnMain == null && _objetSurvole != null)
+ {
+ RackSlot slotConfig = TrouverSlotDeEquipement(_objetSurvole);
+ if (slotConfig != null)
+ {
+ ConfigurationEquipement config = _objetSurvole.GetComponent();
+ if (config == null) config = _objetSurvole.gameObject.AddComponent();
+ if (UIConfigurationEquipement.Instance != null)
+ { UIConfigurationEquipement.Instance.Ouvrir(config, premiereOuverture: false); QuitterSurvol(); }
+ }
+ }
+
+ if (Input.GetKeyDown(KeyCode.F5) && isServer && SaveManager.Instance != null)
+ {
+ if (SaveManager.Instance.SauvegardeRapide())
+ Debug.Log("[QuickSave] Sauvegarde rapide effectuée !");
+ }
+ }
+
+ void FixedUpdate()
+ {
+ if (!isLocalPlayer) return;
+
+ if (_objetEnMain != null && _rbEnMain != null)
+ {
+ Vector3 dir = new Vector3(_camera.transform.forward.x, 0, _camera.transform.forward.z).normalized;
+
+ bool estToron = _objetEnMain.GetComponent() != null;
+ float dist = estToron ? distanceTransportToron : distanceTransport;
+ float hauteur = estToron ? hauteurTransportToron : hauteurTransport;
+ float decalage = estToron ? decalageHorizontalToron : decalageHorizontal;
+
+ _cibleTransport = transform.position + dir * dist
+ + Vector3.up * hauteur + _camera.transform.right * decalage;
+ Vector3 direction = _cibleTransport - _rbEnMain.position;
+ Vector3 desiredVelocity = direction * vitesseSuivi;
+ if (direction.magnitude > 0.05f)
+ {
+ RaycastHit hit;
+ int excludeMask = ~LayerMask.GetMask("Joueur", "Equipement", "Sol", "Baie", "Default", "Emplacement");
+ if (Physics.SphereCast(_rbEnMain.position, 0.15f, direction.normalized, out hit, direction.magnitude, excludeMask))
+ if (!EstColliderIgnore(hit.collider) && hit.distance < 0.15f)
+ desiredVelocity = Vector3.ProjectOnPlane(desiredVelocity, hit.normal);
+ }
+ _rbEnMain.velocity = desiredVelocity;
+
+ if (estToron)
+ _rbEnMain.rotation = Quaternion.LookRotation(dir) * Quaternion.Euler(rotationEulerToron);
+ else
+ _rbEnMain.rotation = Quaternion.LookRotation(dir) * _rotationOffsetTransport;
+
+ CmdUpdateTransportPosition(_rbEnMain.position, _rbEnMain.rotation);
+ }
+ }
+
+ // ══════════════════════════════════════════════════════════
+ // COMMANDS
+ // ══════════════════════════════════════════════════════════
+
+ [Command]
+ void CmdRamasser(uint objetNetId)
+ {
+ if (!NetworkServer.spawned.ContainsKey(objetNetId)) return;
+ _objetPorteNetId = objetNetId;
+ var obj = NetworkServer.spawned[objetNetId];
+ Rigidbody rb = obj.GetComponent();
+ if (rb != null) { rb.isKinematic = false; rb.useGravity = false; rb.freezeRotation = true; }
+
+ // v7.0 : Transfert d'authority pour que NetworkTransform Client-to-Server
+ // puisse propager la position aux autres clients + au serveur pendant le portage.
+ // Sans ce transfert, Mirror refuse les mises a jour de position venant du client porteur.
+ if (obj.connectionToClient != null && obj.connectionToClient != connectionToClient)
+ obj.RemoveClientAuthority();
+ obj.AssignClientAuthority(connectionToClient);
+
+ RpcOnRamasser(objetNetId);
+ }
+
+ [Command]
+ void CmdPoser(uint objetNetId, Vector3 position, Quaternion rotation, bool avecGravite, bool estRacke)
+ {
+ if (!NetworkServer.spawned.ContainsKey(objetNetId)) return;
+ _objetPorteNetId = 0;
+ var obj = NetworkServer.spawned[objetNetId];
+
+ // v7.0 : Retirer l'authority du client qui portait (rendue lors du ramassage),
+ // pour que le serveur redevienne autoritaire sur la position quand l'objet est pose.
+ if (obj.connectionToClient == connectionToClient)
+ obj.RemoveClientAuthority();
+
+ obj.transform.position = position;
+ obj.transform.rotation = rotation;
+ Rigidbody rb = obj.GetComponent();
+ if (rb != null)
+ {
+ if (estRacke) { if (!rb.isKinematic) { rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } rb.isKinematic = true; }
+ else { rb.isKinematic = false; rb.useGravity = avecGravite; rb.velocity = Vector3.zero; rb.freezeRotation = false; rb.interpolation = RigidbodyInterpolation.None; }
+ }
+ RpcOnPoser(objetNetId, position, rotation, avecGravite, estRacke);
+ }
+
+ [Command]
+ void CmdPoserToronSurSupport(uint toronNetId, int ligne, int colonne)
+ {
+ if (!NetworkServer.spawned.ContainsKey(toronNetId)) return;
+ _objetPorteNetId = 0;
+ var obj = NetworkServer.spawned[toronNetId];
+ if (obj == null) return;
+ bool place = false;
+ if (SupportTorons.Instance != null)
+ {
+ if (ligne >= 0 && colonne >= 0) place = SupportTorons.Instance.PoserToronSurAxeSpecifique(obj.gameObject, ligne, colonne);
+ if (!place) place = SupportTorons.Instance.PoserToron(obj.gameObject);
+ }
+ if (place) { RpcOnPoser(toronNetId, obj.transform.position, obj.transform.rotation, false, true); }
+ else { Rigidbody rb = obj.GetComponent(); if (rb != null) { rb.isKinematic = false; rb.useGravity = true; rb.velocity = Vector3.zero; } RpcOnPoser(toronNetId, obj.transform.position, obj.transform.rotation, true, false); }
+ }
+
+ [Command]
+ void CmdPoserPDUSurSupport(uint pduNetId, int ligne, int colonne)
+ {
+ if (!NetworkServer.spawned.ContainsKey(pduNetId)) return;
+ _objetPorteNetId = 0;
+ var obj = NetworkServer.spawned[pduNetId];
+ if (obj == null) return;
+ bool place = false;
+ if (SupportPDU.Instance != null)
+ {
+ if (ligne >= 0 && colonne >= 0) place = SupportPDU.Instance.PoserPDUSurEmplacementSpecifique(obj.gameObject, ligne, colonne);
+ if (!place) place = SupportPDU.Instance.PoserPDU(obj.gameObject);
+ }
+ if (place) { RpcOnPoser(pduNetId, obj.transform.position, obj.transform.rotation, false, true); }
+ else { Rigidbody rb = obj.GetComponent(); if (rb != null) { rb.isKinematic = false; rb.useGravity = true; rb.velocity = Vector3.zero; } RpcOnPoser(pduNetId, obj.transform.position, obj.transform.rotation, true, false); }
+ }
+
+ [Command(channel = Channels.Unreliable)]
+ void CmdUpdateTransportPosition(Vector3 position, Quaternion rotation) { RpcUpdateTransportPosition(position, rotation); }
+
+ [Command]
+ void CmdAppuyerBoutonPower(uint equipNetId)
+ {
+ if (!NetworkServer.spawned.ContainsKey(equipNetId)) return;
+ var obj = NetworkServer.spawned[equipNetId];
+ BoutonPower bouton = obj.GetComponentInChildren();
+ if (bouton != null) { bouton.Appuyer(); RpcSyncBoutonPower(equipNetId, bouton.estAllume); }
+ }
+
+ [ClientRpc]
+ void RpcSyncBoutonPower(uint equipNetId, bool estAllume)
+ {
+ if (!NetworkClient.spawned.ContainsKey(equipNetId)) return;
+ var obj = NetworkClient.spawned[equipNetId];
+ BoutonPower bouton = obj.GetComponentInChildren();
+ if (bouton != null && bouton.estAllume != estAllume) bouton.Appuyer();
+ }
+
+ [Command]
+ void CmdBasculerInterrupteur(uint pduNetId)
+ {
+ if (!NetworkServer.spawned.ContainsKey(pduNetId)) return;
+ var obj = NetworkServer.spawned[pduNetId];
+ PriseC13 prise = obj.GetComponentInChildren();
+ if (prise != null) prise.BasculerInterrupteur();
+ }
+
+ [Command]
+ void CmdInstallerPDU(uint pduNetId, Vector3 position, Quaternion rotation)
+ {
+ if (!NetworkServer.spawned.ContainsKey(pduNetId)) return;
+ var obj = NetworkServer.spawned[pduNetId];
+ PDU pdu = obj.GetComponent();
+ if (pdu == null) return;
+ PDUNetworkState netState = obj.GetComponent();
+ if (netState != null) netState.ServerSetInstalle(true, (int)pdu.coteInstallation);
+ else pdu.estInstalle = true;
+ RpcInstallerPDU(pduNetId, position, rotation);
+ }
+
+ [ClientRpc]
+ void RpcInstallerPDU(uint pduNetId, Vector3 position, Quaternion rotation)
+ {
+ if (!NetworkClient.spawned.ContainsKey(pduNetId)) return;
+ var obj = NetworkClient.spawned[pduNetId];
+ PDU pdu = obj.GetComponent();
+ if (pdu == null) return;
+ pdu.estInstalle = true;
+ if (!isLocalPlayer) { obj.transform.position = position; obj.transform.rotation = rotation; Rigidbody rb = obj.GetComponent(); if (rb != null) { rb.isKinematic = true; rb.useGravity = false; } }
+ }
+
+ [Command]
+ void CmdDesinstallerPDU(uint pduNetId)
+ {
+ if (!NetworkServer.spawned.ContainsKey(pduNetId)) return;
+ var obj = NetworkServer.spawned[pduNetId];
+ PDU pdu = obj.GetComponent();
+ if (pdu == null) return;
+ PDUNetworkState netState = obj.GetComponent();
+ if (netState != null) netState.ServerSetInstalle(false);
+ else pdu.estInstalle = false;
+ RpcDesinstallerPDU(pduNetId);
+ }
+
+ [ClientRpc]
+ void RpcDesinstallerPDU(uint pduNetId)
+ {
+ if (!NetworkClient.spawned.ContainsKey(pduNetId)) return;
+ var obj = NetworkClient.spawned[pduNetId];
+ PDU pdu = obj.GetComponent();
+ if (pdu != null) { pdu.estInstalle = false; Rigidbody rb = obj.GetComponent(); if (rb != null) { rb.isKinematic = false; rb.useGravity = true; } }
+ }
+
+ [Command]
+ void CmdInstallerDansSlot(uint equipNetId, uint baieNetId, int slotIndex)
+ {
+ if (!NetworkServer.spawned.ContainsKey(equipNetId) || !NetworkServer.spawned.ContainsKey(baieNetId)) return;
+ var equipObj = NetworkServer.spawned[equipNetId]; var baieObj = NetworkServer.spawned[baieNetId];
+ RackSlot[] slots = baieObj.GetComponentsInChildren();
+ if (slotIndex >= 0 && slotIndex < slots.Length) { RackSlot slot = slots[slotIndex]; if (!slot.estOccupe) { slot.estOccupe = true; slot.equipementInstalle = equipObj.GetComponent(); } }
+ RpcInstallerDansSlot(equipNetId, baieNetId, slotIndex);
+ }
+
+ [ClientRpc]
+ void RpcInstallerDansSlot(uint equipNetId, uint baieNetId, int slotIndex)
+ {
+ if (isLocalPlayer) return;
+ if (!NetworkClient.spawned.ContainsKey(equipNetId) || !NetworkClient.spawned.ContainsKey(baieNetId)) return;
+ var equipObj = NetworkClient.spawned[equipNetId]; var baieObj = NetworkClient.spawned[baieNetId];
+ RackSlot[] slots = baieObj.GetComponentsInChildren();
+ if (slotIndex >= 0 && slotIndex < slots.Length) { RackSlot slot = slots[slotIndex]; if (!slot.estOccupe) { slot.estOccupe = true; slot.equipementInstalle = equipObj.GetComponent(); } }
+ }
+
+ [Command]
+ void CmdTogglePorte(uint baieNetId, string nomPorte)
+ {
+ if (!NetworkServer.spawned.ContainsKey(baieNetId)) return;
+ var obj = NetworkServer.spawned[baieNetId];
+ foreach (PorteBaie porte in obj.GetComponentsInChildren())
+ if (porte.gameObject.name == nomPorte) { porte.TogglePorte(); RpcTogglePorte(baieNetId, nomPorte); break; }
+ }
+
+ // ══════════════════════════════════════════════════════════
+ // CLIENT RPCs
+ // ══════════════════════════════════════════════════════════
+
+ [ClientRpc]
+ void RpcOnRamasser(uint objetNetId)
+ {
+ if (isLocalPlayer) return;
+ if (!NetworkClient.spawned.ContainsKey(objetNetId)) return;
+ var obj = NetworkClient.spawned[objetNetId];
+ _objetPorteDistant = obj.gameObject;
+ Rigidbody rb = obj.GetComponent();
+ if (rb != null) { rb.isKinematic = true; rb.velocity = Vector3.zero; }
+ Interactable inter = obj.GetComponent();
+ if (inter != null) inter.estRamasse = true;
+ }
+
+ [ClientRpc]
+ void RpcOnPoser(uint objetNetId, Vector3 position, Quaternion rotation, bool avecGravite, bool estRacke)
+ {
+ if (isLocalPlayer) return;
+ if (!NetworkClient.spawned.ContainsKey(objetNetId)) return;
+ var obj = NetworkClient.spawned[objetNetId];
+ obj.transform.position = position; obj.transform.rotation = rotation;
+ Rigidbody rb = obj.GetComponent();
+ if (rb != null)
+ {
+ if (estRacke) { if (!rb.isKinematic) { rb.velocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } rb.isKinematic = true; }
+ else { rb.isKinematic = false; rb.useGravity = avecGravite; rb.velocity = Vector3.zero; }
+ }
+ Interactable inter = obj.GetComponent();
+ if (inter != null) inter.estRamasse = false;
+ _objetPorteDistant = null;
+ }
+
+ [ClientRpc(channel = Channels.Unreliable)]
+ void RpcUpdateTransportPosition(Vector3 position, Quaternion rotation)
+ {
+ if (isLocalPlayer) return;
+ if (_objetPorteDistant != null)
+ {
+ _objetPorteDistant.transform.position = Vector3.Lerp(_objetPorteDistant.transform.position, position, Time.deltaTime * 20f);
+ _objetPorteDistant.transform.rotation = Quaternion.Slerp(_objetPorteDistant.transform.rotation, rotation, Time.deltaTime * 20f);
+ }
+ }
+
+ [ClientRpc]
+ void RpcTogglePorte(uint baieNetId, string nomPorte)
+ {
+ if (isServer) return;
+ if (!NetworkClient.spawned.ContainsKey(baieNetId)) return;
+ var obj = NetworkClient.spawned[baieNetId];
+ foreach (PorteBaie porte in obj.GetComponentsInChildren())
+ if (porte.gameObject.name == nomPorte) { porte.TogglePorte(); break; }
+ }
+
+ // ══════════════════════════════════════════════════════════
+ // HOOK SyncVar
+ // ══════════════════════════════════════════════════════════
+
+ void OnObjetPorteChange(uint ancienId, uint nouveauId)
+ {
+ if (isLocalPlayer) return;
+ if (nouveauId == 0) _objetPorteDistant = null;
+ else if (NetworkClient.spawned.ContainsKey(nouveauId)) _objetPorteDistant = NetworkClient.spawned[nouveauId].gameObject;
+ }
+
+ // ══════════════════════════════════════════════════════════
+ // HELPERS LOCAUX
+ // ══════════════════════════════════════════════════════════
+
+ private bool EstColliderIgnore(Collider col) { for (int i = 0; i < _collisionsIgnorees.Count; i++) if (_collisionsIgnorees[i] == col) return true; return false; }
+
+ void SetEmplacementsVisibles(bool visible)
+ {
+ if (_tousEmplacements == null || _tousEmplacements.Length == 0) _tousEmplacements = FindObjectsOfType();
+ foreach (EmplacementBaie empl in _tousEmplacements) if (empl != null && !empl.estOccupe) empl.SetModeVisible(visible);
+ }
+
+ void SetEmplacementsPDUVisibles(bool visible)
+ {
+ foreach (EmplacementPDU empl in FindObjectsOfType()) if (empl != null && !empl.estOccupe) empl.SetModeVisible(visible);
+ }
+
+ // v6.9 : helper pour cacher les fantomes du chariot
+ void CacherFantomesChariotTous()
+ {
+ if (_chariotVise != null) { _chariotVise.CacherFantomes(); }
+ if (_chariotPilote != null) { _chariotPilote.CacherFantomes(); }
+ // v6.11 : CacherFantomes() inclut deja le fantome toron axe
+ // (cf. Chariot.CacherFantomes v7.3) mais on le reappelle explicitement ici
+ // au cas ou la version du Chariot n'aurait pas encore ete mise a jour
+ if (_chariotVise != null) _chariotVise.CacherFantomeToronAxe();
+ if (_chariotPilote != null) _chariotPilote.CacherFantomeToronAxe();
+ }
+
+ // ==================== DETECTION OBJET ====================
+
+ void DetecterObjet()
+ {
+ QuitterSurvolSlot(); QuitterSurvolEmplacement(); QuitterSurvolEmplacementPDU();
+ QuitterSurvolPrise(); QuitterSurvolBoutonPower();
+ _chariotVise = null; _visePoignee = false; _porteVisee = null;
+
+ if (_camera == null) return;
+
+ Ray rayon = new Ray(_camera.transform.position, _camera.transform.forward);
+ QueryTriggerInteraction triggerMode = detecterTriggers ? QueryTriggerInteraction.Collide : QueryTriggerInteraction.Ignore;
+
+ int maskObjets = ~LayerMask.GetMask("Joueur", "Baie");
+ RaycastHit hitSimple;
+ if (Physics.Raycast(rayon, out hitSimple, porteeInteraction, maskObjets, triggerMode))
+ {
+ if (!EstColliderIgnore(hitSimple.collider))
+ if (TraiterHitDetection(hitSimple)) return;
+ }
+
+ int maskBaie = LayerMask.GetMask("Baie");
+ if (Physics.Raycast(rayon, out hitSimple, porteeInteraction, maskBaie, triggerMode))
+ {
+ if (!EstColliderIgnore(hitSimple.collider))
+ if (TraiterHitBaie(hitSimple)) return;
+ }
+
+ QuitterSurvol();
+ }
+
+ bool TraiterHitDetection(RaycastHit impact)
+ {
+ // v6.8 : chaise pour s'asseoir
+ ChaiseSiege chaise = impact.collider.GetComponentInParent();
+ if (chaise != null && !chaise.estOccupee && _objetEnMain == null)
+ {
+ _chaiseSiegeVisee = chaise;
+ HUDManager hud = GetComponent();
+ if (hud != null) hud.SetInfoEquipement("[E] S'asseoir");
+ QuitterSurvol();
+ return true;
+ }
+ if (_chaiseSiegeVisee != null && chaise == null)
+ _chaiseSiegeVisee = null;
+
+ BoutonPower bouton = impact.collider.GetComponent();
+ if (bouton != null) { _boutonPowerVise = bouton; bouton.Survoler(true); return true; }
+
+ PriseC13 prise = impact.collider.GetComponent();
+ if (prise != null) { _priseSurvolee = prise; prise.Survoler(true); return true; }
+
+ PorteBaie porte = impact.collider.GetComponentInParent();
+ if (porte != null) { _porteVisee = porte; QuitterSurvol(); return true; }
+
+ Chariot chariotHit = impact.collider.GetComponentInParent();
+ if (chariotHit != null && impact.collider.gameObject.name == "Ch_PoigneeZone")
+ {
+ _visePoignee = true;
+ Interactable ci = chariotHit.GetComponent();
+ if (ci != null) SetSurvol(ci);
+ return true;
+ }
+
+ MonitoringDatacenter monitoring = impact.collider.GetComponent();
+ if (monitoring != null) { Interactable interMon = impact.collider.GetComponent(); if (interMon != null) SetSurvol(interMon); return true; }
+
+ PDU pduVise = impact.collider.GetComponentInParent();
+ if (pduVise != null && pduVise.estInstalle) { Interactable interPDU = pduVise.GetComponent(); if (interPDU != null) { SetSurvol(interPDU); return true; } }
+
+ Interactable obj = impact.collider.GetComponentInParent();
+ if (obj != null)
+ {
+ if (obj.GetComponent() != null && !_visePoignee) { QuitterSurvol(); return true; }
+ SetSurvol(obj); return true;
+ }
+
+ return false;
+ }
+
+ bool TraiterHitBaie(RaycastHit impact)
+ {
+ if (impact.collider.gameObject.name == "BaieZoneTrigger") return false;
+ PorteBaie porte = impact.collider.GetComponent();
+ if (porte != null) { _porteVisee = porte; QuitterSurvol(); return true; }
+ RackSlot slot = impact.collider.GetComponent();
+ if (slot != null && slot.estOccupe && slot.equipementInstalle != null)
+ {
+ if (!PorteAvantOuverte(slot)) return false;
+ BoutonPower boutonRack = slot.equipementInstalle.GetComponentInChildren();
+ if (boutonRack != null && ViseBouton(boutonRack.transform.position)) { _boutonPowerVise = boutonRack; boutonRack.Survoler(true); return true; }
+ SetSurvol(slot.equipementInstalle); return true;
+ }
+ EmplacementPDU emplPDU = impact.collider.GetComponent();
+ if (emplPDU != null) return false;
+ return false;
+ }
+
+ // ==================== DETECTION CIBLE POSE ====================
+
+ void DetecterCiblePose()
+ {
+ _porteVisee = null;
+ QuitterSurvolSlot(); QuitterSurvolEmplacement(); QuitterSurvolEmplacementPDU();
+
+ if (_camera == null) return;
+
+ if (DetecterCibleSupportTorons()) return;
+ QuitterSurvolSupport();
+
+ if (DetecterCibleSupportPDU()) return;
+ QuitterSurvolSupportPDU();
+
+ Ray rayon = new Ray(_camera.transform.position, _camera.transform.forward);
+ bool portePDU = _objetEnMain != null && _objetEnMain.GetComponent() != null;
+
+ List colsPortes = new List();
+ if (_objetEnMain != null)
+ foreach (Collider c in _objetEnMain.GetComponentsInChildren())
+ if (c.enabled) { c.enabled = false; colsPortes.Add(c); }
+
+ int slotMask = LayerMask.GetMask("Baie", "Sol", "Emplacement");
+ RaycastHit[] hits = Physics.RaycastAll(rayon, porteeInteraction, slotMask, QueryTriggerInteraction.Collide);
+ System.Array.Sort(hits, (a, b) => a.distance.CompareTo(b.distance));
+ foreach (Collider c in colsPortes) c.enabled = true;
+
+ RackSlot slotTouche = null; EmplacementBaie emplTouche = null; EmplacementPDU emplPDUTouche = null;
+
+ foreach (var hit in hits)
+ {
+ if (hit.collider.gameObject.name == "BaieZoneTrigger") continue;
+ if (portePDU) { EmplacementPDU ep = hit.collider.GetComponent(); if (ep != null && !ep.estOccupe) { emplPDUTouche = ep; break; } continue; }
+ RackSlot s = hit.collider.GetComponent(); if (s != null) { slotTouche = s; break; }
+ EmplacementBaie e = hit.collider.GetComponent(); if (e != null) { emplTouche = e; break; }
+ }
+
+ if (emplPDUTouche != null)
+ {
+ if (_emplacementPDUVise != emplPDUTouche) { QuitterSurvolEmplacementPDU(); _emplacementPDUVise = emplPDUTouche; _emplacementPDUVise.SurvolDebut(); }
+ HUDManager hud = GetComponent();
+ if (hud != null) hud.SetInfoEquipement("[E] Installer PDU (" + (_emplacementPDUVise.cote == EmplacementPDU.CotePDU.Gauche ? "Gauche" : "Droit") + ")");
+ // v6.9 : on a trouve une cible non-chariot -> cacher les fantomes
+ CacherFantomesChariotTous();
+ _chariotVise = null; _chariotZoneEtagereVisee = false;
+ return;
+ }
+
+ if (slotTouche != null)
+ {
+ bool porteOuverte = PorteAvantOuverte(slotTouche);
+ if (_slotSurvole != slotTouche) { QuitterSurvolSlot(); _slotSurvole = slotTouche; if (!slotTouche.estOccupe && porteOuverte) _slotSurvole.SurvolDebut(); }
+ if (!slotTouche.estOccupe) { HUDManager hud = GetComponent(); if (hud != null) hud.SetInfoEquipement(porteOuverte ? "U" + slotTouche.numeroSlot.ToString("D2") + " - [E] Installer ici" : "Ouvrir la porte avant d'abord"); }
+ CacherFantomesChariotTous();
+ _chariotVise = null; _chariotZoneEtagereVisee = false;
+ return;
+ }
+
+ if (emplTouche != null && !emplTouche.estOccupe)
+ { if (_emplacementVise != emplTouche) { QuitterSurvolEmplacement(); _emplacementVise = emplTouche; _emplacementVise.SurvolDebut(); } CacherFantomesChariotTous(); _chariotVise = null; _chariotZoneEtagereVisee = false; return; }
+
+ HUDManager hudClear = GetComponent(); if (hudClear != null && _slotSurvole == null) hudClear.SetInfoEquipement("");
+
+ // ───── v6.11 : Detection zones de pose du chariot (plateau, etagere, axes toron) ─────
+ Chariot ancienChariot = _chariotVise;
+ _chariotVise = null;
+ _chariotZoneEtagereVisee = false;
+ _chariotAxeToronCibleIndex = -1;
+
+ bool porteToron = (_objetEnMain != null && _objetEnMain.GetComponent() != null);
+
+ int maskChariot = LayerMask.GetMask("Chariot");
+ RaycastHit[] hitsChariot;
+ if (maskChariot != 0)
+ hitsChariot = Physics.RaycastAll(rayon, porteeInteraction, maskChariot, QueryTriggerInteraction.Collide);
+ else
+ hitsChariot = Physics.RaycastAll(rayon, porteeInteraction, ~LayerMask.GetMask("Joueur", "Equipement", "Sol", "Baie", "Emplacement"), QueryTriggerInteraction.Collide);
+
+ System.Array.Sort(hitsChariot, (a, b) => a.distance.CompareTo(b.distance));
+
+ Chariot chariotHit = null;
+ int axeToronIdx = -1;
+ bool zoneTrouvee = false;
+
+ foreach (var h in hitsChariot)
+ {
+ Chariot c = h.collider.GetComponentInParent();
+ if (c == null) continue;
+
+ if (porteToron)
+ {
+ // Porte un toron : priorite aux axes toron, on ignore plateau/etagere
+ if (c.zonesToronAxes != null)
+ {
+ int iFound = -1;
+ for (int i = 0; i < c.zonesToronAxes.Length; i++)
+ {
+ if (c.zonesToronAxes[i] != null && h.collider.gameObject == c.zonesToronAxes[i])
+ {
+ // Verifier que l'axe est libre
+ bool libre = (c.equipementsToronAxes == null
+ || i >= c.equipementsToronAxes.Count
+ || c.equipementsToronAxes[i] == null);
+ if (libre) { iFound = i; break; }
+ }
+ }
+ if (iFound >= 0)
+ {
+ chariotHit = c;
+ axeToronIdx = iFound;
+ zoneTrouvee = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // Equipement normal : zones plateau/etagere
+ bool estZonePlateau = (c.zonePosePlateau != null && h.collider.gameObject == c.zonePosePlateau);
+ bool estZoneEtagere = (c.zonePoseEtagere != null && h.collider.gameObject == c.zonePoseEtagere);
+
+ if (estZonePlateau || estZoneEtagere)
+ {
+ chariotHit = c;
+ _chariotZoneEtagereVisee = estZoneEtagere;
+ zoneTrouvee = true;
+ break;
+ }
+ }
+
+ // Fallback : garder le chariot comme candidat si pas encore trouve de zone
+ if (chariotHit == null) chariotHit = c;
+ }
+
+ if (chariotHit != null)
+ {
+ _chariotVise = chariotHit;
+
+ if (ancienChariot != null && ancienChariot != chariotHit)
+ {
+ ancienChariot.CacherFantomes();
+ ancienChariot.CacherFantomeToronAxe();
+ }
+
+ if (porteToron && zoneTrouvee && axeToronIdx >= 0)
+ {
+ _chariotAxeToronCibleIndex = axeToronIdx;
+ chariotHit.AfficherFantomeToronAxe(axeToronIdx);
+ HUDManager hud = GetComponent();
+ if (hud != null)
+ hud.SetInfoEquipement(axeToronIdx == 0
+ ? "[E] Accrocher le toron sur l'axe gauche"
+ : "[E] Accrocher le toron sur l'axe droit");
+ }
+ else if (!porteToron && zoneTrouvee)
+ {
+ int tailleU = (_objetEnMain != null && _objetEnMain.tailleU > 0) ? _objetEnMain.tailleU : 1;
+ chariotHit.AfficherFantomePose(tailleU, _chariotZoneEtagereVisee);
+ HUDManager hud = GetComponent();
+ if (hud != null)
+ hud.SetInfoEquipement(_chariotZoneEtagereVisee ? "[E] Poser sur l'etagere basse" : "[E] Poser sur le plateau");
+ }
+ else
+ {
+ // Chariot vise mais pas de zone specifique : cacher les fantomes
+ chariotHit.CacherFantomes();
+ chariotHit.CacherFantomeToronAxe();
+ }
+ }
+ else
+ {
+ if (ancienChariot != null)
+ {
+ ancienChariot.CacherFantomes();
+ ancienChariot.CacherFantomeToronAxe();
+ }
+ }
+ }
+
+ // ==================== DÉTECTION SUPPORTS MURAUX ====================
+
+ bool DetecterCibleSupportTorons()
+ {
+ if (_objetEnMain == null) return false;
+ if (_objetEnMain.GetComponent() == null) return false;
+ SupportTorons support = SupportTorons.Instance;
+ if (support == null) return false;
+ float dist = Vector3.Distance(transform.position, support.transform.position);
+ if (dist > distanceDetectionSupport) return false;
+ Vector3 dirSupport = (support.transform.position - _camera.transform.position);
+ float distCam = dirSupport.magnitude;
+ if (distCam < 0.01f) return false;
+ dirSupport /= distCam;
+ float angle = Vector3.Angle(_camera.transform.forward, dirSupport);
+ if (angle > angleDetectionSupport) return false;
+ int r, c; Vector3 posAxe;
+ if (!support.TrouverAxeLibreLePlusProche(transform.position, out r, out c, out posAxe)) return false;
+ support.AfficherSurvolAxe(r, c);
+ _axeSupportCibleLigne = r; _axeSupportCibleColonne = c;
+ HUDManager hud = GetComponent();
+ if (hud != null) hud.SetInfoEquipement("[E] Remettre le toron sur le support");
+ return true;
+ }
+
+ void QuitterSurvolSupport()
+ {
+ if (_axeSupportCibleLigne < 0) return;
+ if (SupportTorons.Instance != null) SupportTorons.Instance.CacherSurvolAxe();
+ _axeSupportCibleLigne = -1; _axeSupportCibleColonne = -1;
+ }
+
+ bool DetecterCibleSupportPDU()
+ {
+ if (_objetEnMain == null) return false;
+ PDU pdu = _objetEnMain.GetComponent();
+ if (pdu == null) return false;
+ if (pdu.estInstalle) return false;
+ SupportPDU support = SupportPDU.Instance;
+ if (support == null) return false;
+ float dist = Vector3.Distance(transform.position, support.transform.position);
+ if (dist > distanceDetectionSupport) return false;
+ Vector3 dirSupport = (support.transform.position - _camera.transform.position);
+ float distCam = dirSupport.magnitude;
+ if (distCam < 0.01f) return false;
+ dirSupport /= distCam;
+ float angle = Vector3.Angle(_camera.transform.forward, dirSupport);
+ if (angle > angleDetectionSupport) return false;
+ int r, c; Vector3 posEmpl;
+ if (!support.TrouverEmplacementLibreLePlusProche(transform.position, out r, out c, out posEmpl)) return false;
+ support.AfficherSurvolEmplacement(r, c);
+ _emplacementSupportPDUCibleLigne = r; _emplacementSupportPDUCibleColonne = c;
+ HUDManager hud = GetComponent();
+ if (hud != null) hud.SetInfoEquipement("[E] Remettre le PDU sur le support");
+ return true;
+ }
+
+ void QuitterSurvolSupportPDU()
+ {
+ if (_emplacementSupportPDUCibleLigne < 0) return;
+ if (SupportPDU.Instance != null) SupportPDU.Instance.CacherSurvolEmplacement();
+ _emplacementSupportPDUCibleLigne = -1; _emplacementSupportPDUCibleColonne = -1;
+ }
+
+ // ==================== SURVOL HELPERS ====================
+
+ void SetSurvol(Interactable obj) { if (_objetSurvole != obj) { if (_objetSurvole != null) _objetSurvole.SurvoleeFin(); _objetSurvole = obj; _objetSurvole.SurvoleDebut(); } }
+ void QuitterSurvol() { if (_objetSurvole != null) { _objetSurvole.SurvoleeFin(); _objetSurvole = null; } }
+ void QuitterSurvolSlot() { if (_slotSurvole != null) { _slotSurvole.SurvolFin(); _slotSurvole = null; } }
+ void QuitterSurvolEmplacement() { if (_emplacementVise != null) { _emplacementVise.SurvolFin(); _emplacementVise = null; } }
+ void QuitterSurvolEmplacementPDU() { if (_emplacementPDUVise != null) { _emplacementPDUVise.SurvolFin(); _emplacementPDUVise = null; } }
+ void QuitterSurvolPrise() { if (_priseSurvolee != null) { _priseSurvolee.Survoler(false); _priseSurvolee = null; } }
+ void QuitterSurvolBoutonPower() { if (_boutonPowerVise != null) { _boutonPowerVise.Survoler(false); _boutonPowerVise = null; } }
+
+ // ==================== RAMASSER ====================
+
+ void Ramasser()
+ {
+ TerminalCommande terminal = _objetSurvole.GetComponent();
+ if (terminal != null) { terminal.Interagir(); QuitterSurvol(); return; }
+ MonitoringDatacenter monitoring = _objetSurvole.GetComponent();
+ if (monitoring != null) { monitoring.Interagir(); QuitterSurvol(); return; }
+ TableauTickets tableau = _objetSurvole.GetComponent();
+ if (tableau != null) { tableau.Interagir(); QuitterSurvol(); return; }
+ AgrandissementSalle agr = _objetSurvole.GetComponentInParent();
+ if (agr != null && !agr.estAgrandi && !agr.enAnimation) { agr.Agrandir(); QuitterSurvol(); return; }
+
+ Chariot chariot = _objetSurvole.GetComponent();
+ if (chariot != null && _visePoignee)
+ {
+ if (_chariotPilote == chariot)
+ {
+ LacherChariot();
+ }
+ else
+ {
+ if (_chariotPilote != null) LacherChariot();
+ PrendreChariot(chariot);
+ QuitterSurvol();
+ }
+ return;
+ }
+
+ Chariot chariotParent = _objetSurvole.GetComponentInParent();
+ if (chariotParent != null && chariot == null)
+ {
+ if (chariotParent.estPilote) { QuitterSurvol(); return; }
+
+ // v6.11 : verifier en PREMIER si le toron survole est accroche sur un axe lateral
+ if (chariotParent.equipementsToronAxes != null)
+ {
+ for (int i = 0; i < chariotParent.equipementsToronAxes.Count; i++)
+ {
+ if (chariotParent.equipementsToronAxes[i] == _objetSurvole.gameObject)
+ {
+ GameObject t = chariotParent.RetirerToronDeAxeChariot(i);
+ if (t != null)
+ {
+ PrendreEnMain(t.GetComponent());
+ return;
+ }
+ }
+ }
+ }
+
+ // v6.9 : detecter si l'objet est sur le plateau ou sur l'etagere basse
+ bool surEtagere = chariotParent.equipementsSurEtagere != null
+ && chariotParent.equipementsSurEtagere.Contains(_objetSurvole.gameObject);
+
+ GameObject top = surEtagere
+ ? chariotParent.RetirerEquipementSurEtagere()
+ : chariotParent.RetirerEquipementDuDessus();
+
+ if (top != null)
+ {
+ // v6.13 Bug 2 : broadcaster le detachement a tous les clients
+ // (sinon sur les autres clients l'objet reste parente au chariot et
+ // vole avec lui quand on le lache).
+ NetworkIdentity topNetId = top.GetComponent();
+ if (topNetId != null) CmdDetacherDuChariot(topNetId.netId);
+
+ PrendreEnMain(top.GetComponent());
+ return;
+ }
+ }
+
+ PDU pdu = _objetSurvole.GetComponent();
+ if (pdu != null && pdu.estInstalle)
+ {
+ bool cables = false;
+ foreach (var p in pdu.prises) if (p != null && p.estConnectee) { cables = true; break; }
+ if (cables) { Debug.LogWarning("Impossible : des cables sont branches !"); QuitterSurvol(); return; }
+ pdu.Desinstaller();
+ NetworkIdentity pduNetIdentity = pdu.GetComponent();
+ if (pduNetIdentity != null) CmdDesinstallerPDU(pduNetIdentity.netId);
+ PrendreEnMain(_objetSurvole);
+ return;
+ }
+
+ BaieRack baieRack = _objetSurvole.GetComponent() ?? _objetSurvole.GetComponentInChildren();
+ if (baieRack != null) { EmplacementBaie empl = TrouverEmplacementDeBaie(_objetSurvole.gameObject); if (empl != null) empl.RetirerBaie(); }
+
+ RackSlot slotSrc = TrouverSlotDeEquipement(_objetSurvole);
+ if (slotSrc != null)
+ {
+ if (!PorteAvantOuverte(slotSrc)) { HUDManager hud = GetComponent(); if (hud != null) hud.SetInfoEquipement("Ouvrir la porte avant d'abord"); QuitterSurvol(); return; }
+ RackSlot[] slotsArr = ObtenirSlotsParent(slotSrc);
+ if (slotsArr != null) slotSrc.Liberer(slotsArr);
+ }
+
+ ZoneLivraison zone = _objetSurvole.GetComponentInParent();
+ if (zone == null) zone = ZoneLivraison.Instance;
+ if (zone != null)
+ {
+ Interactable plusHaut = zone.TrouverPlusHautDansPile(_objetSurvole);
+ if (plusHaut == null) plusHaut = _objetSurvole;
+ Vector3 pos = plusHaut.transform.position;
+ if (plusHaut != _objetSurvole) _objetSurvole.SurvoleeFin();
+ plusHaut.transform.SetParent(null);
+ IgnorerCollisionsAvecStructure(plusHaut.gameObject, zone.transform, true);
+ PrendreEnMain(plusHaut);
+ // v6.13 Bug 1 : reorganisation cote SERVEUR pour que les positions
+ // soient sync via NetworkTransform a tous les clients.
+ CmdReorganiserZoneLivraison(pos);
+ return;
+ }
+
+ PrendreEnMain(_objetSurvole);
+ }
+
+ void PrendreEnMain(Interactable obj)
+ {
+ if (obj == null) return;
+ CartonLivraison carton = obj.GetComponent();
+ if (carton != null && carton.ContientBaie()) { QuitterSurvol(); carton.OuvrirPlanPlacement(); return; }
+ _objetEnMain = obj; _objetSurvole = null;
+ _objetEnMain.Ramasser();
+ _rbEnMain = _objetEnMain.GetComponent();
+ if (_rbEnMain != null)
+ {
+ _rbEnMain.isKinematic = false; _rbEnMain.useGravity = false;
+ _rbEnMain.freezeRotation = true;
+ _rbEnMain.collisionDetectionMode = CollisionDetectionMode.Continuous;
+ _rbEnMain.interpolation = RigidbodyInterpolation.Interpolate;
+ Vector3 dirJoueur = new Vector3(_camera.transform.forward.x, 0, _camera.transform.forward.z).normalized;
+ Quaternion rotJoueur = Quaternion.LookRotation(dirJoueur);
+ _rotationOffsetTransport = Quaternion.Inverse(rotJoueur) * _objetEnMain.transform.rotation;
+ }
+ NetworkIdentity objNetId = obj.GetComponent();
+ if (objNetId != null) CmdRamasser(objNetId.netId);
+ }
+
+ // ==================== POSER ====================
+
+ void Poser()
+ {
+ RestaurerCollisionsIgnorees();
+
+ if (_axeSupportCibleLigne >= 0 && _objetEnMain != null && _objetEnMain.GetComponent() != null)
+ { PoserSurSupportTorons(); return; }
+
+ if (_emplacementSupportPDUCibleLigne >= 0 && _objetEnMain != null && _objetEnMain.GetComponent() != null)
+ { PoserSurSupportPDU(); return; }
+
+ // v6.11 : pose d'un toron sur un axe lateral du chariot (prioritaire sur plateau/etagere)
+ if (_chariotVise != null && _chariotAxeToronCibleIndex >= 0
+ && _objetEnMain != null && _objetEnMain.GetComponent() != null)
+ {
+ int indexAxe = _chariotAxeToronCibleIndex;
+ if (_chariotVise.PoserToronSurAxeChariot(_objetEnMain.gameObject, indexAxe))
+ {
+ _chariotVise.CacherFantomeToronAxe();
+ Vector3 posT = _objetEnMain.transform.position;
+ Quaternion rotT = _objetEnMain.transform.rotation;
+ NotifierPoserReseau(false, posT, rotT, estRacke: true);
+
+ // v6.13 Bug 2 : broadcast parenting a tous les clients
+ NetworkIdentity eqNetId = _objetEnMain.GetComponent();
+ NetworkIdentity chNetId = _chariotVise.GetComponent();
+ if (eqNetId != null && chNetId != null)
+ CmdParenterAuChariot(eqNetId.netId, chNetId.netId);
+
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ _chariotAxeToronCibleIndex = -1;
+ HUDManager hud = GetComponent();
+ if (hud != null) hud.SetInfoEquipement("");
+ return;
+ }
+ }
+
+ bool estNonRackable = (_objetEnMain.tailleU <= 0 || _objetEnMain.GetComponent() != null);
+ bool estCarton = _objetEnMain.GetComponent() != null;
+ bool estBaie = _objetEnMain.GetComponent() != null;
+ bool estPDU = _objetEnMain.GetComponent() != null;
+
+ Vector3 posFinal = _objetEnMain.transform.position;
+ Quaternion rotFinal = _objetEnMain.transform.rotation;
+ bool gravite = true;
+
+ if (estPDU && _emplacementPDUVise != null && !_emplacementPDUVise.estOccupe)
+ {
+ PDU pduObj = _objetEnMain.GetComponent();
+ pduObj.InstallerSurEmplacement(_emplacementPDUVise);
+ gravite = false; posFinal = _objetEnMain.transform.position; rotFinal = _objetEnMain.transform.rotation;
+ NotifierPoserReseau(gravite, posFinal, rotFinal, estRacke: true);
+ NetworkIdentity pduNetId = _objetEnMain.GetComponent();
+ if (pduNetId != null) CmdInstallerPDU(pduNetId.netId, posFinal, rotFinal);
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ QuitterSurvolEmplacementPDU();
+ HUDManager hud = GetComponent(); if (hud != null) hud.SetInfoEquipement("");
+ CacherFantomesChariotTous();
+ return;
+ }
+ if (estPDU)
+ {
+ PDU pduObj = _objetEnMain.GetComponent();
+ GestionnaireAlimentation ga = GetComponent();
+ if (ga != null && !pduObj.estInstalle && ga.TenterSnapPDU(pduObj))
+ {
+ gravite = false; posFinal = _objetEnMain.transform.position; rotFinal = _objetEnMain.transform.rotation;
+ NotifierPoserReseau(gravite, posFinal, rotFinal);
+ NetworkIdentity pduNetId = _objetEnMain.GetComponent();
+ if (pduNetId != null) CmdInstallerPDU(pduNetId.netId, posFinal, rotFinal);
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ CacherFantomesChariotTous();
+ return;
+ }
+ }
+ if (estCarton)
+ {
+ CartonLivraison carton = _objetEnMain.GetComponent();
+ if (carton != null && carton.ContientEquipement())
+ {
+ if (_rbEnMain != null) { _rbEnMain.isKinematic = true; _rbEnMain.velocity = Vector3.zero; }
+ gravite = false; posFinal = _objetEnMain.transform.position; rotFinal = _objetEnMain.transform.rotation;
+ NotifierPoserReseau(gravite, posFinal, rotFinal);
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ carton.TransformerAuSol();
+ CacherFantomesChariotTous();
+ return;
+ }
+ }
+ if (!estNonRackable && !estBaie && !estCarton && !estPDU && _slotSurvole != null && !_slotSurvole.estOccupe)
+ {
+ if (!PorteAvantOuverte(_slotSurvole)) { HUDManager hud = GetComponent(); if (hud != null) hud.SetInfoEquipement("Ouvrir la porte avant d'abord"); return; }
+ RackSlot[] slotsArr = ObtenirSlotsParent(_slotSurvole);
+ if (slotsArr != null && _slotSurvole.TenterInstallation(_objetEnMain, slotsArr))
+ {
+ gravite = false; posFinal = _objetEnMain.transform.position; rotFinal = _objetEnMain.transform.rotation;
+ NotifierPoserReseau(gravite, posFinal, rotFinal, estRacke: true);
+ NetworkIdentity equipNetId = _objetEnMain.GetComponent();
+ NetworkIdentity baieNetId = _slotSurvole.GetComponentInParent();
+ if (equipNetId != null && baieNetId != null) { RackSlot[] allSlots = baieNetId.GetComponentsInChildren(); int slotIdx = System.Array.IndexOf(allSlots, _slotSurvole); CmdInstallerDansSlot(equipNetId.netId, baieNetId.netId, slotIdx); }
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ QuitterSurvolSlot();
+ HUDManager hud = GetComponent(); if (hud != null) hud.SetInfoEquipement("");
+ CacherFantomesChariotTous();
+ return;
+ }
+ }
+
+ // ───── v6.10 : Pose sur chariot (plateau ou etagere basse) ─────
+ // IMPORTANT : estRacke=true pour que le serveur mette isKinematic=true.
+ // Sinon les objets poses deviennent non-kinematic sans gravite cote serveur
+ // et flottent en apesanteur des qu'on en retire un du dessus.
+ if (_chariotVise != null && !estNonRackable && !estBaie && !estCarton && !estPDU)
+ {
+ bool pose = _chariotZoneEtagereVisee
+ ? _chariotVise.PoserEquipementSurEtagere(_objetEnMain.gameObject)
+ : _chariotVise.PoserEquipement(_objetEnMain.gameObject);
+ if (pose)
+ {
+ _chariotVise.CacherFantomes();
+ gravite = false; posFinal = _objetEnMain.transform.position; rotFinal = _objetEnMain.transform.rotation;
+ NotifierPoserReseau(gravite, posFinal, rotFinal, estRacke: true);
+
+ // v6.13 Bug 2 : broadcast parenting a tous les clients
+ NetworkIdentity eqNetId = _objetEnMain.GetComponent();
+ NetworkIdentity chNetId = _chariotVise.GetComponent();
+ if (eqNetId != null && chNetId != null)
+ CmdParenterAuChariot(eqNetId.netId, chNetId.netId);
+
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ return;
+ }
+ }
+ if (_chariotPilote != null && _chariotVise == null && !estNonRackable && !estBaie && !estCarton && !estPDU)
+ if (_chariotPilote.PoserEquipement(_objetEnMain.gameObject))
+ {
+ _chariotPilote.CacherFantomes();
+ gravite = false; posFinal = _objetEnMain.transform.position; rotFinal = _objetEnMain.transform.rotation;
+ NotifierPoserReseau(gravite, posFinal, rotFinal, estRacke: true);
+
+ // v6.13 Bug 2 : broadcast parenting a tous les clients
+ NetworkIdentity eqNetId = _objetEnMain.GetComponent();
+ NetworkIdentity chNetId = _chariotPilote.GetComponent();
+ if (eqNetId != null && chNetId != null)
+ CmdParenterAuChariot(eqNetId.netId, chNetId.netId);
+
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ return;
+ }
+
+ // Drop au sol
+ if (_rbEnMain != null) { _rbEnMain.useGravity = true; _rbEnMain.velocity = Vector3.zero; _rbEnMain.interpolation = RigidbodyInterpolation.None; }
+ posFinal = _objetEnMain.transform.position; rotFinal = _objetEnMain.transform.rotation;
+ NotifierPoserReseau(true, posFinal, rotFinal);
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ QuitterSurvolSlot(); QuitterSurvolEmplacement(); QuitterSurvolEmplacementPDU();
+ QuitterSurvolSupport();
+ QuitterSurvolSupportPDU();
+ CacherFantomesChariotTous();
+ }
+
+ void PoserSurSupportTorons()
+ {
+ if (_objetEnMain == null) return;
+ int ligneCible = _axeSupportCibleLigne; int colonneCible = _axeSupportCibleColonne;
+ NetworkIdentity toronNetId = _objetEnMain.GetComponent();
+ if (toronNetId != null) CmdPoserToronSurSupport(toronNetId.netId, ligneCible, colonneCible);
+ if (!isServer && SupportTorons.Instance != null) { bool place = SupportTorons.Instance.PoserToronSurAxeSpecifique(_objetEnMain.gameObject, ligneCible, colonneCible); if (!place) SupportTorons.Instance.PoserToron(_objetEnMain.gameObject); }
+ else if (toronNetId == null && SupportTorons.Instance != null) { bool place = SupportTorons.Instance.PoserToronSurAxeSpecifique(_objetEnMain.gameObject, ligneCible, colonneCible); if (!place) SupportTorons.Instance.PoserToron(_objetEnMain.gameObject); }
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ QuitterSurvolSupport();
+ HUDManager hud = GetComponent(); if (hud != null) hud.SetInfoEquipement("");
+ }
+
+ void PoserSurSupportPDU()
+ {
+ if (_objetEnMain == null) return;
+ int ligneCible = _emplacementSupportPDUCibleLigne; int colonneCible = _emplacementSupportPDUCibleColonne;
+ NetworkIdentity pduNetId = _objetEnMain.GetComponent();
+ if (pduNetId != null) CmdPoserPDUSurSupport(pduNetId.netId, ligneCible, colonneCible);
+ if (!isServer && SupportPDU.Instance != null) { bool place = SupportPDU.Instance.PoserPDUSurEmplacementSpecifique(_objetEnMain.gameObject, ligneCible, colonneCible); if (!place) SupportPDU.Instance.PoserPDU(_objetEnMain.gameObject); }
+ else if (pduNetId == null && SupportPDU.Instance != null) { bool place = SupportPDU.Instance.PoserPDUSurEmplacementSpecifique(_objetEnMain.gameObject, ligneCible, colonneCible); if (!place) SupportPDU.Instance.PoserPDU(_objetEnMain.gameObject); }
+ _objetEnMain.Poser(); _objetEnMain = null; _rbEnMain = null;
+ QuitterSurvolSupportPDU();
+ HUDManager hud = GetComponent(); if (hud != null) hud.SetInfoEquipement("");
+ }
+
+ void NotifierPoserReseau(bool avecGravite, Vector3 position, Quaternion rotation, bool estRacke = false)
+ {
+ if (_objetEnMain == null) return;
+ NetworkIdentity objNetId = _objetEnMain.GetComponent();
+ if (objNetId != null) CmdPoser(objNetId.netId, position, rotation, avecGravite, estRacke);
+ }
+
+ // ==================== HELPERS ====================
+
+ bool ViseBouton(Vector3 positionMonde, float seuilPixels = 60f)
+ {
+ if (_camera == null) return false;
+ Vector3 screenPos = _camera.WorldToScreenPoint(positionMonde);
+ if (screenPos.z <= 0f) return false;
+ return Vector2.Distance(new Vector2(Screen.width / 2f, Screen.height / 2f), new Vector2(screenPos.x, screenPos.y)) < seuilPixels;
+ }
+
+ bool PorteAvantOuverte(RackSlot slot)
+ {
+ if (slot == null) return true;
+ Transform baie = slot.transform.parent; if (baie == null) return true;
+ foreach (Transform enfant in baie)
+ if (enfant.name == "Pivot_PorteAvant") { PorteBaie porte = enfant.GetComponent(); if (porte != null) return porte.estOuverte; }
+ return true;
+ }
+
+ RackSlot[] ObtenirSlotsParent(RackSlot slot)
+ {
+ if (slot == null) return null;
+ BaieRack baieRack = slot.GetComponentInParent(); if (baieRack != null && baieRack.slots != null) return baieRack.slots;
+ BaieProcedurale baieProc = slot.GetComponentInParent(); if (baieProc != null && baieProc.slots != null) return baieProc.slots;
+ Transform parent = slot.transform.parent; if (parent != null) return parent.GetComponentsInChildren();
+ return null;
+ }
+
+ private void IgnorerCollisionsAvecStructure(GameObject objet, Transform parent, bool ignorer)
+ {
+ Collider objCollider = objet.GetComponent(); if (objCollider == null) return;
+ _collisionsIgnorees.Clear();
+ for (int i = 0; i < parent.childCount; i++)
+ {
+ Transform enfant = parent.GetChild(i); string nom = enfant.name;
+ if (nom.StartsWith("Planche_") || nom.StartsWith("Montant_") || nom.StartsWith("Etiquette_"))
+ { Collider sc = enfant.GetComponent(); if (sc != null) { Physics.IgnoreCollision(objCollider, sc, ignorer); if (ignorer) _collisionsIgnorees.Add(sc); } }
+ Interactable inter = enfant.GetComponent();
+ if (inter != null && enfant.gameObject != objet)
+ { Collider ec = enfant.GetComponent(); if (ec != null) { Physics.IgnoreCollision(objCollider, ec, ignorer); if (ignorer) _collisionsIgnorees.Add(ec); } }
+ }
+ }
+
+ private void RestaurerCollisionsIgnorees()
+ {
+ if (_objetEnMain == null || _collisionsIgnorees.Count == 0) return;
+ Collider objCollider = _objetEnMain.GetComponent();
+ if (objCollider == null) { _collisionsIgnorees.Clear(); return; }
+ foreach (Collider col in _collisionsIgnorees) if (col != null) Physics.IgnoreCollision(objCollider, col, false);
+ _collisionsIgnorees.Clear();
+ }
+
+ // v6.12 : gestion pilotage chariot + transfert d'autorite multijoueur
+ void PrendreChariot(Chariot chariot)
+ {
+ if (chariot == null) return;
+ _chariotPilote = chariot;
+
+ // Demander l'autorite au serveur pour pouvoir modifier transform.position
+ // via NetworkTransform ClientToServer. Sans ca, Mirror ignore les changements
+ // cote client et les autres joueurs ne voient pas le chariot bouger.
+ NetworkIdentity ni = chariot.GetComponent();
+ if (ni != null) CmdDemanderAutoriteChariot(ni.netId);
+
+ chariot.TogglePilotage(transform);
+ }
+
+ void LacherChariot()
+ {
+ if (_chariotPilote == null) return;
+
+ NetworkIdentity ni = _chariotPilote.GetComponent();
+
+ _chariotPilote.CacherFantomes();
+ _chariotPilote.TogglePilotage(transform);
+
+ // Relacher l'autorite APRES TogglePilotage pour que la position finale
+ // soit bien envoyee au serveur avant que le client perde son droit d'ecrire.
+ if (ni != null) CmdRelacherAutoriteChariot(ni.netId);
+
+ _chariotPilote = null;
+ }
+
+ [Command]
+ void CmdDemanderAutoriteChariot(uint chariotNetId)
+ {
+ if (!NetworkServer.spawned.ContainsKey(chariotNetId)) return;
+ NetworkIdentity ni = NetworkServer.spawned[chariotNetId];
+ if (ni == null) return;
+
+ // Si un autre client a deja l'autorite, la retirer d'abord
+ if (ni.connectionToClient != null && ni.connectionToClient != connectionToClient)
+ ni.RemoveClientAuthority();
+
+ ni.AssignClientAuthority(connectionToClient);
+ }
+
+ [Command]
+ void CmdRelacherAutoriteChariot(uint chariotNetId)
+ {
+ if (!NetworkServer.spawned.ContainsKey(chariotNetId)) return;
+ NetworkIdentity ni = NetworkServer.spawned[chariotNetId];
+ if (ni == null) return;
+
+ // Ne retirer l'autorite que si c'est bien ce joueur qui l'a actuellement,
+ // pour eviter de voler l'autorite a quelqu'un d'autre par accident.
+ if (ni.connectionToClient == connectionToClient)
+ ni.RemoveClientAuthority();
+ }
+
+ // ===== v6.13 Bug 1 : reorganisation ZoneLivraison cote serveur =====
+ [Command]
+ void CmdReorganiserZoneLivraison(Vector3 posRetireeWorld)
+ {
+ if (ZoneLivraison.Instance != null)
+ ZoneLivraison.Instance.ReorganiserNiveauComplet(posRetireeWorld);
+ }
+
+ // ===== v6.13 Bug 2 : sync du parenting equipement <-> chariot =====
+ [Command]
+ void CmdParenterAuChariot(uint equipNetId, uint chariotNetId)
+ {
+ if (!NetworkServer.spawned.ContainsKey(equipNetId)) return;
+ if (!NetworkServer.spawned.ContainsKey(chariotNetId)) return;
+ RpcParenterAuChariot(equipNetId, chariotNetId);
+ }
+
+ [ClientRpc]
+ void RpcParenterAuChariot(uint equipNetId, uint chariotNetId)
+ {
+ if (!NetworkClient.spawned.TryGetValue(equipNetId, out NetworkIdentity eqNi)) return;
+ if (!NetworkClient.spawned.TryGetValue(chariotNetId, out NetworkIdentity chNi)) return;
+ if (eqNi == null || chNi == null) return;
+
+ // worldPositionStays=true : conserver la position monde actuelle. L'equipement
+ // est deja a la bonne position (via NetworkTransform), on veut juste l'attacher
+ // au chariot pour qu'il suive les futurs mouvements.
+ eqNi.transform.SetParent(chNi.transform, true);
+ }
+
+ [Command]
+ void CmdDetacherDuChariot(uint equipNetId)
+ {
+ if (!NetworkServer.spawned.ContainsKey(equipNetId)) return;
+ RpcDetacherDuChariot(equipNetId);
+ }
+
+ [ClientRpc]
+ void RpcDetacherDuChariot(uint equipNetId)
+ {
+ if (!NetworkClient.spawned.TryGetValue(equipNetId, out NetworkIdentity eqNi)) return;
+ if (eqNi == null) return;
+ eqNi.transform.SetParent(null, true);
+ }
+
+ EmplacementBaie TrouverEmplacementDeBaie(GameObject baie)
+ {
+ if (_tousEmplacements == null) _tousEmplacements = FindObjectsOfType();
+ foreach (EmplacementBaie empl in _tousEmplacements) if (empl.estOccupe && empl.baieInstallee == baie) return empl;
+ return null;
+ }
+
+ RackSlot TrouverSlotDeEquipement(Interactable equipement)
+ {
+ if (equipement == null) return null;
+ foreach (BaieRack baie in FindObjectsOfType()) { if (baie.slots == null) continue; foreach (RackSlot s in baie.slots) if (s != null && s.equipementInstalle == equipement) return s; }
+ foreach (BaieProcedurale baie in FindObjectsOfType()) { if (baie.slots == null) continue; foreach (RackSlot s in baie.slots) if (s != null && s.equipementInstalle == equipement) return s; }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/Patchs/fix_portage/README_FIX_PORTAGE.md b/Patchs/fix_portage/README_FIX_PORTAGE.md
new file mode 100644
index 0000000..3c81d92
--- /dev/null
+++ b/Patchs/fix_portage/README_FIX_PORTAGE.md
@@ -0,0 +1,47 @@
+# Fix : Portage d'équipements invisible en multi
+
+## Le problème
+
+Avec `NetworkTransform Client-to-Server` sur les prefabs d'équipements, les autres joueurs ne voyaient plus les équipements portés suivre le joueur porteur. L'équipement restait figé à l'endroit où il avait été ramassé.
+
+## La cause
+
+`NetworkTransform Client-to-Server` exige que le client qui écrit les positions ait l'**authority** sur l'objet. Sans authority, Mirror **refuse** les mises à jour du client porteur et ne les propage pas aux autres clients.
+
+`CmdRamasser` ne faisait pas ce transfert d'authority, contrairement au chariot qui l'avait déjà.
+
+## Le fix
+
+Un seul fichier modifié : **`PlayerInteraction.cs`**
+
+Deux changements minimaux (10 lignes total) :
+
+1. **`CmdRamasser`** : ajoute `AssignClientAuthority(connectionToClient)` pour que le client porteur puisse écrire la position pendant qu'il transporte
+2. **`CmdPoser`** : ajoute `RemoveClientAuthority()` pour que le serveur redevienne autoritaire une fois l'objet posé
+
+## Action à faire
+
+Juste remplacer `Assets/Scripts/PlayerInteraction.cs` par le fichier fourni.
+
+## Test à faire
+
+### Test 1 — Portage local (toi)
+1. Prends un serveur 1U → il doit te suivre comme avant (rien ne change pour toi)
+
+### Test 2 — Portage vu par un autre joueur
+1. Host + client
+2. Le client prend un serveur 1U
+3. **Attendu** : le host voit le serveur suivre le client partout
+
+### Test 3 — Portage puis pose
+1. Client prend un serveur, se déplace, le pose dans une baie
+2. **Attendu** : le host voit le serveur bouger en temps réel, puis snapper dans la baie
+
+### Test 4 — Non-régression : Déco/Reco après pose
+1. Client pose un serveur dans une baie
+2. Déco/reco
+3. **Attendu** : le serveur est toujours dans la baie (comme avant le fix)
+
+Si tout marche, on peut attaquer les 2 bugs restants :
+- Câbles alim pendants après reco
+- Serveurs éteints après reco