template/docs/intermediate/combat-overview.md
unanmed b1b7c8e0e7 docs(10-08): 重写 combat-overview、enemy-specials(引导式)
- combat-overview 用平实语言解释计算后怪物、为何重建、为何分四步
- enemy-specials 改为引导式开篇,保留两类定义、实现位置表与编辑器约定
2026-10-17 21:05:06 +08:00

90 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 战斗总览
在游戏里点一下怪物,勇士走过去、挥几刀,屏幕上出现伤害和回合数。对玩家来说这只是一瞬间,但在数据端,它背后有一条固定的流水线:先准备一份属性准确的怪物,再把双方的属性换算成伤害,最后跑一遍战前与战后的规则。
本篇沿着这条流水线走一遍,让你知道每一步在做什么、该去哪篇文档看细节。
## 先准备一份「计算后怪物」
怪物数据里保存的是一份**模板**:基础属性(`hp`、`atk`、`def` 等),加上若干特殊属性(先攻、连击、光环……)。它描述的是「这只怪物本来是什么样」。
可战斗不能直接拿模板来算。光环会改周围怪物的属性,查询会根据周围情况调整自己,最终效果还会在收尾时再改一次。如果直接拿模板去算伤害,这些影响就全丢了。
所以进入战斗前,怪物上下文会把每只怪物**重建**成一份属性已经算准的副本,这就是「计算后怪物」。重建的入口是:
```ts
state.enemyContext.buildup();
```
`buildup()` 会按固定顺序处理每只怪物,把所有特殊属性的作用真正写进属性里。之后伤害计算器读到的就是最终结果,而不是模板上的原始数字。
### 为什么要重建
一句话:**为了拿到准确的怪物属性**。
模板上的 `atk` 只是怪物「自己写的」攻击力。实际战斗中,它可能被旁边的光环加成,也可能因为查询看到周围有多少同类而提高,还可能被最终效果改写。只有把这些作用全部算进去,伤害计算才可能准确。
重建还有一个好处:每次重建都从模板的原始值出发,所以反复 `buildup()` 不会让加成越叠越多。光环、查询与最终效果改的始终是那份计算后副本,模板本身保持不变。
### 为什么分四步
重建不是一次算完的,而是分成四个阶段。原因很简单:**不同阶段要处理的事情不一样**。有的阶段要先决定「怪物拥有哪些特殊属性」,有的阶段才能去改基础属性,还有的阶段需要看周围的怪物。
1. **光环与特殊查询**:先把特殊属性转换成光环视图,按优先级执行会修改特殊属性的效果,确定每只怪物最终拥有哪些特殊属性。
2. **普通光环**:执行修改基础属性的光环效果。
3. **常规查询**:允许读取上下文(周围怪物、位置等),并修改自身基础属性。
4. **最终效果**:不再查询上下文,只对自身基础属性做收尾调整。
可以把它想成装配一件装备:先确认要装哪些零件(第 1 步),再装主体(第 2 步),接着根据使用环境微调(第 3 步),最后做一次收尾检查(第 4 步)。顺序不能随便换,因为后面的步骤要建立在前面步骤的结果上。
前三步都可能受优先级影响;光环与查询共用同一套优先级,见[怪物光环](./enemy-aura.md)与[怪物查询](./enemy-query.md)。最终效果见[最终属性效果](./enemy-final.md)。
## 伤害大致怎么算
属性准备完之后,伤害计算器把它们换算成两个数字。一次战斗的伤害可以写成 `f(enemy, hero)`:`enemy` 是计算后的怪物对象,`hero` 是勇士属性对象。计算器读取双方的 `hp`、`atk`、`def`、`mdef` 等属性,返回:
- `damage`:本次战斗勇士会损失的生命值;
- `turn`:战斗持续多少回合。
数据端不逐帧模拟战斗,只根据公式给出这两个值。公式的实现与替换见[伤害计算](./enemy-calculator.md)。
## 战斗流程
把上面几步连起来,就是一次完整的战斗:
```mermaid
graph TD
A[怪物基础数据] --> B[光环 / 查询 / 最终]
B --> C[伤害计算 f(enemy, hero)]
C --> D[战前脚本 before]
D -->|返回 false| X[放弃战斗]
D -->|返回 true| E[战斗结算 damage / turn]
E --> F[战后脚本 after]
F --> G[战后钩子与状态钩子]
```
- **基础数据**:怪物模板的属性与特殊属性,来自怪物数据;
- **光环 / 查询 / 最终**:由 `enemyContext.buildup()` 生成计算后怪物;
- **伤害计算**:`IDamageCalculator.calculate` 返回 `{ damage, turn }`;
- **战前脚本**:`ICombatScript.before` 返回 `false` 时放弃战斗;
- **战斗结算**:按 `damage` 扣血,按 `turn` 记录回合数;
- **战后脚本**:`ICombatScript.after` 结算经验、金币等内容;
- **战后钩子与状态钩子**:处理中毒、衰弱、重生等持续状态。
战斗流程本身由 `state.combat`(`ICombatFlow`)驱动,`battle(view)` 返回本次战斗的伤害信息。脚本见[战斗脚本](./enemy-combat-script.md),状态钩子见[状态钩子](./enemy-status-hooks.md)。
## 流程步骤与对应文档
| 步骤 | 做什么 | 对应文档 |
|------|--------|----------|
| 特殊属性 | 定义怪物的机制与参数 | [特殊属性](./enemy-specials.md) |
| 光环 | 影响周围怪物的基础属性或特殊属性 | [怪物光环](./enemy-aura.md) |
| 查询 | 读取上下文后调整自身 | [怪物查询](./enemy-query.md) |
| 最终效果 | 只调整自身基础属性 | [最终属性效果](./enemy-final.md) |
| 伤害计算 | 实现 `f(enemy, hero)` | [伤害计算](./enemy-calculator.md) |
| 地图伤害 | 勇士移动时按位置结算伤害 | [地图伤害](./enemy-map-damage.md) |
| 战前 / 战后脚本 | 拦截战斗、结算奖励 | [战斗脚本](./enemy-combat-script.md) |
| 状态钩子 | 处理中毒、衰弱、重生等状态 | [状态钩子](./enemy-status-hooks.md) |
勇士一侧的属性读写见[勇士属性](./hero-attribute.md)。