zc
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire.EventSystem.AI.Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// 自主 Agent 循环 - 持续观察游戏并做出决策
|
||||
/// 用户只需给出开放式指令如"帮我挖铁"或"帮我玩殖民地"
|
||||
/// </summary>
|
||||
public class AutonomousAgentLoop : GameComponent
|
||||
{
|
||||
public static AutonomousAgentLoop Instance { get; private set; }
|
||||
|
||||
// Agent 状态
|
||||
private bool _isRunning;
|
||||
private string _currentObjective;
|
||||
private float _lastDecisionTime;
|
||||
private int _decisionCount;
|
||||
private readonly List<string> _actionHistory = new List<string>();
|
||||
|
||||
// 配置
|
||||
private const float DecisionIntervalSeconds = 3f; // 每 3 秒决策一次
|
||||
private const int MaxActionsPerObjective = 100;
|
||||
|
||||
// 事件
|
||||
public event Action<string> OnDecisionMade;
|
||||
public event Action<string> OnObjectiveComplete;
|
||||
public event Action<string> OnError;
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
public string CurrentObjective => _currentObjective;
|
||||
public int DecisionCount => _decisionCount;
|
||||
|
||||
public AutonomousAgentLoop(Game game)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行开放式目标
|
||||
/// </summary>
|
||||
public void StartObjective(string objective)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(objective))
|
||||
{
|
||||
OnError?.Invoke("目标不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentObjective = objective;
|
||||
_isRunning = true;
|
||||
_decisionCount = 0;
|
||||
_actionHistory.Clear();
|
||||
_lastDecisionTime = Time.realtimeSinceStartup;
|
||||
|
||||
WulaLog.Debug($"[AgentLoop] Started objective: {objective}");
|
||||
Messages.Message($"AI Agent 开始执行: {objective}", MessageTypeDefOf.NeutralEvent);
|
||||
|
||||
// 立即执行第一次决策
|
||||
_ = ExecuteDecisionCycleAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止 Agent
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (!_isRunning) return;
|
||||
|
||||
_isRunning = false;
|
||||
WulaLog.Debug($"[AgentLoop] Stopped after {_decisionCount} decisions");
|
||||
Messages.Message($"AI Agent 已停止,执行了 {_decisionCount} 次决策", MessageTypeDefOf.NeutralEvent);
|
||||
}
|
||||
|
||||
public override void GameComponentTick()
|
||||
{
|
||||
if (!_isRunning) return;
|
||||
|
||||
// 检查是否到达决策间隔
|
||||
if (Time.realtimeSinceStartup - _lastDecisionTime < DecisionIntervalSeconds) return;
|
||||
|
||||
// 检查是否超过最大操作次数
|
||||
if (_decisionCount >= MaxActionsPerObjective)
|
||||
{
|
||||
Messages.Message($"AI Agent: 已达到最大操作次数 ({MaxActionsPerObjective}),暂停执行", MessageTypeDefOf.CautionInput);
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastDecisionTime = Time.realtimeSinceStartup;
|
||||
|
||||
// 异步执行决策
|
||||
_ = ExecuteDecisionCycleAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行一次决策循环: Observe → Think → Act
|
||||
/// </summary>
|
||||
private async Task ExecuteDecisionCycleAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. Observe - 收集游戏状态
|
||||
var gameState = StateObserver.CaptureState();
|
||||
string stateText = gameState.ToPromptText();
|
||||
|
||||
// 2. 构建决策提示词
|
||||
string prompt = BuildDecisionPrompt(stateText);
|
||||
|
||||
// 3. Think - 调用 AI 获取决策
|
||||
var settings = WulaFallenEmpireMod.settings;
|
||||
if (settings == null || string.IsNullOrEmpty(settings.apiKey))
|
||||
{
|
||||
OnError?.Invoke("API Key 未配置");
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
var client = new SimpleAIClient(settings.apiKey, settings.baseUrl, settings.model);
|
||||
|
||||
// 使用 VLM 如果启用且配置了
|
||||
string decision;
|
||||
if (settings.enableVlmFeatures && !string.IsNullOrEmpty(settings.vlmApiKey))
|
||||
{
|
||||
// 使用 VLM 分析屏幕
|
||||
string base64Image = ScreenCaptureUtility.CaptureScreenAsBase64();
|
||||
var vlmClient = new SimpleAIClient(settings.vlmApiKey, settings.vlmBaseUrl, settings.vlmModel);
|
||||
decision = await vlmClient.GetVisionCompletionAsync(
|
||||
GetAgentSystemPrompt(),
|
||||
prompt,
|
||||
base64Image,
|
||||
maxTokens: 512,
|
||||
temperature: 0.3f
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 纯文本模式
|
||||
var messages = new List<(string role, string message)>
|
||||
{
|
||||
("user", prompt)
|
||||
};
|
||||
decision = await client.GetChatCompletionAsync(GetAgentSystemPrompt(), messages, 512, 0.3f);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(decision))
|
||||
{
|
||||
WulaLog.Debug("[AgentLoop] Empty decision received");
|
||||
return;
|
||||
}
|
||||
|
||||
_decisionCount++;
|
||||
WulaLog.Debug($"[AgentLoop] Decision #{_decisionCount}: {decision.Substring(0, Math.Min(100, decision.Length))}...");
|
||||
|
||||
// 4. Act - 执行决策
|
||||
ExecuteDecision(decision);
|
||||
|
||||
// 5. 记录历史
|
||||
_actionHistory.Add($"[{_decisionCount}] {decision.Substring(0, Math.Min(50, decision.Length))}");
|
||||
if (_actionHistory.Count > 20)
|
||||
{
|
||||
_actionHistory.RemoveAt(0);
|
||||
}
|
||||
|
||||
OnDecisionMade?.Invoke(decision);
|
||||
|
||||
// 6. 检查是否完成目标
|
||||
if (decision.Contains("<objective_complete") || decision.Contains("目标已完成"))
|
||||
{
|
||||
OnObjectiveComplete?.Invoke(_currentObjective);
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WulaLog.Debug($"[AgentLoop] Error in decision cycle: {ex.Message}");
|
||||
OnError?.Invoke(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildDecisionPrompt(string gameStateText)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("# 当前任务");
|
||||
sb.AppendLine($"**目标**: {_currentObjective}");
|
||||
sb.AppendLine($"**已执行决策次数**: {_decisionCount}");
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine(gameStateText);
|
||||
|
||||
if (_actionHistory.Count > 0)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("# 最近操作历史");
|
||||
foreach (var action in _actionHistory)
|
||||
{
|
||||
sb.AppendLine($"- {action}");
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("# 请决定下一步操作");
|
||||
sb.AppendLine("分析当前状态,输出一个 XML 工具调用来推进目标。");
|
||||
sb.AppendLine("如果目标已完成,输出 <objective_complete/>。");
|
||||
sb.AppendLine("如果不需要操作(等待中),输出 <no_action/>。");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string GetAgentSystemPrompt()
|
||||
{
|
||||
return @"你是一个自主 RimWorld 游戏 AI Agent。你的任务是独立完成用户给出的开放式目标。
|
||||
|
||||
# 核心原则
|
||||
1. **自主决策**: 不要等待用户指示,主动分析情况并采取行动
|
||||
2. **循序渐进**: 每次只执行一个操作,观察结果后再决定下一步
|
||||
3. **问题应对**: 遇到障碍时自己想办法解决
|
||||
4. **目标导向**: 始终围绕目标推进,避免无关操作
|
||||
|
||||
# 可用工具
|
||||
- get_game_state: 获取详细游戏状态
|
||||
- designate_mine: <designate_mine><x>X坐标</x><z>Z坐标</z><radius>可选半径</radius></designate_mine> 标记采矿
|
||||
- draft_pawn: <draft_pawn><pawn_name>名字</pawn_name><draft>true/false</draft></draft_pawn> 征召殖民者
|
||||
- analyze_screen: <analyze_screen><context>分析目标</context></analyze_screen> 分析屏幕(需要VLM)
|
||||
- visual_click: <visual_click><x>0-1比例</x><y>0-1比例</y></visual_click> 模拟点击
|
||||
|
||||
# 输出格式
|
||||
直接输出一个 XML 工具调用,不要解释。
|
||||
如果目标已完成: <objective_complete/>
|
||||
如果需要等待: <no_action/>
|
||||
|
||||
# 注意事项
|
||||
- 坐标使用游戏内整数坐标,不是屏幕比例
|
||||
- 优先使用 API 工具(designate_mine 等),视觉工具用于 mod 内容
|
||||
- 保持简洁高效";
|
||||
}
|
||||
|
||||
private void ExecuteDecision(string decision)
|
||||
{
|
||||
// 解析并执行 AI 的决策
|
||||
// 从 AIIntelligenceCore 借用工具执行逻辑
|
||||
|
||||
var core = AIIntelligenceCore.Instance;
|
||||
if (core == null)
|
||||
{
|
||||
WulaLog.Debug("[AgentLoop] AIIntelligenceCore not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// 提取工具调用并执行
|
||||
// 暂时使用简单的正则匹配,实际应整合 AIIntelligenceCore 的解析逻辑
|
||||
|
||||
if (decision.Contains("<no_action") || decision.Contains("<objective_complete"))
|
||||
{
|
||||
// 不需要执行
|
||||
return;
|
||||
}
|
||||
|
||||
// 委托给 AIIntelligenceCore 执行工具
|
||||
// TODO: 整合更完善的工具执行逻辑
|
||||
WulaLog.Debug($"[AgentLoop] Executing: {decision}");
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Values.Look(ref _currentObjective, "agentObjective", "");
|
||||
Scribe_Values.Look(ref _isRunning, "agentRunning", false);
|
||||
Scribe_Values.Look(ref _decisionCount, "agentDecisionCount", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WulaFallenEmpire.EventSystem.AI.Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// 纯视觉交互工具集 - 仿照 Python VLM Agent
|
||||
/// 当没有原生 API 可用时,AI 可以通过这些工具操作任何界面
|
||||
/// </summary>
|
||||
public static class VisualInteractionTools
|
||||
{
|
||||
// Windows API
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetCursorPos(int X, int Y);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern short VkKeyScan(char ch);
|
||||
|
||||
// 鼠标事件标志
|
||||
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
|
||||
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
|
||||
private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
|
||||
private const uint MOUSEEVENTF_RIGHTUP = 0x0010;
|
||||
private const uint MOUSEEVENTF_WHEEL = 0x0800;
|
||||
|
||||
// 键盘事件标志
|
||||
private const uint KEYEVENTF_KEYDOWN = 0x0000;
|
||||
private const uint KEYEVENTF_KEYUP = 0x0002;
|
||||
|
||||
// 虚拟键码
|
||||
private const byte VK_CONTROL = 0x11;
|
||||
private const byte VK_SHIFT = 0x10;
|
||||
private const byte VK_ALT = 0x12;
|
||||
private const byte VK_RETURN = 0x0D;
|
||||
private const byte VK_BACK = 0x08;
|
||||
private const byte VK_ESCAPE = 0x1B;
|
||||
private const byte VK_TAB = 0x09;
|
||||
private const byte VK_LWIN = 0x5B;
|
||||
private const byte VK_F4 = 0x73;
|
||||
|
||||
/// <summary>
|
||||
/// 1. 鼠标点击 - 在比例坐标处点击
|
||||
/// </summary>
|
||||
public static string MouseClick(float x, float y, string button = "left", int clicks = 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
int screenX = Mathf.RoundToInt(x * Screen.width);
|
||||
int screenY = Mathf.RoundToInt(y * Screen.height);
|
||||
|
||||
// Unity Y 轴翻转
|
||||
int windowsY = Screen.height - screenY;
|
||||
|
||||
SetCursorPos(screenX, windowsY);
|
||||
Thread.Sleep(20);
|
||||
|
||||
for (int i = 0; i < clicks; i++)
|
||||
{
|
||||
if (button == "right")
|
||||
{
|
||||
mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
|
||||
mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
|
||||
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
|
||||
}
|
||||
if (i < clicks - 1) Thread.Sleep(50);
|
||||
}
|
||||
|
||||
string buttonText = button == "right" ? "右键" : "左键";
|
||||
string clickText = clicks == 2 ? "双击" : "单击";
|
||||
return $"Success: 在 ({screenX}, {screenY}) 处{buttonText}{clickText}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 点击失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 2. 输入文本 - 在指定位置点击后输入文本(通过剪贴板)
|
||||
/// </summary>
|
||||
public static string TypeText(float x, float y, string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 先点击
|
||||
MouseClick(x, y);
|
||||
Thread.Sleep(100);
|
||||
|
||||
// 通过剪贴板输入
|
||||
GUIUtility.systemCopyBuffer = text;
|
||||
Thread.Sleep(50);
|
||||
|
||||
// Ctrl+V 粘贴
|
||||
keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYDOWN, 0);
|
||||
keybd_event(0x56, 0, KEYEVENTF_KEYDOWN, 0); // V
|
||||
keybd_event(0x56, 0, KEYEVENTF_KEYUP, 0);
|
||||
keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);
|
||||
|
||||
return $"Success: 在 ({x:F3}, {y:F3}) 处输入文本: {text}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 输入文本失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3. 滚动窗口 - 在指定位置滚动
|
||||
/// </summary>
|
||||
public static string ScrollWindow(float x, float y, string direction = "up", int amount = 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
int screenX = Mathf.RoundToInt(x * Screen.width);
|
||||
int screenY = Mathf.RoundToInt(y * Screen.height);
|
||||
int windowsY = Screen.height - screenY;
|
||||
|
||||
SetCursorPos(screenX, windowsY);
|
||||
Thread.Sleep(20);
|
||||
|
||||
int wheelDelta = (direction == "up" ? 1 : -1) * 120 * amount;
|
||||
mouse_event(MOUSEEVENTF_WHEEL, 0, 0, (uint)wheelDelta, 0);
|
||||
|
||||
string dir = direction == "up" ? "向上" : "向下";
|
||||
return $"Success: 在 ({screenX}, {screenY}) 处{dir}滚动 {amount} 步";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 滚动失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 4. 鼠标拖拽 - 从起点拖到终点
|
||||
/// </summary>
|
||||
public static string MouseDrag(float startX, float startY, float endX, float endY, float durationSec = 0.5f)
|
||||
{
|
||||
try
|
||||
{
|
||||
int sx = Mathf.RoundToInt(startX * Screen.width);
|
||||
int sy = Screen.height - Mathf.RoundToInt(startY * Screen.height);
|
||||
int ex = Mathf.RoundToInt(endX * Screen.width);
|
||||
int ey = Screen.height - Mathf.RoundToInt(endY * Screen.height);
|
||||
|
||||
// 移动到起点
|
||||
SetCursorPos(sx, sy);
|
||||
Thread.Sleep(50);
|
||||
|
||||
// 按下
|
||||
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
|
||||
|
||||
// 平滑移动
|
||||
int steps = Mathf.Max(5, Mathf.RoundToInt(durationSec * 20));
|
||||
int delayMs = Mathf.RoundToInt(durationSec * 1000 / steps);
|
||||
|
||||
for (int i = 1; i <= steps; i++)
|
||||
{
|
||||
float t = (float)i / steps;
|
||||
int cx = Mathf.RoundToInt(Mathf.Lerp(sx, ex, t));
|
||||
int cy = Mathf.RoundToInt(Mathf.Lerp(sy, ey, t));
|
||||
SetCursorPos(cx, cy);
|
||||
Thread.Sleep(delayMs);
|
||||
}
|
||||
|
||||
// 释放
|
||||
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
|
||||
|
||||
return $"Success: 从 ({startX:F3}, {startY:F3}) 拖拽到 ({endX:F3}, {endY:F3})";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 拖拽失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 5. 等待 - 暂停指定秒数
|
||||
/// </summary>
|
||||
public static string Wait(float seconds)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.Sleep(Mathf.RoundToInt(seconds * 1000));
|
||||
return $"Success: 等待了 {seconds} 秒";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 等待失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 6. 按下回车键
|
||||
/// </summary>
|
||||
public static string PressEnter()
|
||||
{
|
||||
try
|
||||
{
|
||||
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYDOWN, 0);
|
||||
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
|
||||
return "Success: 按下回车键";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 按键失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 7. 按下 Escape 键
|
||||
/// </summary>
|
||||
public static string PressEscape()
|
||||
{
|
||||
try
|
||||
{
|
||||
keybd_event(VK_ESCAPE, 0, KEYEVENTF_KEYDOWN, 0);
|
||||
keybd_event(VK_ESCAPE, 0, KEYEVENTF_KEYUP, 0);
|
||||
return "Success: 按下 Escape 键";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 按键失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 8. 删除文本 - 按 Backspace 删除指定数量字符
|
||||
/// </summary>
|
||||
public static string DeleteText(float x, float y, int count = 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
MouseClick(x, y);
|
||||
Thread.Sleep(100);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
keybd_event(VK_BACK, 0, KEYEVENTF_KEYDOWN, 0);
|
||||
keybd_event(VK_BACK, 0, KEYEVENTF_KEYUP, 0);
|
||||
Thread.Sleep(20);
|
||||
}
|
||||
|
||||
return $"Success: 删除了 {count} 个字符";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 删除失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 9. 执行快捷键 - 如 Ctrl+C, Alt+F4 等
|
||||
/// </summary>
|
||||
public static string PressHotkey(float x, float y, string hotkey)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 先点击获取焦点
|
||||
MouseClick(x, y);
|
||||
Thread.Sleep(100);
|
||||
|
||||
// 解析快捷键
|
||||
var keys = hotkey.ToLowerInvariant().Replace("+", " ").Replace("-", " ").Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 按下修饰键
|
||||
foreach (var key in keys)
|
||||
{
|
||||
byte vk = GetVirtualKeyCode(key);
|
||||
if (vk != 0)
|
||||
{
|
||||
keybd_event(vk, 0, KEYEVENTF_KEYDOWN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(50);
|
||||
|
||||
// 释放修饰键(逆序)
|
||||
for (int i = keys.Length - 1; i >= 0; i--)
|
||||
{
|
||||
byte vk = GetVirtualKeyCode(keys[i]);
|
||||
if (vk != 0)
|
||||
{
|
||||
keybd_event(vk, 0, KEYEVENTF_KEYUP, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return $"Success: 执行快捷键 {hotkey}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 快捷键失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 10. 关闭窗口 - Alt+F4
|
||||
/// </summary>
|
||||
public static string CloseWindow(float x, float y)
|
||||
{
|
||||
try
|
||||
{
|
||||
MouseClick(x, y);
|
||||
Thread.Sleep(100);
|
||||
|
||||
keybd_event(VK_ALT, 0, KEYEVENTF_KEYDOWN, 0);
|
||||
keybd_event(VK_F4, 0, KEYEVENTF_KEYDOWN, 0);
|
||||
keybd_event(VK_F4, 0, KEYEVENTF_KEYUP, 0);
|
||||
keybd_event(VK_ALT, 0, KEYEVENTF_KEYUP, 0);
|
||||
|
||||
return "Success: 关闭窗口";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: 关闭窗口失败 - {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private static byte GetVirtualKeyCode(string keyName)
|
||||
{
|
||||
return keyName.ToLowerInvariant() switch
|
||||
{
|
||||
"ctrl" or "control" => VK_CONTROL,
|
||||
"shift" => VK_SHIFT,
|
||||
"alt" => VK_ALT,
|
||||
"enter" or "return" => VK_RETURN,
|
||||
"esc" or "escape" => VK_ESCAPE,
|
||||
"tab" => VK_TAB,
|
||||
"backspace" or "back" => VK_BACK,
|
||||
"win" or "windows" => VK_LWIN,
|
||||
"f4" => VK_F4,
|
||||
// 字母键
|
||||
"a" => 0x41, "b" => 0x42, "c" => 0x43, "d" => 0x44, "e" => 0x45,
|
||||
"f" => 0x46, "g" => 0x47, "h" => 0x48, "i" => 0x49, "j" => 0x4A,
|
||||
"k" => 0x4B, "l" => 0x4C, "m" => 0x4D, "n" => 0x4E, "o" => 0x4F,
|
||||
"p" => 0x50, "q" => 0x51, "r" => 0x52, "s" => 0x53, "t" => 0x54,
|
||||
"u" => 0x55, "v" => 0x56, "w" => 0x57, "x" => 0x58, "y" => 0x59,
|
||||
"z" => 0x5A,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user