264 lines
8.5 KiB
GDScript
264 lines
8.5 KiB
GDScript
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()
|