extends Node var all_quests: Dictionary = {} # quest_id -> QuestData var active_quests: Dictionary = {} # quest_id -> QuestData var completed_quests: Dictionary = {} # quest_id -> QuestData var failed_quests: Dictionary = {} const QUESTS_PATH := "res://game/quests/data/" func _ready() -> void: _load_all_quests() _connect_events() _refresh_quest_states() GameEvents.flag_changed.connect(_on_flag_changed) func _on_flag_changed(_flag_name: StringName, _old_value: Variant, _new_value: Variant) -> void: _refresh_quest_states() # --- Chargement --- func _load_all_quests() -> void: var dir := DirAccess.open(QUESTS_PATH) if dir == null: push_error("Dossier de quêtes introuvable : " + QUESTS_PATH) return for file in dir.get_files(): if file.ends_with(".tres"): var quest: QuestData = load(QUESTS_PATH + file) if quest and quest.quest_id != &"": all_quests[quest.quest_id] = quest # --- Calcul d'état (cœur du système de chaînes) --- ## Recalcule l'état de toutes les quêtes non-commencées : ## une quête passe de LOCKED à AVAILABLE si ses prérequis sont satisfaits. func _refresh_quest_states() -> void: for quest_id in all_quests: var quest: QuestData = all_quests[quest_id] if quest.state in [QuestData.QuestState.ACTIVE, QuestData.QuestState.COMPLETED, QuestData.QuestState.FAILED]: continue if _are_prerequisites_met(quest): if quest.state == QuestData.QuestState.LOCKED: quest.state = QuestData.QuestState.AVAILABLE _emit_quest_available(quest_id) else: quest.state = QuestData.QuestState.LOCKED func _are_prerequisites_met(quest: QuestData) -> bool: # 1. Prérequis de quêtes for prereq in quest.prerequisites: if not prereq in completed_quests: return false # 2. Flags bloquants (si l'un est vrai → la quête ne peut PAS se débloquer) if not quest.blocking_flags.is_empty(): if FlagManager.evaluate_condition(quest.blocking_flags): return false # 3. Flags requis (tous doivent être vrais) if not quest.required_flags.is_empty(): if not FlagManager.evaluate_condition(quest.required_flags): return false return true func _emit_quest_available(quest_id: StringName) -> void: # Optionnel : signal pour notifier l'UI ou un PNJ GameEvents.quest_available.emit(quest_id) # --- Connexion aux événements --- func _connect_events() -> void: GameEvents.pokemon_captured.connect(_on_pokemon_captured) GameEvents.pokemon_defeated.connect(_on_pokemon_defeated) GameEvents.item_collected.connect(_on_item_collected) GameEvents.npc_talked.connect(_on_npc_talked) GameEvents.location_reached.connect(_on_location_reached) # --- Démarrage de quête --- func start_quest(quest_id: StringName, force: bool = false) -> bool: if quest_id in active_quests or quest_id in completed_quests: return false if not quest_id in all_quests: push_warning("Quête inconnue : " + str(quest_id)) return false var quest: QuestData = all_quests[quest_id] # Vérifie les prérequis (sauf si forcé, pour debug) if not force and not _are_prerequisites_met(quest): return false # Reset les compteurs for obj in quest.objectives: obj.current_count = 0 quest.state = QuestData.QuestState.ACTIVE active_quests[quest_id] = quest GameEvents.quest_started.emit(quest_id) return true ## Démarre automatiquement toutes les quêtes disponibles qui n'attendent ## pas de déblocage manuel. À appeler après un chargement de zone, par ex. func auto_start_available_quests() -> void: for quest_id in all_quests: var quest: QuestData = all_quests[quest_id] if quest.state == QuestData.QuestState.AVAILABLE and not quest.requires_manual_unlock: start_quest(quest_id) # --- Progression --- func _progress_objective(event_type: QuestObjective.Type, target: StringName) -> void: for quest_id in active_quests.keys(): var quest: QuestData = active_quests[quest_id] var changed := false for i in quest.objectives.size(): var obj: QuestObjective = quest.objectives[i] if obj.is_complete(): continue if obj.matches(event_type, target): obj.current_count = min(obj.current_count + 1, obj.required_count) changed = true GameEvents.quest_objective_updated.emit( quest_id, i, obj.current_count, obj.required_count ) if changed and quest.are_all_objectives_complete(): _complete_quest(quest_id) # --- Handlers d'événements --- func _on_pokemon_captured(species_id: StringName) -> void: _progress_objective(QuestObjective.Type.CAPTURE, species_id) func _on_pokemon_defeated(species_id: StringName) -> void: _progress_objective(QuestObjective.Type.DEFEAT, species_id) func _on_item_collected(item_id: StringName, amount: int) -> void: # On avance de `amount` unités for quest_id in active_quests.keys(): var quest: QuestData = active_quests[quest_id] for i in quest.objectives.size(): var obj: QuestObjective = quest.objectives[i] if obj.type == QuestObjective.Type.COLLECT \ and obj.target_id == item_id \ and not obj.is_complete(): obj.current_count = min(obj.current_count + amount, obj.required_count) GameEvents.quest_objective_updated.emit( quest_id, i, obj.current_count, obj.required_count ) if quest.are_all_objectives_complete(): _complete_quest(quest_id) func _on_npc_talked(npc_id: StringName) -> void: _progress_objective(QuestObjective.Type.TALK_TO, npc_id) func _on_location_reached(location_id: StringName) -> void: _progress_objective(QuestObjective.Type.REACH_LOCATION, location_id) # --- Fin de quête --- func _complete_quest(quest_id: StringName) -> void: var quest: QuestData = active_quests[quest_id] quest.state = QuestData.QuestState.COMPLETED active_quests.erase(quest_id) completed_quests[quest_id] = quest _give_rewards(quest) GameEvents.quest_completed.emit(quest_id) # 🔑 C'est ici que la magie opère : les quêtes suivantes se débloquent ! _refresh_quest_states() # Optionnel : auto-démarrer les quêtes suivantes qui ne demandent # pas de déblocage manuel (ex: chaîne principale purement scriptée) auto_start_available_quests() func _give_rewards(quest: QuestData) -> void: for reward in quest.rewards: match reward.type: QuestReward.Type.ITEM: # À adapter à ton système d'inventaire GameEvents.item_collected.emit(reward.target_id, reward.amount) QuestReward.Type.MONEY: # Inventory.add_money(reward.amount) pass QuestReward.Type.POKEMON: # Team.add_pokemon(reward.target_id) pass QuestReward.Type.UNLOCK_QUEST: start_quest(reward.target_id) # --- Sauvegarde / Chargement --- func get_save_data() -> Dictionary: var data := { "active": {}, # quest_id -> Array[int] (progressions) "completed": [], # Array[StringName] "failed": [] } for id in active_quests: var quest: QuestData = active_quests[id] var counts: Array[int] = [] for obj in quest.objectives: counts.append(obj.current_count) data["active"][id] = counts data["completed"] = completed_quests.keys() data["failed"] = failed_quests.keys() return data func load_save_data(data: Dictionary) -> void: active_quests.clear() completed_quests.clear() failed_quests.clear() # Reset tous les états à LOCKED for q in all_quests.values(): q.state = QuestData.QuestState.LOCKED for obj in q.objectives: obj.current_count = 0 # Restaure les complétées for id in data.get("completed", []): if id in all_quests: all_quests[id].state = QuestData.QuestState.COMPLETED completed_quests[id] = all_quests[id] # Restaure les actives for id in data.get("active", {}): if id in all_quests: var quest: QuestData = all_quests[id] var counts: Array = data["active"][id] for i in quest.objectives.size(): quest.objectives[i].current_count = counts[i] if i < counts.size() else 0 quest.state = QuestData.QuestState.ACTIVE active_quests[id] = quest # Recalcule les LOCKED / AVAILABLE _refresh_quest_states() func debug_print_all() -> void: print("\n=== ÉTAT DES QUÊTES ===") for quest_id in all_quests: var q: QuestData = all_quests[quest_id] var state_str := q.get_state_name() var cat_str := q.get_category_name() print("\n[%s][%s] %s — %s" % [cat_str, state_str, q.quest_id, q.title]) for i in q.objectives.size(): var obj := q.objectives[i] var done := "✓" if obj.is_complete() else " " var optional := " (optionnel)" if obj.is_optional else "" print(" [%s] %s : %d/%d%s" % [ done, obj.description, obj.current_count, obj.required_count, optional ]) print("\n=======================\n") func _input(event): if event.is_action_pressed("debug_quests"): QuestManager.debug_print_all()