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; } 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]; 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.DemanderAgrandir(); 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; } }