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:
348
breakpilot-drive/UnityScripts/Core/AchievementManager.cs
Normal file
348
breakpilot-drive/UnityScripts/Core/AchievementManager.cs
Normal file
@@ -0,0 +1,348 @@
|
||||
// ==============================================
|
||||
// AchievementManager.cs - Achievement System
|
||||
// ==============================================
|
||||
// Handles achievement display and notifications.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace BreakpilotDrive
|
||||
{
|
||||
[Serializable]
|
||||
public class Achievement
|
||||
{
|
||||
public string id;
|
||||
public string name;
|
||||
public string description;
|
||||
public string icon;
|
||||
public string category;
|
||||
public int threshold;
|
||||
public int progress;
|
||||
public bool unlocked;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class AchievementResponse
|
||||
{
|
||||
public string user_id;
|
||||
public int total;
|
||||
public int unlocked_count;
|
||||
public Achievement[] achievements;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class AchievementArrayWrapper
|
||||
{
|
||||
public Achievement[] achievements;
|
||||
}
|
||||
|
||||
public class AchievementManager : MonoBehaviour
|
||||
{
|
||||
public static AchievementManager Instance { get; private set; }
|
||||
|
||||
[Header("UI References")]
|
||||
[SerializeField] private GameObject achievementPopupPrefab;
|
||||
[SerializeField] private Transform popupContainer;
|
||||
[SerializeField] private float popupDuration = 3f;
|
||||
|
||||
[Header("Audio")]
|
||||
[SerializeField] private AudioClip unlockSound;
|
||||
|
||||
// Events
|
||||
public UnityEvent<Achievement> OnAchievementUnlocked;
|
||||
|
||||
// Cached data
|
||||
private List<Achievement> achievements = new List<Achievement>();
|
||||
private HashSet<string> previouslyUnlocked = new HashSet<string>();
|
||||
private Queue<Achievement> popupQueue = new Queue<Achievement>();
|
||||
private bool isShowingPopup = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Load Achievements from API
|
||||
// ==============================================
|
||||
|
||||
public IEnumerator LoadAchievements(string userId, Action<AchievementResponse> onComplete = null)
|
||||
{
|
||||
string url = $"{GetBaseUrl()}/achievements/{userId}";
|
||||
|
||||
using (var request = UnityEngine.Networking.UnityWebRequest.Get(url))
|
||||
{
|
||||
// Add auth header if available
|
||||
string authHeader = AuthManager.Instance?.GetAuthHeader();
|
||||
if (!string.IsNullOrEmpty(authHeader))
|
||||
{
|
||||
request.SetRequestHeader("Authorization", authHeader);
|
||||
}
|
||||
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
if (request.result == UnityEngine.Networking.UnityWebRequest.Result.Success)
|
||||
{
|
||||
var response = JsonUtility.FromJson<AchievementResponse>(request.downloadHandler.text);
|
||||
|
||||
// Store previously unlocked for comparison
|
||||
previouslyUnlocked.Clear();
|
||||
foreach (var a in achievements)
|
||||
{
|
||||
if (a.unlocked)
|
||||
previouslyUnlocked.Add(a.id);
|
||||
}
|
||||
|
||||
// Update achievements
|
||||
achievements.Clear();
|
||||
if (response.achievements != null)
|
||||
{
|
||||
achievements.AddRange(response.achievements);
|
||||
}
|
||||
|
||||
onComplete?.Invoke(response);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"Failed to load achievements: {request.error}");
|
||||
onComplete?.Invoke(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Check for New Achievements
|
||||
// ==============================================
|
||||
|
||||
public void CheckForNewUnlocks()
|
||||
{
|
||||
foreach (var achievement in achievements)
|
||||
{
|
||||
if (achievement.unlocked && !previouslyUnlocked.Contains(achievement.id))
|
||||
{
|
||||
// New unlock!
|
||||
ShowAchievementPopup(achievement);
|
||||
OnAchievementUnlocked?.Invoke(achievement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call after each game session to check for new achievements
|
||||
public IEnumerator RefreshAndCheckAchievements(string userId)
|
||||
{
|
||||
yield return LoadAchievements(userId, (response) =>
|
||||
{
|
||||
if (response != null)
|
||||
{
|
||||
CheckForNewUnlocks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Achievement Popup
|
||||
// ==============================================
|
||||
|
||||
public void ShowAchievementPopup(Achievement achievement)
|
||||
{
|
||||
popupQueue.Enqueue(achievement);
|
||||
|
||||
if (!isShowingPopup)
|
||||
{
|
||||
StartCoroutine(ProcessPopupQueue());
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator ProcessPopupQueue()
|
||||
{
|
||||
isShowingPopup = true;
|
||||
|
||||
while (popupQueue.Count > 0)
|
||||
{
|
||||
var achievement = popupQueue.Dequeue();
|
||||
yield return ShowSinglePopup(achievement);
|
||||
yield return new WaitForSeconds(0.3f); // Gap between popups
|
||||
}
|
||||
|
||||
isShowingPopup = false;
|
||||
}
|
||||
|
||||
private IEnumerator ShowSinglePopup(Achievement achievement)
|
||||
{
|
||||
Debug.Log($"[Achievement] Unlocked: {achievement.name}");
|
||||
|
||||
// Play sound
|
||||
if (unlockSound != null && AudioManager.Instance != null)
|
||||
{
|
||||
AudioManager.Instance.PlaySFX(unlockSound);
|
||||
}
|
||||
|
||||
// Create popup UI
|
||||
if (achievementPopupPrefab != null && popupContainer != null)
|
||||
{
|
||||
var popup = Instantiate(achievementPopupPrefab, popupContainer);
|
||||
var popupScript = popup.GetComponent<AchievementPopup>();
|
||||
|
||||
if (popupScript != null)
|
||||
{
|
||||
popupScript.Setup(achievement);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(popupDuration);
|
||||
|
||||
if (popup != null)
|
||||
{
|
||||
Destroy(popup);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: just wait
|
||||
yield return new WaitForSeconds(popupDuration);
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Public API
|
||||
// ==============================================
|
||||
|
||||
public List<Achievement> GetAllAchievements()
|
||||
{
|
||||
return new List<Achievement>(achievements);
|
||||
}
|
||||
|
||||
public List<Achievement> GetUnlockedAchievements()
|
||||
{
|
||||
return achievements.FindAll(a => a.unlocked);
|
||||
}
|
||||
|
||||
public List<Achievement> GetLockedAchievements()
|
||||
{
|
||||
return achievements.FindAll(a => !a.unlocked);
|
||||
}
|
||||
|
||||
public List<Achievement> GetAchievementsByCategory(string category)
|
||||
{
|
||||
return achievements.FindAll(a => a.category == category);
|
||||
}
|
||||
|
||||
public int GetUnlockedCount()
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var a in achievements)
|
||||
{
|
||||
if (a.unlocked) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public int GetTotalCount()
|
||||
{
|
||||
return achievements.Count;
|
||||
}
|
||||
|
||||
public float GetCompletionPercentage()
|
||||
{
|
||||
if (achievements.Count == 0) return 0f;
|
||||
return (float)GetUnlockedCount() / achievements.Count * 100f;
|
||||
}
|
||||
|
||||
public Achievement GetAchievement(string id)
|
||||
{
|
||||
return achievements.Find(a => a.id == id);
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Icon Mapping
|
||||
// ==============================================
|
||||
|
||||
public Sprite GetIconSprite(string iconName)
|
||||
{
|
||||
// Load from Resources/Icons/Achievements/
|
||||
string path = $"Icons/Achievements/{iconName}";
|
||||
var sprite = Resources.Load<Sprite>(path);
|
||||
|
||||
if (sprite == null)
|
||||
{
|
||||
// Fallback icon
|
||||
sprite = Resources.Load<Sprite>("Icons/Achievements/default");
|
||||
}
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Helpers
|
||||
// ==============================================
|
||||
|
||||
private string GetBaseUrl()
|
||||
{
|
||||
if (BreakpilotAPI.Instance != null)
|
||||
{
|
||||
return "http://localhost:8000/api/game"; // TODO: Get from BreakpilotAPI
|
||||
}
|
||||
return "http://localhost:8000/api/game";
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Achievement Popup Component
|
||||
// ==============================================
|
||||
|
||||
public class AchievementPopup : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private UnityEngine.UI.Text titleText;
|
||||
[SerializeField] private UnityEngine.UI.Text descriptionText;
|
||||
[SerializeField] private UnityEngine.UI.Image iconImage;
|
||||
|
||||
public void Setup(Achievement achievement)
|
||||
{
|
||||
if (titleText != null)
|
||||
titleText.text = achievement.name;
|
||||
|
||||
if (descriptionText != null)
|
||||
descriptionText.text = achievement.description;
|
||||
|
||||
if (iconImage != null)
|
||||
{
|
||||
var sprite = AchievementManager.Instance?.GetIconSprite(achievement.icon);
|
||||
if (sprite != null)
|
||||
iconImage.sprite = sprite;
|
||||
}
|
||||
|
||||
// Animate in
|
||||
StartCoroutine(AnimateIn());
|
||||
}
|
||||
|
||||
private IEnumerator AnimateIn()
|
||||
{
|
||||
var canvasGroup = GetComponent<CanvasGroup>();
|
||||
if (canvasGroup != null)
|
||||
{
|
||||
canvasGroup.alpha = 0f;
|
||||
float duration = 0.3f;
|
||||
float elapsed = 0f;
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
canvasGroup.alpha = Mathf.Lerp(0f, 1f, elapsed / duration);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
canvasGroup.alpha = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user