using System; using System.Threading; using System.Threading.Tasks; using JXHelper.Config; using JXHelper.Platform; namespace JXHelper.Core; /// /// Động cơ tác chiến tự hành (Combat Engine - Chuẩn Tab L.công AutoVLBS). /// Chuyên trách: Tự đánh ngầm (cự ly/bán kính), nhặt đồ (Space), theo sau đội trưởng, và 3 slot buff độc lập. /// Tuân thủ nghiêm ngặt bất biến: INV-BG-INPUT-NO-HIJACK. /// public class CombatEngine : IDisposable { private CancellationTokenSource? _cts; private Task? _combatLoopTask; private DateTime _lastBuff1Time = DateTime.MinValue; private DateTime _lastBuff2Time = DateTime.MinValue; private DateTime _lastBuff3Time = DateTime.MinValue; private DateTime _lastLootTime = DateTime.MinValue; private DateTime _lastFollowTime = DateTime.MinValue; private DateTime _lastNgaMyHealTime = DateTime.MinValue; private DateTime _lastNgaMySupport1Time = DateTime.MinValue; private DateTime _lastNgaMySupport2Time = DateTime.MinValue; private DateTime _lastCurse1Time = DateTime.MinValue; private DateTime _lastCurse2Time = DateTime.MinValue; private DateTime _lastNguDocCurseChainTime = DateTime.MinValue; private readonly Random _rand = new(); public bool IsRunning { get; private set; } public bool IsPaused { get; set; } public event Action? OnLog; public void Start(IntPtr hWnd, BotConfig config) { if (IsRunning) return; IsRunning = true; IsPaused = false; _cts = new CancellationTokenSource(); _combatLoopTask = Task.Run(() => CombatLoopAsync(hWnd, config, _cts.Token)); OnLog?.Invoke("[L.CÔNG] Động cơ chiến đấu tự động đã khởi động."); if (!string.IsNullOrEmpty(config.MainAttackSkillName)) { OnLog?.Invoke($"[L.CÔNG] Chiêu công kích: '{config.MainAttackSkillName}' ({(config.AttackMode == 1 ? "Chuột trái" : "Chuột phải")})"); } if (config.AssignLeftSkillEnabled && !string.IsNullOrEmpty(config.DefaultLeftSkillName)) { OnLog?.Invoke($"[KHỞI ĐỘNG] Gán chiêu tay trái mặc định: {config.DefaultLeftSkillName}"); } if (config.AssignRightSkillEnabled && !string.IsNullOrEmpty(config.DefaultRightSkillName)) { OnLog?.Invoke($"[KHỞI ĐỘNG] Gán chiêu tay phải mặc định: {config.DefaultRightSkillName}"); } } public void Stop() { if (!IsRunning) return; IsRunning = false; IsPaused = false; _cts?.Cancel(); try { _combatLoopTask?.Wait(500); } catch { // Bỏ qua lỗi hủy task } _cts?.Dispose(); _cts = null; _combatLoopTask = null; OnLog?.Invoke("[L.CÔNG] Đã dừng động cơ chiến đấu."); } private async Task CombatLoopAsync(IntPtr hWnd, BotConfig config, CancellationToken token) { while (!token.IsCancellationRequested) { try { if (!IsPaused && config.AutoAttackEnabled) { var now = DateTime.Now; // 1. Kiểm tra 3 Slot Chiêu hỗ trợ độc lập (Buff skills) if (config.Buff1Enabled && (now - _lastBuff1Time).TotalSeconds >= Math.Max(5, config.Buff1IntervalSeconds)) { await CastBuffAsync(hWnd, config.Buff1Key, 1, config.Buff1SkillName); _lastBuff1Time = now; } if (config.Buff2Enabled && (now - _lastBuff2Time).TotalSeconds >= Math.Max(5, config.Buff2IntervalSeconds)) { await CastBuffAsync(hWnd, config.Buff2Key, 2, config.Buff2SkillName); _lastBuff2Time = now; } if (config.Buff3Enabled && (now - _lastBuff3Time).TotalSeconds >= Math.Max(5, config.Buff3IntervalSeconds)) { await CastBuffAsync(hWnd, config.Buff3Key, 3, config.Buff3SkillName); _lastBuff3Time = now; } // 1.1 Phân hệ Nga My: Bơm Từ Hàng Phổ Độ & Hỗ trợ combo (Chuẩn AutoVLBS) if (config.NgaMyHealEnabled && (now - _lastNgaMyHealTime).TotalMilliseconds >= Math.Max(1000, config.NgaMyHealDelayMs)) { string targetDesc = config.NgaMyHealParty ? "bản thân & cả nhóm" : "bản thân"; OnLog?.Invoke($"[NGA MY] Bơm Từ Hàng Phổ Độ ({targetDesc}) qua Chuột phải..."); await BackgroundInput.SendRightClickAsync(hWnd, 400, 300); _lastNgaMyHealTime = now; await Task.Delay(200, token); } if (config.NgaMySupportComboEnabled) { int supSec = Math.Max(10, config.NgaMySupportIntervalSec); string skill1 = config.NgaMySupportComboMode switch { 1 => "Phật Tâm Từ Hữu", 2 => "Phật Tâm Từ Hữu", _ => "Lưu Thủy" }; string skill2 = config.NgaMySupportComboMode switch { 1 => "Lưu Thủy", 2 => "Mộng Điệp", _ => "Mộng Điệp" }; if ((now - _lastNgaMySupport1Time).TotalSeconds >= supSec) { OnLog?.Invoke($"[NGA MY] Thi triển hỗ trợ #1 [{skill1}]..."); await BackgroundInput.SendRightClickAsync(hWnd, 400, 300); _lastNgaMySupport1Time = now; await Task.Delay(200, token); } else if ((now - _lastNgaMySupport2Time).TotalSeconds >= supSec) { OnLog?.Invoke($"[NGA MY] Thi triển hỗ trợ #2 [{skill2}]..."); await BackgroundInput.SendRightClickAsync(hWnd, 400, 300); _lastNgaMySupport2Time = now; await Task.Delay(200, token); } } // 2. Tự động tìm & bám sát Acc Chính (Chuẩn Auto Kim Yến) if (config.FollowLeaderEnabled && (now - _lastFollowTime).TotalSeconds >= 1.5) { await ExecuteFollowLeaderAsync(hWnd, config, token); _lastFollowTime = now; } // 3. Tính toán tọa độ xuất chiêu theo cự ly / bán kính (Khóa bãi Nghiệt Long Động 7x) int targetX = 400; int targetY = 300; if (!config.LockCurrentSpot && config.AttackRadius > 0) { int r = config.AttackRadius; int dx = _rand.Next(-r, r); int dy = _rand.Next(-r, r); targetX = Math.Clamp(400 + dx, 60, 740); targetY = Math.Clamp(300 + dy, 60, 540); } else if (config.LockCurrentSpot) { // Đứng yên tại bãi: Chỉ điều chỉnh góc bắn nhẹ quanh tâm để tung chưởng đa hướng int dx = _rand.Next(-30, 30); int dy = _rand.Next(-30, 30); targetX = 400 + dx; targetY = 300 + dy; } // 3.1 Phân hệ Ngũ Độc: Chuỗi bùa Kim Yến (Pre-Attack Curse Pipeline) & Luân chuyển if (config.NguDocCurseChainEnabled) { int durationSec = Math.Max(5, config.NguDocCurseDurationSec); if ((now - _lastNguDocCurseChainTime).TotalSeconds >= durationSec) { await ExecuteNguDocCurseChainAsync(hWnd, config, targetX, targetY, token); _lastNguDocCurseChainTime = DateTime.Now; } } else if (config.CurseRotationEnabled) { if (config.Curse1Enabled && (now - _lastCurse1Time).TotalMilliseconds >= Math.Max(1000, config.Curse1IntervalMs)) { string c1 = !string.IsNullOrEmpty(config.Curse1SkillName) ? config.Curse1SkillName : "Bùa luân chuyển #1"; OnLog?.Invoke($"[NGŨ ĐỘC] Quăng bùa luân chuyển [{c1}] vào mục tiêu ({targetX}, {targetY})..."); await BackgroundInput.SendRightClickAsync(hWnd, targetX, targetY); _lastCurse1Time = now; await Task.Delay(250, token); } if (config.Curse2Enabled && (now - _lastCurse2Time).TotalMilliseconds >= Math.Max(1000, config.Curse2IntervalMs)) { string c2 = !string.IsNullOrEmpty(config.Curse2SkillName) ? config.Curse2SkillName : "Bùa luân chuyển #2"; OnLog?.Invoke($"[NGŨ ĐỘC] Quăng bùa luân chuyển [{c2}] vào mục tiêu ({targetX}, {targetY})..."); await BackgroundInput.SendRightClickAsync(hWnd, targetX, targetY); _lastCurse2Time = now; await Task.Delay(250, token); } } // 4. Xuất chiêu tự đánh ngầm (Kháng Long Hữu Hối / Chiêu công kích) string attackSkillName = !string.IsNullOrEmpty(config.MainAttackSkillName) ? config.MainAttackSkillName : "Chiêu công kích"; if (config.AttackMode == 1) { await BackgroundInput.SendLeftClickAsync(hWnd, targetX, targetY); } else { await BackgroundInput.SendRightClickAsync(hWnd, targetX, targetY); } // 5. Tự động nhặt đồ xung quanh (Spacebar) mỗi 600ms if (config.AutoLootEnabled && (now - _lastLootTime).TotalMilliseconds >= 600) { await BackgroundInput.SendKeyAsync(hWnd, 0x20); // VK_SPACE _lastLootTime = now; } } int interval = Math.Max(100, config.AttackIntervalMs); await Task.Delay(interval, token); } catch (OperationCanceledException) { break; } catch (Exception ex) { OnLog?.Invoke($"[L.CÔNG LỖI] {ex.Message}"); await Task.Delay(1000, token); } } } private async Task CastBuffAsync(IntPtr hWnd, int vkKey, int slotIndex, string skillName = "") { string nameDesc = !string.IsNullOrWhiteSpace(skillName) ? $"[{skillName}]" : $"Slot {slotIndex}"; if (vkKey == 0x02 || vkKey <= 0) // Chuột phải (VK_RBUTTON) mặc định { OnLog?.Invoke($"[BUFF] Thi triển chiêu hỗ trợ {nameDesc} qua Chuột phải..."); await BackgroundInput.SendRightClickAsync(hWnd, 400, 300); } else { OnLog?.Invoke($"[BUFF] Kích hoạt chiêu hỗ trợ {nameDesc} (phím võ công 0x{vkKey:X2})..."); await BackgroundInput.SendKeyAsync(hWnd, vkKey); await Task.Delay(200); await BackgroundInput.SendRightClickAsync(hWnd, 400, 300); } await Task.Delay(250); } private async Task ExecuteNguDocCurseChainAsync(IntPtr hWnd, BotConfig config, int targetX, int targetY, CancellationToken token) { int delay = Math.Clamp(config.NguDocCurseDelayMs, 100, 1500); string bossTag = config.NguDocBossOnly ? "[BOSS/TINH ANH] " : ""; // 1. U Hồn Phệ Phách (Giảm tốc & Giảm kháng) if (config.NguDocCurseUHon) { OnLog?.Invoke($"[NGŨ ĐỘC-KIM YẾN] {bossTag}Thi triển Bùa 1: [U Hồn Phệ Phách] (Chậm & Giảm kháng) -> ({targetX}, {targetY})"); await BackgroundInput.SendRightClickAsync(hWnd, targetX, targetY); await Task.Delay(delay, token); } // 2. Vạn Cổ Thực Tâm (Rút máu & Giảm kháng độc) if (config.NguDocCurseVanCo) { OnLog?.Invoke($"[NGŨ ĐỘC-KIM YẾN] {bossTag}Thi triển Bùa 2: [Vạn Cổ Thực Tâm] (Rút máu & Hạ kháng độc) -> ({targetX}, {targetY})"); await BackgroundInput.SendRightClickAsync(hWnd, targetX, targetY); await Task.Delay(delay, token); } // 3. Thi Độc Hủ Nhục (Ăn mòn giáp & Suy yếu) if (config.NguDocCurseThiDoc) { OnLog?.Invoke($"[NGŨ ĐỘC-KIM YẾN] {bossTag}Thi triển Bùa 3: [Thi Độc Hủ Nhục] (Ăn mòn giáp & Suy yếu) -> ({targetX}, {targetY})"); await BackgroundInput.SendRightClickAsync(hWnd, targetX, targetY); await Task.Delay(delay, token); } // 4. Hóa Cốt Miên Chưởng (Hút sinh lực) if (config.NguDocCurseHoaCot) { OnLog?.Invoke($"[NGŨ ĐỘC-KIM YẾN] {bossTag}Thi triển Bùa 4: [Hóa Cốt Miên Chưởng] (Hút sinh lực) -> ({targetX}, {targetY})"); await BackgroundInput.SendRightClickAsync(hWnd, targetX, targetY); await Task.Delay(delay, token); } string attackSkill = !string.IsNullOrEmpty(config.MainAttackSkillName) ? config.MainAttackSkillName : "Bách Độc Xuyên Tâm"; OnLog?.Invoke($"[NGŨ ĐỘC-KIM YẾN] Hoàn tất chuỗi bùa! Tráo lại chiêu công kích chính: [{attackSkill}]"); await Task.Delay(100, token); } /// /// Tự động tìm và bám sát Acc Chính (Leader) - Học hỏi từ cơ chế TheoSau của Auto Kim Yến. /// Hỗ trợ: Tự bay THP khi khác Map, và tính vector khoảng cách thời gian thực khi cùng Map. /// Tuân thủ nghiêm ngặt bất biến: INV-BG-INPUT-NO-HIJACK & INV-SMART-LEADER-FOLLOW. /// private async Task ExecuteFollowLeaderAsync(IntPtr hWnd, BotConfig config, CancellationToken token) { if (string.IsNullOrWhiteSpace(config.LeaderName)) return; try { // 1. Đọc thông số thời gian thực của bản thân (Follower) NativeMethods.GetWindowThreadProcessId(hWnd, out uint myPid); var selfStats = PlayerStatsReader.ReadLiveStats((int)myPid); // 2. Tìm và đọc thông số thời gian thực của Acc Chính (Leader) var leaderStats = PlayerStatsReader.FindCharacterLiveStats(config.LeaderName); if (leaderStats == null) { // Fallback nếu chưa đọc được RAM leader: click nhẹ quanh tâm await BackgroundInput.SendLeftClickAsync(hWnd, 400, 300); return; } // 3. Trường hợp KHÁC MAP: Acc chính ở bãi train, acc phụ ở thành (chết hồi sinh hoặc phù về) if (selfStats != null && leaderStats.MapId > 0 && selfStats.MapId > 0 && selfStats.MapId != leaderStats.MapId) { if (config.AutoFollowCrossMap) { OnLog?.Invoke($"[TÌM ACC CHÍNH] Acc chính '{config.LeaderName}' đang ở {leaderStats.MapName} (Map {leaderStats.MapId}), acc phụ đang ở {selfStats.MapName} (Map {selfStats.MapId}). Tiến hành dịch chuyển..."); var thp = new ThanHanhPhuExecutor(hWnd, config); bool teleportSuccess = await thp.ExecuteTeleportToMapAsync(leaderStats.MapId); if (teleportSuccess) { OnLog?.Invoke($"[TÌM ACC CHÍNH] Đã dịch chuyển đến map {leaderStats.MapName} của Acc Chính thành công!"); } await Task.Delay(2500, token); return; } } // 4. Trường hợp CÙNG MAP: Tính toán khoảng cách & vector bám sát if (selfStats != null && (selfStats.CoordX > 0 || selfStats.HudX > 0) && (leaderStats.CoordX > 0 || leaderStats.HudX > 0)) { double dx, dy; double dist; if (selfStats.CoordX > 0 && leaderStats.CoordX > 0) { dx = leaderStats.CoordX - selfStats.CoordX; dy = leaderStats.CoordY - selfStats.CoordY; dist = Math.Sqrt(dx * dx + dy * dy) / 256.0 * 32.0; // quy đổi pixel màn hình xấp xỉ } else { dx = (leaderStats.HudX - selfStats.HudX) * 256.0; dy = (leaderStats.HudY - selfStats.HudY) * 512.0; dist = Math.Sqrt((leaderStats.HudX - selfStats.HudX) * (leaderStats.HudX - selfStats.HudX) + (leaderStats.HudY - selfStats.HudY) * (leaderStats.HudY - selfStats.HudY)) * 32.0; } int safeFollowDistance = Math.Max(60, config.FollowDistance); if (dist > safeFollowDistance) { // Chuyển đổi sang hệ tọa độ màn hình Isometric của JX1 (800x600, center 400, 300) double isoX = (dx - dy) / 2.0; double isoY = (dx + dy) / 4.0; double isoLen = Math.Sqrt(isoX * isoX + isoY * isoY); if (isoLen > 0.001) { // Giới hạn bán kính click chuột tối đa 200px (X) và 140px (Y) từ tâm màn hình int stepX = (int)Math.Round(isoX / isoLen * Math.Min(200.0, isoLen)); int stepY = (int)Math.Round(isoY / isoLen * Math.Min(140.0, isoLen)); int clickX = Math.Clamp(400 + stepX, 100, 700); int clickY = Math.Clamp(300 + stepY, 80, 500); OnLog?.Invoke($"[BÁM ACC CHÍNH] Bám theo '{config.LeaderName}' (cự ly ~{dist:F0}px -> Click ({clickX}, {clickY}))..."); await BackgroundInput.SendLeftClickAsync(hWnd, clickX, clickY); } } } else { // Fallback khi không có tọa độ RAM await BackgroundInput.SendLeftClickAsync(hWnd, 400, 300); } } catch (OperationCanceledException) { // Bỏ qua } catch (Exception ex) { OnLog?.Invoke($"[LỖI BÁM ACC CHÍNH] {ex.Message}"); } } public void Dispose() { Stop(); } }