using Cirno.Scripts.Components.Actors; using Cirno.Scripts.Enums; using Godot; namespace Cirno.Scripts.Components.FSM.Enemy._3D; public partial class InitState : EnemyStateBase3D { public override EnemyState StateId => EnemyState.Init; private EnemyStorage3D Storage { get; set; } private PlayerDetection3D DetectionProvider { get; set; } private ActorResourceProvider HealthProvider { get; set; } private int _initRetryCount = 0; private const int MaxInitRetries = 20; private const float InitRetryDelaySeconds = 0.05f; public override void Init(IStateMachine machine) { base.Init(machine); // Try an initial resolve; EnterState will retry if necessary Storage ??= StateMachine.GetModule(); DetectionProvider ??= StateMachine.GetModule(); HealthProvider ??= StateMachine.GetModule(); } public override void EnterState() { // Attempt to complete initialization; if modules are missing, retry with a timer TryCompleteInit(); } private void TryCompleteInit() { // Attempt to resolve modules again in case they were attached after Init Storage ??= StateMachine.GetModule(); DetectionProvider ??= StateMachine.GetModule(); HealthProvider ??= StateMachine.GetModule(); if (Storage is null || DetectionProvider is null || HealthProvider is null) { _initRetryCount++; if (_initRetryCount <= MaxInitRetries) { // Schedule a retry shortly to wait for other nodes to finish _Ready var timer = GetTree().CreateTimer(InitRetryDelaySeconds); timer.Timeout += TryCompleteInit; return; } // Final failure: log detailed error and bail out to avoid throws var actorName = MainObject?.Name ?? ""; GD.PrintErr($"[InitState] Failed to resolve required modules on actor '{actorName}' after {_initRetryCount} attempts.\n" + $" Storage: {(Storage is null ? "MISSING" : "OK")},\n" + $" DetectionProvider: {(DetectionProvider is null ? "MISSING" : "OK")},\n" + $" HealthProvider: {(HealthProvider is null ? "MISSING" : "OK")}"); return; } // All modules present, proceed with initialization DetectionProvider.Initialize(MainObject); Storage.AiState = Storage.Root.StartingAiState; Storage.HomePosition = MainObject.GlobalPosition; HealthProvider.MaxResource = Storage.Root.EnemyResource.MaxHealth; HealthProvider.FillResource(); // Transition to the Idle state now that initialization is complete StateMachine.SetState(EnemyState.Idle); } }