diff --git a/Patchs/fix_bridge/PlayerAgrandissementBridge.cs b/Patchs/fix_bridge/PlayerAgrandissementBridge.cs new file mode 100644 index 0000000..21b1fc0 --- /dev/null +++ b/Patchs/fix_bridge/PlayerAgrandissementBridge.cs @@ -0,0 +1,27 @@ +// PlayerAgrandissementBridge.cs + +using UnityEngine; +using Mirror; + +/// +/// Bridge Mirror : composant a ajouter au prefab du joueur pour qu'il puisse +/// envoyer une Cmd d'agrandissement vers le serveur (car les [Command] ne peuvent +/// etre appelees que depuis un NetworkBehaviour porte par le joueur local). +/// +/// A placer sur : le prefab du joueur (celui assigne dans NetworkManager.playerPrefab). +/// Sans ce composant sur le joueur, les clients ne pourront pas demander d'agrandissement. +/// +public class PlayerAgrandissementBridge : NetworkBehaviour +{ + [Command] + public void CmdDemanderAgrandissement(int salleId, string direction) + { + if (SynchronisationAgrandissement.Instance == null) + { + Debug.LogError("[PlayerAgrandissementBridge] Instance SynchronisationAgrandissement introuvable cote serveur !"); + return; + } + + SynchronisationAgrandissement.Instance.TraiterDemandeServeur(salleId, direction); + } +} diff --git a/Patchs/fix_bridge/README_FIX.md b/Patchs/fix_bridge/README_FIX.md new file mode 100644 index 0000000..07058a2 --- /dev/null +++ b/Patchs/fix_bridge/README_FIX.md @@ -0,0 +1,29 @@ +# Fix : PlayerAgrandissementBridge introuvable dans Add Component + +## Cause + +Unity exige qu'un MonoBehaviour/NetworkBehaviour soit dans un fichier `.cs` portant SON nom pour être exposé dans `Add Component`. Les 2 classes étaient dans le même fichier `SynchronisationAgrandissement.cs` — Unity exposait seulement la 1re. + +## Ce que contient ce fix + +1. **`SynchronisationAgrandissement.cs`** (nettoyé) + → La 2e classe `PlayerAgrandissementBridge` a été retirée + +2. **`PlayerAgrandissementBridge.cs`** (nouveau fichier) + → Contient uniquement la classe du bridge, dans son propre fichier + +## Actions à faire dans Unity + +1. **Remplacer** `Assets/Scripts/SynchronisationAgrandissement.cs` par la nouvelle version + +2. **Ajouter** `Assets/Scripts/PlayerAgrandissementBridge.cs` (nouveau fichier) + +3. **Attendre la compilation** (quelques secondes) + +4. **Ouvrir le prefab du joueur** et `Add Component` → taper `PlayerAgrandissementBridge` → il apparaît maintenant ✅ + +## Vérif + +Si le composant n'apparaît TOUJOURS pas après ces étapes : +- Regarder la console Unity pour des erreurs rouges de compilation +- `Assets` → `Reimport All` (en dernier recours) diff --git a/Patchs/fix_bridge/SynchronisationAgrandissement.cs b/Patchs/fix_bridge/SynchronisationAgrandissement.cs new file mode 100644 index 0000000..feffb04 --- /dev/null +++ b/Patchs/fix_bridge/SynchronisationAgrandissement.cs @@ -0,0 +1,261 @@ +// SynchronisationAgrandissement.cs + +using UnityEngine; +using Mirror; +using System.Collections.Generic; + +/// +/// Singleton NetworkBehaviour qui synchronise les agrandissements de salle entre +/// le serveur dedicated et tous les clients. +/// +/// Pattern : +/// 1. Un client clique [E] sur un panneau d'agrandissement +/// -> AgrandissementSalle.DemanderAgrandir() trouve le singleton et appelle CmdAgrandir(id) +/// 2. Cote serveur, CmdAgrandir valide (pas deja agrandi) et diffuse RpcLancerAgrandissement(id) +/// 3. Chaque client (+ serveur) recoit le Rpc et execute l'animation locale d'Agrandir() +/// en parallele. Tous voient la meme animation car les parametres sont identiques. +/// 4. Un client qui rejoint en cours de partie recoit la SyncList des agrandissements deja +/// faits et les rejoue instantanement (sans animation). +/// +/// A placer : en composant sur le GameObject du NetworkManager (ou tout objet avec +/// NetworkIdentity configure pour exister des le demarrage reseau). +/// +/// L'identifiant d'un agrandissement est "salleId_direction", ex: "1_Nord". +/// +public class SynchronisationAgrandissement : NetworkBehaviour +{ + public static SynchronisationAgrandissement Instance { get; private set; } + + // Liste synchronisee des agrandissements deja effectues + // Format des entrees : "salleId_Direction" (ex: "1_Nord", "2_Est") + private readonly SyncList _agrandissementsEffectues = new SyncList(); + + private void Awake() + { + if (Instance != null && Instance != this) + { + Debug.LogWarning("[SyncAgrandissement] Instance deja existante, destruction de ce duplicata"); + Destroy(this); + return; + } + Instance = this; + } + + private void OnDestroy() + { + if (Instance == this) Instance = null; + } + + // ============================================================ + // CLIENT : appel par AgrandissementSalle.DemanderAgrandir() + // ============================================================ + + /// + /// Appele par un client qui veut agrandir. Envoie la demande au serveur. + /// En mode solo (pas de Mirror actif), execute directement localement. + /// + public void DemanderAgrandissement(int salleId, string direction) + { + string id = ConstruireId(salleId, direction); + + // Mode solo (pas de serveur Mirror demarre) : execution directe locale + if (!NetworkServer.active && !NetworkClient.active) + { + Debug.Log($"[SyncAgrandissement] Mode solo, execution locale directe : {id}"); + ExecuterAgrandissementLocal(salleId, direction, false); + return; + } + + // Mode multi : Cmd vers serveur + // Il faut que le client ait authority. On passe par le player local qui + // porte ce role, ou directement si on est serveur nous-meme. + if (NetworkServer.active) + { + // Host ou dedicated : on est serveur, on peut traiter directement + TraiterDemandeServeur(salleId, direction); + } + else if (NetworkClient.active && NetworkClient.connection != null) + { + // Client pur : on doit passer par un PlayerBridge qui porte isLocalPlayer + PlayerAgrandissementBridge bridge = TrouverBridgeLocal(); + if (bridge != null) + { + bridge.CmdDemanderAgrandissement(salleId, direction); + } + else + { + Debug.LogError("[SyncAgrandissement] Aucun PlayerAgrandissementBridge trouve sur le joueur local !"); + } + } + } + + // ============================================================ + // SERVEUR : validation + broadcast + // ============================================================ + + /// + /// Appele cote serveur (soit par un host qui joue, soit par le bridge d'un client). + /// Valide et diffuse aux clients. + /// + [Server] + public void TraiterDemandeServeur(int salleId, string direction) + { + string id = ConstruireId(salleId, direction); + + // Validation + if (_agrandissementsEffectues.Contains(id)) + { + Debug.LogWarning($"[SyncAgrandissement] Demande refusee, deja agrandi : {id}"); + return; + } + + // Enregistrer dans la liste synchronisee (pour rejoin) + _agrandissementsEffectues.Add(id); + Debug.Log($"[SyncAgrandissement] Agrandissement valide : {id} (total : {_agrandissementsEffectues.Count})"); + + // Diffuser a tous les clients (le host le recoit aussi) + RpcLancerAgrandissement(salleId, direction); + } + + // ============================================================ + // CLIENTS : reception du Rpc + // ============================================================ + + [ClientRpc] + private void RpcLancerAgrandissement(int salleId, string direction) + { + Debug.Log($"[SyncAgrandissement] Rpc recu, execution agrandissement : salle {salleId} direction {direction}"); + ExecuterAgrandissementLocal(salleId, direction, false); + } + + // ============================================================ + // REJOIN : un nouveau client rejoue l'etat actuel + // ============================================================ + + public override void OnStartClient() + { + base.OnStartClient(); + + // Ne pas rejouer sur le host (il est deja a jour, c'est lui le serveur) + if (NetworkServer.active) return; + + // Le SyncList n'est pas forcement deja rempli a ce moment-la. + // On se branche sur ses callbacks et on rejoue les entrees existantes. + _agrandissementsEffectues.Callback += OnAgrandissementsListChanged; + + // Rejouer immediatement les entrees deja presentes + if (_agrandissementsEffectues.Count > 0) + { + Debug.Log($"[SyncAgrandissement] Client rejoint : rejeu de {_agrandissementsEffectues.Count} agrandissement(s) passe(s)"); + foreach (string id in _agrandissementsEffectues) + { + RejouerInstantane(id); + } + } + } + + public override void OnStopClient() + { + base.OnStopClient(); + _agrandissementsEffectues.Callback -= OnAgrandissementsListChanged; + } + + private void OnAgrandissementsListChanged(SyncList.Operation op, int index, string oldItem, string newItem) + { + // On ignore les changements qui arrivent par le flux normal (deja gere par Rpc) + // Ce callback ne sert qu'au cas ou la SyncList est modifiee avant que le client recoive + // le Rpc correspondant (race condition rare). Dans ce cas on rejoue aussi. + // En pratique on s'en remet surtout au rejeu initial dans OnStartClient(). + } + + private void RejouerInstantane(string id) + { + int salleId; + string direction; + if (!DecomposerId(id, out salleId, out direction)) + { + Debug.LogWarning($"[SyncAgrandissement] Id mal forme : {id}"); + return; + } + ExecuterAgrandissementLocal(salleId, direction, true); + } + + // ============================================================ + // EXECUTION LOCALE (cote serveur ET cote client) + // ============================================================ + + /// + /// Execute l'agrandissement sur l'instance locale : trouve le bon mur et lance Agrandir(). + /// Si instantane=true, skip les animations (pour rejoin). + /// + private void ExecuterAgrandissementLocal(int salleId, string direction, bool instantane) + { + AgrandissementSalle mur = TrouverMurAgrandissement(salleId, direction); + if (mur == null) + { + Debug.LogWarning($"[SyncAgrandissement] Mur introuvable : salle {salleId} direction {direction}"); + return; + } + + if (mur.estAgrandi || mur.enAnimation) + { + Debug.Log($"[SyncAgrandissement] Agrandissement deja lance ou termine localement, skip"); + return; + } + + mur.AgrandirLocal(instantane); + } + + private AgrandissementSalle TrouverMurAgrandissement(int salleId, string direction) + { + AgrandissementSalle.DirectionAgrandissement dirEnum; + if (!System.Enum.TryParse(direction, out dirEnum)) return null; + + AgrandissementSalle[] tous = FindObjectsOfType(); + foreach (var a in tous) + { + if (a.direction != dirEnum) continue; + SalleDatacenter salle = FindObjectOfType(); + if (salle == null) continue; + if (salle.salleId == salleId) return a; + } + + // Fallback : si aucun salleId ne matche (salle mono, pas encore enregistree + // par le GestionnaireMultiSalles au moment de l'appel), on renvoie n'importe + // quel mur dans la bonne direction. + foreach (var a in tous) + { + if (a.direction == dirEnum) return a; + } + + return null; + } + + private PlayerAgrandissementBridge TrouverBridgeLocal() + { + PlayerAgrandissementBridge[] tous = FindObjectsOfType(); + foreach (var b in tous) + { + if (b.isLocalPlayer) return b; + } + return null; + } + + // ============================================================ + // HELPERS ID + // ============================================================ + + private static string ConstruireId(int salleId, string direction) => $"{salleId}_{direction}"; + + private static bool DecomposerId(string id, out int salleId, out string direction) + { + salleId = -1; + direction = ""; + if (string.IsNullOrEmpty(id)) return false; + int sep = id.IndexOf('_'); + if (sep <= 0 || sep >= id.Length - 1) return false; + if (!int.TryParse(id.Substring(0, sep), out salleId)) return false; + direction = id.Substring(sep + 1); + return true; + } +}