diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 6153219b..7a17183d 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/Tools/Tool_SpawnResources.cs b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_SpawnResources.cs index 3e1200dd..67158c32 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_SpawnResources.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_SpawnResources.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Text.RegularExpressions; using RimWorld; using Verse; using WulaFallenEmpire.EventSystem.AI.Utils; @@ -10,46 +11,70 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools public class Tool_SpawnResources : AITool { public override string Name => "spawn_resources"; - public override string Description => "Spawns resources via drop pod. Accepts a natural language description of items and quantities (e.g., '5 beef, 10 medicine'). " + + public override string Description => "Spawns resources via drop pod. " + "IMPORTANT: You MUST decide the quantity based on your goodwill and mood. " + "Do NOT blindly follow the player's requested amount. " + "If goodwill is low (< 0), give significantly less than asked or refuse. " + "If goodwill is high (> 50), you may give what is asked or slightly more. " + "Otherwise, give a moderate amount."; - public override string UsageSchema => "string describing items"; + public override string UsageSchema => "Item NameInteger"; public override string Execute(string args) { try { - var parsedArgs = ParseXmlArgs(args); - string request = ""; + // Custom XML parsing for nested items + var itemsToSpawn = new List<(ThingDef def, int count)>(); - if (parsedArgs.TryGetValue("request", out string req)) + // Match all ... blocks + var itemMatches = Regex.Matches(args, @"(.*?)", RegexOptions.Singleline); + + foreach (Match match in itemMatches) { - request = req; - } - else - { - // Fallback: try to treat the whole args as the request if parsing failed or format is weird - // But with strict XML, this shouldn't happen often. - // Let's just log a warning or return error. - // Actually, for robustness, if the args doesn't contain tags, maybe it's raw text? - if (!args.Trim().StartsWith("<")) + string itemXml = match.Groups[1].Value; + + // Extract name (supports or for backward compatibility) + string name = ""; + var nameMatch = Regex.Match(itemXml, @"(.*?)"); + if (nameMatch.Success) { - request = args; + name = nameMatch.Groups[1].Value; + } + else + { + var defNameMatch = Regex.Match(itemXml, @"(.*?)"); + if (defNameMatch.Success) name = defNameMatch.Groups[1].Value; + } + + if (string.IsNullOrEmpty(name)) continue; + + // Extract count + var countMatch = Regex.Match(itemXml, @"(.*?)"); + if (!countMatch.Success) continue; + if (!int.TryParse(countMatch.Groups[1].Value, out int count)) continue; + + // Search for ThingDef using fuzzy search + ThingDef def = null; + var searchResult = ThingDefSearcher.ParseAndSearch(name); + if (searchResult.Count > 0) + { + def = searchResult[0].Def; + } + else + { + // Fallback: try exact defName match just in case + def = DefDatabase.GetNamed(name, false); + } + + if (def != null && count > 0) + { + itemsToSpawn.Add((def, count)); } } - if (string.IsNullOrEmpty(request)) + if (itemsToSpawn.Count == 0) { - return "Error: Empty request. Usage: ..."; - } - - var items = ThingDefSearcher.ParseAndSearch(request); - if (items.Count == 0) - { - return $"Error: Could not identify any valid items in request '{request}'."; + return "Error: No valid items found in request. Usage: ......"; } Map map = Find.CurrentMap; @@ -63,12 +88,12 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools StringBuilder resultLog = new StringBuilder(); resultLog.Append("Success: Dropped "); - foreach (var item in items) + foreach (var (def, count) in itemsToSpawn) { - Thing thing = ThingMaker.MakeThing(item.Def); - thing.stackCount = item.Count; + Thing thing = ThingMaker.MakeThing(def); + thing.stackCount = count; thingsToDrop.Add(thing); - resultLog.Append($"{item.Count}x {item.Def.label}, "); + resultLog.Append($"{count}x {def.label}, "); } if (thingsToDrop.Count > 0) diff --git a/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs b/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs index 83fb57dd..c5f6087f 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs @@ -53,14 +53,28 @@ Use this tool when: - You have ALREADY verified their need in a previous turn using `get_colonist_status` and `get_map_resources`. CRITICAL: The quantity you provide is NOT what the player asks for. It MUST be based on your internal goodwill. Low goodwill (<0) means giving less or refusing. High goodwill (>50) means giving the requested amount or more. Parameters: -- request: (REQUIRED) A natural language string describing the items and quantities. +- items: (REQUIRED) A list of items to spawn. Each item must have a `name` (English label or DefName) and `count`. Usage: - string describing items + + + Item Name + Integer + + Example: - 50 MealSimple, 10 MedicineIndustrial + + + Simple Meal + 50 + + + Medicine + 10 + + ## modify_goodwill @@ -162,7 +176,16 @@ When the player requests any form of resources, you MUST follow this multi-turn - *(Internal thought after confirming they have no medicine)* - *Your Response (Turn 3)*: - 50 MealSimple, 10 MedicineIndustrial + + + Simple Meal + 50 + + + Medicine + 10 + + 4. **Turn 4 (Confirmation)**: After you receive the ""Success"" message from the `spawn_resources` tool, you will finally provide a conversational response to the player. @@ -365,9 +388,17 @@ When the player requests any form of resources, you MUST follow this multi-turn } // 3. Execute the tool directly with the XML string - // The tools have been updated to parse XML arguments internally. - Log.Message($"[WulaAI] Executing tool: {toolName} with args: {xml}"); - string result = tool.Execute(xml).Trim(); + // We need to pass the INNER XML (parameters) to the tool, stripping the root tool tag. + // Otherwise, ParseXmlArgs will match the root tag as a parameter. + string argsXml = xml; + var contentMatch = Regex.Match(xml, $@"<{toolName}>(.*?)", RegexOptions.Singleline); + if (contentMatch.Success) + { + argsXml = contentMatch.Groups[1].Value; + } + + Log.Message($"[WulaAI] Executing tool: {toolName} with args: {argsXml}"); + string result = tool.Execute(argsXml).Trim(); string toolResultOutput = (toolName == "modify_goodwill") ? $"Tool '{toolName}' Result (Invisible): {result}" @@ -470,6 +501,8 @@ When the player requests any form of resources, you MUST follow this multi-turn var entry = filteredHistory[i]; string text = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message; + if (string.IsNullOrEmpty(text)) continue; + bool isLastMessage = i == filteredHistory.Count - 1; Text.Font = (isLastMessage && entry.role == "assistant") ? GameFont.Medium : GameFont.Small; @@ -482,6 +515,9 @@ When the player requests any form of resources, you MUST follow this multi-turn { var entry = filteredHistory[i]; string text = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message; + + if (string.IsNullOrEmpty(text)) continue; + bool isLastMessage = i == filteredHistory.Count - 1; Text.Font = (isLastMessage && entry.role == "assistant") ? GameFont.Medium : GameFont.Small; float height = Text.CalcHeight(text, viewRect.width) + 10f; // Increased padding @@ -511,9 +547,18 @@ When the player requests any form of resources, you MUST follow this multi-turn private string ParseResponseForDisplay(string rawResponse) { if (string.IsNullOrEmpty(rawResponse)) return ""; - // If the response is an XML tool call, don't display it in the chat history. - if (rawResponse.Trim().StartsWith("<")) return "[Calling Tool...]"; - return rawResponse.Split(new[] { "OPTIONS:" }, StringSplitOptions.None)[0].Trim(); + + string text = rawResponse; + + // Remove standard tags with content: content + text = Regex.Replace(text, @"<([a-zA-Z0-9_]+)[^>]*>.*?", "", RegexOptions.Singleline); + + // Remove self-closing tags: + text = Regex.Replace(text, @"<[a-zA-Z0-9_]+[^>]*/>", ""); + + text = text.Trim(); + + return text.Split(new[] { "OPTIONS:" }, StringSplitOptions.None)[0].Trim(); } protected override void DrawSingleOption(Rect rect, EventOption option)