template/docs/intermediate/enemy-query.md
unanmed e13ebf71ed docs(10-04): write query, final, and calculator docs
- document query effects as new, not built into the engine
- add final effect comparison and attack-copy example
- explain damage formula, critical conventions, and arithmetic example
2026-10-17 18:44:16 +08:00

177 lines
7.1 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.

# 怪物查询
查询(query)让特殊属性在构建计算后怪物时读取上下文:周围有哪些怪物、它们带什么特殊属性、勇士在哪里。读取之后,查询可以修改怪物自身的基础属性,或增删、修改怪物的特殊属性。基础概念见[战斗总览](./combat-overview.md)。
:::warning
查询效果是**需要自己注册的新增内容**。当前 `src/data/enemy/initialize.ts` 只注册了光环转换器、最终效果、战斗脚本与钩子,没有注册任何查询效果。下面的接口与示例在你自己调用注册方法之前不会生效。
:::
## 常规查询(CommonQuery)
常规查询面向「某个特殊属性要读外部状态,然后改自己」。注册方式:
```ts
state.enemyContext.registerCommonQueryEffect(code, effect);
```
`IEnemyCommonQueryEffect` 只有两个成员:
- `priority`:优先级;
- `apply(handler, special, query)`:处理逻辑。`handler.enemy` 是正在计算的怪物,`special` 是触发查询的特殊属性,`query()` 返回怪物上下文。
`query()` 不只是取上下文,它还告诉上下文「这个怪物依赖外部状态」。调用后,周围怪物或位置变化时,上下文会把这个怪物标记为需要重新查询。建议始终通过 `query()` 取上下文,而不是直接读 `handler.context`。
## 特殊查询(SpecialQuery)
特殊查询面向「修改目标怪物拥有的特殊属性」,例如给范围内的怪物添加、删除或改写特殊属性。注册方式:
```ts
state.enemyContext.registerSpecialQueryEffect(effect);
```
`IEnemySpecialQueryEffect` 有:
- `priority`:优先级;
- `for(ctx)`:返回一个 `IEnemySpecialQueryModifier`。
修饰器包含四个方法:
- `shouldQuery(handler)`:判断当前怪物是否要执行这次查询;
- `add(handler)`:返回要添加给怪物的特殊属性数组;
- `delete(handler)`:返回要删除的特殊属性数组;
- `modify(handler, special)`:修改怪物的某个特殊属性,真正修改了返回 `true`。
`add` 与 `delete` 不应同时返回非空结果:上下文遇到这种组合会告警并放弃这次修改。
## 优先级
常规查询与特殊查询的 `priority` 和光环共用同一套优先级,从高到低处理。高优先级的效果先执行,低优先级能看到它留下的结果;反过来不行。查询新增的光环只能影响之后的阶段,规则与[怪物光环](./enemy-aura.md)一致。
## 示例:抱团
「抱团」的规则是:范围内每有一个带同种特殊属性的怪物,自身攻击力就增加固定值。用常规查询实现:
```ts
import { ManhattanRange } from '@motajs/common';
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import { IEnemyCommonQueryEffect } from '@user/data-system';
const effect: IEnemyCommonQueryEffect<IEnemyAttr, IHeroAttr> = {
priority: 10,
apply(handler, special, query) {
const { radius, atk } = special.value as {
radius: number;
atk: number;
};
const ctx = query();
const range = new ManhattanRange();
let count = 0;
for (const [locator, view] of ctx.scanRange(range, {
cx: handler.locator.x,
cy: handler.locator.y,
radius
})) {
if (
locator.x === handler.locator.x &&
locator.y === handler.locator.y
) {
continue;
}
if (view.getComputedEnemy().hasSpecial(special.code)) {
count++;
}
}
handler.enemy.addAttribute('atk', count * atk);
}
};
state.enemyContext.registerCommonQueryEffect(40, effect);
```
这个需求如果改用光环实现会麻烦得多:光环只能逐个目标施加减益或增益,很难先统计「范围内有多少个同类」再一次性加攻。查询可以在一次 `apply` 里完成扫描与累加。
## 示例:同化
「同化」的规则是:把范围内其他怪物的特殊属性复制到自己身上;数值型属性加算叠加,非数值型属性取位置索引最小的那个。用特殊查询实现:
```ts
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import { IEnemySpecialQueryEffect } from '@user/data-system';
import { ISpecial } from '@user/data-base';
const effect: IEnemySpecialQueryEffect<IEnemyAttr, IHeroAttr> = {
priority: 15,
for(ctx) {
return {
shouldQuery: handler => handler.enemy.hasSpecial(41),
add(handler) {
const result: ISpecial<any>[] = [];
const others = [...ctx.iterateEnemy()]
.filter(([, view]) => view.getComputedEnemy() !== handler.enemy)
.sort(
([a], [b]) =>
ctx.indexer.index(a.x, a.y) -
ctx.indexer.index(b.x, b.y)
);
for (const [, view] of others) {
for (const special of view.getComputedEnemy().iterateSpecials()) {
if (special.code === 41) continue;
if (handler.enemy.hasSpecial(special.code)) continue;
result.push(special.clone());
}
}
return result;
},
delete() {
return [];
},
modify(handler, special) {
if (typeof special.value !== 'number') return false;
let sum = special.value;
let found = false;
for (const [, view] of ctx.iterateEnemy()) {
const other = view
.getComputedEnemy()
.getSpecial<number>(special.code);
if (!other || typeof other.value !== 'number') continue;
sum += other.value;
found = true;
}
if (!found) return false;
special.setValue(sum);
return true;
}
};
}
};
state.enemyContext.registerSpecialQueryEffect(effect);
```
`add` 负责处理「自己还没有的属性」,`modify` 负责处理「自己已经有、需要叠加数值」的属性。两者配合,就能把范围内怪物的特殊属性合并到自身。
## 示例:号角
「号角」的规则是:场上每有一只怪物,自身就获得一份加成。它不关心范围与同类,用常规查询遍历全场即可:
```ts
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import { IEnemyCommonQueryEffect } from '@user/data-system';
const effect: IEnemyCommonQueryEffect<IEnemyAttr, IHeroAttr> = {
priority: 5,
apply(handler, special, query) {
const ctx = query();
let count = 0;
for (const _ of ctx.iterateEnemy()) {
count++;
}
handler.enemy.addAttribute('atk', count * (special.value as number));
}
};
state.enemyContext.registerCommonQueryEffect(42, effect);
```
三个示例分别覆盖了「按范围统计同类」「合并特殊属性」「遍历全场计数」三种典型用法。把注册调用放进 `initializeEnemy`,查询就会参与构建计算后怪物。