From 3ac14f87575c1f478ac1023bd5d5a99bf1504c94 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Fri, 3 Jul 2026 20:05:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8B=87=E5=A3=AB=E8=BA=AB=E4=B8=8A?= =?UTF-8?q?=E7=9A=84=E9=81=93=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dev.md | 17 +- docs/dev/hero/hero-items.md | 131 ++++++++++++++ packages-user/data-base/src/hero/index.ts | 1 + packages-user/data-base/src/hero/items.ts | 179 +++++++++++++++++++ packages-user/data-base/src/hero/state.ts | 8 +- packages-user/data-base/src/hero/types.ts | 72 ++++++++ packages-user/data-common/src/store/types.ts | 18 +- packages-user/data-state/src/legacy/item.ts | 57 +++--- prompt.md | 9 +- 9 files changed, 440 insertions(+), 52 deletions(-) create mode 100644 docs/dev/hero/hero-items.md create mode 100644 packages-user/data-base/src/hero/items.ts diff --git a/dev.md b/dev.md index 410f010..d89c11e 100644 --- a/dev.md +++ b/dev.md @@ -51,6 +51,7 @@ - **模块初始化**:如需初始化,编写一个 `createXxx` 函数,在 `index.ts` 中整合后逐级向上传递,直至顶层模块统一执行。注意,当前设计理念下,**不应该**有场景会需要这种函数。 - **不转发导出**:不允许一个文件导出不属于当前 monorepo 或当前文件夹的内容。 - **无循环引用**:不允许出现循环引用。若遇到不得不循环引用的情况,应首先反思接口设计是否存在问题。 +- **无类型导入**:除非某些极其特殊的场景,否则不得出现 `import type` 引入,全部使用普通引入。 ### 命名规则 @@ -116,7 +117,7 @@ ### 渲染端 -渲染端目前已基本制作完成,分为两层: +渲染端目前已基本制作完成,但底层架构仍需重构,目前分为两层: | 包 | 层级 | 说明 | | ---------------------- | ------ | ------------------------------------ | @@ -131,11 +132,11 @@ **Layer 1 — 数据层**:包含所有会影响游戏存档与流程的数据内容,如地图、怪物、玩家属性等。本层通过统一接口 `IStateBase` 对外暴露数据访问能力,各类数据模块均以此接口为核心组织。 -**Layer 2 — 执行层**:直接引用 Layer 1,负责产生影响游戏进程的动作,如玩家控制、战斗计算等。本层内容不会进入存档,仅通过修改 Layer 1 的数据来影响游戏状态。 +**Layer 2 — 系统层**:直接引用 Layer 1,负责产生影响游戏进程的动作,如玩家控制、战斗计算等。本层内容不会进入存档,仅通过修改 Layer 1 的数据来影响游戏状态。 -| 包 | 层级 | 说明 | -| ------------------- | ------- | ------------------------------------------------------------------------------- | -| `@user/data-common` | Layer 0 | 公共层,定义 `IDataCommon` 及公共无依赖接口 | -| `@user/data-base` | Layer 1 | 数据层,定义 `IStateBase` 及可存档游戏数据(地图、怪物、玩家属性等) | -| `@user/data-system` | Layer 2 | 执行层,定义 `ICoreState`,依赖数据层实现玩家控制、战斗计算等影响游戏进程的动作 | -| `@user/data-state` | Layer 3 | 数据端的顶层模块,一般仅用于初始化以及仅供渲染端调用的顶层模块 | +| 包 | 层级 | 说明 | +| ------------------- | ------- | -------------------------------------------------------------------------------- | +| `@user/data-common` | Layer 0 | 公共层,定义 `IDataCommon` 及公共无依赖接口 | +| `@user/data-base` | Layer 1 | 数据层,定义 `IDataBase` 及可存档游戏数据(地图、怪物、玩家属性等) | +| `@user/data-system` | Layer 2 | 系统层,定义 `IDataSystem`,依赖数据层实现玩家控制、战斗计算等影响游戏进程的动作 | +| `@user/data-state` | Layer 3 | 数据端的顶层模块,一般仅用于初始化以及仅供渲染端调用的顶层模块 | diff --git a/docs/dev/hero/hero-items.md b/docs/dev/hero/hero-items.md new file mode 100644 index 0000000..823bc1b --- /dev/null +++ b/docs/dev/hero/hero-items.md @@ -0,0 +1,131 @@ +# 需求综述 + +为勇士添加背包系统,用于存储永久道具(Constant)、消耗道具(Consumable)和装备道具(Equipment)。即捡即用道具(Pick)在 addItem/getItem 时直接执行效果,不进入背包。 + +背包系统由 `IHeroItems` 接口定义,作为 `IHeroState` 的新成员挂载。三种类型的道具分别存储在三张 `Map` 中,键为道具图块数字 `num`。背包需要支持存档读档,继承 `ISaveableContent`。 + +# 接口设计分析 + +## IHeroItemState + +### 设计意图 + +单个道具在背包中的运行时状态,存储道具的基础标识信息与当前数量。`raw` 为对 `IItemRawData` 的引用,提供 `category`、`name` 等定义数据的便捷访问,读档时通过 `state.itemStore.getData(num)` 恢复。 + +### 接口分析 + +- `IHeroItemState.id`:预期频率**中频**。道具的字符串标识符,用于通过 id 寻址时做匹配。典型使用场景:通过 id 判断是否持有某特定道具。 +- `IHeroItemState.num`:预期频率**高频**。道具的图块数字,是 Map 键及与地图关联的核心标识。典型使用场景:地图事件中检查玩家是否拥有 `num` 对应的钥匙。 +- `IHeroItemState.raw`:预期频率**高频**。道具原始定义数据引用,提供 category、name、effect 等属性。典型使用场景:显示道具名称、判断道具类型、执行道具效果。 +- `IHeroItemState.count`:预期频率**中频**。道具当前持有数量。三种可存储类型的道具均正常存储数量,不存在时视为无此道具。 + +### 预期体量 + +约 12-16 行。 + +## IHeroItemSave + +### 设计意图 + +单个道具的存档格式,仅需存储可序列化的字段(`num`、`count`),`id` 和 `raw` 在读档时通过 `itemStore` 和 `tileStore` 恢复。 + +### 预期体量 + +约 5-8 行。 + +## IHeroItemsSave + +### 设计意图 + +整个背包的存档格式,包含三种类型道具的存档数组。由于三种类型的道具分表存储,存档也对应分表。 + +### 预期体量 + +约 5-8 行。 + +## IHeroItems + +### 设计意图 + +勇士背包管理接口,作为 `IHeroState` 的新成员。提供道具的增减查用等基础操作,所有操作均支持通过 `num`(数字)或 `id`(字符串)寻址。 + +`addItem` 是核心接口,负责向勇士给予道具(`count` 可为负数以实现扣除)。`getItem` 是 `addItem(item, 1)` 的便捷写法,语义为勇士在游戏中"拾取"一个道具。`getItemState` 是查询接口,获取道具的只读状态。 + +对于 Pick 类型道具,`addItem` / `getItem` 不将其存入背包,而是直接执行 `raw.effect.pickEffect`。 + +### 接口分析 + +- `IHeroItems.addItem(item, count?)`:预期频率**中频**。向勇士添加或减少道具。若道具为 Pick 类型则立刻执行其效果;否则根据 `category` 选择对应分表,增加或减少 `count`,数量归零或负数时自动删除条目。典型使用场景:勇士在地图上走到道具格后拾取道具;事件脚本中给予或扣除玩家道具。 +- `IHeroItems.getItem(item)`:预期频率**高频**。使勇士获取一个道具,即 `addItem(item, 1)` 的另一种写法。Pick 类型立即执行效果,其他类型背包数量加一。典型使用场景:勇士走到地图道具格上触发拾取。 +- `IHeroItems.getItemState(item)`:预期频率**高频**。获取指定道具的只读状态,是所有查询操作的入口。返回 `null` 表示未持有。典型使用场景:NPC 检查玩家是否持有某任务道具;UI 显示道具详情。 +- `IHeroItems.useItem(item)`:预期频率**中频**。使用道具,仅对 Constant 和 Consumable 类型生效。调用 `raw.effect.useEffect`,Consumable 类型使用后数量减一。返回 `boolean` 表示是否使用成功。 +- `IHeroItems.itemCount(item)`:预期频率**高频**。获取道具当前持有数量,不存在时返回 0。典型使用场景:UI 中显示道具剩余数量。 + +### 预期体量 + +接口约 20-25 行。 + +实现类 `HeroItems` 约 80-100 行。内部维护三张 `Map`,提供增删查用及存档读档逻辑。 + +# 实现思路 + +## id 与 num 的统一寻址 + +所有接口参数均接受 `number | string` 类型的 `item`。当传入为数字时,直接用作 Map 键;当传入为字符串时,需要先通过 `state.tileStore.idToNumber(item)` 转换为 `num`,再用于查找。若转换结果为 `null` 则视为不存在。 + +注意:道具 `id` 与 `num` 的转换依赖 `ITileStore.idToNumber`,而非 `IItemStore`(道具本身是 tile 的一类具体属性,通过 tile 即可定位,`IItemStore` 只负责道具定义存储,不需要 id↔num 双向索引)。 + +## 道具存取流程 + +1. `addItem(item, count)`:将 `item` 解析为 `num`,调用 `state.itemStore.getData(num)` 获取 `raw`。若 `raw` 为 `null` 则忽略。若 `raw.category === ItemCategory.Pick`,执行 `raw.effect.pickEffect(raw)` 后直接返回。否则,根据 `raw.category` 选择对应分表 `Map`,若已存在条目则加大 `count`,结果 ≤ 0 则删除条目,否则创建新条目插入。 +2. `getItem(item)`:调用 `addItem(item, 1)`。 +3. `getItemState(item)`:将 `item` 解析为 `num`,通过 `state.itemStore.getCategory(num)` 获取道具类型并定位对应分表,从分表中获取条目并返回只读快照,不存在返回 `null`。 +4. `useItem(item)`:通过 `getItemState` 获取条目,若不存在或非 Constant/Consumable 类型则返回 `false`;若 `raw.effect.canUse` 为 `false` 则返回 `false`。调用 `raw.effect.useEffect`。若为 Consumable 类型,`count--`,归零时删除条目。返回 `true`。 +5. `itemCount(item)`:通过 `getItemState` 获取条目,存在则返回 `count`,不存在返回 `undefined`。 + +## 存档读档 + +- `saveState`:遍历三张分表,将每个条目提取 `{ num, count }` 形成 `IHeroItemSave`,构造 `IHeroItemsSave` 并返回。 +- `loadState`:遍历 `IHeroItemsSave` 中三种类型数组,对每条 `IHeroItemSave` 调用 `state.itemStore.getData(num)` 恢复 `raw`,重建分表。 + +# 涉及文件 + +## 需要引用的文件 + +- `@user/data-common`:引用 `ISaveableContent`、`IDataCommonExtended`、`SaveCompression`、`IItemRawData`、`ItemCategory`、`IDataCommon`。 +- `@user/data-base/hero/types.ts`:引用 `IHeroState`,向其新增 `items` 成员。 + +## 需要修改的文件 + +### `@user/data-base/hero/types.ts` + +- [x] 新增 `IHeroItemSave` 接口:包含 `readonly num: number`、`readonly count: number`。 +- [x] 新增 `IHeroItemsSave` 接口:包含 `readonly constants: readonly IHeroItemSave[]`、`readonly consumables: readonly IHeroItemSave[]`、`readonly equipments: readonly IHeroItemSave[]`。 +- [x] 新增 `IHeroItemState` 接口:包含 `readonly id: string`、`readonly num: number`、`readonly raw: IItemRawData`、`count: number`。 +- [x] 新增 `IHeroItems` 接口:继承 `ISaveableContent`、`IDataCommonExtended`,提供 `addItem`、`getItem`、`getItemState`、`useItem`、`itemCount` 方法。 +- [x] 修改 `IHeroStateSave`:新增 `readonly items: IHeroItemsSave` 成员。 +- [x] 修改 `IHeroState`:新增 `readonly items: IHeroItems` 成员。 + +### `@user/data-base/hero/items.ts`(新建) + +- [ ] 实现 `HeroItemState` 类:实现 `IHeroItemState` 接口,存储 `id`、`num`、`raw`、`count`。 +- [ ] 实现 `HeroItems` 类:实现 `IHeroItems` 接口,按功能分区(查询 / 增删 / 使用 / 存档 / 私有辅助)。 +- [ ] `HeroItems` 内部维护 `constants`、`consumables`、`equipments` 三张 `Map`。 +- [ ] 实现 `internalGetItemState` 私有方法:通过 `itemStore.getCategory` 定位分表后从中获取,返回可修改引用。 +- [ ] 实现 `getItemState`:包装 `internalGetItemState`,返回 `Readonly` 快照。 +- [ ] 实现 `addItem`:通过 `state.itemStore.getData` 获取 `raw`,Pick 类型执行效果后直接返回;其他类型按 `category` 插入对应分表,支持负数删除。 +- [ ] 实现 `getItem`:转发至 `addItem(item, 1)`。 +- [ ] 实现 `useItem`:仅对 Constant/Consumable 调用 `raw.effect.useEffect`,Consumable 减量,返回成功与否。 +- [ ] 实现 `itemCount`:通过 `getItemState` 返回 count 或 0。 +- [ ] 实现 `mapToSave` / `loadMap`:存档仅存 `{ num, count }`,读档通过 `itemStore.getData` 恢复 `raw`。 +- [ ] 实现 `saveState` / `loadState`:序列化与反序列化三张分表。 + +### `@user/data-base/hero/state.ts` + +- [ ] `HeroState` 构造函数中新增 `this.items = new HeroItems(state)`。 +- [ ] `saveState` 中新增 `items: this.items.saveState(compression)`。 +- [ ] `loadState` 中新增 `this.items.loadState(state.items, compression)`。 + +### `@user/data-base/hero/index.ts` + +- [ ] 新增 `export * from './items'` 导出。 diff --git a/packages-user/data-base/src/hero/index.ts b/packages-user/data-base/src/hero/index.ts index e381998..207b7c0 100644 --- a/packages-user/data-base/src/hero/index.ts +++ b/packages-user/data-base/src/hero/index.ts @@ -1,5 +1,6 @@ export * from './attribute'; export * from './follower'; +export * from './items'; export * from './location'; export * from './rendering'; export * from './mover'; diff --git a/packages-user/data-base/src/hero/items.ts b/packages-user/data-base/src/hero/items.ts new file mode 100644 index 0000000..683cf51 --- /dev/null +++ b/packages-user/data-base/src/hero/items.ts @@ -0,0 +1,179 @@ +import { isNil } from 'lodash-es'; +import { IDataCommon, ItemCategory } from '@user/data-common'; +import { + IHeroItems, + IHeroItemSave, + IHeroItemsSave, + IHeroItemState +} from './types'; + +export class HeroItems implements IHeroItems { + /** 永久道具 */ + private readonly constants: Map> = new Map(); + /** 消耗道具 */ + private readonly consumables: Map> = + new Map(); + /** 装备道具 */ + private readonly equipments: Map> = new Map(); + + constructor(readonly state: IDataCommon) {} + + /** + * 将 item 参数解析为 num,字符串 id 通过 tileStore 转换为 num + * @param item 道具图块数字或 id + */ + private resolveNum(item: number | string): number | null { + if (typeof item === 'number') return item; + return this.state.tileStore.idToNumber(item); + } + + /** + * 根据道具分类获取对应的分表 + * @param category 道具分类枚举 + */ + private getMap(category: ItemCategory): Map> { + switch (category) { + case ItemCategory.Constant: + return this.constants; + case ItemCategory.Consumable: + return this.consumables; + case ItemCategory.Equipment: + return this.equipments; + default: + return this.constants; + } + } + + /** + * 获取指定道具的内部状态,返回可修改引用供内部逻辑使用 + * @param item 道具图块数字或 id + */ + private internalGetItemState( + item: number | string + ): IHeroItemState | null { + const num = this.resolveNum(item); + if (isNil(num)) return null; + + const category = this.state.itemStore.getCategory(num); + const map = this.getMap(category); + return map.get(num) ?? null; + } + + getItemState( + item: number | string + ): Readonly> | null { + return this.internalGetItemState(item); + } + + itemCount(item: number | string): number { + return this.getItemState(item)?.count ?? 0; + } + + addItem(item: number | string, count: number = 1): void { + const num = this.resolveNum(item); + if (isNil(num)) return; + + const raw = this.state.itemStore.getData(num); + if (!raw) return; + + if (raw.category === ItemCategory.Pick) { + for (let i = 0; i < count; i++) { + raw.effect.useEffect(raw); + } + return; + } + + const map = this.getMap(raw.category); + const existing = map.get(num); + if (existing) { + existing.count += count; + if (existing.count <= 0) { + map.delete(num); + } + } else if (count > 0) { + map.set(num, { id: raw.id, num: raw.num, raw, count }); + } + } + + getItem(item: number | string): void { + this.addItem(item, 1); + } + + useItem(item: number | string): boolean { + const state = this.internalGetItemState(item); + if (!state) return false; + + const { raw } = state; + if ( + raw.category !== ItemCategory.Constant && + raw.category !== ItemCategory.Consumable + ) { + return false; + } + + if (!raw.effect.canUse(raw)) return false; + + raw.effect.useEffect(raw); + + if (raw.category === ItemCategory.Consumable) { + state.count--; + if (state.count <= 0) { + this.consumables.delete(raw.num); + } + } + + return true; + } + + /** + * 将分表转换为存档数组 + * @param map 道具分表 + */ + private mapToSave( + map: Map> + ): readonly IHeroItemSave[] { + const result: IHeroItemSave[] = []; + for (const state of map.values()) { + result.push({ num: state.num, count: state.count }); + } + return result; + } + + /** + * 从存档数组恢复分表 + * @param map 道具分表 + * @param saves 存档数组 + */ + private loadMap( + map: Map>, + saves: readonly IHeroItemSave[] + ): void { + for (const save of saves) { + const raw = this.state.itemStore.getData(save.num); + if (!raw) continue; + map.set(save.num, { + id: raw.id, + num: raw.num, + raw, + count: save.count + }); + } + } + + saveState(): IHeroItemsSave { + return { + constants: this.mapToSave(this.constants), + consumables: this.mapToSave(this.consumables), + equipments: this.mapToSave(this.equipments) + }; + } + + loadState(state: IHeroItemsSave): void { + this.constants.clear(); + this.consumables.clear(); + this.equipments.clear(); + this.loadMap(this.constants, state.constants); + this.loadMap(this.consumables, state.consumables); + this.loadMap(this.equipments, state.equipments); + } +} diff --git a/packages-user/data-base/src/hero/state.ts b/packages-user/data-base/src/hero/state.ts index 71bff7a..60f5926 100644 --- a/packages-user/data-base/src/hero/state.ts +++ b/packages-user/data-base/src/hero/state.ts @@ -1,10 +1,12 @@ import { HeroAttribute } from './attribute'; import { HeroFollowersController } from './follower'; +import { HeroItems } from './items'; import { HeroLocation } from './location'; import { HeroRendering } from './rendering'; import { IHeroAttribute, IHeroFollowersController, + IHeroItems, IHeroLocation, IHeroModifier, IHeroRendering, @@ -31,6 +33,7 @@ export class HeroState implements IHeroState { readonly location: IHeroLocation; readonly rendering: IHeroRendering; readonly followers: IHeroFollowersController; + readonly items: IHeroItems; constructor( state: IDataCommon, @@ -49,6 +52,7 @@ export class HeroState implements IHeroState { this.location, faceHandler ); + this.items = new HeroItems(state); } attachAttribute(attribute: IHeroAttribute): void { @@ -119,7 +123,8 @@ export class HeroState implements IHeroState { location: this.location.saveState(compression), rendering: this.rendering.saveState(compression), followers: followerSaves, - modifiers + modifiers, + items: this.items.saveState(compression) }; } @@ -138,6 +143,7 @@ export class HeroState implements IHeroState { this.attribute = newAttribute; this.location.loadState(state.location, compression); this.rendering.loadState(state.rendering, compression); + this.items.loadState(state.items, compression); void this.followers.removeAllFollowers(); for (const save of state.followers) { const follower = this.followers.addFollower(save.num); diff --git a/packages-user/data-base/src/hero/types.ts b/packages-user/data-base/src/hero/types.ts index 2a04990..2e298b7 100644 --- a/packages-user/data-base/src/hero/types.ts +++ b/packages-user/data-base/src/hero/types.ts @@ -8,6 +8,7 @@ import { FaceDirection, IDataCommonExtended, IFaceHandler, + IItemRawData, IObjectMovable, IObjectMover, ISaveableContent @@ -441,6 +442,73 @@ export interface IHeroFollowersController //#endregion +//#region 勇士道具 + +export interface IHeroItemSave { + /** 道具图块数字 */ + readonly num: number; + /** 道具持有数量 */ + readonly count: number; +} + +export interface IHeroItemsSave { + /** 永久道具存档 */ + readonly constants: readonly IHeroItemSave[]; + /** 消耗道具存档 */ + readonly consumables: readonly IHeroItemSave[]; + /** 装备道具存档 */ + readonly equipments: readonly IHeroItemSave[]; +} + +export interface IHeroItemState { + /** 道具字符串 id */ + readonly id: string; + /** 道具图块数字 */ + readonly num: number; + /** 道具原始定义数据引用 */ + readonly raw: IItemRawData; + /** 道具持有数量 */ + count: number; +} + +export interface IHeroItems + extends ISaveableContent, IDataCommonExtended { + /** + * 增加道具数量,count 可以填负数。当道具为 `Pick` 类型时会立刻执行其效果。 + * @param item 道具图块数字或字符串 id + * @param count 要增加的数量,默认为 1 + */ + addItem(item: number | string, count?: number): void; + + /** + * 勇士获取指定道具,当道具为 `Pick` 类型时会立刻执行其效果,否则使背包中的数量加一。 + * 是 `addItem(item, 1)` 的另一种写法。 + * @param item 带锯图块数字或 id + */ + getItem(item: number | string): void; + + /** + * 获取指定道具的状态 + * @param item 道具图块数字或字符串 id + */ + getItemState(item: number | string): Readonly> | null; + + /** + * 使用道具,仅对 Constant 与 Consumable 类型生效。Consumable 类型使用后数量减一。 + * @param item 道具图块数字或字符串 id + * @returns 道具是否使用成功 + */ + useItem(item: number | string): boolean; + + /** + * 获取指定道具的持有数量 + * @param item 道具图块数字或字符串 id + */ + itemCount(item: number | string): number; +} + +//#endregion + //#region 勇士状态 export interface IHeroStateSave { @@ -454,6 +522,8 @@ export interface IHeroStateSave { readonly followers: readonly IHeroFollowerSave[]; /** 勇士属性修饰器状态 */ readonly modifiers: readonly IModifierStateSave[]; + /** 勇士道具背包状态 */ + readonly items: IHeroItemsSave; } export interface IHeroState extends ISaveableContent< @@ -467,6 +537,8 @@ export interface IHeroState extends ISaveableContent< readonly followers: IHeroFollowersController; /** 勇士的渲染对象,包含一些必要渲染信息,存在于数据端,并非渲染端 */ readonly rendering: IHeroRendering; + /** 勇士道具背包 */ + readonly items: IHeroItems; /** * 获取勇士当前的位置 diff --git a/packages-user/data-common/src/store/types.ts b/packages-user/data-common/src/store/types.ts index 8041a66..616d9bb 100644 --- a/packages-user/data-common/src/store/types.ts +++ b/packages-user/data-common/src/store/types.ts @@ -137,25 +137,21 @@ export const enum ItemCategory { } export interface IItemEffect { - /** 即捡即用事件内容(Pick 类型) */ - readonly pickEvent: unknown; - /** 道具使用事件内容(Constant 和 Consumable 类型) */ + /** + * 道具使用事件内容对于 `Pick` 类型,会在拾取时触发; + * 对于 `Constant` 和 `Consumable` 类型,会在使用时触发。 + */ readonly useEvent: unknown; /** - * 即捡即用效果,拾取道具时调用 - * @param item 当前道具数据 - */ - pickEffect(item: IItemRawData): void; - - /** - * 道具使用效果,使用道具时调用 + * 道具使用效果,使用道具时调用。对于 `Pick` 类型,会在拾取时触发; + * 对于 `Constant` 和 `Consumable` 类型,会在使用时触发。 * @param item 当前道具数据 */ useEffect(item: IItemRawData): void; /** - * 能否使用道具 + * 能否使用道具,仅对 `Constant` 和 `Consumable` 类型的道具生效 * @param item 当前道具数据 */ canUse(item: IItemRawData): boolean; diff --git a/packages-user/data-state/src/legacy/item.ts b/packages-user/data-state/src/legacy/item.ts index 7765327..ef98299 100644 --- a/packages-user/data-state/src/legacy/item.ts +++ b/packages-user/data-state/src/legacy/item.ts @@ -23,8 +23,6 @@ class LegacyItemEffect implements IItemEffect { pickEvent: unknown; useEvent: unknown; - /** 即捡即用效果 */ - private readonly pickFn: LegacyItemEffectFn | null = null; /** 道具使用效果 */ private readonly useFn: LegacyItemEffectFn | null = null; /** 是否能够使用道具 */ @@ -37,33 +35,32 @@ class LegacyItemEffect implements IItemEffect { this.pickEvent = undefined; this.useEvent = legacy.useItemEvent; - if (legacy.itemEffect) { - this.pickFn = new Function( - 'state', - 'item', - legacy.itemEffect - ) as LegacyItemEffectFn; + if (legacy.cls === 'items') { + if (legacy.itemEffect) { + this.useFn = new Function( + 'state', + 'item', + legacy.itemEffect + ) as LegacyItemEffectFn; + } + } else if (legacy.cls === 'constants' || legacy.cls === 'tools') { + if (legacy.useItemEffect) { + this.useFn = new Function( + 'state', + 'item', + legacy.useItemEffect + ) as LegacyItemEffectFn; + } + if (typeof legacy.canUseItemEffect === 'string') { + this.canUseFn = new Function( + 'state', + 'item', + legacy.canUseItemEffect + ) as LegacyItemCanUseFn; + } else { + this.canUseFn = () => !!legacy.canUseItemEffect; + } } - if (legacy.useItemEffect) { - this.useFn = new Function( - 'state', - 'item', - legacy.useItemEffect - ) as LegacyItemEffectFn; - } - if (typeof legacy.canUseItemEffect === 'string') { - this.canUseFn = new Function( - 'state', - 'item', - legacy.canUseItemEffect - ) as LegacyItemCanUseFn; - } else { - this.canUseFn = () => !!legacy.canUseItemEffect; - } - } - - pickEffect(item: IItemRawData): void { - this.pickFn?.(this.state, item); } useEffect(item: IItemRawData): void { @@ -96,6 +93,10 @@ export class ItemLegacyBridge implements IItemLegacyConverter< }; } + /** + * 将旧样板装备信息转换为新样板接口 + * @param legacy 旧样板格式的装备信息 + */ private convertEquip(legacy: Equip): IItemEquipData { const valueMap = new Map, number>(); const perMap = new Map, number>(); diff --git a/prompt.md b/prompt.md index b6c1173..dd608e7 100644 --- a/prompt.md +++ b/prompt.md @@ -13,6 +13,7 @@ 3. **不要主动创造新的模式**。遵循已有的设计与代码风格,不要在不同风格之间来回跳跃。 4. **优先优化维护成本,而不是代码长度**。允许局部重复,允许多几个变量,允许多几个 if。但绝不鼓励:为了减少几行代码增加抽象;为了减少重复增加函数跳转;为了"现代 TS"使用复杂语法。 5. **当不确定时,选择最保守的方案**。如果两种方案都能实现需求,默认选择修改最少、风险最低、最接近已有实现的方案。 +6. **代码组织方式**:组织代码时,以"连续阅读一个功能所需的认知切换最少"为目标,而不是以成员类型或访问修饰符为目标。 # 必须规则 @@ -22,10 +23,10 @@ 2. **不恢复我的修改**:我做的任何代码修改都是有原因的。若我在两次对话期间新增、删除或修改了部分代码,不要将其恢复。 3. **遇到歧义立即提问**:若思考或实现时遇到任何问题——例如描述模糊、接口不清晰、某些地方存在歧义等——应立即向我提问,而不是按自己的想法去写。若完成需求所需的接口尚不存在,也应当立即停止实现,向我提出疑问,而不是擅自新增接口来推进。 4. **修改文件前重读文件,修改后说明内容**:修改文件前务必重新阅读以确保内容是最新版。修改后必须在对话中说明所有改动的文件及具体改动内容。在修改文件时,**不得修改**文档的 `涉及文件` 节未提及的文件。 -5. **关于 `dev.md` 的强度升级**:`dev.md` 中"一般不建议/尽量避免"的条目在此处视为**绝对禁止**;"建议/最好"的条目在此处视为**必须遵守**。所有私有方法与成员必须添加 jsDoc 注释,在构造函数的参数中定义的成员除外。 +5. **关于 `dev.md` 的强度升级**:`dev.md` 中"一般不建议/尽量避免"的条目在此处视为**绝对禁止**;"建议/最好"的条目在此处视为**必须遵守**。所有私有方法与成员必须添加 jsDoc 注释,私有方法的参数必须添加注释说明,在构造函数的参数中定义的成员除外。不允许出现连续两个的 `as`,如 `as unknown as number`。 6. **渲染端永远处于被动**:任何情况下都不可能会出现数据端主动通知渲染端进行更新的场景,渲染端仅通过钩子与数据端通信,通过钩子被动获取信息,并在某些情况下影响数据端行为。 7. **关于非空判断**:对于对象,直接使用 `if (!object)` 的方式进行判断,对于字面量,使用 `lodash` 接口 `isNil` 判断 `if (isNil(value))`,不要使用 `if (value === undefined)` 这种方式。 -8. **方法顺序**:对于类的方法,根据功能进行排序,而不是按照修饰词进行排序。对于较长或功能较多的类,使用 `#region` 进行分区,分区按照方法功能进行划分。所有类的成员必须写到类的开头,并按照功能排序。 +8. **方法顺序**:对于类的方法,根据功能进行排序,而不是按照修饰词进行排序。对于较长或功能较多的类,使用 `#region` 进行分区,分区按照方法功能进行划分。所有类的成员必须写到类的开头,并按照功能排序。被调用的方法应排在调用其的方法之前。 9. **注释要求**:所有私有方法和私有成员必须添加 jsDoc 注释,受保护、公共成员与方法必须在源头处添加注释,继承而来的,除非功能或描述有变化,否则不得添加注释。即要求注释具有唯一性:已经在某些地方添加过注释的,不在其他地方添加。 # 建议规则 @@ -55,7 +56,7 @@ - 文档控制在 100-250 行,简洁但包含所有必要信息,不擅自修改示例文档格式。 - 一般不需要流程图;若必须画,使用 `mermaid`。 -以下为文档结构,示例文档参考 `docs/dev/template.md` +以下为文档结构,示例文档参考 `docs/dev/template.md`,务必认真阅读后再进行文档的编写工作。 ```md # 需求综述 @@ -102,7 +103,7 @@ ### `@user/package/[folder/]file.ts` -除非必要或明确提出,一般不建议擅自新增公共方法或成员,必要时可以向我提问。 +除非必要或明确提出,一般不建议擅自新增公共方法或成员,必要时可以向我提问。不需要写的很细,涵盖重要信息即可。 - [ ] 新增 `Iinterface` 接口:描述新增接口的动机与目的,会用于干什么 - [ ] 新增 `Type` 类型别名:描述新增类型别名的动机与目的,会用于干什么