refactor: refactor replay system record.

This commit is contained in:
unanmed 2026-09-14 19:55:20 +08:00
parent 2ff422a682
commit 17d7c8f46a
7 changed files with 171 additions and 152 deletions

View File

@ -95,6 +95,60 @@ export class HeroEquipment<THero> implements IHeroEquipment<THero> {
}
}
/**
* `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<THero> implements IHeroEquipment<THero> {
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 {

View File

@ -112,7 +112,7 @@ export class HeroItems<THero> implements IHeroItems<THero> {
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<THero> implements IHeroItems<THero> {
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);

View File

@ -25,8 +25,6 @@ export class HeroMover<T extends IHeroLocation>
{
readonly state: IDataCommon;
/** 是否不记录进路线系统 */
private noRoute: boolean = false;
/** 是否忽略地形碰撞检测 */
private ignoreTerrain: boolean = false;
/** 是否在特定时机触发自动存档 */
@ -46,9 +44,6 @@ export class HeroMover<T extends IHeroLocation>
}
config(config: Partial<IHeroMoverConfig>): this {
if (!isNil(config.noRoute)) {
this.noRoute = config.noRoute;
}
if (!isNil(config.ignoreTerrain)) {
this.ignoreTerrain = config.ignoreTerrain;
}
@ -63,7 +58,6 @@ export class HeroMover<T extends IHeroLocation>
getConfig(): Readonly<IHeroMoverConfig> {
return {
noRoute: this.noRoute,
ignoreTerrain: this.ignoreTerrain,
autoSave: this.autoSave,
allowOutBound: this.allowOutBound
@ -179,25 +173,23 @@ export class HeroMover<T extends IHeroLocation>
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;
}
// 移动判断

View File

@ -336,8 +336,6 @@ export interface IHeroMoveTopImpl {
}
export interface IHeroMoverConfig {
/** 是否不记录进路线系统 */
noRoute: boolean;
/** 是否忽略地形碰撞检测 */
ignoreTerrain: boolean;
/** 是否在特定时机触发自动存档 */
@ -562,10 +560,9 @@ export interface IHeroItems<THero>
/**
* 使 Constant Consumable Consumable 使
* @param item id
* @param noRoute
* @returns 使
*/
useItem(item: number | string, noRoute?: boolean): boolean;
useItem(item: number | string): boolean;
/**
*

View File

@ -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<IReplayArraySave>
{
export class ReplayArray implements IReplayArray {
length: number = 0;
commandWidth: ReplayCommandWidth = ReplayCommandWidth.Uint8;
@ -89,6 +85,9 @@ export class ReplayArray
/** 所有可用的读取流 */
private readonly readStreams: Set<IReplayReadStream>;
/** 当前是否处于禁用状态 */
private disabled: number = 0;
constructor(config: Readonly<IReplayArrayConfig>) {
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--;
}
}
}

View File

@ -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,

View File

@ -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;
}