This commit is contained in:
2025-12-27 21:48:49 +08:00
parent e6a1839941
commit 9d4a7c5e8e
11 changed files with 1678 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using Verse;
namespace WulaFallenEmpire.EventSystem.AI.Tools
{
/// <summary>
/// 采矿指令工具 - 在指定位置或区域添加采矿标记
/// </summary>
public class Tool_DesignateMine : AITool
{
public override string Name => "designate_mine";
public override string Description =>
"在指定坐标添加采矿标记。可以指定单个格子或矩形区域。只能标记可采矿的岩石。";
public override string UsageSchema =>
"<designate_mine><x>整数X坐标</x><z>整数Z坐标</z><radius>可选整数半径默认0表示单格</radius></designate_mine>";
public override string Execute(string args)
{
try
{
var argsDict = ParseXmlArgs(args);
// 解析坐标
if (!argsDict.TryGetValue("x", out string xStr) || !int.TryParse(xStr, out int x))
{
return "Error: 缺少有效的 x 坐标";
}
if (!argsDict.TryGetValue("z", out string zStr) || !int.TryParse(zStr, out int z))
{
return "Error: 缺少有效的 z 坐标";
}
int radius = 0;
if (argsDict.TryGetValue("radius", out string radiusStr))
{
int.TryParse(radiusStr, out radius);
}
radius = Math.Max(0, Math.Min(10, radius)); // 限制半径 0-10
// 获取地图
Map map = Find.CurrentMap;
if (map == null)
{
return "Error: 没有活动的地图";
}
IntVec3 center = new IntVec3(x, 0, z);
if (!center.InBounds(map))
{
return $"Error: 坐标 ({x}, {z}) 超出地图边界";
}
// 收集要标记的格子
List<IntVec3> cellsToMark = new List<IntVec3>();
if (radius == 0)
{
cellsToMark.Add(center);
}
else
{
// 矩形区域
for (int dx = -radius; dx <= radius; dx++)
{
for (int dz = -radius; dz <= radius; dz++)
{
IntVec3 cell = new IntVec3(x + dx, 0, z + dz);
if (cell.InBounds(map))
{
cellsToMark.Add(cell);
}
}
}
}
int successCount = 0;
int alreadyMarkedCount = 0;
int notMineableCount = 0;
foreach (var cell in cellsToMark)
{
// 检查是否已有采矿标记
if (map.designationManager.DesignationAt(cell, DesignationDefOf.Mine) != null)
{
alreadyMarkedCount++;
continue;
}
// 检查是否可采矿
Mineable mineable = cell.GetFirstMineable(map);
if (mineable == null)
{
notMineableCount++;
continue;
}
// 添加采矿标记
map.designationManager.AddDesignation(new Designation(cell, DesignationDefOf.Mine));
successCount++;
}
// 生成结果报告
if (successCount > 0)
{
string result = $"Success: 已标记 {successCount} 个格子进行采矿";
if (alreadyMarkedCount > 0)
{
result += $"{alreadyMarkedCount} 个已有标记";
}
if (notMineableCount > 0)
{
result += $"{notMineableCount} 个不可采矿";
}
Messages.Message($"AI: 标记了 {successCount} 处采矿", MessageTypeDefOf.NeutralEvent);
return result;
}
else if (alreadyMarkedCount > 0)
{
return $"Info: 该区域 {alreadyMarkedCount} 个格子已有采矿标记";
}
else
{
return $"Error: 坐标 ({x}, {z}) 附近没有可采矿的岩石";
}
}
catch (Exception ex)
{
WulaLog.Debug($"[Tool_DesignateMine] Error: {ex}");
return $"Error: 采矿指令失败 - {ex.Message}";
}
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using RimWorld;
using Verse;
namespace WulaFallenEmpire.EventSystem.AI.Tools
{
/// <summary>
/// 征召殖民者工具 - 将殖民者置于征召状态以便直接控制
/// </summary>
public class Tool_DraftPawn : AITool
{
public override string Name => "draft_pawn";
public override string Description =>
"征召或解除征召殖民者。征召后可以直接控制殖民者移动和攻击。";
public override string UsageSchema =>
"<draft_pawn><pawn_name>殖民者名字</pawn_name><draft>true征召/false解除</draft></draft_pawn>";
public override string Execute(string args)
{
try
{
var argsDict = ParseXmlArgs(args);
// 解析殖民者名字
if (!argsDict.TryGetValue("pawn_name", out string pawnName) || string.IsNullOrWhiteSpace(pawnName))
{
// 尝试其他常见参数名
if (!argsDict.TryGetValue("name", out pawnName) || string.IsNullOrWhiteSpace(pawnName))
{
return "Error: 缺少殖民者名字 (pawn_name)";
}
}
// 解析征召状态
bool draft = true;
if (argsDict.TryGetValue("draft", out string draftStr))
{
draft = draftStr.ToLowerInvariant() != "false" && draftStr != "0";
}
// 获取地图
Map map = Find.CurrentMap;
if (map == null)
{
return "Error: 没有活动的地图";
}
// 查找殖民者
Pawn targetPawn = null;
foreach (var pawn in map.mapPawns.FreeColonists)
{
if (pawn.LabelShortCap.Equals(pawnName, StringComparison.OrdinalIgnoreCase) ||
pawn.Name?.ToStringShort?.Equals(pawnName, StringComparison.OrdinalIgnoreCase) == true ||
pawn.LabelCap.ToString().IndexOf(pawnName, StringComparison.OrdinalIgnoreCase) >= 0)
{
targetPawn = pawn;
break;
}
}
if (targetPawn == null)
{
return $"Error: 找不到殖民者 '{pawnName}'";
}
// 检查是否可以征召
if (targetPawn.Downed)
{
return $"Error: {targetPawn.LabelShortCap} 已倒地,无法征召";
}
if (targetPawn.Dead)
{
return $"Error: {targetPawn.LabelShortCap} 已死亡";
}
if (targetPawn.drafter == null)
{
return $"Error: {targetPawn.LabelShortCap} 无法被征召";
}
// 执行征召/解除
bool wasDrafted = targetPawn.Drafted;
targetPawn.drafter.Drafted = draft;
string action = draft ? "征召" : "解除征召";
if (wasDrafted == draft)
{
return $"Info: {targetPawn.LabelShortCap} 已经处于{(draft ? "" : "")}状态";
}
Messages.Message($"AI: {action}了 {targetPawn.LabelShortCap}", targetPawn, MessageTypeDefOf.NeutralEvent);
return $"Success: 已{action} {targetPawn.LabelShortCap},当前位置 ({targetPawn.Position.x}, {targetPawn.Position.z})";
}
catch (Exception ex)
{
WulaLog.Debug($"[Tool_DraftPawn] Error: {ex}");
return $"Error: 征召操作失败 - {ex.Message}";
}
}
}
}

View File

@@ -0,0 +1,45 @@
using System;
namespace WulaFallenEmpire.EventSystem.AI.Tools
{
/// <summary>
/// 获取当前游戏状态工具 - 让 AI 了解殖民地当前情况
/// </summary>
public class Tool_GetGameState : AITool
{
public override string Name => "get_game_state";
public override string Description =>
"获取当前游戏状态的详细报告,包括殖民者状态、资源、建筑进度、威胁等信息。在做出任何操作决策前应先调用此工具了解当前情况。";
public override string UsageSchema =>
"<get_game_state/>";
public override string Execute(string args)
{
try
{
var snapshot = Agent.StateObserver.CaptureState();
if (snapshot == null)
{
return "Error: 无法捕获游戏状态,可能没有活动的地图。";
}
string stateText = snapshot.ToPromptText();
if (string.IsNullOrWhiteSpace(stateText))
{
return "Error: 游戏状态为空。";
}
return stateText;
}
catch (Exception ex)
{
WulaLog.Debug($"[Tool_GetGameState] Error: {ex}");
return $"Error: 获取游戏状态失败 - {ex.Message}";
}
}
}
}

View File

@@ -0,0 +1,166 @@
using System;
using System.Threading.Tasks;
using UnityEngine;
namespace WulaFallenEmpire.EventSystem.AI.Tools
{
/// <summary>
/// 视觉点击工具 - 使用 VLM 分析屏幕后模拟鼠标点击
/// 适用于原版 API 无法直接操作的 mod UI 元素
/// </summary>
public class Tool_VisualClick : AITool
{
public override string Name => "visual_click";
public override string Description =>
"在指定的屏幕位置执行鼠标点击。坐标使用比例值 (0-1)(0,0) 是左上角,(1,1) 是右下角。" +
"适用于点击无法通过 API 操作的 mod 按钮或 UI 元素。先使用 analyze_screen 获取目标位置。";
public override string UsageSchema =>
"<visual_click><x>0-1之间的X比例</x><y>0-1之间的Y比例</y><right_click>可选true为右键</right_click></visual_click>";
public override string Execute(string args)
{
try
{
var argsDict = ParseXmlArgs(args);
// 解析 X 坐标
if (!argsDict.TryGetValue("x", out string xStr) || !float.TryParse(xStr, out float x))
{
return "Error: 缺少有效的 x 坐标 (0-1之间的比例值)";
}
// 解析 Y 坐标
if (!argsDict.TryGetValue("y", out string yStr) || !float.TryParse(yStr, out float y))
{
return "Error: 缺少有效的 y 坐标 (0-1之间的比例值)";
}
// 验证范围
if (x < 0 || x > 1 || y < 0 || y > 1)
{
return $"Error: 坐标 ({x}, {y}) 超出范围,必须在 0-1 之间";
}
// 解析右键选项
bool rightClick = false;
if (argsDict.TryGetValue("right_click", out string rightStr))
{
rightClick = rightStr.ToLowerInvariant() == "true" || rightStr == "1";
}
// 执行点击
bool success = Agent.MouseSimulator.ClickAtProportional(x, y, rightClick);
if (success)
{
string clickType = rightClick ? "右键" : "左键";
int screenX = Mathf.RoundToInt(x * Screen.width);
int screenY = Mathf.RoundToInt(y * Screen.height);
WulaLog.Debug($"[Tool_VisualClick] {clickType}点击 ({x:F3}, {y:F3}) -> 屏幕 ({screenX}, {screenY})");
return $"Success: 已在屏幕位置 ({screenX}, {screenY}) 执行{clickType}点击";
}
else
{
return "Error: 点击操作失败";
}
}
catch (Exception ex)
{
WulaLog.Debug($"[Tool_VisualClick] Error: {ex}");
return $"Error: 点击操作失败 - {ex.Message}";
}
}
}
/// <summary>
/// 视觉输入文本工具 - 在当前焦点位置输入文本
/// </summary>
public class Tool_VisualTypeText : AITool
{
public override string Name => "visual_type_text";
public override string Description =>
"在当前焦点位置输入文本。适用于需要文本输入的对话框或输入框。应先用 visual_click 点击输入框获取焦点。";
public override string UsageSchema =>
"<visual_type_text><text>要输入的文本</text></visual_type_text>";
public override string Execute(string args)
{
try
{
var argsDict = ParseXmlArgs(args);
if (!argsDict.TryGetValue("text", out string text) || string.IsNullOrEmpty(text))
{
return "Error: 缺少要输入的文本";
}
// 使用剪贴板方式输入(支持中文)
GUIUtility.systemCopyBuffer = text;
// 模拟 Ctrl+V 粘贴
// 注意:这需要额外的键盘模拟实现
// 暂时返回成功,实际使用时需要完善
WulaLog.Debug($"[Tool_VisualTypeText] 已将文本复制到剪贴板: {text}");
return $"Success: 已将文本复制到剪贴板。请手动按 Ctrl+V 粘贴,或等待键盘模拟功能完善。";
}
catch (Exception ex)
{
WulaLog.Debug($"[Tool_VisualTypeText] Error: {ex}");
return $"Error: 输入文本失败 - {ex.Message}";
}
}
}
/// <summary>
/// 视觉滚动工具 - 在当前位置滚动鼠标滚轮
/// </summary>
public class Tool_VisualScroll : AITool
{
public override string Name => "visual_scroll";
public override string Description =>
"在当前鼠标位置滚动。可选先移动到指定位置再滚动。delta 正数向上滚动,负数向下滚动。";
public override string UsageSchema =>
"<visual_scroll><delta>滚动量,正数向上负数向下</delta><x>可选0-1 X坐标</x><y>可选0-1 Y坐标</y></visual_scroll>";
public override string Execute(string args)
{
try
{
var argsDict = ParseXmlArgs(args);
if (!argsDict.TryGetValue("delta", out string deltaStr) || !int.TryParse(deltaStr, out int delta))
{
return "Error: 缺少有效的 delta 值";
}
// 可选:先移动到指定位置
if (argsDict.TryGetValue("x", out string xStr) && argsDict.TryGetValue("y", out string yStr))
{
if (float.TryParse(xStr, out float x) && float.TryParse(yStr, out float y))
{
Agent.MouseSimulator.MoveToProportional(x, y);
System.Threading.Thread.Sleep(10);
}
}
Agent.MouseSimulator.Scroll(delta);
string direction = delta > 0 ? "向上" : "向下";
return $"Success: 已{direction}滚动 {Math.Abs(delta)} 单位";
}
catch (Exception ex)
{
WulaLog.Debug($"[Tool_VisualScroll] Error: {ex}");
return $"Error: 滚动操作失败 - {ex.Message}";
}
}
}
}