zc
This commit is contained in:
Binary file not shown.
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using RimWorld;
|
using RimWorld;
|
||||||
using Verse;
|
using Verse;
|
||||||
using WulaFallenEmpire.EventSystem.AI.Utils;
|
using WulaFallenEmpire.EventSystem.AI.Utils;
|
||||||
@@ -10,46 +11,70 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
|||||||
public class Tool_SpawnResources : AITool
|
public class Tool_SpawnResources : AITool
|
||||||
{
|
{
|
||||||
public override string Name => "spawn_resources";
|
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. " +
|
"IMPORTANT: You MUST decide the quantity based on your goodwill and mood. " +
|
||||||
"Do NOT blindly follow the player's requested amount. " +
|
"Do NOT blindly follow the player's requested amount. " +
|
||||||
"If goodwill is low (< 0), give significantly less than asked or refuse. " +
|
"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. " +
|
"If goodwill is high (> 50), you may give what is asked or slightly more. " +
|
||||||
"Otherwise, give a moderate amount.";
|
"Otherwise, give a moderate amount.";
|
||||||
public override string UsageSchema => "<spawn_resources><request>string describing items</request></spawn_resources>";
|
public override string UsageSchema => "<spawn_resources><items><item><name>Item Name</name><count>Integer</count></item></items></spawn_resources>";
|
||||||
|
|
||||||
public override string Execute(string args)
|
public override string Execute(string args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parsedArgs = ParseXmlArgs(args);
|
// Custom XML parsing for nested items
|
||||||
string request = "";
|
var itemsToSpawn = new List<(ThingDef def, int count)>();
|
||||||
|
|
||||||
if (parsedArgs.TryGetValue("request", out string req))
|
// Match all <item>...</item> blocks
|
||||||
|
var itemMatches = Regex.Matches(args, @"<item>(.*?)</item>", RegexOptions.Singleline);
|
||||||
|
|
||||||
|
foreach (Match match in itemMatches)
|
||||||
{
|
{
|
||||||
request = req;
|
string itemXml = match.Groups[1].Value;
|
||||||
}
|
|
||||||
else
|
// Extract name (supports <name> or <defName> for backward compatibility)
|
||||||
{
|
string name = "";
|
||||||
// Fallback: try to treat the whole args as the request if parsing failed or format is weird
|
var nameMatch = Regex.Match(itemXml, @"<name>(.*?)</name>");
|
||||||
// But with strict XML, this shouldn't happen often.
|
if (nameMatch.Success)
|
||||||
// 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("<"))
|
|
||||||
{
|
{
|
||||||
request = args;
|
name = nameMatch.Groups[1].Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var defNameMatch = Regex.Match(itemXml, @"<defName>(.*?)</defName>");
|
||||||
|
if (defNameMatch.Success) name = defNameMatch.Groups[1].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(name)) continue;
|
||||||
|
|
||||||
|
// Extract count
|
||||||
|
var countMatch = Regex.Match(itemXml, @"<count>(.*?)</count>");
|
||||||
|
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<ThingDef>.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: <spawn_resources><request>...</request></spawn_resources>";
|
return "Error: No valid items found in request. Usage: <spawn_resources><items><item><name>...</name><count>...</count></item></items></spawn_resources>";
|
||||||
}
|
|
||||||
|
|
||||||
var items = ThingDefSearcher.ParseAndSearch(request);
|
|
||||||
if (items.Count == 0)
|
|
||||||
{
|
|
||||||
return $"Error: Could not identify any valid items in request '{request}'.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map map = Find.CurrentMap;
|
Map map = Find.CurrentMap;
|
||||||
@@ -63,12 +88,12 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
|||||||
StringBuilder resultLog = new StringBuilder();
|
StringBuilder resultLog = new StringBuilder();
|
||||||
resultLog.Append("Success: Dropped ");
|
resultLog.Append("Success: Dropped ");
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var (def, count) in itemsToSpawn)
|
||||||
{
|
{
|
||||||
Thing thing = ThingMaker.MakeThing(item.Def);
|
Thing thing = ThingMaker.MakeThing(def);
|
||||||
thing.stackCount = item.Count;
|
thing.stackCount = count;
|
||||||
thingsToDrop.Add(thing);
|
thingsToDrop.Add(thing);
|
||||||
resultLog.Append($"{item.Count}x {item.Def.label}, ");
|
resultLog.Append($"{count}x {def.label}, ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thingsToDrop.Count > 0)
|
if (thingsToDrop.Count > 0)
|
||||||
|
|||||||
@@ -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`.
|
- 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.
|
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:
|
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:
|
Usage:
|
||||||
<spawn_resources>
|
<spawn_resources>
|
||||||
<request>string describing items</request>
|
<items>
|
||||||
|
<item>
|
||||||
|
<name>Item Name</name>
|
||||||
|
<count>Integer</count>
|
||||||
|
</item>
|
||||||
|
</items>
|
||||||
</spawn_resources>
|
</spawn_resources>
|
||||||
Example:
|
Example:
|
||||||
<spawn_resources>
|
<spawn_resources>
|
||||||
<request>50 MealSimple, 10 MedicineIndustrial</request>
|
<items>
|
||||||
|
<item>
|
||||||
|
<name>Simple Meal</name>
|
||||||
|
<count>50</count>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<name>Medicine</name>
|
||||||
|
<count>10</count>
|
||||||
|
</item>
|
||||||
|
</items>
|
||||||
</spawn_resources>
|
</spawn_resources>
|
||||||
|
|
||||||
## modify_goodwill
|
## 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)*
|
- *(Internal thought after confirming they have no medicine)*
|
||||||
- *Your Response (Turn 3)*:
|
- *Your Response (Turn 3)*:
|
||||||
<spawn_resources>
|
<spawn_resources>
|
||||||
<request>50 MealSimple, 10 MedicineIndustrial</request>
|
<items>
|
||||||
|
<item>
|
||||||
|
<name>Simple Meal</name>
|
||||||
|
<count>50</count>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<name>Medicine</name>
|
||||||
|
<count>10</count>
|
||||||
|
</item>
|
||||||
|
</items>
|
||||||
</spawn_resources>
|
</spawn_resources>
|
||||||
|
|
||||||
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.
|
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
|
// 3. Execute the tool directly with the XML string
|
||||||
// The tools have been updated to parse XML arguments internally.
|
// We need to pass the INNER XML (parameters) to the tool, stripping the root tool tag.
|
||||||
Log.Message($"[WulaAI] Executing tool: {toolName} with args: {xml}");
|
// Otherwise, ParseXmlArgs will match the root tag as a parameter.
|
||||||
string result = tool.Execute(xml).Trim();
|
string argsXml = xml;
|
||||||
|
var contentMatch = Regex.Match(xml, $@"<{toolName}>(.*?)</{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")
|
string toolResultOutput = (toolName == "modify_goodwill")
|
||||||
? $"Tool '{toolName}' Result (Invisible): {result}"
|
? $"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];
|
var entry = filteredHistory[i];
|
||||||
string text = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message;
|
string text = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(text)) continue;
|
||||||
|
|
||||||
bool isLastMessage = i == filteredHistory.Count - 1;
|
bool isLastMessage = i == filteredHistory.Count - 1;
|
||||||
Text.Font = (isLastMessage && entry.role == "assistant") ? GameFont.Medium : GameFont.Small;
|
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];
|
var entry = filteredHistory[i];
|
||||||
string text = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message;
|
string text = entry.role == "assistant" ? ParseResponseForDisplay(entry.message) : entry.message;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(text)) continue;
|
||||||
|
|
||||||
bool isLastMessage = i == filteredHistory.Count - 1;
|
bool isLastMessage = i == filteredHistory.Count - 1;
|
||||||
Text.Font = (isLastMessage && entry.role == "assistant") ? GameFont.Medium : GameFont.Small;
|
Text.Font = (isLastMessage && entry.role == "assistant") ? GameFont.Medium : GameFont.Small;
|
||||||
float height = Text.CalcHeight(text, viewRect.width) + 10f; // Increased padding
|
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)
|
private string ParseResponseForDisplay(string rawResponse)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(rawResponse)) return "";
|
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...]";
|
string text = rawResponse;
|
||||||
return rawResponse.Split(new[] { "OPTIONS:" }, StringSplitOptions.None)[0].Trim();
|
|
||||||
|
// Remove standard tags with content: <tag>content</tag>
|
||||||
|
text = Regex.Replace(text, @"<([a-zA-Z0-9_]+)[^>]*>.*?</\1>", "", RegexOptions.Singleline);
|
||||||
|
|
||||||
|
// Remove self-closing tags: <tag/>
|
||||||
|
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)
|
protected override void DrawSingleOption(Rect rect, EventOption option)
|
||||||
|
|||||||
Reference in New Issue
Block a user