mirror of
https://github.com/motajs/template.git
synced 2026-10-18 04:20:17 +08:00
docs(10-04): write combat overview, specials, and aura docs
- add combat flow overview with mermaid and doc map - document special attribute definitions and implementation locations - document aura priority, built-in auras, and custom examples
This commit is contained in:
parent
678addf2a4
commit
5528edc70d
61
docs/intermediate/combat-overview.md
Normal file
61
docs/intermediate/combat-overview.md
Normal file
@ -0,0 +1,61 @@
|
||||
# 战斗总览
|
||||
|
||||
战斗在数据端完成:怪物先被计算成「计算后怪物」,再由伤害计算器根据怪物与勇士的属性算出伤害与回合数,最后经过战前脚本、战后脚本与状态钩子结算。本篇串起这条主线,各环节的细节见对应文档。
|
||||
|
||||
## 伤害大致怎么算
|
||||
|
||||
一次战斗的伤害可以写成 `f(enemy, hero)`:`enemy` 是计算后的怪物对象,`hero` 是勇士属性对象。计算器读取双方的 `hp`、`atk`、`def`、`mdef` 等属性,返回:
|
||||
|
||||
- `damage`:本次战斗勇士会损失的生命值;
|
||||
- `turn`:战斗持续多少回合。
|
||||
|
||||
数据端不逐帧模拟战斗,只根据公式给出这两个值。公式的实现与替换见[伤害计算](./enemy-calculator.md)。
|
||||
|
||||
## 计算后怪物:buildup 的四个阶段
|
||||
|
||||
怪物在数据里保存的是基础属性与特殊属性。进入战斗前,怪物上下文会把每个怪物重建为「计算后怪物」(`enemyContext.buildup()`),分四步:
|
||||
|
||||
1. **光环与特殊查询**:把特殊属性转换成光环视图,按优先级执行会修改特殊属性的效果,确定每个怪物最终拥有哪些特殊属性;
|
||||
2. **普通光环**:执行修改基础属性的光环效果;
|
||||
3. **常规查询**:允许读取上下文(周围怪物、位置等),并修改自身基础属性;
|
||||
4. **最终效果**:不再查询上下文,只对自身基础属性做收尾调整。
|
||||
|
||||
前三步都可能受优先级影响;光环与查询共用同一套优先级,见[怪物光环](./enemy-aura.md)与[怪物查询](./enemy-query.md)。最终效果见[最终属性效果](./enemy-final.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)。
|
||||
280
docs/intermediate/enemy-aura.md
Normal file
280
docs/intermediate/enemy-aura.md
Normal file
@ -0,0 +1,280 @@
|
||||
# 怪物光环
|
||||
|
||||
光环(aura)是一类特殊属性的执行方式:带有光环的怪物不直接改自己,而是把效果施加到范围内的其他怪物(也可以包括自己)。本篇先讲优先级,再讲内置光环与自定义写法。基础概念见[战斗总览](./combat-overview.md)。
|
||||
|
||||
## 优先级
|
||||
|
||||
每个光环视图都有 `priority`,每个查询效果也有 `priority`,两者共用同一套优先级:
|
||||
|
||||
- 构建时从高优先级开始处理,再处理较低优先级;
|
||||
- 高优先级产生的特殊属性变化,低优先级能看到;低优先级产生变化时,高优先级已经执行完,不能回头影响它;
|
||||
- 由效果新增的光环只能影响之后的阶段。若新光环的优先级高于当前阶段,开发环境会告警并跳过它。
|
||||
|
||||
因此,想让一个光环「先于普通光环生效」,把它的 `priority` 设得更高即可。
|
||||
|
||||
## 内置光环
|
||||
|
||||
| 光环 | code | 视图类 | priority | 行为 |
|
||||
|------|------|--------|----------|------|
|
||||
| 普通光环 | 23 | `CommonAura` | 10 | 按范围给怪物加 `hp` / `atk` / `def` 百分比 |
|
||||
| 支援 | 24 | `GuardAura` | 5 | 把范围内的怪物登记为支援者,战斗时一起结算 |
|
||||
| 特殊光环 | 26 | `SpecialAura` | 20 | 通过 `SpecialAuraModifier` 给目标添加或修改特殊属性 |
|
||||
|
||||
这三类都在 `src/data/enemy/aura.ts` 中实现,并在 `initializeEnemy` 里通过 `ctx.registerAuraConverter(...)` 注册。
|
||||
|
||||
## 运作方式
|
||||
|
||||
一个光环由两部分组成:
|
||||
|
||||
- **转换器** `IAuraConverter`:`shouldConvert(special, handler)` 判断某个特殊属性是否归自己处理;`convert(special, handler, context)` 返回光环视图;
|
||||
- **视图** `IEnemyAuraView`:描述优先级、范围、影响方式。
|
||||
|
||||
视图的核心成员:
|
||||
|
||||
- `priority`:优先级;
|
||||
- `range`:影响范围对象;
|
||||
- `couldApplyBase` / `couldApplySpecial`:描述此光环是否会修改基础属性 / 特殊属性;
|
||||
- `getRangeParam()`:返回范围参数;
|
||||
- `apply(handler, baseEnemy)`:修改目标怪物的基础属性;
|
||||
- `applySpecial(handler, baseEnemy)`:返回 `IEnemySpecialModifier`,用于添加、删除或修改目标怪物的特殊属性。
|
||||
|
||||
`apply` 与 `applySpecial` 的差别:
|
||||
|
||||
- `apply` 在「普通光环」阶段执行,目标是基础属性(`hp`、`atk`、`def` 等),拿到的 `baseEnemy` 是未修改的原始怪物;
|
||||
- `applySpecial` 在「光环与特殊查询」阶段执行,返回一个修饰器,由上下文按当前优先级应用到目标的特殊属性上。只有 `couldApplySpecial` 为 `true` 的光环才会被调用;
|
||||
- 只改基础属性就实现 `apply`;要给目标「上光环」或增删特殊属性,才实现 `applySpecial`。
|
||||
|
||||
范围对象由 `@motajs/common` 提供,常用的有 `FullRange`(全场)、`RectRange`(矩形)、`ManhattanRange`(曼哈顿距离/菱形)与 `RayRange`(射线)。
|
||||
|
||||
## 示例:基础光环(减少周围怪物的金币与经验)
|
||||
|
||||
先定义一个带参数的特殊属性(code 取一个未占用的值,这里用 30):
|
||||
|
||||
```ts
|
||||
/** 30 - 削弱光环 */
|
||||
export interface ISpecial30Value {
|
||||
range: number;
|
||||
square: boolean;
|
||||
moneyBuff: number;
|
||||
expBuff: number;
|
||||
}
|
||||
|
||||
// 30 - 削弱光环
|
||||
map.set(
|
||||
30,
|
||||
defineCommonSerializableSpecial<ISpecial30Value>(
|
||||
30,
|
||||
{ range: 1, square: false, moneyBuff: -20, expBuff: -20 },
|
||||
{
|
||||
name: '削弱光环',
|
||||
desc: special =>
|
||||
`周围怪物金币与经验下降${Math.abs(special.value.moneyBuff)}%。`,
|
||||
color: '#9AC'
|
||||
}
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
然后写转换器与视图:
|
||||
|
||||
```ts
|
||||
import {
|
||||
IManhattanRangeParam,
|
||||
IRange,
|
||||
IRectRangeParam,
|
||||
ITileLocator,
|
||||
ManhattanRange,
|
||||
RectRange
|
||||
} from '@motajs/common';
|
||||
import {
|
||||
IAuraConverter,
|
||||
IEnemyAuraView,
|
||||
IEnemyHandler,
|
||||
IEnemySpecialModifier,
|
||||
IReadonlyEnemyHandler
|
||||
} from '@user/data-system';
|
||||
import { IReadonlyEnemy, ISpecial } from '@user/data-base';
|
||||
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
|
||||
import { ISpecial30Value } from './special';
|
||||
|
||||
const MANHATTAN_RANGE = new ManhattanRange();
|
||||
const RECT_RANGE = new RectRange();
|
||||
|
||||
export class WeakAuraConverter implements IAuraConverter<IEnemyAttr, IHeroAttr> {
|
||||
shouldConvert(special: ISpecial<any>): boolean {
|
||||
return special.code === 30;
|
||||
}
|
||||
|
||||
convert(
|
||||
special: ISpecial<ISpecial30Value>,
|
||||
handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>
|
||||
): WeakAura {
|
||||
return new WeakAura(handler.enemy, special, handler.locator);
|
||||
}
|
||||
}
|
||||
|
||||
export class WeakAura implements IEnemyAuraView<
|
||||
IEnemyAttr,
|
||||
IRectRangeParam | IManhattanRangeParam,
|
||||
ISpecial30Value
|
||||
> {
|
||||
readonly priority = 8;
|
||||
readonly couldApplyBase = true;
|
||||
readonly couldApplySpecial = false;
|
||||
readonly range: IRange<IRectRangeParam | IManhattanRangeParam>;
|
||||
|
||||
constructor(
|
||||
readonly enemy: IReadonlyEnemy<IEnemyAttr>,
|
||||
readonly special: ISpecial<ISpecial30Value>,
|
||||
readonly locator: ITileLocator
|
||||
) {
|
||||
this.range = special.value.square ? RECT_RANGE : MANHATTAN_RANGE;
|
||||
}
|
||||
|
||||
getRangeParam(): IRectRangeParam | IManhattanRangeParam {
|
||||
const { range, square } = this.special.value;
|
||||
if (square) {
|
||||
return {
|
||||
x: this.locator.x - range,
|
||||
y: this.locator.y - range,
|
||||
w: range * 2 + 1,
|
||||
h: range * 2 + 1
|
||||
};
|
||||
}
|
||||
return {
|
||||
cx: this.locator.x,
|
||||
cy: this.locator.y,
|
||||
radius: range
|
||||
};
|
||||
}
|
||||
|
||||
apply(
|
||||
handler: IEnemyHandler<IEnemyAttr, IHeroAttr>,
|
||||
baseEnemy: IReadonlyEnemy<IEnemyAttr>
|
||||
): void {
|
||||
const { moneyBuff, expBuff } = this.special.value;
|
||||
const enemy = handler.enemy;
|
||||
if (moneyBuff !== 0) {
|
||||
const money = (baseEnemy.getAttribute('money') * moneyBuff) / 100;
|
||||
enemy.addAttribute('money', Math.floor(money));
|
||||
}
|
||||
if (expBuff !== 0) {
|
||||
const exp = (baseEnemy.getAttribute('exp') * expBuff) / 100;
|
||||
enemy.addAttribute('exp', Math.floor(exp));
|
||||
}
|
||||
}
|
||||
|
||||
applySpecial(): IEnemySpecialModifier<IEnemyAttr> | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注册:
|
||||
|
||||
```ts
|
||||
state.enemyContext.registerAuraConverter(new WeakAuraConverter());
|
||||
```
|
||||
|
||||
`apply` 里用 `baseEnemy` 的原始值做百分比,因此多个光环不会把彼此的结果继续乘进去。
|
||||
|
||||
## 示例:特殊光环(给周围怪物上光环)
|
||||
|
||||
如果要给周围怪物「上」一个特殊属性,而不是直接改基础属性,就在 `applySpecial` 中返回一个 `IEnemySpecialModifier`。下面的例子给范围内没有普通光环的怪物加上 code 23 的普通光环:
|
||||
|
||||
```ts
|
||||
// special.ts:31 - 传染光环
|
||||
map.set(
|
||||
31,
|
||||
defineNonePropertySpecial(31, {
|
||||
name: '传染光环',
|
||||
desc: '同楼层没有光环的怪物获得普通光环。',
|
||||
color: '#9F9'
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
```ts
|
||||
import { FullRange, ITileLocator } from '@motajs/common';
|
||||
import {
|
||||
IAuraConverter,
|
||||
IEnemyAuraView,
|
||||
IEnemySpecialModifier,
|
||||
IReadonlyEnemyHandler
|
||||
} from '@user/data-system';
|
||||
import { IReadonlyEnemy, ISpecial } from '@user/data-base';
|
||||
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
|
||||
|
||||
export class ChainAuraModifier implements IEnemySpecialModifier<IEnemyAttr> {
|
||||
constructor(readonly special: ISpecial<void>) {}
|
||||
|
||||
add(handler: IReadonlyEnemyHandler<IEnemyAttr, any>): ISpecial<any>[] {
|
||||
if (handler.enemy.hasSpecial(23)) return [];
|
||||
const sp = handler.state.enemyManager.getSpecial(23)?.();
|
||||
if (!sp) return [];
|
||||
sp.setValue({
|
||||
range: 1,
|
||||
square: false,
|
||||
hpBuff: 20,
|
||||
atkBuff: 0,
|
||||
defBuff: 0
|
||||
});
|
||||
return [sp];
|
||||
}
|
||||
|
||||
delete(): ISpecial<any>[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
modify(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChainAuraConverter implements IAuraConverter<IEnemyAttr, IHeroAttr> {
|
||||
shouldConvert(special: ISpecial<any>): boolean {
|
||||
return special.code === 31;
|
||||
}
|
||||
|
||||
convert(
|
||||
special: ISpecial<void>,
|
||||
handler: IReadonlyEnemyHandler<IEnemyAttr, IHeroAttr>
|
||||
): ChainAura {
|
||||
return new ChainAura(handler.enemy, special, handler.locator);
|
||||
}
|
||||
}
|
||||
|
||||
export class ChainAura implements IEnemyAuraView<IEnemyAttr, void, void> {
|
||||
readonly priority = 25;
|
||||
readonly couldApplyBase = false;
|
||||
readonly couldApplySpecial = true;
|
||||
readonly range = new FullRange();
|
||||
|
||||
constructor(
|
||||
readonly enemy: IReadonlyEnemy<IEnemyAttr>,
|
||||
readonly special: ISpecial<void>,
|
||||
readonly locator: ITileLocator
|
||||
) {}
|
||||
|
||||
getRangeParam(): void {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
apply(): void {}
|
||||
|
||||
applySpecial(): IEnemySpecialModifier<IEnemyAttr> | null {
|
||||
return new ChainAuraModifier(this.special);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里 `priority = 25` 高于内置特殊光环的 20,所以它在特殊光环之前执行;它新加上的普通光环(priority 10)会在之后的阶段生效。
|
||||
|
||||
不要把 `couldApplySpecial` 设为 `false` 后又指望 `applySpecial` 被调用:上下文只会对 `couldApplySpecial === true` 的光环调用它。
|
||||
|
||||
## 注册与全局光环
|
||||
|
||||
- 由特殊属性产生的光环:实现 `IAuraConverter` 并 `state.enemyContext.registerAuraConverter(...)`;
|
||||
- 没有来源怪物、始终生效的光环:实现 `IAuraView` 并 `state.enemyContext.addAura(view)`。
|
||||
|
||||
`initializeEnemy` 已经注册了 `CommonAuraConverter`、`GuardAuraConverter` 与 `SpecialAuraConverter`。新增转换器时照同样方式追加即可。
|
||||
141
docs/intermediate/enemy-specials.md
Normal file
141
docs/intermediate/enemy-specials.md
Normal file
@ -0,0 +1,141 @@
|
||||
# 特殊属性
|
||||
|
||||
特殊属性是挂在怪物身上的机制:一个数字 `code` 加一份可选参数 `value`。怪物的基础属性决定「有多少血、多少攻防」,特殊属性决定「它还会做什么」,例如先攻、连击、坚固、光环、中毒。访问怪物特殊属性的入口见[战斗总览](./combat-overview.md)。
|
||||
|
||||
## 在数据里的样子
|
||||
|
||||
数据端的怪物对象(`IReadonlyEnemy`)提供三个读取接口:
|
||||
|
||||
- `getSpecial<T>(code)`:取指定 code 的特殊属性对象,没有则返回 `null`;
|
||||
- `hasSpecial(code)`:判断是否拥有指定特殊属性;
|
||||
- `iterateSpecials()`:迭代全部特殊属性。
|
||||
|
||||
每个特殊属性对象实现 `ISpecial<T>`,常用成员是 `code`、`value` 与 `getDescription()`:
|
||||
|
||||
```ts
|
||||
const multiHit = enemy.getSpecial<number>(4);
|
||||
if (multiHit) {
|
||||
// 连击的 value 是每回合攻击次数
|
||||
console.log(multiHit.value);
|
||||
}
|
||||
|
||||
if (enemy.hasSpecial(3)) {
|
||||
// 坚固:只看有没有,不读参数
|
||||
}
|
||||
```
|
||||
|
||||
`ISpecial` 还提供 `setValue` / `getValue`、`getSpecialName` / `getNameColor`、`clone` 与 `deepEqualsTo`。需要修改怪物时用 `IEnemy` 的 `addSpecial` / `deleteSpecial`,不要直接改 `value` 后忘记刷新上下文。
|
||||
|
||||
## 两类定义
|
||||
|
||||
内置特殊属性在 `src/data/enemy/special.ts` 的 `createSpecials(state)` 中定义,使用两个工厂函数:
|
||||
|
||||
- `defineNonePropertySpecial(code, config)`:无参数,`value` 为 `void`,适合先攻、魔攻、坚固、模仿这类纯机制;
|
||||
- `defineCommonSerializableSpecial<T>(code, defaultValue, config)`:有参数,参数会进入存档,适合连击、破甲、光环这类需要数值描述的效果。
|
||||
|
||||
`config` 的 `name`、`desc`、`color` 都可以写成字符串,也可以写成 `(special) => string` 的函数:
|
||||
|
||||
```ts
|
||||
// 无参:坚固
|
||||
map.set(
|
||||
3,
|
||||
defineNonePropertySpecial(3, {
|
||||
name: '坚固',
|
||||
desc: '怪物防御不小于勇士攻击-1。',
|
||||
color: '#C0B088'
|
||||
})
|
||||
);
|
||||
|
||||
// 有参:n连击,默认 2 次
|
||||
map.set(
|
||||
4,
|
||||
defineCommonSerializableSpecial(4, 2, {
|
||||
name: special => `${special.value}连击`,
|
||||
desc: special => `怪物每回合攻击${special.value}次。`,
|
||||
color: '#FE7'
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
参数可以不只是数字。比如吸血用对象保存比例与是否加到自身:
|
||||
|
||||
```ts
|
||||
/** 9 - 吸血 */
|
||||
export interface ISpecial9Value {
|
||||
/** 吸血比例 */
|
||||
vampire: number;
|
||||
/** 是否加到自身生命值上 */
|
||||
add: boolean;
|
||||
}
|
||||
|
||||
map.set(
|
||||
9,
|
||||
defineCommonSerializableSpecial<ISpecial9Value>(
|
||||
9,
|
||||
{ vampire: 0, add: false },
|
||||
{
|
||||
name: '吸血',
|
||||
desc: special => `战斗前吸取勇士${special.value.vampire}%生命。`,
|
||||
color: '#DD4448'
|
||||
}
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## 自定义一个新特殊属性
|
||||
|
||||
以新增一个「每回合给勇士造成固定伤害」的属性为例(假设使用 code 50):
|
||||
|
||||
1. **选一个未使用的 code**。先搜一遍 `special.ts` 中已有的 `map.set(...)`,确认没有冲突。
|
||||
2. **有参数时定义参数接口**。在文件顶部 `//#region 复合属性值类型` 内声明 `ISpecial50Value`,并在接口前写 `/** 50 - 属性名 */`。
|
||||
3. **在 `//#region 属性定义` 内注册**。每条定义前写 `// 50 - 属性名`,再用 `map.set(50, define...)` 添加。
|
||||
4. **实现效果**。按下方「属性实现位置」表,把效果写进对应的计算器、光环、地图伤害或钩子;如果不属于现有分类,再考虑新增对应的转换器 / 效果 / 钩子。
|
||||
5. **刷新上下文**。修改怪物模板或新增属性后,调用 `state.enemyContext.markDirty(view)` 标记脏数据,必要时调用 `state.enemyContext.buildup()` 全量重建计算后怪物。
|
||||
|
||||
```ts
|
||||
// 50 - 灼烧
|
||||
map.set(
|
||||
50,
|
||||
defineCommonSerializableSpecial(50, 0, {
|
||||
name: '灼烧',
|
||||
desc: special => `每回合对勇士造成${special.value}点伤害。`,
|
||||
color: '#F80'
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
`createSpecials(state)` 返回 `Map<number, SpecialCreation<any>>`。`initializeEnemy` 会遍历这个 Map,逐个调用 `state.enemyManager.registerSpecial(code, cons)`,所以新增属性只要放进这个 Map 就会完成注册。
|
||||
|
||||
## 属性实现位置
|
||||
|
||||
`special.ts` 顶部的注释给出了内置属性的实现分工。新增属性时按这张表找落点:
|
||||
|
||||
| 属性类型 | 实现位置 | 说明 |
|
||||
|----------|----------|------|
|
||||
| 调参类(先攻、魔攻、连击、破甲、反击、净化、固伤、仇恨、自爆等) | `src/data/enemy/calculator.ts` 的 `MainDamageCalculator.calculate` | 只改变伤害公式的结果 |
|
||||
| 坚固、模仿 | `src/data/enemy/final.ts` 的 `MainEnemyFinalEffect.apply` | 只修改怪物自身基础属性 |
|
||||
| 领域、夹击、阻击、激光、伏击 | `src/data/enemy/mapDamage.ts` | 伤害视图 + 转换器 |
|
||||
| 光环、支援、特殊光环 | `src/data/enemy/aura.ts` | 光环视图 + 转换器 |
|
||||
| 中毒的每步效果 | `src/data/enemy/hook.ts` 的 `EnemyMoveHooks.handlePoison` | 移动钩子 |
|
||||
| 中毒的瞬移效果 | `src/data/enemy/hook.ts` 的 `EnemyPathfindingHooks.handlePoison` | 寻路钩子 |
|
||||
| 衰弱 | `src/data/enemy/hook.ts` 的 `EnemyCombatHooks.onAfterCombat` | 战后钩子 + 修饰器 |
|
||||
| 重生 | `src/data/enemy/hook.ts` 的 `EnemyCombatHooks.onAfterCombat` 与 `EnemyChangeFloorHooks.onSetFloor` | 两个钩子协同 |
|
||||
|
||||
具体实现方式分别见[怪物光环](./enemy-aura.md)、[伤害计算](./enemy-calculator.md)、[地图伤害](./enemy-map-damage.md)与[状态钩子](./enemy-status-hooks.md)。
|
||||
|
||||
## 编辑器识别约定
|
||||
|
||||
`special.ts` 里的部分注释是编辑器的识别模式,删除后编辑器可能无法识别属性。新增属性时保持以下写法:
|
||||
|
||||
- 属性定义前写 `// {code} - {name}`,例如 `// 50 - 灼烧`;
|
||||
- 参数接口前写 `/** {code} - {name} */`,例如 `/** 50 - 灼烧 */`;
|
||||
- 参数接口名必须是 `ISpecial{code}Value`,例如 `ISpecial50Value`;
|
||||
- `//#region 复合属性值类型` 与 `//#region 属性定义` 这两行标记不能删除。
|
||||
|
||||
:::warning
|
||||
`special.ts` 顶部的注释明确说明这些注释会被编辑器读取。修改属性定义时不要顺手删掉 `#region` 标记或 `// {code} - {name}` 注释。
|
||||
:::
|
||||
|
||||
## 怪物比较
|
||||
|
||||
`MainEnemyComparer.compare` 负责判断两个怪物是否相同:先比较 `hp`、`atk`、`def`、`money`、`exp`、`point`,再逐个比较特殊属性的 `deepEqualsTo`。它用于怪物模板的脏数据判断。一般不需要改;只有当你希望「两个怪物是否视为同一模板」的判定标准发生变化时,才替换比较器。
|
||||
Loading…
Reference in New Issue
Block a user