From 17d7c8f46a67d51d3bb3c4347f42d88a924ffef0 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 14 Sep 2026 19:55:20 +0800 Subject: [PATCH] refactor: refactor replay system record. --- packages-user/data-base/src/hero/equipment.ts | 137 +++++++++++------- packages-user/data-base/src/hero/items.ts | 8 +- packages-user/data-base/src/hero/mover.ts | 42 +++--- packages-user/data-base/src/hero/types.ts | 5 +- packages-user/data-common/src/replay/array.ts | 44 +++--- .../data-common/src/replay/system.ts | 8 + packages-user/data-common/src/replay/types.ts | 79 +++++----- 7 files changed, 171 insertions(+), 152 deletions(-) diff --git a/packages-user/data-base/src/hero/equipment.ts b/packages-user/data-base/src/hero/equipment.ts index 81a6c2c..07ca6a6 100644 --- a/packages-user/data-base/src/hero/equipment.ts +++ b/packages-user/data-base/src/hero/equipment.ts @@ -95,6 +95,60 @@ export class HeroEquipment implements IHeroEquipment { } } + /** + * 判断一个装备是否已经装备至某个装备槽,如果已装备的装备槽与目标装备槽不符,那么根据 `autoUnload` 判断是否卸下 + * @param uid 装备实例 uid + * @param slot 要装备至的装备槽 + * @param autoUnload 当要装备的装备已经处于某个装备槽,是否自动将其卸下 + * @returns 是否需要进行后续的装备操作 + */ + private checkEuipped( + uid: number, + slot: number | string, + autoUnload: boolean + ): boolean { + // 检查有没有同 uid 装备 + for (const [index, curr] of this.equips) { + if (curr !== uid) continue; + if (index === slot || this.slots[index] === slot) { + // 指定装备已经装备至了指定装备槽,直接忽略 + return false; + } else { + // 否则看 autoUnload + if (autoUnload) { + this.unequip(index); + return true; + } else { + return false; + } + } + } + return true; + } + + /** + * 获取可以装备至的装备槽 + * @param slot 数字装备槽或字符串装备槽 + * @returns 可装备至的数字装备槽,-1 表示没有可用槽位 + */ + private getCouldEquipSlot(slot: number | string): number { + if (typeof slot === 'number') return slot; + let first = -1; + let empty = -1; + this.slots.forEach((name, index) => { + if (name !== slot) return; + if (first === -1) first = index; + if (empty !== -1 && !this.equips.has(index)) { + empty = index; + } + }); + if (empty === -1) { + return first; + } else { + return empty; + } + } + equip( uid: number, slot: number | string, @@ -104,71 +158,42 @@ export class HeroEquipment implements IHeroEquipment { return void 0; } - // 检查有没有同 uid 装备 - for (const [index, curr] of this.equips) { - if (curr !== uid) continue; - if (index === slot || this.slots[index] === slot) { - // 指定装备已经装备至了指定装备槽,直接忽略 - return void 0; - } else { - // 否则看 autoUnload - if (autoUnload) { - this.unequip(index); - break; - } else { - return void 0; - } - } - } - const state = this.store.get(uid); if (!state) { logger.warn(146, uid.toString()); return void 0; } - // 记录录像 - // TODO: 可以考虑把禁用录像记录放到录像系统里面,避免每个可能由代码触发的行动都写一个 noRoute 参数 + // 由于期间会调用 `unload` 卸下装备,因此需要暂时禁用录像记录 const replay = this.state.replaySystem; + replay.disable(); + + // 如果装备已装备,那么应该根据 `autoUnload` 决定是否卸下 + const next = this.checkEuipped(uid, slot, autoUnload); + if (!next) { + replay.revert(); + return void 0; + } + + // 接下来获取可用装备槽 + const available = this.getCouldEquipSlot(slot); + if (available === -1) { + logger.warn(147, uid.toString()); + replay.revert(); + return void 0; + } + + // 然后执行真正的装备效果 + const curr = this.equips.get(available); + this.unequip(available); + this.equips.set(available, uid); + this.loadEquipEffect(state); + + // 最后恢复录像记录并记录录像 + replay.revert(); replay.route.add(ReplayCommandCode.Equip, [uid]); - if (typeof slot === 'number') { - // 数字槽位,直接进行指定替换 - const curr = this.equips.get(slot); - this.unequip(slot); - this.equips.set(slot, uid); - this.loadEquipEffect(state); - return curr; - } else { - // 字符串槽位,需要判断是否包含空槽 - let first = -1; - let empty = -1; - this.slots.forEach((name, index) => { - if (name !== slot) return; - if (first === -1) first = index; - if (empty !== -1 && !this.equips.has(index)) { - empty = index; - } - }); - if (empty === -1) { - // 无空槽,替换第一个匹配的槽位 - if (first === -1) { - logger.warn(147, uid.toString()); - return void 0; - } else { - const curr = this.equips.get(first); - this.unequip(first); - this.equips.set(first, uid); - this.loadEquipEffect(state); - return curr; - } - } else { - // 此时有空槽,直接装备上就行 - this.equips.set(empty, uid); - this.loadEquipEffect(state); - return void 0; - } - } + return curr; } unequip(slot: number): number | undefined { diff --git a/packages-user/data-base/src/hero/items.ts b/packages-user/data-base/src/hero/items.ts index 7c47cb3..0c66c7a 100644 --- a/packages-user/data-base/src/hero/items.ts +++ b/packages-user/data-base/src/hero/items.ts @@ -112,7 +112,7 @@ export class HeroItems implements IHeroItems { this.addItem(item, 1); } - useItem(item: number | string, noRoute: boolean = false): boolean { + useItem(item: number | string): boolean { const state = this.internalGetItemState(item); if (!state) return false; @@ -126,10 +126,8 @@ export class HeroItems implements IHeroItems { if (!raw.effect.canUse(raw)) return false; - if (!noRoute) { - const replay = this.state.replaySystem; - replay.route.add(ReplayCommandCode.UseItem, [raw.num]); - } + const replay = this.state.replaySystem; + replay.route.add(ReplayCommandCode.UseItem, [raw.num]); raw.effect.useEffect(raw); diff --git a/packages-user/data-base/src/hero/mover.ts b/packages-user/data-base/src/hero/mover.ts index b6227bd..5573e10 100644 --- a/packages-user/data-base/src/hero/mover.ts +++ b/packages-user/data-base/src/hero/mover.ts @@ -25,8 +25,6 @@ export class HeroMover { readonly state: IDataCommon; - /** 是否不记录进路线系统 */ - private noRoute: boolean = false; /** 是否忽略地形碰撞检测 */ private ignoreTerrain: boolean = false; /** 是否在特定时机触发自动存档 */ @@ -46,9 +44,6 @@ export class HeroMover } config(config: Partial): this { - if (!isNil(config.noRoute)) { - this.noRoute = config.noRoute; - } if (!isNil(config.ignoreTerrain)) { this.ignoreTerrain = config.ignoreTerrain; } @@ -63,7 +58,6 @@ export class HeroMover getConfig(): Readonly { return { - noRoute: this.noRoute, ignoreTerrain: this.ignoreTerrain, autoSave: this.autoSave, allowOutBound: this.allowOutBound @@ -179,25 +173,23 @@ export class HeroMover type === ObjectMoveType.Special ) { // 录像记录 - if (!this.noRoute) { - const replay = this.state.replaySystem; - switch (handler.direction) { - case FaceDirection.Up: - replay.route.add(ReplayCommandCode.Up, []); - break; - case FaceDirection.Right: - replay.route.add(ReplayCommandCode.Right, []); - break; - case FaceDirection.Left: - replay.route.add(ReplayCommandCode.Left, []); - break; - case FaceDirection.Down: - replay.route.add(ReplayCommandCode.Down, []); - break; - default: - logger.warn(176); - break; - } + const replay = this.state.replaySystem; + switch (handler.direction) { + case FaceDirection.Up: + replay.route.add(ReplayCommandCode.Up, []); + break; + case FaceDirection.Right: + replay.route.add(ReplayCommandCode.Right, []); + break; + case FaceDirection.Left: + replay.route.add(ReplayCommandCode.Left, []); + break; + case FaceDirection.Down: + replay.route.add(ReplayCommandCode.Down, []); + break; + default: + logger.warn(176); + break; } // 移动判断 diff --git a/packages-user/data-base/src/hero/types.ts b/packages-user/data-base/src/hero/types.ts index ff14772..238303b 100644 --- a/packages-user/data-base/src/hero/types.ts +++ b/packages-user/data-base/src/hero/types.ts @@ -336,8 +336,6 @@ export interface IHeroMoveTopImpl { } export interface IHeroMoverConfig { - /** 是否不记录进路线系统 */ - noRoute: boolean; /** 是否忽略地形碰撞检测 */ ignoreTerrain: boolean; /** 是否在特定时机触发自动存档 */ @@ -562,10 +560,9 @@ export interface IHeroItems /** * 使用道具,仅对 Constant 与 Consumable 类型生效。Consumable 类型使用后数量减一。 * @param item 道具图块数字或字符串 id - * @param noRoute 是否不计入录像 * @returns 道具是否使用成功 */ - useItem(item: number | string, noRoute?: boolean): boolean; + useItem(item: number | string): boolean; /** * 获取指定道具的持有数量 diff --git a/packages-user/data-common/src/replay/array.ts b/packages-user/data-common/src/replay/array.ts index 6fe0918..ac6fd0d 100644 --- a/packages-user/data-common/src/replay/array.ts +++ b/packages-user/data-common/src/replay/array.ts @@ -2,13 +2,11 @@ import { logger } from '@motajs/common'; import { IReplayArray, IReplayArrayConfig, - IReplayArraySave, IReplayReadStream, IReplayStepHandler, ReplayCommandWidth, ReplayParamValue } from './types'; -import { ISaveableContent } from '../save'; interface INormalizedParam { /** @@ -46,9 +44,7 @@ interface IDecodedCommand { readonly paramCount: number; } -export class ReplayArray - implements IReplayArray, ISaveableContent -{ +export class ReplayArray implements IReplayArray { length: number = 0; commandWidth: ReplayCommandWidth = ReplayCommandWidth.Uint8; @@ -89,6 +85,9 @@ export class ReplayArray /** 所有可用的读取流 */ private readonly readStreams: Set; + /** 当前是否处于禁用状态 */ + private disabled: number = 0; + constructor(config: Readonly) { this.textEncoder = new TextEncoder(); this.textDecoder = new TextDecoder(); @@ -391,6 +390,7 @@ export class ReplayArray } add(command: number, params: ReplayParamValue[]): void { + if (this.disabled >= 0) return; const normalized = this.normalizeParamList(params); const length = this.calculateParamsLength(normalized); this.checkBufferExpand(length); @@ -410,6 +410,7 @@ export class ReplayArray } insert(index: number, command: number, params: ReplayParamValue[]): void { + if (this.disabled >= 0) return; const normalized = this.normalizeParamList(params); const length = this.calculateParamsLength(normalized); this.checkBufferExpand(length); @@ -439,6 +440,7 @@ export class ReplayArray } delete(index: number): void { + if (this.disabled >= 0) return; const commandSize = this.getCommandSize(); const commandStart = index * commandSize; const paramStart = this.indexArray[index]; @@ -472,6 +474,7 @@ export class ReplayArray } set(index: number, command: number, params: ReplayParamValue[]): void { + if (this.disabled >= 0) return; const normalized = this.normalizeParamList(params); const length = this.calculateParamsLength(normalized); const paramStart = this.indexArray[index]; @@ -510,6 +513,7 @@ export class ReplayArray } setCommandWidth(width: ReplayCommandWidth): void { + this.disable(); const oldWidth = this.commandWidth; const oldSize = this.getCommandSize(); this.commandWidth = width; @@ -549,6 +553,7 @@ export class ReplayArray this.commandView = newView; this.expireStreams(); + this.revert(); } //#endregion @@ -767,6 +772,7 @@ export class ReplayArray paramBuffer: ArrayBuffer, length: number ): void { + this.disabled = 0; this.commandWidth = commandWidth; this.commandBuffer = commandBuffer; this.commandArray = new Uint8Array(commandBuffer); @@ -797,29 +803,13 @@ export class ReplayArray return this.paramBuffer; } - //#region 存读档 - - saveState(): IReplayArraySave { - return { - commands: this.commandBuffer, - params: this.paramBuffer, - metadata: { - commandWidth: this.commandWidth - } - }; + disable(): void { + this.disabled++; } - loadState(state: IReplayArraySave): void { - this.commandBuffer = state.commands; - this.paramBuffer = state.params; - this.commandWidth = state.metadata.commandWidth; - - this.commandView = new DataView(this.commandBuffer); - this.paramView = new DataView(this.paramBuffer); - this.commandArray = new Uint8Array(this.commandBuffer); - this.paramArray = new Uint8Array(this.paramBuffer); - this.indexArray = new Uint32Array(this.indexBuffer); - - this.expireStreams(); + revert(): void { + if (this.disabled > 0) { + this.disabled--; + } } } diff --git a/packages-user/data-common/src/replay/system.ts b/packages-user/data-common/src/replay/system.ts index c713d83..235cfcb 100644 --- a/packages-user/data-common/src/replay/system.ts +++ b/packages-user/data-common/src/replay/system.ts @@ -86,6 +86,14 @@ export class ReplaySystem this.sandbox = null; } + disable(): void { + this.route.disable(); + } + + revert(): void { + this.route.revert(); + } + saveState(): IReplaySystemSave { return { length: this.route.length, diff --git a/packages-user/data-common/src/replay/types.ts b/packages-user/data-common/src/replay/types.ts index 498780a..ac8efcc 100644 --- a/packages-user/data-common/src/replay/types.ts +++ b/packages-user/data-common/src/replay/types.ts @@ -181,38 +181,6 @@ export interface IReplayArrayConfig { paramMaxLength: number; } -export interface IReplayMetadataSave { - /** 当前录像使用的指令码宽度 */ - readonly commandWidth: ReplayCommandWidth; -} - -export interface IReplayArraySave { - /** - * 指令数组,在 Uint8 位宽下,两个字节为一组,第一个字节为参数数量,第二个字节为指令标识。 - * 在 Uint16 位宽下,三个字节为一组,第一个字节为参数数量,后两个字节组成的 uint16 为指令标识。 - */ - readonly commands: ArrayBuffer; - - /** - * 参数数组,由参数类型和参数值组成。参数类型占据一个字节,参数值根据类型不同占据不同的字节。 - * 参数类型列表(包含参数类型字节): - * - * - 0: boolean --- 2 Byte - * - 1: int8 --- 2 Byte - * - 2: int16 --- 3 Byte - * - 3: int32 --- 5 Byte - * - 4: int64 --- 9 Byte - * - 5: float --- 9 Byte - * - 6: bigint --- n + 1 Byte, 其中 n 是 bigint 的字节数 - * - 7: string --- n + 1 Byte, 其中 n 是字符串编码后的字节数 - * - 8 ~ 255: n - 7 长度的字符串 --- n + 1 Byte, 其中 n 是字符串编码后的字节数 - */ - readonly params: ArrayBuffer; - - /** 录像元数据 */ - readonly metadata: IReplayMetadataSave; -} - export interface IReplayArray { /** 录像中的总步数 */ readonly length: number; @@ -307,6 +275,17 @@ export interface IReplayArray { paramBuffer: ArrayBuffer, length: number ): void; + + /** + * 暂时禁用录像记录功能,之后的任何记录将不会生效, + * 一般用于在被动触发的函数中调用会记录录像的方法时临时禁用录像记录 + */ + disable(): void; + + /** + * 将录像记录功能从禁用状态恢复为启用状态 + */ + revert(): void; } export interface IReplaySystemHooks extends IHookBase { @@ -343,11 +322,28 @@ export interface IReplaySandboxConfig { export interface IReplaySystemSave { /** 录像长度 */ readonly length: number; - /** 指令位宽 */ + /** 当前录像使用的指令码宽度 */ readonly commandWidth: number; - /** 指令数组 */ + + /** + * 指令数组,在 Uint8 位宽下,两个字节为一组,第一个字节为参数数量,第二个字节为指令标识。 + * 在 Uint16 位宽下,三个字节为一组,第一个字节为参数数量,后两个字节组成的 uint16 为指令标识。 + */ readonly commandArray: ArrayBuffer; - /** 参数数组 */ + /** + * 参数数组,由参数类型和参数值组成。参数类型占据一个字节,参数值根据类型不同占据不同的字节。 + * 参数类型列表(包含参数类型字节): + * + * - 0: boolean --- 2 Byte + * - 1: int8 --- 2 Byte + * - 2: int16 --- 3 Byte + * - 3: int32 --- 5 Byte + * - 4: int64 --- 9 Byte + * - 5: float --- 9 Byte + * - 6: bigint --- n + 1 Byte, 其中 n 是 bigint 的字节数 + * - 7: string --- n + 1 Byte, 其中 n 是字符串编码后的字节数 + * - 8 ~ 255: n - 7 长度的字符串 --- n + 1 Byte, 其中 n 是字符串编码后的字节数 + */ readonly paramArray: ArrayBuffer; } @@ -390,4 +386,17 @@ export interface IReplaySystem * 释放当前活跃的沙箱 */ releaseSandbox(): void; + + /** + * 暂时禁用录像记录功能,之后的任何记录将不会生效, + * 一般用于在被动触发的函数中调用会记录录像的方法时临时禁用录像记录 + */ + disable(): void; + + /** + * 将录像记录功能从禁用状态恢复为上一个禁用状态。 + * 具体来说,每次调用 `disable` 时都会使得录像禁用层数加一,此方法可以使其减一,直到减为 0。 + * 这么做的目的是为了防止嵌套禁用调用时出现下层启用后上层意外记录录像的问题。 + */ + revert(): void; }