feat(03-07): wire serialized event loading boundary

- Register validated serialized events through the CoreState load seam
- Bind raw map event ids with MapState.fromRaw before replay execution
This commit is contained in:
unanmed 2026-09-10 20:12:47 +08:00
parent 8f3f5cad7d
commit a111b8dcd4
3 changed files with 111 additions and 4 deletions

View File

@ -21,7 +21,8 @@ import {
ItemStore,
IMapStore,
MapStore,
IReplaySystem
IReplaySystem,
IMapRawData
} from '@user/data-common';
import {
EnemyManager,
@ -70,8 +71,11 @@ import {
} from './shared';
import {
createLegacyDependencies,
ILegacyLoadData
ILegacyLoadData,
ILegacySerializedLoadData,
LOAD_SERIALIZED_DATA
} from './legacy/dependencies';
import { registerSerializedEvents } from './legacy/events';
import { ILoadProgressTotal, LoadProgressTotal } from '@motajs/loader';
import { isNil } from 'lodash-es';
import { DirectionMapper, IDirectionMapper, logger } from '@motajs/common';
@ -163,7 +167,6 @@ export class CoreState implements ICoreState {
const eventStore = new GameEventStore();
this.eventStore = eventStore;
this.directionMapper = new DirectionMapper();
// TODO: 后续在此初始化路径注册外部序列化事件定义与地图事件 id 绑定。
//#endregion
@ -261,7 +264,21 @@ export class CoreState implements ICoreState {
this.initTileStore(data.tiles);
this.initItemStore(data.items);
this.initEnemyManager(data.enemies);
this.initMapState(data.floors, data.maps);
if (data.serialized) {
this[LOAD_SERIALIZED_DATA](data.serialized);
} else {
this.initMapState(data.floors, data.maps);
}
}
/** 经内部加载边界注册序列化事件并绑定原始地图事件 id */
[LOAD_SERIALIZED_DATA](data: ILegacySerializedLoadData): void {
registerSerializedEvents(
this.eventStore,
this.eventSystem.executor.interpreter,
data.events
);
this.initRawMapState(data.maps);
}
/**
@ -421,6 +438,20 @@ export class CoreState implements ICoreState {
this.maps.compareWith(reference);
}
private initRawMapState(data: readonly IMapRawData[]): void {
const reference = new Map<string, Map<number, Uint32Array>>();
for (const raw of data) {
const state = this.maps.fromRaw(raw);
if (!state) continue;
const ref = new Map<number, Uint32Array>();
for (const [zIndex, map] of Object.entries(raw.map)) {
ref.set(Number(zIndex), new Uint32Array(map));
}
reference.set(raw.floorId, ref);
}
this.maps.compareWith(reference);
}
//#endregion
//#region 存档方法

View File

@ -4,6 +4,7 @@ import {
IEnemyAttr,
ISaveSystem,
ITileLegacyConverter,
IMapRawData,
MemorySaveSystem,
SaveSystem
} from '@user/data-common';
@ -12,6 +13,16 @@ import { IStateSystem } from '@user/data-system';
import { EnemyLegacyBridge } from '../enemy/legacy';
import { ItemLegacyBridge, LegacyItemData } from './item';
import { LegacyTileData, TileLegacyBridge } from './tile';
import { ISerializedEventDefinitions } from './events';
/** CoreState 内部显式数据加载所需的序列化事件与原始地图 */
export interface ILegacySerializedLoadData {
readonly events: ISerializedEventDefinitions;
readonly maps: readonly IMapRawData[];
}
/** 仅供 CoreState 与 legacy 数据源之间使用的加载方法标识 */
export const LOAD_SERIALIZED_DATA = Symbol('loadSerializedData');
export interface ILegacyLoadData {
readonly tiles: typeof core.maps.blocksInfo;
@ -19,6 +30,7 @@ export interface ILegacyLoadData {
readonly enemies: Record<EnemyIds, Enemy>;
readonly floors: FloorIds[];
readonly maps: Record<FloorIds, ResolvedFloor>;
readonly serialized?: ILegacySerializedLoadData;
}
export interface ILegacyDependencies {

View File

@ -0,0 +1,64 @@
import { EventTrigger, GameEvent, IGameEventStore } from '@user/data-common';
import { AnonTokyoInterpreter, Statement } from 'anon-tokyo';
/** 旧样板内部加载边界使用的序列化事件定义 */
export interface ISerializedEventData {
readonly trigger: EventTrigger;
readonly rawEvent: Statement[];
}
/** 事件 id 到序列化事件定义的映射 */
export interface ISerializedEventDefinitions {
readonly [id: string]: ISerializedEventData;
}
interface IRuntimeStatement {
readonly type?: unknown;
}
function isStatement(value: unknown): value is Statement {
if (!value || typeof value !== 'object') return false;
const statement = value as IRuntimeStatement;
return (
typeof statement.type === 'number' &&
Number.isInteger(statement.type) &&
statement.type >= 0 &&
statement.type <= 10
);
}
function isEventTrigger(value: unknown): value is EventTrigger {
return (
typeof value === 'number' &&
Number.isInteger(value) &&
value >= EventTrigger.None &&
value <= EventTrigger.OnAfterChangeFloor
);
}
function isSerializedEventData(value: unknown): value is ISerializedEventData {
if (!value || typeof value !== 'object') return false;
const data = value as ISerializedEventData;
return isEventTrigger(data.trigger) && Array.isArray(data.rawEvent)
? data.rawEvent.every(isStatement)
: false;
}
/** 将内部序列化事件定义转换为带有统一解释器的游戏事件实例 */
export function registerSerializedEvents(
store: IGameEventStore,
interpreter: AnonTokyoInterpreter,
definitions: ISerializedEventDefinitions
): void {
if (!definitions || typeof definitions !== 'object') return;
for (const [id, data] of Object.entries(definitions)) {
if (!id || !isSerializedEventData(data)) continue;
const event = new GameEvent<
Record<string, any>,
Record<string, any>,
any
>(interpreter, data.rawEvent);
event.setTrigger(data.trigger);
store.addEvent(id, event);
}
}