已添加新工具 get_recent_notifications:
新文件:Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetRecentNotifications.cs 功能:读取最近的 Letter + Message,按游戏 tick 从新到旧排序;默认 count=10,最大 100;默认同时包含 letter 和 message,可用 <includeLetters> / <includeMessages> 关闭其一。 已注册并写入系统工具说明:Source/WulaFallenEmpire/EventSystem/AI/UI/Dialog_AIConversation.cs 已编译输出:1.6/1.6/Assemblies/WulaFallenEmpire.dll 用法示例: <get_recent_notifications><count>20</count></get_recent_notifications>
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire.EventSystem.AI.Tools
|
||||
{
|
||||
public class Tool_GetRecentNotifications : AITool
|
||||
{
|
||||
public override string Name => "get_recent_notifications";
|
||||
public override string Description => "Returns the most recent letters and messages, sorted by in-game time from newest to oldest.";
|
||||
public override string UsageSchema =>
|
||||
"<get_recent_notifications><count>int (optional, default 10, max 100)</count><includeLetters>true/false (optional, default true)</includeLetters><includeMessages>true/false (optional, default true)</includeMessages></get_recent_notifications>";
|
||||
|
||||
private struct NotificationEntry
|
||||
{
|
||||
public int Tick;
|
||||
public string Kind;
|
||||
public string Title;
|
||||
public string Body;
|
||||
}
|
||||
|
||||
public override string Execute(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
int count = 10;
|
||||
bool includeLetters = true;
|
||||
bool includeMessages = true;
|
||||
|
||||
var parsed = ParseXmlArgs(args);
|
||||
if (parsed.TryGetValue("count", out var countStr) && int.TryParse(countStr, out int parsedCount))
|
||||
{
|
||||
count = parsedCount;
|
||||
}
|
||||
|
||||
if (parsed.TryGetValue("includeLetters", out var incLettersStr) && bool.TryParse(incLettersStr, out bool parsedLetters))
|
||||
{
|
||||
includeLetters = parsedLetters;
|
||||
}
|
||||
|
||||
if (parsed.TryGetValue("includeMessages", out var incMessagesStr) && bool.TryParse(incMessagesStr, out bool parsedMessages))
|
||||
{
|
||||
includeMessages = parsedMessages;
|
||||
}
|
||||
|
||||
count = Math.Max(1, Math.Min(100, count));
|
||||
|
||||
int now = Find.TickManager?.TicksGame ?? 0;
|
||||
var entries = new List<NotificationEntry>();
|
||||
|
||||
if (includeLetters)
|
||||
{
|
||||
entries.AddRange(ReadLetters(now));
|
||||
}
|
||||
|
||||
if (includeMessages)
|
||||
{
|
||||
entries.AddRange(ReadMessages(now));
|
||||
}
|
||||
|
||||
if (entries.Count == 0)
|
||||
{
|
||||
return "No recent letters or messages found.";
|
||||
}
|
||||
|
||||
var selected = entries
|
||||
.OrderByDescending(e => e.Tick)
|
||||
.ThenByDescending(e => e.Kind)
|
||||
.Take(count)
|
||||
.ToList();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine($"Found {selected.Count} recent notifications (newest -> oldest):");
|
||||
|
||||
int idx = 1;
|
||||
foreach (var e in selected)
|
||||
{
|
||||
sb.AppendLine($"{idx}. [{e.Kind}] tick={e.Tick}");
|
||||
if (!string.IsNullOrWhiteSpace(e.Title))
|
||||
{
|
||||
sb.AppendLine($" Title: {TrimForDisplay(e.Title, 140)}");
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(e.Body))
|
||||
{
|
||||
sb.AppendLine($" Text: {TrimForDisplay(e.Body, 600)}");
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
return sb.ToString().TrimEnd();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<NotificationEntry> ReadLetters(int fallbackNow)
|
||||
{
|
||||
var list = new List<NotificationEntry>();
|
||||
|
||||
object letterStack = Find.LetterStack;
|
||||
if (letterStack == null) return list;
|
||||
|
||||
IEnumerable letters = null;
|
||||
try
|
||||
{
|
||||
letters = GetMemberValue(letterStack, "LettersListForReading", "letters", "lettersList", "lettersListForReading") as IEnumerable;
|
||||
}
|
||||
catch
|
||||
{
|
||||
letters = null;
|
||||
}
|
||||
|
||||
if (letters == null) return list;
|
||||
|
||||
foreach (var letter in letters)
|
||||
{
|
||||
if (letter == null) continue;
|
||||
|
||||
int tick = GetInt(letter, "arrivalTick", "receivedTick", "tick", "ticksGame") ?? fallbackNow;
|
||||
string label = GetString(letter, "label", "Label", "LabelCap");
|
||||
string text = GetString(letter, "text", "Text", "TextString", "LetterText");
|
||||
|
||||
string defName = GetString(GetMemberValue(letter, "def", "Def"), "defName", "DefName");
|
||||
string kind = string.IsNullOrWhiteSpace(defName) ? "Letter" : $"Letter:{defName}";
|
||||
|
||||
list.Add(new NotificationEntry
|
||||
{
|
||||
Tick = tick,
|
||||
Kind = kind,
|
||||
Title = label,
|
||||
Body = text
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static IEnumerable<NotificationEntry> ReadMessages(int fallbackNow)
|
||||
{
|
||||
var list = new List<NotificationEntry>();
|
||||
|
||||
IEnumerable messages = null;
|
||||
try
|
||||
{
|
||||
messages = GetMemberValue(typeof(Messages), "MessagesListForReading", "messagesListForReading", "messages") as IEnumerable;
|
||||
}
|
||||
catch
|
||||
{
|
||||
messages = null;
|
||||
}
|
||||
|
||||
if (messages == null) return list;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
if (message == null) continue;
|
||||
|
||||
int tick = GetInt(message, "time", "timeReceived", "receivedTick", "ticks", "tick", "startTick") ?? fallbackNow;
|
||||
string text = GetString(message, "text", "Text", "message", "Message");
|
||||
string typeDef = GetString(GetMemberValue(message, "def", "Def", "type", "Type", "messageType", "MessageType"), "defName", "DefName");
|
||||
string kind = string.IsNullOrWhiteSpace(typeDef) ? "Message" : $"Message:{typeDef}";
|
||||
|
||||
list.Add(new NotificationEntry
|
||||
{
|
||||
Tick = tick,
|
||||
Kind = kind,
|
||||
Title = null,
|
||||
Body = text
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string TrimForDisplay(string s, int maxChars)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s)) return s;
|
||||
string oneLine = s.Replace("\r", " ").Replace("\n", " ").Trim();
|
||||
if (oneLine.Length <= maxChars) return oneLine;
|
||||
return oneLine.Substring(0, maxChars) + "...";
|
||||
}
|
||||
|
||||
private static object GetMemberValue(object objOrType, params string[] names)
|
||||
{
|
||||
if (objOrType == null || names == null || names.Length == 0) return null;
|
||||
|
||||
Type t = objOrType as Type ?? objOrType.GetType();
|
||||
bool isStatic = objOrType is Type;
|
||||
|
||||
const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static;
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
|
||||
var prop = t.GetProperty(name, Flags);
|
||||
if (prop != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return prop.GetValue(isStatic ? null : objOrType, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
var field = t.GetField(name, Flags);
|
||||
if (field != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return field.GetValue(isStatic ? null : objOrType);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int? GetInt(object obj, params string[] names)
|
||||
{
|
||||
object val = GetMemberValue(obj, names);
|
||||
if (val == null) return null;
|
||||
if (val is int i) return i;
|
||||
if (val is long l)
|
||||
{
|
||||
if (l > int.MaxValue) return int.MaxValue;
|
||||
if (l < int.MinValue) return int.MinValue;
|
||||
return (int)l;
|
||||
}
|
||||
if (val is float f) return (int)f;
|
||||
if (val is double d) return (int)d;
|
||||
if (int.TryParse(val.ToString(), out int parsed)) return parsed;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetString(object obj, params string[] names)
|
||||
{
|
||||
object val = GetMemberValue(obj, names);
|
||||
if (val == null) return null;
|
||||
return val.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,25 +151,38 @@ Example:
|
||||
<get_colonist_status/>
|
||||
|
||||
## get_map_resources
|
||||
Description: Checks the player's map for specific resources or buildings to verify their inventory.
|
||||
Description: Checks the player's map for specific resources or buildings to verify their inventory.
|
||||
Use this tool when:
|
||||
- The player claims they are lacking a specific resource (e.g., ""we need steel,"" ""we have no food"").
|
||||
- You want to assess the colony's material wealth before making a decision.
|
||||
Parameters:
|
||||
- resourceName: (OPTIONAL) The specific `ThingDef` name of the resource to check (e.g., 'Steel', 'MealSimple'). If omitted, provides a general overview.
|
||||
Usage:
|
||||
<get_map_resources>
|
||||
<resourceName>optional resource name</resourceName>
|
||||
</get_map_resources>
|
||||
Example (checking for Steel):
|
||||
<get_map_resources>
|
||||
<resourceName>Steel</resourceName>
|
||||
</get_map_resources>
|
||||
|
||||
## get_recent_notifications
|
||||
Description: Gets the most recent letters and messages, sorted by in-game time from newest to oldest.
|
||||
Use this tool when:
|
||||
- The player claims they are lacking a specific resource (e.g., ""we need steel,"" ""we have no food"").
|
||||
- You want to assess the colony's material wealth before making a decision.
|
||||
- You need recent context about what happened (raids, alerts, rewards, failures) without relying on player memory.
|
||||
Parameters:
|
||||
- resourceName: (OPTIONAL) The specific `ThingDef` name of the resource to check (e.g., 'Steel', 'MealSimple'). If omitted, provides a general overview.
|
||||
- count: (OPTIONAL) How many entries to return (default 10, max 100).
|
||||
- includeLetters: (OPTIONAL) true/false (default true).
|
||||
- includeMessages: (OPTIONAL) true/false (default true).
|
||||
Usage:
|
||||
<get_map_resources>
|
||||
<resourceName>optional resource name</resourceName>
|
||||
</get_map_resources>
|
||||
Example (checking for Steel):
|
||||
<get_map_resources>
|
||||
<resourceName>Steel</resourceName>
|
||||
</get_map_resources>
|
||||
<get_recent_notifications>
|
||||
<count>10</count>
|
||||
</get_recent_notifications>
|
||||
|
||||
## get_map_pawns
|
||||
Description: Scans the current map and lists pawns. Supports filtering by relation/type/status.
|
||||
Use this tool when:
|
||||
- You need to know what pawns are present on the map (raiders, visitors, animals, mechs, colonists).
|
||||
Description: Scans the current map and lists pawns. Supports filtering by relation/type/status.
|
||||
Use this tool when:
|
||||
- You need to know what pawns are present on the map (raiders, visitors, animals, mechs, colonists).
|
||||
- The player claims there are threats or asks about who/what is nearby.
|
||||
Parameters:
|
||||
- filter: (OPTIONAL) Comma-separated filters: friendly, hostile, neutral, colonist, animal, mech, humanlike, prisoner, slave, guest, wild, downed.
|
||||
@@ -266,13 +279,14 @@ When the player requests any form of resources, you MUST follow this multi-turn
|
||||
_tools.Add(new Tool_SpawnResources());
|
||||
_tools.Add(new Tool_ModifyGoodwill());
|
||||
_tools.Add(new Tool_SendReinforcement());
|
||||
_tools.Add(new Tool_GetColonistStatus());
|
||||
_tools.Add(new Tool_GetMapResources());
|
||||
_tools.Add(new Tool_GetMapPawns());
|
||||
_tools.Add(new Tool_CallBombardment());
|
||||
_tools.Add(new Tool_ChangeExpression());
|
||||
_tools.Add(new Tool_SearchThingDef());
|
||||
}
|
||||
_tools.Add(new Tool_GetColonistStatus());
|
||||
_tools.Add(new Tool_GetMapResources());
|
||||
_tools.Add(new Tool_GetRecentNotifications());
|
||||
_tools.Add(new Tool_GetMapPawns());
|
||||
_tools.Add(new Tool_CallBombardment());
|
||||
_tools.Add(new Tool_ChangeExpression());
|
||||
_tools.Add(new Tool_SearchThingDef());
|
||||
}
|
||||
|
||||
public override Vector2 InitialSize => def.windowSize != Vector2.zero ? def.windowSize : Dialog_CustomDisplay.Config.windowSize;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user