diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index dee74753..e837ec46 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs b/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs index 027cdf3d..6f9e0dc6 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs @@ -248,7 +248,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori return; } - // 附加选中对象的上下文信息 + // 闄勫姞閫変腑瀵硅薄鐨勪笂涓嬫枃淇℃伅 string messageWithContext = BuildUserMessageWithContext(text); _history.Add(("user", messageWithContext)); PersistHistory(); @@ -317,23 +317,18 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori _tools.Add(new Tool_SendReinforcement()); _tools.Add(new Tool_GetColonistStatus()); _tools.Add(new Tool_GetMapResources()); + _tools.Add(new Tool_GetAvailablePrefabs()); _tools.Add(new Tool_GetMapPawns()); _tools.Add(new Tool_GetRecentNotifications()); _tools.Add(new Tool_CallBombardment()); _tools.Add(new Tool_SearchThingDef()); _tools.Add(new Tool_SearchPawnKind()); + _tools.Add(new Tool_CallPrefabAirdrop()); - // Agent 工具 - 纯视觉操作 (移除了 GetGameState, DesignateMine, DraftPawn) + // Agent 宸ュ叿 - 淇濈暀鐢婚潰鍒嗘瀽鎴浘鑳藉姏锛岀Щ闄ゆ墍鏈夋ā鎷熸搷浣滃伐鍏 if (WulaFallenEmpireMod.settings?.enableVlmFeatures == true) { _tools.Add(new Tool_AnalyzeScreen()); - _tools.Add(new Tool_VisualClick()); - _tools.Add(new Tool_VisualScroll()); - _tools.Add(new Tool_VisualTypeText()); - _tools.Add(new Tool_VisualDrag()); - _tools.Add(new Tool_VisualHotkey()); - _tools.Add(new Tool_VisualWait()); - _tools.Add(new Tool_VisualDeleteText()); } } @@ -482,6 +477,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori "- send_reinforcement\n" + "- call_bombardment\n" + "- modify_goodwill\n" + + "- call_prefab_airdrop\n" + "If no action is required, output exactly: .\n" + "Query tools exist but are disabled in this phase (not listed here).\n" : string.Empty; @@ -497,8 +493,8 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori string actionWhitelist = phase == RequestPhase.ActionTools ? "ACTION PHASE VALID TAGS ONLY:\n" + - ", , , , , , , , , , , \n" + - "INVALID EXAMPLES (do NOT use now): , JSON, Markdown Code Blocks\n" + ", , , , , \n" + + "INVALID EXAMPLES (do NOT use now): , \n" : string.Empty; return string.Join("\n\n", new[] @@ -571,7 +567,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori "Rules:\n" + "- You MUST NOT write any natural language to the user in this phase.\n" + "- Output XML tool calls only, or exactly: .\n" + - "- ONLY action tools are accepted in this phase (spawn_resources, send_reinforcement, call_bombardment, modify_goodwill, visual_click, visual_scroll, visual_type_text, visual_drag, visual_hotkey, visual_wait, visual_delete_text).\n" + + "- ONLY action tools are accepted in this phase (spawn_resources, send_reinforcement, call_bombardment, modify_goodwill, call_prefab_airdrop).\n" + "- Query tools (get_*/search_*) will be ignored.\n" + "- Prefer action tools (spawn_resources, send_reinforcement, call_bombardment, modify_goodwill).\n" + "- Avoid queries unless absolutely required.\n" + @@ -643,13 +639,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori toolName == "send_reinforcement" || toolName == "call_bombardment" || toolName == "modify_goodwill" || - toolName == "visual_click" || - toolName == "visual_scroll" || - toolName == "visual_type_text" || - toolName == "visual_drag" || - toolName == "visual_hotkey" || - toolName == "visual_wait" || - toolName == "visual_delete_text"; + toolName == "call_prefab_airdrop"; } private static bool IsQueryToolName(string toolName) @@ -839,15 +829,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori } } - private bool CheckVisualIntent(string message) - { - if (string.IsNullOrEmpty(message)) return false; - string[] keywords = new string[] { - "屏幕", "画面", "截图", "看", "找", "显示", // CN - "screen", "screenshot", "image", "view", "look", "see", "find", "visual", "scan" // EN - }; - return keywords.Any(k => message.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0); - } private async Task RunPhasedRequestAsync() { if (_isThinking) return; @@ -880,7 +861,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori if (settings.enableVlmFeatures && settings.showThinkingProcess) { // Optional: We can still say "Analyzing data link..." - AddAssistantMessage("[P.I.A] 正在分析数据链路..."); + AddAssistantMessage("[P.I.A] 姝e湪鍒嗘瀽鏁版嵁閾捐矾..."); } var queryPhase = RequestPhase.QueryTools; @@ -914,7 +895,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori base64Image = queryResult.CapturedImage; if (settings.showThinkingProcess) { - AddAssistantMessage("[P.I.A] 视觉传感器已激活,图像已捕获..."); + AddAssistantMessage("[P.I.A] 瑙嗚浼犳劅鍣ㄥ凡婵娲伙紝鍥惧儚宸叉崟鑾..."); } } @@ -965,7 +946,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori if (settings.showThinkingProcess) { - AddAssistantMessage("[P.I.A] 正在计算最优战术方案..."); + AddAssistantMessage("[P.I.A] 姝e湪璁$畻鏈浼樻垬鏈柟妗..."); } var actionPhase = RequestPhase.ActionTools; @@ -998,19 +979,14 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori "Preserve the intent of the previous output.\n" + "If the previous output indicates no action is needed or refuses action, output exactly: .\n" + "Do NOT invent new actions.\n" + - "Output VALID XML tool calls only. No natural language, no commentary.\nIf the previous output contains JSON (including JSON code blocks or inline {\"point\": [x,y]}), convert it to XML.\n- For a point coordinate, output: .......\n- If coordinates are larger than 1 (e.g., 0-1000), normalize by dividing by 1000.\nIgnore any non-XML text.\n" + - "Allowed tags: , , , , , , , .\n" + + "Output VALID XML tool calls only. No natural language, no commentary.\nIgnore any non-XML text.\n" + + "Allowed tags: , , , , , .\n" + "\nAction tool XML formats:\n" + "- DefNameInt\n" + "- PawnKindDef: Count, ...\n" + "- DefNameIntInt\n" + "- Int\n" + - "- FloatFloat\n" + - "- Int\n" + - "- String\n" + - "- 0-10-10-10-1\n" + - "- String (e.g. 'enter', 'esc', 'space')\n" + - "- Float\n" + + "- DefNameIntInt\n" + "\nPrevious output:\n" + TrimForPrompt(actionResponse, 600); string fixedResponse = await client.GetChatCompletionAsync(fixInstruction, actionContext, maxTokens: 2048, temperature: 0.1f); bool fixedHasXml = !string.IsNullOrEmpty(fixedResponse) && IsXmlToolCall(fixedResponse); @@ -1073,19 +1049,14 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori "Preserve the intent of the previous output.\n" + "If the previous output indicates no action is needed or refuses action, output exactly: .\n" + "Do NOT invent new actions.\n" + - "Output VALID XML tool calls only. No natural language, no commentary.\nIf the previous output contains JSON (including JSON code blocks or inline {\"point\": [x,y]}), convert it to XML.\n- For a point coordinate, output: .......\n- If coordinates are larger than 1 (e.g., 0-1000), normalize by dividing by 1000.\nIgnore any non-XML text.\n" + - "Allowed tags: , , , , , , , .\n" + + "Output VALID XML tool calls only. No natural language, no commentary.\nIgnore any non-XML text.\n" + + "Allowed tags: , , , , , .\n" + "\nAction tool XML formats:\n" + "- DefNameInt\n" + "- PawnKindDef: Count, ...\n" + "- DefNameIntInt\n" + "- Int\n" + - "- FloatFloat\n" + - "- Int\n" + - "- String\n" + - "- 0-10-10-10-1\n" + - "- String\n" + - "- Float\n" + + "- DefNameIntInt\n" + "\nPrevious output:\n" + TrimForPrompt(retryActionResponse, 600); string retryFixedResponse = await client.GetChatCompletionAsync(retryFixInstruction, retryActionContext, maxTokens: 2048, temperature: 0.1f); bool retryFixedHasXml = !string.IsNullOrEmpty(retryFixedResponse) && IsXmlToolCall(retryFixedResponse); @@ -1151,7 +1122,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori if (settings.showThinkingProcess) { - AddAssistantMessage("[P.I.A] 正在汇总战报并建立通讯记录..."); + AddAssistantMessage("[P.I.A] 姝e湪姹囨绘垬鎶ュ苟寤虹珛閫氳璁板綍..."); } // VISUAL CONTEXT FOR REPLY: Pass the image so the AI can describe what it sees. @@ -1212,52 +1183,6 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori string guidance = "ToolRunner Guidance: Reply to the player in natural language only. Do NOT output any XML. You may include [EXPR:n] to set expression (n=1-6)."; var matches = Regex.Matches(xml ?? "", @"<([a-zA-Z0-9_]+)(?:>.*?|/>)", RegexOptions.Singleline); - - // GEMINI 3 JSON FALLBACK (Restored & Fixed) - // If no XML found, try to parse JSON markdown block commonly output by Gemini 3 Flash Preview - // Uses a 0-1000 coordinate system typically. - if (matches.Count == 0 && (xml ?? "").Contains("```json")) - { - try - { - var jsonMatch = Regex.Match(xml, @"```json\s*(\[.*?\])\s*```", RegexOptions.Singleline); - if (jsonMatch.Success) - { - string jsonArr = jsonMatch.Groups[1].Value; - // Regex to extract objects with "point" (and optional "action") - // Matches: { "point": [123, 456] } - // Note: In verbatim string @"""", double quotes are escaped as "". Not \". - var pointMatches = Regex.Matches(jsonArr, @"\{.*?\""point\""\s*:\s*\[\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\].*?\}", RegexOptions.Singleline); - - if (pointMatches.Count > 0) - { - StringBuilder synthesizedXml = new StringBuilder(); - foreach (Match pm in pointMatches) - { - if (float.TryParse(pm.Groups[1].Value, out float xVal) && float.TryParse(pm.Groups[2].Value, out float yVal)) - { - // Gemini uses 0-1000 scale usually - if (xVal > 1 || yVal > 1) - { - xVal /= 1000.0f; - yVal /= 1000.0f; - } - synthesizedXml.Append($"{xVal}{yVal}"); - } - } - if (synthesizedXml.Length > 0) - { - xml = synthesizedXml.ToString() + "\n" + xml; // Prepend to process downstream - matches = Regex.Matches(xml, @"<([a-zA-Z0-9_]+)(?:>.*?|/>)", RegexOptions.Singleline); - } - } - } - } - catch (Exception ex) - { - WulaLog.Debug($"[JSON Fallback Error] {ex.Message}"); - } - } if (matches.Count == 0 || (matches.Count == 1 && matches[0].Groups[1].Value.Equals("no_action", StringComparison.OrdinalIgnoreCase))) { diff --git a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_CallPrefabAirdrop.cs b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_CallPrefabAirdrop.cs new file mode 100644 index 00000000..e4247507 --- /dev/null +++ b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_CallPrefabAirdrop.cs @@ -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 => "DefName of the prefabOptional, default is WULA_Prefab_Incomingintint"; + + public override string Execute(string args) + { + try + { + var parsed = ParseXmlArgs(args); + + if (!parsed.TryGetValue("prefabDefName", out string prefabDefName) || string.IsNullOrWhiteSpace(prefabDefName)) + { + return "Error: Missing . Example: WULA_NewColonyBase"; + } + + 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 and ."; + } + + 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.GetNamed(prefabDefName, false); + if (prefabDef == null) + { + return $"Error: PrefabDef '{prefabDefName}' not found."; + } + + // Check if skyfaller exists + ThingDef skyfallerDef = DefDatabase.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}"; + } + } + } +} diff --git a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetAvailablePrefabs.cs b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetAvailablePrefabs.cs new file mode 100644 index 00000000..ba72f370 --- /dev/null +++ b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetAvailablePrefabs.cs @@ -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 => ""; + + public override string Execute(string args) + { + try + { + var prefabs = DefDatabase.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}"; + } + } + } +} diff --git a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_VisualClick.cs b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_VisualClick.cs deleted file mode 100644 index 9a43db5d..00000000 --- a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_VisualClick.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Threading.Tasks; -using UnityEngine; -using WulaFallenEmpire.EventSystem.AI.Agent; - -namespace WulaFallenEmpire.EventSystem.AI.Tools -{ - /// - /// 瑙嗚鐐瑰嚮宸ュ叿 - 浣跨敤 VLM 鍒嗘瀽灞忓箷鍚庢ā鎷熼紶鏍囩偣鍑 - /// 閫傜敤浜庡師鐗 API 鏃犳硶鐩存帴鎿嶄綔鐨 mod UI 鍏冪礌 - /// - 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 => - "0-1涔嬮棿鐨刋姣斾緥0-1涔嬮棿鐨刌姣斾緥鍙夛紝true涓哄彸閿"; - - public override Task ExecuteAsync(string args) - { - try - { - var argsDict = ParseXmlArgs(args); - - // 瑙f瀽 X 鍧愭爣 - if (!argsDict.TryGetValue("x", out string xStr) || !float.TryParse(xStr, out float x)) - { - return Task.FromResult("Error: 缂哄皯鏈夋晥鐨 x 鍧愭爣 (0-1涔嬮棿鐨勬瘮渚嬪)"); - } - - // 瑙f瀽 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 涔嬮棿"); - } - - // 瑙f瀽鍙抽敭閫夐」 - 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}"); - } - } - } - - /// - /// 瑙嗚杈撳叆鏂囨湰宸ュ叿 - 鍦ㄥ綋鍓嶇劍鐐逛綅缃緭鍏ユ枃鏈 - /// - public class Tool_VisualTypeText : AITool - { - public override string Name => "visual_type_text"; - - public override string Description => - "鍦ㄥ綋鍓嶇劍鐐逛綅缃緭鍏ユ枃鏈傞傜敤浜庨渶瑕佹枃鏈緭鍏ョ殑瀵硅瘽妗嗘垨杈撳叆妗嗐傚簲鍏堢敤 visual_click 鐐瑰嚮杈撳叆妗嗚幏鍙栫劍鐐广"; - - public override string UsageSchema => - "瑕佽緭鍏ョ殑鏂囨湰"; - - public override Task 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}"); - } - } - } - - /// - /// 瑙嗚婊氬姩宸ュ叿 - 鍦ㄥ綋鍓嶄綅缃粴鍔ㄩ紶鏍囨粴杞 - /// - public class Tool_VisualScroll : AITool - { - public override string Name => "visual_scroll"; - - public override string Description => - "鍦ㄥ綋鍓嶉紶鏍囦綅缃粴鍔ㄣ傚彲閫夊厛绉诲姩鍒版寚瀹氫綅缃啀婊氬姩銆俤elta 姝f暟鍚戜笂婊氬姩锛岃礋鏁板悜涓嬫粴鍔ㄣ"; - - public override string UsageSchema => - "婊氬姩閲忥紝姝f暟鍚戜笂璐熸暟鍚戜笅鍙夛紝0-1 X鍧愭爣鍙夛紝0-1 Y鍧愭爣"; - - public override Task 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}"); - } - } - } -} diff --git a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_VisualUtils.cs b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_VisualUtils.cs deleted file mode 100644 index d652bde1..00000000 --- a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_VisualUtils.cs +++ /dev/null @@ -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 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 ExecuteAsync(string args); - } - - /// - /// 瑙嗚鎷栨嫿宸ュ叿 - /// - public class Tool_VisualDrag : VisualToolBase - { - public override string Name => "visual_drag"; - public override string Description => "浠庤捣濮嬪潗鏍囨嫋鎷藉埌缁撴潫鍧愭爣銆傞傜敤浜庢閫夊崟浣嶃佹嫋鍔ㄦ粦鍧楁垨鍦板浘銆"; - public override string UsageSchema => "0-10-10-10-1绉(榛樿0.5)"; - - public override Task 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}"); } - } - } - - /// - /// 瑙嗚蹇嵎閿伐鍏 (閫氱敤) - /// - 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 => "蹇嵎閿鍙鍙"; - - public override Task 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}"); } - } - } - - /// - /// 瑙嗚绛夊緟宸ュ叿 - /// - public class Tool_VisualWait : VisualToolBase - { - public override string Name => "visual_wait"; - public override string Description => "绛夊緟鎸囧畾鏃堕棿銆傜敤浜庣瓑寰匲I鍔ㄧ敾鎴栧姞杞姐"; - public override string UsageSchema => "绉掓暟"; - - public override Task 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}"); } - } - } - - /// - /// 瑙嗚鍒犻櫎鏂囨湰宸ュ叿 - /// - public class Tool_VisualDeleteText : VisualToolBase - { - public override string Name => "visual_delete_text"; - public override string Description => "鐐瑰嚮鎸囧畾浣嶇疆骞舵寜 Backspace 鍒犻櫎鎸囧畾鏁伴噺鐨勫瓧绗︺傜敤浜庢竻绌鸿緭鍏ユ銆"; - public override string UsageSchema => "0-10-1瀛楃鏁(榛樿1)"; - - public override Task 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}"); } - } - } -}