GuildTycoon/Assets/_/Features/Quests/Runtime/QuestManager.cs

547 lines
18 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Adventurer.Runtime;
using Core.Runtime;
using Quest.Runtime;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Quests.Runtime
{
public class QuestManager : BaseMonobehaviour
{
#region Singleton
public static QuestManager Instance { get; set; }
void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
#endregion
#region Events
public static event Action<QuestClass> OnQuestCompleted;
public static event Action<QuestEvent> OnEventReceived;
public static event Action<QuestClass> OnEventFromQuest;
public static event Action<List<QuestTemplate>> OnAvailableQuestsUpdated;
#endregion
#region Unity API
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
if (_activeEvents == null) _activeEvents = new List<QuestEvent>();
}
void OnEnable()
{
GameManager.OnTimeAdvanced += CheckMissionsProgress;
}
void OnDisable()
{
GameManager.OnTimeAdvanced -= CheckMissionsProgress;
}
void Start()
{
if (_questDatabase != null)
{
_disponibleQuests = _questDatabase
.GetAll()
.SelectMany(f => f.questTemplates)
.Select(t => t.ToQuestClass(QuestStateEnum.Disponible))
.ToList();
}
else
{
_disponibleQuests = new List<QuestClass>();
}
// Ensure quest lists are initialized to avoid null issues in consumers
if (_activeQuests == null) _activeQuests = new List<QuestClass>();
if (_completedQuests == null) _completedQuests = new List<QuestClass>();
if (_disponibleQuests == null) _disponibleQuests = new List<QuestClass>();
}
#endregion
#region Properties
List<QuestClass> _activeQuests;
List<QuestClass> _disponibleQuests;
List<QuestClass> _completedQuests;
QuestClass _currentQuest;
public QuestClass CurrentQuest
{
get => _currentQuest;
set => _currentQuest = value;
}
public List<QuestClass> DisponibleQuests
{
get => _disponibleQuests;
set => _disponibleQuests = value;
}
public List<QuestClass> ActiveQuests
{
get => _activeQuests;
set => _activeQuests = value;
}
public List<QuestClass> CompletedQuests
{
get => _completedQuests;
set => _completedQuests = value;
}
public List<Guid> AssignedAdventurers => _currentQuest?.AssignedAdventurersID;
[SerializeField] QuestFactoryDatabase _questDatabase;
public QuestFactoryDatabase QuestDatabase
{
get => _questDatabase;
set => _questDatabase = value;
}
public int currentTimeInQuest => _snapTime;
int _snapTime = 0;
List<QuestEvent> _activeEvents;
// Champ inutilisé - conservé pour référence future
List<QuestEvent> inActiveEvents;
#endregion
#region Initialization
public QuestManager()
{
// Unity will call constructor before Awake; avoid subscribing here.
}
#endregion
#region Public Methods
/// <summary>
/// Démarre une quête avec une équipe d'aventuriers
/// </summary>
public void StartQuest(QuestClass quest, List<AdventurerClass> team, GameTime gameTime)
{
// Enforce state order: must be Accepted before starting
if (quest.State != QuestStateEnum.Accepted)
{
Debug.LogWarning($"Cannot start quest '{quest.Name}' because it is not in Accepted state (current: {quest.State}).");
return;
}
AssignAdventurersToQuest(quest, team);
SetQuestTimings(quest, gameTime);
UpdateQuestStatus(quest);
}
/// <summary>
/// Complète une quête et libère les aventuriers assignés
/// </summary>
public void CompleteQuest(QuestClass quest, List<Guid> team)
{
if (quest.State != QuestStateEnum.InProgress) return;
ReleaseAdventurers(team);
UpdateQuestCompletionStatus(quest);
SaveQuestProgress();
}
/// <summary>
/// Notifie les observateurs des quêtes complétées
/// </summary>
public void NotifyCompletedQuests()
{
foreach (var quest in _completedQuests)
{
OnQuestCompleted?.Invoke(quest);
}
}
/// <summary>
/// Récupère l'historique des events lié à une quête
/// </summary>
public QuestSummary GetQuestHistory(Guid questId)
{
QuestClass quest = GetQuestById(questId);
var dict = GetFact<Dictionary<Guid, List<QuestEventLog>>>("events_quests_history");
List<QuestEventLog> events = null;
if (dict != null && dict.TryGetValue(questId, out var history))
{
events = history;
}
else
{
events = new List<QuestEventLog>();
}
return new QuestSummary(quest, events);
}
/// <summary>
/// Vérifie si des aventuriers peuvent être sélectionnés pour la quête courante
/// </summary>
public bool CanSelectedAdventurers()
{
// Adventurers can be selected only when the quest has been accepted
return _currentQuest != null && _currentQuest.State == QuestStateEnum.Accepted;
}
/// <summary>
/// Vérifie si une quête est complétée par son nom
/// </summary>
public bool IsQuestCompleted(string questName)
{
return CompletedQuests != null
&& CompletedQuests.Any(q => q.Name == questName);
}
/// <summary>
/// Résout une liste de quêtes à partir des données sauvegardées
/// </summary>
public List<QuestClass> ResolveQuestsList(List<QuestClass> questsFromSave)
{
List<QuestClass> quests = new List<QuestClass>();
foreach (var saved in questsFromSave)
{
QuestTemplate template = _questDatabase.GetTemplatesByName(saved.Name);
if (template == null) continue;
var rebuilt = template.ToQuestClass(saved.State, saved.ID);
// Restaurer les infos runtime utiles
rebuilt.StartSeconds = saved.StartSeconds;
rebuilt.EndSeconds = saved.EndSeconds;
if (saved.AssignedAdventurersID != null)
rebuilt.AssignedAdventurersID = new List<Guid>(saved.AssignedAdventurersID);
quests.Add(rebuilt);
}
return quests;
}
public List<QuestTemplate> GetAvailableQuests(int level)
{
var factory = _questDatabase.GetFactoryForLevel(level);
if (factory == null)
return new List<QuestTemplate>();
return factory.questTemplates
.Where(q => q.data.MinLevel <= level)
.ToList();
}
public void NotifyAvailableQuestsUpdated(int level)
{
var available = GetAvailableQuests(level);
OnAvailableQuestsUpdated?.Invoke(available);
}
#endregion
#region Utils
/// <summary>
/// Assigne des aventuriers à une quête
/// </summary>
void AssignAdventurersToQuest(QuestClass quest, List<AdventurerClass> team)
{
foreach (var adventurer in team)
{
adventurer.IsAvailable = false;
if (quest.AssignedAdventurersID == null)
quest.AssignedAdventurersID = new List<Guid>();
quest.AssignedAdventurersID.Add(adventurer.ID);
}
// Synchroniser la sauvegarde de la quête avec les aventuriers assignés
var saveQuests = GetFact<List<QuestClass>>("accepted_quests");
if (saveQuests != null)
{
var saveQuest = saveQuests.FirstOrDefault(q => q.ID == quest.ID);
if (saveQuest != null)
{
saveQuest.AssignedAdventurersID = new List<Guid>(quest.AssignedAdventurersID);
}
SaveFacts();
}
}
/// <summary>
/// Configure les temps de début et de fin d'une quête
/// </summary>
void SetQuestTimings(QuestClass quest, GameTime gameTime)
{
quest.State = QuestStateEnum.InProgress;
quest.StartSeconds = gameTime.TotalSeconds;
quest.EndSeconds = gameTime.TotalSeconds + (quest.Duration * 60);
}
/// <summary>
/// Met à jour le statut d'une quête dans les listes actives et sauvegardées
/// </summary>
void UpdateQuestStatus(QuestClass quest)
{
// Avoid duplicates in active quests
if (_activeQuests.All(q => q.ID != quest.ID))
{
_activeQuests.Add(quest);
}
List<QuestClass> saveQuests = GetFact<List<QuestClass>>(("accepted_quests"));
foreach (var saveQuest in saveQuests)
{
if (saveQuest.Name == quest.Name)
{
saveQuest.State = QuestStateEnum.InProgress;
saveQuest.StartSeconds = quest.StartSeconds;
saveQuest.EndSeconds = quest.EndSeconds;
}
}
SaveFacts();
}
/// <summary>
/// Libère les aventuriers assignés à une quête
/// </summary>
void ReleaseAdventurers(List<Guid> team)
{
bool anyChanged = false;
foreach (var adventurerId in team)
{
AdventurerClass adventurer = QuestClass.GetOneAdventurerFromId(adventurerId);
if (adventurer == null)
{
Info($"<color=orange>Aventurer {adventurerId} introuvable</color>");
continue;
}
Info($"<color=orange>{adventurer.Name} est dans la team avec le status dispo : {adventurer.IsAvailable}</color>");
if (adventurer != null && adventurer.IsAvailable == false)
{
adventurer.IsAvailable = true;
anyChanged = true;
}
}
if (anyChanged)
{
Info("<color=cyan>Comme les données on changées, on les sauvegarde.</color>");
SaveFacts();
}
}
/// <summary>
/// Met à jour le statut de complétion d'une quête
/// </summary>
void UpdateQuestCompletionStatus(QuestClass quest)
{
quest.State = QuestStateEnum.Completed;
_activeQuests.RemoveAll(q => q.Name == quest.Name);
_completedQuests.Add(quest);
List<QuestClass> quests = GetFact<List<QuestClass>>(("accepted_quests"));
QuestClass questToUpdate = quests.FirstOrDefault(q => q.ID == quest.ID);
if (questToUpdate != null)
{
questToUpdate.State = QuestStateEnum.Completed;
}
NotifyCompletedQuests();
}
/// <summary>
/// Sauvegarde les progrès des quêtes
/// </summary>
void SaveQuestProgress()
{
SaveFacts();
}
/// <summary>
/// Vérifie la progression des missions actives
/// </summary>
void CheckMissionsProgress(int currentSeconds)
{
if(_activeQuests == null) return;
var questsToComplete = new List<QuestClass>();
var activeQuests = _activeQuests.Where(q => q.State == QuestStateEnum.InProgress).ToList();
foreach (var quest in activeQuests)
{
_snapTime = currentSeconds - quest.StartSeconds;
CheckQuestEvents(quest, currentSeconds);
if (quest.State == QuestStateEnum.InProgress && currentSeconds >= quest.EndSeconds)
{
questsToComplete.Add(quest);
}
}
foreach (var quest in questsToComplete)
{
CompleteQuest(quest, quest.AssignedAdventurersID);
}
}
/// <summary>
/// Vérifie et déclenche les événements de quête lorsque les conditions sont remplies
/// </summary>
void CheckQuestEvents(QuestClass quest, int currentSeconds)
{
if (quest?.ActiveEvents == null || quest.TriggeredEventsDescriptionKeys == null)
return;
foreach (var questEvent in quest.ActiveEvents)
{
if (questEvent == null) continue;
if (quest.TriggeredEventsDescriptionKeys.Contains(questEvent.DescriptionKey))
continue;
// Skip malformed time windows
if (questEvent.MinTimeTrigger > questEvent.MaxTimeTrigger)
continue;
if (_snapTime >= questEvent.MinTimeTrigger && _snapTime <= questEvent.MaxTimeTrigger)
{
float percent = Mathf.Clamp(questEvent.PercentTrigger, 0f, 100f);
if (Random.Range(0f, 100f) <= percent)
{
TriggerEvent(questEvent, quest);
quest.TriggeredEventsDescriptionKeys.Add(questEvent.DescriptionKey);
}
}
}
}
/// <summary>
/// Déclenche un événement de quête
/// </summary>
void TriggerEvent(QuestEvent questEvent, QuestClass quest)
{
questEvent.Time = _snapTime;
OnEventReceived?.Invoke(questEvent);
OnEventFromQuest?.Invoke(quest);
var targets = questEvent.GetTargets(quest.AssignedAdventurersID);
ApplyEffect(questEvent.Effects, targets);
var eventsDict = GetFact<Dictionary<Guid, List<QuestEventLog>>>("events_quests_history");
if (eventsDict == null)
{
eventsDict = new Dictionary<Guid, List<QuestEventLog>>();
SetFact("events_quests_history", eventsDict, FactPersistence.Persistent);
}
if(!eventsDict.ContainsKey(quest.ID))
{
eventsDict.Add(quest.ID, new List<QuestEventLog>());
}
QuestEventLog questEventLog = new QuestEventLog(_snapTime, questEvent.Id);
eventsDict[quest.ID].Add(questEventLog);
if (_activeEvents == null) _activeEvents = new List<QuestEvent>();
_activeEvents.Add(questEvent);
SaveFacts();
}
/// <summary>
/// Applique les effets aux aventuriers ciblés
/// </summary>
void ApplyEffect(List<EventEffect> effects, List<AdventurerClass> targets)
{
foreach (var effect in effects)
{
foreach (var target in targets)
{
ApplyEffectToTarget(effect, target);
}
}
}
/// <summary>
/// Applique un effet spécifique à un aventurier cible
/// </summary>
void ApplyEffectToTarget(EventEffect effect, AdventurerClass target)
{
switch (effect.Type)
{
case EffectType.Damage:
target.TakeDamage(effect.Value);
break;
case EffectType.Heal:
target.Heal(effect.Value);
break;
case EffectType.Buff:
target.ApplyBuff(effect.Value);
break;
}
}
/// <summary>
/// Récupère une quête via son ID
/// </summary>
/// <param name="questId"></param>
/// <returns></returns>
QuestClass GetQuestById(Guid questId)
{
QuestClass quest = _activeQuests.FirstOrDefault(q => q.ID == questId);
if(quest != null)
return quest;
quest = _completedQuests.FirstOrDefault(q => q.ID == questId);
if(quest != null)
return quest;
foreach (var factory in _questDatabase.GetAll())
{
foreach (var template in factory.questTemplates)
{
if (Guid.TryParse(template.m_assetGuid, out Guid guid) && guid == questId)
{
return template.ToQuestClass(QuestStateEnum.Disponible, questId);
}
}
}
return null;
}
/// <summary>
/// Récupère un event via son ID
/// </summary>
/// <param name="eventId"></param>
/// <returns>QuestEvent</returns>
public QuestEvent GetEventById(Guid eventId)
{
if (_activeEvents == null) return null;
return _activeEvents.FirstOrDefault(e => e.Id == eventId);
}
#endregion
}
}