zc
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
@@ -8,8 +9,33 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string UsageSchema { get; } // JSON schema or simple description of args
|
||||
public abstract string UsageSchema { get; } // XML schema description
|
||||
|
||||
public abstract string Execute(string args);
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to parse XML arguments into a dictionary.
|
||||
/// Supports simple tags and CDATA blocks.
|
||||
/// </summary>
|
||||
protected Dictionary<string, string> ParseXmlArgs(string xml)
|
||||
{
|
||||
var argsDict = new Dictionary<string, string>();
|
||||
if (string.IsNullOrEmpty(xml)) return argsDict;
|
||||
|
||||
// Regex to match <tag>value</tag> or <tag><![CDATA[value]]></tag>
|
||||
// Group 1: Tag name
|
||||
// Group 2: CDATA value
|
||||
// Group 3: Simple value
|
||||
var paramMatches = Regex.Matches(xml, @"<([a-zA-Z0-9_]+)>(?:<!\[CDATA\[(.*?)]]>|(.*?))</\1>", RegexOptions.Singleline);
|
||||
|
||||
foreach (Match match in paramMatches)
|
||||
{
|
||||
string key = match.Groups[1].Value;
|
||||
string value = match.Groups[2].Success ? match.Groups[2].Value : match.Groups[3].Value;
|
||||
argsDict[key] = value;
|
||||
}
|
||||
|
||||
return argsDict;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,25 +8,31 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public override string Name => "change_expression";
|
||||
public override string Description => "Changes your visual expression/portrait to match your current mood or reaction.";
|
||||
public override string UsageSchema => "{\"expression_id\": \"int (1-6)\"}";
|
||||
public override string UsageSchema => "<change_expression><expression_id>int (1-6)</expression_id></change_expression>";
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = SimpleJsonParser.Parse(args);
|
||||
var parsedArgs = ParseXmlArgs(args);
|
||||
int id = 0;
|
||||
if (json.TryGetValue("expression_id", out string idStr) && int.TryParse(idStr, out id))
|
||||
|
||||
if (parsedArgs.TryGetValue("expression_id", out string idStr))
|
||||
{
|
||||
var window = Find.WindowStack.WindowOfType<Dialog_AIConversation>();
|
||||
if (window != null)
|
||||
if (int.TryParse(idStr, out id))
|
||||
{
|
||||
window.SetPortrait(id);
|
||||
return $"Expression changed to {id}.";
|
||||
var window = Find.WindowStack.WindowOfType<Dialog_AIConversation>();
|
||||
if (window != null)
|
||||
{
|
||||
window.SetPortrait(id);
|
||||
return $"Expression changed to {id}.";
|
||||
}
|
||||
return "Error: Dialog window not found.";
|
||||
}
|
||||
return "Error: Dialog window not found.";
|
||||
return "Error: Invalid arguments. 'expression_id' must be an integer.";
|
||||
}
|
||||
return "Error: Invalid arguments. 'expression_id' must be an integer.";
|
||||
|
||||
return "Error: Missing <expression_id> parameter.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public override string Name => "get_colonist_status";
|
||||
public override string Description => "Returns detailed status of colonists. Can be filtered to find the colonist in the worst condition (e.g., lowest mood, most injured). This helps the AI understand the colony's state without needing to know specific names.";
|
||||
public override string UsageSchema => "{'filter': 'string (optional, can be 'lowest_mood', 'most_injured', 'hungriest', 'most_tired')'}";
|
||||
public override string UsageSchema => "<get_colonist_status><filter>string (optional, can be 'lowest_mood', 'most_injured', 'hungriest', 'most_tired')</filter></get_colonist_status>";
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
@@ -20,8 +20,8 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
string filter = null;
|
||||
if (!string.IsNullOrEmpty(args))
|
||||
{
|
||||
var json = SimpleJsonParser.Parse(args);
|
||||
if (json != null && json.TryGetValue("filter", out var filterObj) && filterObj is string filterStr)
|
||||
var parsedArgs = ParseXmlArgs(args);
|
||||
if (parsedArgs.TryGetValue("filter", out string filterStr))
|
||||
{
|
||||
filter = filterStr.ToLower();
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public override string Name => "get_map_resources";
|
||||
public override string Description => "Checks the player's map for specific resources or buildings. Use this to verify if the player is truly lacking something they requested (e.g., 'we need steel'). Returns inventory count and mineable deposits.";
|
||||
public override string UsageSchema => "{\"resourceName\": \"string (optional, e.g., 'Steel')\"}";
|
||||
public override string UsageSchema => "<get_map_resources><resourceName>string (optional, e.g., 'Steel')</resourceName></get_map_resources>";
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
@@ -22,11 +22,18 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
if (map == null) return "Error: No active map.";
|
||||
|
||||
string resourceName = "";
|
||||
var cleanArgs = args.Trim('{', '}').Replace("\"", "");
|
||||
var parts = cleanArgs.Split(':');
|
||||
if (parts.Length >= 2)
|
||||
var parsedArgs = ParseXmlArgs(args);
|
||||
if (parsedArgs.TryGetValue("resourceName", out string resName))
|
||||
{
|
||||
resourceName = parts[1].Trim();
|
||||
resourceName = resName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback
|
||||
if (!args.Trim().StartsWith("<"))
|
||||
{
|
||||
resourceName = args;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -80,9 +87,9 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
|
||||
// Key resources
|
||||
var keyResources = new[] { "Steel", "WoodLog", "ComponentIndustrial", "MedicineIndustrial", "MealSimple" };
|
||||
foreach (var resName in keyResources)
|
||||
foreach (var keyResName in keyResources)
|
||||
{
|
||||
ThingDef def = DefDatabase<ThingDef>.GetNamed(resName, false);
|
||||
ThingDef def = DefDatabase<ThingDef>.GetNamed(keyResName, false);
|
||||
if (def != null)
|
||||
{
|
||||
int count = map.resourceCounter.GetCount(def);
|
||||
|
||||
@@ -8,22 +8,32 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public override string Name => "modify_goodwill";
|
||||
public override string Description => "Adjusts your goodwill towards the player. Use this to reflect your changing opinion based on the conversation. Positive values increase goodwill, negative values decrease it. Keep changes small (e.g., -5 to 5). THIS IS INVISIBLE TO THE PLAYER.";
|
||||
public override string UsageSchema => "{\"amount\": \"int\"}";
|
||||
public override string UsageSchema => "<modify_goodwill><amount>integer</amount></modify_goodwill>";
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cleanArgs = args.Trim('{', '}').Replace("\"", "");
|
||||
var parts = cleanArgs.Split(':');
|
||||
var parsedArgs = ParseXmlArgs(args);
|
||||
int amount = 0;
|
||||
|
||||
foreach (var part in parts)
|
||||
|
||||
if (parsedArgs.TryGetValue("amount", out string amountStr))
|
||||
{
|
||||
if (int.TryParse(part.Trim(), out int val))
|
||||
if (!int.TryParse(amountStr, out amount))
|
||||
{
|
||||
return $"Error: Invalid amount '{amountStr}'. Must be an integer.";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback for simple number string
|
||||
if (int.TryParse(args.Trim(), out int val))
|
||||
{
|
||||
amount = val;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "Error: Missing <amount> parameter.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
}
|
||||
}
|
||||
|
||||
public override string UsageSchema => "{\"units\": \"string (e.g., 'Wula_PIA_Heavy_Unit_Melee: 2, Wula_PIA_Legion_Escort_Unit: 5')\"}";
|
||||
public override string UsageSchema => "<send_reinforcement><units>string (e.g., 'Wula_PIA_Heavy_Unit_Melee: 2, Wula_PIA_Legion_Escort_Unit: 5')</units></send_reinforcement>";
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
@@ -68,67 +68,26 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
if (faction == null) return "Error: Faction Wula_PIA_Legion_Faction not found.";
|
||||
|
||||
// Parse args
|
||||
var cleanArgs = args.Trim('{', '}').Replace("\"", "");
|
||||
var parts = cleanArgs.Split(':');
|
||||
var parsedArgs = ParseXmlArgs(args);
|
||||
string unitString = "";
|
||||
if (parts.Length >= 2 && parts[0].Trim() == "units")
|
||||
|
||||
if (parsedArgs.TryGetValue("units", out string units))
|
||||
{
|
||||
unitString = args.Substring(args.IndexOf(':') + 1).Trim('"', ' ', '}');
|
||||
unitString = units;
|
||||
}
|
||||
else
|
||||
{
|
||||
unitString = cleanArgs;
|
||||
// Fallback
|
||||
if (!args.Trim().StartsWith("<"))
|
||||
{
|
||||
unitString = args;
|
||||
}
|
||||
}
|
||||
|
||||
var unitPairs = unitString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// Build dynamic PawnGroupMaker
|
||||
PawnGroupMaker groupMaker = new PawnGroupMaker();
|
||||
groupMaker.kindDef = PawnGroupKindDefOf.Combat;
|
||||
groupMaker.options = new List<PawnGenOption>();
|
||||
|
||||
float totalCost = 0;
|
||||
|
||||
foreach (var pair in unitPairs)
|
||||
{
|
||||
var kv = pair.Split(':');
|
||||
if (kv.Length != 2) continue;
|
||||
|
||||
string defName = kv[0].Trim();
|
||||
if (!int.TryParse(kv[1].Trim(), out int count)) continue;
|
||||
|
||||
PawnKindDef kind = DefDatabase<PawnKindDef>.GetNamed(defName, false);
|
||||
if (kind == null) return $"Error: PawnKind '{defName}' not found.";
|
||||
|
||||
// Add to group maker options
|
||||
// We use selectionWeight 1 and count as cost? No, PawnGroupMaker uses points.
|
||||
// But here we want exact counts.
|
||||
// Standard PawnGroupMaker generates based on points.
|
||||
// If we want EXACT counts, we should just generate them manually or use a custom logic.
|
||||
// But user asked to use PawnGroupMaker dynamically.
|
||||
// Actually, Effect_TriggerRaid uses PawnGroupMaker to generate pawns based on points.
|
||||
// If we want exact counts, we can't easily use standard PawnGroupMaker logic which is probabilistic/points-based.
|
||||
// However, we can simulate it by creating a list of pawns manually, which is what I did before.
|
||||
// But user said "You should dynamically generate pawngroupmaker similar to Effect_TriggerRaid".
|
||||
// Effect_TriggerRaid uses existing PawnGroupMakers from XML or generates based on points.
|
||||
|
||||
// Let's stick to manual generation but wrapped in a way that respects the user's request for "dynamic composition".
|
||||
// Actually, if the user wants AI to decide composition based on points, AI should just give us the list.
|
||||
// If AI gives list, we generate list.
|
||||
|
||||
// Let's use the manual generation approach but ensure we use the correct raid logic.
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Pawn p = PawnGenerator.GeneratePawn(new PawnGenerationRequest(kind, faction, PawnGenerationContext.NonPlayer, -1, true));
|
||||
totalCost += kind.combatPower;
|
||||
// We can't easily add to a "group maker" to generate exact counts without hacking it.
|
||||
// So we will just collect the pawns.
|
||||
}
|
||||
}
|
||||
|
||||
// Re-parsing to get the list of pawns (I can't use the loop above directly because I need to validate points first)
|
||||
List<Pawn> pawnsToSpawn = new List<Pawn>();
|
||||
totalCost = 0;
|
||||
float totalCost = 0;
|
||||
foreach (var pair in unitPairs)
|
||||
{
|
||||
var kv = pair.Split(':');
|
||||
|
||||
@@ -16,31 +16,34 @@ namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
"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 => "{\"request\": \"string (e.g., '5 beef, 10 medicine')\"}";
|
||||
public override string UsageSchema => "<spawn_resources><request>string describing items</request></spawn_resources>";
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse args: {"request": "..."}
|
||||
var parsedArgs = ParseXmlArgs(args);
|
||||
string request = "";
|
||||
try
|
||||
|
||||
if (parsedArgs.TryGetValue("request", out string req))
|
||||
{
|
||||
var parsed = SimpleJsonParser.Parse(args);
|
||||
if (parsed.TryGetValue("request", out string req))
|
||||
{
|
||||
request = req;
|
||||
}
|
||||
request = req;
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
// Fallback for non-json args
|
||||
request = args.Trim('"');
|
||||
// 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("<"))
|
||||
{
|
||||
request = args;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request))
|
||||
{
|
||||
return "Error: Empty request.";
|
||||
return "Error: Empty request. Usage: <spawn_resources><request>...</request></spawn_resources>";
|
||||
}
|
||||
|
||||
var items = ThingDefSearcher.ParseAndSearch(request);
|
||||
|
||||
Reference in New Issue
Block a user