From b052a0ef104e4d4bfda507698373dd1728694b98 Mon Sep 17 00:00:00 2001 From: smauro Date: Fri, 17 Apr 2026 20:40:46 +0000 Subject: [PATCH] =?UTF-8?q?T=C3=A9l=C3=A9verser=20les=20fichiers=20vers=20?= =?UTF-8?q?"/"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DebugFalling.cs | 94 ++++++++++ MasterServerClient.cs | 422 ++++++++++++++++++++++++++++++++++++++++++ README_PATCHES_V2.md | 47 +++++ 3 files changed, 563 insertions(+) create mode 100644 DebugFalling.cs create mode 100644 MasterServerClient.cs create mode 100644 README_PATCHES_V2.md diff --git a/DebugFalling.cs b/DebugFalling.cs new file mode 100644 index 0000000..67fb40e --- /dev/null +++ b/DebugFalling.cs @@ -0,0 +1,94 @@ +using UnityEngine; + +/// +/// TEMPORAIRE - Debug pour diagnostiquer le problème de chute sous la map. +/// Ajouter sur le prefab PlayerCapsule, supprimer après diagnostic. +/// +public class DebugFalling : MonoBehaviour, IClientOnly +{ + private CharacterController _cc; + private StarterAssets.FirstPersonController _fpc; + private float _lastLogTime = 0f; + private float _lastY; + private bool _wasFalling = false; + + void Start() + { + _cc = GetComponent(); + _fpc = GetComponent(); + _lastY = transform.position.y; + + if (_cc != null) + { + Debug.Log($"[DebugFall] CharacterController: center={_cc.center} height={_cc.height} radius={_cc.radius} skinWidth={_cc.skinWidth}"); + Debug.Log($"[DebugFall] Layer joueur: {gameObject.layer} ({LayerMask.LayerToName(gameObject.layer)})"); + } + + if (_fpc != null) + { + Debug.Log($"[DebugFall] FPC enabled={_fpc.enabled} GroundLayers={_fpc.GroundLayers.value} GroundedOffset={_fpc.GroundedOffset} GroundedRadius={_fpc.GroundedRadius}"); + } + + // Test : y a-t-il un sol sous nous ? + RaycastHit hit; + if (Physics.Raycast(transform.position + Vector3.up * 0.5f, Vector3.down, out hit, 50f)) + Debug.Log($"[DebugFall] Sol détecté: {hit.collider.gameObject.name} layer={LayerMask.LayerToName(hit.collider.gameObject.layer)} distance={hit.distance:F2} point={hit.point}"); + else + Debug.Log("[DebugFall] AUCUN SOL DÉTECTÉ sous le joueur !"); + + // Test GroundLayers + if (_fpc != null) + { + int solLayer = LayerMask.NameToLayer("Sol"); + bool solInclus = (_fpc.GroundLayers.value & (1 << solLayer)) != 0; + Debug.Log($"[DebugFall] Layer Sol ({solLayer}) inclus dans GroundLayers: {solInclus}"); + + // Test CheckSphere comme le fait le FPC + Vector3 spherePos = new Vector3(transform.position.x, transform.position.y - _fpc.GroundedOffset, transform.position.z); + bool grounded = Physics.CheckSphere(spherePos, _fpc.GroundedRadius, _fpc.GroundLayers, QueryTriggerInteraction.Ignore); + Debug.Log($"[DebugFall] CheckSphere grounded={grounded} spherePos={spherePos} radius={_fpc.GroundedRadius}"); + } + } + + void Update() + { + if (_fpc == null || _cc == null) return; + + float y = transform.position.y; + bool falling = y < _lastY - 0.01f; + + // Log quand on commence à tomber + if (falling && !_wasFalling) + { + Debug.LogWarning($"[DebugFall] DÉBUT CHUTE à Y={y:F2} pos={transform.position} Grounded={_fpc.Grounded} FPC.enabled={_fpc.enabled} CC.enabled={_cc.enabled} CC.isGrounded={_cc.isGrounded}"); + + // Vérifier ce qu'il y a sous nous + RaycastHit hit; + if (Physics.Raycast(transform.position, Vector3.down, out hit, 50f)) + Debug.LogWarning($"[DebugFall] Sous nous: {hit.collider.gameObject.name} layer={LayerMask.LayerToName(hit.collider.gameObject.layer)} dist={hit.distance:F2}"); + else + Debug.LogWarning("[DebugFall] RIEN sous le joueur !"); + } + + // Log continu pendant la chute (1x par seconde) + if (falling && Time.time - _lastLogTime > 1f) + { + _lastLogTime = Time.time; + Debug.LogWarning($"[DebugFall] EN CHUTE Y={y:F2} velocity.y={_cc.velocity.y:F2} Grounded={_fpc.Grounded} CC.isGrounded={_cc.isGrounded}"); + } + + // Log quand on tombe trop bas + if (y < -5f && Time.time - _lastLogTime > 2f) + { + _lastLogTime = Time.time; + Debug.LogError($"[DebugFall] SOUS LA MAP Y={y:F2} — téléportation de secours !"); + // Téléporter au spawn de secours + _cc.enabled = false; + transform.position = new Vector3(0, 2, 0); + _cc.enabled = true; + } + + _wasFalling = falling; + _lastY = y; + } +} \ No newline at end of file diff --git a/MasterServerClient.cs b/MasterServerClient.cs new file mode 100644 index 0000000..7f0082c --- /dev/null +++ b/MasterServerClient.cs @@ -0,0 +1,422 @@ +using UnityEngine; +using UnityEngine.Networking; +using System; +using System.Collections; +using System.Collections.Generic; +using Mirror; + +/// +/// MasterServerClient.cs - v6.6 +/// +/// v6.6 : Robustesse du heartbeat pour dedicated server +/// - Heartbeat passe de 30s a 10s par defaut (reduit la fenetre +/// ou le master server peut purger un serveur inactif) +/// - Sur reponse 404 (serveur inconnu), re-enregistrement AUTOMATIQUE +/// immediat avec les memes parametres que le register initial (au lieu +/// de juste reset les variables et attendre le prochain heartbeat) +/// - Memoire des parametres de registration pour pouvoir re-register +/// sans intervention exterieure +/// - Apres 3 echecs consecutifs, abandon et log d'erreur +/// +/// v6.5 : Version originale +/// +/// Usage : +/// Placer sur le meme GameObject que le NetworkManager (ou un singleton). +/// Configurer masterServerUrl dans l'Inspector. +/// +public class MasterServerClient : MonoBehaviour +{ + // ══════════════════════════════════════════════════ + // CONFIGURATION + // ══════════════════════════════════════════════════ + + [Header("Master Server")] + [Tooltip("URL du master server (ex: http://192.168.1.50:8080)")] + public string masterServerUrl = "http://localhost:8080"; + + [Tooltip("Token d'authentification (doit correspondre au serveur)")] + public string token = "dcsim-2026-secret"; + + [Header("Heartbeat")] + [Tooltip("Intervalle du heartbeat en secondes (10s recommande pour dedicated)")] + public float heartbeatInterval = 10f; + + [Tooltip("Nombre max d'echecs consecutifs avant abandon (reset si succes)")] + public int maxEchecsConsecutifs = 3; + + // ══════════════════════════════════════════════════ + // ETAT + // ══════════════════════════════════════════════════ + + [HideInInspector] public string serverId = ""; + [HideInInspector] public bool estEnregistre = false; + [HideInInspector] public List derniereListeServeurs = new List(); + [HideInInspector] public bool requeteEnCours = false; + [HideInInspector] public string dernierStatut = ""; + + // Singleton + public static MasterServerClient Instance { get; private set; } + + private Coroutine _heartbeatCoroutine; + private int _echecsConsecutifs = 0; + + // v6.6 : memoire des parametres d'enregistrement pour re-register automatique + private string _dernierNom = ""; + private int _dernierPort = 7777; + private int _dernierMaxJoueurs = 4; + private int _derniereLangue = 1; + private string _dernierMode = "Sandbox"; + private string _derniereVersion = "v6.5g"; + private bool _dernierMotDePasse = false; + + // ══════════════════════════════════════════════════ + // MODELES DE DONNEES + // ══════════════════════════════════════════════════ + + [System.Serializable] + public class ServerData + { + public string id; + public string nom; + public string ip; + public int port; + public int joueurs; + public int max_joueurs; + public int ping; + public int langue; + public string mode; + public string version; + public bool mot_de_passe; + } + + [System.Serializable] + private class ServerListWrapper + { + public List servers; + } + + [System.Serializable] + private class RegisterRequest + { + public string nom; + public int port; + public int max_joueurs; + public int langue; + public string mode; + public string version; + public bool mot_de_passe; + public string token; + } + + [System.Serializable] + private class RegisterResponse + { + public string server_id; + public string status; + } + + [System.Serializable] + private class HeartbeatRequest + { + public int joueurs; + public string token; + } + + // ══════════════════════════════════════════════════ + // LIFECYCLE + // ══════════════════════════════════════════════════ + + void Awake() + { + if (Instance != null && Instance != this) + { + Destroy(gameObject); + return; + } + Instance = this; + Debug.Log($"[MasterServer] Awake → Instance initialisee, URL={masterServerUrl}"); + } + + void OnDestroy() + { + if (Instance == this) + Instance = null; + } + + void OnApplicationQuit() + { + if (estEnregistre && !string.IsNullOrEmpty(serverId)) + { + var request = new UnityWebRequest( + $"{masterServerUrl}/unregister/{serverId}", "DELETE"); + request.timeout = 2; + request.SendWebRequest(); + Debug.Log($"[MasterServer] Desenregistrement envoye ({serverId})"); + } + } + + // ══════════════════════════════════════════════════ + // API PUBLIQUE + // ══════════════════════════════════════════════════ + + public void EnregistrerServeur(string nom, int port, int maxJoueurs, + int langue, string mode, string version, bool motDePasse) + { + // v6.6 : memorise les parametres pour pouvoir re-register en cas de 404 + _dernierNom = nom; + _dernierPort = port; + _dernierMaxJoueurs = maxJoueurs; + _derniereLangue = langue; + _dernierMode = mode; + _derniereVersion = version; + _dernierMotDePasse = motDePasse; + + StartCoroutine(Register(nom, port, maxJoueurs, langue, mode, version, motDePasse)); + } + + public void DesenregistrerServeur() + { + if (estEnregistre && !string.IsNullOrEmpty(serverId)) + StartCoroutine(Unregister()); + } + + public void RecupererServeurs(System.Action, string> callback) + { + StartCoroutine(FetchServers(callback)); + } + + public void VerifierConnexion(System.Action callback) + { + StartCoroutine(CheckHealth(callback)); + } + + // ══════════════════════════════════════════════════ + // COROUTINES HTTP + // ══════════════════════════════════════════════════ + + private IEnumerator Register(string nom, int port, int maxJoueurs, + int langue, string mode, string version, bool motDePasse) + { + requeteEnCours = true; + dernierStatut = "Enregistrement..."; + + var body = new RegisterRequest + { + nom = nom, + port = port, + max_joueurs = maxJoueurs, + langue = langue, + mode = mode, + version = version, + mot_de_passe = motDePasse, + token = token + }; + + string json = JsonUtility.ToJson(body); + byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(json); + + var request = new UnityWebRequest($"{masterServerUrl}/register", "POST"); + request.uploadHandler = new UploadHandlerRaw(bodyRaw); + request.downloadHandler = new DownloadHandlerBuffer(); + request.SetRequestHeader("Content-Type", "application/json"); + request.timeout = 10; + + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + var response = JsonUtility.FromJson(request.downloadHandler.text); + serverId = response.server_id; + estEnregistre = true; + _echecsConsecutifs = 0; + dernierStatut = $"Enregistre ({serverId})"; + Debug.Log($"[MasterServer] Serveur enregistre : {nom} → ID={serverId}"); + + // Demarrer le heartbeat (une seule coroutine active a la fois) + if (_heartbeatCoroutine != null) + StopCoroutine(_heartbeatCoroutine); + _heartbeatCoroutine = StartCoroutine(HeartbeatLoop()); + } + else + { + dernierStatut = $"Erreur enregistrement : {request.error}"; + Debug.LogWarning($"[MasterServer] Erreur register : {request.error} (code {request.responseCode})"); + } + + requeteEnCours = false; + request.Dispose(); + } + + private IEnumerator Unregister() + { + requeteEnCours = true; + dernierStatut = "Desenregistrement..."; + + if (_heartbeatCoroutine != null) + { + StopCoroutine(_heartbeatCoroutine); + _heartbeatCoroutine = null; + } + + var request = UnityWebRequest.Delete($"{masterServerUrl}/unregister/{serverId}"); + request.timeout = 5; + + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + Debug.Log($"[MasterServer] Serveur desenregistre : {serverId}"); + dernierStatut = "Serveur retire de la liste"; + } + else + { + Debug.LogWarning($"[MasterServer] Erreur unregister : {request.error}"); + dernierStatut = "Erreur desenregistrement (le cleanup nettoiera)"; + } + + estEnregistre = false; + serverId = ""; + requeteEnCours = false; + request.Dispose(); + } + + private IEnumerator HeartbeatLoop() + { + while (estEnregistre && !string.IsNullOrEmpty(serverId)) + { + yield return new WaitForSecondsRealtime(heartbeatInterval); + + if (!estEnregistre || string.IsNullOrEmpty(serverId)) + yield break; + + int nbJoueurs = NetworkServer.active ? NetworkServer.connections.Count : 0; + + var body = new HeartbeatRequest + { + joueurs = nbJoueurs, + token = token + }; + + string json = JsonUtility.ToJson(body); + byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(json); + + var request = new UnityWebRequest( + $"{masterServerUrl}/heartbeat/{serverId}", "PUT"); + request.uploadHandler = new UploadHandlerRaw(bodyRaw); + request.downloadHandler = new DownloadHandlerBuffer(); + request.SetRequestHeader("Content-Type", "application/json"); + request.timeout = 10; + + yield return request.SendWebRequest(); + + if (request.result == UnityWebRequest.Result.Success) + { + // Reset du compteur d'echecs + _echecsConsecutifs = 0; + } + else + { + _echecsConsecutifs++; + long code = request.responseCode; + string err = request.error; + Debug.LogWarning($"[MasterServer] Heartbeat echoue ({_echecsConsecutifs}/{maxEchecsConsecutifs}) : HTTP {code} - {err}"); + + // v6.6 : si 404, le master server nous a purges → on re-register AUTOMATIQUEMENT + // et IMMEDIATEMENT, sans attendre le prochain cycle de heartbeat + if (code == 404) + { + Debug.Log("[MasterServer] Serveur inconnu (404), re-enregistrement automatique..."); + request.Dispose(); + + // Reset de l'etat + estEnregistre = false; + string ancienId = serverId; + serverId = ""; + + // Relance Register() avec les parametres memorises + yield return StartCoroutine(Register( + _dernierNom, _dernierPort, _dernierMaxJoueurs, + _derniereLangue, _dernierMode, _derniereVersion, _dernierMotDePasse + )); + + if (estEnregistre) + { + Debug.Log($"[MasterServer] Re-enregistrement OK : {ancienId} → {serverId}"); + // Le HeartbeatLoop continue, mais on vient de demarrer un nouveau via Register. + // On sort de cette boucle pour eviter d'en avoir deux en parallele. + yield break; + } + else + { + Debug.LogError($"[MasterServer] Re-enregistrement echoue, abandon du heartbeat"); + yield break; + } + } + + // Apres trop d'echecs consecutifs (pas 404 mais reseau/timeout/etc), abandon + if (_echecsConsecutifs >= maxEchecsConsecutifs) + { + Debug.LogError($"[MasterServer] Abandon du heartbeat apres {maxEchecsConsecutifs} echecs consecutifs"); + estEnregistre = false; + yield break; + } + } + + request.Dispose(); + } + } + + private IEnumerator FetchServers(System.Action, string> callback) + { + requeteEnCours = true; + dernierStatut = "Recherche de serveurs..."; + + float startTime = Time.realtimeSinceStartup; + + var request = UnityWebRequest.Get($"{masterServerUrl}/servers"); + request.timeout = 10; + + yield return request.SendWebRequest(); + + int pingMs = Mathf.RoundToInt((Time.realtimeSinceStartup - startTime) * 1000f); + + if (request.result == UnityWebRequest.Result.Success) + { + string responseText = request.downloadHandler.text; + string wrapped = "{\"servers\":" + responseText + "}"; + var wrapper = JsonUtility.FromJson(wrapped); + + derniereListeServeurs = wrapper.servers ?? new List(); + + foreach (var srv in derniereListeServeurs) + srv.ping = pingMs; + + dernierStatut = $"{derniereListeServeurs.Count} serveur(s) trouve(s)"; + Debug.Log($"[MasterServer] {derniereListeServeurs.Count} serveurs recuperes (ping={pingMs}ms)"); + + callback?.Invoke(derniereListeServeurs, null); + } + else + { + string erreur = $"Erreur : {request.error}"; + dernierStatut = erreur; + Debug.LogWarning($"[MasterServer] Erreur fetch : {request.error}"); + callback?.Invoke(new List(), erreur); + } + + requeteEnCours = false; + request.Dispose(); + } + + private IEnumerator CheckHealth(System.Action callback) + { + var request = UnityWebRequest.Get($"{masterServerUrl}/health"); + request.timeout = 5; + + yield return request.SendWebRequest(); + + bool ok = request.result == UnityWebRequest.Result.Success; + callback?.Invoke(ok); + request.Dispose(); + } +} diff --git a/README_PATCHES_V2.md b/README_PATCHES_V2.md new file mode 100644 index 0000000..fad1790 --- /dev/null +++ b/README_PATCHES_V2.md @@ -0,0 +1,47 @@ +# Patches v2 - Correction des problèmes du premier déploiement + +## Ce qui est dans ce ZIP + +### Scripts modifiés (2) + +- **`MasterServerClient.cs` v6.6** + - Heartbeat passe de 30s → 10s par défaut + - Sur 404, re-registration automatique IMMÉDIATE (mémoise les paramètres) + - Compteur d'échecs consécutifs (abandon après 3) + +- **`DebugFalling.cs`** + - Marqué `IClientOnly` → détruit au boot serveur, zéro log parasite + +## Actions manuelles dans Unity (à faire AVANT de rebuild) + +### 1. Remplacer les 2 scripts +Copie les 2 `.cs` dans `Assets/Scripts/`, écrase les fichiers existants. + +### 2. Assigner le catalogue à BoutiqueReseau (Inspector) + +Dans la scène `Datacenter_01` : +1. Sélectionne le GameObject qui porte le composant `BoutiqueReseau` (probablement sur le même GameObject que le `NetworkManager` ou sur un "Managers") +2. Dans l'Inspector, déplie le composant `BoutiqueReseau` +3. Champ **Catalogue** → clique sur le petit triangle pour déplier, mets la taille à X +4. Glisse-dépose tes `ArticleCatalogue` ScriptableObjects depuis le Project Window vers les slots +5. Sauvegarde la scène (Ctrl+S) + +💡 **Pour retrouver tes articles plus rapidement** : sélectionne l'objet qui porte `UIBoutique` actuellement et regarde son champ `catalogue`, tu dois y voir tous les ScriptableObjects. Note-les ou fais un drag multi-sélection. + +### 3. Rebuild + +Build settings → Linux Dedicated Server → Build → déploie sur la Debian. + +## Effet attendu + +Après ces patches, plus de : +- ❌ `[MasterServer] Heartbeat échoué : HTTP/1.1 404 Not Found` + → Résolu par re-registration automatique + intervalle réduit à 10s + +- ❌ `[BoutiqueReseau] Article introuvable : Baie 42U` + → Résolu par l'assignation directe du catalogue dans l'Inspector + +- ❌ Spam de logs `[DebugFall] ...` + → Résolu par le marqueur IClientOnly + +Logs serveur propres = monitoring plus efficace.