Skip to content

Commit 3fe4e9d

Browse files
committed
修复expand_base指令不生效的问题
1 parent 4ccf01b commit 3fe4e9d

30 files changed

Lines changed: 493 additions & 111 deletions

HOW_TO_ADD_API_COMMANDS_ZH.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# 如何添加新的 API 指令
2+
3+
本文档详细记录了在 OpenRA Copilot Mod 中添加新 API 指令的流程、架构说明以及开发经验总结。
4+
5+
## 1. API 架构简介
6+
7+
OpenRA Copilot 的 API 系统基于 Socket 通信,主要涉及以下几个核心文件:
8+
9+
* **`OpenRA.Game/CopilotCommandServer.cs`**: 服务器入口,负责接收 Socket 请求、解析 JSON、验证基础格式、并路由到对应的处理函数。
10+
* **`OpenRA.Game/CopilotModels.cs`**: 定义请求和响应的数据模型,以及参数验证逻辑。
11+
* **`OpenRA.Mods.Common/ServerCommands.cs`**: 包含具体的指令处理逻辑(静态方法)。通常在这里解析参数并调用游戏内的逻辑。
12+
* **`OpenRA.Mods.Common/Traits/Copilot/`**: 对于复杂的指令(如 `expand_base`),通常会封装成一个 `Trait`(特性),挂载在 Player 或 World 上,由 `ServerCommands` 调用。
13+
14+
## 2. 添加指令的详细步骤
15+
16+
假设我们要添加一个名为 `my_command` 的新指令。
17+
18+
### 步骤 1: 定义参数验证 (CopilotModels.cs)
19+
20+
`OpenRA.Game/CopilotModels.cs` 中,找到 `ValidateCommandParams` 方法,添加新指令的参数检查逻辑。
21+
22+
```csharp
23+
// CopilotModels.cs
24+
25+
public static (bool isValid, MCPError error) ValidateCommandParams(string command, JObject parameters)
26+
{
27+
switch (command)
28+
{
29+
// ... 其他指令 ...
30+
case "my_command":
31+
return ValidateMyCommandParams(parameters);
32+
// ...
33+
}
34+
}
35+
36+
// 编写具体的验证函数
37+
private static (bool isValid, MCPError error) ValidateMyCommandParams(JObject parameters)
38+
{
39+
// 示例:必须包含 targetId
40+
if (parameters == null || !parameters.ContainsKey("targetId"))
41+
{
42+
return (false, new MCPError
43+
{
44+
Code = "MISSING_PARAM",
45+
Message = "Missing parameter: targetId"
46+
});
47+
}
48+
return (true, null);
49+
}
50+
```
51+
52+
### 步骤 2: 实现处理逻辑 (ServerCommands.cs)
53+
54+
`OpenRA.Mods.Common/ServerCommands.cs` 中添加静态处理方法。
55+
56+
```csharp
57+
// ServerCommands.cs
58+
59+
public static string MyCommand(JObject json, World world)
60+
{
61+
// 1. 解析玩家
62+
var player = ResolvePlayer(json, world);
63+
64+
// 2. 解析参数
65+
var targetId = json["targetId"]?.ToObject<int>();
66+
67+
// 3. 执行游戏逻辑
68+
// ...
69+
70+
return "Command executed successfully";
71+
}
72+
```
73+
74+
### 步骤 3: 注册指令 (CopilotCommandServer.cs)
75+
76+
`OpenRA.Game/CopilotCommandServer.cs``WorldLoaded` 方法中注册该指令。
77+
78+
```csharp
79+
// CopilotCommandServer.cs
80+
81+
public void WorldLoaded(World w, WorldRenderer wr)
82+
{
83+
if (w.Type == WorldType.Regular && w.CopilotServer != null)
84+
{
85+
// ...
86+
w.CopilotServer.CommandHandlers["my_command"] = ServerCommands.MyCommand;
87+
// ...
88+
}
89+
}
90+
```
91+
92+
### 步骤 4: (可选) 实现复杂逻辑 Trait
93+
94+
如果指令涉及跨多个 Tick 的操作(如 `expand_base` 需要造车、移动、展开、造建筑),建议创建一个独立的 `Trait`
95+
96+
1.`OpenRA.Mods.Common/Traits/Copilot/` 下创建新文件(如 `MyComplexManager.cs`)。
97+
2. 实现 `ITick` 接口以处理每帧逻辑。
98+
3.`ServerCommands.cs` 中获取该 Trait 并调用其方法。
99+
100+
## 3. 经验总结与避坑指南 (重要)
101+
102+
在开发 `expand_base` 指令的过程中,我们总结了以下关键经验,请务必阅读以避免走弯路:
103+
104+
### 3.1 必须在 YAML 中注册 Trait
105+
**现象**:代码写得完美无缺,但在运行时调用指令返回 `Player does not have X trait`
106+
**原因**:C# 代码中定义了 Trait 只是第一步,**必须**在模组的规则文件(`rules/player.yaml``rules/world.yaml`)中显式添加该 Trait,否则它不会被加载到 Actor 上。
107+
**对策**
108+
* 检查 `mods/ra/rules/player.yaml`
109+
* 检查 `mods/cnc/rules/player.yaml`
110+
* 确保你的 Trait 名称(如 `CopilotExpansionManager`)出现在 `Player` 定义下。
111+
112+
### 3.2 生产队列的“批处理”与“单次”
113+
**现象**:请求建造多个建筑(如2个矿场),但只造了1个就不动了,或者逻辑卡死。
114+
**原因**:旧的逻辑是“检查是否在造 -> 如果没在造 -> 发送一个建造指令”。但这会导致每次 Tick 都尝试发指令,或者造完一个后无法自动衔接下一个。
115+
**对策**
116+
* 使用 `EnsureProduction(type, quantity)` 模式。
117+
* 计算 `需建造总数 - (当前队列中数量 + 已完成未放置数量)`
118+
* 使用 `Order.StartProduction(actor, item, count)` 一次性发送剩余所需的数量,让引擎内部的队列系统去管理排队。
119+
120+
### 3.3 Order 的执行与 Target
121+
**现象**:发送了 `Move``Deploy` 指令,但单位没有任何反应。
122+
**原因**
123+
1. **Target 构造错误**`Target.FromCell``Target.FromActor` 必须正确使用。
124+
2. **Order 构造函数**:某些 Order(如 `DeployTransform`)需要特定的构造函数参数(如 `suppressVisualFeedback``queued`)。
125+
3. **队列阻塞**:如果不使用 `queued=false`(即立即执行),新指令可能会被追加到现有指令(如巡逻)之后而迟迟不执行。对于紧急指令,通常应设为不排队(覆盖当前指令)。
126+
127+
### 3.4 状态机管理长流程
128+
**场景**`expand_base` 需要:造MCV -> 等待MCV -> 移动MCV -> 展开 -> 造电厂 -> 放电厂 -> 造矿厂...
129+
**经验**:不要试图在一个函数里做完。使用 `enum` 定义状态机(State Machine),在 `ITick.Tick` 中根据当前状态执行微小的一步。
130+
* **Waiting 状态**:每个动作发出后,进入对应的 Waiting 状态(如 `WaitingForMCV`)。
131+
* **超时/重试**:设置 `waitTicks`,避免每帧都高频检查,同时也作为简单的超时重试机制。
132+
133+
### 3.5 错误信息反馈
134+
**经验**:API 返回的错误信息越详细越好。
135+
* 不要只返回 "Failed"。
136+
* 如果是缺前置,调用 `DescribeMissingPrerequisites` 返回具体缺什么(如 "缺少:重工厂")。
137+
* 如果是状态不对,返回当前正在做什么(如 "Base expansion already in progress")。
138+
139+
### 3.6 跨 Mod 兼容性
140+
**现象**:代码在 RA (红警) 下能跑,在 CNC (泰伯利亚之日) 下报错或无反应。
141+
**原因**:不同 Mod 的 Actor 命名不同。例如电厂在 RA 里叫 `pwr``apwr`,在 CNC 里可能叫 `nukr`
142+
**对策**:在代码中定义别名数组,如 `string[] powerNames = { "POWR", "APWR", "PWR", "NUKR" };`,并遍历尝试解析。

OpenRA.Game/CopilotCommandServer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ public interface IGameStatsRecorder
9494
{
9595
["en"] = "Missing attackers or targets parameter",
9696
["zh"] = "缺少attackers或targets参数"
97+
},
98+
["INVALID_PARAMS_EXPAND_BASE"] = new()
99+
{
100+
["en"] = "expand_base command does not accept parameters",
101+
["zh"] = "expand_base命令不需要参数"
97102
}
98103
};
99104

OpenRA.Game/CopilotModels.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ public static (bool isValid, MCPError error) ValidateCommandParams(string comman
111111
return ValidateMoveActorParams(parameters);
112112
case "attack":
113113
return ValidateAttackParams(parameters);
114+
case "expand_base":
115+
return ValidateExpandBaseParams(parameters);
114116
// 添加更多命令的参数验证
115117
default:
116118
return (true, null);
@@ -166,5 +168,19 @@ private static (bool isValid, MCPError error) ValidateAttackParams(JObject param
166168

167169
return (true, null);
168170
}
171+
172+
private static (bool isValid, MCPError error) ValidateExpandBaseParams(JObject parameters)
173+
{
174+
if (parameters != null && parameters.HasValues)
175+
{
176+
return (false, new MCPError
177+
{
178+
Code = "INVALID_PARAMS_EXPAND_BASE",
179+
Message = "expand_base命令不需要参数"
180+
});
181+
}
182+
183+
return (true, null);
184+
}
169185
}
170-
}
186+
}

OpenRA.Mods.Common/ServerCommands.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -889,8 +889,8 @@ public static string ExpandBaseCommand(JObject json, World world)
889889
if (expansionManager == null)
890890
return "Player does not have CopilotExpansionManager trait.";
891891

892-
expansionManager.StartExpansion(player);
893-
return "Base expansion started.";
892+
expansionManager.TryStartExpansion(player, out var message);
893+
return message;
894894
}
895895

896896
public static string PlaceBuildingCommand(JObject json, World world)
@@ -916,7 +916,7 @@ public static string PlaceBuildingCommand(JObject json, World world)
916916
var buildingActor = validBuildings.FirstOrDefault().Actor;
917917
ProductionQueue queue = validBuildings.FirstOrDefault().Queue;
918918
var readyBuilding = queue.AllQueued().Any(item => item.Done);
919-
if (readyBuilding == null)
919+
if (!readyBuilding)
920920
return "没有就绪的建筑可以放置";
921921

922922
var readyItem = queue.AllQueued().First(item => item.Done);

0 commit comments

Comments
 (0)