fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
318
breakpilot-drive/UnityScripts/QuizManager.cs
Normal file
318
breakpilot-drive/UnityScripts/QuizManager.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
// ==============================================
|
||||
// QuizManager.cs - Quiz-Steuerung
|
||||
// ==============================================
|
||||
// Verwaltet Quick-Fragen (waehrend Fahrt) und
|
||||
// Pause-Fragen (Spiel haelt an).
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
namespace BreakpilotDrive
|
||||
{
|
||||
public class QuizManager : MonoBehaviour
|
||||
{
|
||||
public static QuizManager Instance { get; private set; }
|
||||
|
||||
[Header("UI Referenzen")]
|
||||
[SerializeField] private GameObject quickQuizPanel;
|
||||
[SerializeField] private GameObject pauseQuizPanel;
|
||||
[SerializeField] private TextMeshProUGUI questionText;
|
||||
[SerializeField] private Button[] answerButtons;
|
||||
[SerializeField] private TextMeshProUGUI timerText;
|
||||
[SerializeField] private Slider timerSlider;
|
||||
|
||||
[Header("Audio (fuer Audio-Version)")]
|
||||
[SerializeField] private AudioSource audioSource;
|
||||
[SerializeField] private bool isAudioMode = false;
|
||||
|
||||
[Header("Einstellungen")]
|
||||
[SerializeField] private int pointsCorrect = 500;
|
||||
[SerializeField] private int pointsWrong = -100;
|
||||
[SerializeField] private float pauseQuestionInterval = 500f; // Alle X Meter
|
||||
|
||||
// Aktueller Zustand
|
||||
private QuizQuestion currentQuestion;
|
||||
private bool isQuizActive = false;
|
||||
private float timeRemaining;
|
||||
private Coroutine timerCoroutine;
|
||||
|
||||
// Events
|
||||
public event Action<bool, int> OnQuestionAnswered; // (correct, points)
|
||||
public event Action OnQuizStarted;
|
||||
public event Action OnQuizEnded;
|
||||
|
||||
// Statistik
|
||||
private int questionsAnswered = 0;
|
||||
private int questionsCorrect = 0;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
// UI initial verstecken
|
||||
if (quickQuizPanel) quickQuizPanel.SetActive(false);
|
||||
if (pauseQuizPanel) pauseQuizPanel.SetActive(false);
|
||||
|
||||
// Answer Buttons einrichten
|
||||
for (int i = 0; i < answerButtons.Length; i++)
|
||||
{
|
||||
int index = i; // Capture fuer Lambda
|
||||
answerButtons[i].onClick.AddListener(() => OnAnswerSelected(index));
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Keyboard-Eingabe fuer Antworten (1-4)
|
||||
if (isQuizActive && currentQuestion != null)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1))
|
||||
OnAnswerSelected(0);
|
||||
else if (Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2))
|
||||
OnAnswerSelected(1);
|
||||
else if (Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3))
|
||||
OnAnswerSelected(2);
|
||||
else if (Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4))
|
||||
OnAnswerSelected(3);
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Quick Quiz (waehrend der Fahrt)
|
||||
// ==============================================
|
||||
public void ShowQuickQuestion(string visualTrigger)
|
||||
{
|
||||
if (isQuizActive) return;
|
||||
|
||||
// Frage aus Cache holen
|
||||
int difficulty = BreakpilotAPI.Instance != null ?
|
||||
(int)GetCurrentDifficulty() : 3;
|
||||
|
||||
QuizQuestion question = BreakpilotAPI.Instance?.GetQuestionForTrigger(visualTrigger, difficulty);
|
||||
|
||||
if (question == null)
|
||||
{
|
||||
Debug.LogWarning($"Keine Frage fuer Trigger '{visualTrigger}' gefunden");
|
||||
return;
|
||||
}
|
||||
|
||||
currentQuestion = question;
|
||||
isQuizActive = true;
|
||||
|
||||
// UI anzeigen
|
||||
if (quickQuizPanel) quickQuizPanel.SetActive(true);
|
||||
DisplayQuestion(question);
|
||||
|
||||
// Timer starten
|
||||
float timeLimit = question.time_limit_seconds > 0 ? question.time_limit_seconds : 5f;
|
||||
timerCoroutine = StartCoroutine(QuickQuizTimer(timeLimit));
|
||||
|
||||
// Audio-Version: Frage vorlesen
|
||||
if (isAudioMode)
|
||||
{
|
||||
SpeakQuestion(question);
|
||||
}
|
||||
|
||||
OnQuizStarted?.Invoke();
|
||||
}
|
||||
|
||||
private IEnumerator QuickQuizTimer(float duration)
|
||||
{
|
||||
timeRemaining = duration;
|
||||
|
||||
while (timeRemaining > 0)
|
||||
{
|
||||
timeRemaining -= Time.deltaTime;
|
||||
|
||||
// UI updaten
|
||||
if (timerText) timerText.text = $"{timeRemaining:F1}s";
|
||||
if (timerSlider) timerSlider.value = timeRemaining / duration;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Zeit abgelaufen = falsche Antwort
|
||||
OnAnswerSelected(-1);
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Pause Quiz (Spiel haelt an)
|
||||
// ==============================================
|
||||
public void ShowPauseQuestion(QuizQuestion question)
|
||||
{
|
||||
if (isQuizActive) return;
|
||||
|
||||
currentQuestion = question;
|
||||
isQuizActive = true;
|
||||
|
||||
// Spiel pausieren
|
||||
Time.timeScale = 0;
|
||||
|
||||
// UI anzeigen
|
||||
if (pauseQuizPanel) pauseQuizPanel.SetActive(true);
|
||||
DisplayQuestion(question);
|
||||
|
||||
// Audio-Version: Frage vorlesen
|
||||
if (isAudioMode)
|
||||
{
|
||||
SpeakQuestion(question);
|
||||
}
|
||||
|
||||
OnQuizStarted?.Invoke();
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Antwort verarbeiten
|
||||
// ==============================================
|
||||
private void OnAnswerSelected(int index)
|
||||
{
|
||||
if (!isQuizActive || currentQuestion == null) return;
|
||||
|
||||
// Timer stoppen
|
||||
if (timerCoroutine != null)
|
||||
{
|
||||
StopCoroutine(timerCoroutine);
|
||||
timerCoroutine = null;
|
||||
}
|
||||
|
||||
bool isCorrect = index == currentQuestion.correct_index;
|
||||
int points = isCorrect ? pointsCorrect : pointsWrong;
|
||||
|
||||
questionsAnswered++;
|
||||
if (isCorrect) questionsCorrect++;
|
||||
|
||||
// Visuelles Feedback
|
||||
ShowAnswerFeedback(isCorrect, currentQuestion.correct_index);
|
||||
|
||||
// Audio Feedback
|
||||
if (isAudioMode)
|
||||
{
|
||||
SpeakFeedback(isCorrect);
|
||||
}
|
||||
|
||||
// Event ausloesen
|
||||
OnQuestionAnswered?.Invoke(isCorrect, points);
|
||||
|
||||
// Quiz beenden (mit kurzer Verzoegerung fuer Feedback)
|
||||
StartCoroutine(EndQuizAfterDelay(isCorrect ? 0.5f : 1f));
|
||||
}
|
||||
|
||||
private IEnumerator EndQuizAfterDelay(float delay)
|
||||
{
|
||||
// Bei pausiertem Spiel: unscaled Zeit nutzen
|
||||
yield return new WaitForSecondsRealtime(delay);
|
||||
|
||||
EndQuiz();
|
||||
}
|
||||
|
||||
private void EndQuiz()
|
||||
{
|
||||
isQuizActive = false;
|
||||
currentQuestion = null;
|
||||
|
||||
// UI verstecken
|
||||
if (quickQuizPanel) quickQuizPanel.SetActive(false);
|
||||
if (pauseQuizPanel) pauseQuizPanel.SetActive(false);
|
||||
|
||||
// Spiel fortsetzen (falls pausiert)
|
||||
Time.timeScale = 1;
|
||||
|
||||
OnQuizEnded?.Invoke();
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// UI Helper
|
||||
// ==============================================
|
||||
private void DisplayQuestion(QuizQuestion question)
|
||||
{
|
||||
if (questionText) questionText.text = question.question_text;
|
||||
|
||||
for (int i = 0; i < answerButtons.Length; i++)
|
||||
{
|
||||
if (i < question.options.Length)
|
||||
{
|
||||
answerButtons[i].gameObject.SetActive(true);
|
||||
var buttonText = answerButtons[i].GetComponentInChildren<TextMeshProUGUI>();
|
||||
if (buttonText) buttonText.text = $"[{i + 1}] {question.options[i]}";
|
||||
}
|
||||
else
|
||||
{
|
||||
answerButtons[i].gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowAnswerFeedback(bool correct, int correctIndex)
|
||||
{
|
||||
// Richtige Antwort gruen markieren
|
||||
if (correctIndex >= 0 && correctIndex < answerButtons.Length)
|
||||
{
|
||||
var colors = answerButtons[correctIndex].colors;
|
||||
colors.normalColor = Color.green;
|
||||
answerButtons[correctIndex].colors = colors;
|
||||
}
|
||||
|
||||
// TODO: Animation, Sound, Partikel
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Audio-Version
|
||||
// ==============================================
|
||||
private void SpeakQuestion(QuizQuestion question)
|
||||
{
|
||||
// TODO: Text-to-Speech Integration
|
||||
// Option 1: Voraufgenommene Audio-Dateien
|
||||
// Option 2: Web-TTS API
|
||||
// Option 3: Unity Speech Synthesis (platform-abhaengig)
|
||||
|
||||
Debug.Log($"[TTS] {question.question_text}");
|
||||
for (int i = 0; i < question.options.Length; i++)
|
||||
{
|
||||
Debug.Log($"[TTS] {i + 1} fuer {question.options[i]}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SpeakFeedback(bool correct)
|
||||
{
|
||||
string message = correct ? "Richtig! Gut gemacht!" : "Nicht ganz. Versuch es nochmal!";
|
||||
Debug.Log($"[TTS] {message}");
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Statistik
|
||||
// ==============================================
|
||||
public int GetQuestionsAnswered() => questionsAnswered;
|
||||
public int GetQuestionsCorrect() => questionsCorrect;
|
||||
|
||||
public float GetAccuracy()
|
||||
{
|
||||
if (questionsAnswered == 0) return 0;
|
||||
return (float)questionsCorrect / questionsAnswered;
|
||||
}
|
||||
|
||||
public void ResetStatistics()
|
||||
{
|
||||
questionsAnswered = 0;
|
||||
questionsCorrect = 0;
|
||||
}
|
||||
|
||||
private float GetCurrentDifficulty()
|
||||
{
|
||||
// TODO: Aus GameManager holen
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user