feat(03-02): implement approved event built-ins

- Add map, hero, touch, and temporary event controls
- Register exactly eight built-ins through top-level event assembly
- Cover awaited behavior and safe failure with explicit fixtures
This commit is contained in:
unanmed 2026-09-10 16:25:02 +08:00
parent e9e89fdfdb
commit 3134537d30
9 changed files with 849 additions and 12 deletions

View File

@ -72,6 +72,7 @@ import { ILoadProgressTotal, LoadProgressTotal } from '@motajs/loader';
import { isNil } from 'lodash-es';
import { DirectionMapper, IDirectionMapper, logger } from '@motajs/common';
import { DefaultHeroMoveTopImpl } from './hero';
import { createEventBuiltinRegistrations } from './event';
export class CoreState implements ICoreState {
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
@ -207,7 +208,10 @@ export class CoreState implements ICoreState {
this.enemyContext = enemyContext;
// 游戏事件系统
const eventSystem = new GameEventSystem(this);
const eventSystem = new GameEventSystem(
this,
createEventBuiltinRegistrations()
);
this.eventSystem = eventSystem;
//#endregion

View File

@ -0,0 +1,230 @@
import { describe, expect, it } from 'vitest';
import {
FaceDirection,
IGameEvent,
ObjectMoveType,
EventTrigger,
TileType
} from '@user/data-common';
import {
BlockEventType,
IBlockEventEnv,
IGameEventInvocation
} from '@user/data-system';
import { CoreState } from '../core';
import { eventDeleteBlock, eventMoveBlock, eventSetBlock } from './map';
import { eventMoveHero, eventMoveHeroStep } from './hero';
import { eventInsertEvent, eventInsertEvents, eventTouchFront } from './event';
import { createEventBuiltinRegistrations } from './index';
import { EventBuiltinName } from './types';
interface EventFixture {
readonly state: CoreState;
readonly map: ReturnType<CoreState['maps']['createMap']>;
readonly layer: ReturnType<
ReturnType<CoreState['maps']['createMap']>['addLayer']
>;
readonly env: IBlockEventEnv;
}
function createFixture(): EventFixture {
const state = new CoreState();
state.tileStore.addTile({
num: 1,
id: 'floor',
events: {},
type: TileType.Terrain,
pass: { onlyEvents: false, outPass: 15, inPass: 15 },
eventPass: true
});
state.tileStore.addTile({
num: 2,
id: 'block',
events: {},
type: TileType.Terrain,
pass: { onlyEvents: false, outPass: 15, inPass: 15 },
eventPass: true
});
const map = state.maps.createMap('F1', 4, 1);
const layer = map.addLayer();
layer.setMapRef(new Uint32Array([1, 1, 1, 1]));
map.setEventLayer(layer);
state.hero.location.setFloor('F1');
state.hero.location.setPos(0, 0);
state.hero.location.mover.setFaceDir(FaceDirection.Right);
const env: IBlockEventEnv = {
state,
type: BlockEventType.CommonEvent,
trigger: EventTrigger.None,
heroLocator: state.hero.getLocation(),
heroFloor: 'F1',
triggerLocator: null,
tile: null,
layer,
map
};
return { state, map, layer, env };
}
function createEvent(
state: CoreState,
trigger: EventTrigger,
execute: (env: IBlockEventEnv) => Promise<void>
): IGameEvent<Record<string, never>, IBlockEventEnv, void> {
return {
interpreter: state.eventSystem.executor.interpreter,
trigger,
rawEvent: [],
compiled: null,
compile: () => null,
execute: async (_param, env) => execute(env),
setTrigger: () => {},
setRaw: () => {}
};
}
function invocation(id: string, env: IBlockEventEnv): IGameEventInvocation {
return { id, env };
}
describe('event built-ins', () => {
// 验证设置图块能解析环境图层并拒绝无效图块
it('sets a block and safely skips an unknown tile', () => {
const fixture = createFixture();
eventSetBlock({ x: 1, y: 0, tile: 'block' }, fixture.env);
expect(fixture.layer.getBlock(1, 0)).toBe(2);
expect(() =>
eventSetBlock({ x: 1, y: 0, tile: 'missing' }, fixture.env)
).not.toThrow();
});
// 验证动态图块移动完成后会按 safe 分支还原
it('moves a dynamic block and respects safe static transfer', async () => {
const fixture = createFixture();
await eventMoveBlock(
{
x: 1,
y: 0,
steps: [
{ type: ObjectMoveType.Teleport, x: 2, y: 0, rel: false }
],
safe: true
},
fixture.env
);
expect([...fixture.layer.getDynamicTilesAt(2, 0)]).toHaveLength(1);
expect(fixture.layer.getBlock(2, 0)).toBe(1);
});
// 验证删除图块会等待并清理静态与动态图块
it('deletes static and dynamic blocks at a coordinate', async () => {
const fixture = createFixture();
fixture.layer.transferToDynamic(1, 0);
await eventDeleteBlock({ x: 1, y: 0 }, fixture.env);
expect(fixture.layer.getBlock(1, 0)).toBe(0);
expect([...fixture.layer.getDynamicTilesAt(1, 0)]).toHaveLength(0);
});
// 验证勇士移动序列和向前一步都等待移动结束
it('awaits hero sequence and forward-step movement', async () => {
const fixture = createFixture();
await eventMoveHero(
{
steps: [{ type: ObjectMoveType.Dir, move: FaceDirection.Right }]
},
fixture.env
);
expect(fixture.state.hero.location.x).toBe(1);
await eventMoveHeroStep({}, fixture.env);
expect(fixture.state.hero.location.x).toBe(2);
});
// 验证面前事件按 onTouch 触发且不移动勇士
it('triggers front onTouch events without moving the hero', async () => {
const fixture = createFixture();
const calls: IGameEventInvocation[] = [];
fixture.layer.event(1, 0)!.set(10, 'touch');
fixture.state.eventStore.addEvent(
'touch',
createEvent(fixture.state, EventTrigger.OnTouch, async env => {
calls.push(invocation('touch', env));
})
);
await eventTouchFront({}, fixture.env);
expect(calls).toHaveLength(1);
expect(calls[0].env.trigger).toBe(EventTrigger.OnTouch);
expect(fixture.state.hero.location.x).toBe(0);
});
// 验证临时事件序列和单事件都会按顺序等待执行
it('awaits temporary event sequences and single event insertion', async () => {
const fixture = createFixture();
const calls: string[] = [];
fixture.state.eventStore.addEvent(
'first',
createEvent(fixture.state, EventTrigger.None, async () => {
await Promise.resolve();
calls.push('first');
})
);
fixture.state.eventStore.addEvent(
'second',
createEvent(fixture.state, EventTrigger.None, async () => {
calls.push('second');
})
);
await eventInsertEvents(
{ ids: ['first', 'second', 'missing'] },
fixture.env
);
await eventInsertEvent({ id: 'first' }, fixture.env);
expect(calls).toEqual(['first', 'second', 'first']);
});
// 验证默认注册项只包含批准的八个稳定名称
it('registers exactly the approved built-ins in AnonTokyo', () => {
const fixture = createFixture();
const names = createEventBuiltinRegistrations().map(item => item.name);
expect(names).toEqual([
EventBuiltinName.SetBlock,
EventBuiltinName.MoveBlock,
EventBuiltinName.DeleteBlock,
EventBuiltinName.MoveHero,
EventBuiltinName.MoveHeroStep,
EventBuiltinName.TouchFront,
EventBuiltinName.InsertEvents,
EventBuiltinName.InsertEvent
]);
for (const name of names) {
expect(
fixture.state.eventSystem.executor.interpreter.getBuiltInFunction(
name
)
).toMatchObject({
name,
func: expect.any(Function)
});
}
});
// 验证缺失地图、勇士和事件 id 时所有函数都安全返回
it('safely skips missing targets and event ids', async () => {
const fixture = createFixture();
const missingEnv: IBlockEventEnv = {
...fixture.env,
map: null,
layer: null,
heroFloor: 'missing'
};
await expect(
eventMoveBlock({ x: 0, y: 0, steps: [] }, missingEnv)
).resolves.toBeUndefined();
await expect(
eventDeleteBlock({ x: 0, y: 0 }, missingEnv)
).resolves.toBeUndefined();
await expect(eventTouchFront({}, missingEnv)).resolves.toBeUndefined();
await expect(
eventInsertEvent({ id: 'missing' }, fixture.env)
).resolves.toBeUndefined();
});
});

View File

@ -0,0 +1,201 @@
import {
IBlockEventEnv,
IBlockEventParam,
IGameEventExecutor,
IGameEventInvocation,
IGameEventSystem,
BlockEventType
} from '@user/data-system';
import {
FaceDirection,
EventTrigger,
IGameEventStore
} from '@user/data-common';
import {
IStateBase,
IReadonlyTileBase as IReadonlyMapTileBase
} from '@user/data-base';
import { getPossibleLayer } from './map';
import {
IInsertEventEventParam,
IInsertEventsEventParam,
ITouchFrontEventParam
} from './types';
interface IEventSource {
readonly priority: number;
readonly id: string;
readonly type: BlockEventType;
readonly tile: IReadonlyMapTileBase | null;
}
interface IEventState extends IStateBase {
readonly eventSystem: IGameEventSystem;
}
const EVENT_INSERT_MAX_DEPTH = 32;
const eventInsertDepth: WeakMap<IBlockEventEnv, number> = new WeakMap();
/** 判断状态是否包含事件执行器 */
function hasEventSystem(state: IStateBase): state is IEventState {
return 'eventSystem' in state;
}
/** 从环境获取事件执行器和事件存储器 */
function getEventRuntime(env: IBlockEventEnv): {
readonly executor: IGameEventExecutor;
readonly store: IGameEventStore;
} | null {
if (!hasEventSystem(env.state)) return null;
const store = env.state.eventStore;
if (!store) return null;
return {
executor: env.state.eventSystem.executor,
store
};
}
/** 按坐标收集事件来源并保留其触发环境 */
function collectInvocations(
env: IBlockEventEnv,
layer: NonNullable<IBlockEventEnv['layer']>,
x: number,
y: number,
trigger: EventTrigger
): IGameEventInvocation[] {
const pointSources: IEventSource[] = [];
const tileSources: IEventSource[] = [];
const point = layer.getPointEvent(x, y);
const location = layer.getLocationData(x, y);
if (point) {
for (const [priority, id] of point) {
pointSources.push({
priority,
id,
type: BlockEventType.PointEvent,
tile: null
});
}
}
if (location) {
if (location.static) {
for (const [priority, id] of location.static.tileEvent().get()) {
tileSources.push({
priority,
id,
type: BlockEventType.TileEvent,
tile: location.static
});
}
}
for (const tile of location.dynamics) {
for (const [priority, id] of tile.tileEvent().get()) {
tileSources.push({
priority,
id,
type: BlockEventType.TileEvent,
tile
});
}
}
}
pointSources.sort((a, b) => b.priority - a.priority);
tileSources.sort((a, b) => b.priority - a.priority);
const hero = env.state.hero.getLocation();
const invocations: IGameEventInvocation[] = [];
for (const source of [...pointSources, ...tileSources]) {
const sourceEnv: IBlockEventEnv = {
state: env.state,
type: source.type,
trigger,
heroLocator: hero,
heroFloor: env.heroFloor,
triggerLocator: { x, y },
tile: source.tile,
layer,
map: layer.map
};
invocations.push({ id: source.id, env: sourceEnv });
}
return invocations;
}
/** 触发勇士正面的 onTouch 事件 */
export async function eventTouchFront(
_param: ITouchFrontEventParam,
env: IBlockEventEnv
): Promise<void> {
if (!env.state.hero) return;
const layer = getPossibleLayer(env);
if (!layer) return;
const runtime = getEventRuntime(env);
if (!runtime) return;
const hero = env.state.hero.getLocation();
const mover = env.state.hero.location.mover;
const direction = mover.tile.getCurrentFaceDirection();
if (direction === FaceDirection.Unknown) return;
const movement = mover.faceHandler.movement(direction);
const x = hero.x + movement.x;
const y = hero.y + movement.y;
if (!layer.inMap(x, y)) return;
const invocations = collectInvocations(
env,
layer,
x,
y,
EventTrigger.OnTouch
);
if (invocations.length === 0) return;
await runtime.executor.execute<void>(invocations, { custom: {} });
}
/** 过滤存在的事件 id 并构造临时事件调用 */
function collectEventInvocations(
ids: readonly string[],
env: IBlockEventEnv,
store: IGameEventStore
): IGameEventInvocation[] {
const invocations: IGameEventInvocation[] = [];
for (const id of ids) {
if (
!id ||
!store.getEvent<IBlockEventParam, IBlockEventEnv, void>(id)
) {
continue;
}
invocations.push({ id, env });
}
return invocations;
}
/** 临时按顺序执行指定事件 */
export async function eventInsertEvents(
param: IInsertEventsEventParam,
env: IBlockEventEnv
): Promise<void> {
if (param.ids.length === 0) return;
const runtime = getEventRuntime(env);
if (!runtime) return;
const depth = eventInsertDepth.get(env) ?? 0;
if (depth >= EVENT_INSERT_MAX_DEPTH) return;
const invocations = collectEventInvocations(param.ids, env, runtime.store);
if (invocations.length === 0) return;
eventInsertDepth.set(env, depth + 1);
try {
await runtime.executor.execute<void>(invocations, { custom: {} });
} finally {
eventInsertDepth.delete(env);
}
}
/** 临时执行指定事件 */
export async function eventInsertEvent(
param: IInsertEventEventParam,
env: IBlockEventEnv
): Promise<void> {
if (!param.id) return;
await eventInsertEvents({ ids: [param.id] }, env);
}

View File

@ -0,0 +1,93 @@
import { IBlockEventEnv } from '@user/data-system';
import { IHeroLocation, IHeroMover } from '@user/data-base';
import {
IMoverController,
IObjectMovable,
IObjectMover,
ObjectMoveStep,
ObjectMoveType,
ObjectSpecialStep
} from '@user/data-common';
import { IMoveHeroEventParam, IMoveHeroStepEventParam } from './types';
/** 启动勇士移动并等待其完整结束 */
export function appendMoveSteps<T extends IObjectMovable>(
mover: IObjectMover<T>,
steps: readonly ObjectMoveStep[]
): void {
for (const step of steps) {
switch (step.type) {
case ObjectMoveType.Dir:
mover.step(step.move);
break;
case ObjectMoveType.DirFace:
mover.stepFace(step.move, step.face);
break;
case ObjectMoveType.Speed:
mover.speed(step.value);
break;
case ObjectMoveType.Face:
mover.face(step.value);
break;
case ObjectMoveType.Special:
if (step.direction === ObjectSpecialStep.Forward) {
mover.forward();
} else {
mover.backward();
}
break;
case ObjectMoveType.AnimDir:
mover.animDir(step.dir);
break;
case ObjectMoveType.Teleport:
mover.tp(step.x, step.y, step.rel);
break;
case ObjectMoveType.Jump:
mover.jump(step.x, step.y, step.rel);
break;
}
}
}
/** 启动勇士移动并等待其完整结束 */
async function startHeroMove(
mover: IHeroMover<IHeroLocation>,
steps: readonly ObjectMoveStep[]
): Promise<void> {
if (mover.moving) return;
appendMoveSteps(mover, steps);
const controller: Readonly<IMoverController> | null = mover.start();
if (!controller) return;
await controller.onEnd;
}
/** 获取可以执行移动的勇士移动器 */
function getHeroMover(env: IBlockEventEnv): IHeroMover<IHeroLocation> | null {
const hero = env.state.hero;
if (!hero) return null;
return hero.location.mover;
}
/** 按指定移动序列移动勇士 */
export async function eventMoveHero(
param: IMoveHeroEventParam,
env: IBlockEventEnv
): Promise<void> {
const mover = getHeroMover(env);
if (!mover) return;
await startHeroMove(mover, param.steps);
}
/** 让勇士沿当前朝向移动一步 */
export async function eventMoveHeroStep(
_param: IMoveHeroStepEventParam,
env: IBlockEventEnv
): Promise<void> {
const mover = getHeroMover(env);
if (!mover) return;
if (mover.moving) return;
mover.forward(1);
const controller = mover.start();
if (!controller) return;
await controller.onEnd;
}

View File

@ -0,0 +1,193 @@
import { BuiltInFunction } from 'anon-tokyo';
import { IBlockEventEnv } from '@user/data-system';
import { ObjectMoveStep } from '@user/data-common';
import { eventDeleteBlock, eventMoveBlock, eventSetBlock } from './map';
import { eventMoveHero, eventMoveHeroStep } from './hero';
import { eventInsertEvent, eventInsertEvents, eventTouchFront } from './event';
import {
EventBuiltinName,
IDeleteBlockEventParam,
IInsertEventEventParam,
IInsertEventsEventParam,
IMoveBlockEventParam,
IMoveHeroEventParam,
ISetBlockEventParam
} from './types';
function readProperty(value: object, key: string): unknown {
const descriptor = Object.getOwnPropertyDescriptor(value, key);
return descriptor ? descriptor.value : undefined;
}
function readNumber(value: object, key: string): number | null {
const result = readProperty(value, key);
return typeof result === 'number' ? result : null;
}
function readString(value: object, key: string): string | null {
const result = readProperty(value, key);
return typeof result === 'string' ? result : null;
}
function readBoolean(value: object, key: string): boolean | undefined {
const result = readProperty(value, key);
return typeof result === 'boolean' ? result : undefined;
}
function isObjectMoveStep(value: unknown): value is ObjectMoveStep {
if (!value || typeof value !== 'object') return false;
return typeof readProperty(value, 'type') === 'number';
}
function readMoveSteps(
value: object,
key: string
): readonly ObjectMoveStep[] | null {
const result = readProperty(value, key);
if (!Array.isArray(result)) return null;
if (!result.every(isObjectMoveStep)) return null;
return result;
}
function readStringArray(value: object, key: string): readonly string[] | null {
const result = readProperty(value, key);
if (!Array.isArray(result)) return null;
if (!result.every(item => typeof item === 'string')) return null;
return result;
}
function parseSetBlock(param: object): ISetBlockEventParam | null {
const x = readNumber(param, 'x');
const y = readNumber(param, 'y');
const tile = readProperty(param, 'tile');
if (x === null || y === null) return null;
if (typeof tile !== 'number' && typeof tile !== 'string') return null;
return { x, y, tile };
}
function parseMoveBlock(param: object): IMoveBlockEventParam | null {
const x = readNumber(param, 'x');
const y = readNumber(param, 'y');
const steps = readMoveSteps(param, 'steps');
if (x === null || y === null || !steps) return null;
return { x, y, steps, safe: readBoolean(param, 'safe') };
}
function parseDeleteBlock(param: object): IDeleteBlockEventParam | null {
const x = readNumber(param, 'x');
const y = readNumber(param, 'y');
if (x === null || y === null) return null;
return { x, y };
}
function parseMoveHero(param: object): IMoveHeroEventParam | null {
const steps = readMoveSteps(param, 'steps');
return steps ? { steps } : null;
}
function parseInsertEvents(param: object): IInsertEventsEventParam | null {
const ids = readStringArray(param, 'ids');
return ids ? { ids } : null;
}
function parseInsertEvent(param: object): IInsertEventEventParam | null {
const id = readString(param, 'id');
return id === null ? null : { id };
}
type BuiltinParameter = Parameters<BuiltInFunction['func']>[0];
type BuiltinEnvironment = Parameters<BuiltInFunction['func']>[1];
function isBlockEventEnv(value: BuiltinEnvironment): value is IBlockEventEnv {
return (
'state' in value &&
'type' in value &&
'trigger' in value &&
'heroLocator' in value &&
'heroFloor' in value &&
'triggerLocator' in value &&
'tile' in value &&
'layer' in value &&
'map' in value
);
}
type EventBuiltinHandler = (
param: BuiltinParameter,
env: IBlockEventEnv
) => void | Promise<void>;
function createBuiltin(handler: EventBuiltinHandler): BuiltInFunction['func'] {
return (param: BuiltinParameter, env: BuiltinEnvironment) => {
if (!isBlockEventEnv(env)) return;
return handler(param, env);
};
}
/** 创建八个批准事件 built-in 的稳定注册项 */
export function createEventBuiltinRegistrations(): ReadonlyArray<BuiltInFunction> {
const registrations: BuiltInFunction[] = [
{
name: EventBuiltinName.SetBlock,
func: createBuiltin((param, env) => {
const parsed = parseSetBlock(param);
if (!parsed) return;
return eventSetBlock(parsed, env);
})
},
{
name: EventBuiltinName.MoveBlock,
func: createBuiltin((param, env) => {
const parsed = parseMoveBlock(param);
if (!parsed) return;
return eventMoveBlock(parsed, env);
})
},
{
name: EventBuiltinName.DeleteBlock,
func: createBuiltin((param, env) => {
const parsed = parseDeleteBlock(param);
if (!parsed) return;
return eventDeleteBlock(parsed, env);
})
},
{
name: EventBuiltinName.MoveHero,
func: createBuiltin((param, env) => {
const parsed = parseMoveHero(param);
if (!parsed) return;
return eventMoveHero(parsed, env);
})
},
{
name: EventBuiltinName.MoveHeroStep,
func: createBuiltin((_param, env) => eventMoveHeroStep({}, env))
},
{
name: EventBuiltinName.TouchFront,
func: createBuiltin((_param, env) => eventTouchFront({}, env))
},
{
name: EventBuiltinName.InsertEvents,
func: createBuiltin((param, env) => {
const parsed = parseInsertEvents(param);
if (!parsed) return;
return eventInsertEvents(parsed, env);
})
},
{
name: EventBuiltinName.InsertEvent,
func: createBuiltin((param, env) => {
const parsed = parseInsertEvent(param);
if (!parsed) return;
return eventInsertEvent(parsed, env);
})
}
];
return registrations;
}
export * from './event';
export * from './hero';
export * from './map';
export * from './types';

View File

@ -1,14 +1,19 @@
import { IBlockEventEnv } from '@user/data-system';
import { ISetBlockEventParam } from './types';
import {
IDeleteBlockEventParam,
IMoveBlockEventParam,
ISetBlockEventParam
} from './types';
import { isNil } from 'lodash-es';
import { logger } from '@motajs/common';
import { IGameMap } from '@user/data-base';
import { IGameMap, IMapLayer } from '@user/data-base';
import { appendMoveSteps } from './hero';
/**
*
* @param env
*/
function getPossibleMap(env: IBlockEventEnv): IGameMap | null {
export function getPossibleMap(env: IBlockEventEnv): IGameMap | null {
if (env.map) return env.map;
if (env.layer) return env.layer.map;
@ -18,21 +23,70 @@ function getPossibleMap(env: IBlockEventEnv): IGameMap | null {
return null;
}
/** 通过环境参量获取可能的事件图层 */
export function getPossibleLayer(env: IBlockEventEnv): IMapLayer | null {
if (env.layer) return env.layer;
const map = getPossibleMap(env);
if (map?.eventLayer) return map.eventLayer;
return null;
}
export function eventSetBlock(
param: ISetBlockEventParam,
env: IBlockEventEnv
): void {
const map = getPossibleMap(env);
if (!map) return;
const layer = map.eventLayer;
const layer = getPossibleLayer(env);
if (!layer) return;
if (!layer.inMap(param.x, param.y)) return;
const num = env.state.tileStore.num(param.tile);
if (isNil(num)) {
logger.warn(1001);
return;
}
return layer.setBlock(num, param.x, param.y);
layer.setBlock(num, param.x, param.y);
}
/** 将动态图块移动完成后还原为静态图块 */
export async function eventMoveBlock(
param: IMoveBlockEventParam,
env: IBlockEventEnv
): Promise<void> {
const layer = getPossibleLayer(env);
if (!layer || !layer.inMap(param.x, param.y)) return;
if (!layer.getTile(param.x, param.y)) return;
const dynamic = layer.transferToDynamic(param.x, param.y);
if (!dynamic) return;
if (dynamic.mover.moving) return;
appendMoveSteps(dynamic.mover, param.steps);
const controller = dynamic.mover.start();
if (!controller) return;
await controller.onEnd;
if (param.safe) {
layer.transferToStaticIfSafe(dynamic);
} else {
layer.transferToStatic(dynamic);
}
}
/** 删除目标坐标的静态图块和动态图块 */
export async function eventDeleteBlock(
param: IDeleteBlockEventParam,
env: IBlockEventEnv
): Promise<void> {
const layer = getPossibleLayer(env);
if (!layer || !layer.inMap(param.x, param.y)) return;
const dynamics = [...layer.getDynamicTilesAt(param.x, param.y)];
await Promise.all(dynamics.map(tile => layer.deleteDynamic(tile)));
if (layer.getTile(param.x, param.y)) {
layer.setBlock(0, param.x, param.y);
}
}

View File

@ -1,3 +1,5 @@
import { ObjectMoveStep } from '@user/data-common';
//#region 地图控制
/** 事件:设置图块 */
@ -10,12 +12,68 @@ export interface ISetBlockEventParam {
readonly tile: number | string;
}
/** 事件:移动图块 */
export interface IMoveBlockEventParam {
/** 起始横坐标 */
readonly x: number;
/** 起始纵坐标 */
readonly y: number;
/** 移动步骤 */
readonly steps: readonly ObjectMoveStep[];
/** 是否仅在目标位置安全时转回静态图块 */
readonly safe?: boolean;
}
/** 事件:删除图块 */
export interface IDeleteBlockEventParam {
/** 横坐标 */
readonly x: number;
/** 纵坐标 */
readonly y: number;
}
//#endregion
//#region 玩家控制
/** 事件:按步骤移动勇士 */
export interface IMoveHeroEventParam {
/** 移动步骤 */
readonly steps: readonly ObjectMoveStep[];
}
/** 事件:向前移动一步 */
export interface IMoveHeroStepEventParam {}
/** 事件:触发勇士面前的 onTouch */
export interface ITouchFrontEventParam {}
//#endregion
//#region 事件控制
/** 事件:临时插入多个事件 */
export interface IInsertEventsEventParam {
/** 事件 id 顺序 */
readonly ids: readonly string[];
}
/** 事件:临时插入一个事件 */
export interface IInsertEventEventParam {
/** 事件 id */
readonly id: string;
}
/** 内建函数的稳定注册名称 */
export const enum EventBuiltinName {
SetBlock = 'eventSetBlock',
MoveBlock = 'eventMoveBlock',
DeleteBlock = 'eventDeleteBlock',
MoveHero = 'eventMoveHero',
MoveHeroStep = 'eventMoveHeroStep',
TouchFront = 'eventTouchFront',
InsertEvents = 'eventInsertEvents',
InsertEvent = 'eventInsertEvent'
}
//#endregion

View File

@ -1,4 +1,5 @@
export * from './enemy';
export * from './event';
export * from './hero';
export * from './core';

View File

@ -1,6 +1,6 @@
import { IStateBase } from '@user/data-base';
import { IGameEventStore } from '@user/data-common';
import { AnonTokyoInterpreter } from 'anon-tokyo';
import { AnonTokyoInterpreter, BuiltInFunction } from 'anon-tokyo';
import { EventExecutor } from './executor';
import { IGameEventExecutor, IGameEventSystem } from './types';
@ -8,10 +8,13 @@ export class GameEventSystem implements IGameEventSystem {
readonly executor: IGameEventExecutor;
store: IGameEventStore | null;
constructor(readonly state: IStateBase) {
constructor(
readonly state: IStateBase,
builtins: ReadonlyArray<BuiltInFunction> = []
) {
this.store = state.eventStore;
const interpreter = new AnonTokyoInterpreter({
builtInFunctions: [],
builtInFunctions: [...builtins],
globalFunctions: []
});
this.executor = new EventExecutor(interpreter, () => this.store);