using System; using System.Threading; using System.Threading.Tasks; using JXHelper.Common; using JXHelper.Config; using JXHelper.Platform; namespace JXHelper.Core; /// /// Bộ não điều phối trung tâm vòng đời tự hành của Bot. /// Tuân thủ bất biến: INV-FSM-SINGLE-OWNER & INV-SHM-TELEMETRY-SSOT. /// public class BotBrain : IDisposable { private CancellationTokenSource? _cts; private Task? _runnerTask; private readonly SharedMemoryBridge _shm = new(); private readonly CombatEngine _combat = new(); private readonly RecoveryEngine _recovery = new(); private readonly ControlEngine _control = new(); private readonly PatrolEngine _patrol = new(); private IntPtr _activeHwnd = IntPtr.Zero; private int _activePid; private BotConfig? _currentConfig; private string _lastActionText = "Khởi tạo"; public bool IsRunning { get; private set; } public BotState CurrentState { get; private set; } = BotState.Idle; public int CompletedCycles { get; private set; } public event Action? OnLog; public event Action? OnStateChanged; public event Action? OnCountdownTick; public BotBrain() { _combat.OnLog += Log; _recovery.OnLog += Log; _control.OnLog += Log; _patrol.OnLog += Log; } private void SetState(BotState state, string actionName = "") { CurrentState = state; if (!string.IsNullOrEmpty(actionName)) _lastActionText = actionName; SyncShm(0); TelemetryLogger.LogEvent(actionName.Length > 0 ? actionName : state.ToString(), state, _activePid, _lastActionText); OnStateChanged?.Invoke(state); } private void Log(string message) { _lastActionText = message; string timestamp = DateTime.Now.ToString("HH:mm:ss"); OnLog?.Invoke($"[{timestamp}] {message}"); TelemetryLogger.LogEvent("LOG", CurrentState, _activePid, message); } private void SyncShm(int countdownSec) { _shm.UpdateTelemetry( CurrentState, _activeHwnd, _activePid, CompletedCycles, countdownSec, _currentConfig?.RepairItemX ?? 0, _currentConfig?.RepairItemY ?? 0, _currentConfig?.TravelItemX ?? 0, _currentConfig?.TravelItemY ?? 0, _lastActionText ); } public void Start(IntPtr targetHwnd, BotConfig config) { if (IsRunning) return; _activeHwnd = targetHwnd; NativeMethods.GetWindowThreadProcessId(targetHwnd, out uint pid); _activePid = (int)pid; _currentConfig = config; _cts = new CancellationTokenSource(); IsRunning = true; SetState(BotState.Running, "Bắt đầu tự hành"); Log("Bắt đầu khởi chạy bộ não tự hành..."); // Khởi động các phân hệ tác chiến & phục hồi theo chuẩn VLBS _combat.Start(targetHwnd, config); _recovery.Start(targetHwnd, config); _control.Start(targetHwnd, config); _patrol.Start(targetHwnd, config); _runnerTask = Task.Run(() => RunLoopAsync(targetHwnd, config, _cts.Token)); } public void Stop() { if (!IsRunning) return; _cts?.Cancel(); _combat.Stop(); _recovery.Stop(); _control.Stop(); _patrol.Stop(); IsRunning = false; SetState(BotState.Idle, "Đã dừng"); Log("Đã dừng hoạt động của Bot."); } public async Task TriggerImmediateCycleAsync(IntPtr targetHwnd, BotConfig config) { if (CurrentState is BotState.ReturningToTown or BotState.OpeningBag or BotState.ClickingRepairItem) { Log("[CẢNH BÁO] Đang trong một chu kỳ khác, bỏ qua yêu cầu."); return; } _activeHwnd = targetHwnd; NativeMethods.GetWindowThreadProcessId(targetHwnd, out uint pid); _activePid = (int)pid; _currentConfig = config; Log("[THỦ CÔNG] Kích hoạt chạy ngay một chu kỳ Về thành, Sửa đồ & Quay lại bãi..."); SetState(BotState.ReturningToTown, "Chu kỳ thủ công"); _combat.IsPaused = true; _patrol.IsPaused = true; using var tempCts = new CancellationTokenSource(TimeSpan.FromSeconds(45)); bool success = await ActionExecutor.ExecuteTownReturnAndRepairAsync(targetHwnd, config, Log, tempCts.Token); if (success) { CompletedCycles++; } _combat.IsPaused = false; _patrol.IsPaused = false; SetState(IsRunning ? BotState.Running : BotState.Idle, "Hoàn tất chu kỳ thủ công"); } private async Task RunLoopAsync(IntPtr targetHwnd, BotConfig config, CancellationToken ct) { try { while (!ct.IsCancellationRequested) { if (!config.PeriodicReturnEnabled || config.ReturnIntervalMinutes <= 0) { SyncShm(0); await Task.Delay(1000, ct); continue; } var interval = TimeSpan.FromMinutes(config.ReturnIntervalMinutes); var targetTime = DateTime.UtcNow.Add(interval); Log($"Đã lên lịch chu kỳ kế tiếp sau {config.ReturnIntervalMinutes} phút ({DateTime.Now.Add(interval):HH:mm:ss})."); // Đếm ngược từng giây while (DateTime.UtcNow < targetTime) { ct.ThrowIfCancellationRequested(); var remaining = targetTime - DateTime.UtcNow; int remainingSec = (int)remaining.TotalSeconds; SyncShm(remainingSec); OnCountdownTick?.Invoke(remaining); await Task.Delay(1000, ct); } // Kích hoạt chu kỳ SetState(BotState.ReturningToTown, "Bắt đầu chu kỳ định kỳ"); _combat.IsPaused = true; _patrol.IsPaused = true; bool success = await ActionExecutor.ExecuteTownReturnAndRepairAsync(targetHwnd, config, Log, ct); if (success) { CompletedCycles++; SetState(BotState.FinishedCycle, "Hoàn tất chu kỳ an toàn"); } _combat.IsPaused = false; _patrol.IsPaused = false; await Task.Delay(1000, ct); SetState(BotState.Running, "Đang chờ chu kỳ kế tiếp"); } } catch (OperationCanceledException) { // Kết thúc êm đẹp khi người dùng bấm Dừng } catch (Exception ex) { Log($"[LỖI VÒNG LẶP] {ex.Message}"); SetState(BotState.Error, ex.Message); } finally { IsRunning = false; } } public void Dispose() { Stop(); _shm.Dispose(); _control.Dispose(); _patrol.Dispose(); } }