Les events se déroule bien comme il faut. Les aventuriers sont bien repasser en IsAvailable = true quand une quête se termine.

This commit is contained in:
mrtoine 2025-10-11 17:50:11 +02:00
parent 838f91ede7
commit 3ba8dad4a2
16 changed files with 310 additions and 68 deletions

View file

@ -26,11 +26,11 @@ namespace Core.Runtime
{
get
{
return _fact;
return m_gameFacts;
}
set
{
_fact = value;
m_gameFacts = value;
}
}
@ -135,7 +135,7 @@ namespace Core.Runtime
{
GeneralSettings settings = new GeneralSettings
{
Language = EnumLanguage.English,
Language = EnumLanguage.French,
};
SetFact("GeneralSettings", settings, FactPersistence.Persistent);
SaveFacts("GeneralSettings");

View file

@ -30,8 +30,6 @@ namespace Decor.Runtime
void Start()
{
_player = GetFact<PlayerClass>(GameManager.Instance.Profile);
Info($"Player : {_player.GuildName}");
QuestManager.Instance.NotifyAvailableQuestsUpdated(_player.GuildLevel);
UpdateParchmentState();
}

View file

@ -74,7 +74,11 @@ namespace Quests.Runtime
case TargetingType.AllHeroes:
return assignedAdventurers;
case TargetingType.RandomHero:
return new List<AdventurerClass> { assignedAdventurers[UnityEngine.Random.Range(0, assignedAdventurers.Count)] };
if (assignedAdventurers == null || assignedAdventurers.Count == 0)
return new List<AdventurerClass>();
int count = assignedAdventurers.Count;
int idx = UnityEngine.Random.Range(0, count);
return new List<AdventurerClass> { assignedAdventurers[idx] };
case TargetingType.LowestHp:
return assignedAdventurers
.OrderBy(adventurer => adventurer.Health)

View file

@ -56,7 +56,7 @@ namespace Quests.Runtime
public List<ItemReward> Rewards
{
get { return _rewards; }
set { _rewards = new List<ItemReward>(); }
set { _rewards = value ?? new List<ItemReward>(); }
}
public QuestStateEnum State
@ -215,12 +215,35 @@ namespace Quests.Runtime
public static AdventurerClass GetOneAdventurerFromId(Guid adventurerId)
{
if (GameManager.Instance.Fact.GetFact<List<AdventurerClass>>("my_adventurers") == null)
// Guard against GameManager or Fact system not ready
Debug.Log($"ID recherché : {adventurerId}");
if (GameManager.Instance == null)
{
Debug.Log("GameManager introuvable ou pas initialisé");
return null;
}
List<AdventurerClass> currentAdventurers = GameManager.Instance.Fact.GetFact<List<AdventurerClass>>("my_adventurers");
return currentAdventurers.Find(adventurer => adventurer.ID == adventurerId);
if (GameManager.Instance.Fact == null)
{
Debug.Log("Fact introuvable ou null");
return null;
}
var list = GameManager.Instance.Fact.GetFact<List<AdventurerClass>>("my_adventurers");
if (list == null)
{
Debug.Log("Liste des aventuriers est null");
return null;
}
else
{
Debug.Log($"Nombre d'Aventuriers : {list.Count}");
}
var adventurer = list.Find(adventurer => adventurer != null && adventurer.ID == adventurerId);
Debug.Log($"retourne : {adventurer}");
return list.Find(adventurer => adventurer != null && adventurer.ID == adventurerId);
}
public static List<Guid> GetIdFromAdventurers(List<AdventurerClass> adventurers)
@ -235,8 +258,13 @@ namespace Quests.Runtime
public static Guid GetOneIdFromAdventurer(AdventurerClass adventurer)
{
List<AdventurerClass> currentAdventurers = GameManager.Instance.Fact.GetFact<List<AdventurerClass>>("my_adventurers");
return currentAdventurers.Find(adv => adv.ID == adventurer.ID).ID;
if (adventurer == null || GameManager.Instance == null || GameManager.Instance.Fact == null)
{
return Guid.Empty;
}
var currentAdventurers = GameManager.Instance.Fact.GetFact<List<AdventurerClass>>("my_adventurers");
var adv = currentAdventurers?.Find(a => a != null && a.ID == adventurer.ID);
return adv != null ? adv.ID : Guid.Empty;
}
#endregion

View file

@ -45,11 +45,33 @@ namespace Quests.Runtime
return;
}
Instance = this;
if (_activeEvents == null) _activeEvents = new List<QuestEvent>();
}
void OnEnable()
{
GameManager.OnTimeAdvanced += CheckMissionsProgress;
}
void OnDisable()
{
GameManager.OnTimeAdvanced -= CheckMissionsProgress;
}
void Start()
{
_disponibleQuests = _questDatabase.GetAll().SelectMany(f => f.questTemplates).Select(t => t.ToQuestClass(QuestStateEnum.Disponible)).ToList();
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>();
@ -112,8 +134,7 @@ namespace Quests.Runtime
public QuestManager()
{
_activeEvents = new List<QuestEvent>();
GameManager.OnTimeAdvanced += CheckMissionsProgress;
// Unity will call constructor before Awake; avoid subscribing here.
}
#endregion
@ -166,7 +187,16 @@ namespace Quests.Runtime
{
QuestClass quest = GetQuestById(questId);
List<QuestEventLog> events = GetFact<Dictionary<Guid, List<QuestEventLog>>>("events_quests_history")[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);
}
@ -185,8 +215,8 @@ namespace Quests.Runtime
/// </summary>
public bool IsQuestCompleted(string questName)
{
return ActiveQuests != null
&& ActiveQuests.Any(q => q.Name == questName);
return CompletedQuests != null
&& CompletedQuests.Any(q => q.Name == questName);
}
/// <summary>
@ -195,11 +225,20 @@ namespace Quests.Runtime
public List<QuestClass> ResolveQuestsList(List<QuestClass> questsFromSave)
{
List<QuestClass> quests = new List<QuestClass>();
foreach (var quest in questsFromSave)
foreach (var saved in questsFromSave)
{
QuestTemplate template = _questDatabase.GetTemplatesByName(quest.Name);
QuestTemplate template = _questDatabase.GetTemplatesByName(saved.Name);
if (template == null) continue;
quests.Add(template.ToQuestClass(quest.State, quest.ID));
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;
}
@ -237,6 +276,18 @@ namespace Quests.Runtime
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>
@ -277,11 +328,26 @@ namespace Quests.Runtime
/// </summary>
void ReleaseAdventurers(List<Guid> team)
{
bool anyChanged = false;
foreach (var adventurerId in team)
{
AdventurerClass adventurer = QuestClass.GetOneAdventurerFromId(adventurerId);
if (adventurer != null)
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();
}
}
@ -318,7 +384,8 @@ namespace Quests.Runtime
void CheckMissionsProgress(int currentSeconds)
{
if(_activeQuests == null) return;
var questsToComplete = new List<QuestClass>();
var activeQuests = _activeQuests.Where(q => q.State == QuestStateEnum.InProgress).ToList();
@ -344,14 +411,23 @@ namespace Quests.Runtime
/// </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)
{
if (Random.Range(0f, 100f) <= questEvent.PercentTrigger)
float percent = Mathf.Clamp(questEvent.PercentTrigger, 0f, 100f);
if (Random.Range(0f, 100f) <= percent)
{
TriggerEvent(questEvent, quest);
quest.TriggeredEventsDescriptionKeys.Add(questEvent.DescriptionKey);
@ -371,16 +447,22 @@ namespace Quests.Runtime
var targets = questEvent.GetTargets(quest.AssignedAdventurersID);
ApplyEffect(questEvent.Effects, targets);
Dictionary<Guid, List<QuestEventLog>> events = GetFact<Dictionary<Guid, List<QuestEventLog>>>("events_quests_history");
if(!events.ContainsKey(quest.ID))
var eventsDict = GetFact<Dictionary<Guid, List<QuestEventLog>>>("events_quests_history");
if (eventsDict == null)
{
events.Add(quest.ID, new List<QuestEventLog>());
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);
events[quest.ID].Add(questEventLog);
eventsDict[quest.ID].Add(questEventLog);
if (_activeEvents == null) _activeEvents = new List<QuestEvent>();
_activeEvents.Add(questEvent);
SaveFacts();
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: efcd90c5fc4a4d29a9edb30408d27e72
timeCreated: 1760191011

View file

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using Quests.Runtime;
using Core.Runtime;
namespace GuildTycoon.Tests.Editor
{
public class QuestManagerTests
{
private GameObject _gmGO;
private GameObject _qmGO;
private GameManager _gameManager;
private QuestManager _questManager;
[SetUp]
public void SetUp()
{
// Create and initialize GameManager (Awake will set up facts dictionary)
_gmGO = new GameObject("GameManager_Test");
_gameManager = _gmGO.AddComponent<GameManager>();
// Ensure profile and base facts are set
_gameManager.Profile = "UnitTestProfile";
_gameManager.Fact = new FactDictionnary();
GameManager.m_gameFacts = _gameManager.Fact; // ensure BaseMonobehaviour access works
// Pre-seed facts used by QuestManager
_gameManager.Fact.SetFact("accepted_quests", new List<QuestClass>(), BaseMonobehaviour.FactPersistence.Normal);
_gameManager.Fact.SetFact("events_quests_history", new Dictionary<Guid, List<QuestEventLog>>(), BaseMonobehaviour.FactPersistence.Normal);
// Create QuestManager
_qmGO = new GameObject("QuestManager_Test");
_questManager = _qmGO.AddComponent<QuestManager>();
// Initialize lists to avoid nulls and isolate from Start() DB usage
_questManager.ActiveQuests = new List<QuestClass>();
_questManager.CompletedQuests = new List<QuestClass>();
_questManager.DisponibleQuests = new List<QuestClass>();
}
[TearDown]
public void TearDown()
{
if (_qmGO != null) UnityEngine.Object.DestroyImmediate(_qmGO);
if (_gmGO != null) UnityEngine.Object.DestroyImmediate(_gmGO);
}
[Test]
public void CompletingQuest_MovesItFromActiveToCompleted_AndUpdatesFacts()
{
// Arrange: create a simple quest in progress
var questId = Guid.NewGuid();
var quest = new QuestClass(questId, "Test Quest", "Desc", "Obj", duration: 1, difficulty: QuestDifficultyEnum.Easy, reward: new List<Item.Runtime.ItemReward>(), minLevel: 1)
{
State = QuestStateEnum.InProgress,
StartSeconds = 0,
EndSeconds = 10,
AssignedAdventurersID = new List<Guid>()
};
_questManager.ActiveQuests.Add(quest);
// Seed facts with the same quest so QuestManager can update saved state
var accepted = new List<QuestClass> { new QuestClass(questId, quest.Name, quest.Description, quest.Objective, quest.Duration, quest.Difficulty, quest.Rewards, quest.MinLevel) { State = QuestStateEnum.InProgress } };
_gameManager.Fact.SetFact("accepted_quests", accepted, BaseMonobehaviour.FactPersistence.Normal);
QuestClass completedRaised = null;
Action<QuestClass> handler = q => completedRaised = q;
QuestManager.OnQuestCompleted += handler;
try
{
// Act: Directly complete the quest
_questManager.CompleteQuest(quest, quest.AssignedAdventurersID);
}
finally
{
QuestManager.OnQuestCompleted -= handler; // ensure unsubscribe even if assertion fails
}
// Assert: quest state and lists updated
Assert.AreEqual(QuestStateEnum.Completed, quest.State, "La quête devrait être marquée comme complétée.");
CollectionAssert.DoesNotContain(_questManager.ActiveQuests, quest, "La quête ne doit plus être dans la liste des quêtes actives.");
CollectionAssert.Contains(_questManager.CompletedQuests, quest, "La quête devrait apparaître dans la liste des quêtes complétées.");
// Facts: accepted_quests should reflect Completed state for the same ID
var saved = _gameManager.Fact.GetFact<List<QuestClass>>("accepted_quests");
var savedQuest = saved.Find(q => q.ID == questId);
Assert.IsNotNull(savedQuest, "La quête devrait être présente dans les faits 'accepted_quests'.");
Assert.AreEqual(QuestStateEnum.Completed, savedQuest.State, "L'état sauvegardé de la quête devrait être 'Completed'.");
// Event notification raised
Assert.IsNotNull(completedRaised, "Un événement OnQuestCompleted devrait être déclenché.");
Assert.AreEqual(questId, completedRaised.ID, "L'événement devrait concerner la même quête.");
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 64e828a55046493c82c79b4ed49e10ba
timeCreated: 1760191011

View file

@ -112,7 +112,6 @@ namespace GameUI.Runtime
_panelRecap.SetActive(false);
break;
case QuestStateEnum.InProgress:
Info("La quête est active.");
_buttonActivation.SetActive(false);
_adventurersOnThisQuestPanel.SetActive(true);
_adventurersSelection.SetActive(false);

View file

@ -60,19 +60,17 @@ namespace GameUI.Runtime
// Ensure state order: Disponible -> Accepted
_quest.State = QuestStateEnum.Accepted;
// Persist in saved quests list
List<QuestClass> savedQuests = GetFact<List<QuestClass>>("accepted_quests");
savedQuests.Add(_quest);
// Also add to runtime active quests list so other systems can find it
if (FactExists<List<QuestClass>>("accepted_quests", out _))
// Persist in saved quests list, ensure it exists
var savedQuests = GetFact<List<QuestClass>>("accepted_quests");
if (savedQuests == null)
{
var acceptedQuests = GetFact<List<QuestClass>>("accepted_quests");
// Avoid duplicates by GUID
if (!acceptedQuests.Any(q => q.ID == _quest.ID))
{
acceptedQuests.Add(_quest);
}
savedQuests = new List<QuestClass>();
SetFact("accepted_quests", savedQuests, FactPersistence.Persistent);
}
// Avoid duplicates by GUID
if (!savedQuests.Any(q => q.ID == _quest.ID))
{
savedQuests.Add(_quest);
}
SaveFacts();
@ -83,7 +81,6 @@ namespace GameUI.Runtime
QuestManager.Instance.NotifyAvailableQuestsUpdated(player.GuildLevel);
// Remove the accepted card from the board
Info($"<color=green>Quest accepted?: {_quest.Name} : {_quest.State}</color>");
Destroy(gameObject);
}

View file

@ -92,7 +92,6 @@ namespace GameUI.Runtime
#endregion
#region privates and protected
[SerializeField] GameObject _panel;