From 8c5e8979e13b3c47e42abd2518c7705a33ab4114 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Sun, 18 Oct 2026 14:07:25 +0800 Subject: [PATCH] =?UTF-8?q?docs(10-11):=20=E7=94=A8=E6=88=B7=E4=BF=AE?= =?UTF-8?q?=E8=AE=A2=E5=BE=AA=E7=8E=AF=20batch1=EF=BC=88=E6=88=98=E6=96=97?= =?UTF-8?q?=E6=A6=82=E5=BF=B5/=E8=AF=B4=E6=98=8E=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - combat-overview/enemy-specials: 说明上下文重建时机(惰性、一般无需手动) - enemy-final/aura/query: 补「既依赖又改外部」不提供的原因(循环依赖) - enemy-specials: getSpecial 泛型说明 + 特殊属性定义逐行注释与动态文案 - enemy-aura: 转换器/视图大白话,每条属性可对应不同转换器 - enemy-query: query() 必须调用 + 查询上下文改为只读 IReadonlyEnemyContext - 追加 10-11-READER-REVIEW.md 用户循环 batch1 记录 --- .../10-11-READER-REVIEW.md | 111 ++++++++++++++++++ docs/intermediate/combat-overview.md | 10 ++ docs/intermediate/enemy-aura.md | 6 + docs/intermediate/enemy-final.md | 8 ++ docs/intermediate/enemy-query.md | 6 +- docs/intermediate/enemy-specials.md | 50 +++++--- 6 files changed, 174 insertions(+), 17 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 e9979bc..1cedfff 100644 --- a/.planning/phases/10-docs-restructure/10-11-READER-REVIEW.md +++ b/.planning/phases/10-docs-restructure/10-11-READER-REVIEW.md @@ -187,4 +187,115 @@ - 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` → **green**,输出 `build complete`,dead link = 0。 - 结论:第 2 轮遗留的 B-03 与 R3-01..R3-04 全部消除;本轮为第 3 轮(上限),未发现新的阻断性理解问题。 +--- + +## 第 3 轮 · 复审结果(Task 3 收尾复议) + +**触发:** 第 3 轮修正 + prettier 格式化(提交 `4f4090d style(10-11): prettier 格式化中级教程文档`)之后,再次派发一个**独立 persona 子代理**(同一人格设定,逐字同 Task 1:「假设你是一个不懂代码的游戏作者,你正在阅读这个样板的使用文档,现在看到了中级教程,你了解魔塔游戏的基本概念」),让它重新通读全部 24 篇并回答「现在还有哪些看不懂的地方?」。 + +**复审结论:阻断性问题 = 0,审阅通过。** + +### 逐条复核(首轮 B-01..B-13 · 第 2 轮 R2-01..R2-06 · 第 3 轮 R3-01..R3-04) + +| # | 结论 | 说明 | +|---|------|------| +| B-01 | 已消除 | `src-tour.md`「包从哪来」讲清 `@motajs/*` / `@user/*` / `src` 三层与单向依赖,各处 `import` 有据可循。 | +| B-02 | 已消除 | `enemy-specials.md` / `enemy-map-damage.md` 说明 `map` 即 `createSpecials(state)` 返回的、最终注册的那张属性表。 | +| B-03 | 已消除 | 内置编号以 `special.ts` 为准给出 0–26 权威表;地图伤害(13 领域 / 14 夹击 / 16 阻击 / 22 激光 / 25 捕捉)在 `enemy-specials.md` 与 `enemy-map-damage.md` 两处一致,并写明「挂上对应内置属性即可生效」。 | +| B-04 | 已消除 | `enemy-map-damage.md` 的 warning 改为「今日可用 vs 待补齐」,五类地图伤害可用性明确。 | +| B-05 | 已消除 | `replay-commands.md` 补指向「包从哪来」,`packages-user` 有解释。 | +| B-06 | 已消除 | 装饰器 `@shouldReplay` / `@ignoreReplay` 前有铺垫并链到 `code-basics.md` 的「装饰器」小节。 | +| B-07 | 已消除 | `map-tiles.md` 定义「图块数字」= 内容层 `tile.jsonc` 的 `num`,并给 `state.tileStore.num/id` 换算。 | +| B-08 | 已消除 | `putMapData` 数组按行排列、高度 = `array.length / width` 已说明。 | +| B-09 | 已消除 | `ILayerStatic`(静态那半)与 `ILayerDynamic`(动态那半)已在开头解释。 | +| B-10 | 已消除 | `events.md` 说明自定义事件的类放 `src/data/event/` 下并由 `registrations.ts` 引入注册。 | +| B-11 | 已消除 | `onSetFloor` 归属统一为 `IHeroLocationHooks`,与 `IHeroStateHooks` 分族。 | +| B-12 | 已消除 | `data-plugins.md` 区分定义时的 id 数组与时机方法收到的 `IDataPlugin[]` 对象。 | +| B-13 | 已消除 | `enemy-query.md` 新增「查询上下文 ctx」小节。 | +| R2-01 | 已消除 | `BuiltInFunction` 来源 `@motajs/anon-tokyo` 已注明。 | +| R2-02 | 已消除 | 内置光环 code 23 的 `ISpecial23Value` 字段(`range`/`square`/`hpBuff`/`atkBuff`/`defBuff`)已给表。 | +| R2-03 | 已消除 | `initializeEnemy` 已在 `enemy-specials.md` / `enemy-aura.md` 介绍。 | +| R2-04 | 已消除 | `events.md` 注明参数接口应放 `src/data/event/types.ts`。 | +| R2-05 | 已消除 | `compareEquip` 已列入 `IHeroEquipment` 接口清单。 | +| R2-06 | 已消除 | code-43 示例中的 `FaceGroup.Dir4` / `dir.mapMovement()` / `IFaceHandler` / `MapDamageType.Layer` 均有注解。 | +| R3-01 | 已消除 | 删除「15 双重含义」冲突叙述,15 现在只表示仇恨。 | +| R3-02 | 已消除 | 「27 / 伏击」统一为「25 / 捕捉」。 | +| R3-03 | 已消除 | `registerCommonQueryEffect(code, effect)` 与 `registerSpecialQueryEffect(effect)` 的签名差异已解释。 | +| R3-04 | 已消除 | 「同化」描述与示例逻辑(遍历全场、数值加算)已对齐。 | + +### 非阻断 residual(仅记录,不在本计划修正) + +以下为第 3 次复议后仍存在的**非阻断**残项,均属内容层 / 编辑层或命名措辞范畴,不影响读者按文档完成操作,记录供用户判断(D-14): + +| # | 残项 | 定位 / 性质 | +|---|------|-------------| +| a | 这 24 篇内没有 `enemy.jsonc` 中「一条特殊属性条目带值长什么样」的示例 | 内容层 / 编辑器范畴,超出中级教程定位 | +| b | 查询示例的 code(40/41/42)没有像光环 / 最终效果页那样给出 `map.set` 定义 | 示例完整性 | +| c | 「最终效果 / 脚本」与「光环 / 查询」两处的优先级作用范围未完全区分 | 描述精度 | +| d | 「普通光环 / 特殊光环」命名过载(阶段名 vs code 23/26) | 命名措辞 | +| e | `data-plugins.md` 表格里的 `awake` 签名与占位示例不一致 | 占位示例 | +| f | `IEnemyDamageInfo` 与 `IEnemyDamageInfoBase` 命名漂移 | 命名措辞 | +| g | 措辞「30 及以上是示例编号」可能被读成 27–29 已被占用 | 措辞 | + +### 校验 + +- 结构门禁:24 篇全部 CRLF、单 H1(`STRUCT-OK`)。 +- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` → **green**,输出 `build complete`,dead link = 0。 +- 结论:阻断性理解问题 = 0,审阅通过;非阻断残项(a)–(g)仅记录,留待用户判断(D-14)。 + +--- + +## 用户修订循环 · 第 1 轮(用户主导,领域视角) + +> 规则见 `10-CONTEXT.md` D-46:子代理自审后,必须再经用户主导的修订循环(用户提问题 → AI 派子代理修改 → 回报 → 继续),经用户确认后才可收尾。 + +| # | 用户意见 | 主要落点 | +|---|----------|----------| +| U-01 | `content` 文件夹主要是在编辑器内修改的,对应**初级教程**,需明确说明 | `src-tour.md` | +| U-02 | 为什么有些地方获得的 `state` 主对象不包含所有层级?需解释 | `data-state-overview.md` | +| U-03 | 图块数字与图块 id 的区别、为何要区分;**新增一篇基础接口文档**简述 `TileStore` 基本概念与 `id()` / `num()` 接口 | 新文档 + `map-tiles.md` / `hero-items.md` | +| U-04 | 怎么**获取地图的指定图层**?目前只讲遍历所有图层 | `map-structure.md` | +| U-05 | 地图批量接口怎么用?说明**高级教程会详细解释** | `map-tiles.md` | +| U-06 | Flag 的**字段与字段值**区别;如何选择用哪个 | `flags.md` | +| U-07 | 怪物上下文重建**何时调用**、是否需要手动;说明样板已内置处理大部分情况,一般不需要手动重建(**不写「代码未实装」**) | 战斗各篇 | +| U-08 | 为什么不提供「既依赖外部环境、又修改外部环境」的能力——会引入**循环依赖**(依赖修改前还是修改后的环境?),要提 | `enemy-aura` / `enemy-query` / `enemy-final` | +| U-09 | `getSpecial()` 的 `T` 是什么、怎么填、可否不填 | 战斗各篇 | +| U-10 | 特殊属性定义示例代码逐行是什么意思;`name` 为何可填字符串也可填函数(代码基础衔接) | `enemy-specials.md` | +| U-11 | 光环运作方式:作者能否理解转换器/视图;需说明「每个特殊属性可对应不同的转换器」 | `enemy-aura.md` | +| U-12 | 转换器/视图代码逐行注释;哪些是模板可直接照抄、哪些需自己改 | 战斗各篇 | +| U-13 | 注册上下文:给出 `initializeEnemy` 函数体(无关内容可注释省略),说明代码放哪 | 战斗各篇 | +| U-14 | 常规查询能否用 `handler.context` 替代 `query`?必须用 `query`,否则上下文认为未查询 → 后续计算不一致(严重) | `enemy-query.md` | +| U-15 | 查询上下文是 `IReadonlyEnemyContext`(只读),文档写成 `IEnemyContext` | `enemy-query.md` | +| U-16 | 其他示例也有 U-12 的问题,全部要改 | 战斗各篇 | +| U-17 | 中级应主要教**改内置代码**而非自创新类;说明直接改内置 vs 自己写的区别;仅必须自定义的(光环/查询)说明模板 vs 自己写;钩子建议直接改 `hook/` 下内置 | 战斗各篇 + `hooks.md` | +| U-18 | 「受地形遮挡的激光」示例过复杂,换成更简单的**八方向激光** | `enemy-map-damage.md` | +| U-19 | 计算器**必须无副作用**(临界/显示/手册会多次调用);「下次战斗伤害乘二」实现错误,需修正 | `enemy-calculator.md` + `enemy-status-hooks.md` | +| U-20 | 内置事件将扩展到约 100 个(发布前完成);以**已完成视角**编写,只说明新增需要改什么 | `events.md` | +| U-21 | 录像指令会在**高级教程细讲**,要说明 | `replay-commands.md` | +| U-22 | 所有会在高级教程再细讲的内容都要说明,并**预留链接**,方便后续直接链接 | 全部 | + +--- + +## 用户循环 · batch 1 记录(战斗概念) + +**范围:** 用户修订循环第 1 轮 batch 1(U-07、U-08、U-09、U-10、U-11、U-14、U-15)。 + +### 逐条处置 + +| # | 处置 | 改动文件 | +|---|------|----------| +| U-07 | 新增「什么时候重建」小节:重建由 `enemyContext` 惰性处理,`buildup()` 为全量入口、`markDirty(view)` 为局部标记;放置/移除怪物、注册效果、绑定勇士会自动标记更新,读取计算后怪物时自动重算,一般**无需手动重建**;仅绕开正常流程直接改怪物对象时才需手动 `markDirty(view)`。**不写**「代码未实装」。同步改写 `enemy-specials.md` 自定义步骤第 5 步。 | `combat-overview.md`、`enemy-specials.md` | +| U-08 | 在三方对比后新增「为什么没有『既依赖外部、又修改外部』的能力」:说明该组合会引入**循环依赖**(无法确定结果基于改动前还是改动后的环境),故刻意不提供;`enemy-aura.md` / `enemy-query.md` 各加一句点出归类并交叉引用。 | `enemy-final.md`、`enemy-aura.md`、`enemy-query.md` | +| U-09 | 详释 `getSpecial(code)`:`T` 即该特殊属性 `value` 的类型(数字如连击 4、对象如光环 23 的 `ISpecial23Value`),给出 `getSpecial(4)` 与 `getSpecial(23)`;说明可省略,但省略后 `value` 退化为取不到具体类型的宽泛类型(需自行断言),故建议显式写 ``;链到 `code-basics` 的泛型。 | `enemy-specials.md` | +| U-10 | 为特殊属性定义示例补逐行注释(无参坚固 / 有参连击 / 对象参数吸血),并解释 `name` / `desc` / `color` 为何可写字符串或函数(函数收到该特殊属性、可用其 `value` 拼出动态文字),链到 `code-basics` 的函数一节。 | `enemy-specials.md` | +| U-11 | 在「运作方式」用大白话讲清转换器(分诊台)与视图(操作说明)的分工,并明确**每条特殊属性可对应不同转换器**、多个 code 也可共用一个转换器;内置三条光环各有转换器。 | `enemy-aura.md` | +| U-14 | 将「建议」改为**必须**调用 `query()`:直接读 `handler.context` 不会登记「查询过外部状态」,环境变化时上下文会视其为未查询、不重算,导致后续伤害等结果不一致甚至出错(严重)。 | `enemy-query.md` | +| U-15 | 查询上下文类型由 `IEnemyContext` 更正为 `IReadonlyEnemyContext`,并说明其只读是有意为之(避免查询效果依赖可变外部上下文)。 | `enemy-query.md` | + +### 校验 + +- 源码核对:`packages-user/data-base/src/enemy/types.ts`(`getSpecial(code): ISpecial | null`、`IEnemy.getModifiableEnemy`)、`packages-user/data-base/src/enemy/special.ts`(`ICommonSpecialConfig` 的 `name`/`desc`/`color: string | (special) => string`)、`packages-user/data-system/src/combat/types.ts`(`IAuraConverter` / `IAuraView` / `IEnemyAuraView`;`query: () => IEnemyContext` / `for(ctx: IEnemyContext)`;`IReadonlyEnemyContext` / `IEnemyContext`)、`packages-user/data-system/src/combat/context.ts`(`buildup()` 惰性早退、`markDirty(view)`、`requestRefresh`、`buildupQuery` 对 `queried` 的登记)。 +- 结构门禁:被改的 5 篇(`combat-overview`、`enemy-specials`、`enemy-aura`、`enemy-query`、`enemy-final`)均 CRLF、单 H1。 +- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` → **green**,输出 `build complete`,dead link = 0。 +- 备注(待用户确认):`combat/types.ts` 当前 `query: () => IEnemyContext` / `for(ctx: IEnemyContext)` 的签名仍为可写 `IEnemyContext`;U-15 已按用户口径把**文档**改为只读 `IReadonlyEnemyContext`。若需源码同步为只读,请另行派发。 diff --git a/docs/intermediate/combat-overview.md b/docs/intermediate/combat-overview.md index 7254176..c21ec21 100644 --- a/docs/intermediate/combat-overview.md +++ b/docs/intermediate/combat-overview.md @@ -18,6 +18,16 @@ state.enemyContext.buildup(); `buildup()` 会按固定顺序处理每只怪物,把所有特殊属性的作用真正写进属性里。之后伤害计算器读到的就是最终结果,而不是模板上的原始数字。 +### 什么时候重建 + +重建的时机由怪物上下文自己掌握,而且是**惰性**的:真正读取某只怪物的计算后属性时,上下文才会检查它需不需要重算。因此日常流程里,你一般**不需要**手动重建: + +- 放置或移除怪物、注册光环转换器 / 查询效果 / 最终效果、绑定勇士,都会自动把上下文标记为「需要更新」; +- `buildup()` 是全量重建的入口,会把所有怪物的光环、查询与最终效果重新算一遍;若上下文没有变化,它会直接跳过; +- `markDirty(view)` 只把某一只怪物标记为待重算,下次读取它时做局部刷新。 + +也就是说,「什么时候重建」大多由样板与引擎内部处理好了。只有当你绕过正常流程、直接改动了怪物对象(例如用可写接口拿到怪物后改属性),才需要自己对那只怪物调用 `markDirty(view)`,让改动生效。 + ### 为什么要重建 一句话:**为了拿到准确的怪物属性**。 diff --git a/docs/intermediate/enemy-aura.md b/docs/intermediate/enemy-aura.md index 1a7030c..da57354 100644 --- a/docs/intermediate/enemy-aura.md +++ b/docs/intermediate/enemy-aura.md @@ -4,6 +4,8 @@ 光环的效果作用在范围内的其他怪物身上(也可以包括自己)。它要回答两个问题:先算谁、后算谁?改基础属性还是改特殊属性?下面先从优先级讲起,再看内置光环与自定义写法。基础概念见[战斗总览](./combat-overview.md)。 +光环属于「不读取外部环境、但会修改外部」的那一类:它只按范围命中目标,不会先观察环境再决定自己。(为什么没有「既读取外部、又修改外部」的能力,见[最终属性效果](./enemy-final.md)。) + ## 优先级 每只怪物可能同时受到多个光环影响。谁先算、谁后算,会直接改变结果,所以光环用 `priority` 排队。 @@ -47,6 +49,10 @@ - **转换器** `IAuraConverter`:`shouldConvert(special, handler)` 判断某个特殊属性是否归自己处理;`convert(special, handler, context)` 返回光环视图; - **视图** `IEnemyAuraView`:描述优先级、范围、影响方式。 +用大白话说:**转换器**像一个「分诊台」——引擎拿到一条特殊属性后,会挨个问每个已注册的转换器「这条你管吗?」(`shouldConvert`),哪个回答「管」,就由它把这条属性翻译成一份**视图**;**视图**则是一张「操作说明」,写清这条光环的范围多大、优先级多少、具体要改目标的哪些属性(`apply` / `applySpecial`)。 + +因此,**每一条特殊属性都可以对应不同的转换器**,不必都挤进同一个里;反过来,只要 `shouldConvert` 认得出来,多个 code 也可以共用同一个转换器。内置的三条光环就各有各的转换器(`CommonAuraConverter` / `GuardAuraConverter` / `SpecialAuraConverter`),下面两个自定义示例也各自写了一个转换器。 + 视图的核心成员: - `priority`:优先级; diff --git a/docs/intermediate/enemy-final.md b/docs/intermediate/enemy-final.md index 493d2f3..39ee7d2 100644 --- a/docs/intermediate/enemy-final.md +++ b/docs/intermediate/enemy-final.md @@ -34,6 +34,14 @@ state.enemyContext.registerFinalEffect(effect); 最终效果可以读取勇士属性(模仿与仿攻都需要),但不会扫描周围怪物,也不会改变其他怪物。 +### 为什么没有「既依赖外部、又修改外部」的能力 + +看到这里可能会问:为什么没有一种效果,既能像查询那样读取周围环境,又能像光环那样修改其他怪物? + +因为那会引入**循环依赖**。如果一个效果既读取外部、又改动外部,就再也说不清一只怪物最终的结果是根据**改动前**的环境、还是**改动后**的环境算出来的——而环境本身又被这次效果改掉了,前后互相牵扯,结果无法稳定确定。 + +所以上表里「读取外部」与「影响外部」同时为「是」的那一格被刻意留空:需要「读外部之后改变自己的结果」,就用查询;需要「影响其他怪物」,就用光环。两者分开、按固定阶段执行,循环依赖就不会出现。 + ## 示例:仿攻 「仿攻」让怪物的攻击力等于勇士的最终攻击力。仿攻与内置的模仿类似,区别是只改攻击、不改防御(这里的 code 32 是示例自选的未占用编号;内置的最终效果是坚固 code 3、模仿 code 8,内置编号见[特殊属性](./enemy-specials.md)): diff --git a/docs/intermediate/enemy-query.md b/docs/intermediate/enemy-query.md index 2972bb7..d5e44bd 100644 --- a/docs/intermediate/enemy-query.md +++ b/docs/intermediate/enemy-query.md @@ -21,7 +21,7 @@ state.enemyContext.registerCommonQueryEffect(code, effect); - `priority`:优先级; - `apply(handler, special, query)`:处理逻辑。`handler.enemy` 是正在计算的怪物,`special` 是触发查询的特殊属性,`query()` 返回怪物上下文。 -`query()` 不只是取上下文,它还告诉上下文「这个怪物依赖外部状态」。调用后,周围怪物或位置变化时,上下文会把这个怪物标记为需要重新查询。建议始终通过 `query()` 取上下文,而不是直接读 `handler.context`。 +`query()` 不只是取上下文,它还告诉上下文「这个怪物依赖外部状态」。**常规查询里必须调用 `query()`**:直接读 `handler.context` 虽然也能拿到同一个上下文对象,却不会把「这只怪物查询过外部状态」这件事登记下来。于是当周围怪物或位置发生变化时,上下文会认为这只怪物「没有进行过查询」,不会重新计算它,导致后续的伤害等计算结果不一致、甚至出错——这是个很容易踩、后果又严重的坑。因此请始终通过 `query()` 取上下文,不要用 `handler.context` 代替。 ## 特殊查询(SpecialQuery) @@ -51,9 +51,11 @@ state.enemyContext.registerSpecialQueryEffect(effect); 常规查询与特殊查询的 `priority` 和光环共用同一套优先级,从高到低处理。高优先级的效果先执行,低优先级能看到它留下的结果;反过来不行。查询新增的光环只能影响之后的阶段,规则与[怪物光环](./enemy-aura.md)一致。 +查询属于「依赖外部、但只改自己」的那一类;「既依赖外部、又修改外部」的能力是刻意不提供的,原因见[最终属性效果](./enemy-final.md)。 + ## 查询上下文 ctx -常规查询里的 `query()` 与特殊查询里的 `for(ctx)`,拿到的都是**查询上下文**(类型 `IEnemyContext`),它代表「这次查询所在的怪物战场」。示例里用到它的这几个成员: +常规查询里的 `query()` 与特殊查询里的 `for(ctx)`,拿到的都是**查询上下文**(类型 `IReadonlyEnemyContext`),它代表「这次查询所在的怪物战场」。它是**只读**的,这是有意为之:查询效果应当只读取外部状态、再改自己,而不应反过来依赖可变的外部上下文,否则「谁先算、谁后算」的结果就无法确定。示例里用到它的这几个成员: - `ctx.scanRange(range, param)`:用一个范围对象扫出范围内的怪物,逐项返回 `[位置, 怪物视图]`; - `ctx.iterateEnemy()`:遍历上下文中的全部怪物,同样逐项返回 `[位置, 怪物视图]`; diff --git a/docs/intermediate/enemy-specials.md b/docs/intermediate/enemy-specials.md index 40ce2d0..575dde7 100644 --- a/docs/intermediate/enemy-specials.md +++ b/docs/intermediate/enemy-specials.md @@ -52,6 +52,13 @@ - `hasSpecial(code)`:判断是否拥有指定特殊属性; - `iterateSpecials()`:迭代全部特殊属性。 +其中 `getSpecial(code)` 的尖括号 `T` 是**这条特殊属性的参数类型**,也就是它 `value` 的类型:连击(code 4)的 `value` 是次数(`number`),光环(code 23)的 `value` 是 `ISpecial23Value` 对象。写 `` 就是在告诉引擎「我要按这个类型读它的 `value`」: + +- 参数是数字:`enemy.getSpecial(4)`,之后 `value` 就是 `number`,可以直接参与计算; +- 参数是对象:`enemy.getSpecial(23)`,之后 `value.range`、`value.hpBuff` 等字段都能直接读取。 + +`T` 也可以省略(写成 `getSpecial(4)`)。但省略后引擎推断不出具体类型,`value` 会退化为一个取不到具体类型的宽泛类型,必须自己断言之后才能当数字或对象用。因此读取已知属性时,建议把 `` 显式写出来。`T` 的写法(尖括号里填类型)就是[代码基础](./code-basics.md)里讲的泛型。 + 每个特殊属性对象实现 `ISpecial`,常用成员是 `code`、`value` 与 `getDescription()`: ```ts @@ -80,27 +87,38 @@ if (enemy.hasSpecial(3)) { `config` 的 `name`、`desc`、`color` 都可以写成字符串,也可以写成 `(special) => string` 的函数。需要根据参数变化显示不同文字时,就写成函数: ```ts -// 无参:坚固 +// 无参:坚固。用 defineNonePropertySpecial 定义,value 为空,所以不需要默认值 map.set( - 3, + 3, // 往属性表里登记 code 3 defineNonePropertySpecial(3, { - name: '坚固', - desc: '怪物防御不小于勇士攻击-1。', - color: '#C0B088' + name: '坚固', // 名称:固定字符串,直接显示 + desc: '怪物防御不小于勇士攻击-1。', // 描述:固定字符串 + color: '#C0B088' // 名称显示颜色 }) ); -// 有参:n连击,默认 2 次 +// 有参:n连击。用 defineCommonSerializableSpecial 定义 map.set( 4, - defineCommonSerializableSpecial(4, 2, { - name: special => `${special.value}连击`, - desc: special => `怪物每回合攻击${special.value}次。`, - color: '#FE7' - }) + defineCommonSerializableSpecial( + 4, // code + 2, // value 的默认值:默认每回合攻击 2 次 + { + name: special => `${special.value}连击`, // 名称写成函数:按 value 拼出「2连击」 + desc: special => `怪物每回合攻击${special.value}次。`, // 描述随 value 变化 + color: '#FE7' + } + ) ); ``` +`name`、`desc`、`color` 既可以直接写字符串,也可以写成 `(special) => string` 的函数,区别在于文字是否要随参数变化: + +- 写字符串时,这段文字是固定的,例如坚固永远显示「坚固」; +- 写函数时,引擎会在需要显示时把这条特殊属性对象传进来(就是参数 `special`),你就能用它的 `value` 拼出动态文字——上面的连击在 `value` 为 2 时显示「2连击」,为 3 时显示「3连击」。 + +函数这种「接收参数、返回结果」的写法,见[代码基础](./code-basics.md)的函数一节。 + 参数可以不只是数字。比如吸血用对象保存比例与是否加到自身: ```ts @@ -114,11 +132,13 @@ export interface ISpecial9Value { map.set( 9, + // 参数是对象时,把类型写进尖括号,value 默认值也是一个对象 defineCommonSerializableSpecial( - 9, - { vampire: 0, add: false }, + 9, // code + { vampire: 0, add: false }, // value 的默认值:不吸血 { - name: '吸血', + name: '吸血', // 固定名称 + // 描述写成函数,从对象 value 里读字段拼出文字 desc: special => `战斗前吸取勇士${special.value.vampire}%生命。`, color: '#DD4448' } @@ -134,7 +154,7 @@ map.set( 2. **有参数时定义参数接口**。在文件顶部 `//#region 复合属性值类型` 内声明 `ISpecial50Value`,并在接口前写 `/** 50 - 属性名 */`。 3. **在 `//#region 属性定义` 内注册**。每条定义前写 `// 50 - 属性名`,再用 `map.set(50, define...)` 添加。 4. **实现效果**。按下方「属性实现位置」表,把效果写进对应的计算器、光环、地图伤害或钩子;如果不属于现有分类,再考虑新增对应的转换器 / 效果 / 钩子。 -5. **刷新上下文**。修改怪物模板或新增属性后,调用 `state.enemyContext.markDirty(view)` 标记脏数据,必要时调用 `state.enemyContext.buildup()` 全量重建计算后怪物。 +5. **刷新上下文(通常无需手动)**。重建由怪物上下文惰性处理:放置 / 移除怪物、注册效果、绑定勇士都会自动标记更新,读取计算后怪物时自动重算。只有当你绕开正常流程、直接改动了怪物对象时,才需要手动 `state.enemyContext.markDirty(view)` 标记脏数据(必要时 `state.enemyContext.buildup()` 全量重建)。详见[战斗总览](./combat-overview.md)的「什么时候重建」。 ```ts // 50 - 灼烧