using System; using System.Collections.Generic; using System.Linq; using JXHelper.Common; using JXHelper.Config; using JXHelper.Platform; namespace JXHelper.Core; /// /// Bộ điều phối đa tab game (Multi-Instance Coordinator). /// Quản lý danh sách các BotSession độc lập, hỗ trợ Start/Stop hàng loạt và đồng bộ cửa sổ. /// public class BotCoordinator { private readonly List _sessions = new(); private readonly object _lock = new(); public IReadOnlyList Sessions { get { lock (_lock) { return _sessions.ToList(); } } } public BotSession? ActiveSession { get; set; } public event Action? OnSessionsChanged; public event Action? OnSessionUpdated; public event Action? OnSessionLog; public void SyncWindows(BotConfig? baseConfig = null) { lock (_lock) { var detected = WindowManager.FindGameWindows(); var detectedHandles = detected.Select(d => d.Handle).ToHashSet(); // 1. Dừng và gỡ bỏ các session có cửa sổ đã đóng for (int i = _sessions.Count - 1; i >= 0; i--) { var s = _sessions[i]; if (!detectedHandles.Contains(s.WindowInfo.Handle) || !NativeMethods.IsWindow(s.WindowInfo.Handle)) { s.Stop(); _sessions.RemoveAt(i); } } // 2. Thêm mới các cửa sổ game chưa có trong danh sách foreach (var win in detected) { var existing = _sessions.FirstOrDefault(s => s.WindowInfo.Handle == win.Handle); if (existing != null) { // Cập nhật lại tiêu đề / trạng thái thu nhỏ / tên nhân vật nếu có thay đổi existing.WindowInfo = win; if (string.IsNullOrEmpty(existing.Config.CharacterName) && !string.IsNullOrEmpty(win.CharacterName)) { existing.Config.CharacterName = win.CharacterName; } } else { // Tải cấu hình độc lập cho từng nhân vật theo tên var cfg = !string.IsNullOrEmpty(win.CharacterName) ? BotConfig.LoadForCharacter(win.CharacterName) : (baseConfig != null ? CloneConfig(baseConfig) : BotConfig.Load()); if (!string.IsNullOrEmpty(win.CharacterName)) { cfg.CharacterName = win.CharacterName; } var newSession = new BotSession(win, cfg); newSession.OnStateUpdated += s => OnSessionUpdated?.Invoke(s); newSession.OnLogReceived += (s, msg) => OnSessionLog?.Invoke(s, msg); _sessions.Add(newSession); } } // Đảm bảo luôn có ActiveSession nếu danh sách không rỗng if (ActiveSession == null || !_sessions.Contains(ActiveSession)) { ActiveSession = _sessions.FirstOrDefault(); } } OnSessionsChanged?.Invoke(); } public void StartAllChecked() { lock (_lock) { foreach (var session in _sessions.Where(s => s.IsChecked)) { session.Start(); } } } public void StopAll() { lock (_lock) { foreach (var session in _sessions) { session.Stop(); } } } /// /// Đồng bộ cấu hình dùng chung (Bơm máu, Tổ đội, Hậu cần, Tuần tra) cho các tab được tích chọn. /// TUYỆT ĐỐI KHÔNG ghi đè Môn phái và Chiêu thức của từng nhân vật! /// public void ApplyConfigToAllChecked(BotConfig sourceConfig) { lock (_lock) { foreach (var session in _sessions.Where(s => s.IsChecked)) { if (session.Config != sourceConfig) { ApplyCommonSettings(sourceConfig, session.Config); session.Config.SaveForCharacter(); } } } } public int GetRunningCount() { lock (_lock) { return _sessions.Count(s => s.IsRunning); } } public BotSession? GetSessionByCharacter(string characterName) { lock (_lock) { return _sessions.FirstOrDefault(s => string.Equals(s.CharacterName, characterName, StringComparison.OrdinalIgnoreCase)); } } /// /// Điều phối chuỗi kịch bản thực địa 4 tài khoản theo yêu cầu chỉ huy: /// 1. Về thành Dương Châu & Lưu điểm hồi sinh qua Thần Hành Phù. /// 2. Truyền tống Nghiệt Long Động tuần tự: Cái Bang -> Thiếu Lâm -> Nga My -> Ngũ Độc. /// 3. Thiết lập Tổ đội: Thiếu Lâm làm chủ PT mời các thành viên, thành viên auto-accept. /// 4. Đội hình: 3 thành viên theo sau Thiếu Lâm khoảng cách 100px. /// 5. Tác chiến: Thiếu Lâm đánh quanh điểm bán kính 1000px, các thành viên hỗ trợ & xuất chiêu theo phái. /// public async Task OrchestrateFieldScenarioAsync(Action? logCallback = null) { void Log(string msg) { TelemetryLogger.LogAction(msg); logCallback?.Invoke(msg); } Log("[ĐIỀU PHỐI TÁC CHIẾN] Bắt đầu khởi chạy kịch bản thực địa 4 tài khoản..."); var caiBang = GetSessionByCharacter("BoCuaCacCon"); var thieuLam = GetSessionByCharacter("ConCuaBoMe"); var ngaMy = GetSessionByCharacter("MeCuaCacCon"); var nguDoc = GetSessionByCharacter("ConGailCung"); // Giai đoạn 1: Về thành Dương Châu & Lưu điểm hồi sinh Log("[GIAI ĐOẠN 1] Triệu hồi Thần Hành Phù về thành Dương Châu và Lưu điểm hồi sinh..."); var allSessions = new[] { caiBang, thieuLam, ngaMy, nguDoc }.Where(s => s != null).ToList(); foreach (var session in allSessions) { Log($"[DƯƠNG CHÂU] Đang thực thi cho nhân vật '{session!.CharacterName}'..."); var thp = new ThanHanhPhuExecutor(session.WindowInfo.Handle, session.Config); await thp.ExecuteReturnToDuongChauAsync(saveRespawn: true); await Task.Delay(1000); } // Giai đoạn 2: Truyền tống Nghiệt Long Động tuần tự // Thứ tự bắt buộc: (1) Cái Bang -> (2) Thiếu Lâm -> (3) Nga My -> (4) Ngũ Độc Log("[GIAI ĐOẠN 2] Truyền tống Nghiệt Long Động theo thứ tự nghiêm ngặt: Cái Bang -> Thiếu Lâm -> Nga My -> Ngũ Độc..."); var sequence = new[] { ("Cái Bang (BoCuaCacCon)", caiBang), ("Thiếu Lâm (ConCuaBoMe)", thieuLam), ("Nga My (MeCuaCacCon)", ngaMy), ("Ngũ Độc (ConGailCung)", nguDoc) }; foreach (var (role, session) in sequence) { if (session != null) { Log($"[NGHIỆT LONG ĐỘNG] Dịch chuyển {role}..."); var thp = new ThanHanhPhuExecutor(session.WindowInfo.Handle, session.Config); await thp.ExecuteReturnToNghietLongGateAsync(); await Task.Delay(1500); } else { Log($"[CẢNH BÁO] Không tìm thấy phiên làm việc cho {role}. Bỏ qua bước này."); } } // Giai đoạn 3: Thiết lập Đội hình & Tổ đội (Thiếu Lâm làm chủ PT) Log("[GIAI ĐOẠN 3] Thiết lập Tổ Đội: Thiếu Lâm (ConCuaBoMe) làm Chủ PT, các thành viên theo sau cự ly 100px..."); if (thieuLam != null) { thieuLam.Config.IsPartyLeader = true; thieuLam.Config.PartyMembers = "BoCuaCacCon,MeCuaCacCon,ConGailCung"; thieuLam.Config.LockCurrentSpot = false; thieuLam.Config.AttackRadius = 1000; thieuLam.Config.FollowLeaderEnabled = false; thieuLam.Config.SaveForCharacter(); } var followers = new[] { caiBang, ngaMy, nguDoc }.Where(s => s != null); foreach (var f in followers) { f!.Config.IsPartyLeader = false; f.Config.FollowLeaderEnabled = true; f.Config.LeaderName = "ConCuaBoMe"; f.Config.FollowDistance = 100; f.Config.AutoPartyEnabled = true; f.Config.SaveForCharacter(); } // Giai đoạn 4: Khởi động Động cơ Chiến đấu & Hỗ trợ Log("[GIAI ĐOẠN 4] Khởi động tất cả các phiên chiến đấu..."); StartAllChecked(); Log("[ĐIỀU PHỐI TÁC CHIẾN] Hoàn tất điều phối kịch bản thực địa thành công!"); return true; } private static BotConfig CloneConfig(BotConfig src) { var dst = new BotConfig(); CopyAllConfig(src, dst); return dst; } /// /// Sao chép CHỈ các thiết lập dùng chung (Hồi máu, Hậu cần, Tiện ích). /// Giữ nguyên 100% Môn phái, Chiêu tấn công và 3 Slot Chiêu Buff riêng biệt của nhân vật. /// private static void ApplyCommonSettings(BotConfig src, BotConfig dst) { // Phục hồi (Máu / Mana) dst.HpPump1Enabled = src.HpPump1Enabled; dst.HpPump1Key = src.HpPump1Key; dst.HpPump1DelayMs = src.HpPump1DelayMs; dst.HpPump2Enabled = src.HpPump2Enabled; dst.HpPump2Key = src.HpPump2Key; dst.HpPump2DelayMs = src.HpPump2DelayMs; dst.MpPump1Enabled = src.MpPump1Enabled; dst.MpPump1Key = src.MpPump1Key; dst.MpPump1DelayMs = src.MpPump1DelayMs; dst.MpPump2Enabled = src.MpPump2Enabled; dst.MpPump2Key = src.MpPump2Key; dst.MpPump2DelayMs = src.MpPump2DelayMs; dst.AlarmOnTdp = src.AlarmOnTdp; // Phân hệ Túi Máu Tân Thủ (Chuẩn Kim Yến) dst.AutoNewbiePouchEnabled = src.AutoNewbiePouchEnabled; dst.NewbiePouchIntervalSeconds = src.NewbiePouchIntervalSeconds; dst.NewbiePouchItemX = src.NewbiePouchItemX; dst.NewbiePouchItemY = src.NewbiePouchItemY; dst.NewbiePouchMenuSequence = src.NewbiePouchMenuSequence; dst.NewbiePouchPotionName = src.NewbiePouchPotionName; dst.NewbiePouchEmergencyHpPercent = src.NewbiePouchEmergencyHpPercent; // Tiện ích chung & Điều khiển dst.FollowLeaderEnabled = src.FollowLeaderEnabled; dst.LeaderName = src.LeaderName; dst.FollowDistance = src.FollowDistance; dst.AutoFollowCrossMap = src.AutoFollowCrossMap; dst.AutoHorseEnabled = src.AutoHorseEnabled; dst.AutoLootEnabled = src.AutoLootEnabled; dst.AutoPartyEnabled = src.AutoPartyEnabled; dst.AutoTtlEnabled = src.AutoTtlEnabled; dst.TtlIntervalMinutes = src.TtlIntervalMinutes; dst.TtlItemX = src.TtlItemX; dst.TtlItemY = src.TtlItemY; dst.ScheduledShutdownEnabled = src.ScheduledShutdownEnabled; dst.ShutdownMinutes = src.ShutdownMinutes; dst.ScheduledExitGameEnabled = src.ScheduledExitGameEnabled; dst.ExitGameMinutes = src.ExitGameMinutes; // Tuần tra tọa độ dst.PatrolEnabled = src.PatrolEnabled; dst.WaypointsData = src.WaypointsData; dst.PatrolIntervalSeconds = src.PatrolIntervalSeconds; // Hậu cần & Thành thị dst.PeriodicReturnEnabled = src.PeriodicReturnEnabled; dst.ReturnIntervalMinutes = src.ReturnIntervalMinutes; dst.KeyPhuVirtualCode = src.KeyPhuVirtualCode; dst.MapLoadDelayMs = src.MapLoadDelayMs; dst.RepairMode = src.RepairMode; dst.BlacksmithNpcX = src.BlacksmithNpcX; dst.BlacksmithNpcY = src.BlacksmithNpcY; dst.RepairItemX = src.RepairItemX; dst.RepairItemY = src.RepairItemY; dst.AutoBeginnerMedicineEnabled = src.AutoBeginnerMedicineEnabled; dst.MedicineIntervalMinutes = src.MedicineIntervalMinutes; dst.MedicineItemX = src.MedicineItemX; dst.MedicineItemY = src.MedicineItemY; // Bắt tùy chọn đối thoại theo text dst.NpcDialogMatchByText = src.NpcDialogMatchByText; dst.RepairMenuKeywords = src.RepairMenuKeywords; dst.RepairSubMenuKeywords = src.RepairSubMenuKeywords; dst.MedicineMenuKeywords = src.MedicineMenuKeywords; dst.TravelMenuKeywords = src.TravelMenuKeywords; dst.NpcDialogFallbackOption = src.NpcDialogFallbackOption; } private static void CopyAllConfig(BotConfig src, BotConfig dst) { ApplyCommonSettings(src, dst); dst.DetectedSect = src.DetectedSect; dst.AutoAttackEnabled = src.AutoAttackEnabled; dst.AttackMode = src.AttackMode; dst.AttackIntervalMs = src.AttackIntervalMs; dst.AttackRadius = src.AttackRadius; dst.MainAttackSkillName = src.MainAttackSkillName; dst.Buff1Enabled = src.Buff1Enabled; dst.Buff1Key = src.Buff1Key; dst.Buff1IntervalSeconds = src.Buff1IntervalSeconds; dst.Buff1SkillName = src.Buff1SkillName; dst.Buff2Enabled = src.Buff2Enabled; dst.Buff2Key = src.Buff2Key; dst.Buff2IntervalSeconds = src.Buff2IntervalSeconds; dst.Buff2SkillName = src.Buff2SkillName; dst.Buff3Enabled = src.Buff3Enabled; dst.Buff3Key = src.Buff3Key; dst.Buff3IntervalSeconds = src.Buff3IntervalSeconds; dst.Buff3SkillName = src.Buff3SkillName; dst.AssignLeftSkillEnabled = src.AssignLeftSkillEnabled; dst.DefaultLeftSkillName = src.DefaultLeftSkillName; dst.AssignRightSkillEnabled = src.AssignRightSkillEnabled; dst.DefaultRightSkillName = src.DefaultRightSkillName; dst.NgaMyHealEnabled = src.NgaMyHealEnabled; dst.NgaMyHealHpPercent = src.NgaMyHealHpPercent; dst.NgaMyHealDelayMs = src.NgaMyHealDelayMs; dst.NgaMyHealParty = src.NgaMyHealParty; dst.NgaMySupportComboEnabled = src.NgaMySupportComboEnabled; dst.NgaMySupportComboMode = src.NgaMySupportComboMode; dst.NgaMySupportIntervalSec = src.NgaMySupportIntervalSec; dst.CurseRotationEnabled = src.CurseRotationEnabled; dst.CurseTriggerMode = src.CurseTriggerMode; dst.Curse1Enabled = src.Curse1Enabled; dst.Curse1SkillName = src.Curse1SkillName; dst.Curse1IntervalMs = src.Curse1IntervalMs; dst.Curse2Enabled = src.Curse2Enabled; dst.Curse2SkillName = src.Curse2SkillName; dst.Curse2IntervalMs = src.Curse2IntervalMs; dst.AutoBuffEnabled = src.AutoBuffEnabled; dst.BuffKeys = src.BuffKeys; dst.BuffIntervalSeconds = src.BuffIntervalSeconds; dst.AutoPartyEnabled = src.AutoPartyEnabled; dst.IsPartyLeader = src.IsPartyLeader; dst.PartyMembers = src.PartyMembers; dst.AutoTtlEnabled = src.AutoTtlEnabled; dst.TtlIntervalMinutes = src.TtlIntervalMinutes; dst.TtlItemX = src.TtlItemX; dst.TtlItemY = src.TtlItemY; dst.ScheduledShutdownEnabled = src.ScheduledShutdownEnabled; dst.ShutdownMinutes = src.ShutdownMinutes; dst.ScheduledExitGameEnabled = src.ScheduledExitGameEnabled; dst.ExitGameMinutes = src.ExitGameMinutes; dst.PatrolEnabled = src.PatrolEnabled; dst.WaypointsData = src.WaypointsData; dst.PatrolIntervalSeconds = src.PatrolIntervalSeconds; dst.HpPump1Enabled = src.HpPump1Enabled; dst.HpPump1Key = src.HpPump1Key; dst.HpPump1DelayMs = src.HpPump1DelayMs; dst.HpPump2Enabled = src.HpPump2Enabled; dst.HpPump2Key = src.HpPump2Key; dst.HpPump2DelayMs = src.HpPump2DelayMs; dst.MpPump1Enabled = src.MpPump1Enabled; dst.MpPump1Key = src.MpPump1Key; dst.MpPump1DelayMs = src.MpPump1DelayMs; dst.MpPump2Enabled = src.MpPump2Enabled; dst.MpPump2Key = src.MpPump2Key; dst.MpPump2DelayMs = src.MpPump2DelayMs; dst.AlarmOnTdp = src.AlarmOnTdp; dst.PeriodicReturnEnabled = src.PeriodicReturnEnabled; dst.ReturnIntervalMinutes = src.ReturnIntervalMinutes; dst.KeyPhuVirtualCode = src.KeyPhuVirtualCode; dst.MapLoadDelayMs = src.MapLoadDelayMs; dst.RepairMode = src.RepairMode; dst.BlacksmithNpcX = src.BlacksmithNpcX; dst.BlacksmithNpcY = src.BlacksmithNpcY; dst.RepairItemX = src.RepairItemX; dst.RepairItemY = src.RepairItemY; dst.AutoBeginnerMedicineEnabled = src.AutoBeginnerMedicineEnabled; dst.MedicineIntervalMinutes = src.MedicineIntervalMinutes; dst.MedicineItemX = src.MedicineItemX; dst.MedicineItemY = src.MedicineItemY; } }