# 09 单位任务、小队脚本与电脑战略：AI 其实分布在好几层

红警源码中的 `AI()` 通常只是“这个对象本轮更新逻辑”的名称，并不专指电脑对手。玩家的矿车会自动采矿，玩家坦克会自动警戒，建筑会推进状态，动画会播放，这些都可以进入各自的 AI 更新。理解这个约定，才能避免把全部游戏模拟误读为敌方决策算法。

电脑对手的实际表现，是关卡触发器、阵营级生产与战略、小队脚本、单体任务以及移动战斗机制共同合成的。本章分析真实函数体，刻意区分实现、空桩和历史注释；案例是静态流程推演。

## 1. 四种决策尺度，以及它们如何碰头

| 层次 | 代表类型 | 回答的问题 | 输出 |
|---|---|---|---|
| 关卡导演 | `TriggerClass/TActionClass` | 何时开始生产、派哪波援军 | 状态开关、队伍创建、胜负标记 |
| 阵营战略 | `HouseClass` | 建什么、敌人是谁、何时总攻 | 建造请求、任务下发 |
| 小队协调 | `TeamClass/TeamTypeClass` | 组哪些单位、先去哪再打什么 | 成员的 Mission、NavCom、TarCom |
| 单体行为 | `MissionClass` 及派生类 | 当前该走、打、采矿还是停 | 状态转换、移动与攻击要求 |

`LogicClass::AI` 先处理通用触发器，随后更新 Teams，再更新逻辑对象，之后处理地图、工厂和阵营。这个顺序解释了为何不同层能在相邻逻辑时刻观察到彼此结果，也提醒读者这不是多个独立智能体并发线程。[主逻辑更新顺序](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/LOGIC.CPP#L198-L267)、[对象、地图与工厂更新](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/LOGIC.CPP#L277-L342)。

一个重要的工程判断是：这种分层让任务作者无需理解坦克的每一个转弯动作，单位作者也不用知道战役第几波援军何时到。两边通过少量命令和共享状态连接；代价是排查“单位为什么不行动”时，必须沿层级往下追。

## 2. Mission 是有定时器的状态机，不是一次函数调用

`MissionClass` 有当前 `Mission`、待切换的 `MissionQueue`、内部阶段 `Status` 和 `Timer`。`Assign_Mission` 通常只是写待执行槽；`Commence` 才真正将其变成当前任务，同时清零 `Status` 与 `Timer`。这里的 Queue 是**一个待执行任务槽**，不要想象成可无限排队的容器。[排队与开始](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MISSION.CPP#L343-L391)。

每次 `MissionClass::AI` 都先做基类更新，但只有任务定时器到零、对象仍有生命值时，才分派到 `Mission_Move/Attack/Guard/Harvest` 等函数。任务函数返回“下次多久之后再来”，返回值直接写入 Timer。悬在空中的步兵、车辆、舰船还会暂停这一层处理，避免伞降尚未落地就开展地面行动。[任务调度](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MISSION.CPP#L213-L319)。

这让昂贵的寻敌和决策不用每个逻辑更新都完整执行，同时底层移动、朝向、动画仍可以持续变化。很多任务使用规则中的 `Normal_Delay()`，再加少量随机偏移；这种错开检查时点的写法可以降低所有单位同时醒来的尖峰。后者是从结构得出的工程解释，不是源码中的性能测试结论。

三种很容易混淆的行为可直接从 `FootClass` 看出来：

- `Move` 关心导航目标是否完成；部分非人类控制单位还会寻找射程内目标。
- `Attack` 在目标合法时调用 `Approach_Target`，失去目标则回到空闲模式。
- `Guard` 以 `THREAT_RANGE` 搜索身边威胁；`Hunt` 用更广的 `THREAT_NORMAL` 获取目标，然后接近。特殊步兵还会转为占领或破坏任务。

因此“站着不动也会打人”和“主动追着敌人跑”是不同任务策略，不是同一个布尔值“开启 AI”。[移动与攻击任务](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/FOOT.CPP#L492-L570)、[警戒与猎杀](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/FOOT.CPP#L589-L703)。

任务还能被临时覆盖。`MissionClass::Override_Mission` 保存原任务，`TechnoClass` 再保存攻击目标，`FootClass` 再保存导航目标；恢复时逐层取回。这不是通用调用栈，而是分别保留一份暂停状态。前一章坦克为清除路障临时开火，就是理解这一设计的好例子：仅把 Mission 改回 Move 还不够，原本要去哪里也必须恢复。多层继承在这里不仅用于分类，还分担恢复上下文的职责。[任务保存](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MISSION.CPP#L468-L503)、[攻击目标保存](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TECHNO.CPP#L3640-L3670)、[导航目标保存](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/FOOT.CPP#L1503-L1534)。

## 3. 先用矿车理解“状态”与“动作”之间的差别

`UnitClass::Mission_Harvest` 给出了非常直观的状态机：`LOOKING`、`HARVESTING`、`FINDHOME`、`HEADINGHOME`、`GOINGTOIDLE`。非采矿型车辆收到该任务会长时间等待；没有矿场时则改为警戒。这些保护条件说明任务接口允许被通用地调用，但每种对象仍负责验证自身能力。[采矿状态与入口保护](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/UNIT.CPP#L2749-L2776)。

假设矿车正在矿区，流程可以这样跟踪：寻找状态会尝试记忆中的矿区位置并作较大范围搜索；找到可采位置后打开采矿动画，切入采集状态。采集状态检查动画阶段再实际采矿；装满则记下当前矿区为 `ArchiveTarget` 并转入找家。找家阶段查询可停靠矿场、发 `RADIO_HELLO`，收到 `RADIO_ROGER` 才进入返家协调；没有得到允许时可能先去矿场附近等候。[寻找、采集及返航握手](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/UNIT.CPP#L2778-L2893)。

所以“采矿 AI”不只是 `moveTo(ore); money += amount`。它还需要处理矿尽、装载比例、运输、基地接收能力、建筑忙闲与动画时序。源码变量继续叫 Tiberium 是系列代码沿用痕迹，不能据此说本作采集的是前作泰伯利亚矿。经济闭环在其他章节展开，这里把它作为任务状态机的可读样本。

## 4. TeamType 是配方，Team 是运行中的队伍

`TeamTypeClass::Fill_In` 从关卡文本读入队伍所属方、招募优先级、数量限制、出发 waypoint、成员类型与数量，以及一个按顺序执行的任务列表。新格式将绕行、自杀式、自动创建、预先生产、可补员等标志打包为位；旧格式分别占文本字段。随后按名字寻找步兵、车辆、飞机和舰船类型，最后读入 `(任务编号, 参数)` 对。[队伍配方解析](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAMTYPE.CPP#L1660-L1779)。

一个 TeamType 可以有多个运行实例；Team 保存实际成员链、各类型现有数量、当前脚本位置、目标、集合状态和超时。它也不是玩家按 Ctrl+数字建立的快捷选择组；本章的 Team 主要是游戏逻辑中的编组与脚本控制对象。

招募不是随便拉一个最近单位。`Can_Add` 要求对象属于同一 House、可活动、不正忙于无线电协调、当前任务允许招募、已有队伍的招募优先级低于新队伍；带武器但无弹药的飞机会被拒绝，类型和剩余名额也要吻合。`Recruit` 再扫描对应对象集合、结合队伍中心或 Origin 评估距离。车辆分支的添加位置有自己的历史实现，不能把所有分支简化成完全相同的“一次挑最近一个”。[招募资格](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L961-L1028)、[成员搜索](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L1180-L1285)。

`TeamClass::AI` 在成员改变时重新判断人数：达到配方总人数就是满员；可补员队伍通常在不足约三分之一时进入兵力不足状态，小队总编制很小时有专门条件。不可补员队伍一旦正式出发，就不会因为减员一直回头等人。严重减员时，它会尝试挑选友方建筑附近的集合点，距离结合地图威胁评价，维修设施获得偏好。[兵力与重组](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L495-L617)。

## 5. 小队脚本怎样驱动一波进攻

构造一个“3 辆坦克，移至 waypoint A，进攻建筑”的队伍作为阅读案例。这里用语义描述，不把它冒充仓库自带的某张地图或可直接粘贴的 INI。

1. 关卡或 House 创建 Team 实例，它还没有凭空得到三辆坦克。Team 的招募逻辑寻找合格的现有车辆；缺兵可能通过阵营生产请求补足。
2. 满员或被 `Force_Active` 后，`IsMoving` 置位，`CurrentMission=-1`，`IsNextMission=true`。下一次推进会把脚本下标加一。
3. `TMISSION_MOVE` 的参数是 waypoint 编号，先查 `Scen.Waypoint[]`。领队不能进入该格时，可改找附近位置。
4. `Coordinate_Move` 对每个成员派 `MISSION_MOVE` 和导航目标；掉队者、仍在卸载者、未停止的导航都会影响完成判定。普通队伍允许一定距离容差，编队移动可能要求精确格位。
5. 全部满足后设置 `IsNextMission`，下次推进执行攻击步骤。领队按目标类别调用 `Greatest_Threat` 选一个目标，小队再给成员分配攻击任务与 `TarCom`。
6. 每辆坦克在单体任务里接近敌人，交给移动层寻路、战斗层决定能否开火。队伍本身不直接移动坦克坐标，也不直接扣敌人生命值。

[出发与脚本推进](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L623-L752)、[移动协调完成条件](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L1874-L2007)、[按猎物类别选择目标](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L2687-L2749)、[给成员下攻击命令](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L1675-L1715)。

脚本还支持装载、卸载、巡逻、部署、编队、执行单体任务、设置全局标志和循环。`TimeOut` 初始化看似对所有任务都赋值，但不能据此说每个步骤都按同一计时器自动跳过；主分派后的通用超时检查明确针对 Guard，而其他步骤有自己的完成条件。这正是读函数体比只看字段名可靠的原因。[任务分派与 Guard 超时](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L755-L860)。

脚本的“循环”也没有隐藏的高级虚拟机：`TMission_Loop` 将 `CurrentMission` 写为指定下标减一，再标记进入下一步，让统一的下标递增逻辑完成跳转。`TMission_Set_Global` 则设置场景全局标志并立即进入下一步，这样队伍到达某处或完成进攻后，可以通知关卡导演开展下一阶段。巡逻任务会周期性让领队搜寻附近目标；当前目标是对象时协调攻击，是位置时协调移动。这些简单原语组合起来，就能制造比单条 Hunt 命令丰富得多的战场行为。[循环与全局通知](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L2866-L2924)、[巡逻分派](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAM.CPP#L2945-L2983)。

## 6. House 把生产、敌情与进攻节奏连接起来

`HouseClass::AI` 中，启用基地建设或 IQ 达到生产阈值会联动设置 `IsBaseBuilding/IsStarted/IsAlerted`。警戒已启动且 `AlertTime` 归零时，它选择队伍类型、创建实例，然后重新随机化下次间隔。`Suggested_New_Team` 会按所属方、是否自动创建、该类型已存在数量与最大数量筛选；不能将注释中已废弃的候选评分逻辑当作实际算法。[阵营激活与自动造队](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L923-L1001)、[队伍候选筛选](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/TEAMTYPE.CPP#L419-L444)。

生产则有两条重要来源。战役的 `AI_Unit` 观察活跃队伍缺口与 `IsPrebuilt` 类型需求，扣掉可招募现有单位，再考虑能否建造与资金。基地建设模式另有基于可造单位的随机权重：带武器车辆权重 20，非采矿车的其他可造车辆权重 1。两段代码可以在同一次调用中先后运行，因此不能说战役与遭遇战只有两个互不重叠的独立 AI。[车辆生产需求与随机选择](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L5778-L5902)。

`AI_Building` 先可参考关卡的 `Base.Next_Buildable`，再在基地建设模式下按电力、收入、建筑比例、科技及资金形成候选。例如电力接近不足就加入电厂；炼矿厂数量低于比例且有条件时加入炼矿厂；兵营也有比例和上限。它让电脑扩张的外观像一种策略，但本质是手工规则与状态统计驱动。[建筑选择](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L5422-L5515)。

## 7. “专家系统”有实装，也有看起来完整的空接口

`Expert_AI` 会失效旧敌人引用，并结合阵营距离、对己方的击杀、相对兵力建筑规模等选择敌方。它也形成各战略动作的 urgency，再从高到低调用对应处理函数。这是规则系统，不是机器学习、博弈树搜索或行为树解释器。[敌方评估](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L4581-L4665)、[紧急度分派](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L4760-L4874)。

不过 `AI_Build_Power/Defense/Offense/Income` 在这份代码里只是 `return(false)`，一些对应检查也返回 `URGENCY_NONE`。真正的电厂和兵力建设逻辑在前述 `AI_Building/AI_Unit` 等路径。紧急度循环旁的注释还说“最高优先级已行动则停止”，但函数体没有据 `acted` 提前退出的代码。这些都应作为历史代码现状理解，不能为了讲出漂亮架构而补上不存在的行为。[空实现](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L5110-L5149)。

实装的 `AI_Attack` 会依据随机选择让部分有武器单位改为 Hunt，或让守基地单位调整站位，然后重设进攻计时器；没有生产设施时的 `AI_Fire_Sale` 可卖掉建筑并全面出击。玩家感受到的“最后一波总攻”，因此可能来自阵营规则，而不一定由关卡作者单独写了一支特殊队伍。[进攻与节奏](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L5049-L5102)、[清仓总攻](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L5152-L5161)。

难度也不能仅解释成“思考更聪明”。`Assign_Handicap` 明确调整火力、地面与空中速度、装甲、射速、费用、维修延迟与建造速度等倍率；多人模式还结合所扮演国家的修正。IQ 则是功能门槛之一。这两套概念各有作用，不能把难度枚举名称直接当作玩家感知难度的全部定义。[难度倍率](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/HOUSE.CPP#L278-L306)。

## 8. 调试一个“不进攻”的电脑应该检查什么

沿因果链看比从总入口逐行单步更有效：关卡是否打开生产或自动创建；Team 是否已创建、配方是否允许当前数量；所需单位能否生产、能否被招募；队伍是否满员、在重组或挂起；脚本目标是否合法、成员是否仍有 NavCom；单体 Mission 是否已经 Commence；最后才检查路线、武器和射程。

这种诊断方法连接了[地图与寻路](04-map-coordinates-and-pathfinding.md)以及[关卡触发器](10-scenarios-triggers-and-campaign.md)：队伍层看似停止，根因可能是唯一桥梁被炸断；战略层看似缺钱，根因可能是矿车一直等待矿场；剧情未触发又可能根本没有启动 IsAlerted。共享状态让系统便宜地协作，也让局部现象很难单独解释。

## 源码阅读路线与待验证边界

建议按 `MissionClass::AI/Commence → FootClass::Mission_Guard → UnitClass::Mission_Harvest → TeamTypeClass::Fill_In → TeamClass::Can_Add/AI/Coordinate_Move → HouseClass::AI/AI_Unit/AI_Building/Expert_AI` 阅读。

尚未通过游戏运行测量各难度胜率、生产偏好或队伍卡住概率；这些需要规则资源、固定地图与重复回放。本文能确认的是决策结构和分支条件。尤其不要根据功能名字认定 AI 会完整执行空接口注释中的意图，也不要将这套脚本系统解释为现代神经网络智能体。
