diff --git a/packages-user/data-base/src/hero/attribute.ts b/packages-user/data-base/src/hero/attribute.ts index 01b1d0d..baa684d 100644 --- a/packages-user/data-base/src/hero/attribute.ts +++ b/packages-user/data-base/src/hero/attribute.ts @@ -199,10 +199,9 @@ export class HeroAttribute implements IHeroAttribute { name: K, index: number ): IHeroModifier | null { - const arr = this.modifier.get(name); + const arr = this.modifier.get(name) as IHeroModifier[]; if (!arr) return null; - const modifier = arr[index] as IHeroModifier | undefined; - // 越界与负索引一律不删除任何修饰器,避免旧 splice 语义下误删末尾元素 + const modifier = arr[index]; if (!modifier) return null; // 删除簿记统一由 deleteModifier 承担,故需先取出修饰器再委托以保留返回值 this.deleteModifier(name, modifier); diff --git a/packages-user/data-base/src/hero/follower.ts b/packages-user/data-base/src/hero/follower.ts index ac933b4..e3aadb3 100644 --- a/packages-user/data-base/src/hero/follower.ts +++ b/packages-user/data-base/src/hero/follower.ts @@ -23,6 +23,8 @@ import { } from './types'; import { isNil } from 'lodash-es'; +// TODO: 挪到渲染端 + export class HeroFollower implements IHeroFollower { readonly num: number; readonly state: IDataCommon; diff --git a/packages-user/data-base/src/hero/items.ts b/packages-user/data-base/src/hero/items.ts index 5143dc7..64707ed 100644 --- a/packages-user/data-base/src/hero/items.ts +++ b/packages-user/data-base/src/hero/items.ts @@ -13,13 +13,11 @@ import { IHeroItemsSave, IHeroItemState } from './types'; +import { logger } from '@motajs/common'; export class HeroItems implements IHeroItems { - /** 永久道具 */ - private readonly constants: Map> = new Map(); - /** 消耗道具 */ - private readonly consumables: Map> = - new Map(); + /** 当前勇士拥有的所有道具 */ + private readonly items: Map> = new Map(); readonly equipment: HeroEquipsStore; @@ -27,30 +25,6 @@ export class HeroItems implements IHeroItems { this.equipment = new HeroEquipsStore(state); } - /** - * 将 item 参数解析为 num,字符串 id 通过 tileStore 转换为 num - * @param item 道具图块数字或 id - */ - private resolveNum(item: number | string): number | undefined { - 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; - default: - return this.constants; - } - } - /** * 获取指定道具的内部状态,返回可修改引用供内部逻辑使用 * @param item 道具图块数字或 id @@ -58,12 +32,9 @@ export class HeroItems implements IHeroItems { private internalGetItemState( item: number | string ): IHeroItemState | null { - const num = this.resolveNum(item); + const num = this.state.tileStore.num(item); if (isNil(num)) return null; - - const category = this.state.itemStore.getCategory(num); - const map = this.getMap(category); - return map.get(num) ?? null; + return this.items.get(num) ?? null; } getItemState( @@ -78,8 +49,8 @@ export class HeroItems implements IHeroItems { @shouldReplay('Adding item to hero should be replayed.') addItem(item: number | string, count: number = 1): void { - const num = this.resolveNum(item); - if (isNil(num)) return; + const [num, id] = this.state.tileStore.identity(item); + if (isNil(num) || isNil(id)) return; const raw = this.state.itemStore.getData(num); if (!raw) return; @@ -98,23 +69,18 @@ export class HeroItems implements IHeroItems { return; } - const map = this.getMap(raw.category); - const existing = map.get(num); + const existing = this.items.get(num); if (existing) { existing.count += count; if (existing.count <= 0) { - map.delete(num); + this.items.delete(num); } } else if (count > 0) { - const store = this.state.tileStore; - map.set(num, { id: store.id(raw.num)!, num: raw.num, raw, count }); + const consumable = raw.category === ItemCategory.Consumable; + this.items.set(num, { id, num, raw, count, consumable }); } } - getItem(item: number | string): void { - this.addItem(item, 1); - } - @shouldReplay('Using item should be replayed.') useItem(item: number | string): boolean { const state = this.internalGetItemState(item); @@ -136,10 +102,10 @@ export class HeroItems implements IHeroItems { raw.effect.useEffect?.(raw, this.state); - if (raw.category === ItemCategory.Consumable) { + if (state.consumable) { state.count--; if (state.count <= 0) { - this.consumables.delete(raw.num); + this.items.delete(raw.num); } } @@ -173,19 +139,24 @@ export class HeroItems implements IHeroItems { const raw = this.state.itemStore.getData(save.num); if (!raw) continue; const store = this.state.tileStore; + const id = store.id(raw.num); + if (isNil(id)) { + logger.warn(193, raw.num.toString()); + continue; + } map.set(save.num, { - id: store.id(raw.num)!, + id, num: raw.num, raw, - count: save.count + count: save.count, + consumable: raw.category === ItemCategory.Consumable }); } } saveState(compression: SaveCompression): IHeroItemsSave { return { - constants: this.mapToSave(this.constants), - consumables: this.mapToSave(this.consumables), + items: this.mapToSave(this.items), equipStore: this.equipment.saveState(compression) }; } @@ -194,10 +165,8 @@ export class HeroItems implements IHeroItems { state: IHeroItemsSave, compression: SaveCompression ): void { - this.constants.clear(); - this.consumables.clear(); - this.loadMap(this.constants, state.constants); - this.loadMap(this.consumables, state.consumables); + this.items.clear(); + this.loadMap(this.items, state.items); this.equipment.loadState(state.equipStore, compression); } } diff --git a/packages-user/data-base/src/hero/rendering.ts b/packages-user/data-base/src/hero/rendering.ts index 029788e..8692d16 100644 --- a/packages-user/data-base/src/hero/rendering.ts +++ b/packages-user/data-base/src/hero/rendering.ts @@ -6,6 +6,8 @@ import { IHeroRenderingSave } from './types'; +// TODO: 挪到渲染端 + export class HeroRendering extends Hookable implements IHeroRendering diff --git a/packages-user/data-base/src/hero/state.ts b/packages-user/data-base/src/hero/state.ts index 443f80e..a4a0985 100644 --- a/packages-user/data-base/src/hero/state.ts +++ b/packages-user/data-base/src/hero/state.ts @@ -1,16 +1,12 @@ import { HeroEquipment } from './equipment'; -import { HeroFollowersController } from './follower'; import { HeroItems } from './items'; import { HeroLocation } from './location'; -import { HeroRendering } from './rendering'; import { IHeroAttribute, - IHeroFollowersController, IHeroEquipment, IHeroItems, IHeroLocation, IHeroModifier, - IHeroRendering, IHeroState, IHeroStateSave, IReadonlyHeroAttribute, @@ -35,8 +31,6 @@ export class HeroState implements IHeroState { readonly location: IHeroLocation; - readonly rendering: IHeroRendering; - readonly followers: IHeroFollowersController; readonly items: IHeroItems; readonly equip: IHeroEquipment; @@ -46,18 +40,12 @@ export class HeroState public readonly attribute: IHeroAttribute ) { super(); - this.rendering = new HeroRendering(state); const defaultLoc: IFacedTileLocator = { x: 0, y: 0, direction: FaceDirection.Down }; this.location = new HeroLocation(state, defaultLoc, faceHandler); - this.followers = new HeroFollowersController( - state, - this.location, - faceHandler - ); this.items = new HeroItems(state); this.equip = new HeroEquipment(this.items.equipment, this.attribute); } @@ -125,15 +113,9 @@ export class HeroState } saveState(compression: SaveCompression): IHeroStateSave { - const followerSaves = this.followers - .getAllFollowers() - .map(v => v.saveState(compression)); - return { attribute: this.attribute.saveState(compression), location: this.location.saveState(compression), - rendering: this.rendering.saveState(compression), - followers: followerSaves, items: this.items.saveState(compression), equip: this.equip.saveState(compression) }; @@ -146,10 +128,7 @@ export class HeroState // 属性原地读档:不替换实例,使装备与战斗侧持有的引用跨读档始终有效 this.attribute.loadState(state.attribute, compression); this.location.loadState(state.location, compression); - this.rendering.loadState(state.rendering, compression); this.items.loadState(state.items, compression); this.equip.loadState(state.equip, compression); - // 跟随者保留式恢复:同索引同图块数字的实例原地读档,其余按存档替换或删除 - this.followers.restoreFollowers(state.followers, compression); } } diff --git a/packages-user/data-base/src/hero/types.ts b/packages-user/data-base/src/hero/types.ts index 036c620..9e64c84 100644 --- a/packages-user/data-base/src/hero/types.ts +++ b/packages-user/data-base/src/hero/types.ts @@ -579,9 +579,7 @@ export interface IHeroItemSave { export interface IHeroItemsSave { /** 永久道具存档 */ - readonly constants: readonly IHeroItemSave[]; - /** 消耗道具存档 */ - readonly consumables: readonly IHeroItemSave[]; + readonly items: readonly IHeroItemSave[]; /** 装备实例仓库存档 */ readonly equipStore: IHeroEquipsStoreSave; } @@ -593,6 +591,8 @@ export interface IHeroItemState { readonly num: number; /** 道具原始定义数据引用 */ readonly raw: IItemRawData; + /** 是否为可消耗道具 */ + readonly consumable: boolean; /** 道具持有数量 */ count: number; } @@ -609,13 +609,6 @@ export interface IHeroItems */ addItem(item: number | string, count?: number): void; - /** - * 勇士获取指定道具,当道具为 `Pick` 类型时会立刻执行其效果,否则使背包中的数量加一。 - * 是 `addItem(item, 1)` 的另一种写法。 - * @param item 带锯图块数字或 id - */ - getItem(item: number | string): void; - /** * 获取指定道具的状态 * @param item 道具图块数字或字符串 id @@ -640,12 +633,6 @@ export interface IHeroItems //#region 勇士装备 -/** - * 装备实例的存档。装备自身的数值与百分比加成是加成的唯一事实源, - * 存档持久化的正是这两个表:无压缩档全量存储、压缩档只存与原始定义的差异; - * 读档也由它们重建装备修饰器。故运行时要改变装备加成,必须改装备自身属性, - * 而不是修改它的修饰器 - */ export interface IEquipmentStateSave { /** 装备实例 uid */ readonly uid: number; @@ -914,10 +901,6 @@ export interface IHeroStateSave { readonly attribute: IHeroAttributeSave; /** 勇士当前位置 */ readonly location: IHeroLocationSave; - /** 勇士渲染状态 */ - readonly rendering: IHeroRenderingSave; - /** 勇士当前的跟随者 */ - readonly followers: readonly IHeroFollowerSave[]; /** 勇士道具背包状态 */ readonly items: IHeroItemsSave; /** 勇士装备状态 */ @@ -932,10 +915,6 @@ export interface IHeroState readonly location: IHeroLocation; /** 勇士属性对象 */ readonly attribute: IReadonlyHeroAttribute; - /** 勇士跟随者对象 */ - 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 794b1f0..82f76b8 100644 --- a/packages-user/data-common/src/store/types.ts +++ b/packages-user/data-common/src/store/types.ts @@ -110,6 +110,12 @@ export interface ITileStore { * @param token 图块数字或 id */ id(token: number | string): string | undefined; + + /** + * 传入图块数字或 id,返回图块数字和 id + * @param token 图块数字或 id + */ + identity(token: number | string): [number, string] | []; } //#endregion diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index e57b548..980acd4 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -275,6 +275,7 @@ "190": "Cannot flatten autotile since its width or height cannot be divided evenly. Autotile frame size: [$1,$2].", "191": "Cannot cache autotile since no its frame or type data stored.", "192": "A FaceGroup.Dir8 face handler registration is expected.", + "193": "Cannot load item state since no item id registration for item num $1.", "1001": "Event(setBlock): Unknown tile '$1' when setting block." } }