|
| 1 | +--- |
| 2 | +name: "openra-copilot-api-command" |
| 3 | +description: "Guides adding new OpenRA Copilot API commands. Invoke when implementing, registering, validating, or debugging Socket-based Copilot commands and related Traits." |
| 4 | +--- |
| 5 | + |
| 6 | +# OpenRA Copilot API Command |
| 7 | + |
| 8 | +在这个工作区中,当任务涉及为 OpenRA Copilot Mod 新增、修改、排查 API 指令时,使用这个技能。 |
| 9 | + |
| 10 | +## 何时使用 |
| 11 | + |
| 12 | +在以下场景调用这个技能: |
| 13 | + |
| 14 | +- 用户要求新增一个 OpenRA Copilot API 指令 |
| 15 | +- 需要修改已有 Copilot 指令的参数验证、注册或执行逻辑 |
| 16 | +- 需要排查 Socket API 请求能到达服务端,但命令执行失败的问题 |
| 17 | +- 任务涉及 `CopilotCommandServer.cs`、`CopilotModels.cs`、`ServerCommands.cs` 或 `Traits/Copilot/` |
| 18 | +- 某个复杂命令需要拆成跨多个 Tick 执行的 Trait 状态机 |
| 19 | + |
| 20 | +不要在纯地图编辑、纯 YAML 地图资源修改、或与 Copilot API 无关的通用游戏逻辑任务中调用这个技能。 |
| 21 | + |
| 22 | +## 核心架构 |
| 23 | + |
| 24 | +OpenRA Copilot 的 API 系统基于 Socket 通信,核心职责通常分布在以下文件: |
| 25 | + |
| 26 | +- `OpenRA.Game/CopilotCommandServer.cs` |
| 27 | + - 服务端入口 |
| 28 | + - 接收 Socket 请求 |
| 29 | + - 解析 JSON |
| 30 | + - 验证基础格式 |
| 31 | + - 将命令路由到对应处理函数 |
| 32 | + |
| 33 | +- `OpenRA.Game/CopilotModels.cs` |
| 34 | + - 定义请求与响应模型 |
| 35 | + - 负责命令参数验证 |
| 36 | + |
| 37 | +- `OpenRA.Mods.Common/ServerCommands.cs` |
| 38 | + - 放置具体指令处理逻辑 |
| 39 | + - 通常负责解析参数、解析玩家、调用游戏逻辑或 Trait |
| 40 | + |
| 41 | +- `OpenRA.Mods.Common/Traits/Copilot/` |
| 42 | + - 存放复杂命令对应的 Trait |
| 43 | + - 适合封装跨多个 Tick 的长流程逻辑 |
| 44 | + - 常见挂载位置是 `Player` 或 `World` |
| 45 | + |
| 46 | +## 标准新增流程 |
| 47 | + |
| 48 | +假设要新增一个名为 `my_command` 的新指令,优先按以下顺序实现。 |
| 49 | + |
| 50 | +### 第 1 步:在 `CopilotModels.cs` 中补齐参数验证 |
| 51 | + |
| 52 | +在 `ValidateCommandParams` 中新增分支,并为命令编写专用校验函数。 |
| 53 | + |
| 54 | +```csharp |
| 55 | +public static (bool isValid, MCPError error) ValidateCommandParams(string command, JObject parameters) |
| 56 | +{ |
| 57 | + switch (command) |
| 58 | + { |
| 59 | + case "my_command": |
| 60 | + return ValidateMyCommandParams(parameters); |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +private static (bool isValid, MCPError error) ValidateMyCommandParams(JObject parameters) |
| 65 | +{ |
| 66 | + if (parameters == null || !parameters.ContainsKey("targetId")) |
| 67 | + { |
| 68 | + return (false, new MCPError |
| 69 | + { |
| 70 | + Code = "MISSING_PARAM", |
| 71 | + Message = "Missing parameter: targetId" |
| 72 | + }); |
| 73 | + } |
| 74 | + |
| 75 | + return (true, null); |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +实现要点: |
| 80 | + |
| 81 | +- 不要把参数校验延后到真正执行逻辑时才做 |
| 82 | +- 错误信息要明确指出缺失字段或格式问题 |
| 83 | +- 如果已有类似命令,优先复用现有校验风格 |
| 84 | + |
| 85 | +### 第 2 步:在 `ServerCommands.cs` 中实现命令逻辑 |
| 86 | + |
| 87 | +为新命令添加静态处理方法。 |
| 88 | + |
| 89 | +```csharp |
| 90 | +public static string MyCommand(JObject json, World world) |
| 91 | +{ |
| 92 | + var player = ResolvePlayer(json, world); |
| 93 | + var targetId = json["targetId"]?.ToObject<int>(); |
| 94 | + |
| 95 | + // 执行游戏逻辑 |
| 96 | +
|
| 97 | + return "Command executed successfully"; |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +实现要点: |
| 102 | + |
| 103 | +- 先解析玩家,再解析命令参数 |
| 104 | +- 尽量沿用已有的 `ResolvePlayer`、实体解析、命名风格和错误返回模式 |
| 105 | +- 简单命令直接在这里完成 |
| 106 | +- 复杂命令只在这里做参数入口与调度,把流程下沉到 Trait |
| 107 | + |
| 108 | +### 第 3 步:在 `CopilotCommandServer.cs` 中注册命令 |
| 109 | + |
| 110 | +在 `WorldLoaded` 中把字符串命令名映射到处理函数。 |
| 111 | + |
| 112 | +```csharp |
| 113 | +public void WorldLoaded(World w, WorldRenderer wr) |
| 114 | +{ |
| 115 | + if (w.Type == WorldType.Regular && w.CopilotServer != null) |
| 116 | + { |
| 117 | + w.CopilotServer.CommandHandlers["my_command"] = ServerCommands.MyCommand; |
| 118 | + } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +实现要点: |
| 123 | + |
| 124 | +- 注册名必须和请求里的命令名完全一致 |
| 125 | +- 如果命令逻辑已实现但没有注册,外部调用仍然会失败 |
| 126 | +- 修改后要检查是否只在正确的世界类型下注册 |
| 127 | + |
| 128 | +### 第 4 步:复杂逻辑改用 Trait |
| 129 | + |
| 130 | +如果命令涉及跨多个 Tick 的操作,优先创建独立 Trait,而不是把所有逻辑塞进一个方法。 |
| 131 | + |
| 132 | +典型场景: |
| 133 | + |
| 134 | +- 造单位 |
| 135 | +- 等待生产完成 |
| 136 | +- 控制移动 |
| 137 | +- 展开或部署 |
| 138 | +- 放置建筑 |
| 139 | +- 串联多个前置检查与状态切换 |
| 140 | + |
| 141 | +推荐做法: |
| 142 | + |
| 143 | +1. 在 `OpenRA.Mods.Common/Traits/Copilot/` 下创建新的 Trait 文件 |
| 144 | +2. 实现 `ITick` 处理逐帧推进 |
| 145 | +3. 由 `ServerCommands.cs` 获取对应 Trait 并调用入口方法 |
| 146 | + |
| 147 | +## 长流程命令的设计原则 |
| 148 | + |
| 149 | +### 使用状态机管理流程 |
| 150 | + |
| 151 | +对于类似 `expand_base` 这样的长流程,使用状态机而不是单函数串行逻辑。 |
| 152 | + |
| 153 | +推荐模式: |
| 154 | + |
| 155 | +- 使用 `enum` 表达流程状态 |
| 156 | +- 在 `ITick.Tick` 中根据状态推进一步 |
| 157 | +- 每次只做一个小动作 |
| 158 | +- 动作发出后切换到对应 Waiting 状态 |
| 159 | + |
| 160 | +示例状态: |
| 161 | + |
| 162 | +- `WaitingForMCV` |
| 163 | +- `MovingMCV` |
| 164 | +- `WaitingForDeploy` |
| 165 | +- `WaitingForPowerPlant` |
| 166 | + |
| 167 | +### 使用等待与重试节流 |
| 168 | + |
| 169 | +不要每个 Tick 都高频重复发命令或高频检查完整条件。 |
| 170 | + |
| 171 | +推荐模式: |
| 172 | + |
| 173 | +- 使用 `waitTicks` 做简单节流 |
| 174 | +- 把它既当作轮询间隔,也当作基础超时重试机制 |
| 175 | +- 对生产、移动、部署这类动作都设置明确等待窗口 |
| 176 | + |
| 177 | +## 经验总结与避坑指南 |
| 178 | + |
| 179 | +### 1. Trait 必须在 YAML 中注册 |
| 180 | + |
| 181 | +常见现象: |
| 182 | + |
| 183 | +- 代码已经写好 |
| 184 | +- 调用命令时报错 `Player does not have X trait` |
| 185 | + |
| 186 | +根本原因: |
| 187 | + |
| 188 | +- 仅在 C# 中定义 Trait 还不够 |
| 189 | +- 必须在规则文件中显式挂载,否则 Actor 不会拥有它 |
| 190 | + |
| 191 | +检查位置: |
| 192 | + |
| 193 | +- `mods/ra/rules/player.yaml` |
| 194 | +- `mods/cnc/rules/player.yaml` |
| 195 | +- 必要时检查 `rules/world.yaml` |
| 196 | + |
| 197 | +检查重点: |
| 198 | + |
| 199 | +- 确认 Trait 名称出现在正确的 Actor 定义下 |
| 200 | +- 例如 `CopilotExpansionManager` 是否真实挂在 `Player` 或 `World` 上 |
| 201 | + |
| 202 | +### 2. 生产队列优先使用批量补齐,而不是单次反复发单 |
| 203 | + |
| 204 | +常见现象: |
| 205 | + |
| 206 | +- 目标是建造多个建筑 |
| 207 | +- 实际只造了一个 |
| 208 | +- 或者逻辑在 Tick 循环里不断重复下单 |
| 209 | + |
| 210 | +更可靠的模式是 `EnsureProduction(type, quantity)`: |
| 211 | + |
| 212 | +- 计算目标总数 |
| 213 | +- 扣除队列中已有数量 |
| 214 | +- 扣除已完成但尚未放置的数量 |
| 215 | +- 一次性把剩余数量交给引擎队列系统处理 |
| 216 | + |
| 217 | +推荐思路: |
| 218 | + |
| 219 | +- 使用 `需建造总数 - (当前队列数量 + 已完成未放置数量)` |
| 220 | +- 调用 `Order.StartProduction(actor, item, count)` 一次下发剩余数 |
| 221 | + |
| 222 | +这样可以避免: |
| 223 | + |
| 224 | +- 每 Tick 重复发单 |
| 225 | +- 造完第一个后无法自然衔接第二个 |
| 226 | +- 人工维护队列状态过于复杂 |
| 227 | + |
| 228 | +### 3. 正确构造 Order 与 Target |
| 229 | + |
| 230 | +常见现象: |
| 231 | + |
| 232 | +- 发送了 `Move` 或 `Deploy` |
| 233 | +- 单位没有反应 |
| 234 | + |
| 235 | +优先检查以下几点: |
| 236 | + |
| 237 | +1. `Target` 是否正确构造 |
| 238 | + - `Target.FromCell` |
| 239 | + - `Target.FromActor` |
| 240 | + - 二者不能混用 |
| 241 | + |
| 242 | +2. `Order` 构造函数是否匹配具体命令 |
| 243 | + - 某些命令如 `DeployTransform` 需要特殊参数 |
| 244 | + - 例如视觉反馈参数或是否排队参数 |
| 245 | + |
| 246 | +3. 是否错误地把命令加入已有队列末尾 |
| 247 | + - 紧急命令通常应设置为立即执行 |
| 248 | + - 如果不覆盖当前队列,单位可能永远先执行旧命令 |
| 249 | + |
| 250 | +处理原则: |
| 251 | + |
| 252 | +- 对移动、展开、脱离卡死状态这类强控制命令,优先考虑非排队执行 |
| 253 | + |
| 254 | +### 4. 错误信息必须具体 |
| 255 | + |
| 256 | +不要只返回: |
| 257 | + |
| 258 | +- `Failed` |
| 259 | +- `Command failed` |
| 260 | + |
| 261 | +推荐返回: |
| 262 | + |
| 263 | +- 缺少哪个参数 |
| 264 | +- 缺少哪个前置建筑或科技 |
| 265 | +- 当前流程正在执行什么 |
| 266 | +- 是否已有同类任务在进行中 |
| 267 | + |
| 268 | +优先模式: |
| 269 | + |
| 270 | +- 如果缺前置,调用类似 `DescribeMissingPrerequisites` |
| 271 | +- 如果流程已在运行,返回如 `Base expansion already in progress` |
| 272 | + |
| 273 | +### 5. 做好多 Mod 兼容 |
| 274 | + |
| 275 | +常见现象: |
| 276 | + |
| 277 | +- 在 RA 下正常 |
| 278 | +- 在 CNC 下报错或无反应 |
| 279 | + |
| 280 | +根本原因: |
| 281 | + |
| 282 | +- 不同 Mod 的 Actor 名称不同 |
| 283 | + |
| 284 | +例如同类建筑可能存在多种名字: |
| 285 | + |
| 286 | +```csharp |
| 287 | +string[] powerNames = { "POWR", "APWR", "PWR", "NUKR" }; |
| 288 | +``` |
| 289 | + |
| 290 | +推荐做法: |
| 291 | + |
| 292 | +- 对关键单位和建筑定义别名数组 |
| 293 | +- 解析时按别名依次尝试 |
| 294 | +- 不要把 RA 的命名硬编码成唯一来源 |
| 295 | + |
| 296 | +## 实施检查清单 |
| 297 | + |
| 298 | +完成新增命令后,至少检查以下几点: |
| 299 | + |
| 300 | +- 参数验证已接入 `ValidateCommandParams` |
| 301 | +- 命令处理函数已写入 `ServerCommands.cs` |
| 302 | +- 命令已在 `CopilotCommandServer.cs` 注册 |
| 303 | +- 如果使用 Trait,已在对应 YAML 中挂载 |
| 304 | +- 错误返回信息足够明确 |
| 305 | +- 长流程逻辑已拆成状态机,而不是单函数硬写 |
| 306 | +- 对 RA 和 CNC 的命名差异做了兼容处理 |
| 307 | + |
| 308 | +## 回答此类任务时的建议 |
| 309 | + |
| 310 | +当你处理这类需求时,优先按下面的顺序工作: |
| 311 | + |
| 312 | +1. 先定位命令入口、参数校验、注册表和实际处理逻辑 |
| 313 | +2. 判断这是简单即时命令还是复杂长流程命令 |
| 314 | +3. 简单命令直接在 `ServerCommands.cs` 实现 |
| 315 | +4. 长流程命令下沉到 Trait,并配套状态机 |
| 316 | +5. 最后检查 YAML 注册与跨 Mod 命名兼容 |
| 317 | + |
| 318 | +如果用户是在排查已有命令失败,优先检查: |
| 319 | + |
| 320 | +1. 命令是否注册 |
| 321 | +2. 参数是否通过校验 |
| 322 | +3. Trait 是否实际挂载 |
| 323 | +4. Order 与 Target 是否构造正确 |
| 324 | +5. 是否被旧命令队列阻塞 |
0 commit comments