#nullable enable using System.Collections.Generic; using Godot; namespace Cirno.Scripts.Components.FSM; public abstract partial class StateMachineBase : Node, IStateMachine where TKey : notnull where TType : Node { [Signal] public delegate void StateChangedEventHandler(Variant from, Variant to); public Dictionary> States { get; set; } = new(); public TKey CurrentStateIndex { get; set; } public TKey PreviousStateIndex { get; private set; } public IState CurrentState => States[CurrentStateIndex]; public abstract TKey InitialState { get; protected set; } private TType _mainObject = default!; public TType MainObject => _mainObject; // Internal flag to indicate if a state has been set previously private bool _hasState; public override void _Ready() { _mainObject = this.GetParent(); var children = GetChildren(); foreach (var child in children) { if (child is IState state) { States.Add(state.StateId, state); state.Init(this); } } SetState(InitialState); } public TKey GetState() { return CurrentState.StateId; } public void SetState(TKey stateId) { if (_hasState) { PreviousStateIndex = CurrentStateIndex; CurrentState.ExitState(); } var from = _hasState ? CurrentStateIndex.ToString() : string.Empty; CurrentStateIndex = stateId; var to = CurrentStateIndex.ToString(); CurrentState.EnterState(); _hasState = true; EmitSignal(nameof(StateChanged), from, to); } /// /// Returns the first descendant under the main object (root node that owns this state machine) /// that matches the requested type T, or null if none found. This lets states fetch /// modules attached to the actor without per-state editor wiring. /// public T? GetModule() where T : Node { if (MainObject is null) return default; return FindInChildren(MainObject); } private static T? FindInChildren(Node parent) where T : Node { foreach (var obj in parent.GetChildren()) { if (obj is T t) return t; if (obj is Node node) { var found = FindInChildren(node); if (found is not null) return found; } } return default; } public override void _Process(double delta) { if (!_hasState) return; CurrentState.ProcessState(delta); } public override void _PhysicsProcess(double delta) { if (!_hasState) return; CurrentState.PhysicsProcessState(delta); } }