From 454325052f2d114f628b7f452070791c326810ab Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 19 Oct 2026 15:30:42 +0800 Subject: [PATCH] =?UTF-8?q?docs(10-11):=20=E6=8C=89=20D-48=20=E9=87=8D?= =?UTF-8?q?=E5=81=9A=E5=AE=9E=E6=93=8D=EF=BC=88=E6=88=98=E6=96=97=E7=BB=84?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 战斗组 9 篇的实操/示例改为六步(目标→改哪里→复制什么→改什么→怎么验证→注意) - 各篇代码片段去掉 import,并补统一复制说明(指向 code-basics) - 保留系统/原理讲解与 U-07..U-19 既有修正;combat-overview 无实操不改 - 追加 10-11-READER-REVIEW.md 的 batch 5 记录 --- .../10-11-READER-REVIEW.md | 31 ++++++++ docs/intermediate/enemy-aura.md | 71 +++++++++---------- docs/intermediate/enemy-calculator.md | 25 ++++--- docs/intermediate/enemy-combat-script.md | 46 ++++++------ docs/intermediate/enemy-final.md | 24 ++++--- docs/intermediate/enemy-map-damage.md | 39 +++++----- docs/intermediate/enemy-query.md | 60 +++++++++++----- docs/intermediate/enemy-specials.md | 26 ++++--- docs/intermediate/enemy-status-hooks.md | 26 +++---- 9 files changed, 206 insertions(+), 142 deletions(-) diff --git a/.planning/phases/10-docs-restructure/10-11-READER-REVIEW.md b/.planning/phases/10-docs-restructure/10-11-READER-REVIEW.md index f5657ad..fdd88e0 100644 --- a/.planning/phases/10-docs-restructure/10-11-READER-REVIEW.md +++ b/.planning/phases/10-docs-restructure/10-11-READER-REVIEW.md @@ -384,3 +384,34 @@ - 边界:仅改 `docs/intermediate/*.md`(`data-vs-render`、`src-tour` 无实操、不改);未改 `config.ts` 与任何生产代码;未新增文件;未引入指向 `docs/advanced/` 的真实链接(D-47 预留仍为代码 span + HTML 注释)。 - 未决:战斗组与「其他」组(`combat-overview`、`enemy-*`、`events`、`replay-commands`、`data-plugins`)的实操尚未按 D-48 重做,其中仍是含 `import` 的代码块;留待后续批次。 +--- + +## 用户循环 · batch 5 记录(D-48 实操·战斗) + +**范围:** 用户修订循环第 1 轮 batch 5(D-48 实操写法)——战斗组 9 篇(`combat-overview`、`enemy-specials`、`enemy-aura`、`enemy-query`、`enemy-final`、`enemy-calculator`、`enemy-map-damage`、`enemy-combat-script`、`enemy-status-hooks`)。沿用 batch 4 的口径:**保留系统/原理讲解**,只重做「实操/示例」部分,使其可分步照抄(目标 → 改哪里 → 复制什么 → 改什么 → 怎么验证 → 注意);片段一律去掉 `import`,并显式给出「顶部通常已有、不要重复粘,报缺再补」的说明(统一指向 `code-basics.md` 的「照抄教程里的代码时,怎么处理 import」)。既有的 U-07/U-08/U-09/U-10/U-11/U-13/U-14/U-15/U-17/U-18/U-19 修正全部保留。 + +### 逐条处置 + +| 文档 | 处置 | +| --- | --- | +| `combat-overview.md` | 无「实操/示例」段落(纯概念 + 流程图 + 一处 `buildup()` 单行接口引用,本就不含 `import`),本批不改。 | +| `enemy-specials.md` | 「自定义一个新特殊属性」的 5 步清单改写为六步实操;属性定义片段补 `[需改]` 标注;「两类定义」「内置属性编号表」等系统讲解保留。 | +| `enemy-aura.md` | 两个示例(基础光环 / 特殊光环)各改写为六步实操;删去两个代码块的 `import`,补复制说明;优先级、运作方式、「改内置还是自己写」等系统讲解保留。 | +| `enemy-query.md` | 三个示例(抱团 / 同化 / 号角)各改写为六步实操;删去三处 `import`,补复制说明;`ctx` 说明、warning、优先级、签名差异说明等保留。 | +| `enemy-final.md` | 「示例:仿攻」改写为六步实操;删去 `import`,补复制说明;「注册在哪里」(U-13)保留。 | +| `enemy-calculator.md` | 「示例:等差数列属性」改写为六步实操;删去 `import`,补复制说明;「计算器必须无副作用」(U-19)warning 与内置公式逐段导读保留。 | +| `enemy-map-damage.md` | 「示例:八方向激光」(U-18)改写为六步实操;删去 `import`,补复制说明;五类内置表、编号/结算说明、`handleMapDamage` 结算段保留。 | +| `enemy-combat-script.md` | 战前 / 战后两个示例各改写为六步实操;删去两处 `import`,补复制说明;「脚本 vs 钩子」选型表与「注册在哪里」保留。 | +| `enemy-status-hooks.md` | 「下次战斗伤害乘二」示例改写为六步实操(一处「复制什么」统领「属性定义 + 钩子 + 脚本」三个片段,第三步计算器只读片段单列);删去两处 `import`;按 U-19 保留「钩子挂状态 → 计算器只读 flag → 战斗脚本每场消费一次」的三系统结构(消费必须每场只跑一次,属 D-48「确实需要」的额外脚本)。 | + +### 共用复制说明(各篇统一) + +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 `code-basics.md` 的「照抄教程里的代码时,怎么处理 import」)。 + +### 校验 + +- 结构门禁:24 篇全部 CRLF、单 H1(`STRUCT-OK`)。 +- 格式门禁:`node node_modules/prettier/bin/prettier.cjs --write`(D-45)已跑,改动篇目全部 `unchanged`(内容本就符合 prettier)。 +- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` → **green**,输出 `build complete`,dead link = 0。 +- 边界:仅改 `docs/intermediate/*.md`(`combat-overview` 无实操、不改);未改 `config.ts` 与任何生产代码;未新增文件;未引入指向 `docs/advanced/` 的真实链接(D-47 预留仍为代码 span + HTML 注释);未引入 `dev.md` 引擎开发约定,未写发布前/数量类信息。 + diff --git a/docs/intermediate/enemy-aura.md b/docs/intermediate/enemy-aura.md index 004052d..61c3675 100644 --- a/docs/intermediate/enemy-aura.md +++ b/docs/intermediate/enemy-aura.md @@ -79,11 +79,17 @@ - **转换器**:只有 `shouldConvert` 与 `convert` 两个方法,负责「认领」特殊属性并构造视图。`shouldConvert` 通常照抄、只换一个 code;`convert` 里构造视图那一行要按你的视图构造函数改。 - **视图**:真正描述效果的地方(范围、优先级、`apply` / `applySpecial`),这部分必须按你的需求写。 -下面两个示例用 `[模板]` 标出可原样照抄的行(多为依赖导入、接口签名、`shouldConvert`),用 `[需改]` 标出必须按需求改写的行(code、范围、优先级,以及 `apply` / `applySpecial` 里做的事)。 +下面两个示例用 `[模板]` 标出可原样照抄的行(多为接口签名、`shouldConvert`、范围对象的构建),用 `[需改]` 标出必须按需求改写的行(code、范围、优先级,以及 `apply` / `applySpecial` 里做的事)。 ## 示例:基础光环(减少周围怪物的金币与经验) -先从一个只改基础属性的例子开始:让范围内的怪物金币与经验下降。第一步是定义一条带参数的特殊属性(code 取一个未占用的值,这里用 30): +先从一个只改基础属性的例子开始:让范围内的怪物金币与经验下降。它用 code 30(示例自选的未占用号)定义一条带参数的特殊属性,再配一个转换器与视图。 + +**目标**:站在带「削弱光环」怪物周围的怪物,金币与经验按参数下降。 + +**改哪里**:`src/data/enemy/special.ts` 登记属性;另建文件写转换器与视图;`src/data/enemy/initialize.ts` 注册(见文末「注册在哪里」)。 + +**复制什么**:先在 `special.ts` 登记属性—— ```ts // [需改] 在 src/data/enemy/special.ts 里新增这条属性定义(编号换成你没用过的) @@ -111,29 +117,11 @@ map.set( ); ``` -然后写转换器与视图: +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + +再写转换器与视图—— ```ts -// [模板] 依赖导入:@motajs/common 提供范围对象,其余来自用户包 -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(); @@ -227,17 +215,27 @@ export class WeakAura implements IEnemyAuraView< } ``` -注册: +最后把注册行加进 `initializeEnemy`(`src/data/enemy/initialize.ts`,见文末「注册在哪里」): ```ts state.enemyContext.registerAuraConverter(new WeakAuraConverter()); ``` -`apply` 里用 `baseEnemy` 的原始值做百分比,因此多个光环不会把彼此的结果继续乘进去。 +**改什么**:编号(`30`)、类名、`priority`、范围参数与 `apply` 里改的字段(本示例 `moneyBuff` / `expBuff`)换成你自己的。 + +**怎么验证**:把带该属性的怪物与另一只怪物放在范围内,看后者的金币 / 经验是否按参数下降。 + +**注意**:`apply` 里用 `baseEnemy` 的原始值做百分比,因此多个光环不会把彼此的结果继续乘进去。只改基础属性就实现 `apply`;要改目标的特殊属性才需要 `applySpecial`。 ## 示例:特殊光环(给周围怪物上光环) -如果光环要给目标「加一条特殊属性」,而不是直接改基础属性,就把逻辑放进 `applySpecial`。下面的例子给范围内没有普通光环的怪物加上 code 23 的普通光环: +如果光环要给目标「加一条特殊属性」,而不是直接改基础属性,就把逻辑放进 `applySpecial`。下面的例子给范围内没有普通光环的怪物加上 code 23 的普通光环。 + +**目标**:站在带「传染光环」怪物附近的怪物,若自己没有普通光环,就获得一层 code 23 的普通光环。 + +**改哪里**:`src/data/enemy/special.ts` 登记属性;另建文件写转换器与视图;`src/data/enemy/initialize.ts` 注册(见文末「注册在哪里」)。 + +**复制什么**:先在 `special.ts` 登记属性—— ```ts // [需改] 这条无参属性定义同样加进 src/data/enemy/special.ts @@ -252,18 +250,11 @@ map.set( ); ``` -```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'; +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 +再写转换器与视图—— + +```ts // [需改] 类名:换成你的修饰器名;它负责「给目标增删改特殊属性」 export class ChainAuraModifier implements IEnemySpecialModifier { // [模板] 构造函数存下来源属性 @@ -351,9 +342,11 @@ export class ChainAura implements IEnemyAuraView { } ``` -这里 `priority = 25` 高于内置特殊光环的 20,所以它在特殊光环之前执行;它新加上的普通光环(priority 10)会在之后的阶段生效。 +**改什么**:编号(`31`)、类名、`priority`、范围与 `add` 里设置的参数(本示例的 `range` / `hpBuff` 等光环字段)换成你自己的。 -不要把 `couldApplySpecial` 设为 `false` 后又指望 `applySpecial` 被调用:上下文只会对 `couldApplySpecial === true` 的光环调用它。 +**怎么验证**:把带该属性的怪物与一只没有普通光环的怪物放在范围内,看后者是否被加上了普通光环(属性提升)。 + +**注意**:`priority = 25` 高于内置特殊光环的 20,所以它在特殊光环之前执行;它新加上的普通光环(priority 10)会在之后的阶段生效。不要把 `couldApplySpecial` 设为 `false` 后又指望 `applySpecial` 被调用:上下文只会对 `couldApplySpecial === true` 的光环调用它。 ## 注册与全局光环 diff --git a/docs/intermediate/enemy-calculator.md b/docs/intermediate/enemy-calculator.md index b49d0ce..de8405a 100644 --- a/docs/intermediate/enemy-calculator.md +++ b/docs/intermediate/enemy-calculator.md @@ -70,18 +70,15 @@ state.enemyContext.getDamageSystem()?.useCalculator(new MainDamageCalculator()); 「等差数列属性」指怪物攻击每回合按固定值递增。属性本身在一次计算中是常量,所以不能靠给 `atk` 加值实现;要在 `calculate` 里把「每回合伤害」按等差数列求和: -示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」。下面用 `[模板]` 标出可原样照抄的行,用 `[需改]` 标出必须按需求修改的行。 +示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」。下面用 `[模板]` 标出可原样照抄的行,用 `[需改]` 标出必须按需求改的行。 + +**目标**:让怪物的每回合伤害按固定公差逐回合递增(本示例公差为 5)。 + +**改哪里**:`src/data/enemy/calculator.ts` 的 `MainDamageCalculator.calculate`(改内置),或另建文件写一个计算器再用 `useCalculator` 替换。 + +**复制什么**:下面是只保留本示例所需分支的最小计算器—— ```ts -// [模板] 依赖导入:类型分别来自 @user/data-common 与 @user/data-system -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { - CriticalableHeroStatus, - IDamageCalculator, - IEnemyDamageInfoBase, - IReadonlyEnemyHandler -} from '@user/data-system'; - // [需改] 类名:换成你自己的计算器名 export class ArithmeticCalculator implements IDamageCalculator< IEnemyAttr, @@ -127,7 +124,13 @@ export class ArithmeticCalculator implements IDamageCalculator< } ``` -这是只保留本示例所需分支的最小计算器;内置公式里的无敌、吸血、支援等分支需要你按上文顺序自行保留。 +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + +**改什么**:`step`(公差,本示例为 `5`)与 `base` 的算法换成你要的递增规则;换算成「改内置」时,把这段逻辑并入 `calculate` 的对应段落即可。 + +**怎么验证**:给怪物挂上后开战,看显示的回合伤害是否随回合数按公差递增、总伤害是否等于求和结果。 + +**注意**:这是只保留本示例分支的最小计算器;若用 `useCalculator` 替换,内置的无敌、吸血、支援等分支需要你按上文顺序自行保留。计算器**必须无副作用**(见上方 warning)。 ## 临界 diff --git a/docs/intermediate/enemy-combat-script.md b/docs/intermediate/enemy-combat-script.md index 463ff07..f73cd67 100644 --- a/docs/intermediate/enemy-combat-script.md +++ b/docs/intermediate/enemy-combat-script.md @@ -59,17 +59,15 @@ state.combat.addCombatScript(new MainCombatScript()); ## 战前示例:拥有某特殊属性时无法战斗 -下面的脚本检查怪物是否拥有 code 18 的特殊属性(无敌,编号见[特殊属性](./enemy-specials.md)的「内置属性编号表」),有则直接放弃战斗(示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」)。代码中用 `[模板]` 标出可原样照抄的行,用 `[需改]` 标出必须按需求修改的行。 +下面的脚本检查怪物是否拥有 code 18 的特殊属性(无敌,编号见[特殊属性](./enemy-specials.md)的「内置属性编号表」),有则直接放弃战斗(示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」)。代码中用 `[模板]` 标出可原样照抄的行,用 `[需改]` 标出必须按需求改的行。 + +**目标**:带「无敌」(code 18)的怪物不能被打,点它时直接放弃战斗并给出提示。 + +**改哪里**:另建文件写这个脚本类;`src/data/enemy/initialize.ts` 用 `addCombatScript` 注册(见文末「注册在哪里」)。 + +**复制什么**: ```ts -// [模板] 依赖导入:类型分别来自 @user/data-common 与 @user/data-system -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { - ICombatFlowHandler, - ICombatScript, - IEnemyDamageInfo -} from '@user/data-system'; - // [需改] 类名:换成你的脚本名 export class SealCombatScript implements ICombatScript { // [需改] priority:越大越先执行;内置脚本是 0 @@ -99,23 +97,25 @@ export class SealCombatScript implements ICombatScript { state.combat.addCombatScript(new SealCombatScript()); ``` -`priority = 100` 让它先于内置脚本执行;返回 `false` 后,后续战前脚本、战后脚本与 `onAfterCombat` 钩子都不会执行。 +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 -这里的 18 号在内置属性编号表里就是「无敌」,地图伤害的转换器也使用同一套编号(阻击是 16),所以这个判断与其他系统没有编号冲突。 +**改什么**:`priority`(本示例 `100`,越大越先执行,内置脚本是 `0`)、`hasSpecial(18)` 的判断与提示文字换成你自己的。 + +**怎么验证**:给一只怪物挂上「无敌」后点它开战,应看到提示、且勇士不入场、战斗不结算。 + +**注意**:`before` 一旦返回 `false`,后续战前脚本、战后脚本与 `onAfterCombat` 钩子都不会执行。这里的 18 号在内置属性编号表里就是「无敌」,地图伤害使用同一套编号(阻击是 16),所以这个判断与其他系统没有编号冲突。 ## 战后示例:战后玩家回复固定量生命值 下面的脚本在每场战斗后给勇士回复 20 点生命: -```ts -// [模板] 依赖导入 -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { - ICombatFlowHandler, - ICombatScript, - IEnemyDamageInfo -} from '@user/data-system'; +**目标**:每场战斗结束后,给勇士回复固定量生命(本示例 20 点)。 +**改哪里**:另建文件写这个脚本类;`src/data/enemy/initialize.ts` 用 `addCombatScript` 注册(见文末「注册在哪里」)。 + +**复制什么**: + +```ts // [需改] 类名:换成你的脚本名 export class HealAfterCombatScript implements ICombatScript< IEnemyAttr, @@ -143,9 +143,13 @@ export class HealAfterCombatScript implements ICombatScript< state.combat.addCombatScript(new HealAfterCombatScript()); ``` -`priority = -10` 让它在内置脚本之后执行,避免在奖励结算之前改动勇士状态。`handler.hero` 是可修改对象,直接 `add` 即可。 +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 -`priority` 只需要记住一句:数值越大越先执行;战前与战后各自按这个顺序遍历脚本列表。 +**改什么**:`priority`(本示例 `-10`,设成负值排在内置脚本之后)、回复量 `20` 与回复的属性换成你自己的。 + +**怎么验证**:打完一场战斗,看勇士生命是否回复了设定量,且奖励结算仍正常。 + +**注意**:`handler.hero` 是可修改对象,直接 `add` 即可。`priority` 越大越先执行;战前与战后各自按这个顺序遍历脚本列表。把 `priority` 设成负值可在内置脚本之后执行,避免在奖励结算前改动勇士状态。 ## 注册在哪里 diff --git a/docs/intermediate/enemy-final.md b/docs/intermediate/enemy-final.md index a18fb82..6608ede 100644 --- a/docs/intermediate/enemy-final.md +++ b/docs/intermediate/enemy-final.md @@ -53,11 +53,15 @@ state.enemyContext.registerFinalEffect(effect); ## 示例:仿攻 -「仿攻」让怪物的攻击力等于勇士的最终攻击力。仿攻与内置的模仿类似,区别是只改攻击、不改防御(这里的 code 32 是示例自选的未占用编号;内置的最终效果是坚固 code 3、模仿 code 8,内置编号见[特殊属性](./enemy-specials.md)): +「仿攻」让怪物的攻击力等于勇士的最终攻击力。仿攻与内置的模仿类似,区别是只改攻击、不改防御(这里的 code 32 是示例自选的未占用编号;内置的最终效果是坚固 code 3、模仿 code 8,内置编号见[特殊属性](./enemy-specials.md))。 -示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」。 +示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」。下面代码用 `[模板]` 标出可原样照抄的行,用 `[需改]` 标出必须按需求改的行。 -下面代码中用 `[模板]` 标出可以原样照抄的部分,用 `[需改]` 标出必须按你的需求修改的部分。特殊属性定义写在 `src/data/enemy/special.ts`: +**目标**:让带「仿攻」(code 32)的怪物,攻击力始终等于勇士的最终攻击力。 + +**改哪里**:`src/data/enemy/special.ts` 登记一条属性;另建一个文件放最终效果的类;`src/data/enemy/initialize.ts` 注册这个效果(见文末「注册在哪里」)。 + +**复制什么**:先在 `special.ts` 里登记属性—— ```ts // special.ts:32 - 仿攻 @@ -72,13 +76,11 @@ map.set( ); ``` -然后写最终效果本身: +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + +再写最终效果的类—— ```ts -// [模板] 依赖导入:类型分别来自 @user/data-common 与 @user/data-system -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { IEnemyFinalEffect, IEnemyHandler } from '@user/data-system'; - // [需改] 类名:换成你自己的效果名 export class AttackCopyFinal implements IEnemyFinalEffect< IEnemyAttr, @@ -103,9 +105,11 @@ export class AttackCopyFinal implements IEnemyFinalEffect< state.enemyContext.registerFinalEffect(new AttackCopyFinal()); ``` -`apply` 里直接 `setAttribute`,因为最终阶段拿到的 `handler.enemy` 就是可修改的怪物对象。最终阶段之后不再有别的属性修正,所以这里写进去的值会直接进入伤害计算。 +**改什么**:编号(`32`)、名称、描述与颜色换成你自己的;`hasSpecial(32)` 的触发条件与 `setAttribute` 里的效果换成你要的逻辑。 -`priority` 只需要记住一句:数值越大越先执行;内置最终效果的 `priority` 是 `0`。 +**怎么验证**:给一只怪物挂上「仿攻」后重建并开战,看它显示的攻击是否等于勇士当前最终攻击(勇士换装备后应同步变化)。 + +**注意**:`apply` 里 `handler.enemy` 已是可修改对象,直接 `setAttribute` 即可;最终阶段之后不再有别的属性修正,这里写进去的值会直接进入伤害计算。`priority` 越大越先执行,内置最终效果的 `priority` 是 `0`。 ### 注册在哪里 diff --git a/docs/intermediate/enemy-map-damage.md b/docs/intermediate/enemy-map-damage.md index 5a1abde..d538e58 100644 --- a/docs/intermediate/enemy-map-damage.md +++ b/docs/intermediate/enemy-map-damage.md @@ -64,7 +64,13 @@ mapDamage?.useConverter(new MainMapDamageConverter()); `useConverter` 是**替换**当前转换器:选了自己的就把内置的整个换掉,若要同时保留内置五类,需要在自己的转换器里把它们的视图也收集进去,或直接改内置转换器。 -下面代码用 `[模板]` 标出可原样照抄的行,用 `[需改]` 标出必须按需求修改的行。先在 `src/data/enemy/special.ts` 登记这条属性: +下面代码用 `[模板]` 标出可原样照抄的行,用 `[需改]` 标出必须按需求改的行。 + +**目标**:新增一个「八方向激光」特殊属性(示例 code 43),沿上下左右加四个斜向共八个方向发射,其余与内置激光一致。 + +**改哪里**:`src/data/enemy/special.ts` 登记属性;另建文件写伤害视图与转换器;`initializeEnemy` 里用 `useConverter` 替换(见下方「注册在哪里」)。 + +**复制什么**:先在 `src/data/enemy/special.ts` 登记这条属性—— ```ts /** 43 - 八方向激光 */ @@ -88,30 +94,11 @@ map.set( ); ``` -再写伤害视图与转换器: +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + +再写伤害视图与转换器—— ```ts -// [模板] 依赖导入:类型分别来自 @motajs/common、@user/data-system、@user/data-base 与 @user/data-common -import { - IFaceDescriptor, - IFaceHandler, - IRange, - IRayRangeParam, - ITileLocator, - RayRange -} from '@motajs/common'; -import { - IEnemyContext, - IMapDamageConverter, - IMapDamageInfo, - IMapDamageView, - IReadonlyEnemyHandler -} from '@user/data-system'; -import { ISpecial } from '@user/data-base'; -import { IEnemyAttr, IHeroAttr, FaceGroup } from '@user/data-common'; -import { MapDamageType } from './types'; -import { ISpecial43Value } from './special'; - // [模板] 范围对象可以复用,在模块顶层建一次即可 const RAY_RANGE = new RayRange(); @@ -198,6 +185,12 @@ const mapDamage = state.enemyContext.getMapDamage(); mapDamage?.useConverter(new EightDirLaserConverter()); ``` +**改什么**:编号(`43`)、类名、方向组(本示例 `FaceGroup.Dir8`)与 `getDamageWithoutCheck` 返回的伤害 / 类型换成你自己的。 + +**怎么验证**:给带该属性的怪物挂上后,站在它八个方向任一格的勇士都应受到伤害;斜向也要覆盖到(这正是与内置四方向激光的区别)。 + +**注意**:`useConverter` 是**替换**当前转换器——选了自己的就把内置的整个换掉;要同时保留内置五类,需要在自己的转换器里把它们的视图也收集进去,或直接改内置转换器。 + ### 注册在哪里 `useConverter` 要写进 `initializeEnemy`。下面是 `src/data/enemy/initialize.ts` 中该函数的样子(省略了无关的行): diff --git a/docs/intermediate/enemy-query.md b/docs/intermediate/enemy-query.md index f612847..71c7193 100644 --- a/docs/intermediate/enemy-query.md +++ b/docs/intermediate/enemy-query.md @@ -74,14 +74,15 @@ state.enemyContext.registerSpecialQueryEffect(effect); ## 示例:抱团 -「抱团」的规则是:范围内每有一个带同种特殊属性的怪物,自身攻击力就增加固定值。它只需要读周围、改自己的攻击,所以用常规查询实现(示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」): +「抱团」的规则是:范围内每有一个带同种特殊属性的怪物,自身攻击力就增加固定值。它只需要读周围、改自己的攻击,所以用常规查询实现(示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」)。 + +**目标**:带「抱团」(示例 code 40)的怪物,范围内每多一个同种特殊属性的怪物,自身攻击加固定值。 + +**改哪里**:另建文件写这个效果对象;`src/data/enemy/initialize.ts` 里用 `registerCommonQueryEffect` 注册(见文末「注册在哪里」)。 + +**复制什么**: ```ts -// [模板] 依赖导入 -import { ManhattanRange } from '@motajs/common'; -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { IEnemyCommonQueryEffect } from '@user/data-system'; - // [需改] 变量名:这是一个常规查询效果对象 const effect: IEnemyCommonQueryEffect = { // [需改] priority:与光环共用一套优先级,越大越先执行 @@ -125,18 +126,27 @@ const effect: IEnemyCommonQueryEffect = { state.enemyContext.registerCommonQueryEffect(40, effect); ``` +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + +**改什么**:`priority`、触发用的 `special.code` 与注册时的 code(本示例 40)、`radius` / `atk` 的读法换成你自己的。 + +**怎么验证**:给带该属性的怪物周围放上同类怪物,看它的攻击是否随同类数量按 `atk` 递增。 + +**注意**:`apply` 里必须用 `query()` 取上下文(直接读 `handler.context` 不会被登记为「查询过」,见上文)。 + 这个需求如果改用光环实现会麻烦得多:光环只能逐个目标施加减益或增益,很难先统计「范围内有多少个同类」再一次性加攻。查询可以在一次 `apply` 里完成扫描与累加。 ## 示例:同化 -「同化」的规则是:把场上其他怪物的特殊属性搬到自己身上——自己还没有的属性直接照抄(先按位置索引从小到大排序,靠前位置的属性先被抄过来);自己已经有的数值型属性则与场上其他怪物的同一属性加算叠加。它要增删、改写自己拥有的特殊属性,所以用特殊查询(示例用 `ctx.iterateEnemy()` 遍历全场;若要限定范围,把遍历换成 `ctx.scanRange`): +「同化」的规则是:把场上其他怪物的特殊属性搬到自己身上——自己还没有的属性直接照抄(先按位置索引从小到大排序,靠前位置的属性先被抄过来);自己已经有的数值型属性则与场上其他怪物的同一属性加算叠加。它要增删、改写自己拥有的特殊属性,所以用特殊查询(示例用 `ctx.iterateEnemy()` 遍历全场;若要限定范围,把遍历换成 `ctx.scanRange`)。 + +**目标**:带「同化」(示例 code 41)的怪物,把其他怪物的特殊属性搬到自己身上(没有的照抄,数值型的叠加)。 + +**改哪里**:另建文件写这个效果对象;`src/data/enemy/initialize.ts` 里用 `registerSpecialQueryEffect` 注册(见文末「注册在哪里」)。 + +**复制什么**: ```ts -// [模板] 依赖导入 -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { IEnemySpecialQueryEffect } from '@user/data-system'; -import { ISpecial } from '@user/data-base'; - // [需改] 变量名:特殊查询效果对象 const effect: IEnemySpecialQueryEffect = { priority: 15, // [需改] 优先级 @@ -202,17 +212,25 @@ const effect: IEnemySpecialQueryEffect = { state.enemyContext.registerSpecialQueryEffect(effect); ``` -`add` 负责处理「自己还没有的属性」,`modify` 负责处理「自己已经有、需要叠加数值」的属性。两者配合,就能把范围内怪物的特殊属性合并到自身。 +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + +**改什么**:`priority`、`shouldQuery` 里的判断(本示例 `hasSpecial(41)`)、`add` / `modify` 的合并逻辑换成你自己的。 + +**怎么验证**:给一只怪物挂上「同化」,让它周围有其他带特殊属性的怪物,看它是否照抄了别人的属性、数值型是否叠加。 + +**注意**:`add` 与 `delete` 不要同时返回非空结果(见上文);`add` 负责处理「自己还没有的属性」,`modify` 负责处理「自己已经有、需要叠加数值」的属性。两者配合,就能把范围内怪物的特殊属性合并到自身。 ## 示例:号角 「号角」的规则是:场上每有一只怪物,自身就获得一份加成。它不关心范围与同类,用常规查询遍历全场即可: -```ts -// [模板] 依赖导入 -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { IEnemyCommonQueryEffect } from '@user/data-system'; +**目标**:带「号角」(示例 code 42)的怪物,场上每有一只怪物就给自身加一份攻击。 +**改哪里**:另建文件写这个效果对象;`src/data/enemy/initialize.ts` 里用 `registerCommonQueryEffect` 注册(见文末「注册在哪里」)。 + +**复制什么**: + +```ts // [需改] 变量名:同样是常规查询效果对象 const effect: IEnemyCommonQueryEffect = { priority: 5, // [需改] 优先级 @@ -234,6 +252,14 @@ const effect: IEnemyCommonQueryEffect = { state.enemyContext.registerCommonQueryEffect(42, effect); ``` +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + +**改什么**:`priority`、注册时的 code(本示例 42)、`special.value` 的用法换成你自己的。 + +**怎么验证**:把怪物放到场上(场上有其他怪物),看它的攻击是否随全场怪物数量递增。 + +**注意**:`apply` 里同样必须用 `query()` 取上下文。 + 三个示例分别覆盖了「按范围统计同类」「合并特殊属性」「遍历全场计数」三种典型用法。 ## 注册在哪里 diff --git a/docs/intermediate/enemy-specials.md b/docs/intermediate/enemy-specials.md index 12573bf..aee2b14 100644 --- a/docs/intermediate/enemy-specials.md +++ b/docs/intermediate/enemy-specials.md @@ -152,24 +152,32 @@ map.set( 当你需要引擎里没有的机制时,就新增一条特殊属性。以「每回合给勇士造成固定伤害」为例(假设使用 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()` 全量重建)。详见[战斗总览](./combat-overview.md)的「什么时候重建」。 +**目标**:新增一条「每回合对勇士造成固定伤害」的特殊属性(示例 code 50)。 + +**改哪里**:`src/data/enemy/special.ts`——有参数时在顶部 `//#region 复合属性值类型` 内加参数接口,在 `//#region 属性定义` 内加 `map.set(...)`;效果实现按下方「属性实现位置」表找落点。 + +**复制什么**: ```ts // 50 - 灼烧 map.set( - 50, + 50, // [需改] 编号 defineCommonSerializableSpecial(50, 0, { - name: '灼烧', - desc: special => `每回合对勇士造成${special.value}点伤害。`, - color: '#F80' + name: '灼烧', // [需改] 名称 + desc: special => `每回合对勇士造成${special.value}点伤害。`, // [需改] 说明 + color: '#F80' // [需改] 颜色 }) ); ``` +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + +**改什么**:先搜一遍 `special.ts` 中已有的 `map.set(...)`,确认这个 code 没被占用;再把编号、名称、默认值与颜色换成你自己的。有参数且参数是对象时,还要按上面的步骤在顶部补一个 `ISpecial{code}Value` 参数接口。 + +**怎么验证**:把这条新属性挂到怪物上(在怪物数据里填上 code 50 与参数),编辑器应能识别并显示名称与描述;再按「属性实现位置」实现的效果应在战斗中生效。 + +**注意**:别破坏编辑器识别约定——`//#region` 标记、`// {code} - {name}` 与 `/** {code} - {name} */` 注释、参数接口名 `ISpecial{code}Value`(见下方「编辑器识别约定」)。刷新上下文通常无需手动:重建由怪物上下文惰性处理(放 / 移怪物、注册效果、绑定勇士都会自动标记),读取计算后怪物时自动重算;只有绕开正常流程、直接改怪物对象时才需要手动 `state.enemyContext.markDirty(view)`(必要时 `state.enemyContext.buildup()` 全量重建)。详见[战斗总览](./combat-overview.md)的「什么时候重建」。 + `createSpecials(state)` 返回 `Map>`。`initializeEnemy`(`src/data/enemy/initialize.ts`,数据端集中注册内置怪物功能的入口函数)会遍历这个 Map,逐个调用 `state.enemyManager.registerSpecial(code, cons)`。也就是说,内置属性与你自己新增的属性走的是同一套注册流程:只要把新属性放进这个 Map,它就会随 `initializeEnemy` 一起完成注册。 ## 属性实现位置 diff --git a/docs/intermediate/enemy-status-hooks.md b/docs/intermediate/enemy-status-hooks.md index 3e93267..4404f8a 100644 --- a/docs/intermediate/enemy-status-hooks.md +++ b/docs/intermediate/enemy-status-hooks.md @@ -66,7 +66,13 @@ state.hero.location.addHook(new EnemyChangeFloorHooks(state)); 不要在伤害计算器里 `deleteField` 之类地去「消费」这个 flag。计算器在一次战斗里会被反复调用(算临界、显示伤害、图鉴等),谁先调用谁就把 flag 用掉了,真正开打时反而不翻倍;而且这属于改动外部状态,违反「计算器必须无副作用」的规则(见[伤害计算](./enemy-calculator.md))。正确做法是:**计算器只读 flag**,由「一场战斗只跑一次」的**战斗脚本**去消费它。 ::: -先定义一个无参数特殊属性(code 45)。下面代码中用 `[模板]` 标出可原样照抄的行,用 `[需改]` 标出必须按需求修改的行: +本示例要串起三个系统:先用战斗钩子在战斗结束时挂上「蓄力」状态,让伤害计算器只读这个 flag 把伤害翻倍,再由战斗脚本在一次战斗后把 flag 消费掉。 + +**目标**:带「蓄力」(code 45)的怪物打完后,下一场战斗的伤害翻倍,且只翻倍一次。 + +**改哪里**:`src/data/enemy/special.ts` 登记属性;另建文件写战斗钩子与战斗脚本;`initializeEnemy` 里注册(见文末)。另外在伤害计算器里加一段只读逻辑(见第三步)。 + +**复制什么**:先定义一个无参数特殊属性(code 45)—— ```ts // special.ts:45 - 蓄力 @@ -81,14 +87,11 @@ map.set( ); ``` +> 复制说明:片段已省略 `import`;文件顶部通常已有,不要再粘。若编辑器提示缺少某个名字,再在顶部补对应的 `import`(详见 [代码基础](./code-basics.md) 的「照抄教程里的代码时,怎么处理 import」)。 + **第一步:用战斗钩子「挂上状态」。** 它不修改内置的 `EnemyCombatHooks`,而是作为另一个钩子并存(示例里的 `@motajs/*`、`@user/*` 是包名,含义见 [src 导览](./src-tour.md) 的「包从哪来」): ```ts -// [模板] 依赖导入 -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { ICoreState } from '@user/data-state'; -import { ICombatFlowHooks, IEnemyDamageInfo } from '@user/data-system'; - // [需改] 类名 export class ChargedHooks implements ICombatFlowHooks { constructor(readonly state: ICoreState) {} // [模板] 存下数据端状态,供读写 flag @@ -112,11 +115,6 @@ state.combat.addHook(new ChargedHooks(state)); **第二步:用战斗脚本「消费状态」。** 脚本的 `after` 一场战斗只执行一次,正好用来把 flag 清掉: ```ts -// [模板] 依赖导入 -import { IEnemyAttr, IHeroAttr } from '@user/data-common'; -import { ICoreState } from '@user/data-state'; -import { ICombatScript } from '@user/data-system'; - // [需改] 类名 export class ConsumeDoubleScript implements ICombatScript< IEnemyAttr, @@ -157,6 +155,10 @@ if (double) { 把这段读 flag 的逻辑放在 `calculate` 返回之前即可;伤害公式的其余部分见[伤害计算](./enemy-calculator.md)。 -这个例子体现了「一个状态要用多个系统」:`onAfterCombat` 钩子负责记录状态,计算器负责读取并翻倍,战斗脚本负责在真正的战斗里消费掉它。若把消费放进计算器,就会因计算器被反复调用而提前把 flag 用掉——只有放进「每次战斗只跑一次」的脚本里才可靠。 +**改什么**:编号(`45`)、类名、flag 名(`nextBattleDouble`)、翻倍写法与注册位置换成你自己的。 + +**怎么验证**:让带「蓄力」的怪物战斗一场,再打一场,看第二场伤害是否翻倍;连打第三场时应恢复原值(flag 已被消费)。 + +**注意**:这里要三个系统配合,缺一不可——钩子负责记录状态,计算器只读并翻倍,战斗脚本负责在真正的战斗里消费掉它。若把消费放进计算器,就会因计算器被反复调用(临界、显示、图鉴等)而提前把 flag 用掉,只有放进「每场战斗只跑一次」的脚本里才可靠。 状态里用到的临时加减或百分比,优先用修饰器实现,概念与 `save` 参数见[勇士属性](./hero-attribute.md)。