diff --git a/Dedicated_Server_Linux/DatacenterSim.x86_64 b/Dedicated_Server_Linux/DatacenterSim.x86_64 deleted file mode 100755 index 9021e73..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim.x86_64 and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/Plugins/lib_burst_generated.so b/Dedicated_Server_Linux/DatacenterSim_Data/Plugins/lib_burst_generated.so deleted file mode 100644 index 9c188d5..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/Plugins/lib_burst_generated.so and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/Resources/UnityPlayer.png b/Dedicated_Server_Linux/DatacenterSim_Data/Resources/UnityPlayer.png deleted file mode 100644 index 43897cf..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/Resources/UnityPlayer.png and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/Resources/unity default resources b/Dedicated_Server_Linux/DatacenterSim_Data/Resources/unity default resources deleted file mode 100644 index 7cf4261..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/Resources/unity default resources and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/Resources/unity_builtin_extra b/Dedicated_Server_Linux/DatacenterSim_Data/Resources/unity_builtin_extra deleted file mode 100644 index 5b39231..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/Resources/unity_builtin_extra and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/RuntimeInitializeOnLoads.json b/Dedicated_Server_Linux/DatacenterSim_Data/RuntimeInitializeOnLoads.json deleted file mode 100644 index e648b56..0000000 --- a/Dedicated_Server_Linux/DatacenterSim_Data/RuntimeInitializeOnLoads.json +++ /dev/null @@ -1 +0,0 @@ -{"root":[{"assemblyName":"Assembly-CSharp","nameSpace":"Mirror","className":"GeneratedNetworkCode","methodName":"InitReadWriters","loadTypes":1,"isUnityClass":false},{"assemblyName":"Cinemachine","nameSpace":"Cinemachine","className":"CinemachineStoryboard","methodName":"InitializeModule","loadTypes":0,"isUnityClass":false},{"assemblyName":"Cinemachine","nameSpace":"Cinemachine","className":"CinemachineCore","methodName":"InitializeModule","loadTypes":0,"isUnityClass":false},{"assemblyName":"Cinemachine","nameSpace":"Cinemachine","className":"UpdateTracker","methodName":"InitializeModule","loadTypes":0,"isUnityClass":false},{"assemblyName":"Cinemachine","nameSpace":"Cinemachine","className":"CinemachineImpulseManager","methodName":"InitializeModule","loadTypes":0,"isUnityClass":false},{"assemblyName":"Cinemachine","nameSpace":"Cinemachine.PostFX","className":"CinemachineVolumeSettings","methodName":"InitializeModule","loadTypes":0,"isUnityClass":false},{"assemblyName":"glTFast","nameSpace":"","className":"$BurstDirectCallInitializer","methodName":"Initialize","loadTypes":2,"isUnityClass":false},{"assemblyName":"Mirror.Authenticators","nameSpace":"Mirror.Authenticators","className":"UniqueNameAuthenticator","methodName":"ResetStatics","loadTypes":0,"isUnityClass":false},{"assemblyName":"Mirror.Authenticators","nameSpace":"Mirror","className":"GeneratedNetworkCode","methodName":"InitReadWriters","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror.Components","nameSpace":"Mirror","className":"GeneratedNetworkCode","methodName":"InitReadWriters","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"NetworkClient","methodName":"Shutdown","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"NetworkDiagnostics","methodName":"ResetStatics","loadTypes":0,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"NetworkIdentity","methodName":"ResetStatics","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"NetworkLoop","methodName":"ResetStatics","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"NetworkLoop","methodName":"RuntimeInitializeOnLoad","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"NetworkManager","methodName":"ResetStatics","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"NetworkServer","methodName":"Shutdown","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"NetworkTime","methodName":"ResetStatics","loadTypes":0,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"ThreadLog","methodName":"Initialize","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror","nameSpace":"Mirror","className":"GeneratedNetworkCode","methodName":"InitReadWriters","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror.Examples","nameSpace":"Mirror.Examples.MultipleMatch","className":"CanvasController","methodName":"ResetStatics","loadTypes":1,"isUnityClass":false},{"assemblyName":"Mirror.Examples","nameSpace":"Mirror","className":"GeneratedNetworkCode","methodName":"InitReadWriters","loadTypes":1,"isUnityClass":false},{"assemblyName":"Unity.Burst","nameSpace":"","className":"$BurstDirectCallInitializer","methodName":"Initialize","loadTypes":2,"isUnityClass":true},{"assemblyName":"Unity.Collections","nameSpace":"","className":"$BurstDirectCallInitializer","methodName":"Initialize","loadTypes":2,"isUnityClass":true},{"assemblyName":"Unity.InputSystem","nameSpace":"UnityEngine.InputSystem","className":"InputSystem","methodName":"RunInitializeInPlayer","loadTypes":4,"isUnityClass":true},{"assemblyName":"Unity.InputSystem","nameSpace":"UnityEngine.InputSystem","className":"InputSystem","methodName":"RunInitialUpdate","loadTypes":1,"isUnityClass":true},{"assemblyName":"Unity.InputSystem","nameSpace":"UnityEngine.InputSystem.UI","className":"InputSystemUIInputModule","methodName":"ResetDefaultActions","loadTypes":4,"isUnityClass":true},{"assemblyName":"Unity.RenderPipelines.Core.Runtime","nameSpace":"UnityEngine.Experimental.Rendering","className":"XRSystem","methodName":"XRSystemInit","loadTypes":3,"isUnityClass":true},{"assemblyName":"Unity.RenderPipelines.Core.Runtime","nameSpace":"UnityEngine.Rendering","className":"DebugUpdater","methodName":"RuntimeInit","loadTypes":0,"isUnityClass":true},{"assemblyName":"Unity.VisualScripting.Core","nameSpace":"Unity.VisualScripting","className":"RuntimeVSUsageUtility","methodName":"RuntimeInitializeOnLoadBeforeSceneLoad","loadTypes":1,"isUnityClass":true}]} diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/ScriptingAssemblies.json b/Dedicated_Server_Linux/DatacenterSim_Data/ScriptingAssemblies.json deleted file mode 100644 index c32c357..0000000 --- a/Dedicated_Server_Linux/DatacenterSim_Data/ScriptingAssemblies.json +++ /dev/null @@ -1 +0,0 @@ -{"names":["UnityEngine.dll","UnityEngine.AIModule.dll","UnityEngine.AccessibilityModule.dll","UnityEngine.AndroidJNIModule.dll","UnityEngine.AnimationModule.dll","UnityEngine.AssetBundleModule.dll","UnityEngine.AudioModule.dll","UnityEngine.ClothModule.dll","UnityEngine.ClusterInputModule.dll","UnityEngine.ClusterRendererModule.dll","UnityEngine.ContentLoadModule.dll","UnityEngine.CoreModule.dll","UnityEngine.CrashReportingModule.dll","UnityEngine.DSPGraphModule.dll","UnityEngine.DirectorModule.dll","UnityEngine.GIModule.dll","UnityEngine.GameCenterModule.dll","UnityEngine.GridModule.dll","UnityEngine.HotReloadModule.dll","UnityEngine.IMGUIModule.dll","UnityEngine.ImageConversionModule.dll","UnityEngine.InputModule.dll","UnityEngine.InputLegacyModule.dll","UnityEngine.JSONSerializeModule.dll","UnityEngine.LocalizationModule.dll","UnityEngine.ParticleSystemModule.dll","UnityEngine.PerformanceReportingModule.dll","UnityEngine.PhysicsModule.dll","UnityEngine.Physics2DModule.dll","UnityEngine.ProfilerModule.dll","UnityEngine.PropertiesModule.dll","UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule.dll","UnityEngine.ScreenCaptureModule.dll","UnityEngine.SharedInternalsModule.dll","UnityEngine.SpriteMaskModule.dll","UnityEngine.SpriteShapeModule.dll","UnityEngine.StreamingModule.dll","UnityEngine.SubstanceModule.dll","UnityEngine.SubsystemsModule.dll","UnityEngine.TLSModule.dll","UnityEngine.TerrainModule.dll","UnityEngine.TerrainPhysicsModule.dll","UnityEngine.TextCoreFontEngineModule.dll","UnityEngine.TextCoreTextEngineModule.dll","UnityEngine.TextRenderingModule.dll","UnityEngine.TilemapModule.dll","UnityEngine.UIModule.dll","UnityEngine.UIElementsModule.dll","UnityEngine.UmbraModule.dll","UnityEngine.UnityAnalyticsModule.dll","UnityEngine.UnityAnalyticsCommonModule.dll","UnityEngine.UnityConnectModule.dll","UnityEngine.UnityCurlModule.dll","UnityEngine.UnityTestProtocolModule.dll","UnityEngine.UnityWebRequestModule.dll","UnityEngine.UnityWebRequestAssetBundleModule.dll","UnityEngine.UnityWebRequestAudioModule.dll","UnityEngine.UnityWebRequestTextureModule.dll","UnityEngine.UnityWebRequestWWWModule.dll","UnityEngine.VFXModule.dll","UnityEngine.VRModule.dll","UnityEngine.VehiclesModule.dll","UnityEngine.VideoModule.dll","UnityEngine.VirtualTexturingModule.dll","UnityEngine.WindModule.dll","UnityEngine.XRModule.dll","Assembly-CSharp.dll","glTFast.dots.dll","Unity.RenderPipelines.Core.Runtime.dll","Unity.ProBuilder.Stl.dll","Unity.ProBuilder.Csg.dll","Unity.RenderPipelines.Universal.Config.Runtime.dll","Unity.VisualScripting.Flow.dll","Telepathy.dll","glTFast.Newtonsoft.dll","Unity.RenderPipelines.Core.ShaderLibrary.dll","Mirror.Examples.dll","Unity.Collections.dll","Unity.ProBuilder.Poly2Tri.dll","glTFast.Documentation.Examples.dll","Unity.TextMeshPro.dll","kcp2k.dll","Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary.dll","Unity.RenderPipelines.Universal.Runtime.dll","Unity.Burst.dll","Unity.ProBuilder.dll","Mirror.dll","Unity.VisualScripting.Core.dll","Mirror.Transports.dll","UnityEngine.UI.dll","Mirror.Components.dll","glTFast.Export.dll","Unity.RenderPipelines.Universal.2D.Internal.dll","Unity.RenderPipeline.Universal.ShaderLibrary.dll","Unity.Timeline.dll","Unity.InputSystem.dll","SimpleWebTransport.dll","Unity.InputSystem.ForUI.dll","Mirror.Authenticators.dll","glTFast.dll","Unity.ProBuilder.KdTree.dll","Unity.Mathematics.dll","Unity.RenderPipelines.Universal.Shaders.dll","Unity.VisualScripting.State.dll","Cinemachine.dll","Unity.Collections.LowLevel.ILSupport.dll","Mirror.BouncyCastle.Cryptography.dll","Unity.VisualScripting.Antlr3.Runtime.dll","Unity.Burst.Unsafe.dll","Newtonsoft.Json.dll"],"types":[2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16]} \ No newline at end of file diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/app.info b/Dedicated_Server_Linux/DatacenterSim_Data/app.info deleted file mode 100644 index 7a89685..0000000 --- a/Dedicated_Server_Linux/DatacenterSim_Data/app.info +++ /dev/null @@ -1,2 +0,0 @@ -MAURO Stéphane -DatacenterSim_Alpha \ No newline at end of file diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/boot.config b/Dedicated_Server_Linux/DatacenterSim_Data/boot.config deleted file mode 100644 index a6da18b..0000000 --- a/Dedicated_Server_Linux/DatacenterSim_Data/boot.config +++ /dev/null @@ -1,6 +0,0 @@ -gfx-enable-gfx-jobs=1 -gfx-enable-native-gfx-jobs=1 -wait-for-native-debugger=0 -hdr-display-enabled=0 -gc-max-time-slice=3 -build-guid=e778e91ac99847908610029fa6e532a7 diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/globalgamemanagers b/Dedicated_Server_Linux/DatacenterSim_Data/globalgamemanagers deleted file mode 100644 index 235e219..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/globalgamemanagers and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/globalgamemanagers.assets b/Dedicated_Server_Linux/DatacenterSim_Data/globalgamemanagers.assets deleted file mode 100644 index 89ea865..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/globalgamemanagers.assets and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/il2cpp_data/Metadata/global-metadata.dat b/Dedicated_Server_Linux/DatacenterSim_Data/il2cpp_data/Metadata/global-metadata.dat deleted file mode 100644 index f4d6d3e..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/il2cpp_data/Metadata/global-metadata.dat and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/il2cpp_data/Resources/mscorlib.dll-resources.dat b/Dedicated_Server_Linux/DatacenterSim_Data/il2cpp_data/Resources/mscorlib.dll-resources.dat deleted file mode 100644 index 6d144fc..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/il2cpp_data/Resources/mscorlib.dll-resources.dat and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/level0 b/Dedicated_Server_Linux/DatacenterSim_Data/level0 deleted file mode 100644 index 99dc680..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/level0 and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/resources.assets b/Dedicated_Server_Linux/DatacenterSim_Data/resources.assets deleted file mode 100644 index 4528481..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/resources.assets and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.assets b/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.assets deleted file mode 100644 index 116992e..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.assets and /dev/null differ diff --git a/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.resource b/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.resource deleted file mode 100644 index 465cf10..0000000 Binary files a/Dedicated_Server_Linux/DatacenterSim_Data/sharedassets0.resource and /dev/null differ diff --git a/Dedicated_Server_Linux/GameAssembly.so b/Dedicated_Server_Linux/GameAssembly.so deleted file mode 100644 index f386cda..0000000 Binary files a/Dedicated_Server_Linux/GameAssembly.so and /dev/null differ diff --git a/Dedicated_Server_Linux/UnityPlayer.so b/Dedicated_Server_Linux/UnityPlayer.so deleted file mode 100644 index 50b14d6..0000000 Binary files a/Dedicated_Server_Linux/UnityPlayer.so and /dev/null differ diff --git a/Patchs/pack_multi/README_PACK_v7.1.md b/Patchs/pack_multi/README_PACK_v7.1.md new file mode 100644 index 0000000..73ef7f5 --- /dev/null +++ b/Patchs/pack_multi/README_PACK_v7.1.md @@ -0,0 +1,179 @@ +# DatacenterSim v7.1 — Pack multi complet + +Pack de 3 patches qui corrigent les 3 bugs identifiés en multijoueur après reco : +1. **Portage d'équipements** invisible pour les autres joueurs +2. **Câbles alim + RJ45** pendants après reco +3. **Serveurs éteints** après reco + +--- + +## Ce qu'il y a dans le ZIP + +### Scripts_Modifies/ (remplacer dans Assets/Scripts/) + +- **`PlayerInteraction.cs`** — 3 changements + - Revert de mon patch authority (plus de AssignClientAuthority/RemoveClientAuthority sur équipements) + - `CmdUpdateTransportPosition` bouge maintenant l'objet côté serveur + - Helper `SetNetworkTransformActif()` + désactivation NT local pendant portage + réactivation à la pose + - `CmdAppuyerBoutonPower` appelle `EtatEquipementNetworkSync.ServerSyncEtat` pour le rejoin + +- **`CablageReseau.cs`** — 4 ajouts + - `CmdCreerCableRJ45` / `CmdCreerCableAlim` enregistrent dans le gestionnaire + - `CmdSupprimerCableRJ45` / `CmdSupprimerCableAlim` désenregistrent + +- **`BaieNetworkSetup.cs`** — 1 ajout + - `OnStartClient` notifie le gestionnaire que la baie est prête + +- **`EtatEquipement.cs`** — 1 ajout + - `VerifierAlimentation` appelle `EtatEquipementNetworkSync.ServerSyncEtat` quand l'état change + +### Scripts_Nouveaux/ (AJOUTER dans Assets/Scripts/) + +- **`GestionnaireCablesReseau.cs`** — NetworkBehaviour singleton qui tient 2 SyncLists (câbles alim + RJ45) et rejoue les câbles au rejoin des clients + +- **`EtatEquipementNetworkSync.cs`** — Composant à ajouter sur les prefabs d'équipements rackmount pour synchroniser l'état bouton/sous-tension au rejoin + +--- + +## Configuration Unity requise + +### 1. Prefabs d'équipements : NetworkTransform en `Server To Client` + +**IMPORTANT** : tu avais configuré `Client To Server` pour mon dernier patch raté. **Remets-les en `Server To Client`** : + +Pour chaque prefab dans `NetworkManager.spawnPrefabs` (sauf Chariot et prefab joueur) : +- Ouvrir le prefab +- Le `NetworkTransformReliable` / `NetworkTransformUnreliable` : + - **Sync Direction : `Server To Client`** ⚠️ + - Sync Position : ✅ + - Sync Rotation : ✅ + - Sync Scale : ❌ + - Compress Rotation : ✅ + - Only Sync On Change : ✅ + - Interpolate Position : ✅ + - Interpolate Rotation : ✅ + - Send Interval Multiplier : 3 ou 5 + +Prefabs concernés : `Serveur_1U`, `Switch_24P`, `Parefeu`, `PDU_APC_*`, `Baie_Procedurale`, `Toron_Base`. + +**Ne PAS toucher** : le chariot (Client To Server) et le prefab joueur (Client To Server). + +### 2. Ajouter `GestionnaireCablesReseau` à la scène `Datacenter_01` + +1. Dans la scène, `Hierarchy` → clic droit → `Create Empty` +2. Renommer : `GestionnaireCables_Holder` +3. `Add Component` → `NetworkIdentity` (laisser toutes les checkboxes décochées) +4. `Add Component` → `GestionnaireCablesReseau` +5. **Sauver la scène** (Ctrl+S) + +### 3. Ajouter `EtatEquipementNetworkSync` sur les prefabs d'équipements rackmount + +Pour chaque prefab qui a un `BoutonPower` + `EtatEquipement` : +- Ouvrir le prefab +- `Add Component` → `EtatEquipementNetworkSync` +- Sauver + +Prefabs concernés : `Serveur_1U`, `Switch_24P`, `Parefeu`. +(Pas besoin sur PDU, Toron, Baie — ces prefabs n'ont pas de bouton power.) + +--- + +## Tests à faire + +### Test 1 — Non-régression solo +Lancer en éditeur Play. Tout doit fonctionner comme avant (pas de multi). + +### Test 2 — Portage visible en multi +1. Host + 1 client connecté +2. Client prend un serveur 1U depuis un carton → **tu dois voir le serveur suivre le client** +3. Client le pose dans une baie → **tu dois voir le snap** +4. Tu prends le même serveur pour le déracker → **le client doit te voir le porter** + +### Test 3 — Câbles alim au rejoin +1. Client pose baie + 2 serveurs + 2 PDUs + 2 câbles alim +2. Client allume les serveurs (bouton Power) +3. Client déco + reco +4. **Attendu** : câbles alim toujours connectés (non pendants), serveurs toujours allumés (ventilos tournent, LED verte) + +### Test 4 — Câbles RJ45 au rejoin +1. Client pose baie + 2 serveurs + 1 switch + torons + câbles RJ45 +2. Client déco + reco +3. **Attendu** : câbles RJ45 toujours branchés correctement + +### Test 5 — 2e joueur qui rejoint en cours +1. Client 1 a tout câblé et allumé +2. Client 2 se connecte +3. **Attendu** : client 2 voit TOUT l'état (câbles + serveurs allumés) + +--- + +## Logs à surveiller + +### Côté serveur, quand un câble est créé : +``` +[Serveur] Câble alim créé : PDU 10 prise 11 → equip 7 port 0 +[GestionnaireCablesReseau] Cable alim enregistre (total : 1) +``` + +### Côté client qui rejoint : +``` +[GestionnaireCablesReseau] Client rejoint : 2 cables alim, 3 cables RJ45 a recreer +[BaieNetworkSetup] Baie regeneree cote client : Baie_Procedurale(Clone) +[GestionnaireCablesReseau] Baie prete : Baie_Procedurale(Clone), retry cables en attente +[GestionnaireCablesReseau] Cable alim recree : PDU 10 prise #11 -> PSU0 +[GestionnaireCablesReseau] Tous les cables ont ete recrees +``` + +### Si timeout (normalement jamais) : +``` +[GestionnaireCablesReseau] Timeout : 1 cables alim + 0 cables RJ45 jamais recrees +``` +→ ça veut dire qu'une baie ou un équipement ne s'est pas spawné correctement côté client. + +--- + +## Architecture du mécanisme "baie prête" + +Le problème classique au rejoin : on tente de recréer un câble entre PDU X et serveur Y, mais X ou Y n'existent pas encore localement parce que leur parent (la baie) n'a pas fini de se régénérer. + +**Solution** : +1. Au `OnStartClient` du gestionnaire, on met tous les câbles à recréer en "liste d'attente" +2. On tente une première passe après 0.5s (laisse le temps aux premiers spawn) +3. Chaque `BaieNetworkSetup.OnStartClient` se termine par `NotifierBaiePrete()` qui déclenche un **retry** sur tous les câbles en attente +4. En fallback, un retry périodique toutes les 0.5s pendant 15s max +5. Timeout à 15s avec un warning dans les logs + +C'est robuste aux ordres de spawn aléatoires et évite tout callback hell. + +--- + +## Remarques techniques + +### Pourquoi retirer l'authority pour le portage ? + +`NetworkTransform Client-to-Server` exige l'authority. Le patch précédent transférait l'authority au ramassage, mais ça créait plein de race conditions (logs `EntityStateMessage without authority`, objets figés/dédoublés...). + +La nouvelle approche : le **serveur est TOUJOURS autoritaire**. Le client porteur envoie des `CmdUpdateTransportPosition` qui bougent l'objet côté serveur, puis `NetworkTransform Server-to-Client` propage aux autres clients. Beaucoup plus simple, plus robuste. + +### Pourquoi désactiver NT local pour le porteur ? + +Sans ça, le client porteur reçoit sa propre position serveur avec ~50ms de latence → son objet tremble. En désactivant le NT localement, son objet suit parfaitement ses mouvements (il est pilote par le Rigidbody local). + +Quand il pose, on réactive NT → il reçoit alors la position "figée" du serveur (qui a fait `rb.isKinematic=true` + position snap). + +### Pourquoi un gestionnaire séparé pour les câbles ? + +`CablageReseau` est sur le prefab joueur : chaque joueur a sa propre instance. Impossible d'y mettre une SyncList globale. On crée donc un singleton à part qui porte l'état global. + +### Pourquoi un composant séparé pour l'état power ? + +Pour éviter de transformer `EtatEquipement` en `NetworkBehaviour` (ce qui casserait les équipements instanciés localement, par ex. pour les tests en éditeur). Composant séparé = découplage propre. + +--- + +## Si ça ne marche pas + +1. **Serveur crash au lancement** → probablement `GestionnaireCablesReseau` sans `NetworkIdentity` dans la scène. Vérifier. +2. **Câbles toujours pendants au reco** → regarder les logs `[GestionnaireCablesReseau]`. Si "Timeout", c'est que les refs PDU/port sont toujours null. Ouvrir un ticket. +3. **Portage local trembant** → `SetNetworkTransformActif` pas appelé au ramassage. Vérifier logs console. +4. **Erreur `EntityStateMessage without authority`** → il reste un NT en Client-to-Server quelque part. Vérifier tous les prefabs. diff --git a/Patchs/pack_multi/Scripts_Modifies/BaieNetworkSetup.cs b/Patchs/pack_multi/Scripts_Modifies/BaieNetworkSetup.cs new file mode 100644 index 0000000..47316aa --- /dev/null +++ b/Patchs/pack_multi/Scripts_Modifies/BaieNetworkSetup.cs @@ -0,0 +1,61 @@ +using UnityEngine; +using Mirror; + +/// +/// BaieNetworkSetup.cs - v6.3 Multijoueur +/// +/// Ajouté automatiquement par BoutiqueReseau lors du spawn d'une baie. +/// Quand le client reçoit l'objet via NetworkServer.Spawn(), les materials +/// créés en runtime par BaieProcedurale.Generer() ne sont PAS synchronisés +/// (Mirror ne sync pas les materials). Ce script régénère la baie localement +/// sur chaque client après le spawn. +/// +/// SETUP : Ne pas ajouter manuellement — BoutiqueReseau l'ajoute automatiquement. +/// +public class BaieNetworkSetup : NetworkBehaviour +{ + public override void OnStartClient() + { + base.OnStartClient(); + + // Sur le serveur/host, la baie est déjà générée dans TraiterPlacementBaieServeur + // Sur les clients distants, il faut la régénérer pour avoir les materials + if (isServer) return; + + BaieProcedurale bp = GetComponent(); + if (bp != null) + { + bp.Generer(); + bp.InstallerPDUAutomatiquement(); + Debug.Log($"[BaieNetworkSetup] Baie régénérée côté client : {gameObject.name}"); + } + + BaieRack br = GetComponent() ?? GetComponentInChildren(); + if (br != null) br.GenererSlots(); + + // Appliquer les layers + int layerBaie = LayerMask.NameToLayer("Baie"); + int layerEquip = LayerMask.NameToLayer("Equipement"); + + if (layerBaie >= 0) SetLayerRecursif(gameObject, layerBaie); + + foreach (Transform t in GetComponentsInChildren(true)) + { + if (t.GetComponent() != null && layerEquip >= 0) + SetLayerRecursif(t.gameObject, layerEquip); + } + + // v7.1 : notifier le gestionnaire de cables que cette baie est prete. + // Le gestionnaire va retenter de recreer les cables en attente qui + // referencent cette baie / ses PDUs / ses equipements. + if (GestionnaireCablesReseau.Instance != null) + GestionnaireCablesReseau.Instance.NotifierBaiePrete(gameObject); + } + + private void SetLayerRecursif(GameObject obj, int layer) + { + obj.layer = layer; + foreach (Transform c in obj.transform) + SetLayerRecursif(c.gameObject, layer); + } +} \ No newline at end of file diff --git a/Patchs/pack_multi/Scripts_Modifies/CablageReseau.cs b/Patchs/pack_multi/Scripts_Modifies/CablageReseau.cs new file mode 100644 index 0000000..59b256f --- /dev/null +++ b/Patchs/pack_multi/Scripts_Modifies/CablageReseau.cs @@ -0,0 +1,427 @@ +using UnityEngine; +using System.Collections.Generic; +using Mirror; + +/// +/// CablageReseau.cs - v6.7 Multijoueur +/// +/// v6.7 : sync longueur toron +/// - CmdSyncLongueurToron : met à jour la SyncVar longueur sur le serveur +/// après consommation ou restitution de câble. Propage automatiquement +/// à tous les clients via le hook de ToronNetworkSync. +/// +/// v6.4 : sync câbles RJ45 + alimentation + interrupteur prise +/// +/// SETUP : Ajouter sur le prefab PlayerCapsule. +/// +public class CablageReseau : NetworkBehaviour +{ + // ════════════════════════════════════════════════════════════ + // CÂBLES RJ45 + // ════════════════════════════════════════════════════════════ + + [Command] + public void CmdCreerCableRJ45(uint equipSourceNetId, int portSourceIndex, + uint equipDestNetId, int portDestIndex, + Color couleur, float longueur) + { + Debug.Log($"[Serveur] Câble RJ45 créé : equip {equipSourceNetId} port {portSourceIndex} → equip {equipDestNetId} port {portDestIndex}"); + + // v7.1 : enregistrer le cable dans le gestionnaire pour le rejoin des clients + if (GestionnaireCablesReseau.Instance != null) + { + GestionnaireCablesReseau.Instance.EnregistrerCableRJ45( + equipSourceNetId, portSourceIndex, equipDestNetId, portDestIndex, couleur, longueur); + } + + RpcCreerCableRJ45(equipSourceNetId, portSourceIndex, equipDestNetId, portDestIndex, couleur, longueur); + } + + [ClientRpc] + void RpcCreerCableRJ45(uint equipSourceNetId, int portSourceIndex, + uint equipDestNetId, int portDestIndex, + Color couleur, float longueur) + { + if (isLocalPlayer) return; + + PortRJ45 portSource = TrouverPortRJ45(equipSourceNetId, portSourceIndex); + PortRJ45 portDest = TrouverPortRJ45(equipDestNetId, portDestIndex); + + if (portSource == null || portDest == null) + { + Debug.LogWarning($"[CablageReseau] Ports introuvables pour câble RJ45 distant"); + return; + } + + List chemin = ConstruireCheminAutoLocal( + portSource.GetPointConnexion(), portDest.GetPointConnexion()); + + string nom = "Cable_" + portSource.nomPort + "_vers_" + portDest.nomPort + "_distant"; + GameObject cableObj = new GameObject(nom); + CableRJ45 cable = cableObj.AddComponent(); + cable.Initialiser(portSource, portDest, chemin, couleur, longueur, null); + + portSource.Connecter(portDest, cable); + portDest.Connecter(portSource, cable); + + Debug.Log($"[CablageReseau] Câble RJ45 distant créé : {portSource.nomPort} → {portDest.nomPort}"); + } + + [Command] + public void CmdSupprimerCableRJ45(uint equipSourceNetId, int portSourceIndex, + uint equipDestNetId, int portDestIndex) + { + Debug.Log($"[Serveur] Câble RJ45 supprimé"); + + // v7.1 : desinscrire du gestionnaire + if (GestionnaireCablesReseau.Instance != null) + { + GestionnaireCablesReseau.Instance.OublierCableRJ45( + equipSourceNetId, portSourceIndex, equipDestNetId, portDestIndex); + } + + RpcSupprimerCableRJ45(equipSourceNetId, portSourceIndex, equipDestNetId, portDestIndex); + } + + [ClientRpc] + void RpcSupprimerCableRJ45(uint equipSourceNetId, int portSourceIndex, + uint equipDestNetId, int portDestIndex) + { + if (isLocalPlayer) return; + + PortRJ45 portSource = TrouverPortRJ45(equipSourceNetId, portSourceIndex); + PortRJ45 portDest = TrouverPortRJ45(equipDestNetId, portDestIndex); + + if (portSource != null && portSource.cable != null) + { + portSource.cable.SupprimerComplet(); + } + else if (portDest != null && portDest.cable != null) + { + portDest.cable.SupprimerComplet(); + } + + Debug.Log("[CablageReseau] Câble RJ45 distant supprimé"); + } + + // ════════════════════════════════════════════════════════════ + // SYNC LONGUEUR TORON (v6.7) + // ════════════════════════════════════════════════════════════ + + /// + /// v6.7 : Met à jour la SyncVar longueur du toron côté serveur. + /// Appelé par CableManager après consommation ou restitution de câble. + /// La SyncVar de ToronNetworkSync propage automatiquement la nouvelle + /// longueur à TOUS les clients via le hook. + /// + [Command] + public void CmdSyncLongueurToron(uint toronNetId, float longueurRestante, float longueurTotale) + { + if (!NetworkServer.spawned.ContainsKey(toronNetId)) return; + var obj = NetworkServer.spawned[toronNetId]; + if (obj == null) return; + + ToronNetworkSync sync = obj.GetComponent(); + if (sync != null) + { + sync.ServerSetLongueur(longueurTotale, longueurRestante); + Debug.Log($"[Serveur] Longueur toron sync : {obj.name} → {longueurRestante:F1}m / {longueurTotale:F1}m"); + } + } + + // ════════════════════════════════════════════════════════════ + // CÂBLES ALIMENTATION + // ════════════════════════════════════════════════════════════ + + [Command] + public void CmdCreerCableAlim(uint pduNetId, int indexPrise, + uint equipNetId, int indexPortAlim, + Color couleur) + { + Debug.Log($"[Serveur] Câble alim créé : PDU {pduNetId} prise {indexPrise} → equip {equipNetId} port {indexPortAlim}"); + + PriseC13 prise = TrouverPriseC13(pduNetId, indexPrise); + PortAlimentation port = TrouverPortAlimentation(equipNetId, indexPortAlim); + if (prise != null && port != null && !prise.estConnectee) + { + GameObject cableObj = new GameObject("CableAlim_serveur"); + CableAlimentation cable = cableObj.AddComponent(); + cable.couleurCable = couleur; + cable.Initialiser(prise, port); + prise.Connecter(cable, port); + port.Connecter(cable, prise); + } + + // v7.1 : enregistrer le cable dans le gestionnaire pour le rejoin des clients + if (GestionnaireCablesReseau.Instance != null) + { + GestionnaireCablesReseau.Instance.EnregistrerCableAlim( + pduNetId, indexPrise, equipNetId, indexPortAlim, couleur); + } + + RpcCreerCableAlim(pduNetId, indexPrise, equipNetId, indexPortAlim, couleur); + } + + [ClientRpc] + void RpcCreerCableAlim(uint pduNetId, int indexPrise, + uint equipNetId, int indexPortAlim, + Color couleur) + { + if (isLocalPlayer) return; + + PriseC13 prise = TrouverPriseC13(pduNetId, indexPrise); + PortAlimentation port = TrouverPortAlimentation(equipNetId, indexPortAlim); + + if (prise == null || port == null) + { + Debug.LogWarning("[CablageReseau] Prise/Port introuvables pour câble alim distant"); + return; + } + + GameObject cableObj = new GameObject("CableAlim_distant"); + CableAlimentation cable = cableObj.AddComponent(); + cable.couleurCable = couleur; + cable.Initialiser(prise, port); + + prise.Connecter(cable, port); + port.Connecter(cable, prise); + + Debug.Log($"[CablageReseau] Câble alim distant créé : PDU prise #{indexPrise} → {port.nomPort}"); + } + + [Command] + public void CmdSupprimerCableAlim(uint pduNetId, int indexPrise, + uint equipNetId, int indexPortAlim) + { + Debug.Log($"[Serveur] Câble alim supprimé"); + + // v7.1 : desinscrire du gestionnaire + if (GestionnaireCablesReseau.Instance != null) + { + GestionnaireCablesReseau.Instance.OublierCableAlim( + pduNetId, indexPrise, equipNetId, indexPortAlim); + } + + PriseC13 prise = TrouverPriseC13(pduNetId, indexPrise); + if (prise != null && prise.cableBranche != null) + prise.cableBranche.Supprimer(); + + RpcSupprimerCableAlim(pduNetId, indexPrise, equipNetId, indexPortAlim); + } + + [ClientRpc] + void RpcSupprimerCableAlim(uint pduNetId, int indexPrise, + uint equipNetId, int indexPortAlim) + { + if (isLocalPlayer) return; + + PriseC13 prise = TrouverPriseC13(pduNetId, indexPrise); + + if (prise != null && prise.cableBranche != null) + { + prise.cableBranche.Supprimer(); + } + else + { + PortAlimentation port = TrouverPortAlimentation(equipNetId, indexPortAlim); + if (port != null && port.cableBranche != null) + port.cableBranche.Supprimer(); + } + + Debug.Log("[CablageReseau] Câble alim distant supprimé"); + } + + // ════════════════════════════════════════════════════════════ + // INTERRUPTEUR PRISE + // ════════════════════════════════════════════════════════════ + + [Command] + public void CmdBasculerPrise(uint pduNetId, int indexPrise) + { + PriseC13 prise = TrouverPriseC13(pduNetId, indexPrise); + if (prise != null) + prise.BasculerInterrupteur(); + + RpcBasculerPrise(pduNetId, indexPrise); + } + + [ClientRpc] + void RpcBasculerPrise(uint pduNetId, int indexPrise) + { + if (isLocalPlayer) return; + + PriseC13 prise = TrouverPriseC13(pduNetId, indexPrise); + if (prise != null) + prise.BasculerInterrupteur(); + } + + // ════════════════════════════════════════════════════════════ + // HELPERS — Recherche de composants par netId + index + // ════════════════════════════════════════════════════════════ + + private PortRJ45 TrouverPortRJ45(uint equipNetId, int portIndex) + { + if (!NetworkClient.spawned.ContainsKey(equipNetId)) return null; + + GameObject equipGO = NetworkClient.spawned[equipNetId].gameObject; + PortRJ45[] ports = equipGO.GetComponentsInChildren(); + + foreach (var port in ports) + { + if (port.numeroPort == portIndex) + return port; + } + + if (portIndex >= 0 && portIndex < ports.Length) + return ports[portIndex]; + + return null; + } + + private PriseC13 TrouverPriseC13(uint pduNetId, int indexPrise) + { + if (!NetworkClient.spawned.ContainsKey(pduNetId)) return null; + + GameObject pduGO = NetworkClient.spawned[pduNetId].gameObject; + PDU pdu = pduGO.GetComponent(); + + if (pdu != null && indexPrise >= 0 && indexPrise < pdu.prises.Count) + return pdu.prises[indexPrise]; + + PriseC13[] prises = pduGO.GetComponentsInChildren(); + foreach (var p in prises) + if (p.indexPrise == indexPrise) return p; + + return null; + } + + private PortAlimentation TrouverPortAlimentation(uint equipNetId, int indexPort) + { + if (!NetworkClient.spawned.ContainsKey(equipNetId)) return null; + + GameObject equipGO = NetworkClient.spawned[equipNetId].gameObject; + PortAlimentation[] ports = equipGO.GetComponentsInChildren(); + + if (indexPort >= 0 && indexPort < ports.Length) + return ports[indexPort]; + + return null; + } + + private List ConstruireCheminAutoLocal(Vector3 posSource, Vector3 posDest) + { + List chemin = new List(); + PointAccroche[] tousPoints = FindObjectsOfType(); + + if (tousPoints.Length == 0) return chemin; + + PointAccroche ptDepart = null; + float bestDistDepart = 3f; + foreach (var p in tousPoints) + { + float d = Vector3.Distance(posSource, p.GetPosition()); + if (d < bestDistDepart) { bestDistDepart = d; ptDepart = p; } + } + + PointAccroche ptArrivee = null; + float bestDistArrivee = 3f; + foreach (var p in tousPoints) + { + float d = Vector3.Distance(posDest, p.GetPosition()); + if (d < bestDistArrivee) { bestDistArrivee = d; ptArrivee = p; } + } + + if (ptDepart == null || ptArrivee == null) return chemin; + if (ptDepart == ptArrivee) { chemin.Add(ptDepart); return chemin; } + + float distanceVoisinage = 1.5f; + Dictionary gScore = new Dictionary(); + Dictionary cameFrom = new Dictionary(); + HashSet closedSet = new HashSet(); + List openSet = new List(); + + gScore[ptDepart] = 0; + openSet.Add(ptDepart); + + int maxIter = 500; + int iter = 0; + + while (openSet.Count > 0 && iter < maxIter) + { + iter++; + PointAccroche current = null; + float bestF = float.MaxValue; + foreach (var n in openSet) + { + float g = gScore.ContainsKey(n) ? gScore[n] : float.MaxValue; + float f = g + Vector3.Distance(n.GetPosition(), ptArrivee.GetPosition()); + if (f < bestF) { bestF = f; current = n; } + } + if (current == null) break; + if (current == ptArrivee) + { + List result = new List { current }; + while (cameFrom.ContainsKey(current)) + { + current = cameFrom[current]; + result.Insert(0, current); + } + return result; + } + + openSet.Remove(current); + closedSet.Add(current); + + foreach (var autre in tousPoints) + { + if (autre == current || closedSet.Contains(autre)) continue; + if (Vector3.Distance(current.GetPosition(), autre.GetPosition()) > distanceVoisinage) continue; + + float tentG = gScore[current] + Vector3.Distance(current.GetPosition(), autre.GetPosition()); + if (!openSet.Contains(autre)) openSet.Add(autre); + else if (gScore.ContainsKey(autre) && tentG >= gScore[autre]) continue; + + cameFrom[autre] = current; + gScore[autre] = tentG; + } + } + + chemin.Add(ptDepart); + chemin.Add(ptArrivee); + return chemin; + } + + // ════════════════════════════════════════════════════════════ + // HELPERS PUBLICS + // ════════════════════════════════════════════════════════════ + + public static uint GetEquipNetId(PortRJ45 port) + { + if (port == null) return 0; + NetworkIdentity netId = port.GetComponentInParent(); + return netId != null ? netId.netId : 0; + } + + public static uint GetPDUNetId(PriseC13 prise) + { + if (prise == null || prise.pduParent == null) return 0; + NetworkIdentity netId = prise.pduParent.GetComponent(); + return netId != null ? netId.netId : 0; + } + + public static uint GetEquipNetIdFromPortAlim(PortAlimentation port) + { + if (port == null || port.equipement == null) return 0; + NetworkIdentity netId = port.equipement.GetComponent(); + return netId != null ? netId.netId : 0; + } + + public static int GetPortAlimIndex(PortAlimentation port) + { + if (port == null || port.equipement == null) return 0; + PortAlimentation[] ports = port.equipement.GetComponentsInChildren(); + for (int i = 0; i < ports.Length; i++) + if (ports[i] == port) return i; + return 0; + } +} \ No newline at end of file diff --git a/Patchs/pack_multi/Scripts_Modifies/EtatEquipement.cs b/Patchs/pack_multi/Scripts_Modifies/EtatEquipement.cs new file mode 100644 index 0000000..e9196bd --- /dev/null +++ b/Patchs/pack_multi/Scripts_Modifies/EtatEquipement.cs @@ -0,0 +1,306 @@ +using UnityEngine; +using System.Collections.Generic; + +/// +/// Gère l'état sous tension d'un équipement rackmount. +/// +/// NE GÉNÈRE PAS les ports alimentation → utiliser EquipementAlim pour ça. +/// NE GÉNÈRE PAS de ventilateur → utiliser un child "Ventilateur" existant +/// ou laisser vide (pas de ventilo visuel). +/// +/// Quand alimenté : +/// - LEDs d'activité s'allument (emission verte) sur la face avant +/// - Ventilateur tourne (si un child "Ventilateur" est assigné) +/// - Signale à AmbianceSonore qu'une machine est active +/// +/// Setup Unity : +/// 1. Ajouter EquipementAlim sur l'équipement → générer les ports alim +/// 2. Ajouter EtatEquipement sur l'équipement +/// 3. Les ports alim sont trouvés automatiquement (GetComponentsInChildren) +/// 4. Optionnel : créer un child "Ventilateur" et l'assigner +/// 5. Configurer le nombre de LEDs, consommation, etc. +/// +public class EtatEquipement : MonoBehaviour +{ + [Header("Configuration")] + [Tooltip("Mode alimentation redondante : l'équipement reste allumé si au moins 1 port est alimenté")] + public bool alimentationRedondante = false; + + [Tooltip("Consommation électrique en watts")] + public float consommationWatts = 350f; + + [Header("LEDs")] + [Tooltip("Nombre de LEDs d'activité sur la face avant")] + [Range(0, 8)] + public int nombreLEDs = 3; + + public Color couleurLEDAllumee = new Color(0f, 1f, 0f, 1f); // Vert + public Color couleurLEDEteinte = new Color(0.05f, 0.05f, 0.05f, 1f); // Quasi noir + public Color couleurLEDStandby = new Color(1f, 0.5f, 0f, 1f); // Orange + + [Tooltip("Intensité de l'émission des LEDs")] + public float intensiteLED = 3f; + + [Tooltip("Décalage vertical des LEDs depuis le centre")] + public float ledsDecalageY = 0f; + [Tooltip("Décalage horizontal de départ des LEDs")] + public float ledsDecalageX = 0f; + [Tooltip("Espacement entre les LEDs")] + public float ledsEspacement = 0.015f; + [Tooltip("Taille d'une LED")] + public Vector3 ledsTaille = new Vector3(0.006f, 0.006f, 0.003f); + + [Header("Ventilateur")] + [Tooltip("Assigner un child 'Ventilateur' existant. Si vide, pas d'animation ventilo.")] + public Transform ventilateur; + [Tooltip("Vitesse de rotation du ventilateur en degrés/seconde")] + public float vitesseVentilateur = 720f; + [Tooltip("Axe de rotation du ventilateur (local)")] + public Vector3 axeRotation = Vector3.forward; + + [Header("État (lecture seule)")] + public bool estSousTension = false; + public bool estEnStandby = false; + + [Header("Références (auto-remplies au Start)")] + public List ledsRenderers = new List(); + + // Privé + private bool _ledsGenerees = false; + + void Start() + { + // Génère les LEDs visuelles si configurées + if (nombreLEDs > 0 && !_ledsGenerees) + { + GenererLEDs(); + } + + // Cherche un ventilateur existant si pas assigné + if (ventilateur == null) + { + Transform ventiloChild = transform.Find("Ventilateur"); + if (ventiloChild != null) + ventilateur = ventiloChild; + // PAS de création automatique — on ne pollue plus le mesh + } + + // État initial : éteint + MettreAJourVisuel(); + } + + void Update() + { + // Animation ventilateur + if (estSousTension && ventilateur != null) + { + ventilateur.Rotate(axeRotation, vitesseVentilateur * Time.deltaTime, Space.Self); + } + } + + /// + /// Génère les LEDs d'activité sur la face avant de l'équipement. + /// + private void GenererLEDs() + { + // Supprime les anciennes LEDs + for (int i = transform.childCount - 1; i >= 0; i--) + { + Transform enfant = transform.GetChild(i); + if (enfant.name.StartsWith("LED_Activity_")) + { + if (Application.isPlaying) Destroy(enfant.gameObject); + else DestroyImmediate(enfant.gameObject); + } + } + ledsRenderers.Clear(); + + Bounds bounds = GetBounds(); + + float startX = ledsDecalageX - ((nombreLEDs - 1) * ledsEspacement) / 2f; + + for (int i = 0; i < nombreLEDs; i++) + { + GameObject ledObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); + ledObj.name = $"LED_Activity_{i}"; + ledObj.transform.SetParent(transform); + + ledObj.transform.localPosition = new Vector3( + startX + i * ledsEspacement, + ledsDecalageY, + bounds.extents.z + 0.002f // Juste devant la face avant + ); + ledObj.transform.localScale = ledsTaille; + + // Désactive le collider (pas interactif) + Collider ledCol = ledObj.GetComponent(); + if (ledCol != null) + { + if (Application.isPlaying) Destroy(ledCol); + else DestroyImmediate(ledCol); + } + + // Material noir par défaut + Renderer rend = ledObj.GetComponent(); + if (rend != null) + { + rend.material = new Material(Shader.Find("Standard")); + rend.material.color = couleurLEDEteinte; + } + + ledsRenderers.Add(rend); + } + + _ledsGenerees = true; + } + + /// + /// Vérifie l'état d'alimentation en fonction des ports connectés. + /// Appelé par PortAlimentation quand l'état change. + /// Cherche les ports dynamiquement via GetComponentsInChildren. + /// + public void VerifierAlimentation() + { + bool ancienEtat = estSousTension; + + PortAlimentation[] ports = GetComponentsInChildren(); + + if (ports.Length == 0) + { + estSousTension = false; + } + else if (alimentationRedondante) + { + // Au moins 1 port alimenté suffit + estSousTension = false; + foreach (var port in ports) + { + if (port.estAlimente) + { + estSousTension = true; + break; + } + } + } + else + { + // Tous les ports doivent être alimentés + estSousTension = true; + foreach (var port in ports) + { + if (!port.estAlimente) + { + estSousTension = false; + break; + } + } + } + + if (ancienEtat != estSousTension) + { + MettreAJourVisuel(); + + string etat = estSousTension ? "SOUS TENSION" : "HORS TENSION"; + Debug.Log($"{gameObject.name}: {etat}"); + + // Notifie l'ambiance sonore + AmbianceSonore ambiance = FindObjectOfType(); + if (ambiance != null) + ambiance.RecalculerAmbiance(); + + // v7.1 : propager l'etat sous-tension au EtatEquipementNetworkSync (pour rejoin) + if (Mirror.NetworkServer.active) + { + EtatEquipementNetworkSync sync = GetComponent(); + if (sync == null) sync = GetComponentInParent(); + if (sync != null) sync.ServerSyncEtat(); + } + } + } + + /// + /// Met à jour l'apparence visuelle selon l'état d'alimentation. + /// + public void MettreAJourVisuel() + { + // LEDs + foreach (var ledRenderer in ledsRenderers) + { + if (ledRenderer == null) continue; + + Color couleur; + if (estSousTension) + couleur = couleurLEDAllumee; + else if (estEnStandby) + couleur = couleurLEDStandby; + else + couleur = couleurLEDEteinte; + + ledRenderer.material.color = couleur; + ledRenderer.material.EnableKeyword("_EMISSION"); + + if (estSousTension || estEnStandby) + ledRenderer.material.SetColor("_EmissionColor", couleur * intensiteLED); + else + ledRenderer.material.SetColor("_EmissionColor", Color.black); + } + + // Clignotement + if (estSousTension) + InvokeRepeating(nameof(ClignoterLEDs), 0.5f, 0.3f); + else + CancelInvoke(nameof(ClignoterLEDs)); + } + + /// + /// Fait clignoter aléatoirement les LEDs pour simuler l'activité. + /// + private void ClignoterLEDs() + { + if (!estSousTension) return; + + foreach (var ledRenderer in ledsRenderers) + { + if (ledRenderer == null) continue; + + if (Random.value < 0.3f) + { + bool allumee = Random.value > 0.5f; + Color couleur = allumee ? couleurLEDAllumee : couleurLEDEteinte; + ledRenderer.material.color = couleur; + ledRenderer.material.SetColor("_EmissionColor", + allumee ? couleurLEDAllumee * intensiteLED : Color.black); + } + } + } + + /// + /// Retourne la consommation actuelle en watts (0 si éteint). + /// + public float GetConsommationActuelle() + { + return estSousTension ? consommationWatts : 0f; + } + + private Bounds GetBounds() + { + BoxCollider boxCol = GetComponent(); + if (boxCol != null) + return new Bounds(boxCol.center, boxCol.size); + + Renderer rend = GetComponent(); + if (rend != null) + { + Bounds worldBounds = rend.bounds; + Vector3 localCenter = transform.InverseTransformPoint(worldBounds.center); + Vector3 localSize = new Vector3( + worldBounds.size.x / transform.lossyScale.x, + worldBounds.size.y / transform.lossyScale.y, + worldBounds.size.z / transform.lossyScale.z + ); + return new Bounds(localCenter, localSize); + } + + return new Bounds(Vector3.zero, new Vector3(0.48f, 0.044f, 0.7f)); + } +} \ No newline at end of file diff --git a/Patchs/pack_multi/Scripts_Modifies/PlayerInteraction.cs b/Patchs/pack_multi/Scripts_Modifies/PlayerInteraction.cs new file mode 100644 index 0000000..9c0a737 --- /dev/null +++ b/Patchs/pack_multi/Scripts_Modifies/PlayerInteraction.cs @@ -0,0 +1,1476 @@ +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) + { + // v7.1 : bouger l'objet COTE SERVEUR pour que NetworkTransform Server-to-Client + // propage aux autres clients. On garde le Rpc en complement pour un rendu + // smooth Lerp cote clients observateurs (latence masquee). + if (_objetPorteNetId != 0 && NetworkServer.spawned.ContainsKey(_objetPorteNetId)) + { + var obj = NetworkServer.spawned[_objetPorteNetId]; + if (obj != null) + { + obj.transform.position = position; + obj.transform.rotation = 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); + + // v7.1 : propager l'etat au EtatEquipementNetworkSync pour le rejoin + EtatEquipementNetworkSync sync = obj.GetComponent(); + if (sync != null) sync.ServerSyncEtat(); + } + } + + [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); + + // v7.1 : desactiver le NetworkTransform local pour que le client porteur ne + // recoive pas la position serveur (qui a une latence). Ainsi il voit son objet + // suivre fluidement ses mouvements. NT est reactive au moment de la pose. + SetNetworkTransformActif(_objetEnMain.gameObject, false); + } + + // v7.1 : helper pour activer/desactiver le NetworkTransform local pendant le portage + // Evite que le client porteur recoive la position serveur (avec latence) et voie + // son objet trembler. Les autres clients continuent de recevoir normalement. + void SetNetworkTransformActif(GameObject obj, bool actif) + { + if (obj == null) return; + foreach (var nt in obj.GetComponents()) + nt.enabled = actif; + } + + // ==================== 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); + + // v7.1 : reactiver NetworkTransform local avant de lacher la reference + if (_objetEnMain != null) SetNetworkTransformActif(_objetEnMain.gameObject, true); + _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); + // v7.1 : reactiver NetworkTransform local avant de lacher la reference + if (_objetEnMain != null) SetNetworkTransformActif(_objetEnMain.gameObject, true); + _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); + // v7.1 : reactiver NetworkTransform local avant de lacher la reference + if (_objetEnMain != null) SetNetworkTransformActif(_objetEnMain.gameObject, true); + _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); + // v7.1 : reactiver NetworkTransform local avant de lacher la reference + if (_objetEnMain != null) SetNetworkTransformActif(_objetEnMain.gameObject, true); + _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); } + // v7.1 : reactiver NetworkTransform local avant de lacher la reference + if (_objetEnMain != null) SetNetworkTransformActif(_objetEnMain.gameObject, true); + _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); + + // v7.1 : reactiver NetworkTransform local avant de lacher la reference + if (_objetEnMain != null) SetNetworkTransformActif(_objetEnMain.gameObject, true); + _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); + + // v7.1 : reactiver NetworkTransform local avant de lacher la reference + if (_objetEnMain != null) SetNetworkTransformActif(_objetEnMain.gameObject, true); + _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/pack_multi/Scripts_Nouveaux/EtatEquipementNetworkSync.cs b/Patchs/pack_multi/Scripts_Nouveaux/EtatEquipementNetworkSync.cs new file mode 100644 index 0000000..bbd81d2 --- /dev/null +++ b/Patchs/pack_multi/Scripts_Nouveaux/EtatEquipementNetworkSync.cs @@ -0,0 +1,107 @@ +// EtatEquipementNetworkSync.cs - v1.0 +// +// Composant a ajouter sur chaque prefab d'equipement rackmount qui possede un +// EtatEquipement + BoutonPower (Serveur_1U, Switch_24P, Parefeu, etc.). +// +// Synchronise 2 etats critiques au rejoin : +// 1. L'etat "bouton Power allume" (BoutonPower.estAllume) +// 2. L'etat "sous tension" de l'equipement (EtatEquipement.estSousTension) +// +// Le serveur est autoritaire : quand le serveur change l'etat (via +// CmdAppuyerBoutonPower), il appelle ServerSetEstAllume() qui met a jour la +// SyncVar. Les clients (actuellement connectes ET futurs rejoigneurs) +// recoivent l'etat via le hook SyncVar. +// +// Note : les animations visuelles (ventilos, LEDs) sont gerees par l'Update() +// de EtatEquipement et BoutonPower qui lisent simplement les bool locaux. + +using UnityEngine; +using Mirror; + +[RequireComponent(typeof(NetworkIdentity))] +public class EtatEquipementNetworkSync : NetworkBehaviour +{ + [SyncVar(hook = nameof(OnBoutonAllumeChanged))] + private bool _syncBoutonAllume = false; + + [SyncVar(hook = nameof(OnSousTensionChanged))] + private bool _syncSousTension = false; + + private BoutonPower _bouton; + private EtatEquipement _etat; + + void Awake() + { + _bouton = GetComponentInChildren(); + _etat = GetComponentInChildren(); + } + + public override void OnStartClient() + { + base.OnStartClient(); + + // Sur le host, rien a faire (le serveur est deja a jour) + if (NetworkServer.active) return; + + // Au rejoin : appliquer les valeurs actuelles des SyncVars. + // Les hooks ne sont PAS appeles automatiquement pour les valeurs initiales, + // il faut forcer l'application ici. + ApplyBoutonAllume(_syncBoutonAllume); + ApplySousTension(_syncSousTension); + } + + // ============================================================ + // API SERVEUR + // ============================================================ + + /// + /// Appele cote serveur quand BoutonPower.Appuyer() a ete execute et qu'il + /// faut synchroniser l'etat aux clients (actuels et futurs). + /// + [Server] + public void ServerSyncEtat() + { + if (_bouton != null) _syncBoutonAllume = _bouton.estAllume; + if (_etat != null) _syncSousTension = _etat.estSousTension; + } + + // ============================================================ + // HOOKS + // ============================================================ + + void OnBoutonAllumeChanged(bool ancien, bool nouveau) + { + ApplyBoutonAllume(nouveau); + } + + void OnSousTensionChanged(bool ancien, bool nouveau) + { + ApplySousTension(nouveau); + } + + // ============================================================ + // APPLICATION LOCALE + // ============================================================ + + private void ApplyBoutonAllume(bool valeur) + { + if (_bouton == null) _bouton = GetComponentInChildren(); + if (_bouton == null) return; + + if (_bouton.estAllume != valeur) + { + // On assigne directement le champ sans appeler Appuyer() (qui ferait + // des effets de bord comme verifier le cablage, rejouer les sons, etc.) + _bouton.estAllume = valeur; + _bouton.MettreAJourVisuel(); + } + } + + private void ApplySousTension(bool valeur) + { + if (_etat == null) _etat = GetComponentInChildren(); + if (_etat == null) return; + + _etat.estSousTension = valeur; + } +} diff --git a/Patchs/pack_multi/Scripts_Nouveaux/GestionnaireCablesReseau.cs b/Patchs/pack_multi/Scripts_Nouveaux/GestionnaireCablesReseau.cs new file mode 100644 index 0000000..4c76a6e --- /dev/null +++ b/Patchs/pack_multi/Scripts_Nouveaux/GestionnaireCablesReseau.cs @@ -0,0 +1,474 @@ +// GestionnaireCablesReseau.cs - v1.0 +// +// Singleton NetworkBehaviour qui synchronise les cables (alimentation + RJ45) entre +// le serveur et les clients, y compris au moment du rejoin d'un client. +// +// Architecture : +// - Cote serveur : chaque creation de cable est enregistree dans une SyncList. +// La suppression retire l'entree correspondante. +// - Cote client au rejoin (OnStartClient) : parcours des SyncLists et recreation +// locale de chaque cable. On attend d'abord que toutes les baies et PDUs soient +// "pretes" (via un systeme d'event) pour eviter les refs null sur les ports. +// +// Setup Unity : +// 1. Dans la scene Datacenter_01, creer un GameObject "GestionnaireCables_Holder" +// 2. Add Component : NetworkIdentity (rien coche) +// 3. Add Component : GestionnaireCablesReseau +// 4. Sauver la scene +// +// Le gestionnaire est appele depuis CablageReseau.CmdCreerCable* / CmdSupprimerCable* +// via GestionnaireCablesReseau.Instance. + +using UnityEngine; +using Mirror; +using System.Collections; +using System.Collections.Generic; + +public class GestionnaireCablesReseau : NetworkBehaviour +{ + public static GestionnaireCablesReseau Instance { get; private set; } + + // ============================================================ + // STRUCTURES DE DONNEES + // ============================================================ + + [System.Serializable] + public struct CableAlimData + { + public uint pduNetId; + public int indexPrise; + public uint equipNetId; + public int indexPortAlim; + public Color couleur; + + public string Id => $"A_{pduNetId}_{indexPrise}_{equipNetId}_{indexPortAlim}"; + } + + [System.Serializable] + public struct CableRJ45Data + { + public uint equipSourceNetId; + public int portSourceIndex; + public uint equipDestNetId; + public int portDestIndex; + public Color couleur; + public float longueur; + + public string Id => $"R_{equipSourceNetId}_{portSourceIndex}_{equipDestNetId}_{portDestIndex}"; + } + + // SyncLists — synchronisees automatiquement aux clients + private readonly SyncList _cablesAlim = new SyncList(); + private readonly SyncList _cablesRJ45 = new SyncList(); + + // ============================================================ + // EVENT : "Baies et equipements pretes pour recevoir les cables" + // ============================================================ + // + // Probleme : au rejoin, les baies sont spawnees dans un ordre imprevisible. + // Si on tente de recreer un cable alors que la baie+PDU cibles ne sont pas + // encore pretes (BaieNetworkSetup.OnStartClient pas encore termine), on aura + // des refs null. + // + // Solution : chaque BaieNetworkSetup notifie le gestionnaire quand sa baie + // est prete. Quand on recoit une notification, on re-tente les cables en + // attente (ceux qui referencaient cette baie/PDU). + + // Structures de "cables en attente" cote client + private readonly HashSet _cablesAlimRecrees = new HashSet(); + private readonly HashSet _cablesRJ45Recrees = new HashSet(); + + // Cables en attente : ceux qu'on a tente de recreer mais dont les refs etaient null + private readonly List _attenteAlim = new List(); + private readonly List _attenteRJ45 = new List(); + + // ============================================================ + // LIFECYCLE + // ============================================================ + + void Awake() + { + if (Instance != null && Instance != this) { Destroy(gameObject); return; } + Instance = this; + } + + void OnDestroy() + { + if (Instance == this) Instance = null; + } + + public override void OnStartClient() + { + base.OnStartClient(); + + // Sur le host, rien a faire : le serveur a deja cree les cables en live + if (NetworkServer.active) return; + + Debug.Log($"[GestionnaireCablesReseau] Client rejoint : {_cablesAlim.Count} cables alim, {_cablesRJ45.Count} cables RJ45 a recreer"); + + // Lancer la coroutine qui tentera de recreer les cables regulierement + // jusqu'a ce que tous soient crees (ou abandon apres timeout) + StartCoroutine(RejouerCablesProgressivement()); + } + + // ============================================================ + // API SERVEUR (appelee par CablageReseau) + // ============================================================ + + [Server] + public void EnregistrerCableAlim(uint pduNetId, int indexPrise, uint equipNetId, int indexPortAlim, Color couleur) + { + CableAlimData data = new CableAlimData + { + pduNetId = pduNetId, + indexPrise = indexPrise, + equipNetId = equipNetId, + indexPortAlim = indexPortAlim, + couleur = couleur + }; + + // Eviter les doublons (meme prise + meme port = meme cable) + for (int i = 0; i < _cablesAlim.Count; i++) + { + if (_cablesAlim[i].pduNetId == pduNetId && _cablesAlim[i].indexPrise == indexPrise && + _cablesAlim[i].equipNetId == equipNetId && _cablesAlim[i].indexPortAlim == indexPortAlim) + return; + } + + _cablesAlim.Add(data); + Debug.Log($"[GestionnaireCablesReseau] Cable alim enregistre (total : {_cablesAlim.Count})"); + } + + [Server] + public void OublierCableAlim(uint pduNetId, int indexPrise, uint equipNetId, int indexPortAlim) + { + for (int i = _cablesAlim.Count - 1; i >= 0; i--) + { + if (_cablesAlim[i].pduNetId == pduNetId && _cablesAlim[i].indexPrise == indexPrise && + _cablesAlim[i].equipNetId == equipNetId && _cablesAlim[i].indexPortAlim == indexPortAlim) + { + _cablesAlim.RemoveAt(i); + Debug.Log($"[GestionnaireCablesReseau] Cable alim supprime (reste : {_cablesAlim.Count})"); + return; + } + } + } + + [Server] + public void EnregistrerCableRJ45(uint equipSourceNetId, int portSourceIndex, + uint equipDestNetId, int portDestIndex, + Color couleur, float longueur) + { + CableRJ45Data data = new CableRJ45Data + { + equipSourceNetId = equipSourceNetId, + portSourceIndex = portSourceIndex, + equipDestNetId = equipDestNetId, + portDestIndex = portDestIndex, + couleur = couleur, + longueur = longueur + }; + + for (int i = 0; i < _cablesRJ45.Count; i++) + { + if (_cablesRJ45[i].equipSourceNetId == equipSourceNetId && + _cablesRJ45[i].portSourceIndex == portSourceIndex && + _cablesRJ45[i].equipDestNetId == equipDestNetId && + _cablesRJ45[i].portDestIndex == portDestIndex) + return; + } + + _cablesRJ45.Add(data); + Debug.Log($"[GestionnaireCablesReseau] Cable RJ45 enregistre (total : {_cablesRJ45.Count})"); + } + + [Server] + public void OublierCableRJ45(uint equipSourceNetId, int portSourceIndex, + uint equipDestNetId, int portDestIndex) + { + for (int i = _cablesRJ45.Count - 1; i >= 0; i--) + { + var c = _cablesRJ45[i]; + // Match dans les deux sens car un cable RJ45 est bidirectionnel + bool match1 = c.equipSourceNetId == equipSourceNetId && c.portSourceIndex == portSourceIndex && + c.equipDestNetId == equipDestNetId && c.portDestIndex == portDestIndex; + bool match2 = c.equipSourceNetId == equipDestNetId && c.portSourceIndex == portDestIndex && + c.equipDestNetId == equipSourceNetId && c.portDestIndex == portSourceIndex; + + if (match1 || match2) + { + _cablesRJ45.RemoveAt(i); + Debug.Log($"[GestionnaireCablesReseau] Cable RJ45 supprime (reste : {_cablesRJ45.Count})"); + return; + } + } + } + + // ============================================================ + // API CLIENT : notification de baie prete + // ============================================================ + + /// + /// Appele par BaieNetworkSetup.OnStartClient une fois que la baie + ses PDUs + /// sont genere localement. Le gestionnaire tente de recreer les cables en + /// attente qui referencaient cette baie ou ses enfants. + /// + public void NotifierBaiePrete(GameObject baie) + { + if (!NetworkClient.active || NetworkServer.active) return; + + Debug.Log($"[GestionnaireCablesReseau] Baie prete : {baie.name}, retry cables en attente"); + RetenterCablesEnAttente(); + } + + // ============================================================ + // COROUTINE DE REJEU (client qui rejoint) + // ============================================================ + + private IEnumerator RejouerCablesProgressivement() + { + // Attendre 0.5s pour laisser les premieres baies + PDUs se generer via BaieNetworkSetup + yield return new WaitForSeconds(0.5f); + + // Initialiser la liste d'attente avec tous les cables connus + _attenteAlim.Clear(); + foreach (var data in _cablesAlim) _attenteAlim.Add(data); + + _attenteRJ45.Clear(); + foreach (var data in _cablesRJ45) _attenteRJ45.Add(data); + + // Tenter une premiere passe + RetenterCablesEnAttente(); + + // Si il reste des cables en attente, re-tenter periodiquement + // (les notifications de baie prete vont aussi declencher des retries) + float tempsEcoule = 0f; + const float timeoutMax = 15f; + const float intervalRetry = 0.5f; + + while ((_attenteAlim.Count > 0 || _attenteRJ45.Count > 0) && tempsEcoule < timeoutMax) + { + yield return new WaitForSeconds(intervalRetry); + tempsEcoule += intervalRetry; + RetenterCablesEnAttente(); + } + + if (_attenteAlim.Count > 0 || _attenteRJ45.Count > 0) + { + Debug.LogWarning($"[GestionnaireCablesReseau] Timeout : {_attenteAlim.Count} cables alim + {_attenteRJ45.Count} cables RJ45 jamais recrees"); + } + else + { + Debug.Log($"[GestionnaireCablesReseau] Tous les cables ont ete recrees"); + } + } + + private void RetenterCablesEnAttente() + { + // Cables alim + for (int i = _attenteAlim.Count - 1; i >= 0; i--) + { + CableAlimData data = _attenteAlim[i]; + if (_cablesAlimRecrees.Contains(data.Id)) { _attenteAlim.RemoveAt(i); continue; } + + if (TenterRecreerCableAlim(data)) + { + _cablesAlimRecrees.Add(data.Id); + _attenteAlim.RemoveAt(i); + } + } + + // Cables RJ45 + for (int i = _attenteRJ45.Count - 1; i >= 0; i--) + { + CableRJ45Data data = _attenteRJ45[i]; + if (_cablesRJ45Recrees.Contains(data.Id)) { _attenteRJ45.RemoveAt(i); continue; } + + if (TenterRecreerCableRJ45(data)) + { + _cablesRJ45Recrees.Add(data.Id); + _attenteRJ45.RemoveAt(i); + } + } + } + + // ============================================================ + // RECREATION LOCALE DES CABLES + // ============================================================ + + private bool TenterRecreerCableAlim(CableAlimData data) + { + PriseC13 prise = TrouverPriseC13(data.pduNetId, data.indexPrise); + PortAlimentation port = TrouverPortAlimentation(data.equipNetId, data.indexPortAlim); + + if (prise == null || port == null) return false; + + // Deja connecte ? (si BaieNetworkSetup l'a fait automatiquement via InstallerPDUAutomatiquement) + if (prise.estConnectee || port.estConnecte) return true; + + GameObject cableObj = new GameObject($"CableAlim_rejoin_{data.pduNetId}_{data.indexPrise}"); + CableAlimentation cable = cableObj.AddComponent(); + cable.couleurCable = data.couleur; + cable.Initialiser(prise, port); + prise.Connecter(cable, port); + port.Connecter(cable, prise); + + Debug.Log($"[GestionnaireCablesReseau] Cable alim recree : PDU {data.pduNetId} prise #{data.indexPrise} -> {port.nomPort}"); + return true; + } + + private bool TenterRecreerCableRJ45(CableRJ45Data data) + { + PortRJ45 portSource = TrouverPortRJ45(data.equipSourceNetId, data.portSourceIndex); + PortRJ45 portDest = TrouverPortRJ45(data.equipDestNetId, data.portDestIndex); + + if (portSource == null || portDest == null) return false; + + // Deja connecte ? + if (portSource.cable != null || portDest.cable != null) return true; + + List chemin = ConstruireCheminAuto( + portSource.GetPointConnexion(), portDest.GetPointConnexion()); + + GameObject cableObj = new GameObject($"CableRJ45_rejoin_{data.equipSourceNetId}_{data.portSourceIndex}"); + CableRJ45 cable = cableObj.AddComponent(); + cable.Initialiser(portSource, portDest, chemin, data.couleur, data.longueur, null); + + portSource.Connecter(portDest, cable); + portDest.Connecter(portSource, cable); + + Debug.Log($"[GestionnaireCablesReseau] Cable RJ45 recree : {portSource.nomPort} -> {portDest.nomPort}"); + return true; + } + + // ============================================================ + // HELPERS DE RECHERCHE (copies depuis CablageReseau) + // ============================================================ + + private PortRJ45 TrouverPortRJ45(uint equipNetId, int portIndex) + { + if (!NetworkClient.spawned.ContainsKey(equipNetId)) return null; + GameObject equipGO = NetworkClient.spawned[equipNetId].gameObject; + if (equipGO == null) return null; + + PortRJ45[] ports = equipGO.GetComponentsInChildren(); + foreach (var port in ports) + if (port.numeroPort == portIndex) return port; + + if (portIndex >= 0 && portIndex < ports.Length) + return ports[portIndex]; + + return null; + } + + private PriseC13 TrouverPriseC13(uint pduNetId, int indexPrise) + { + if (!NetworkClient.spawned.ContainsKey(pduNetId)) return null; + GameObject pduGO = NetworkClient.spawned[pduNetId].gameObject; + if (pduGO == null) return null; + + PDU pdu = pduGO.GetComponent(); + if (pdu != null && indexPrise >= 0 && indexPrise < pdu.prises.Count) + return pdu.prises[indexPrise]; + + PriseC13[] prises = pduGO.GetComponentsInChildren(); + foreach (var p in prises) + if (p.indexPrise == indexPrise) return p; + + return null; + } + + private PortAlimentation TrouverPortAlimentation(uint equipNetId, int indexPort) + { + if (!NetworkClient.spawned.ContainsKey(equipNetId)) return null; + GameObject equipGO = NetworkClient.spawned[equipNetId].gameObject; + if (equipGO == null) return null; + + PortAlimentation[] ports = equipGO.GetComponentsInChildren(); + if (indexPort >= 0 && indexPort < ports.Length) return ports[indexPort]; + return null; + } + + private List ConstruireCheminAuto(Vector3 posSource, Vector3 posDest) + { + // Algorithme A* simplifie (copie depuis CablageReseau) + List chemin = new List(); + PointAccroche[] tousPoints = FindObjectsOfType(); + if (tousPoints.Length == 0) return chemin; + + PointAccroche ptDepart = null; + float bestDistDepart = 3f; + foreach (var p in tousPoints) + { + float d = Vector3.Distance(posSource, p.GetPosition()); + if (d < bestDistDepart) { bestDistDepart = d; ptDepart = p; } + } + + PointAccroche ptArrivee = null; + float bestDistArrivee = 3f; + foreach (var p in tousPoints) + { + float d = Vector3.Distance(posDest, p.GetPosition()); + if (d < bestDistArrivee) { bestDistArrivee = d; ptArrivee = p; } + } + + if (ptDepart == null || ptArrivee == null) return chemin; + if (ptDepart == ptArrivee) { chemin.Add(ptDepart); return chemin; } + + float distanceVoisinage = 1.5f; + Dictionary gScore = new Dictionary(); + Dictionary cameFrom = new Dictionary(); + HashSet closedSet = new HashSet(); + List openSet = new List(); + + gScore[ptDepart] = 0; + openSet.Add(ptDepart); + + int maxIter = 500; + int iter = 0; + + while (openSet.Count > 0 && iter < maxIter) + { + iter++; + PointAccroche current = null; + float bestF = float.MaxValue; + foreach (var n in openSet) + { + float g = gScore.ContainsKey(n) ? gScore[n] : float.MaxValue; + float f = g + Vector3.Distance(n.GetPosition(), ptArrivee.GetPosition()); + if (f < bestF) { bestF = f; current = n; } + } + if (current == null) break; + if (current == ptArrivee) + { + List result = new List { current }; + while (cameFrom.ContainsKey(current)) + { + current = cameFrom[current]; + result.Insert(0, current); + } + return result; + } + + openSet.Remove(current); + closedSet.Add(current); + + foreach (var autre in tousPoints) + { + if (autre == current || closedSet.Contains(autre)) continue; + if (Vector3.Distance(current.GetPosition(), autre.GetPosition()) > distanceVoisinage) continue; + + float tentG = gScore[current] + Vector3.Distance(current.GetPosition(), autre.GetPosition()); + if (!openSet.Contains(autre)) openSet.Add(autre); + else if (gScore.ContainsKey(autre) && tentG >= gScore[autre]) continue; + + cameFrom[autre] = current; + gScore[autre] = tentG; + } + } + + chemin.Add(ptDepart); + chemin.Add(ptArrivee); + return chemin; + } +} diff --git a/dcsim_pack_multi_v7.1.zip b/ZIP/dcsim_pack_multi_v7.1.zip similarity index 100% rename from dcsim_pack_multi_v7.1.zip rename to ZIP/dcsim_pack_multi_v7.1.zip