Init
This commit is contained in:
589
Assets/UnityTestClient/UnityTestClient_Stats.cs
Normal file
589
Assets/UnityTestClient/UnityTestClient_Stats.cs
Normal file
@@ -0,0 +1,589 @@
|
||||
/*
|
||||
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ STATISTIKY A HTTP API - UnityTestClient_Stats.cs ║
|
||||
║ ║
|
||||
║ Tento soubor obsahuje komunikaci s HTTP Stats API serveru: ║
|
||||
║ • Načítání statistik hráče ║
|
||||
║ • Načítání leaderboardu ║
|
||||
║ • Health check serveru ║
|
||||
║ ║
|
||||
║ API ENDPOINTY (port 8088): ║
|
||||
║ • GET /stats/{playerId} - statistiky konkrétního hráče ║
|
||||
║ • GET /leaderboard?limit=N - top N hráčů podle win rate ║
|
||||
║ • GET /health - stav serveru (aktivní lobby, hráči) ║
|
||||
║ ║
|
||||
║ STATISTIKY ZAHRNUJÍ: ║
|
||||
║ • Počet her, výher, proher ║
|
||||
║ • Win rate celkově, jako impostor, jako crew ║
|
||||
║ • Počet zabití, smrtí, K/D ratio ║
|
||||
║ • Dokončené úkoly ║
|
||||
║ • Přesnost hlasování (správně odhalení impostoři) ║
|
||||
║ ║
|
||||
║ POZNÁMKA PRO STUDENTY: ║
|
||||
║ Unity používá UnityWebRequest pro HTTP komunikaci. ║
|
||||
║ Všechny requesty musí běžet jako coroutiny (StartCoroutine). ║
|
||||
║ JSON parsing: JsonUtility (jednodušší) nebo Newtonsoft.Json (flexibilnější). ║
|
||||
║ ║
|
||||
║ Pro vlastní hru můžete rozšířit statistiky o: ║
|
||||
║ • Achievements/Badges ║
|
||||
║ • Sezónní ranky ║
|
||||
║ • Historii her ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
|
||||
*/
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public partial class UnityTestClient
|
||||
{
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// STATS PROMĚNNÉ
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>Port pro Stats HTTP API</summary>
|
||||
protected int statsApiPort = 8088;
|
||||
|
||||
/// <summary>URL pro Stats API (automaticky používá HTTPS pokud je povoleno)</summary>
|
||||
protected string StatsApiUrl => useHttps
|
||||
? $"https://{serverHost}"
|
||||
: $"http://{serverHost}:{statsApiPort}";
|
||||
|
||||
/// <summary>Načtené statistiky hráče</summary>
|
||||
protected PlayerStatistics playerStats = null;
|
||||
|
||||
/// <summary>Načtený leaderboard</summary>
|
||||
protected List<LeaderboardEntry> leaderboard = null;
|
||||
|
||||
/// <summary>Stav zdraví serveru</summary>
|
||||
protected ServerHealthStatus healthStatus = null;
|
||||
|
||||
/// <summary>Probíhá načítání statistik?</summary>
|
||||
protected bool isLoadingStats = false;
|
||||
|
||||
/// <summary>Probíhá načítání leaderboardu?</summary>
|
||||
protected bool isLoadingLeaderboard = false;
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// DATOVÉ STRUKTURY
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Statistiky hráče.
|
||||
/// Mapuje se z JSON response z /stats/{playerId}.
|
||||
/// POZOR: Názvy polí musí přesně odpovídat JSON z serveru!
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class PlayerStatistics
|
||||
{
|
||||
/// <summary>UUID hráče</summary>
|
||||
public string clientUuid;
|
||||
|
||||
/// <summary>Zobrazované jméno</summary>
|
||||
public string displayName;
|
||||
|
||||
/// <summary>Celkový počet odehraných her</summary>
|
||||
public int totalGames;
|
||||
|
||||
/// <summary>Počet her jako crew</summary>
|
||||
public int gamesAsCrew;
|
||||
|
||||
/// <summary>Počet her jako impostor</summary>
|
||||
public int gamesAsImpostor;
|
||||
|
||||
/// <summary>Počet výher jako crew</summary>
|
||||
public int crewWins;
|
||||
|
||||
/// <summary>Počet výher jako impostor</summary>
|
||||
public int impostorWins;
|
||||
|
||||
/// <summary>Win rate jako crew (0-1)</summary>
|
||||
public float crewWinRate;
|
||||
|
||||
/// <summary>Win rate jako impostor (0-1)</summary>
|
||||
public float impostorWinRate;
|
||||
|
||||
/// <summary>Počet zabití (jako impostor)</summary>
|
||||
public int totalKills;
|
||||
|
||||
/// <summary>Počet smrtí</summary>
|
||||
public int totalDeaths;
|
||||
|
||||
/// <summary>Kill/Death ratio</summary>
|
||||
public float killDeathRatio;
|
||||
|
||||
/// <summary>Počet dokončených úkolů</summary>
|
||||
public int tasksCompleted;
|
||||
|
||||
/// <summary>Průměrný počet tasků za hru</summary>
|
||||
public float averageTasksPerGame;
|
||||
|
||||
/// <summary>Počet nahlášených těl</summary>
|
||||
public int bodiesReported;
|
||||
|
||||
/// <summary>Počet svolaných emergency meetingů</summary>
|
||||
public int emergencyMeetingsCalled;
|
||||
|
||||
/// <summary>Kolikrát byl vyhozen hlasováním</summary>
|
||||
public int timesVotedOut;
|
||||
|
||||
/// <summary>Počet úspěšných hlasů (správně identifikovaný impostor)</summary>
|
||||
public int successfulVotes;
|
||||
|
||||
/// <summary>Celkový čas hraní v sekundách</summary>
|
||||
public long totalPlaytimeSeconds;
|
||||
|
||||
/// <summary>Počet cheat incidentů</summary>
|
||||
public int cheatIncidents;
|
||||
|
||||
/// <summary>Kdy byl naposledy viděn</summary>
|
||||
public string lastSeen;
|
||||
|
||||
// Pomocné vlastnosti pro zobrazení v UI
|
||||
public int GamesWon => crewWins + impostorWins;
|
||||
public float WinRate => totalGames > 0 ? (float)GamesWon / totalGames : 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Položka leaderboardu.
|
||||
/// POZOR: Názvy polí musí přesně odpovídat JSON z serveru!
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class LeaderboardEntry
|
||||
{
|
||||
/// <summary>UUID hráče</summary>
|
||||
public string clientUuid;
|
||||
|
||||
/// <summary>Zobrazované jméno</summary>
|
||||
public string displayName;
|
||||
|
||||
/// <summary>Celkový počet her</summary>
|
||||
public int totalGames;
|
||||
|
||||
/// <summary>Výhry jako crew</summary>
|
||||
public int crewWins;
|
||||
|
||||
/// <summary>Výhry jako impostor</summary>
|
||||
public int impostorWins;
|
||||
|
||||
/// <summary>Počet zabití</summary>
|
||||
public int totalKills;
|
||||
|
||||
/// <summary>Počet dokončených tasků</summary>
|
||||
public int tasksCompleted;
|
||||
|
||||
// Pomocné vlastnosti
|
||||
public int TotalWins => crewWins + impostorWins;
|
||||
public float WinRate => totalGames > 0 ? (float)TotalWins / totalGames : 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper pro leaderboard response (JSON array).
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class LeaderboardResponse
|
||||
{
|
||||
public List<LeaderboardEntry> entries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stav serveru.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class ServerHealthStatus
|
||||
{
|
||||
/// <summary>Stav: "healthy" nebo "unhealthy"</summary>
|
||||
public string status;
|
||||
|
||||
/// <summary>Verze serveru</summary>
|
||||
public string version;
|
||||
|
||||
/// <summary>Uptime v sekundách</summary>
|
||||
public long uptimeSeconds;
|
||||
|
||||
/// <summary>Počet aktivních lobby</summary>
|
||||
public int activeLobbies;
|
||||
|
||||
/// <summary>Počet připojených hráčů</summary>
|
||||
public int connectedPlayers;
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// NAČÍTÁNÍ STATISTIK
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Spustí načítání statistik hráče.
|
||||
/// Výsledek bude v playerStats.
|
||||
/// </summary>
|
||||
protected void FetchPlayerStats()
|
||||
{
|
||||
Debug.Log($"[Stats] FetchPlayerStats called. isLoadingStats={isLoadingStats}, clientUuid={clientUuid}");
|
||||
|
||||
if (isLoadingStats)
|
||||
{
|
||||
Debug.Log("[Stats] Already loading, skipping");
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrEmpty(clientUuid))
|
||||
{
|
||||
Debug.Log("[Stats] clientUuid is empty, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[Stats] Starting coroutine for player {clientUuid}");
|
||||
StartCoroutine(FetchPlayerStatsCoroutine(clientUuid));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine pro načtení statistik hráče.
|
||||
///
|
||||
/// POZNÁMKA PRO STUDENTY:
|
||||
/// Coroutiny v Unity umožňují asynchronní operace.
|
||||
/// yield return čeká na dokončení operace před pokračováním.
|
||||
/// UnityWebRequest.Get vytváří HTTP GET request.
|
||||
/// </summary>
|
||||
/// <param name="playerId">UUID hráče</param>
|
||||
private IEnumerator FetchPlayerStatsCoroutine(string playerId)
|
||||
{
|
||||
isLoadingStats = true;
|
||||
|
||||
string url = $"{StatsApiUrl}/stats/{playerId}";
|
||||
Debug.Log($"[Stats] Fetching from: {url}");
|
||||
|
||||
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
||||
{
|
||||
// Nastavení timeoutu
|
||||
request.timeout = 10;
|
||||
|
||||
// Odeslání requestu a čekání na odpověď
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
Debug.Log($"[Stats] Request completed. Result: {request.result}, Code: {request.responseCode}");
|
||||
|
||||
// Kontrola chyb
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
// Parsování JSON
|
||||
try
|
||||
{
|
||||
string json = request.downloadHandler.text;
|
||||
playerStats = JsonUtility.FromJson<PlayerStatistics>(json);
|
||||
|
||||
Debug.Log($"[Stats] Loaded stats for {playerStats.displayName}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[Stats] Failed to parse stats: {e.Message}");
|
||||
playerStats = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chyba při requestu
|
||||
Debug.LogWarning($"[Stats] Failed to fetch stats: {request.error}");
|
||||
|
||||
// Pokud hráč neexistuje (404), vytvoříme prázdné statistiky
|
||||
if (request.responseCode == 404)
|
||||
{
|
||||
playerStats = new PlayerStatistics
|
||||
{
|
||||
clientUuid = playerId,
|
||||
displayName = displayName,
|
||||
totalGames = 0,
|
||||
gamesAsCrew = 0,
|
||||
gamesAsImpostor = 0,
|
||||
crewWins = 0,
|
||||
impostorWins = 0,
|
||||
crewWinRate = 0,
|
||||
impostorWinRate = 0,
|
||||
totalKills = 0,
|
||||
totalDeaths = 0,
|
||||
killDeathRatio = 0,
|
||||
tasksCompleted = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isLoadingStats = false;
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// LEADERBOARD
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Spustí načítání leaderboardu.
|
||||
/// </summary>
|
||||
/// <param name="limit">Počet hráčů k načtení (max 100)</param>
|
||||
protected void FetchLeaderboard(int limit = 10)
|
||||
{
|
||||
if (isLoadingLeaderboard) return;
|
||||
|
||||
StartCoroutine(FetchLeaderboardCoroutine(limit));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine pro načtení leaderboardu.
|
||||
/// </summary>
|
||||
private IEnumerator FetchLeaderboardCoroutine(int limit)
|
||||
{
|
||||
isLoadingLeaderboard = true;
|
||||
|
||||
string url = $"{StatsApiUrl}/leaderboard?limit={limit}";
|
||||
|
||||
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.timeout = 10;
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = request.downloadHandler.text;
|
||||
|
||||
// JsonUtility nemá přímou podporu pro arrays, potřebujeme wrapper
|
||||
// nebo použijeme jednoduchý workaround
|
||||
string wrappedJson = "{\"entries\":" + json + "}";
|
||||
LeaderboardResponse response = JsonUtility.FromJson<LeaderboardResponse>(wrappedJson);
|
||||
leaderboard = response.entries;
|
||||
|
||||
Debug.Log($"[Stats] Loaded leaderboard with {leaderboard.Count} entries");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[Stats] Failed to parse leaderboard: {e.Message}");
|
||||
leaderboard = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[Stats] Failed to fetch leaderboard: {request.error}");
|
||||
}
|
||||
}
|
||||
|
||||
isLoadingLeaderboard = false;
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// HEALTH CHECK
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Spustí health check serveru.
|
||||
/// </summary>
|
||||
protected void CheckServerHealth()
|
||||
{
|
||||
StartCoroutine(CheckServerHealthCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine pro health check.
|
||||
/// </summary>
|
||||
private IEnumerator CheckServerHealthCoroutine()
|
||||
{
|
||||
string url = $"{StatsApiUrl}/health";
|
||||
|
||||
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.timeout = 5;
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = request.downloadHandler.text;
|
||||
healthStatus = JsonUtility.FromJson<ServerHealthStatus>(json);
|
||||
|
||||
Debug.Log($"[Stats] Server health: {healthStatus.status}, " +
|
||||
$"uptime: {healthStatus.uptimeSeconds}s, " +
|
||||
$"lobbies: {healthStatus.activeLobbies}, " +
|
||||
$"players: {healthStatus.connectedPlayers}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[Stats] Failed to parse health: {e.Message}");
|
||||
healthStatus = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[Stats] Health check failed: {request.error}");
|
||||
healthStatus = new ServerHealthStatus
|
||||
{
|
||||
status = "unreachable",
|
||||
version = "unknown"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// UI PRO LEADERBOARD
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Vykreslení leaderboard panelu.
|
||||
/// Volá se z DrawStatsTab().
|
||||
/// </summary>
|
||||
protected void DrawLeaderboardPanel()
|
||||
{
|
||||
if (leaderboard == null)
|
||||
{
|
||||
GUILayout.Label("Načítám leaderboard...", labelStyle);
|
||||
|
||||
if (GUILayout.Button("Načíst", buttonStyle))
|
||||
{
|
||||
FetchLeaderboard(10);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label("TOP HRÁČI", subtitleStyle);
|
||||
GUILayout.Space(5);
|
||||
|
||||
// Hlavička
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("#", GUILayout.Width(30));
|
||||
GUILayout.Label("Jméno", GUILayout.Width(150));
|
||||
GUILayout.Label("Skóre", GUILayout.Width(80));
|
||||
GUILayout.Label("Win%", GUILayout.Width(60));
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Položky
|
||||
int rank = 1;
|
||||
foreach (var entry in leaderboard)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
// Speciální barvy pro top 3
|
||||
if (rank == 1) GUI.color = Color.yellow;
|
||||
else if (rank == 2) GUI.color = new Color(0.8f, 0.8f, 0.8f);
|
||||
else if (rank == 3) GUI.color = new Color(0.8f, 0.5f, 0.2f);
|
||||
else GUI.color = Color.white;
|
||||
|
||||
GUILayout.Label($"{rank}.", GUILayout.Width(30));
|
||||
GUILayout.Label(entry.displayName, GUILayout.Width(150));
|
||||
GUILayout.Label(entry.TotalWins.ToString(), GUILayout.Width(80));
|
||||
GUILayout.Label($"{entry.WinRate * 100:F0}%", GUILayout.Width(60));
|
||||
|
||||
GUI.color = Color.white;
|
||||
GUILayout.EndHorizontal();
|
||||
rank++;
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("Obnovit", buttonStyle))
|
||||
{
|
||||
leaderboard = null;
|
||||
FetchLeaderboard(10);
|
||||
}
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// UI PRO SERVER HEALTH
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Vykreslení panelu stavu serveru.
|
||||
/// </summary>
|
||||
protected void DrawServerHealthPanel()
|
||||
{
|
||||
GUILayout.BeginVertical(boxStyle);
|
||||
|
||||
GUILayout.Label("STAV SERVERU", subtitleStyle);
|
||||
|
||||
if (healthStatus == null)
|
||||
{
|
||||
GUILayout.Label("Neznámý", labelStyle);
|
||||
|
||||
if (GUILayout.Button("Zkontrolovat", buttonStyle))
|
||||
{
|
||||
CheckServerHealth();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Status
|
||||
if (healthStatus.status == "healthy")
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
GUILayout.Label("● Online", labelStyle);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.red;
|
||||
GUILayout.Label("● Offline", labelStyle);
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
|
||||
// Detaily
|
||||
GUILayout.Label($"Verze: {healthStatus.version}", labelStyle);
|
||||
|
||||
// Uptime
|
||||
TimeSpan uptime = TimeSpan.FromSeconds(healthStatus.uptimeSeconds);
|
||||
GUILayout.Label($"Uptime: {uptime.Days}d {uptime.Hours}h {uptime.Minutes}m", labelStyle);
|
||||
|
||||
// Aktivita
|
||||
GUILayout.Label($"Lobby: {healthStatus.activeLobbies}", labelStyle);
|
||||
GUILayout.Label($"Hráči: {healthStatus.connectedPlayers}", labelStyle);
|
||||
|
||||
GUILayout.Space(5);
|
||||
|
||||
if (GUILayout.Button("Obnovit", buttonStyle))
|
||||
{
|
||||
healthStatus = null;
|
||||
CheckServerHealth();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#region ═══════════════════════════════════════════════════════════════════
|
||||
// AUTOMATICKÉ NAČÍTÁNÍ
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Interval automatického obnovení statistik (sekundy)
|
||||
/// </summary>
|
||||
protected float statsRefreshInterval = 60f;
|
||||
|
||||
/// <summary>Čas posledního obnovení</summary>
|
||||
protected float lastStatsRefresh = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Automatické načítání statistik.
|
||||
/// Volá se v Update().
|
||||
/// </summary>
|
||||
protected void UpdateStatsAutoRefresh()
|
||||
{
|
||||
if (currentState != AppState.MainMenu) return;
|
||||
if (string.IsNullOrEmpty(clientUuid)) return;
|
||||
|
||||
if (Time.time - lastStatsRefresh >= statsRefreshInterval)
|
||||
{
|
||||
FetchPlayerStats();
|
||||
CheckServerHealth();
|
||||
lastStatsRefresh = Time.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user