Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
600 lines
20 KiB
C#
600 lines
20 KiB
C#
// ==============================================
|
|
// BreakpilotAPI.cs - Unity API Client
|
|
// ==============================================
|
|
// Kopiere diese Datei nach Assets/Scripts/Network/
|
|
// um mit dem Breakpilot Backend zu kommunizieren.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
|
|
namespace BreakpilotDrive
|
|
{
|
|
[Serializable]
|
|
public class LearningLevel
|
|
{
|
|
public string user_id;
|
|
public int overall_level;
|
|
public float math_level;
|
|
public float german_level;
|
|
public float english_level;
|
|
}
|
|
|
|
[Serializable]
|
|
public class GameDifficulty
|
|
{
|
|
public float lane_speed;
|
|
public float obstacle_frequency;
|
|
public float power_up_chance;
|
|
public int question_complexity;
|
|
public int answer_time;
|
|
public bool hints_enabled;
|
|
public float speech_speed;
|
|
}
|
|
|
|
[Serializable]
|
|
public class QuizQuestion
|
|
{
|
|
public string id;
|
|
public string question_text;
|
|
public string audio_url;
|
|
public string[] options;
|
|
public int correct_index;
|
|
public int difficulty;
|
|
public string subject;
|
|
public int grade_level;
|
|
public string quiz_mode; // "quick" oder "pause"
|
|
public string visual_trigger; // z.B. "bridge"
|
|
public float time_limit_seconds;
|
|
}
|
|
|
|
[Serializable]
|
|
public class QuizQuestionList
|
|
{
|
|
public QuizQuestion[] questions;
|
|
}
|
|
|
|
[Serializable]
|
|
public class GameSession
|
|
{
|
|
public string user_id;
|
|
public string game_mode; // "video" oder "audio"
|
|
public int duration_seconds;
|
|
public float distance_traveled;
|
|
public int score;
|
|
public int questions_answered;
|
|
public int questions_correct;
|
|
public int difficulty_level;
|
|
}
|
|
|
|
[Serializable]
|
|
public class SessionResponse
|
|
{
|
|
public string session_id;
|
|
public string status;
|
|
public int? new_level;
|
|
}
|
|
|
|
[Serializable]
|
|
public class VisualTrigger
|
|
{
|
|
public string trigger;
|
|
public int question_count;
|
|
public int[] difficulties;
|
|
public string[] subjects;
|
|
}
|
|
|
|
[Serializable]
|
|
public class ProgressData
|
|
{
|
|
public string date;
|
|
public int sessions;
|
|
public int total_score;
|
|
public int questions;
|
|
public int correct;
|
|
public float accuracy;
|
|
public float avg_difficulty;
|
|
}
|
|
|
|
[Serializable]
|
|
public class ProgressResponse
|
|
{
|
|
public string user_id;
|
|
public int days;
|
|
public int data_points;
|
|
public ProgressData[] progress;
|
|
}
|
|
|
|
[Serializable]
|
|
public class LeaderboardEntry
|
|
{
|
|
public int rank;
|
|
public string user_id;
|
|
public int total_score;
|
|
public string display_name;
|
|
}
|
|
|
|
public class BreakpilotAPI : MonoBehaviour
|
|
{
|
|
// Singleton Pattern
|
|
public static BreakpilotAPI Instance { get; private set; }
|
|
|
|
[Header("API Konfiguration")]
|
|
[SerializeField] private string baseUrl = "http://localhost:8000/api/game";
|
|
[SerializeField] private bool useOfflineCache = true;
|
|
|
|
// Cached Data
|
|
private LearningLevel cachedLevel;
|
|
private GameDifficulty cachedDifficulty;
|
|
private List<QuizQuestion> cachedQuestions = new List<QuizQuestion>();
|
|
private List<VisualTrigger> cachedTriggers = new List<VisualTrigger>();
|
|
|
|
// API URL property (for other scripts)
|
|
public string BaseUrl => baseUrl;
|
|
|
|
void Awake()
|
|
{
|
|
if (Instance == null)
|
|
{
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
}
|
|
else
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
// ==============================================
|
|
// Helper: Add Auth Header to Request
|
|
// ==============================================
|
|
private void AddAuthHeader(UnityWebRequest request)
|
|
{
|
|
if (AuthManager.Instance != null)
|
|
{
|
|
string authHeader = AuthManager.Instance.GetAuthHeader();
|
|
if (!string.IsNullOrEmpty(authHeader))
|
|
{
|
|
request.SetRequestHeader("Authorization", authHeader);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get current user ID (from auth or fallback)
|
|
public string GetCurrentUserId()
|
|
{
|
|
if (AuthManager.Instance != null && AuthManager.Instance.IsAuthenticated)
|
|
{
|
|
return AuthManager.Instance.UserId;
|
|
}
|
|
return "anonymous";
|
|
}
|
|
|
|
// ==============================================
|
|
// Lernniveau abrufen
|
|
// ==============================================
|
|
public IEnumerator GetLearningLevel(string userId, Action<LearningLevel> onSuccess, Action<string> onError = null)
|
|
{
|
|
string url = $"{baseUrl}/learning-level/{userId}";
|
|
|
|
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
|
{
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
LearningLevel level = JsonUtility.FromJson<LearningLevel>(request.downloadHandler.text);
|
|
cachedLevel = level;
|
|
onSuccess?.Invoke(level);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"API Fehler: {request.error}");
|
|
|
|
// Offline-Fallback
|
|
if (useOfflineCache && cachedLevel != null)
|
|
{
|
|
onSuccess?.Invoke(cachedLevel);
|
|
}
|
|
else
|
|
{
|
|
// Standard-Level
|
|
onSuccess?.Invoke(new LearningLevel { user_id = userId, overall_level = 3 });
|
|
}
|
|
|
|
onError?.Invoke(request.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==============================================
|
|
// Schwierigkeit abrufen
|
|
// ==============================================
|
|
public IEnumerator GetDifficulty(int level, Action<GameDifficulty> onSuccess, Action<string> onError = null)
|
|
{
|
|
string url = $"{baseUrl}/difficulty/{level}";
|
|
|
|
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
|
{
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
GameDifficulty difficulty = JsonUtility.FromJson<GameDifficulty>(request.downloadHandler.text);
|
|
cachedDifficulty = difficulty;
|
|
onSuccess?.Invoke(difficulty);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"API Fehler: {request.error}");
|
|
|
|
// Fallback: Default-Schwierigkeit
|
|
if (cachedDifficulty != null)
|
|
{
|
|
onSuccess?.Invoke(cachedDifficulty);
|
|
}
|
|
else
|
|
{
|
|
onSuccess?.Invoke(GetDefaultDifficulty(level));
|
|
}
|
|
|
|
onError?.Invoke(request.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==============================================
|
|
// Quiz-Fragen abrufen
|
|
// ==============================================
|
|
public IEnumerator GetQuizQuestions(
|
|
int difficulty,
|
|
int count,
|
|
string mode = null, // "quick", "pause", oder null fuer beide
|
|
string subject = null,
|
|
Action<QuizQuestion[]> onSuccess = null,
|
|
Action<string> onError = null)
|
|
{
|
|
string url = $"{baseUrl}/quiz/questions?difficulty={difficulty}&count={count}";
|
|
if (!string.IsNullOrEmpty(mode)) url += $"&mode={mode}";
|
|
if (!string.IsNullOrEmpty(subject)) url += $"&subject={subject}";
|
|
|
|
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
|
{
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
// Unity's JsonUtility braucht Wrapper fuer Arrays
|
|
string wrappedJson = "{\"questions\":" + request.downloadHandler.text + "}";
|
|
QuizQuestionList list = JsonUtility.FromJson<QuizQuestionList>(wrappedJson);
|
|
|
|
// Cache updaten
|
|
if (list.questions != null)
|
|
{
|
|
cachedQuestions.AddRange(list.questions);
|
|
}
|
|
|
|
onSuccess?.Invoke(list.questions);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"API Fehler: {request.error}");
|
|
|
|
// Offline-Fallback
|
|
if (useOfflineCache && cachedQuestions.Count > 0)
|
|
{
|
|
var filtered = cachedQuestions.FindAll(q =>
|
|
Math.Abs(q.difficulty - difficulty) <= 1 &&
|
|
(string.IsNullOrEmpty(mode) || q.quiz_mode == mode)
|
|
);
|
|
onSuccess?.Invoke(filtered.GetRange(0, Math.Min(count, filtered.Count)).ToArray());
|
|
}
|
|
|
|
onError?.Invoke(request.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==============================================
|
|
// Visuelle Trigger abrufen
|
|
// ==============================================
|
|
public IEnumerator GetVisualTriggers(Action<VisualTrigger[]> onSuccess, Action<string> onError = null)
|
|
{
|
|
string url = $"{baseUrl}/quiz/visual-triggers";
|
|
|
|
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
|
{
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
// Parse Array
|
|
string wrappedJson = "{\"triggers\":" + request.downloadHandler.text + "}";
|
|
VisualTriggersWrapper wrapper = JsonUtility.FromJson<VisualTriggersWrapper>(wrappedJson);
|
|
cachedTriggers = new List<VisualTrigger>(wrapper.triggers);
|
|
onSuccess?.Invoke(wrapper.triggers);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"API Fehler: {request.error}");
|
|
onError?.Invoke(request.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
private class VisualTriggersWrapper
|
|
{
|
|
public VisualTrigger[] triggers;
|
|
}
|
|
|
|
// ==============================================
|
|
// Spielsession speichern
|
|
// ==============================================
|
|
public IEnumerator SaveGameSession(GameSession session, Action<SessionResponse> onSuccess = null, Action<string> onError = null)
|
|
{
|
|
string url = $"{baseUrl}/session";
|
|
string json = JsonUtility.ToJson(session);
|
|
|
|
using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
|
|
{
|
|
byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(json);
|
|
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
|
request.downloadHandler = new DownloadHandlerBuffer();
|
|
request.SetRequestHeader("Content-Type", "application/json");
|
|
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
SessionResponse response = JsonUtility.FromJson<SessionResponse>(request.downloadHandler.text);
|
|
onSuccess?.Invoke(response);
|
|
|
|
// Bei Level-Aenderung cachedLevel updaten
|
|
if (response.new_level.HasValue && cachedLevel != null)
|
|
{
|
|
cachedLevel.overall_level = response.new_level.Value;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Session speichern fehlgeschlagen: {request.error}");
|
|
|
|
// Offline: Spaeter synchronisieren
|
|
if (useOfflineCache)
|
|
{
|
|
QueueOfflineSession(session);
|
|
}
|
|
|
|
onError?.Invoke(request.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==============================================
|
|
// Offline-Handling
|
|
// ==============================================
|
|
private List<GameSession> offlineSessions = new List<GameSession>();
|
|
|
|
private void QueueOfflineSession(GameSession session)
|
|
{
|
|
offlineSessions.Add(session);
|
|
// Spaeter: PlayerPrefs oder lokale DB nutzen
|
|
Debug.Log($"Session zur Offline-Queue hinzugefuegt. Queue-Groesse: {offlineSessions.Count}");
|
|
}
|
|
|
|
public IEnumerator SyncOfflineSessions()
|
|
{
|
|
var sessionsToSync = new List<GameSession>(offlineSessions);
|
|
offlineSessions.Clear();
|
|
|
|
foreach (var session in sessionsToSync)
|
|
{
|
|
yield return SaveGameSession(session,
|
|
onSuccess: (response) => Debug.Log($"Offline-Session synchronisiert: {response.session_id}"),
|
|
onError: (error) => offlineSessions.Add(session) // Zurueck in Queue
|
|
);
|
|
}
|
|
}
|
|
|
|
// ==============================================
|
|
// Helper
|
|
// ==============================================
|
|
private GameDifficulty GetDefaultDifficulty(int level)
|
|
{
|
|
// Fallback-Werte wenn API nicht erreichbar
|
|
return new GameDifficulty
|
|
{
|
|
lane_speed = 3f + level,
|
|
obstacle_frequency = 0.3f + (level * 0.1f),
|
|
power_up_chance = 0.4f - (level * 0.05f),
|
|
question_complexity = level,
|
|
answer_time = 15 - (level * 2),
|
|
hints_enabled = level <= 3,
|
|
speech_speed = 0.8f + (level * 0.1f)
|
|
};
|
|
}
|
|
|
|
// ==============================================
|
|
// Frage fuer visuellen Trigger holen
|
|
// ==============================================
|
|
public QuizQuestion GetQuestionForTrigger(string triggerType, int difficulty)
|
|
{
|
|
var matching = cachedQuestions.FindAll(q =>
|
|
q.quiz_mode == "quick" &&
|
|
q.visual_trigger == triggerType &&
|
|
Math.Abs(q.difficulty - difficulty) <= 1
|
|
);
|
|
|
|
if (matching.Count > 0)
|
|
{
|
|
return matching[UnityEngine.Random.Range(0, matching.Count)];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// ==============================================
|
|
// Cache-Zugriff
|
|
// ==============================================
|
|
public LearningLevel GetCachedLevel()
|
|
{
|
|
return cachedLevel;
|
|
}
|
|
|
|
public List<QuizQuestion> GetCachedQuestions()
|
|
{
|
|
return cachedQuestions;
|
|
}
|
|
|
|
public GameDifficulty GetCachedDifficulty()
|
|
{
|
|
return cachedDifficulty;
|
|
}
|
|
|
|
public int GetCachedQuestionsCount()
|
|
{
|
|
return cachedQuestions.Count;
|
|
}
|
|
|
|
public void ClearQuestionCache()
|
|
{
|
|
cachedQuestions.Clear();
|
|
}
|
|
|
|
// ==============================================
|
|
// Progress API (Phase 5)
|
|
// ==============================================
|
|
public IEnumerator GetProgress(
|
|
string userId,
|
|
int days,
|
|
Action<ProgressResponse> onSuccess,
|
|
Action<string> onError = null)
|
|
{
|
|
string url = $"{baseUrl}/progress/{userId}?days={days}";
|
|
|
|
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
|
{
|
|
AddAuthHeader(request);
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
var response = JsonUtility.FromJson<ProgressResponse>(request.downloadHandler.text);
|
|
onSuccess?.Invoke(response);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Progress API error: {request.error}");
|
|
onError?.Invoke(request.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==============================================
|
|
// Leaderboard API (Phase 5)
|
|
// ==============================================
|
|
public IEnumerator GetLeaderboard(
|
|
string timeframe,
|
|
int limit,
|
|
bool anonymize,
|
|
Action<LeaderboardEntry[]> onSuccess,
|
|
Action<string> onError = null)
|
|
{
|
|
string url = $"{baseUrl}/leaderboard/display?timeframe={timeframe}&limit={limit}&anonymize={anonymize.ToString().ToLower()}";
|
|
|
|
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
|
{
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
string wrappedJson = "{\"entries\":" + request.downloadHandler.text + "}";
|
|
var wrapper = JsonUtility.FromJson<LeaderboardWrapper>(wrappedJson);
|
|
onSuccess?.Invoke(wrapper.entries);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Leaderboard API error: {request.error}");
|
|
onError?.Invoke(request.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
private class LeaderboardWrapper
|
|
{
|
|
public LeaderboardEntry[] entries;
|
|
}
|
|
|
|
// ==============================================
|
|
// Stats API
|
|
// ==============================================
|
|
public IEnumerator GetUserStats(
|
|
string userId,
|
|
Action<UserStats> onSuccess,
|
|
Action<string> onError = null)
|
|
{
|
|
string url = $"{baseUrl}/stats/{userId}";
|
|
|
|
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
|
{
|
|
AddAuthHeader(request);
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
var stats = JsonUtility.FromJson<UserStats>(request.downloadHandler.text);
|
|
onSuccess?.Invoke(stats);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"Stats API error: {request.error}");
|
|
onError?.Invoke(request.error);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public class UserStats
|
|
{
|
|
public string user_id;
|
|
public int overall_level;
|
|
public float math_level;
|
|
public float german_level;
|
|
public float english_level;
|
|
public int total_play_time_minutes;
|
|
public int total_sessions;
|
|
public int questions_answered;
|
|
public int questions_correct;
|
|
public float accuracy;
|
|
}
|
|
|
|
// ==============================================
|
|
// Health Check
|
|
// ==============================================
|
|
public IEnumerator HealthCheck(Action<bool, string> onComplete)
|
|
{
|
|
string url = $"{baseUrl}/health";
|
|
|
|
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
|
{
|
|
request.timeout = 5;
|
|
yield return request.SendWebRequest();
|
|
|
|
if (request.result == UnityWebRequest.Result.Success)
|
|
{
|
|
onComplete?.Invoke(true, request.downloadHandler.text);
|
|
}
|
|
else
|
|
{
|
|
onComplete?.Invoke(false, request.error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|