diff --git a/dev.md b/dev.md index a109105..5fc770f 100644 --- a/dev.md +++ b/dev.md @@ -118,6 +118,7 @@ - 通常情况下,一个文件应只包含一个类,但如果一个类仅是另一个类的依赖,或一个类的长度远远小于另一个类等特殊情况时,允许将多个类放在同一个文件中。 - 私有方法应该放到调用之的方法之前,同时保证其处在合理的 region 内。 - 对象的非空判断用 `!obj`,字面量的非空判断用 `!isNil(value)`。 +- 任何情况下都不建议写 `try-catch-finally`,哪怕有时会因为报错导致某些关键状态产生错误,产生错误就应该让用户去修,而不是让系统兜底。 ## 双端分离 diff --git a/packages-user/data-base/src/hero/equipment.ts b/packages-user/data-base/src/hero/equipment.ts index d82519f..cd5d028 100644 --- a/packages-user/data-base/src/hero/equipment.ts +++ b/packages-user/data-base/src/hero/equipment.ts @@ -192,7 +192,7 @@ export class HeroEquipment implements IHeroEquipment { // 最后恢复录像记录并记录录像 replay.revert(); - replay.route.add(ReplayCode.Equip, [uid]); + replay.array.add(ReplayCode.Equip, [uid]); return curr; } @@ -209,7 +209,7 @@ export class HeroEquipment implements IHeroEquipment { // 记录录像 const replay = this.state.replaySystem; - replay.route.add(ReplayCode.Unequip, [slot]); + replay.array.add(ReplayCode.Unequip, [slot]); this.unloadEquipEffect(state); this.equips.delete(slot); @@ -346,16 +346,8 @@ export class HeroEquipment implements IHeroEquipment { }); // 由于装备修饰器不进存档,所以此时的勇士处于没有任何装备修饰器的状态,故可以安全清除 this.equips.clear(); - // 读档期间经 equip 恢复槽位会产生真实录像指令,故暂时禁用录像记录 - const replay = this.state.replaySystem; - replay.disable(); - try { - for (const [index, uid] of state.equipped) { - this.equip(uid, index); - } - } finally { - // 必须保证所有路径都恢复录像记录,否则会永久吞掉后续录像 - replay.revert(); + for (const [index, uid] of state.equipped) { + this.equip(uid, index); } } } diff --git a/packages-user/data-base/src/hero/items.ts b/packages-user/data-base/src/hero/items.ts index 7ab6bde..c3d2173 100644 --- a/packages-user/data-base/src/hero/items.ts +++ b/packages-user/data-base/src/hero/items.ts @@ -127,7 +127,7 @@ export class HeroItems implements IHeroItems { if (!raw.effect.canUse(raw)) return false; const replay = this.state.replaySystem; - replay.route.add(ReplayCode.UseItem, [raw.num]); + replay.array.add(ReplayCode.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 002699a..608310d 100644 --- a/packages-user/data-base/src/hero/mover.ts +++ b/packages-user/data-base/src/hero/mover.ts @@ -176,16 +176,16 @@ export class HeroMover const replay = this.state.replaySystem; switch (handler.direction) { case FaceDirection.Up: - replay.route.add(ReplayCode.Up, []); + replay.array.add(ReplayCode.Up, []); break; case FaceDirection.Right: - replay.route.add(ReplayCode.Right, []); + replay.array.add(ReplayCode.Right, []); break; case FaceDirection.Left: - replay.route.add(ReplayCode.Left, []); + replay.array.add(ReplayCode.Left, []); break; case FaceDirection.Down: - replay.route.add(ReplayCode.Down, []); + replay.array.add(ReplayCode.Down, []); break; default: logger.warn(176); diff --git a/packages-user/data-common/src/replay/array.ts b/packages-user/data-common/src/replay/array.ts index 22ff009..51815f1 100644 --- a/packages-user/data-common/src/replay/array.ts +++ b/packages-user/data-common/src/replay/array.ts @@ -853,7 +853,6 @@ export class ReplayArray implements IReplayArray { paramBuffer: ArrayBuffer, length: number ): void { - this.disabled = 0; this.commandWidth = commandWidth; this.commandBuffer = commandBuffer; this.commandArray = new Uint8Array(commandBuffer); @@ -896,4 +895,8 @@ export class ReplayArray implements IReplayArray { this.disabled--; } } + + clearDisableFlag(): void { + this.disabled = 0; + } } diff --git a/packages-user/data-common/src/replay/func.ts b/packages-user/data-common/src/replay/func.ts index 5d66b70..4c1c623 100644 --- a/packages-user/data-common/src/replay/func.ts +++ b/packages-user/data-common/src/replay/func.ts @@ -66,7 +66,7 @@ export function beginReplaySafetyCollection(system: IReplaySystem): void { } replaySystem = system; collecting = true; - beforeLength = system.route.length; + beforeLength = system.array.length; shouldIgnore = false; collection.messages.length = 0; currentCollection = collection; @@ -82,7 +82,7 @@ export function endReplaySafetyCollection(): void { } try { if (shouldIgnore) return; - if (replaySystem.route.length > beforeLength) return; + if (replaySystem.array.length > beforeLength) return; if (collection.messages.length === 0) return; // 需要把收集内容输出,这里只输出一层,完整输出需要在控制台手动调用 diff --git a/packages-user/data-common/src/replay/sandbox.test.ts b/packages-user/data-common/src/replay/sandbox.test.ts index 816f0c4..ab1a6a8 100644 --- a/packages-user/data-common/src/replay/sandbox.test.ts +++ b/packages-user/data-common/src/replay/sandbox.test.ts @@ -33,7 +33,7 @@ function createCommand( // 用录像系统构造一个可手动驱动的录像沙箱 function createSandbox(system: ReplaySystem): IManualReplaySandbox { return system.createReplaySandbox({ - route: system.route, + route: system.array, reseter: { reset: () => {} } }) as IManualReplaySandbox; } @@ -204,7 +204,7 @@ describe('ReplaySandbox stepping', () => { system.record(1); const sandbox = createSandbox(system); start(sandbox); - system.route.add(1, []); + system.array.add(1, []); await expect(sandbox.step()).resolves.toBe(false); @@ -226,7 +226,7 @@ describe('ReplaySandbox stepping', () => { system.record(1); system.record(1); const sandbox = system.createReplaySandbox({ - route: system.route, + route: system.array, reseter: { reset: () => {} }, startIndex: 2 }) as IManualReplaySandbox; @@ -386,7 +386,7 @@ describe('ReplaySandbox playback control', () => { ); system.record(1); const sandbox = createSandbox(system); - system.route.add(1, []); + system.array.add(1, []); sandbox.play(); await new Promise(resolve => setTimeout(resolve, 0)); diff --git a/packages-user/data-common/src/replay/saveLoad.test.ts b/packages-user/data-common/src/replay/saveLoad.test.ts index 3f19acc..736122e 100644 --- a/packages-user/data-common/src/replay/saveLoad.test.ts +++ b/packages-user/data-common/src/replay/saveLoad.test.ts @@ -40,9 +40,9 @@ describe('ReplaySystem save and load round trips', () => { system.replaying = true; system.loadState(snapshot); - expect(system.route.length).toBe(1); - expect(system.route.commandWidth).toBe(snapshot.commandWidth); - expect(system.route.get(0)).toEqual({ + expect(system.array.length).toBe(1); + expect(system.array.commandWidth).toBe(snapshot.commandWidth); + expect(system.array.get(0)).toEqual({ command: 3, params: [1, true, 'param'], index: 0 @@ -60,9 +60,9 @@ describe('ReplaySystem save and load round trips', () => { system.record(5, 9); system.loadState(snapshot); - expect(system.route.getCommandArray()).toBe(snapshot.commandArray); - expect(system.route.getParamArray()).toBe(snapshot.paramArray); - expect(system.route.get(0).command).toBe(2); + expect(system.array.getCommandArray()).toBe(snapshot.commandArray); + expect(system.array.getParamArray()).toBe(snapshot.paramArray); + expect(system.array.get(0).command).toBe(2); }); // 验证读档整体替换缓冲区后,此前创建的活跃沙箱读流被标记过期并触发告警码 156(#06-17-3) @@ -75,7 +75,7 @@ describe('ReplaySystem save and load round trips', () => { system.record(1, 9); const sandbox = system.createReplaySandbox({ - route: system.route, + route: system.array, reseter: { reset: () => {} } }); system.loadState(snapshot); diff --git a/packages-user/data-common/src/replay/system.test.ts b/packages-user/data-common/src/replay/system.test.ts index fc36a2c..fc09f52 100644 --- a/packages-user/data-common/src/replay/system.test.ts +++ b/packages-user/data-common/src/replay/system.test.ts @@ -41,8 +41,8 @@ describe('ReplaySystem registration and recording', () => { it('builds an empty uint8 route', () => { const system = new ReplaySystem(); - expect(system.route.commandWidth).toBe(ReplayCommandWidth.Uint8); - expect(system.route.length).toBe(0); + expect(system.array.commandWidth).toBe(ReplayCommandWidth.Uint8); + expect(system.array.length).toBe(0); }); // 验证注册后可取回命令且未知码返回 null @@ -85,8 +85,8 @@ describe('ReplaySystem registration and recording', () => { system.record(5, 1, true); - expect(system.route.length).toBe(1); - expect(system.route.get(0)).toEqual({ + expect(system.array.length).toBe(1); + expect(system.array.get(0)).toEqual({ command: 5, params: [1, true], index: 0 @@ -102,8 +102,8 @@ describe('ReplaySystem registration and recording', () => { first.record(1); expect(second.getCommand(1)).toBeNull(); - expect(second.route.length).toBe(0); - expect(first.route.length).toBe(1); + expect(second.array.length).toBe(0); + expect(first.array.length).toBe(1); }); }); @@ -123,7 +123,7 @@ describe('ReplaySystem sandbox lifecycle', () => { const save = new Map([['hp', 10]]); const sandbox = system.createReplaySandbox({ - route: system.route, + route: system.array, reseter, save }); @@ -138,7 +138,7 @@ describe('ReplaySystem sandbox lifecycle', () => { const system = new ReplaySystem(); const { reseter } = createReseter(); const sandbox = system.createReplaySandbox({ - route: system.route, + route: system.array, reseter }); const stop = vi.spyOn(sandbox, 'stop').mockResolvedValue(undefined); @@ -163,19 +163,19 @@ describe('ReplaySystem disable and revert', () => { // 验证禁用后 record 被忽略,恢复后继续记录并委托给 route it('delegates disable and revert to the route', () => { const system = new ReplaySystem(); - const disable = vi.spyOn(system.route, 'disable'); - const revert = vi.spyOn(system.route, 'revert'); + const disable = vi.spyOn(system.array, 'disable'); + const revert = vi.spyOn(system.array, 'revert'); system.record(1); system.disable(); system.record(2); expect(disable).toHaveBeenCalledTimes(1); - expect(system.route.length).toBe(1); + expect(system.array.length).toBe(1); system.revert(); system.record(3); expect(revert).toHaveBeenCalledTimes(1); - expect(system.route.length).toBe(2); - expect(system.route.get(1).command).toBe(3); + expect(system.array.length).toBe(2); + expect(system.array.get(1).command).toBe(3); }); }); diff --git a/packages-user/data-common/src/replay/system.ts b/packages-user/data-common/src/replay/system.ts index afddb7f..a0e2bb3 100644 --- a/packages-user/data-common/src/replay/system.ts +++ b/packages-user/data-common/src/replay/system.ts @@ -24,14 +24,14 @@ export class ReplaySystem { replaying: boolean = false; sandbox: IReplaySandbox | null = null; - readonly route: IReplayArray; + readonly array: IReplayArray; /** 所有注册的指令 */ private readonly commands: Map = new Map(); constructor() { super(); - this.route = new ReplayArray({ + this.array = new ReplayArray({ initCommandLength: 1000, initParamLength: 10000, commandExpandMultiplier: 1.2, @@ -61,9 +61,9 @@ export class ReplaySystem } record(code: number, ...params: ReplayParamValue[]): void { - this.route.add(code, params); + this.array.add(code, params); this.forEachHook(hook => - hook.onRecordCommand?.(code, this.route.length - 1, params) + hook.onRecordCommand?.(code, this.array.length - 1, params) ); } @@ -87,24 +87,24 @@ export class ReplaySystem } disable(): void { - this.route.disable(); + this.array.disable(); } revert(): void { - this.route.revert(); + this.array.revert(); } saveState(): IReplaySystemSave { return { - length: this.route.length, - commandWidth: this.route.commandWidth, - commandArray: this.route.getCommandArray(), - paramArray: this.route.getParamArray() + length: this.array.length, + commandWidth: this.array.commandWidth, + commandArray: this.array.getCommandArray(), + paramArray: this.array.getParamArray() }; } loadState(state: IReplaySystemSave): void { - this.route.setReplayArray( + this.array.setReplayArray( state.commandWidth, state.commandArray, state.paramArray, diff --git a/packages-user/data-common/src/replay/types.ts b/packages-user/data-common/src/replay/types.ts index ecc4f00..057f94f 100644 --- a/packages-user/data-common/src/replay/types.ts +++ b/packages-user/data-common/src/replay/types.ts @@ -292,9 +292,16 @@ export interface IReplayArray { disable(): void; /** - * 将录像记录功能从禁用状态恢复为启用状态 + * 将录像记录功能从禁用状态恢复为上一个禁用状态。 + * 具体来说,每次调用 `disable` 时都会使得录像禁用层数加一,此方法可以使其减一,直到减为 0。 + * 这么做的目的是为了防止嵌套禁用调用时出现下层启用后上层意外记录录像的问题。 */ revert(): void; + + /** + * 清空录像记录的 `disable` 记录,直接跳出所有的 `disable` 层 + */ + clearDisableFlag(): void; } export interface IReplaySystemHooks extends IHookBase { @@ -369,7 +376,7 @@ export interface IReplaySystem /** 当前正在播放的录像沙箱实例 */ readonly sandbox: IReplaySandbox | null; /** 当前的录像操作器,用于直接操作或读取录像数据 */ - readonly route: IReplayArray; + readonly array: IReplayArray; /** * 注册一个录像命令 diff --git a/packages-user/data-state/src/core.ts b/packages-user/data-state/src/core.ts index 4fb30de..7d95d8b 100644 --- a/packages-user/data-state/src/core.ts +++ b/packages-user/data-state/src/core.ts @@ -495,6 +495,8 @@ export class CoreState implements ICoreState { state: ReadonlyMap, compression: SaveCompression ): void { + // 需要先清空录像的禁用标记 + this.replaySystem.array.clearDisableFlag(); for (const [key, value] of this.saveables) { // 使用 has 判断是否在映射中,而非值的非空判断,因为空值也有可能是存档的一部分 if (!state.has(key)) { @@ -502,7 +504,11 @@ export class CoreState implements ICoreState { continue; } const data = state.get(key); + // 使用禁用录像包裹所有的读档行为,避免产生意外记录 + this.replaySystem.disable(); value.loadState(data, compression); + this.replaySystem.revert(); + // 但 Executor 不包裹,因为它确实可能产生需要记录的行为 const executor = this.executors.get(value); if (executor) { executor.afterLoad(value, this); diff --git a/packages-user/data-state/src/replay/commands.test.ts b/packages-user/data-state/src/replay/commands.test.ts index df75580..c476da5 100644 --- a/packages-user/data-state/src/replay/commands.test.ts +++ b/packages-user/data-state/src/replay/commands.test.ts @@ -280,7 +280,7 @@ describe('replay commands', () => { replay.record(ReplayCode.Right); replay.record(ReplayCode.UseItem, 5); const sandbox = replay.createReplaySandbox({ - route: replay.route, + route: replay.array, reseter: { reset: () => {} } }) as IManualReplaySandbox; sandbox.playing = true; diff --git a/packages-user/data-state/test/dataClosure.test.ts b/packages-user/data-state/test/dataClosure.test.ts index 42f84c0..60e69d4 100644 --- a/packages-user/data-state/test/dataClosure.test.ts +++ b/packages-user/data-state/test/dataClosure.test.ts @@ -269,7 +269,7 @@ describe('DATA-01 closure', () => { ]); state.replaySystem.record(ReplayCode.Right, 4, 'route'); - expect(state.replaySystem.route.get(0)).toMatchObject({ + expect(state.replaySystem.array.get(0)).toMatchObject({ command: ReplayCode.Right, params: [4, 'route'] }); @@ -291,7 +291,7 @@ describe('DATA-01 closure', () => { replay.record(0); replay.record(1); const sandbox = replay.createReplaySandbox({ - route: replay.route, + route: replay.array, reseter: { reset: () => {} } }) as IManualReplaySandbox; sandbox.pausing = false; diff --git a/packages-user/data-state/test/fixtures/closed-loop.ts b/packages-user/data-state/test/fixtures/closed-loop.ts index 3c082b1..1e629f8 100644 --- a/packages-user/data-state/test/fixtures/closed-loop.ts +++ b/packages-user/data-state/test/fixtures/closed-loop.ts @@ -169,7 +169,7 @@ export function createClosedLoopFixture(): IClosedLoopFixture { const replay = state.replaySystem; replay.record(ReplayCode.Right); - const route = replay.route; + const route = replay.array; const initialState: IClosedLoopInitialState = { hero: state.hero.saveState(SaveCompression.NoCompression), @@ -245,7 +245,7 @@ export function createClosedLoopFixture(): IClosedLoopFixture { ); }; const sandbox = replay.createReplaySandbox({ - route: replay.route, + route: replay.array, reseter: { reset } }); diff --git a/packages-user/data-state/test/nodeReplay.test.ts b/packages-user/data-state/test/nodeReplay.test.ts index ecb8474..36fb71c 100644 --- a/packages-user/data-state/test/nodeReplay.test.ts +++ b/packages-user/data-state/test/nodeReplay.test.ts @@ -118,12 +118,12 @@ function createHarness(options: ITestHarnessOptions): ITestHarness { const sandbox = { ended: false }; let cursor = 0; const runtime: IReplayVerifierRuntime = { - route: replay.route, + route: replay.array, expected: createSnapshot(), sandbox, getCommand: code => replay.getCommand(code), step: async () => { - const step = replay.route.get(cursor++); + const step = replay.array.get(cursor++); const command = replay.getCommand(step.command); if (!command) return false; return command.execute(step); diff --git a/packages-user/data-state/test/replayPlayback.test.ts b/packages-user/data-state/test/replayPlayback.test.ts index e999315..9eb263c 100644 --- a/packages-user/data-state/test/replayPlayback.test.ts +++ b/packages-user/data-state/test/replayPlayback.test.ts @@ -159,7 +159,7 @@ async function waitForEnded(sandbox: IReplaySandbox): Promise { async function playRoute(state: CoreState): Promise { const replay = state.replaySystem; const sandbox = replay.createReplaySandbox({ - route: replay.route, + route: replay.array, reseter: { reset: () => resetHero(state) } }); replay.disable(); @@ -236,16 +236,16 @@ describe('replay recording and route read-back', () => { replay.disable(); replay.record(ReplayCode.Right); - expect(replay.route.length).toBe(0); + expect(replay.array.length).toBe(0); replay.disable(); replay.revert(); replay.record(ReplayCode.Right); - expect(replay.route.length).toBe(0); + expect(replay.array.length).toBe(0); replay.revert(); replay.record(ReplayCode.Right); - expect(replay.route.length).toBe(1); + expect(replay.array.length).toBe(1); }); // 验证八个稳定指令码及其参数都能经录像数组逐条读回 @@ -258,24 +258,24 @@ describe('replay recording and route read-back', () => { replay.record(ReplayCode.Equip, 99, 1, true); replay.record(ReplayCode.Unequip, 1); - expect(replay.route.length).toBe(5); - expect(replay.route.get(0)).toMatchObject({ + expect(replay.array.length).toBe(5); + expect(replay.array.get(0)).toMatchObject({ command: ReplayCode.Up, params: [] }); - expect(replay.route.get(1)).toMatchObject({ + expect(replay.array.get(1)).toMatchObject({ command: ReplayCode.Teleport, params: [3, 4] }); - expect(replay.route.get(2)).toMatchObject({ + expect(replay.array.get(2)).toMatchObject({ command: ReplayCode.UseItem, params: [12] }); - expect(replay.route.get(3)).toMatchObject({ + expect(replay.array.get(3)).toMatchObject({ command: ReplayCode.Equip, params: [99, 1, true] }); - expect(replay.route.get(4)).toMatchObject({ + expect(replay.array.get(4)).toMatchObject({ command: ReplayCode.Unequip, params: [1] }); @@ -286,12 +286,12 @@ describe('replay recording and route read-back', () => { const state = createCoreState(); withReplayDisabled(state, () => createSmallMapScene(state)); - expect(state.replaySystem.route.length).toBe(0); + expect(state.replaySystem.array.length).toBe(0); await runHeroStep(state, FaceDirection.Right); - expect(state.replaySystem.route.length).toBe(1); - expect(state.replaySystem.route.get(0)).toMatchObject({ + expect(state.replaySystem.array.length).toBe(1); + expect(state.replaySystem.array.get(0)).toMatchObject({ command: ReplayCode.Right, params: [] }); @@ -325,14 +325,14 @@ describe('small-map replay playback and second recording', () => { replay.record(ReplayCode.Teleport, 1, 0); replay.record(ReplayCode.Up); - const firstSteps = snapshotRoute(replay.route); + const firstSteps = snapshotRoute(replay.array); expect(firstSteps.map(item => item.code)).toEqual([ ReplayCode.Right, ReplayCode.Right, ReplayCode.Teleport, ReplayCode.Up ]); - expect(replay.route.get(2)).toMatchObject({ + expect(replay.array.get(2)).toMatchObject({ command: ReplayCode.Teleport, params: [1, 0] }); @@ -342,7 +342,7 @@ describe('small-map replay playback and second recording', () => { expect(state.hero.location.y).toBe(0); replay.loadState(emptyReplay, SaveCompression.NoCompression); - expect(replay.route.length).toBe(0); + expect(replay.array.length).toBe(0); withReplayDisabled(state, () => resetHero(state)); await runHeroStep(state, FaceDirection.Right); @@ -350,7 +350,7 @@ describe('small-map replay playback and second recording', () => { replay.record(ReplayCode.Teleport, 1, 0); replay.record(ReplayCode.Up); - const secondSteps = snapshotRoute(replay.route); + const secondSteps = snapshotRoute(replay.array); expectReplayEqual(secondSteps, firstSteps); await playRoute(state); @@ -480,7 +480,7 @@ describe('replay playback error codes 2001-2008', () => { // 验证录像不可存档属主为 ReplaySystem,ReplayArray 不再提供 saveState/loadState it('keeps the saveable owner on ReplaySystem instead of ReplayArray', () => { const state = createCoreState(); - const route = state.replaySystem.route; + const route = state.replaySystem.array; const save = state.replaySystem.saveState( SaveCompression.NoCompression ); diff --git a/packages-user/data-state/test/saveablesRoundTrip.test.ts b/packages-user/data-state/test/saveablesRoundTrip.test.ts index b66693a..58de331 100644 --- a/packages-user/data-state/test/saveablesRoundTrip.test.ts +++ b/packages-user/data-state/test/saveablesRoundTrip.test.ts @@ -240,7 +240,7 @@ function assertRestored(state: CoreState, seeded: SeededState): void { expect(state.enemyManager.getPrefab(1)!.getAttribute('hp')).toBe(30); expect(state.enemyManager.getPrefab(1)!.getAttribute('atk')).toBe(9); - const route = state.replaySystem.route; + const route = state.replaySystem.array; expect(route.length).toBe(REPLAY_STEPS.length); REPLAY_STEPS.forEach((step, index) => { expect(route.get(index)).toEqual({ @@ -454,13 +454,13 @@ describe('CoreState container coverage for compression-less saveables', () => { expect(state.flags.getFieldValue('score')).toBe(5); expect(state.flags.getFieldValue('stage')).toBe(3); - expect(state.replaySystem.route.length).toBe(2); - expect(state.replaySystem.route.get(0)).toEqual({ + expect(state.replaySystem.array.length).toBe(2); + expect(state.replaySystem.array.get(0)).toEqual({ command: 1, params: [], index: 0 }); - expect(state.replaySystem.route.get(1)).toEqual({ + expect(state.replaySystem.array.get(1)).toEqual({ command: 2, params: [7], index: 1 diff --git a/packages-user/data-system/src/path/system.ts b/packages-user/data-system/src/path/system.ts index 4cb4323..922138c 100644 --- a/packages-user/data-system/src/path/system.ts +++ b/packages-user/data-system/src/path/system.ts @@ -99,7 +99,7 @@ export class PathfindingSystem implements IPathfindingSystem { // TODO:后续需要调整设计方式,最好不用 instanceof if (this.mover instanceof HeroMover) { const replay = this.state.replaySystem; - replay.route.add(ReplayCode.Teleport, [target.x, target.y]); + replay.array.add(ReplayCode.Teleport, [target.x, target.y]); } return this.startMove(path, true); }