slrpg-app/lib/src/features/gamification/application/quest_service.dart

179 lines
5.4 KiB
Dart

import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:drift/drift.dart';
import '../../../shared/data/local/app_database.dart';
import '../../../shared/data/repositories/user_repository.dart';
import '../../../shared/data/repositories/workout_repository.dart';
import '../data/repositories/quest_repository.dart';
import '../../../core/constants/app_constants.dart';
enum QuestTrigger {
workoutComplete,
volume,
repCount,
inventoryChange,
}
final questServiceProvider = Provider<QuestService>((ref) {
final questRepo = ref.watch(questRepositoryProvider);
final userRepo = ref.watch(userRepositoryProvider);
return QuestService(ref: ref, questRepo: questRepo, userRepo: userRepo);
});
class QuestService {
final Ref ref;
final QuestRepository questRepo;
final UserRepository userRepo;
QuestService({
required this.ref,
required this.questRepo,
required this.userRepo,
});
Future<void> checkAndGenerateQuests() async {
await _cleanupExpired();
await _generateDailiesIfNeeded();
await _ensureStoryQuests();
}
Future<void> _cleanupExpired() async {
await questRepo.cleanupExpiredQuests();
}
Future<void> _generateDailiesIfNeeded() async {
final activeDailies = await questRepo.getActiveQuests();
final hasDailies = activeDailies.any((q) => q.type == 'daily');
if (!hasDailies) {
debugPrint('🎲 Generating new Daily Quests...');
final random = Random();
final newQuests = <QuestCollection>[];
final pool = List<_QuestTemplate>.from(_dailyQuestPool)..shuffle();
final selected = pool.take(3);
final now = DateTime.now();
final endOfDay = DateTime(now.year, now.month, now.day, 23, 59, 59);
for (var template in selected) {
newQuests.add(QuestCollection(
id: 'daily_${now.millisecondsSinceEpoch}_${random.nextInt(1000)}',
type: 'daily',
title: template.title,
description: template.description,
targetValue: template.target,
currentValue: 0,
rewardXP: template.xp,
rewardItem: template.itemId,
isCompleted: false,
isClaimed: false,
expiresAt: endOfDay,
createdAt: now,
));
}
for (var q in newQuests) {
await questRepo.createQuest(q);
}
}
}
Future<void> _ensureStoryQuests() async {
final activeQuests = await questRepo.getActiveQuests();
// Helper: Prüft ob Quest-ID schon existiert (aktiv oder erledigt)
// Hinweis: getActiveQuests liefert aktuell alle nicht-abgelaufenen.
// Für Story Quests (die nie ablaufen) reicht das.
bool hasQuest(String id) => activeQuests.any((q) => q.id == id);
if (!hasQuest('story_initiate')) {
await questRepo.createQuest(QuestCollection(
id: 'story_initiate',
type: 'story',
title: 'The Awakening',
description: 'Complete your first workout to prove your worth.',
targetValue: 1,
currentValue: 0,
rewardXP: 100,
isCompleted: false,
isClaimed: false,
createdAt: DateTime.now(),
));
}
if (!hasQuest('story_inventory')) {
await questRepo.createQuest(QuestCollection(
id: 'story_inventory',
type: 'story',
title: 'Armory Master',
description: 'Setup your equipment inventory.',
targetValue: 1,
currentValue: 0,
rewardXP: 50,
isCompleted: false,
isClaimed: false,
createdAt: DateTime.now(),
));
final inventory = await userRepo.getInventorySettingsAsync();
if ((inventory['plates'] as List).isNotEmpty) {
await questRepo.updateProgress('story_inventory', 1);
}
}
}
Future<void> reportEvent(QuestTrigger trigger, {dynamic data}) async {
final activeQuests = await questRepo.getActiveQuests();
for (var quest in activeQuests) {
if (quest.isCompleted) continue;
if (quest.id == 'story_initiate' &&
trigger == QuestTrigger.workoutComplete) {
await questRepo.updateProgress(quest.id, 1);
}
if (quest.title == 'Volume Eater' && trigger == QuestTrigger.volume) {
final volume = data as int; // kg
await questRepo.updateProgress(quest.id, volume);
}
if (quest.title == 'Workout Warrior' &&
trigger == QuestTrigger.workoutComplete) {
await questRepo.updateProgress(quest.id, 1);
}
if (quest.title == 'Rep Collector' && trigger == QuestTrigger.repCount) {
final reps = data as int;
await questRepo.updateProgress(quest.id, reps);
}
}
}
}
// --- QUEST DEFINITIONS ---
class _QuestTemplate {
final String title;
final String description;
final int target;
final int xp;
final String? itemId;
const _QuestTemplate(this.title, this.description, this.target, this.xp,
[this.itemId]);
}
const List<_QuestTemplate> _dailyQuestPool = [
_QuestTemplate(
'Volume Eater', 'Move a total of 500kg in a single day.', 500, 100),
_QuestTemplate('Workout Warrior', 'Complete 1 Workout today.', 1, 50),
_QuestTemplate('Rep Collector',
'Perform 50 total repetitions across all exercises.', 50, 75),
_QuestTemplate('Early Bird', 'Start a workout before noon.', 1,
50), // Logik müsste Zeit prüfen
_QuestTemplate(
'Iron Discipline', 'Log your bodyweight in the profile.', 1, 25),
];