179 lines
5.4 KiB
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),
|
|
];
|