423 lines
15 KiB
C#
423 lines
15 KiB
C#
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();
|
|
}
|
|
}
|