using System; using System.Collections.Generic; using System.Linq; using Cthangover.Core.Characters; using Cthangover.Core.Settings; using Cthangover.Core.Utils; namespace Cthangover.Core.Relationship { /// /// Central registry and lifecycle dispatcher for recruit behaviours /// and conditions. Behaviours () and /// conditions () of every mod arrive /// through the kernel's assembly consumers /// (, ) /// as mods load, and leave again when their mod unloads — the kernel /// stays the only place that enumerates assemblies. /// /// Condition gating — evaluates every /// registered condition conjunctively (logical AND), so a single /// false blocks recruitment. This allows mods to compose /// recruitment gates without coordination. /// /// Id-based deduplication — a registration replaces any earlier /// implementation with the same Id, so two mods shipping the same /// id never double-dispatch notifications. /// /// Tick registration — the first call to any lifecycle method /// triggers , /// which registers for the scene's timer event. This avoids paying /// the tick overhead unless at least one behaviour is active. /// /// Singleton — accessed via the static Instance field /// rather than a Lazy<T> wrapper because Godot /// reflection-based discovery must tolerate partial-initialisation /// scenarios where Lazy's thread-safety check could deadlock. /// public class RecruitBehaviourRegistry { /// Eagerly-initialised singleton for reflection-safe access. public static readonly RecruitBehaviourRegistry Instance = new(); private readonly List _behaviours = new(); private readonly List _conditions = new(); private readonly RecruitTickController _tickController = new(); private void EnsureTickRegistered() { _tickController.EnsureRegistered(); } /// /// Registers a behaviour, replacing an earlier one with the same Id. Fed /// by for every mod assembly. /// public void RegisterBehaviour(IRecruitBehaviour behaviour) { if (behaviour == null) return; _behaviours.RemoveAll(existing => existing.Id == behaviour.Id); _behaviours.Add(behaviour); } /// Removes exactly (its mod is being unloaded). public void UnregisterBehaviour(IRecruitBehaviour behaviour) { _behaviours.Remove(behaviour); } /// /// Registers a condition, replacing an earlier one with the same Id. Fed /// by for every mod assembly. /// public void RegisterCondition(IRecruitCondition condition) { if (condition == null) return; _conditions.RemoveAll(existing => existing.Id == condition.Id); _conditions.Add(condition); } /// Removes exactly (its mod is being unloaded). public void UnregisterCondition(IRecruitCondition condition) { _conditions.Remove(condition); } /// /// Conjunctive gate: returns true only if every registered /// returns true for the /// given enemy and runtime state. /// public bool CanRecruit(Character enemy, RuntimeData runtime) { return _conditions.All(c => c.CanRecruit(enemy, runtime)); } /// /// Looks up a behaviour by its Id (linear scan — /// behaviour lists are small enough that a dictionary is /// unnecessary). Returns null if not found. /// public IRecruitBehaviour Get(string id) { return _behaviours.Find(b => b.Id == id); } /// Returns all registered behaviours for external enumeration. public IEnumerable All() { return _behaviours; } /// /// Dispatches to every /// behaviour for every active recruit. Ensures tick registration /// on first call via lazy init. /// public void OnTick() { EnsureTickRegistered(); var runtime = GameData.Instance.Runtime; if (runtime == null) return; var recruits = RecruitingData.Instance.Data; foreach (var behaviour in _behaviours) { foreach (var recruit in recruits) { behaviour.OnTick(recruit, runtime, runtime.Time.Tick); } } } /// /// Dispatches to /// every behaviour for the newly-added . /// public void OnConfigure(Recruit recruit) { EnsureTickRegistered(); var runtime = GameData.Instance.Runtime; if (runtime == null) return; foreach (var behaviour in _behaviours) behaviour.ConfigureRecruit(recruit, runtime); } /// /// Dispatches to every /// behaviour for the departing . /// public void OnRemove(Recruit recruit) { EnsureTickRegistered(); var runtime = GameData.Instance.Runtime; if (runtime == null) return; foreach (var behaviour in _behaviours) behaviour.OnRemove(recruit, runtime); } } }