zc
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public class Tool_CallPrefabAirdrop : AITool
|
||||
{
|
||||
public override string Name => "call_prefab_airdrop";
|
||||
public override string Description => "Calls a large prefab building airdrop at the specified coordinates. " +
|
||||
"You must specify the prefabDefName (e.g., 'WULA_NewColonyBase') and the coordinates (x, z). " +
|
||||
"TIP: Use the 'get_available_prefabs' tool first to see which structures are available. " +
|
||||
"The default skyfaller animation is 'WULA_Prefab_Incoming'.";
|
||||
public override string UsageSchema => "<call_prefab_airdrop><prefabDefName>DefName of the prefab</prefabDefName><skyfallerDef>Optional, default is WULA_Prefab_Incoming</skyfallerDef><x>int</x><z>int</z></call_prefab_airdrop>";
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsed = ParseXmlArgs(args);
|
||||
|
||||
if (!parsed.TryGetValue("prefabDefName", out string prefabDefName) || string.IsNullOrWhiteSpace(prefabDefName))
|
||||
{
|
||||
return "Error: Missing <prefabDefName>. Example: <prefabDefName>WULA_NewColonyBase</prefabDefName>";
|
||||
}
|
||||
|
||||
if (!parsed.TryGetValue("x", out string xStr) || !int.TryParse(xStr, out int x) ||
|
||||
!parsed.TryGetValue("z", out string zStr) || !int.TryParse(zStr, out int z))
|
||||
{
|
||||
return "Error: Missing or invalid target coordinates. Provide <x> and <z>.";
|
||||
}
|
||||
|
||||
string skyfallerDefName = parsed.TryGetValue("skyfallerDef", out string sd) && !string.IsNullOrWhiteSpace(sd)
|
||||
? sd.Trim()
|
||||
: "WULA_Prefab_Incoming";
|
||||
|
||||
Map map = Find.CurrentMap;
|
||||
if (map == null) return "Error: No active map.";
|
||||
|
||||
IntVec3 targetCell = new IntVec3(x, 0, z);
|
||||
if (!targetCell.InBounds(map)) return $"Error: Target {targetCell} is out of bounds.";
|
||||
|
||||
// Check if prefab exists
|
||||
PrefabDef prefabDef = DefDatabase<PrefabDef>.GetNamed(prefabDefName, false);
|
||||
if (prefabDef == null)
|
||||
{
|
||||
return $"Error: PrefabDef '{prefabDefName}' not found.";
|
||||
}
|
||||
|
||||
// Check if skyfaller exists
|
||||
ThingDef skyfallerDef = DefDatabase<ThingDef>.GetNamed(skyfallerDefName, false);
|
||||
if (skyfallerDef == null)
|
||||
{
|
||||
return $"Error: Skyfaller ThingDef '{skyfallerDefName}' not found.";
|
||||
}
|
||||
|
||||
// Spawning must happen on main thread
|
||||
string resultMessage = $"Success: Scheduled airdrop for '{prefabDefName}' at {targetCell} using {skyfallerDefName}.";
|
||||
|
||||
// We use a closure to capture the parameters
|
||||
string pDef = prefabDefName;
|
||||
ThingDef sDef = skyfallerDef;
|
||||
IntVec3 cell = targetCell;
|
||||
Map targetMap = map;
|
||||
|
||||
LongEventHandler.ExecuteWhenFinished(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var skyfaller = (Skyfaller_PrefabSpawner)SkyfallerMaker.MakeSkyfaller(sDef);
|
||||
skyfaller.prefabDefName = pDef;
|
||||
GenSpawn.Spawn(skyfaller, cell, targetMap);
|
||||
WulaLog.Debug($"[WulaAI] Prefab airdrop spawned: {pDef} at {cell}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WulaLog.Debug($"[WulaAI] Failed to spawn prefab airdrop on main thread: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
return resultMessage;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public class Tool_GetAvailablePrefabs : AITool
|
||||
{
|
||||
public override string Name => "get_available_prefabs";
|
||||
public override string Description => "Returns a list of available building prefabs (blueprints) that can be summoned. " +
|
||||
"Use this to find the correct 'prefabDefName' for the 'call_prefab_airdrop' tool.";
|
||||
public override string UsageSchema => "<get_available_prefabs/>";
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var prefabs = DefDatabase<PrefabDef>.AllDefs.ToList();
|
||||
if (prefabs.Count == 0)
|
||||
{
|
||||
return "No prefabs found in the database.";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"Found {prefabs.Count} available prefabs:");
|
||||
|
||||
// Group by prefix to help AI categorize
|
||||
var wulaPrefabs = prefabs.Where(p => p.defName.StartsWith("WULA_", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
var otherPrefabs = prefabs.Where(p => !p.defName.StartsWith("WULA_", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
if (wulaPrefabs.Count > 0)
|
||||
{
|
||||
sb.AppendLine("\n[Wula Empire Specialized Prefabs]:");
|
||||
foreach (var p in wulaPrefabs)
|
||||
{
|
||||
string label = !string.IsNullOrEmpty(p.label) ? $" ({p.label})" : "";
|
||||
sb.AppendLine($"- {p.defName}{label}, Size: {p.size}");
|
||||
}
|
||||
}
|
||||
|
||||
if (otherPrefabs.Count > 0)
|
||||
{
|
||||
sb.AppendLine("\n[Generic/Other Prefabs]:");
|
||||
// Limit generic ones to avoid token bloat
|
||||
var genericToShow = otherPrefabs.Take(20).ToList();
|
||||
foreach (var p in genericToShow)
|
||||
{
|
||||
string label = !string.IsNullOrEmpty(p.label) ? $" ({p.label})" : "";
|
||||
sb.AppendLine($"- {p.defName}{label}, Size: {p.size}");
|
||||
}
|
||||
if (otherPrefabs.Count > 20)
|
||||
{
|
||||
sb.AppendLine($"- ... and {otherPrefabs.Count - 20} more generic prefabs.");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using WulaFallenEmpire.EventSystem.AI.Agent;
|
||||
|
||||
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 Task<string> ExecuteAsync(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var argsDict = ParseXmlArgs(args);
|
||||
|
||||
// 解析 X 坐标
|
||||
if (!argsDict.TryGetValue("x", out string xStr) || !float.TryParse(xStr, out float x))
|
||||
{
|
||||
return Task.FromResult("Error: 缺少有效的 x 坐标 (0-1之间的比例值)");
|
||||
}
|
||||
|
||||
// 解析 Y 坐标
|
||||
if (!argsDict.TryGetValue("y", out string yStr) || !float.TryParse(yStr, out float y))
|
||||
{
|
||||
return Task.FromResult("Error: 缺少有效的 y 坐标 (0-1之间的比例值)");
|
||||
}
|
||||
|
||||
// 验证范围
|
||||
if (x < 0 || x > 1 || y < 0 || y > 1)
|
||||
{
|
||||
return Task.FromResult($"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 Task.FromResult($"Success: 已在屏幕位置 ({screenX}, {screenY}) 执行{clickType}点击");
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult("Error: 点击操作失败");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WulaLog.Debug($"[Tool_VisualClick] Error: {ex}");
|
||||
return Task.FromResult($"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 Task<string> ExecuteAsync(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var argsDict = ParseXmlArgs(args);
|
||||
|
||||
if (!argsDict.TryGetValue("text", out string text) || string.IsNullOrEmpty(text))
|
||||
{
|
||||
return Task.FromResult("Error: 缺少要输入的文本");
|
||||
}
|
||||
|
||||
// 获取当前鼠标位置
|
||||
var pos = MouseSimulator.GetCurrentPosition();
|
||||
|
||||
float propX = Mathf.Clamp01((float)pos.x / Screen.width);
|
||||
float propY = Mathf.Clamp01((float)pos.y / Screen.height);
|
||||
|
||||
WulaLog.Debug($"[VisualTypeText] Current Pos: ({pos.x}, {pos.y}) -> Proportional: ({propX:F3}, {propY:F3})");
|
||||
|
||||
return Task.FromResult(VisualInteractionTools.TypeText(propX, propY, text));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WulaLog.Debug($"[Tool_VisualTypeText] Error: {ex}");
|
||||
return Task.FromResult($"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 Task<string> ExecuteAsync(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var argsDict = ParseXmlArgs(args);
|
||||
|
||||
if (!argsDict.TryGetValue("delta", out string deltaStr) || !int.TryParse(deltaStr, out int delta))
|
||||
{
|
||||
return Task.FromResult("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 Task.FromResult($"Success: 已{direction}滚动 {Math.Abs(delta)} 单位");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WulaLog.Debug($"[Tool_VisualScroll] Error: {ex}");
|
||||
return Task.FromResult($"Error: 滚动操作失败 - {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using WulaFallenEmpire.EventSystem.AI.Agent;
|
||||
|
||||
namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public abstract class VisualToolBase : AITool
|
||||
{
|
||||
protected bool GetFloat(Dictionary<string, string> dict, string key, out float result)
|
||||
{
|
||||
result = 0f;
|
||||
if (dict.TryGetValue(key, out string val) && float.TryParse(val, out result))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract override Task<string> ExecuteAsync(string args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视觉拖拽工具
|
||||
/// </summary>
|
||||
public class Tool_VisualDrag : VisualToolBase
|
||||
{
|
||||
public override string Name => "visual_drag";
|
||||
public override string Description => "从起始坐标拖拽到结束坐标。适用于框选单位、拖动滑块或地图。";
|
||||
public override string UsageSchema => "<visual_drag><start_x>0-1</start_x><start_y>0-1</start_y><end_x>0-1</end_x><end_y>0-1</end_y><duration>秒(默认0.5)</duration></visual_drag>";
|
||||
|
||||
public override Task<string> ExecuteAsync(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dict = ParseXmlArgs(args);
|
||||
if (!GetFloat(dict, "start_x", out float sx) || !GetFloat(dict, "start_y", out float sy) ||
|
||||
!GetFloat(dict, "end_x", out float ex) || !GetFloat(dict, "end_y", out float ey))
|
||||
return Task.FromResult("Error: 缺少有效的坐标参数 (0-1)");
|
||||
|
||||
float duration = 0.5f;
|
||||
if (GetFloat(dict, "duration", out float d)) duration = d;
|
||||
|
||||
return Task.FromResult(VisualInteractionTools.MouseDrag(sx, sy, ex, ey, duration));
|
||||
}
|
||||
catch (Exception ex) { return Task.FromResult($"Error: {ex.Message}"); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视觉快捷键工具 (通用)
|
||||
/// </summary>
|
||||
public class Tool_VisualHotkey : VisualToolBase
|
||||
{
|
||||
public override string Name => "visual_hotkey";
|
||||
public override string Description => "在指定位置点击(可选)并按下快捷键。支持组合键如 'ctrl+c', 'alt+f4', 单键如 'enter', 'esc', 'r', 'space'。";
|
||||
public override string UsageSchema => "<visual_hotkey><key>快捷键</key><x>可选</x><y>可选</y></visual_hotkey>";
|
||||
|
||||
public override Task<string> ExecuteAsync(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dict = ParseXmlArgs(args);
|
||||
string key = dict.ContainsKey("key") ? dict["key"] : "";
|
||||
if (string.IsNullOrEmpty(key)) return Task.FromResult("Error: 缺少 key 参数");
|
||||
|
||||
// 如果提供了坐标,先点击
|
||||
if (GetFloat(dict, "x", out float x) && GetFloat(dict, "y", out float y))
|
||||
{
|
||||
return Task.FromResult(VisualInteractionTools.PressHotkey(x, y, key));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 在当前位置直接按键
|
||||
var pos = MouseSimulator.GetCurrentPosition();
|
||||
float propX = Mathf.Clamp01((float)pos.x / Screen.width);
|
||||
float propY = Mathf.Clamp01(1.0f - ((float)pos.y / Screen.height));
|
||||
return Task.FromResult(VisualInteractionTools.PressHotkey(propX, propY, key));
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { return Task.FromResult($"Error: {ex.Message}"); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视觉等待工具
|
||||
/// </summary>
|
||||
public class Tool_VisualWait : VisualToolBase
|
||||
{
|
||||
public override string Name => "visual_wait";
|
||||
public override string Description => "等待指定时间。用于等待UI动画或加载。";
|
||||
public override string UsageSchema => "<visual_wait><seconds>秒数</seconds></visual_wait>";
|
||||
|
||||
public override Task<string> ExecuteAsync(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dict = ParseXmlArgs(args);
|
||||
if (!GetFloat(dict, "seconds", out float seconds)) return Task.FromResult("Error: 缺少 seconds 参数");
|
||||
return Task.FromResult(VisualInteractionTools.Wait(seconds));
|
||||
}
|
||||
catch (Exception ex) { return Task.FromResult($"Error: {ex.Message}"); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视觉删除文本工具
|
||||
/// </summary>
|
||||
public class Tool_VisualDeleteText : VisualToolBase
|
||||
{
|
||||
public override string Name => "visual_delete_text";
|
||||
public override string Description => "点击指定位置并按 Backspace 删除指定数量的字符。用于清空输入框。";
|
||||
public override string UsageSchema => "<visual_delete_text><x>0-1</x><y>0-1</y><count>字符数(默认1)</count></visual_delete_text>";
|
||||
|
||||
public override Task<string> ExecuteAsync(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dict = ParseXmlArgs(args);
|
||||
if (!GetFloat(dict, "x", out float x) || !GetFloat(dict, "y", out float y))
|
||||
return Task.FromResult("Error: 缺少有效的坐标参数");
|
||||
|
||||
int count = 1;
|
||||
if (dict.TryGetValue("count", out string cStr) && int.TryParse(cStr, out int c)) count = c;
|
||||
|
||||
return Task.FromResult(VisualInteractionTools.DeleteText(x, y, count));
|
||||
}
|
||||
catch (Exception ex) { return Task.FromResult($"Error: {ex.Message}"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user