Téléverser les fichiers vers "/"

This commit is contained in:
smauro 2026-04-17 20:40:46 +00:00
parent fd4deaaa07
commit b052a0ef10
3 changed files with 563 additions and 0 deletions

94
DebugFalling.cs Normal file
View File

@ -0,0 +1,94 @@
using UnityEngine;
/// <summary>
/// TEMPORAIRE - Debug pour diagnostiquer le problème de chute sous la map.
/// Ajouter sur le prefab PlayerCapsule, supprimer après diagnostic.
/// </summary>
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<CharacterController>();
_fpc = GetComponent<StarterAssets.FirstPersonController>();
_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;
}
}

422
MasterServerClient.cs Normal file
View File

@ -0,0 +1,422 @@
using UnityEngine;
using UnityEngine.Networking;
using System;
using System.Collections;
using System.Collections.Generic;
using Mirror;
/// <summary>
/// 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.
/// </summary>
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<ServerData> derniereListeServeurs = new List<ServerData>();
[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<ServerData> 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<List<ServerData>, string> callback)
{
StartCoroutine(FetchServers(callback));
}
public void VerifierConnexion(System.Action<bool> 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<RegisterResponse>(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<List<ServerData>, 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<ServerListWrapper>(wrapped);
derniereListeServeurs = wrapper.servers ?? new List<ServerData>();
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<ServerData>(), erreur);
}
requeteEnCours = false;
request.Dispose();
}
private IEnumerator CheckHealth(System.Action<bool> 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();
}
}

47
README_PATCHES_V2.md Normal file
View File

@ -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.