zc
This commit is contained in:
405
.agent/workflows/ai_letter_auto_commentary.md
Normal file
405
.agent/workflows/ai_letter_auto_commentary.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# AI Letter Auto-Response System - 开发文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
这个功能将使 P.I.A AI 能够自动监听游戏内的 Letter(信封通知),并根据内容智能决定是否向玩家发送评论、吐槽、警告或提供帮助建议。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 功能需求
|
||||||
|
|
||||||
|
### 核心功能
|
||||||
|
1. **Mod 设置开关**: 在设置中添加 `启用 AI 自动评论` 开关
|
||||||
|
2. **Letter 监听**: 拦截所有发送给玩家的 Letter
|
||||||
|
3. **智能判断**: AI 分析 Letter 内容,决定是否需要回应
|
||||||
|
4. **自动回复**: 通过现有的 AI 对话系统发送回复
|
||||||
|
|
||||||
|
### AI 回应类型
|
||||||
|
| 类型 | 触发场景示例 | 回应风格 |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| 警告 | 袭击通知、疫病爆发 | 紧急提醒,询问是否需要启动防御 |
|
||||||
|
| 吐槽 | 殖民者精神崩溃、愚蠢死亡 | 幽默/讽刺评论 |
|
||||||
|
| 建议 | 资源短缺、贸易商到来 | 实用建议 |
|
||||||
|
| 庆祝 | 任务完成、殖民者加入 | 积极反馈 |
|
||||||
|
| 沉默 | 常规事件、无关紧要的通知 | 不发送任何回复 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
|
||||||
|
### 1. 文件结构
|
||||||
|
```
|
||||||
|
Source/WulaFallenEmpire/
|
||||||
|
├── Settings/
|
||||||
|
│ └── WulaModSettings.cs # 添加新设置字段
|
||||||
|
├── EventSystem/
|
||||||
|
│ └── AI/
|
||||||
|
│ ├── LetterInterceptor/
|
||||||
|
│ │ ├── Patch_LetterStack.cs # Harmony Patch 拦截 Letter
|
||||||
|
│ │ ├── LetterAnalyzer.cs # Letter 分析和分类
|
||||||
|
│ │ └── LetterToPromptConverter.cs # Letter 转提示词
|
||||||
|
│ └── AIAutoCommentary.cs # AI 自动评论逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 关键类设计
|
||||||
|
|
||||||
|
#### 2.1 WulaModSettings.cs (修改)
|
||||||
|
```csharp
|
||||||
|
public class WulaModSettings : ModSettings
|
||||||
|
{
|
||||||
|
// 现有设置...
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
public bool enableAIAutoCommentary = false; // AI 自动评论开关
|
||||||
|
public float aiCommentaryChance = 0.7f; // AI 评论概率 (0-1)
|
||||||
|
public bool commentOnNegativeOnly = false; // 仅评论负面事件
|
||||||
|
|
||||||
|
public override void ExposeData()
|
||||||
|
{
|
||||||
|
base.ExposeData();
|
||||||
|
Scribe_Values.Look(ref enableAIAutoCommentary, "enableAIAutoCommentary", false);
|
||||||
|
Scribe_Values.Look(ref aiCommentaryChance, "aiCommentaryChance", 0.7f);
|
||||||
|
Scribe_Values.Look(ref commentOnNegativeOnly, "commentOnNegativeOnly", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 Patch_LetterStack.cs (新建)
|
||||||
|
```csharp
|
||||||
|
using HarmonyLib;
|
||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI.LetterInterceptor
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter),
|
||||||
|
new Type[] { typeof(Letter), typeof(string) })]
|
||||||
|
public static class Patch_LetterStack_ReceiveLetter
|
||||||
|
{
|
||||||
|
public static void Postfix(Letter let, string debugInfo)
|
||||||
|
{
|
||||||
|
// 检查设置开关
|
||||||
|
if (!WulaModSettings.Instance.enableAIAutoCommentary) return;
|
||||||
|
|
||||||
|
// 异步处理,避免阻塞游戏
|
||||||
|
AIAutoCommentary.ProcessLetter(let);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.3 LetterAnalyzer.cs (新建)
|
||||||
|
```csharp
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI.LetterInterceptor
|
||||||
|
{
|
||||||
|
public enum LetterCategory
|
||||||
|
{
|
||||||
|
Raid, // 袭击
|
||||||
|
Disease, // 疾病
|
||||||
|
MentalBreak, // 精神崩溃
|
||||||
|
Trade, // 贸易
|
||||||
|
Quest, // 任务
|
||||||
|
Death, // 死亡
|
||||||
|
Recruitment, // 招募
|
||||||
|
Resource, // 资源
|
||||||
|
Weather, // 天气
|
||||||
|
Positive, // 正面事件
|
||||||
|
Negative, // 负面事件
|
||||||
|
Neutral, // 中性事件
|
||||||
|
Unknown // 未知
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LetterAnalyzer
|
||||||
|
{
|
||||||
|
public static LetterCategory Categorize(Letter letter)
|
||||||
|
{
|
||||||
|
// 根据 LetterDef 分类
|
||||||
|
var def = letter.def;
|
||||||
|
|
||||||
|
if (def == LetterDefOf.ThreatBig || def == LetterDefOf.ThreatSmall)
|
||||||
|
return LetterCategory.Raid;
|
||||||
|
if (def == LetterDefOf.Death)
|
||||||
|
return LetterCategory.Death;
|
||||||
|
if (def == LetterDefOf.PositiveEvent)
|
||||||
|
return LetterCategory.Positive;
|
||||||
|
if (def == LetterDefOf.NegativeEvent)
|
||||||
|
return LetterCategory.Negative;
|
||||||
|
if (def == LetterDefOf.NeutralEvent)
|
||||||
|
return LetterCategory.Neutral;
|
||||||
|
|
||||||
|
// 根据内容关键词进一步分类
|
||||||
|
string text = letter.text?.ToLower() ?? "";
|
||||||
|
if (text.Contains("raid") || text.Contains("袭击") || text.Contains("attack"))
|
||||||
|
return LetterCategory.Raid;
|
||||||
|
if (text.Contains("disease") || text.Contains("疫病") || text.Contains("plague"))
|
||||||
|
return LetterCategory.Disease;
|
||||||
|
if (text.Contains("mental") || text.Contains("精神") || text.Contains("break"))
|
||||||
|
return LetterCategory.MentalBreak;
|
||||||
|
if (text.Contains("trade") || text.Contains("贸易") || text.Contains("商队"))
|
||||||
|
return LetterCategory.Trade;
|
||||||
|
|
||||||
|
return LetterCategory.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ShouldComment(Letter letter)
|
||||||
|
{
|
||||||
|
var category = Categorize(letter);
|
||||||
|
|
||||||
|
// 始终评论的类型
|
||||||
|
switch (category)
|
||||||
|
{
|
||||||
|
case LetterCategory.Raid:
|
||||||
|
case LetterCategory.Death:
|
||||||
|
case LetterCategory.MentalBreak:
|
||||||
|
case LetterCategory.Disease:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case LetterCategory.Trade:
|
||||||
|
case LetterCategory.Quest:
|
||||||
|
case LetterCategory.Positive:
|
||||||
|
return Rand.Chance(WulaModSettings.Instance.aiCommentaryChance);
|
||||||
|
|
||||||
|
case LetterCategory.Neutral:
|
||||||
|
case LetterCategory.Unknown:
|
||||||
|
return Rand.Chance(0.3f); // 低概率评论
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.4 LetterToPromptConverter.cs (新建)
|
||||||
|
```csharp
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI.LetterInterceptor
|
||||||
|
{
|
||||||
|
public static class LetterToPromptConverter
|
||||||
|
{
|
||||||
|
public static string Convert(Letter letter, LetterCategory category)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine("[SYSTEM EVENT NOTIFICATION]");
|
||||||
|
sb.AppendLine($"Event Type: {category}");
|
||||||
|
sb.AppendLine($"Severity: {GetSeverityFromDef(letter.def)}");
|
||||||
|
sb.AppendLine($"Title: {letter.label}");
|
||||||
|
sb.AppendLine($"Content: {letter.text}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("[INSTRUCTION]");
|
||||||
|
sb.AppendLine("You have received a game event notification. Based on the event type and content:");
|
||||||
|
sb.AppendLine("- For RAIDS/THREATS: Offer tactical advice or ask if player needs orbital support");
|
||||||
|
sb.AppendLine("- For DEATHS: Express condolences or make a sardonic comment if death was avoidable");
|
||||||
|
sb.AppendLine("- For MENTAL BREAKS: Comment on the colonist's weakness or offer mood management tips");
|
||||||
|
sb.AppendLine("- For TRADE: Suggest useful purchases or sales");
|
||||||
|
sb.AppendLine("- For POSITIVE events: Celebrate briefly");
|
||||||
|
sb.AppendLine("- For trivial events: You may choose to say nothing (respond with [NO_COMMENT])");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("Keep your response brief (1-2 sentences). Match your personality as the Legion AI.");
|
||||||
|
sb.AppendLine("If you don't think this event warrants a response, reply with exactly: [NO_COMMENT]");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSeverityFromDef(LetterDef def)
|
||||||
|
{
|
||||||
|
if (def == LetterDefOf.ThreatBig) return "CRITICAL";
|
||||||
|
if (def == LetterDefOf.ThreatSmall) return "WARNING";
|
||||||
|
if (def == LetterDefOf.Death) return "SERIOUS";
|
||||||
|
if (def == LetterDefOf.NegativeEvent) return "MODERATE";
|
||||||
|
if (def == LetterDefOf.PositiveEvent) return "GOOD";
|
||||||
|
return "INFO";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.5 AIAutoCommentary.cs (新建)
|
||||||
|
```csharp
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI
|
||||||
|
{
|
||||||
|
public static class AIAutoCommentary
|
||||||
|
{
|
||||||
|
private static Queue<Letter> pendingLetters = new Queue<Letter>();
|
||||||
|
private static bool isProcessing = false;
|
||||||
|
|
||||||
|
public static void ProcessLetter(Letter letter)
|
||||||
|
{
|
||||||
|
// 检查是否应该评论
|
||||||
|
if (!LetterAnalyzer.ShouldComment(letter))
|
||||||
|
{
|
||||||
|
WulaLog.Debug($"[AI Commentary] Skipping letter: {letter.label}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入队列
|
||||||
|
pendingLetters.Enqueue(letter);
|
||||||
|
|
||||||
|
// 开始处理(如果还没在处理中)
|
||||||
|
if (!isProcessing)
|
||||||
|
{
|
||||||
|
ProcessNextLetter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async void ProcessNextLetter()
|
||||||
|
{
|
||||||
|
if (pendingLetters.Count == 0)
|
||||||
|
{
|
||||||
|
isProcessing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isProcessing = true;
|
||||||
|
var letter = pendingLetters.Dequeue();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var category = LetterAnalyzer.Categorize(letter);
|
||||||
|
var prompt = LetterToPromptConverter.Convert(letter, category);
|
||||||
|
|
||||||
|
// 获取 AI 核心
|
||||||
|
var aiCore = Find.World?.GetComponent<AIIntelligenceCore>();
|
||||||
|
if (aiCore == null)
|
||||||
|
{
|
||||||
|
WulaLog.Debug("[AI Commentary] AIIntelligenceCore not found.");
|
||||||
|
ProcessNextLetter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送到 AI 并等待响应
|
||||||
|
string response = await aiCore.SendSystemMessageAsync(prompt);
|
||||||
|
|
||||||
|
// 检查是否选择不评论
|
||||||
|
if (string.IsNullOrEmpty(response) || response.Contains("[NO_COMMENT]"))
|
||||||
|
{
|
||||||
|
WulaLog.Debug($"[AI Commentary] AI chose not to comment on: {letter.label}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 显示 AI 的评论
|
||||||
|
DisplayAICommentary(response, letter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WulaLog.Debug($"[AI Commentary] Error processing letter: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟处理下一个,避免刷屏
|
||||||
|
await Task.Delay(2000);
|
||||||
|
ProcessNextLetter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DisplayAICommentary(string response, Letter originalLetter)
|
||||||
|
{
|
||||||
|
// 方式1: 作为小型通知显示在 WulaLink 小 UI
|
||||||
|
var overlay = Find.WindowStack.Windows.OfType<Overlay_WulaLink>().FirstOrDefault();
|
||||||
|
if (overlay != null)
|
||||||
|
{
|
||||||
|
overlay.AddAIMessage(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式2: 作为 Message 显示在屏幕左上角
|
||||||
|
Messages.Message($"[P.I.A]: {response}", MessageTypeDefOf.SilentInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现步骤
|
||||||
|
|
||||||
|
### 阶段 1: 基础设施 (预计 1 小时)
|
||||||
|
1. [ ] 在 `WulaModSettings.cs` 添加新设置字段
|
||||||
|
2. [ ] 在设置 UI 中添加开关
|
||||||
|
3. [ ] 添加对应的 Keyed 翻译
|
||||||
|
|
||||||
|
### 阶段 2: Letter 拦截 (预计 30 分钟)
|
||||||
|
1. [ ] 创建 `Patch_LetterStack.cs` Harmony Patch
|
||||||
|
2. [ ] 确保 Patch 正确注册到 Harmony 实例
|
||||||
|
3. [ ] 测试 Letter 拦截是否正常工作
|
||||||
|
|
||||||
|
### 阶段 3: Letter 分析 (预计 1 小时)
|
||||||
|
1. [ ] 创建 `LetterAnalyzer.cs` 分类逻辑
|
||||||
|
2. [ ] 创建 `LetterToPromptConverter.cs` 转换逻辑
|
||||||
|
3. [ ] 测试不同类型 Letter 的分类准确性
|
||||||
|
|
||||||
|
### 阶段 4: AI 集成 (预计 1.5 小时)
|
||||||
|
1. [ ] 创建 `AIAutoCommentary.cs` 管理类
|
||||||
|
2. [ ] 集成到现有的 `AIIntelligenceCore` 系统
|
||||||
|
3. [ ] 实现队列处理避免刷屏
|
||||||
|
4. [ ] 添加 `SendSystemMessageAsync` 方法到 AIIntelligenceCore
|
||||||
|
|
||||||
|
### 阶段 5: UI 显示 (预计 30 分钟)
|
||||||
|
1. [ ] 决定评论显示方式(WulaLink UI / Message / 独立通知)
|
||||||
|
2. [ ] 实现显示逻辑
|
||||||
|
3. [ ] 测试显示效果
|
||||||
|
|
||||||
|
### 阶段 6: 测试与优化 (预计 1 小时)
|
||||||
|
1. [ ] 测试各类 Letter 的评论效果
|
||||||
|
2. [ ] 调整评论概率和过滤规则
|
||||||
|
3. [ ] 优化提示词以获得更好的 AI 回应
|
||||||
|
4. [ ] 添加速率限制避免 API 过载
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 需要添加的翻译键
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- AI Auto Commentary Settings -->
|
||||||
|
<Wula_AISettings_AutoCommentary>启用 AI 自动评论</Wula_AISettings_AutoCommentary>
|
||||||
|
<Wula_AISettings_AutoCommentaryDesc>开启后,P.I.A 会自动对游戏事件(袭击、死亡、贸易等)发表评论或提供建议。</Wula_AISettings_AutoCommentaryDesc>
|
||||||
|
<Wula_AISettings_CommentaryChance>评论概率</Wula_AISettings_CommentaryChance>
|
||||||
|
<Wula_AISettings_CommentaryChanceDesc>AI 对中性事件发表评论的概率。负面事件(如袭击)总是会评论。</Wula_AISettings_CommentaryChanceDesc>
|
||||||
|
<Wula_AISettings_NegativeOnly>仅评论负面事件</Wula_AISettings_NegativeOnly>
|
||||||
|
<Wula_AISettings_NegativeOnlyDesc>开启后,AI 只会对负面事件(袭击、死亡、疾病等)发表评论。</Wula_AISettings_NegativeOnlyDesc>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **API 限流**: 需要实现请求队列和速率限制,避免短时间内发送过多请求
|
||||||
|
2. **异步处理**: 所有 AI 请求必须异步处理,避免阻塞游戏主线程
|
||||||
|
3. **用户控制**: 提供足够的设置选项让用户控制评论频率和类型
|
||||||
|
4. **优雅降级**: 如果 AI 服务不可用,静默失败而不影响游戏
|
||||||
|
5. **内存管理**: 队列大小限制,避免积累过多未处理的 Letter
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 预期效果示例
|
||||||
|
|
||||||
|
**场景 1: 袭击通知**
|
||||||
|
```
|
||||||
|
[Letter] 海盗袭击!一群海盗正在向你的殖民地进发。
|
||||||
|
[P.I.A] 检测到敌对势力入侵。需要我启动轨道监视协议吗?
|
||||||
|
```
|
||||||
|
|
||||||
|
**场景 2: 殖民者死亡**
|
||||||
|
```
|
||||||
|
[Letter] 张三死了。他被一只疯狂的松鼠咬死了。
|
||||||
|
[P.I.A] ...被松鼠咬死?这位殖民者的战斗技能令人印象深刻。
|
||||||
|
```
|
||||||
|
|
||||||
|
**场景 3: 贸易商到来**
|
||||||
|
```
|
||||||
|
[Letter] 商队到来。一个来自外部势力的商队想要与你交易。
|
||||||
|
[P.I.A] 贸易商队抵达。我注意到你的钢铁储备较低,建议优先采购。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 依赖项
|
||||||
|
|
||||||
|
- Harmony 2.0+ (用于 Patch)
|
||||||
|
- 现有的 AIIntelligenceCore 系统
|
||||||
|
- 现有的 WulaModSettings 系统
|
||||||
|
- 现有的 Overlay_WulaLink UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*文档版本: 1.0*
|
||||||
|
*创建时间: 2025-12-28*
|
||||||
Binary file not shown.
Binary file not shown.
@@ -162,6 +162,12 @@
|
|||||||
<Wula_AISettings_UseStreamingDesc>启用实时打字机效果。如果遇到问题请禁用。</Wula_AISettings_UseStreamingDesc>
|
<Wula_AISettings_UseStreamingDesc>启用实时打字机效果。如果遇到问题请禁用。</Wula_AISettings_UseStreamingDesc>
|
||||||
<Wula_EnableDebugLogs>启用调试日志</Wula_EnableDebugLogs>
|
<Wula_EnableDebugLogs>启用调试日志</Wula_EnableDebugLogs>
|
||||||
<Wula_EnableDebugLogsDesc>启用详细的调试日志记录(独立于开发者模式)</Wula_EnableDebugLogsDesc>
|
<Wula_EnableDebugLogsDesc>启用详细的调试日志记录(独立于开发者模式)</Wula_EnableDebugLogsDesc>
|
||||||
|
<Wula_AISettings_AutoCommentary>启用 AI 自动评论</Wula_AISettings_AutoCommentary>
|
||||||
|
<Wula_AISettings_AutoCommentaryDesc>开启后,P.I.A 会对游戏事件信件(袭击、死亡、贸易等)发表简短评论或建议。</Wula_AISettings_AutoCommentaryDesc>
|
||||||
|
<Wula_AISettings_CommentaryChance>评论概率</Wula_AISettings_CommentaryChance>
|
||||||
|
<Wula_AISettings_CommentaryChanceDesc>AI 对中性或正面信件发表评论的概率。</Wula_AISettings_CommentaryChanceDesc>
|
||||||
|
<Wula_AISettings_NegativeOnly>仅评论负面事件</Wula_AISettings_NegativeOnly>
|
||||||
|
<Wula_AISettings_NegativeOnlyDesc>开启后,AI 只会对负面信件(袭击、死亡、疾病等)发表评论。</Wula_AISettings_NegativeOnlyDesc>
|
||||||
|
|
||||||
<!-- AI Conversation -->
|
<!-- AI Conversation -->
|
||||||
<Wula_AI_Retry>重试</Wula_AI_Retry>
|
<Wula_AI_Retry>重试</Wula_AI_Retry>
|
||||||
|
|||||||
@@ -162,6 +162,12 @@
|
|||||||
<Wula_AISettings_UseStreamingDesc>Enable real-time typewriter effect. Disable if encountering issues.</Wula_AISettings_UseStreamingDesc>
|
<Wula_AISettings_UseStreamingDesc>Enable real-time typewriter effect. Disable if encountering issues.</Wula_AISettings_UseStreamingDesc>
|
||||||
<Wula_EnableDebugLogs>Enable Debug Logs</Wula_EnableDebugLogs>
|
<Wula_EnableDebugLogs>Enable Debug Logs</Wula_EnableDebugLogs>
|
||||||
<Wula_EnableDebugLogsDesc>Enable detailed debug logging (independent of DevMode)</Wula_EnableDebugLogsDesc>
|
<Wula_EnableDebugLogsDesc>Enable detailed debug logging (independent of DevMode)</Wula_EnableDebugLogsDesc>
|
||||||
|
<Wula_AISettings_AutoCommentary>Enable AI Auto Commentary</Wula_AISettings_AutoCommentary>
|
||||||
|
<Wula_AISettings_AutoCommentaryDesc>When enabled, P.I.A will comment on in-game letters (raids, deaths, trade, etc.).</Wula_AISettings_AutoCommentaryDesc>
|
||||||
|
<Wula_AISettings_CommentaryChance>Commentary Chance</Wula_AISettings_CommentaryChance>
|
||||||
|
<Wula_AISettings_CommentaryChanceDesc>Chance for neutral or positive letters to receive commentary.</Wula_AISettings_CommentaryChanceDesc>
|
||||||
|
<Wula_AISettings_NegativeOnly>Comment on Negative Only</Wula_AISettings_NegativeOnly>
|
||||||
|
<Wula_AISettings_NegativeOnlyDesc>When enabled, AI only comments on negative letters (raids, deaths, disease, etc.).</Wula_AISettings_NegativeOnlyDesc>
|
||||||
|
|
||||||
<!-- AI Conversation -->
|
<!-- AI Conversation -->
|
||||||
<Wula_AI_Retry>Retry</Wula_AI_Retry>
|
<Wula_AI_Retry>Retry</Wula_AI_Retry>
|
||||||
|
|||||||
70
Source/WulaFallenEmpire/EventSystem/AI/AIAutoCommentary.cs
Normal file
70
Source/WulaFallenEmpire/EventSystem/AI/AIAutoCommentary.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
using WulaFallenEmpire.EventSystem.AI.UI;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 简化版 AI 自动评论系统
|
||||||
|
/// 直接将 Letter 信息发送给 AI 对话流程,让 LLM 自己决定是否回复
|
||||||
|
/// </summary>
|
||||||
|
public static class AIAutoCommentary
|
||||||
|
{
|
||||||
|
private static int lastProcessedTick = 0;
|
||||||
|
private const int MinTicksBetweenComments = 300; // 5 秒冷却
|
||||||
|
|
||||||
|
public static void ProcessLetter(Letter letter)
|
||||||
|
{
|
||||||
|
if (letter == null) return;
|
||||||
|
|
||||||
|
// 检查设置
|
||||||
|
var settings = WulaFallenEmpireMod.settings;
|
||||||
|
if (settings == null || !settings.enableAIAutoCommentary) return;
|
||||||
|
|
||||||
|
// 简单的冷却检查,避免刷屏
|
||||||
|
int currentTick = Find.TickManager?.TicksGame ?? 0;
|
||||||
|
if (currentTick - lastProcessedTick < MinTicksBetweenComments) return;
|
||||||
|
lastProcessedTick = currentTick;
|
||||||
|
|
||||||
|
// 获取 AI 核心
|
||||||
|
var aiCore = Find.World?.GetComponent<AIIntelligenceCore>();
|
||||||
|
if (aiCore == null)
|
||||||
|
{
|
||||||
|
WulaLog.Debug("[AI Commentary] AIIntelligenceCore not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建提示词 - 让 AI 自己决定是否需要回复
|
||||||
|
string prompt = BuildPrompt(letter);
|
||||||
|
|
||||||
|
// 直接发送到正常的 AI 对话流程(会经过完整的思考流程)
|
||||||
|
aiCore.SendAutoCommentaryMessage(prompt);
|
||||||
|
|
||||||
|
WulaLog.Debug($"[AI Commentary] Sent letter to AI: {letter.Label.Resolve()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildPrompt(Letter letter)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 获取 Letter 信息
|
||||||
|
string label = letter.Label.Resolve() ?? "Unknown";
|
||||||
|
string defName = letter.def?.defName ?? "Unknown";
|
||||||
|
|
||||||
|
sb.AppendLine("[游戏事件通知]");
|
||||||
|
sb.AppendLine($"事件标题: {label}");
|
||||||
|
sb.AppendLine($"事件类型: {defName}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("请根据这个事件决定是否需要向玩家发表简短评论。");
|
||||||
|
sb.AppendLine("- 如果是重要事件(如袭击、死亡),可以提供建议或警告");
|
||||||
|
sb.AppendLine("- 如果是有趣的事件,可以发表幽默评论");
|
||||||
|
sb.AppendLine("- 如果事件不重要或不值得评论,什么都不说即可");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("评论要简短(1-2句话),符合你作为帝国AI的人设。");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -255,6 +255,66 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
|
|||||||
_ = RunPhasedRequestAsync();
|
_ = RunPhasedRequestAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> SendSystemMessageAsync(string message, int maxTokens = 256, float temperature = 0.3f)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = WulaFallenEmpireMod.settings;
|
||||||
|
if (settings == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string apiKey = settings.useGeminiProtocol ? settings.geminiApiKey : settings.apiKey;
|
||||||
|
if (string.IsNullOrWhiteSpace(apiKey))
|
||||||
|
{
|
||||||
|
WulaLog.Debug("[WulaAI] Auto commentary skipped: API key not configured.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string baseUrl = settings.useGeminiProtocol ? settings.geminiBaseUrl : settings.baseUrl;
|
||||||
|
string model = settings.useGeminiProtocol ? settings.geminiModel : settings.model;
|
||||||
|
var client = new SimpleAIClient(apiKey, baseUrl, model, settings.useGeminiProtocol);
|
||||||
|
|
||||||
|
string instruction = GetSystemInstruction(false, "");
|
||||||
|
int clampedTokens = Math.Max(32, maxTokens);
|
||||||
|
|
||||||
|
string response = await client.GetChatCompletionAsync(
|
||||||
|
instruction,
|
||||||
|
new List<(string role, string message)> { ("user", message) },
|
||||||
|
maxTokens: clampedTokens,
|
||||||
|
temperature: temperature);
|
||||||
|
|
||||||
|
return response?.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InjectAssistantMessage(string message)
|
||||||
|
{
|
||||||
|
AddAssistantMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于自动评论系统 - 走正常的对话流程(包含完整的思考步骤)
|
||||||
|
/// 让 AI 自己决定是否需要回复
|
||||||
|
/// </summary>
|
||||||
|
public void SendAutoCommentaryMessage(string eventInfo)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(eventInfo)) return;
|
||||||
|
|
||||||
|
// 标记为自动评论消息,不显示在对话历史中
|
||||||
|
string internalMessage = $"[AUTO_COMMENTARY]\n{eventInfo}";
|
||||||
|
|
||||||
|
// 添加到历史并触发正常的 AI 思考流程
|
||||||
|
_history.Add(("user", internalMessage));
|
||||||
|
PersistHistory();
|
||||||
|
|
||||||
|
// 使用正常的分阶段请求流程(包含工具调用能力等)
|
||||||
|
_ = RunPhasedRequestAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private string BuildUserMessageWithContext(string userText)
|
private string BuildUserMessageWithContext(string userText)
|
||||||
{
|
{
|
||||||
var sb = new System.Text.StringBuilder();
|
var sb = new System.Text.StringBuilder();
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using HarmonyLib;
|
||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
using WulaFallenEmpire.EventSystem.AI;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI.LetterInterceptor
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), new Type[] { typeof(Letter), typeof(string) })]
|
||||||
|
public static class Patch_LetterStack_ReceiveLetter
|
||||||
|
{
|
||||||
|
public static void Postfix(Letter let, string debugInfo)
|
||||||
|
{
|
||||||
|
var settings = WulaFallenEmpireMod.settings;
|
||||||
|
if (settings == null || !settings.enableAIAutoCommentary)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (let == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AIAutoCommentary.ProcessLetter(let);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,13 +36,15 @@ namespace WulaFallenEmpire.EventSystem.AI.UI
|
|||||||
_size = new Vector2(Mathf.Max(250f, textWidth + 85f), 85f); // Taller base
|
_size = new Vector2(Mathf.Max(250f, textWidth + 85f), 85f); // Taller base
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window properties
|
// Window properties - ensure no input blocking
|
||||||
this.layer = WindowLayer.Super;
|
this.layer = WindowLayer.Super;
|
||||||
this.closeOnClickedOutside = false;
|
this.closeOnClickedOutside = false;
|
||||||
this.forcePause = false;
|
this.forcePause = false;
|
||||||
this.absorbInputAroundWindow = false;
|
this.absorbInputAroundWindow = false;
|
||||||
this.doWindowBackground = false;
|
this.doWindowBackground = false;
|
||||||
this.drawShadow = false;
|
this.drawShadow = false;
|
||||||
|
this.focusWhenOpened = false; // Don't steal focus
|
||||||
|
this.preventCameraMotion = false; // Allow WASD camera control
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SetInitialSizeAndPosition()
|
protected override void SetInitialSizeAndPosition()
|
||||||
|
|||||||
@@ -88,6 +88,17 @@ namespace WulaFallenEmpire
|
|||||||
listingStandard.GapLine();
|
listingStandard.GapLine();
|
||||||
listingStandard.CheckboxLabeled("Wula_EnableDebugLogs".Translate(), ref settings.enableDebugLogs, "Wula_EnableDebugLogsDesc".Translate());
|
listingStandard.CheckboxLabeled("Wula_EnableDebugLogs".Translate(), ref settings.enableDebugLogs, "Wula_EnableDebugLogsDesc".Translate());
|
||||||
|
|
||||||
|
listingStandard.GapLine();
|
||||||
|
listingStandard.CheckboxLabeled("Wula_AISettings_AutoCommentary".Translate(), ref settings.enableAIAutoCommentary, "Wula_AISettings_AutoCommentaryDesc".Translate());
|
||||||
|
if (settings.enableAIAutoCommentary)
|
||||||
|
{
|
||||||
|
listingStandard.Label("Wula_AISettings_CommentaryChance".Translate() + $" ({settings.aiCommentaryChance:P0})");
|
||||||
|
listingStandard.Label("Wula_AISettings_CommentaryChanceDesc".Translate());
|
||||||
|
settings.aiCommentaryChance = listingStandard.Slider(settings.aiCommentaryChance, 0f, 1f);
|
||||||
|
settings.aiCommentaryChance = Mathf.Clamp01(settings.aiCommentaryChance);
|
||||||
|
listingStandard.CheckboxLabeled("Wula_AISettings_NegativeOnly".Translate(), ref settings.commentOnNegativeOnly, "Wula_AISettings_NegativeOnlyDesc".Translate());
|
||||||
|
}
|
||||||
|
|
||||||
// 视觉设置部分
|
// 视觉设置部分
|
||||||
listingStandard.GapLine();
|
listingStandard.GapLine();
|
||||||
listingStandard.Label("<color=cyan>视觉与多模态设置</color>");
|
listingStandard.Label("<color=cyan>视觉与多模态设置</color>");
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
// 视觉功能配置
|
// 视觉功能配置
|
||||||
public bool enableVlmFeatures = false;
|
public bool enableVlmFeatures = false;
|
||||||
|
public bool enableAIAutoCommentary = false;
|
||||||
|
public float aiCommentaryChance = 0.7f;
|
||||||
|
public bool commentOnNegativeOnly = false;
|
||||||
|
|
||||||
public override void ExposeData()
|
public override void ExposeData()
|
||||||
{
|
{
|
||||||
@@ -36,6 +39,9 @@ namespace WulaFallenEmpire
|
|||||||
|
|
||||||
// 简化后的视觉配置
|
// 简化后的视觉配置
|
||||||
Scribe_Values.Look(ref enableVlmFeatures, "enableVlmFeatures", false);
|
Scribe_Values.Look(ref enableVlmFeatures, "enableVlmFeatures", false);
|
||||||
|
Scribe_Values.Look(ref enableAIAutoCommentary, "enableAIAutoCommentary", false);
|
||||||
|
Scribe_Values.Look(ref aiCommentaryChance, "aiCommentaryChance", 0.7f);
|
||||||
|
Scribe_Values.Look(ref commentOnNegativeOnly, "commentOnNegativeOnly", false);
|
||||||
|
|
||||||
base.ExposeData();
|
base.ExposeData();
|
||||||
}
|
}
|
||||||
|
|||||||
405
Tools/ai_letter_auto_commentary.md
Normal file
405
Tools/ai_letter_auto_commentary.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# AI Letter Auto-Response System - 开发文档
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
这个功能将使 P.I.A AI 能够自动监听游戏内的 Letter(信封通知),并根据内容智能决定是否向玩家发送评论、吐槽、警告或提供帮助建议。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 功能需求
|
||||||
|
|
||||||
|
### 核心功能
|
||||||
|
1. **Mod 设置开关**: 在设置中添加 `启用 AI 自动评论` 开关
|
||||||
|
2. **Letter 监听**: 拦截所有发送给玩家的 Letter
|
||||||
|
3. **智能判断**: AI 分析 Letter 内容,决定是否需要回应
|
||||||
|
4. **自动回复**: 通过现有的 AI 对话系统发送回复
|
||||||
|
|
||||||
|
### AI 回应类型
|
||||||
|
| 类型 | 触发场景示例 | 回应风格 |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| 警告 | 袭击通知、疫病爆发 | 紧急提醒,询问是否需要启动防御 |
|
||||||
|
| 吐槽 | 殖民者精神崩溃、愚蠢死亡 | 幽默/讽刺评论 |
|
||||||
|
| 建议 | 资源短缺、贸易商到来 | 实用建议 |
|
||||||
|
| 庆祝 | 任务完成、殖民者加入 | 积极反馈 |
|
||||||
|
| 沉默 | 常规事件、无关紧要的通知 | 不发送任何回复 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术架构
|
||||||
|
|
||||||
|
### 1. 文件结构
|
||||||
|
```
|
||||||
|
Source/WulaFallenEmpire/
|
||||||
|
├── Settings/
|
||||||
|
│ └── WulaModSettings.cs # 添加新设置字段
|
||||||
|
├── EventSystem/
|
||||||
|
│ └── AI/
|
||||||
|
│ ├── LetterInterceptor/
|
||||||
|
│ │ ├── Patch_LetterStack.cs # Harmony Patch 拦截 Letter
|
||||||
|
│ │ ├── LetterAnalyzer.cs # Letter 分析和分类
|
||||||
|
│ │ └── LetterToPromptConverter.cs # Letter 转提示词
|
||||||
|
│ └── AIAutoCommentary.cs # AI 自动评论逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 关键类设计
|
||||||
|
|
||||||
|
#### 2.1 WulaModSettings.cs (修改)
|
||||||
|
```csharp
|
||||||
|
public class WulaModSettings : ModSettings
|
||||||
|
{
|
||||||
|
// 现有设置...
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
public bool enableAIAutoCommentary = false; // AI 自动评论开关
|
||||||
|
public float aiCommentaryChance = 0.7f; // AI 评论概率 (0-1)
|
||||||
|
public bool commentOnNegativeOnly = false; // 仅评论负面事件
|
||||||
|
|
||||||
|
public override void ExposeData()
|
||||||
|
{
|
||||||
|
base.ExposeData();
|
||||||
|
Scribe_Values.Look(ref enableAIAutoCommentary, "enableAIAutoCommentary", false);
|
||||||
|
Scribe_Values.Look(ref aiCommentaryChance, "aiCommentaryChance", 0.7f);
|
||||||
|
Scribe_Values.Look(ref commentOnNegativeOnly, "commentOnNegativeOnly", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 Patch_LetterStack.cs (新建)
|
||||||
|
```csharp
|
||||||
|
using HarmonyLib;
|
||||||
|
using RimWorld;
|
||||||
|
using Verse;
|
||||||
|
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI.LetterInterceptor
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter),
|
||||||
|
new Type[] { typeof(Letter), typeof(string) })]
|
||||||
|
public static class Patch_LetterStack_ReceiveLetter
|
||||||
|
{
|
||||||
|
public static void Postfix(Letter let, string debugInfo)
|
||||||
|
{
|
||||||
|
// 检查设置开关
|
||||||
|
if (!WulaModSettings.Instance.enableAIAutoCommentary) return;
|
||||||
|
|
||||||
|
// 异步处理,避免阻塞游戏
|
||||||
|
AIAutoCommentary.ProcessLetter(let);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.3 LetterAnalyzer.cs (新建)
|
||||||
|
```csharp
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI.LetterInterceptor
|
||||||
|
{
|
||||||
|
public enum LetterCategory
|
||||||
|
{
|
||||||
|
Raid, // 袭击
|
||||||
|
Disease, // 疾病
|
||||||
|
MentalBreak, // 精神崩溃
|
||||||
|
Trade, // 贸易
|
||||||
|
Quest, // 任务
|
||||||
|
Death, // 死亡
|
||||||
|
Recruitment, // 招募
|
||||||
|
Resource, // 资源
|
||||||
|
Weather, // 天气
|
||||||
|
Positive, // 正面事件
|
||||||
|
Negative, // 负面事件
|
||||||
|
Neutral, // 中性事件
|
||||||
|
Unknown // 未知
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LetterAnalyzer
|
||||||
|
{
|
||||||
|
public static LetterCategory Categorize(Letter letter)
|
||||||
|
{
|
||||||
|
// 根据 LetterDef 分类
|
||||||
|
var def = letter.def;
|
||||||
|
|
||||||
|
if (def == LetterDefOf.ThreatBig || def == LetterDefOf.ThreatSmall)
|
||||||
|
return LetterCategory.Raid;
|
||||||
|
if (def == LetterDefOf.Death)
|
||||||
|
return LetterCategory.Death;
|
||||||
|
if (def == LetterDefOf.PositiveEvent)
|
||||||
|
return LetterCategory.Positive;
|
||||||
|
if (def == LetterDefOf.NegativeEvent)
|
||||||
|
return LetterCategory.Negative;
|
||||||
|
if (def == LetterDefOf.NeutralEvent)
|
||||||
|
return LetterCategory.Neutral;
|
||||||
|
|
||||||
|
// 根据内容关键词进一步分类
|
||||||
|
string text = letter.text?.ToLower() ?? "";
|
||||||
|
if (text.Contains("raid") || text.Contains("袭击") || text.Contains("attack"))
|
||||||
|
return LetterCategory.Raid;
|
||||||
|
if (text.Contains("disease") || text.Contains("疫病") || text.Contains("plague"))
|
||||||
|
return LetterCategory.Disease;
|
||||||
|
if (text.Contains("mental") || text.Contains("精神") || text.Contains("break"))
|
||||||
|
return LetterCategory.MentalBreak;
|
||||||
|
if (text.Contains("trade") || text.Contains("贸易") || text.Contains("商队"))
|
||||||
|
return LetterCategory.Trade;
|
||||||
|
|
||||||
|
return LetterCategory.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ShouldComment(Letter letter)
|
||||||
|
{
|
||||||
|
var category = Categorize(letter);
|
||||||
|
|
||||||
|
// 始终评论的类型
|
||||||
|
switch (category)
|
||||||
|
{
|
||||||
|
case LetterCategory.Raid:
|
||||||
|
case LetterCategory.Death:
|
||||||
|
case LetterCategory.MentalBreak:
|
||||||
|
case LetterCategory.Disease:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case LetterCategory.Trade:
|
||||||
|
case LetterCategory.Quest:
|
||||||
|
case LetterCategory.Positive:
|
||||||
|
return Rand.Chance(WulaModSettings.Instance.aiCommentaryChance);
|
||||||
|
|
||||||
|
case LetterCategory.Neutral:
|
||||||
|
case LetterCategory.Unknown:
|
||||||
|
return Rand.Chance(0.3f); // 低概率评论
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.4 LetterToPromptConverter.cs (新建)
|
||||||
|
```csharp
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI.LetterInterceptor
|
||||||
|
{
|
||||||
|
public static class LetterToPromptConverter
|
||||||
|
{
|
||||||
|
public static string Convert(Letter letter, LetterCategory category)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine("[SYSTEM EVENT NOTIFICATION]");
|
||||||
|
sb.AppendLine($"Event Type: {category}");
|
||||||
|
sb.AppendLine($"Severity: {GetSeverityFromDef(letter.def)}");
|
||||||
|
sb.AppendLine($"Title: {letter.label}");
|
||||||
|
sb.AppendLine($"Content: {letter.text}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("[INSTRUCTION]");
|
||||||
|
sb.AppendLine("You have received a game event notification. Based on the event type and content:");
|
||||||
|
sb.AppendLine("- For RAIDS/THREATS: Offer tactical advice or ask if player needs orbital support");
|
||||||
|
sb.AppendLine("- For DEATHS: Express condolences or make a sardonic comment if death was avoidable");
|
||||||
|
sb.AppendLine("- For MENTAL BREAKS: Comment on the colonist's weakness or offer mood management tips");
|
||||||
|
sb.AppendLine("- For TRADE: Suggest useful purchases or sales");
|
||||||
|
sb.AppendLine("- For POSITIVE events: Celebrate briefly");
|
||||||
|
sb.AppendLine("- For trivial events: You may choose to say nothing (respond with [NO_COMMENT])");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("Keep your response brief (1-2 sentences). Match your personality as the Legion AI.");
|
||||||
|
sb.AppendLine("If you don't think this event warrants a response, reply with exactly: [NO_COMMENT]");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSeverityFromDef(LetterDef def)
|
||||||
|
{
|
||||||
|
if (def == LetterDefOf.ThreatBig) return "CRITICAL";
|
||||||
|
if (def == LetterDefOf.ThreatSmall) return "WARNING";
|
||||||
|
if (def == LetterDefOf.Death) return "SERIOUS";
|
||||||
|
if (def == LetterDefOf.NegativeEvent) return "MODERATE";
|
||||||
|
if (def == LetterDefOf.PositiveEvent) return "GOOD";
|
||||||
|
return "INFO";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.5 AIAutoCommentary.cs (新建)
|
||||||
|
```csharp
|
||||||
|
namespace WulaFallenEmpire.EventSystem.AI
|
||||||
|
{
|
||||||
|
public static class AIAutoCommentary
|
||||||
|
{
|
||||||
|
private static Queue<Letter> pendingLetters = new Queue<Letter>();
|
||||||
|
private static bool isProcessing = false;
|
||||||
|
|
||||||
|
public static void ProcessLetter(Letter letter)
|
||||||
|
{
|
||||||
|
// 检查是否应该评论
|
||||||
|
if (!LetterAnalyzer.ShouldComment(letter))
|
||||||
|
{
|
||||||
|
WulaLog.Debug($"[AI Commentary] Skipping letter: {letter.label}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入队列
|
||||||
|
pendingLetters.Enqueue(letter);
|
||||||
|
|
||||||
|
// 开始处理(如果还没在处理中)
|
||||||
|
if (!isProcessing)
|
||||||
|
{
|
||||||
|
ProcessNextLetter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async void ProcessNextLetter()
|
||||||
|
{
|
||||||
|
if (pendingLetters.Count == 0)
|
||||||
|
{
|
||||||
|
isProcessing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isProcessing = true;
|
||||||
|
var letter = pendingLetters.Dequeue();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var category = LetterAnalyzer.Categorize(letter);
|
||||||
|
var prompt = LetterToPromptConverter.Convert(letter, category);
|
||||||
|
|
||||||
|
// 获取 AI 核心
|
||||||
|
var aiCore = Find.World?.GetComponent<AIIntelligenceCore>();
|
||||||
|
if (aiCore == null)
|
||||||
|
{
|
||||||
|
WulaLog.Debug("[AI Commentary] AIIntelligenceCore not found.");
|
||||||
|
ProcessNextLetter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送到 AI 并等待响应
|
||||||
|
string response = await aiCore.SendSystemMessageAsync(prompt);
|
||||||
|
|
||||||
|
// 检查是否选择不评论
|
||||||
|
if (string.IsNullOrEmpty(response) || response.Contains("[NO_COMMENT]"))
|
||||||
|
{
|
||||||
|
WulaLog.Debug($"[AI Commentary] AI chose not to comment on: {letter.label}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 显示 AI 的评论
|
||||||
|
DisplayAICommentary(response, letter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
WulaLog.Debug($"[AI Commentary] Error processing letter: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟处理下一个,避免刷屏
|
||||||
|
await Task.Delay(2000);
|
||||||
|
ProcessNextLetter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DisplayAICommentary(string response, Letter originalLetter)
|
||||||
|
{
|
||||||
|
// 方式1: 作为小型通知显示在 WulaLink 小 UI
|
||||||
|
var overlay = Find.WindowStack.Windows.OfType<Overlay_WulaLink>().FirstOrDefault();
|
||||||
|
if (overlay != null)
|
||||||
|
{
|
||||||
|
overlay.AddAIMessage(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方式2: 作为 Message 显示在屏幕左上角
|
||||||
|
Messages.Message($"[P.I.A]: {response}", MessageTypeDefOf.SilentInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 实现步骤
|
||||||
|
|
||||||
|
### 阶段 1: 基础设施 (预计 1 小时)
|
||||||
|
1. [ ] 在 `WulaModSettings.cs` 添加新设置字段
|
||||||
|
2. [ ] 在设置 UI 中添加开关
|
||||||
|
3. [ ] 添加对应的 Keyed 翻译
|
||||||
|
|
||||||
|
### 阶段 2: Letter 拦截 (预计 30 分钟)
|
||||||
|
1. [ ] 创建 `Patch_LetterStack.cs` Harmony Patch
|
||||||
|
2. [ ] 确保 Patch 正确注册到 Harmony 实例
|
||||||
|
3. [ ] 测试 Letter 拦截是否正常工作
|
||||||
|
|
||||||
|
### 阶段 3: Letter 分析 (预计 1 小时)
|
||||||
|
1. [ ] 创建 `LetterAnalyzer.cs` 分类逻辑
|
||||||
|
2. [ ] 创建 `LetterToPromptConverter.cs` 转换逻辑
|
||||||
|
3. [ ] 测试不同类型 Letter 的分类准确性
|
||||||
|
|
||||||
|
### 阶段 4: AI 集成 (预计 1.5 小时)
|
||||||
|
1. [ ] 创建 `AIAutoCommentary.cs` 管理类
|
||||||
|
2. [ ] 集成到现有的 `AIIntelligenceCore` 系统
|
||||||
|
3. [ ] 实现队列处理避免刷屏
|
||||||
|
4. [ ] 添加 `SendSystemMessageAsync` 方法到 AIIntelligenceCore
|
||||||
|
|
||||||
|
### 阶段 5: UI 显示 (预计 30 分钟)
|
||||||
|
1. [ ] 决定评论显示方式(WulaLink UI / Message / 独立通知)
|
||||||
|
2. [ ] 实现显示逻辑
|
||||||
|
3. [ ] 测试显示效果
|
||||||
|
|
||||||
|
### 阶段 6: 测试与优化 (预计 1 小时)
|
||||||
|
1. [ ] 测试各类 Letter 的评论效果
|
||||||
|
2. [ ] 调整评论概率和过滤规则
|
||||||
|
3. [ ] 优化提示词以获得更好的 AI 回应
|
||||||
|
4. [ ] 添加速率限制避免 API 过载
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 需要添加的翻译键
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- AI Auto Commentary Settings -->
|
||||||
|
<Wula_AISettings_AutoCommentary>启用 AI 自动评论</Wula_AISettings_AutoCommentary>
|
||||||
|
<Wula_AISettings_AutoCommentaryDesc>开启后,P.I.A 会自动对游戏事件(袭击、死亡、贸易等)发表评论或提供建议。</Wula_AISettings_AutoCommentaryDesc>
|
||||||
|
<Wula_AISettings_CommentaryChance>评论概率</Wula_AISettings_CommentaryChance>
|
||||||
|
<Wula_AISettings_CommentaryChanceDesc>AI 对中性事件发表评论的概率。负面事件(如袭击)总是会评论。</Wula_AISettings_CommentaryChanceDesc>
|
||||||
|
<Wula_AISettings_NegativeOnly>仅评论负面事件</Wula_AISettings_NegativeOnly>
|
||||||
|
<Wula_AISettings_NegativeOnlyDesc>开启后,AI 只会对负面事件(袭击、死亡、疾病等)发表评论。</Wula_AISettings_NegativeOnlyDesc>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **API 限流**: 需要实现请求队列和速率限制,避免短时间内发送过多请求
|
||||||
|
2. **异步处理**: 所有 AI 请求必须异步处理,避免阻塞游戏主线程
|
||||||
|
3. **用户控制**: 提供足够的设置选项让用户控制评论频率和类型
|
||||||
|
4. **优雅降级**: 如果 AI 服务不可用,静默失败而不影响游戏
|
||||||
|
5. **内存管理**: 队列大小限制,避免积累过多未处理的 Letter
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 预期效果示例
|
||||||
|
|
||||||
|
**场景 1: 袭击通知**
|
||||||
|
```
|
||||||
|
[Letter] 海盗袭击!一群海盗正在向你的殖民地进发。
|
||||||
|
[P.I.A] 检测到敌对势力入侵。需要我启动轨道监视协议吗?
|
||||||
|
```
|
||||||
|
|
||||||
|
**场景 2: 殖民者死亡**
|
||||||
|
```
|
||||||
|
[Letter] 张三死了。他被一只疯狂的松鼠咬死了。
|
||||||
|
[P.I.A] ...被松鼠咬死?这位殖民者的战斗技能令人印象深刻。
|
||||||
|
```
|
||||||
|
|
||||||
|
**场景 3: 贸易商到来**
|
||||||
|
```
|
||||||
|
[Letter] 商队到来。一个来自外部势力的商队想要与你交易。
|
||||||
|
[P.I.A] 贸易商队抵达。我注意到你的钢铁储备较低,建议优先采购。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 依赖项
|
||||||
|
|
||||||
|
- Harmony 2.0+ (用于 Patch)
|
||||||
|
- 现有的 AIIntelligenceCore 系统
|
||||||
|
- 现有的 WulaModSettings 系统
|
||||||
|
- 现有的 Overlay_WulaLink UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*文档版本: 1.0*
|
||||||
|
*创建时间: 2025-12-28*
|
||||||
Reference in New Issue
Block a user