diff --git a/packages-user/data-base/src/flag/field.ts b/packages-user/data-base/src/flag/field.ts index b21e4aa..b6ecf5a 100644 --- a/packages-user/data-base/src/flag/field.ts +++ b/packages-user/data-base/src/flag/field.ts @@ -1,3 +1,4 @@ +import { shouldReplay } from '@user/data-common'; import { IFlagCommonField, IFlagSystem } from './types'; import { logger } from '@motajs/common'; @@ -16,10 +17,12 @@ export class FlagCommonField implements IFlagCommonField { this.value = value; } + @shouldReplay('Flag field value set should be replayed.') set(value: T): void { this.value = value; } + @shouldReplay('Flag field value set should be replayed.') add(value: number): void { if (typeof this.value !== 'number') { logger.warn(111, String(this.key)); diff --git a/packages-user/data-base/src/flag/system.ts b/packages-user/data-base/src/flag/system.ts index d7d6dd8..b288d7e 100644 --- a/packages-user/data-base/src/flag/system.ts +++ b/packages-user/data-base/src/flag/system.ts @@ -1,5 +1,6 @@ import { IFlagCommonField, IFlagSystem, IFlagSystemSave } from './types'; import { FlagCommonField } from './field'; +import { shouldReplay } from '@user/data-common'; export class FlagSystem implements IFlagSystem { private readonly fieldMap: Map> = @@ -9,6 +10,7 @@ export class FlagSystem implements IFlagSystem { return this.fieldMap.has(field); } + @shouldReplay('Flag field set should be replayed.') setField(field: PropertyKey, value: T): IFlagCommonField { return this.getOrInsert(field, value); } @@ -34,6 +36,7 @@ export class FlagSystem implements IFlagSystem { ); } + @shouldReplay('Flag field deleting should be replayed.') deleteField(field: PropertyKey): void { this.fieldMap.delete(field); } @@ -63,26 +66,11 @@ export class FlagSystem implements IFlagSystem { } loadState(state: IFlagSystemSave): void { - // 按 key 复用现有字段原地写值,使外部持有的引用跨读档仍然有效 + this.fieldMap.clear(); for (const [key, data] of state.fields) { - const existing = this.fieldMap.get(key); - if (existing) { - existing.fromStructured(data); - } else { - const field = new FlagCommonField( - this, - key, - undefined - ); - field.fromStructured(data); - this.fieldMap.set(key, field); - } - } - // 以存档为准:存档中不存在的字段一律删除 - for (const key of this.fieldMap.keys()) { - if (!state.fields.has(key)) { - this.fieldMap.delete(key); - } + const field = new FlagCommonField(this, key, void 0); + field.fromStructured(data); + this.fieldMap.set(key, field); } } } diff --git a/packages-user/data-base/src/hero/items.ts b/packages-user/data-base/src/hero/items.ts index d3145be..5143dc7 100644 --- a/packages-user/data-base/src/hero/items.ts +++ b/packages-user/data-base/src/hero/items.ts @@ -3,7 +3,8 @@ import { IDataCommon, ItemCategory, ReplayCode, - SaveCompression + SaveCompression, + shouldReplay } from '@user/data-common'; import { HeroEquipsStore } from './equipStore'; import { @@ -75,6 +76,7 @@ export class HeroItems implements IHeroItems { return this.getItemState(item)?.count ?? 0; } + @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; @@ -113,6 +115,7 @@ export class HeroItems implements IHeroItems { this.addItem(item, 1); } + @shouldReplay('Using item should be replayed.') useItem(item: number | string): boolean { const state = this.internalGetItemState(item); if (!state) return false; diff --git a/packages-user/data-base/src/hero/location.ts b/packages-user/data-base/src/hero/location.ts index caca751..1af9ec0 100644 --- a/packages-user/data-base/src/hero/location.ts +++ b/packages-user/data-base/src/hero/location.ts @@ -7,7 +7,8 @@ import { import { FaceDirection, IDataCommon, - IFacedTileLocator + IFacedTileLocator, + shouldReplay } from '@user/data-common'; import { HeroMover } from './mover'; import { @@ -50,12 +51,14 @@ export class HeroLocation return new HookController(this, hook); } + @shouldReplay('Setting hero floor should be replayed.') setFloor(map: IGameMap): void { this.floorId = map.floorId; this.map = map; this.forEachHook(hook => hook.onSetFloor?.(map)); } + @shouldReplay('Setting hero position should be replayed.') setPos(x: number, y: number): void { this.x = x; this.y = y; diff --git a/packages-user/data-base/src/hero/mover.ts b/packages-user/data-base/src/hero/mover.ts index 8e3f10e..7e1c913 100644 --- a/packages-user/data-base/src/hero/mover.ts +++ b/packages-user/data-base/src/hero/mover.ts @@ -6,7 +6,8 @@ import { ObjectMover, ObjectMoveType, IDataCommon, - ReplayCode + ReplayCode, + shouldReplay } from '@user/data-common'; import { HeroMoveCode, @@ -152,6 +153,7 @@ export class HeroMover protected async onMoveEnd(): Promise {} + @shouldReplay('Hero moving step should be replayed.') protected async onStepStart( step: Readonly, tile: IHeroLocation @@ -238,6 +240,7 @@ export class HeroMover return HeroMoveCode.Step; } + @shouldReplay('Hero moving step should be replayed.') protected async onStepEnd( code: number, step: Readonly, @@ -279,6 +282,7 @@ export class HeroMover return { x: tile.x, y: tile.y }; } + @shouldReplay('Hero moving step should be replayed.') protected async onStepSettled( before: ITileLocator, curr: ITileLocator, diff --git a/packages-user/data-base/src/map/dynamicTile.ts b/packages-user/data-base/src/map/dynamicTile.ts index f15941f..625b4df 100644 --- a/packages-user/data-base/src/map/dynamicTile.ts +++ b/packages-user/data-base/src/map/dynamicTile.ts @@ -3,7 +3,8 @@ import { FaceDirection, IMoverController, IObjectMover, - ITileRawData + ITileRawData, + shouldReplay } from '@user/data-common'; import { IDynamicBlockSave, @@ -53,6 +54,7 @@ export class DynamicTile return this.tileRaw; } + @shouldReplay('Setting dynamic block num should be replayed.') set(num: number): void { this.tileNum = num; const data = this.state.tileStore.getData(num); @@ -65,6 +67,7 @@ export class DynamicTile this.restoreDefaultEvents(); } + @shouldReplay('Setting dynamic block position should be replayed.') setPos(x: number, y: number): void { this.x = x; this.y = y; @@ -81,10 +84,12 @@ export class DynamicTile } } + @shouldReplay('Transfering dynamic tile to static should be replayed.') toStatic(): IStaticTile | null { return this.layer.transferToStatic(this); } + @shouldReplay('Transfering dynamic tile to static should be replayed.') toStaticIfSafe(): IStaticTile | null { return this.layer.transferToStaticIfSafe(this); } diff --git a/packages-user/data-base/src/map/eventView.ts b/packages-user/data-base/src/map/eventView.ts index 40af7d1..cd6fbc0 100644 --- a/packages-user/data-base/src/map/eventView.ts +++ b/packages-user/data-base/src/map/eventView.ts @@ -1,5 +1,6 @@ import { logger } from '@motajs/common'; import { ILayerEventView } from './types'; +import { shouldReplay } from '@user/data-common'; export class LayerEventView implements ILayerEventView { /** 当前绑定的事件 */ @@ -43,6 +44,7 @@ export class LayerEventView implements ILayerEventView { } } + @shouldReplay('Setting layer event view should be replayed.') set(priority: number, event: string): void { if (this.store.has(priority)) { logger.warn(136, priority.toString()); @@ -52,6 +54,7 @@ export class LayerEventView implements ILayerEventView { this.updateDirtyEntry(priority, before); } + @shouldReplay('Deleting layer event view should be replayed.') delete(priority: number): void { const before = this.isEntryDirty(priority); this.store.delete(priority); diff --git a/packages-user/data-base/src/map/gameMap.ts b/packages-user/data-base/src/map/gameMap.ts index 024ad75..6b85008 100644 --- a/packages-user/data-base/src/map/gameMap.ts +++ b/packages-user/data-base/src/map/gameMap.ts @@ -19,7 +19,8 @@ import { ILocationIndexer, ITileStore, MapLocIndexer, - SaveCompression + SaveCompression, + shouldReplay } from '@user/data-common'; import { MapLayer } from './mapLayer'; @@ -56,6 +57,7 @@ export class GameMap extends Hookable implements IGameMap { this.indexer.setWidth(width); } + @shouldReplay('Adding game map layer should be replayed.') addLayer(): IMapLayer { const array = new Uint32Array(this.width * this.height); const layer = new MapLayer(array, this.width, this.height, this); @@ -69,6 +71,7 @@ export class GameMap extends Hookable implements IGameMap { return layer; } + @shouldReplay('Removing game map layer should be replayed.') removeLayer(layer: IMapLayer): void { this.layerList.delete(layer as IResizableMapLayer); const alias = this.layerAliasMap.get(layer); @@ -109,6 +112,7 @@ export class GameMap extends Hookable implements IGameMap { return this.layerAliasMap.get(layer); } + @shouldReplay('Resizing game map layer should be replayed.') resizeLayer( width: number, height: number, @@ -141,6 +145,7 @@ export class GameMap extends Hookable implements IGameMap { this.active = active; } + @shouldReplay("Setting game map's event layer should be replayed.") setEventLayer(layer: IMapLayer | null): void { if (!layer) { this.eventLayer = null; diff --git a/packages-user/data-base/src/map/mapLayer.ts b/packages-user/data-base/src/map/mapLayer.ts index 292cbbd..f32a33a 100644 --- a/packages-user/data-base/src/map/mapLayer.ts +++ b/packages-user/data-base/src/map/mapLayer.ts @@ -19,7 +19,8 @@ import { FaceDirection, IDataCommon, IRoleFaceBinder, - SaveCompression + SaveCompression, + shouldReplay } from '@user/data-common'; import { DynamicTile } from './dynamicTile'; import { LayerEventView } from './eventView'; @@ -233,6 +234,7 @@ export class MapLayer return x >= 0 && y >= 0 && x < this.width && y < this.height; } + @shouldReplay('Setting map layer block should be replayed.') setBlock(block: number, x: number, y: number): void { if (!this.inMap(x, y)) return; const index = y * this.width + x; @@ -254,6 +256,7 @@ export class MapLayer return this.mapArray[y * this.width + x]; } + @shouldReplay('Removing map layer block should be replayed.') removeBlock(x: number, y: number): number { if (!this.inMap(x, y)) { return -1; @@ -287,6 +290,7 @@ export class MapLayer }; } + @shouldReplay('Setting map layer data should be replayed.') putMapData(array: Uint32Array, x: number, y: number, width: number): void { if (array.length % width !== 0) { logger.warn(8); @@ -394,6 +398,7 @@ export class MapLayer return this.mapData; } + @shouldReplay('Setting map layer block direction should be replayed.') setStaticDirection(x: number, y: number, direction: FaceDirection): number { const tile = this.getTile(x, y); if (!tile) return -1; @@ -415,6 +420,7 @@ export class MapLayer //#region 动态图层操作 + @shouldReplay('Creating dynamic tile should be replayed.') createDynamic(num: number, x: number, y: number): IDynamicTile { const tile = new DynamicTile(num, x, y, this); const location = this.getLocationData(x, y); @@ -432,6 +438,7 @@ export class MapLayer return tile; } + @shouldReplay('Transfering static tile to dynamic should be replayed.') transferToDynamic( x: number, y: number, @@ -457,6 +464,7 @@ export class MapLayer return tile; } + @shouldReplay('Transfering dynamic tile to static should be replayed.') transferToStatic( tile: IDynamicTile, keepEvent: boolean = true @@ -477,6 +485,7 @@ export class MapLayer return this.getTile(x, y); } + @shouldReplay('Transfering dynamic tile to static should be replayed.') transferToStaticIfSafe( tile: IDynamicTile, keepEvent: boolean = true @@ -495,6 +504,7 @@ export class MapLayer return this.getTile(x, y); } + @shouldReplay('Deleting dynamic tile should be replayed.') async deleteDynamic(tile: IDynamicTile): Promise { if (!this.posTileMap.has(tile)) { logger.warn(130); @@ -513,6 +523,7 @@ export class MapLayer return this.posTileMap.keys(); } + @shouldReplay('Setting map layer block direction should be replayed.') setDynamicDirection(tile: IDynamicTile, direction: FaceDirection): number { const numBefore = tile.num(); tile.setFaceDirection(direction); @@ -524,6 +535,7 @@ export class MapLayer return tile.num(); } + @shouldReplay('Updating dynamic tile position should be replayed.') updateDynamicTile(tile: IDynamicTile): void { const oldPos = this.posTileMap.get(tile); if (oldPos) { @@ -542,6 +554,7 @@ export class MapLayer //#region 开关门 + @shouldReplay('Opening door should be replayed.') async openDoor(x: number, y: number): Promise { const index = y * this.width + x; const num = this.mapArray[index]; @@ -554,6 +567,7 @@ export class MapLayer this.setBlock(0, x, y); } + @shouldReplay('Closing door should be replayed.') async closeDoor(num: number, x: number, y: number): Promise { const index = y * this.width + x; const nowNum = this.mapArray[index]; @@ -573,10 +587,12 @@ export class MapLayer //#region 图层操作 + @shouldReplay('Setting z-index of map layer should be replayed.') setZIndex(zIndex: number): void { this.zIndex = zIndex; } + @shouldReplay('Setting role face binder of map layer should be replayed.') setFaceBinder(binder: IRoleFaceBinder | null): void { if (!binder) return; this.faceBinder = binder; @@ -616,6 +632,7 @@ export class MapLayer return new MapLayerHookController(this, hook); } + @shouldReplay('Resizing map layer should be replayed.') resize(width: number, height: number): void { if (this.width === width && this.height === height) { return; @@ -656,6 +673,7 @@ export class MapLayer }); } + @shouldReplay('Resizing map layer should be replayed.') resize2(width: number, height: number): void { this.layerDirty = true; this.pointEvents.clear(); diff --git a/packages-user/data-base/src/map/mapState.ts b/packages-user/data-base/src/map/mapState.ts index d1ccf20..fe78b00 100644 --- a/packages-user/data-base/src/map/mapState.ts +++ b/packages-user/data-base/src/map/mapState.ts @@ -1,5 +1,9 @@ -import { uniq } from 'lodash-es'; -import { IDataCommon, IMapRawData, SaveCompression } from '@user/data-common'; +import { + IDataCommon, + IMapRawData, + SaveCompression, + shouldReplay +} from '@user/data-common'; import { ITileStore } from '@user/data-common'; import { IGameMap, @@ -35,160 +39,9 @@ export class MapState implements IMapState { public readonly state: IDataCommon ) {} - /** - * 判断原始数据中的值是否为可枚举的对象容器 - * @param value 待判断的运行时值 - */ - private isRecord(value: unknown): value is Record { - return !!value && typeof value === 'object' && !Array.isArray(value); - } - - /** - * 判断字符串键是否能无歧义地转换为有限数字 - * @param key 原始对象键 - */ - private isNumericKey(key: string): boolean { - return key.trim() !== '' && Number.isFinite(Number(key)); - } - - /** - * 在创建地图前验证外部原始地图及事件结构 - * @param raw 待验证的楼层原始数据 - */ - private validateRaw(raw: IMapRawData): boolean { - const rawMap: unknown = raw.map; - const rawEvents: unknown = raw.events; - const rawAliases: unknown = raw.layerAlias; - if (!this.isRecord(rawMap)) { - logger.error(63, 'map', raw.floorId); - return false; - } - if (!this.isRecord(rawEvents)) { - logger.error(63, 'events', raw.floorId); - return false; - } - if (!this.isRecord(rawAliases)) { - logger.error(63, 'layerAlias', raw.floorId); - return false; - } - if (!Number.isInteger(raw.width) || raw.width <= 0) { - logger.error(64, 'width', raw.floorId); - return false; - } - - let length = 0; - for (const [zIndex, map] of Object.entries(rawMap)) { - if (!this.isNumericKey(zIndex)) { - logger.error( - 62, - 'layer', - raw.floorId, - 'IMapRawData.map', - zIndex - ); - return false; - } - if (!Array.isArray(map)) { - logger.error(64, 'map layer', raw.floorId); - return false; - } - if ( - !map.every( - value => - typeof value === 'number' && - Number.isFinite(value) && - Number.isInteger(value) && - value >= 0 - ) - ) { - logger.error(64, 'map value', raw.floorId); - return false; - } - if (length > 0 && map.length !== length) { - logger.error( - 60, - map.length.toString(), - length.toString(), - raw.floorId - ); - return false; - } - length = map.length; - - const alias = rawAliases[zIndex]; - if (typeof alias !== 'string') { - logger.error(64, 'layer alias', raw.floorId); - return false; - } - const events = rawEvents[zIndex]; - if (!this.isRecord(events)) { - logger.error(63, `events layer ${zIndex}`, raw.floorId); - return false; - } - for (const [index, tileEvents] of Object.entries(events)) { - if (!this.isNumericKey(index)) { - logger.error( - 62, - 'event', - raw.floorId, - 'IMapRawData.events', - index - ); - return false; - } - const indexNum = Number(index); - if ( - !Number.isInteger(indexNum) || - indexNum < 0 || - indexNum >= length - ) { - logger.error(64, 'event position', raw.floorId); - return false; - } - if (!this.isRecord(tileEvents)) { - logger.error(63, `events position ${index}`, raw.floorId); - return false; - } - for (const [priority, id] of Object.entries(tileEvents)) { - if (!this.isNumericKey(priority)) { - logger.error( - 62, - 'event', - raw.floorId, - 'IMapRawData.events', - priority - ); - return false; - } - if (typeof id !== 'string') { - logger.error(64, 'event id', raw.floorId); - return false; - } - } - } - } - if (length % raw.width !== 0) { - logger.error( - 61, - length.toString(), - raw.width.toString(), - raw.floorId - ); - return false; - } - for (const zIndex of Object.keys(rawEvents)) { - if (!this.isNumericKey(zIndex) || !Object.hasOwn(rawMap, zIndex)) { - logger.error(64, 'event layer', raw.floorId); - return false; - } - } - return true; - } - //#region 楼层管理 fromRaw(raw: IMapRawData): IGameMap | null { - if (!this.validateRaw(raw)) return null; let length = 0; const entries = Object.entries(raw.map); for (const [_, map] of Object.entries(raw.map)) { @@ -246,6 +99,7 @@ export class MapState implements IMapState { return state; } + @shouldReplay('Creating map should be replayed.') createMap(id: string, width: number, height: number): IGameMap { if (this.mapData.has(id)) { logger.warn(121, id); @@ -262,9 +116,10 @@ export class MapState implements IMapState { return state; } + @shouldReplay('Setting map list should be replayed.') setMapList(maps: string[]): void { this.maps.length = 0; - this.maps.push(...uniq(maps)); + this.maps.push(...maps); } getMap(id: string): IGameMap | null { @@ -285,6 +140,7 @@ export class MapState implements IMapState { this.areaList = areas; } + @shouldReplay('Activating area should be replayed.') activeArea(id: string): void { const idx = this.maps.indexOf(id); if (idx === -1) return; @@ -293,6 +149,7 @@ export class MapState implements IMapState { this.setAreaActive(area, true); } + @shouldReplay('Deactivating area should be replayed.') deactiveArea(id: string): void { const idx = this.maps.indexOf(id); if (idx === -1) return; @@ -357,6 +214,7 @@ export class MapState implements IMapState { return this.mapData.get(id)?.active ?? false; } + @shouldReplay('Setting map active status should be replayed.') setMapActiveStatus(id: string, active: boolean): void { this.mapData.get(id)?.setActiveStatus(active); } diff --git a/packages-user/data-base/src/map/mover.ts b/packages-user/data-base/src/map/mover.ts index aaf9891..bb3e49b 100644 --- a/packages-user/data-base/src/map/mover.ts +++ b/packages-user/data-base/src/map/mover.ts @@ -3,7 +3,8 @@ import { getFaceMovement, ObjectMover, ObjectMoveStep, - ObjectMoveType + ObjectMoveType, + shouldReplay } from '@user/data-common'; import { IDynamicTile } from './types'; import { DYNAMIC_MOVER_FACE } from '../shared'; @@ -31,6 +32,7 @@ export class DynamicTileMover extends ObjectMover { return Promise.resolve(DynamicMoveCode.Success); } + @shouldReplay('Dynamic tile moving step should be replayed.') protected onStepEnd( code: number, step: ObjectMoveStep, diff --git a/packages-user/data-base/src/map/staticTile.ts b/packages-user/data-base/src/map/staticTile.ts index 50b6960..27a6305 100644 --- a/packages-user/data-base/src/map/staticTile.ts +++ b/packages-user/data-base/src/map/staticTile.ts @@ -1,4 +1,4 @@ -import { ITileRawData } from '@user/data-common'; +import { ITileRawData, shouldReplay } from '@user/data-common'; import { IDynamicTile, IMapLayer, @@ -24,11 +24,13 @@ export class StaticTile return this.state.tileStore.getData(this.num()); } + @shouldReplay('Setting static tile num should be replayed.') set(num: number): void { this.layer.setBlock(num, this.locator.x, this.locator.y); this.restoreDefaultEvents(); } + @shouldReplay('Transfering static tile to dynamic should be replayed.') toDynamic(): IDynamicTile { return this.layer.transferToDynamic(this.locator.x, this.locator.y)!; } diff --git a/packages-user/data-base/src/map/tile.ts b/packages-user/data-base/src/map/tile.ts index b900dca..f882c76 100644 --- a/packages-user/data-base/src/map/tile.ts +++ b/packages-user/data-base/src/map/tile.ts @@ -4,7 +4,8 @@ import { IDataCommon, IDataCommonExtended, ISaveableContent, - ITileRawData + ITileRawData, + shouldReplay } from '@user/data-common'; import { LayerEventView } from './eventView'; import { @@ -52,6 +53,7 @@ export abstract class MapTileBase eventView.markPure(); } + @shouldReplay('Setting map tile face direction should be replayed.') setFaceDirection(direction: FaceDirection): number { const cur = this.num(); const next = this.layer.faceBinder.getFaceOf(cur, direction); diff --git a/packages-user/data-system/src/combat/combat.ts b/packages-user/data-system/src/combat/combat.ts index 364947a..2316532 100644 --- a/packages-user/data-system/src/combat/combat.ts +++ b/packages-user/data-system/src/combat/combat.ts @@ -23,6 +23,7 @@ import { IReadonlyEnemy, IStateBase } from '@user/data-base'; +import { shouldReplay } from '@user/data-common'; export class CombatFlow extends Hookable> @@ -193,6 +194,7 @@ export class CombatFlow return damage; } + @shouldReplay('Battle with enemy should be replayed.') battle( enemy: IEnemyView ): Promise | null> { @@ -214,6 +216,7 @@ export class CombatFlow return this.combatFlow(handler, eHandler); } + @shouldReplay('Battle with enemy should be replayed.') battleComputed( enemy: IReadonlyEnemy ): Promise | null> { diff --git a/packages-user/data-system/src/event/executor.ts b/packages-user/data-system/src/event/executor.ts index fd1d565..906c777 100644 --- a/packages-user/data-system/src/event/executor.ts +++ b/packages-user/data-system/src/event/executor.ts @@ -1,5 +1,5 @@ import { logger } from '@motajs/common'; -import { IGameEventStore, ignoreReplay } from '@user/data-common'; +import { IGameEventStore, shouldReplay } from '@user/data-common'; import { AnonTokyoInterpreter } from '@motajs/anon-tokyo'; import { EventExecuteMode, @@ -52,8 +52,7 @@ export class EventExecutor implements IGameEventExecutor { } } - // @ts-expect-error 泛型无法推导 - @ignoreReplay('事件一般由其他动作被动触发,不应计入录像') + @shouldReplay('Executing event should be replayed.') async execute( events: IGameEventInvocation[], param: IBlockEventParam diff --git a/packages-user/data-system/src/path/system.ts b/packages-user/data-system/src/path/system.ts index 4e0f286..ad02076 100644 --- a/packages-user/data-system/src/path/system.ts +++ b/packages-user/data-system/src/path/system.ts @@ -1,5 +1,5 @@ import { ITileLocator, logger } from '@motajs/common'; -import { IObjectMovable, IObjectMover } from '@user/data-common'; +import { IObjectMovable, IObjectMover, shouldReplay } from '@user/data-common'; import { IStateBase } from '@user/data-base'; import { PathFinder } from './finder'; import { @@ -79,12 +79,14 @@ export class PathfindingSystem implements IPathfindingSystem { return result; } + @shouldReplay('Moving hero by path finding system should be replayed.') moveTo(target: ITileLocator): IPathfindingController | null { const path = this.getPath(target); if (path.status !== PathfindingStatus.Success) return null; return this.startMove(path, false); } + @shouldReplay('Teleporting hero by path finding system should be replayed.') teleportTo(target: ITileLocator): IPathfindingController | null { const path = this.getPath(target); if (path.status !== PathfindingStatus.Success) return null;