From 8830602a8e691a0cb9623a49c015f9303d03537b Mon Sep 17 00:00:00 2001 From: Stephane MAURO Date: Sat, 18 Apr 2026 11:00:36 +0000 Subject: [PATCH] fix_portage --- .../DatacenterSim_Data/boot.config | 2 +- .../DatacenterSim_Data/sharedassets0.assets | Bin 3059764 -> 3059764 bytes Patchs/fix_portage/PlayerInteraction.cs | 1438 +++++++++++++++++ Patchs/fix_portage/README_FIX_PORTAGE.md | 47 + 4 files changed, 1486 insertions(+), 1 deletion(-) create mode 100644 Patchs/fix_portage/PlayerInteraction.cs create mode 100644 Patchs/fix_portage/README_FIX_PORTAGE.md 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 dc59ac86457948c060514ea1987889033de2fe82..116992e66acb93f3c5d8a70cd48a511692ef1d07 100644 GIT binary patch delta 4168 zcmZ9PeNfcL8OMKnhk7PxAqWSe1QjB>x?-CbtZ!JYV#ivv)X)eGXe^|F;?%ZgqCE*( z4mE|Da zVnAMId;ToP9*hg*el>q^2CVVWD&vvC8Ngx!m%iHVdk3Q-ke@v|GaU~TSo{$^0IC6D-STy~>94`A)zx7!sik%ug}b6M6!wU>=|4 z_?rb(GVf$w4Bz3&#RB z(6jJv=9SE|;47gzljk8`&pf@r$>X%cXIXpL;AeyBwuni8^ zjo18NrU3ICu&1C8nBQc+8tiH4bdcjr6<73_k=4clr~_|EAOKAkSh8i(%-?9sH@;oK|hB6H7{2mGh-|A=`` zHy#iEZ|27y;vX{)qhOxEwL9Uyd}kcIm5fJhJNq;-H>9z74Tj>N$Dw#=2s9KL1|>j=&=b&bCq!}`m5 zv7c@~B_r)C{pHAB2d$21qm8k0jQEsJ#7eK&L8Hr+SA0VUs_?ayatmdIJ!p`; zE<`nnIC(^TV80nB*9lQ(Pl=aj9~I?x_$YbRB~IJ7m3-DEw%aqu%Do}ta~sp1?-u9j zm*bFsmA!JD+#e#|byhZYl3Xd;?GuyaGlKHHGL%B&Nv zj}Uuw;YfSZG}$b~Hv9I^;R~d_GC9#aadivLNz~()%xT5=8f6#A$)c5N3*>lFOlJzDm%0k%lT@Sg zBp1qr81v-OvF5E;kKeM3(0VuBn=B(~^Ab5V(cHkb)x3b{@$qhZzme`Qk@Km23DRd) zsZhJPSf;uK*@yoqPYUs|a|ky4Ne*?3X8Z7RNiOlev)F|%%fY>=&96MRzeFAr;yrsx zsXP=SI_Y*9n!49M;g=OcG}4^4@_X_xkfCxWr}qrS0;#5U)EMI=fcB?WVM`X7l4-lOx??XK*^SuukUoGq0w4 znV-=4I_Vu~UZpfLPiH;e(3fTx%P5Mjm!UD{4O|<3xP4&UM7d?kOG!~E868le;u00r z%P}+oLE;RZ$C;_d z3ZqSHR1RIAqvD;P1Lmq6yKaW^xTuX_UY`Za>gj+JexVxr(E7{LLo4R0bQ&9*uc+{aV%G2yM<#$7#Y!<)NBN{CVLws*+aEQ}LqJuAir-3S7Xs zThw?vAych#X~!_Tt8a{Xoam02N0A?CT47A~Tg=)f_H&?1MkyzdBQa3O1dP3$OPd%=s z+X3b6Xa14U=uKKwrN#&!o$=$iHR38w{NZ0rnD2DU8hlOHG58#$B7JsofrjD+DUg+J z{T|2mIECwDJ0YMF#A(Xa)y%uG4!b~W0%{n|LK#}Fk37){4H_`+XWjv{<3;;^KrM7R z3%AX2${>{{u;SWkJPs&p5B4qdFSzp0ZB^g$Hm}daHh;8TCE@o>v&{+fc$@9eTGgd9 r+~1*aXLU{~#W$!ByJMGX4bgYUdUYHQ*?ZLFzD}=i`OxdeL(2bux@?m< delta 4230 zcmZ9P4^Y&{6~}*jcbcOC3PCs!FsEWf7gueg#){^wF+#>VU@%?;8>v=NP%kZ+l16(H zw1A@gIrV`+#0Uxg3y8#@N$mi}M3UI3X>71T#lN-FWQtZtJ5#59d;7K?xtZ_X?>_JC z+xOn??{|CceIK%RRBM~Xcfm5+*Jc^rX3h9}Ood?>(WqC0@C{#KspI^g6MU<`jb+ik z(r-Irr2P%D=gcKLvKU|XcbV}z!Upna$WR^`=;8ET_-?OPdz6qzt6O;-M~~sCQsSPM z14bg>OuU3u_N`_lxc!16csBOgiGF6~_WTQ~! z#NAns5_$@G1@Ttmx`0aJp2f=FgTA9tReo+Yj6UD`e>%L;KUd~w&=iZ>PQ%iOQ@+Ly z;?2Z$|8^38<`oqu31cLpfOrmZ?O#p&f#SZ1{_vQzC67vS;@Dk!F5mQihu6p+m|>ls zk+{yVo_Gau7eMJKdV)j5Q@jHwc#pV!gW{bSa}275 zcsFq!r2G1LE?LF%5NrxT{*_psV^T@owVsK1oJ> zqvL$fznA!i8h;$Ge`0y7UnE11G8j%|(@~dnAmTDn>3^Ac+OC03=_B4uTsQX$@toQ~ zf7}OG$VMg4RwJ74C6ZS9^!W~nHi`X#Lw0%e;wy0@rt(;Peb2lQGb&>4An;l z*=ko&;r-&m`wV44Sy49BAk<*gV<Iu)KF9`Dh?Ho8islj4@RRH^%v^zi=0)?#R_KZ4!O+}GSnimve=cP-eddtYcc}65 zTAy{~b!`Cmn2~Y(AB^pi`5s}Po9_?fTNo=bXFB~q=8?s?GHg3^C1Q{@ukfK(w#6*{1>eos z4dpRw8Q*7tk}46*yym}`bE}OVfQPI2G`1hIBY8x)Y}xApExQhjbNREZ3+i+Ev=Dtl zmR_B3Lt0M2s60MCM*k*Bi#{jI>))EI^LT_63SQ%puwyNs1#Sv&zSy_#F_}BatV_&OXvF$ZJ!pe@AhYL7Z*eO-e(l>bgVD^bwSjamV z``DaW#9M6a3fwN{XV^ZotAv*`b_mip<0||omfKDD7M^H@byKn9W!w1mL97&B-Oj&e zSD|taA8mSe@E$AJvv8k}jpd=Hr-s`t>>Km-S{c7lt=_zP{-KraR$qqg;bX0Ak6N0g zjXXOg^Z;ctE6*tL(1!6b!HUx&*UWZ}sF2IER z-n}b@-As5%Jk9V{Ket0nGq*1k8!YUE8M#P+1@c!3r$4W{RhZYoERhuiiBULf9(E%{ z|4))#I|JjEizzB)$m z)fT905n<*suQ#4UV~>1H2%UN<|gSh&pgO~S@P z^jTh}R{|HSa1m4Flg*8HV#v9G`|Xg)iUmF%yDLTTP<@Dyqx8{AUSAD@FV^xX%Z(rq zx%gx$klV_HO(i(80``>%F4GHx>m{NGQ~zVBaL5wQZx#_YYAz&{3n#A2>~hheYROuF zwG62c1uRDYA(C_H!@ayNg+sr`%%gpnwEkBi`B6AuDaOm2$`0;a+yik~mb~49W9|ji zrjPM*NDEBIY@rOxgmvg0qyGp>V@JTCI@nVs#A=@V$JAEQLM4xZufcN1c z{x(1|a(r4`lu6 +/// 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