using System; using System.Collections.Generic; namespace Jovian.EncounterSystem { /// /// Gated quest progression. Encounter E is gated iff some QuestKind encounter P has /// P.nextEncounter == E and P hasn't been resolved. Predecessor map is built once at /// construction; rolling and advancement are O(1). /// public class QuestProgress { private readonly HashSet resolvedIds = new(); private readonly Dictionary predecessorOf = new(); public event Action QuestStarted; public event Action QuestAdvanced; public event Action QuestCompleted; public IReadOnlyCollection ResolvedIds => resolvedIds; public QuestProgress(EncountersCollection encountersCollection) { IndexQuests(encountersCollection); } public bool IsGated(IEncounter encounter) { var id = encounter?.EncounterDefinition?.internalId; if(string.IsNullOrEmpty(id)) { return false; } if(!predecessorOf.TryGetValue(id, out var predecessor)) { return false; } return !resolvedIds.Contains(predecessor.EncounterDefinition.internalId); } public void OnEncounterTriggered(IEncounter encounter) { if(encounter?.Kind is not QuestKind questKind) { return; } var id = encounter.EncounterDefinition?.internalId; if(string.IsNullOrEmpty(id) || !resolvedIds.Add(id)) { return; } var next = questKind.nextEncounter.Resolve(); var hasPredecessor = predecessorOf.TryGetValue(id, out var predecessor); if(!hasPredecessor) { QuestStarted?.Invoke(encounter); } else { QuestAdvanced?.Invoke(predecessor, encounter); } if(next == null) { QuestCompleted?.Invoke(encounter); } } public void LoadResolvedIds(IEnumerable ids) { resolvedIds.Clear(); if(ids == null) { return; } foreach(var id in ids) { if(!string.IsNullOrEmpty(id)) { resolvedIds.Add(id); } } } private void IndexQuests(EncountersCollection collection) { if(collection?.encounterTables == null) { return; } foreach(var table in collection.encounterTables) { if(table?.encounters == null) { continue; } foreach(var encounter in table.encounters) { if(encounter?.Kind is not QuestKind questKind) { continue; } var next = questKind.nextEncounter.Resolve(); if(next == null) { continue; } var nextId = next.EncounterDefinition?.internalId; if(!string.IsNullOrEmpty(nextId)) { predecessorOf[nextId] = encounter; } } } } } }