From f0fd2f54a14137a500a073f1af9e48577d8565eb Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Sun, 13 Sep 2026 19:17:32 +0800
Subject: [PATCH] refactor(event): class-based built-ins, shared utils, single
registration
- rewrite every built-in as EventXxx implements BuiltInFunction
(setBlock/moveBlock/deleteBlock/moveHero/moveHeroStep/touchFront/
insertEvents/insertEvent), matching the EventSetBlock reference style
- move shared helpers to event/utils.ts (getPossibleMap, getPossibleLayer,
getEventExecutor, enter/exitEventInsert); utils is not exported via index
- move per-class helpers into private methods (EventTouchFront and
EventInsertEvents collectInvocations)
- use IObjectMover.push for move sequences instead of manual step calls
- registrations.ts: one createEventRegistrations() writing the eight entries
in order, categories separated by single-line comments
- remove EventBuiltinName; classes own short string names
- update event tests; core.ts uses createEventRegistrations
---
packages-user/data-state/src/core.ts | 4 +-
.../data-state/src/event/event.test.ts | 229 ++++-------
packages-user/data-state/src/event/event.ts | 165 +++-----
packages-user/data-state/src/event/hero.ts | 389 ++++++------------
packages-user/data-state/src/event/index.ts | 7 +-
packages-user/data-state/src/event/map.ts | 110 ++---
.../data-state/src/event/registrations.ts | 67 +--
packages-user/data-state/src/event/types.ts | 14 +-
packages-user/data-state/src/event/utils.ts | 51 ++-
.../data-state/src/replay/commands.test.ts | 58 +--
.../data-state/test/fixtures/closed-loop.ts | 4 +-
11 files changed, 397 insertions(+), 701 deletions(-)
diff --git a/packages-user/data-state/src/core.ts b/packages-user/data-state/src/core.ts
index b574f34..6ce3141 100644
--- a/packages-user/data-state/src/core.ts
+++ b/packages-user/data-state/src/core.ts
@@ -81,7 +81,7 @@ import {
} from './legacy';
import { isNil } from 'lodash-es';
import { DefaultHeroMoveTopImpl } from './hero';
-import { createEventBuiltinRegistrations } from './event/registrations';
+import { createEventRegistrations } from './event/registrations';
import {
ReplayCommandCode,
ReplayEquipCommand,
@@ -222,7 +222,7 @@ export class CoreState implements ICoreState {
// 游戏事件系统
const eventSystem = new GameEventSystem(
this,
- createEventBuiltinRegistrations()
+ createEventRegistrations()
);
this.eventSystem = eventSystem;
diff --git a/packages-user/data-state/src/event/event.test.ts b/packages-user/data-state/src/event/event.test.ts
index d3dc689..a44c971 100644
--- a/packages-user/data-state/src/event/event.test.ts
+++ b/packages-user/data-state/src/event/event.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
-import { Statement, StatementType } from 'anon-tokyo';
+import { Statement, StatementType } from '@motajs/anon-tokyo';
import {
FaceDirection,
IGameEvent,
@@ -15,24 +15,20 @@ import {
} from '@user/data-system';
import { CoreState } from '../core';
import {
- createControlEventBuiltinRegistrations,
- createEventBuiltinRegistrations,
- createHeroEventBuiltinRegistrations,
- createMapEventBuiltinRegistrations,
- DeleteBlockEventRegistration,
- InsertEventEventRegistration,
- InsertEventsEventRegistration,
- MoveBlockEventRegistration,
- MoveHeroEventRegistration,
- MoveHeroStepEventRegistration,
- SetBlockEventRegistration,
- TouchFrontEventRegistration
+ createEventRegistrations,
+ EventDeleteBlock,
+ EventInsertEvent,
+ EventInsertEvents,
+ EventMoveBlock,
+ EventMoveHero,
+ EventMoveHeroStep,
+ EventSetBlock,
+ EventTouchFront
} from './index';
-import { SetBlockEventRegistration as MapSetBlockEventRegistration } from './map';
-import { TouchFrontEventRegistration as HeroTouchFrontEventRegistration } from './hero';
-import { InsertEventEventRegistration as ControlInsertEventEventRegistration } from './event';
+import { EventSetBlock as MapEventSetBlock } from './map';
+import { EventTouchFront as HeroEventTouchFront } from './hero';
+import { EventInsertEvent as ControlEventInsertEvent } from './event';
import * as dataStateRoot from '../index';
-import { EventBuiltinName } from './types';
interface EventFixture {
readonly state: CoreState;
@@ -43,22 +39,18 @@ interface EventFixture {
readonly env: IBlockEventEnv;
}
-type RegisteredBuiltin = ReturnType<
- typeof createEventBuiltinRegistrations
->[number];
+type RegisteredBuiltin = ReturnType[number];
function invokeBuiltin(
registration: RegisteredBuiltin,
param: TParam,
env: IBlockEventEnv
): Promise {
- return Promise.resolve(
- Reflect.apply(registration.func, undefined, [param, env])
- );
+ return Promise.resolve(registration.func(param, env));
}
-function getRegistration(name: EventBuiltinName): RegisteredBuiltin {
- const registration = createEventBuiltinRegistrations().find(
+function getRegistration(name: string): RegisteredBuiltin {
+ const registration = createEventRegistrations().find(
item => item.name === name
);
if (!registration) throw new Error(`${name} registration missing`);
@@ -129,7 +121,7 @@ describe('event built-ins', () => {
// 验证真实注册项设置图块并安全跳过无效图块
it('sets a block and safely skips an unknown tile', async () => {
const fixture = createFixture();
- const registration = getRegistration(EventBuiltinName.SetBlock);
+ const registration = getRegistration('setBlock');
await invokeBuiltin(
registration,
{ x: 1, y: 0, tile: 'block' },
@@ -148,7 +140,7 @@ describe('event built-ins', () => {
it('moves a dynamic block and respects safe static transfer', async () => {
const fixture = createFixture();
await invokeBuiltin(
- getRegistration(EventBuiltinName.MoveBlock),
+ getRegistration('moveBlock'),
{
x: 1,
y: 0,
@@ -168,7 +160,7 @@ describe('event built-ins', () => {
const fixture = createFixture();
fixture.layer.transferToDynamic(1, 0);
await invokeBuiltin(
- getRegistration(EventBuiltinName.DeleteBlock),
+ getRegistration('deleteBlock'),
{ x: 1, y: 0 },
fixture.env
);
@@ -180,18 +172,14 @@ describe('event built-ins', () => {
it('awaits hero sequence and forward-step movement', async () => {
const fixture = createFixture();
await invokeBuiltin(
- getRegistration(EventBuiltinName.MoveHero),
+ getRegistration('moveHero'),
{
steps: [{ type: ObjectMoveType.Dir, move: FaceDirection.Right }]
},
fixture.env
);
expect(fixture.state.hero.location.x).toBe(1);
- await invokeBuiltin(
- getRegistration(EventBuiltinName.MoveHeroStep),
- {},
- fixture.env
- );
+ await invokeBuiltin(getRegistration('moveHeroStep'), {}, fixture.env);
expect(fixture.state.hero.location.x).toBe(2);
});
@@ -206,11 +194,7 @@ describe('event built-ins', () => {
calls.push(invocation('touch', env));
})
);
- await invokeBuiltin(
- getRegistration(EventBuiltinName.TouchFront),
- {},
- fixture.env
- );
+ await invokeBuiltin(getRegistration('touchFront'), {}, fixture.env);
expect(calls).toHaveLength(1);
expect(calls[0].env.trigger).toBe(EventTrigger.OnTouch);
expect(fixture.state.hero.location.x).toBe(0);
@@ -234,24 +218,20 @@ describe('event built-ins', () => {
})
);
await invokeBuiltin(
- getRegistration(EventBuiltinName.InsertEvents),
+ getRegistration('insertEvents'),
{ ids: ['first', 'second', 'missing'] },
fixture.env
);
const body: Statement[] = [
{
type: StatementType.Call,
- functionName: EventBuiltinName.SetBlock,
+ functionName: 'setBlock',
builtIn: true,
async: true,
parameters: { x: 3, y: 0, tile: 2 }
}
];
- await invokeBuiltin(
- getRegistration(EventBuiltinName.InsertEvent),
- body,
- fixture.env
- );
+ await invokeBuiltin(getRegistration('insertEvent'), body, fixture.env);
expect(calls).toEqual(['first', 'second']);
expect(fixture.layer.getBlock(3, 0)).toBe(2);
expect(fixture.state.eventStore.getEvent('inline-body')).toBeNull();
@@ -260,16 +240,16 @@ describe('event built-ins', () => {
// 验证默认注册项只包含批准的八个稳定名称
it('registers exactly the approved built-ins in AnonTokyo', () => {
const fixture = createFixture();
- const names = createEventBuiltinRegistrations().map(item => item.name);
+ const names = createEventRegistrations().map(item => item.name);
expect(names).toEqual([
- EventBuiltinName.SetBlock,
- EventBuiltinName.MoveBlock,
- EventBuiltinName.DeleteBlock,
- EventBuiltinName.MoveHero,
- EventBuiltinName.MoveHeroStep,
- EventBuiltinName.TouchFront,
- EventBuiltinName.InsertEvents,
- EventBuiltinName.InsertEvent
+ 'setBlock',
+ 'moveBlock',
+ 'deleteBlock',
+ 'moveHero',
+ 'moveHeroStep',
+ 'touchFront',
+ 'insertEvents',
+ 'insertEvent'
]);
for (const name of names) {
expect(
@@ -294,110 +274,74 @@ describe('event built-ins', () => {
};
await expect(
invokeBuiltin(
- getRegistration(EventBuiltinName.MoveBlock),
+ getRegistration('moveBlock'),
{ x: 0, y: 0, steps: [] },
missingEnv
)
).resolves.toBeUndefined();
await expect(
invokeBuiltin(
- getRegistration(EventBuiltinName.DeleteBlock),
+ getRegistration('deleteBlock'),
{ x: 0, y: 0 },
missingEnv
)
).resolves.toBeUndefined();
await expect(
- invokeBuiltin(
- getRegistration(EventBuiltinName.TouchFront),
- {},
- missingEnv
- )
+ invokeBuiltin(getRegistration('touchFront'), {}, missingEnv)
).resolves.toBeUndefined();
await expect(
- invokeBuiltin(
- getRegistration(EventBuiltinName.InsertEvent),
- [],
- fixture.env
- )
+ invokeBuiltin(getRegistration('insertEvent'), [], fixture.env)
).resolves.toBeUndefined();
});
});
describe('event registration ownership', () => {
- // 验证四个公开注册函数各自返回新实例且拼接顺序为地图勇士控制
- it('keeps the three-map three-hero two-control split and stable order', () => {
- const map = createMapEventBuiltinRegistrations();
- const hero = createHeroEventBuiltinRegistrations();
- const control = createControlEventBuiltinRegistrations();
- expect(map).toHaveLength(3);
- expect(hero).toHaveLength(3);
- expect(control).toHaveLength(2);
- expect(map.map(item => item.name)).toEqual([
- EventBuiltinName.SetBlock,
- EventBuiltinName.MoveBlock,
- EventBuiltinName.DeleteBlock
+ // 验证单一注册函数按地图勇士控制三类顺序返回全部八个新鲜实例
+ it('returns the eight fresh registrations in stable category order', () => {
+ const registrations = createEventRegistrations();
+ expect(registrations).toHaveLength(8);
+ expect(registrations.map(item => item.name)).toEqual([
+ 'setBlock',
+ 'moveBlock',
+ 'deleteBlock',
+ 'moveHero',
+ 'moveHeroStep',
+ 'touchFront',
+ 'insertEvents',
+ 'insertEvent'
]);
- expect(hero.map(item => item.name)).toEqual([
- EventBuiltinName.MoveHero,
- EventBuiltinName.MoveHeroStep,
- EventBuiltinName.TouchFront
- ]);
- expect(control.map(item => item.name)).toEqual([
- EventBuiltinName.InsertEvents,
- EventBuiltinName.InsertEvent
- ]);
- const aggregate = createEventBuiltinRegistrations();
- expect(aggregate.map(item => item.name)).toEqual(
- [...map, ...hero, ...control].map(item => item.name)
- );
- expect(aggregate).toHaveLength(8);
- expect(createMapEventBuiltinRegistrations()[0]).not.toBe(map[0]);
- expect(createHeroEventBuiltinRegistrations()[0]).not.toBe(hero[0]);
- expect(createControlEventBuiltinRegistrations()[0]).not.toBe(
- control[0]
- );
+ expect(registrations[0]).toBeInstanceOf(EventSetBlock);
+ expect(registrations[1]).toBeInstanceOf(EventMoveBlock);
+ expect(registrations[2]).toBeInstanceOf(EventDeleteBlock);
+ expect(registrations[3]).toBeInstanceOf(EventMoveHero);
+ expect(registrations[4]).toBeInstanceOf(EventMoveHeroStep);
+ expect(registrations[5]).toBeInstanceOf(EventTouchFront);
+ expect(registrations[6]).toBeInstanceOf(EventInsertEvents);
+ expect(registrations[7]).toBeInstanceOf(EventInsertEvent);
+ expect(createEventRegistrations()).not.toBe(registrations);
+ expect(createEventRegistrations()[0]).not.toBe(registrations[0]);
});
// 验证每个注册实例直接拥有自己的稳定名称与可调用函数
it('owns a stable name and callable function on every instance', () => {
- for (const registration of createEventBuiltinRegistrations()) {
- expect(
- Object.prototype.hasOwnProperty.call(registration, 'name')
- ).toBe(true);
- expect(
- Object.prototype.hasOwnProperty.call(registration, 'func')
- ).toBe(true);
+ for (const registration of createEventRegistrations()) {
expect(typeof registration.name).toBe('string');
+ expect(registration.name.length).toBeGreaterThan(0);
expect(typeof registration.func).toBe('function');
}
});
// 验证注册类分别来自地图勇士事件模块且勇士面前注册类归 hero 所有
it('originates registration classes from their owning event modules', () => {
- expect(SetBlockEventRegistration).toBe(MapSetBlockEventRegistration);
- expect(TouchFrontEventRegistration).toBe(
- HeroTouchFrontEventRegistration
- );
- expect(InsertEventEventRegistration).toBe(
- ControlInsertEventEventRegistration
- );
- const map = createMapEventBuiltinRegistrations();
- const hero = createHeroEventBuiltinRegistrations();
- const control = createControlEventBuiltinRegistrations();
- expect(map[0]).toBeInstanceOf(MapSetBlockEventRegistration);
- expect(map[1]).toBeInstanceOf(MoveBlockEventRegistration);
- expect(map[2]).toBeInstanceOf(DeleteBlockEventRegistration);
- expect(hero[0]).toBeInstanceOf(MoveHeroEventRegistration);
- expect(hero[1]).toBeInstanceOf(MoveHeroStepEventRegistration);
- expect(hero[2]).toBeInstanceOf(HeroTouchFrontEventRegistration);
- expect(control[0]).toBeInstanceOf(InsertEventsEventRegistration);
- expect(control[1]).toBeInstanceOf(ControlInsertEventEventRegistration);
- expect(getRegistration(EventBuiltinName.TouchFront)).toBeInstanceOf(
- HeroTouchFrontEventRegistration
+ expect(EventSetBlock).toBe(MapEventSetBlock);
+ expect(EventTouchFront).toBe(HeroEventTouchFront);
+ expect(EventInsertEvent).toBe(ControlEventInsertEvent);
+ expect(getRegistration('touchFront')).toBeInstanceOf(
+ HeroEventTouchFront
);
});
- // 验证两个事件 barrel 仅含导出语句且既有公开事件符号仍可从根索引导入
+ // 验证事件 barrel 仅含导出语句且既有公开事件符号仍可从根索引导入
it('keeps both barrels export-only and preserves public event symbols', () => {
const eventBarrel = readFileSync(
new URL('./index.ts', import.meta.url),
@@ -413,30 +357,15 @@ describe('event registration ownership', () => {
/^\s*(?:export\s+)?(?:async\s+)?function\s/m
);
const reachableNames = [
- 'createEventBuiltinRegistrations',
- 'createMapEventBuiltinRegistrations',
- 'createHeroEventBuiltinRegistrations',
- 'createControlEventBuiltinRegistrations',
- 'eventSetBlock',
- 'eventMoveBlock',
- 'eventDeleteBlock',
- 'eventMoveHero',
- 'eventMoveHeroStep',
- 'eventTouchFront',
- 'eventInsertEvents',
- 'eventInsertEvent',
- 'getPossibleMap',
- 'getPossibleLayer',
- 'appendMoveSteps',
- 'SetBlockEventRegistration',
- 'MoveBlockEventRegistration',
- 'DeleteBlockEventRegistration',
- 'MoveHeroEventRegistration',
- 'MoveHeroStepEventRegistration',
- 'TouchFrontEventRegistration',
- 'InsertEventsEventRegistration',
- 'InsertEventEventRegistration',
- 'EventBuiltinName'
+ 'createEventRegistrations',
+ 'EventSetBlock',
+ 'EventMoveBlock',
+ 'EventDeleteBlock',
+ 'EventMoveHero',
+ 'EventMoveHeroStep',
+ 'EventTouchFront',
+ 'EventInsertEvents',
+ 'EventInsertEvent'
];
const rootKeys = Object.keys(dataStateRoot);
for (const name of reachableNames) {
diff --git a/packages-user/data-state/src/event/event.ts b/packages-user/data-state/src/event/event.ts
index 21af0d8..af02916 100644
--- a/packages-user/data-state/src/event/event.ts
+++ b/packages-user/data-state/src/event/event.ts
@@ -1,123 +1,76 @@
-import { BuiltInFunction } from 'anon-tokyo';
+import { BuiltInFunction } from '@motajs/anon-tokyo';
import {
IBlockEventEnv,
IBlockEventParam,
- IGameEventExecutor,
- IGameEventInvocation,
- IGameEventSystem
+ IGameEventInvocation
} from '@user/data-system';
import { IGameEventStore } from '@user/data-common';
-import { IStateBase } from '@user/data-base';
-import {
- EventBuiltinName,
- IInsertEventEventParam,
- IInsertEventsEventParam
-} from './types';
+import { IInsertEventEventParam, IInsertEventsEventParam } from './types';
+import { enterEventInsert, exitEventInsert, getEventExecutor } from './utils';
-interface IEventState extends IStateBase {
- readonly eventSystem: IGameEventSystem;
-}
+export class EventInsertEvents implements BuiltInFunction<
+ IInsertEventsEventParam,
+ IBlockEventEnv
+> {
+ name: string = 'insertEvents';
-const EVENT_INSERT_MAX_DEPTH = 32;
-const eventInsertDepth: WeakMap = new WeakMap();
+ async func(param: IInsertEventsEventParam, env: IBlockEventEnv) {
+ if (param.ids.length === 0) return;
-/**
- * 判断状态是否包含事件执行器
- */
-function hasEventSystem(state: IStateBase): state is IEventState {
- return 'eventSystem' in state;
-}
+ const executor = getEventExecutor(env);
+ if (!executor) return;
+ const store = env.state.eventStore;
+ if (!store) return;
-/**
- * 从环境获取事件执行器和事件存储器
- */
-export 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
- };
-}
-
-/**
- * 过滤存在的事件 id 并构造临时事件调用
- */
-function collectEventInvocations(
- ids: readonly string[],
- env: IBlockEventEnv,
- store: IGameEventStore
-): IGameEventInvocation[] {
- const invocations: IGameEventInvocation[] = [];
- for (const id of ids) {
- if (
- !id ||
- !store.getEvent(id)
- ) {
- continue;
+ if (!enterEventInsert(env)) return;
+ try {
+ const invocations = this.collectInvocations(param.ids, env, store);
+ if (invocations.length === 0) return;
+ await executor.execute(invocations, { custom: {} });
+ } finally {
+ exitEventInsert(env);
}
- invocations.push({ id, env });
}
- return invocations;
-}
-/**
- * 临时按顺序执行指定事件
- */
-export async function eventInsertEvents(
- param: IInsertEventsEventParam,
- env: IBlockEventEnv
-): Promise {
- 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(invocations, { custom: {} });
- } finally {
- eventInsertDepth.delete(env);
+ /**
+ * 过滤存在的事件 id 并构造临时事件调用
+ */
+ private collectInvocations(
+ ids: readonly string[],
+ env: IBlockEventEnv,
+ store: IGameEventStore
+ ): IGameEventInvocation[] {
+ const invocations: IGameEventInvocation[] = [];
+ for (const id of ids) {
+ if (
+ !id ||
+ !store.getEvent(id)
+ ) {
+ continue;
+ }
+ invocations.push({ id, env });
+ }
+ return invocations;
}
}
-/**
- * 临时直接执行一段事件语句
- */
-export async function eventInsertEvent(
- param: IInsertEventEventParam,
- env: IBlockEventEnv
-): Promise {
- if (param.length === 0 || !hasEventSystem(env.state)) return;
- const depth = eventInsertDepth.get(env) ?? 0;
- if (depth >= EVENT_INSERT_MAX_DEPTH) return;
- eventInsertDepth.set(env, depth + 1);
- try {
- await env.state.eventSystem.executor.interpreter.exec(
- param,
- { custom: {} },
- env
- );
- } finally {
- eventInsertDepth.delete(env);
+export class EventInsertEvent implements BuiltInFunction<
+ IInsertEventEventParam,
+ IBlockEventEnv
+> {
+ name: string = 'insertEvent';
+
+ async func(param: IInsertEventEventParam, env: IBlockEventEnv) {
+ if (param.length === 0) return;
+
+ const executor = getEventExecutor(env);
+ if (!executor) return;
+
+ if (!enterEventInsert(env)) return;
+ try {
+ await executor.interpreter.exec(param, { custom: {} }, env);
+ } finally {
+ exitEventInsert(env);
+ }
}
}
-
-export class InsertEventsEventRegistration implements BuiltInFunction {
- readonly name: EventBuiltinName.InsertEvents =
- EventBuiltinName.InsertEvents;
- readonly func: BuiltInFunction['func'] =
- eventInsertEvents as BuiltInFunction['func'];
-}
-
-export class InsertEventEventRegistration implements BuiltInFunction {
- readonly name: EventBuiltinName.InsertEvent = EventBuiltinName.InsertEvent;
- readonly func: BuiltInFunction['func'] =
- eventInsertEvent as BuiltInFunction['func'];
-}
diff --git a/packages-user/data-state/src/event/hero.ts b/packages-user/data-state/src/event/hero.ts
index cb7be2f..e73f26d 100644
--- a/packages-user/data-state/src/event/hero.ts
+++ b/packages-user/data-state/src/event/hero.ts
@@ -1,150 +1,17 @@
-import { BuiltInFunction } from 'anon-tokyo';
+import { BuiltInFunction } from '@motajs/anon-tokyo';
import {
BlockEventType,
IBlockEventEnv,
IGameEventInvocation
} from '@user/data-system';
+import { IReadonlyTileBase as IReadonlyMapTileBase } from '@user/data-base';
+import { EventTrigger, FaceDirection } from '@user/data-common';
import {
- IGameMap,
- IHeroLocation,
- IHeroMover,
- IMapLayer,
- IReadonlyTileBase as IReadonlyMapTileBase
-} from '@user/data-base';
-import {
- EventTrigger,
- FaceDirection,
- IMoverController,
- IObjectMovable,
- IObjectMover,
- ObjectMoveStep,
- ObjectMoveType,
- ObjectSpecialStep
-} from '@user/data-common';
-import {
- EventBuiltinName,
IMoveHeroEventParam,
IMoveHeroStepEventParam,
ITouchFrontEventParam
} from './types';
-import { getEventRuntime } from './event';
-
-/**
- * 通过环境参量获取可能的地图对象
- */
-export function getPossibleMap(env: IBlockEventEnv): IGameMap | null {
- if (env.map) return env.map;
- if (env.layer) return env.layer.map;
-
- const map = env.state.maps.getMap(env.heroFloor);
- if (map) return map;
-
- 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 appendMoveSteps(
- mover: IObjectMover,
- 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,
- steps: readonly ObjectMoveStep[]
-): Promise {
- if (mover.moving) return;
- appendMoveSteps(mover, steps);
- const controller: Readonly | null = mover.start();
- if (!controller) return;
- await controller.onEnd;
-}
-
-/**
- * 获取可以执行移动的勇士移动器
- */
-function getHeroMover(env: IBlockEventEnv): IHeroMover | null {
- const hero = env.state.hero;
- if (!hero) return null;
- return hero.location.mover;
-}
-
-/**
- * 按指定移动序列移动勇士
- */
-export async function eventMoveHero(
- param: IMoveHeroEventParam,
- env: IBlockEventEnv
-): Promise {
- const mover = getHeroMover(env);
- if (!mover) return;
- await startHeroMove(mover, param.steps);
-}
-
-/**
- * 让勇士沿当前朝向移动一步
- */
-export async function eventMoveHeroStep(
- _param: IMoveHeroStepEventParam,
- env: IBlockEventEnv
-): Promise {
- const mover = getHeroMover(env);
- if (!mover) return;
- if (mover.moving) return;
- mover.forward(1);
- const controller = mover.start();
- if (!controller) return;
- await controller.onEnd;
-}
+import { getEventExecutor, getPossibleLayer } from './utils';
interface IEventSource {
readonly priority: number;
@@ -153,122 +20,140 @@ interface IEventSource {
readonly tile: IReadonlyMapTileBase | null;
}
-/**
- * 按坐标收集事件来源并保留其触发环境
- */
-function collectInvocations(
- env: IBlockEventEnv,
- layer: NonNullable,
- 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);
+export class EventMoveHero implements BuiltInFunction<
+ IMoveHeroEventParam,
+ IBlockEventEnv
+> {
+ name: string = 'moveHero';
- 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,
+ async func(param: IMoveHeroEventParam, env: IBlockEventEnv) {
+ const mover = env.state.hero.location.mover;
+ if (mover.moving) return;
+
+ mover.push([...param.steps]);
+ const controller = mover.start();
+ if (!controller) return;
+ await controller.onEnd;
+ }
+}
+
+export class EventMoveHeroStep implements BuiltInFunction<
+ IMoveHeroStepEventParam,
+ IBlockEventEnv
+> {
+ name: string = 'moveHeroStep';
+
+ async func(_param: IMoveHeroStepEventParam, env: IBlockEventEnv) {
+ const mover = env.state.hero.location.mover;
+ if (mover.moving) return;
+
+ mover.forward(1);
+ const controller = mover.start();
+ if (!controller) return;
+ await controller.onEnd;
+ }
+}
+
+export class EventTouchFront implements BuiltInFunction<
+ ITouchFrontEventParam,
+ IBlockEventEnv
+> {
+ name: string = 'touchFront';
+
+ async func(_param: ITouchFrontEventParam, env: IBlockEventEnv) {
+ const layer = getPossibleLayer(env);
+ if (!layer) return;
+
+ const executor = getEventExecutor(env);
+ if (!executor) 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 = this.collectInvocations(
+ env,
layer,
- map: layer.map
- };
- invocations.push({ id: source.id, env: sourceEnv });
+ x,
+ y,
+ EventTrigger.OnTouch
+ );
+ if (invocations.length === 0) return;
+ await executor.execute(invocations, { custom: {} });
+ }
+
+ /**
+ * 按坐标收集事件来源并保留其触发环境
+ */
+ private collectInvocations(
+ env: IBlockEventEnv,
+ layer: NonNullable,
+ 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;
}
- return invocations;
-}
-
-/**
- * 触发勇士正面的 onTouch 事件
- */
-export async function eventTouchFront(
- _param: ITouchFrontEventParam,
- env: IBlockEventEnv
-): Promise {
- 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(invocations, { custom: {} });
-}
-
-export class MoveHeroEventRegistration implements BuiltInFunction {
- readonly name: EventBuiltinName.MoveHero = EventBuiltinName.MoveHero;
- readonly func: BuiltInFunction['func'] =
- eventMoveHero as BuiltInFunction['func'];
-}
-
-export class MoveHeroStepEventRegistration implements BuiltInFunction {
- readonly name: EventBuiltinName.MoveHeroStep =
- EventBuiltinName.MoveHeroStep;
- readonly func: BuiltInFunction['func'] =
- eventMoveHeroStep as BuiltInFunction['func'];
-}
-
-export class TouchFrontEventRegistration implements BuiltInFunction {
- readonly name: EventBuiltinName.TouchFront = EventBuiltinName.TouchFront;
- readonly func: BuiltInFunction['func'] =
- eventTouchFront as BuiltInFunction['func'];
}
diff --git a/packages-user/data-state/src/event/index.ts b/packages-user/data-state/src/event/index.ts
index e0833cb..1a9c181 100644
--- a/packages-user/data-state/src/event/index.ts
+++ b/packages-user/data-state/src/event/index.ts
@@ -1,9 +1,4 @@
-export {
- createControlEventBuiltinRegistrations,
- createEventBuiltinRegistrations,
- createHeroEventBuiltinRegistrations,
- createMapEventBuiltinRegistrations
-} from './registrations';
+export { createEventRegistrations } from './registrations';
export * from './event';
export * from './hero';
export * from './map';
diff --git a/packages-user/data-state/src/event/map.ts b/packages-user/data-state/src/event/map.ts
index c3bfea9..bf1fbb1 100644
--- a/packages-user/data-state/src/event/map.ts
+++ b/packages-user/data-state/src/event/map.ts
@@ -1,14 +1,13 @@
import { BuiltInFunction } from '@motajs/anon-tokyo';
import { IBlockEventEnv } from '@user/data-system';
import {
- EventBuiltinName,
IDeleteBlockEventParam,
IMoveBlockEventParam,
ISetBlockEventParam
} from './types';
import { isNil } from 'lodash-es';
import { logger } from '@motajs/common';
-import { appendMoveSteps, getPossibleLayer } from './hero';
+import { getPossibleLayer } from './utils';
export class EventSetBlock implements BuiltInFunction<
ISetBlockEventParam,
@@ -32,85 +31,48 @@ export class EventSetBlock implements BuiltInFunction<
}
}
-/**
- * 在解析出的目标图层设置静态图块,目标或图块缺失时安全返回
- */
-export function eventSetBlock(
- param: ISetBlockEventParam,
- env: IBlockEventEnv
-): void {
- const layer = getPossibleLayer(env);
- if (!layer) return;
+export class EventMoveBlock implements BuiltInFunction<
+ IMoveBlockEventParam,
+ IBlockEventEnv
+> {
+ name: string = 'moveBlock';
- if (!layer.inMap(param.x, param.y)) return;
+ async func(param: IMoveBlockEventParam, env: IBlockEventEnv) {
+ const layer = getPossibleLayer(env);
+ if (!layer || !layer.inMap(param.x, param.y)) return;
+ if (!layer.getTile(param.x, param.y)) return;
- const num = env.state.tileStore.num(param.tile);
- if (isNil(num)) {
- logger.warn(1001);
- return;
- }
+ const dynamic = layer.transferToDynamic(param.x, param.y);
+ if (!dynamic) return;
- layer.setBlock(num, param.x, param.y);
-}
+ if (dynamic.mover.moving) return;
+ dynamic.mover.push([...param.steps]);
+ const controller = dynamic.mover.start();
+ if (!controller) return;
+ await controller.onEnd;
-/**
- * 将动态图块移动完成后还原为静态图块
- */
-export async function eventMoveBlock(
- param: IMoveBlockEventParam,
- env: IBlockEventEnv
-): Promise {
- 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);
+ if (param.safe) {
+ layer.transferToStaticIfSafe(dynamic);
+ } else {
+ layer.transferToStatic(dynamic);
+ }
}
}
-/**
- * 删除目标坐标的静态图块和动态图块
- */
-export async function eventDeleteBlock(
- param: IDeleteBlockEventParam,
- env: IBlockEventEnv
-): Promise {
- const layer = getPossibleLayer(env);
- if (!layer || !layer.inMap(param.x, param.y)) return;
+export class EventDeleteBlock implements BuiltInFunction<
+ IDeleteBlockEventParam,
+ IBlockEventEnv
+> {
+ name: string = 'deleteBlock';
- 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);
+ async func(param: IDeleteBlockEventParam, env: IBlockEventEnv) {
+ 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);
+ }
}
}
-
-export class SetBlockEventRegistration implements BuiltInFunction {
- readonly name: EventBuiltinName.SetBlock = EventBuiltinName.SetBlock;
- readonly func: BuiltInFunction['func'] =
- eventSetBlock as BuiltInFunction['func'];
-}
-
-export class MoveBlockEventRegistration implements BuiltInFunction {
- readonly name: EventBuiltinName.MoveBlock = EventBuiltinName.MoveBlock;
- readonly func: BuiltInFunction['func'] =
- eventMoveBlock as BuiltInFunction['func'];
-}
-
-export class DeleteBlockEventRegistration implements BuiltInFunction {
- readonly name: EventBuiltinName.DeleteBlock = EventBuiltinName.DeleteBlock;
- readonly func: BuiltInFunction['func'] =
- eventDeleteBlock as BuiltInFunction['func'];
-}
diff --git a/packages-user/data-state/src/event/registrations.ts b/packages-user/data-state/src/event/registrations.ts
index 0eb49df..c4ceb28 100644
--- a/packages-user/data-state/src/event/registrations.ts
+++ b/packages-user/data-state/src/event/registrations.ts
@@ -1,58 +1,25 @@
-import { BuiltInFunction } from 'anon-tokyo';
-import {
- DeleteBlockEventRegistration,
- MoveBlockEventRegistration,
- SetBlockEventRegistration
-} from './map';
-import {
- MoveHeroEventRegistration,
- MoveHeroStepEventRegistration,
- TouchFrontEventRegistration
-} from './hero';
-import {
- InsertEventEventRegistration,
- InsertEventsEventRegistration
-} from './event';
+import { BuiltInFunction } from '@motajs/anon-tokyo';
+import { EventDeleteBlock, EventMoveBlock, EventSetBlock } from './map';
+import { EventMoveHero, EventMoveHeroStep, EventTouchFront } from './hero';
+import { EventInsertEvent, EventInsertEvents } from './event';
/**
- * 创建地图控制事件的内建函数注册项
+ * 创建全部内建事件的注册项
*/
-export function createMapEventBuiltinRegistrations(): ReadonlyArray {
+export function createEventRegistrations(): BuiltInFunction[] {
return [
- new SetBlockEventRegistration(),
- new MoveBlockEventRegistration(),
- new DeleteBlockEventRegistration()
- ];
-}
+ // 地图控制
+ new EventSetBlock(),
+ new EventMoveBlock(),
+ new EventDeleteBlock(),
-/**
- * 创建勇士控制事件的内建函数注册项
- */
-export function createHeroEventBuiltinRegistrations(): ReadonlyArray {
- return [
- new MoveHeroEventRegistration(),
- new MoveHeroStepEventRegistration(),
- new TouchFrontEventRegistration()
- ];
-}
+ // 玩家控制
+ new EventMoveHero(),
+ new EventMoveHeroStep(),
+ new EventTouchFront(),
-/**
- * 创建事件控制事件的内建函数注册项
- */
-export function createControlEventBuiltinRegistrations(): ReadonlyArray {
- return [
- new InsertEventsEventRegistration(),
- new InsertEventEventRegistration()
- ];
-}
-
-/**
- * 组装八个批准事件 built-in 的稳定注册项
- */
-export function createEventBuiltinRegistrations(): ReadonlyArray {
- return [
- ...createMapEventBuiltinRegistrations(),
- ...createHeroEventBuiltinRegistrations(),
- ...createControlEventBuiltinRegistrations()
+ // 事件控制
+ new EventInsertEvents(),
+ new EventInsertEvent()
];
}
diff --git a/packages-user/data-state/src/event/types.ts b/packages-user/data-state/src/event/types.ts
index 3f06a57..7d00303 100644
--- a/packages-user/data-state/src/event/types.ts
+++ b/packages-user/data-state/src/event/types.ts
@@ -1,5 +1,5 @@
import { ObjectMoveStep } from '@user/data-common';
-import { Statement } from 'anon-tokyo';
+import { Statement } from '@motajs/anon-tokyo';
//#region 地图控制
@@ -62,16 +62,4 @@ export interface IInsertEventsEventParam {
/** 事件:临时执行一段事件语句 */
export type IInsertEventEventParam = Statement[];
-/** 内建函数的稳定注册名称 */
-export const enum EventBuiltinName {
- SetBlock = 'eventSetBlock',
- MoveBlock = 'eventMoveBlock',
- DeleteBlock = 'eventDeleteBlock',
- MoveHero = 'eventMoveHero',
- MoveHeroStep = 'eventMoveHeroStep',
- TouchFront = 'eventTouchFront',
- InsertEvents = 'eventInsertEvents',
- InsertEvent = 'eventInsertEvent'
-}
-
//#endregion
diff --git a/packages-user/data-state/src/event/utils.ts b/packages-user/data-state/src/event/utils.ts
index 8a739ea..580f065 100644
--- a/packages-user/data-state/src/event/utils.ts
+++ b/packages-user/data-state/src/event/utils.ts
@@ -1,5 +1,20 @@
-import { IGameMap, IMapLayer } from '@user/data-base';
-import { IBlockEventEnv } from '@user/data-system';
+import { IGameMap, IMapLayer, IStateBase } from '@user/data-base';
+import {
+ IBlockEventEnv,
+ IGameEventExecutor,
+ IGameEventSystem
+} from '@user/data-system';
+
+interface IEventState extends IStateBase {
+ readonly eventSystem: IGameEventSystem;
+}
+
+/**
+ * 判断状态对象是否包含事件系统
+ */
+function hasEventSystem(state: IStateBase): state is IEventState {
+ return 'eventSystem' in state;
+}
/**
* 通过环境参量获取可能的地图对象
@@ -25,3 +40,35 @@ export function getPossibleLayer(env: IBlockEventEnv): IMapLayer | null {
return null;
}
+
+/**
+ * 通过环境参量获取事件执行器
+ */
+export function getEventExecutor(
+ env: IBlockEventEnv
+): IGameEventExecutor | null {
+ if (!hasEventSystem(env.state)) return null;
+ return env.state.eventSystem.executor;
+}
+
+/** 事件插入允许的最大嵌套深度 */
+const EVENT_INSERT_MAX_DEPTH = 32;
+/** 每个环境当前的事件插入深度 */
+const eventInsertDepth: WeakMap = new WeakMap();
+
+/**
+ * 进入一层事件插入,超过最大嵌套深度时返回 false
+ */
+export function enterEventInsert(env: IBlockEventEnv): boolean {
+ const depth = eventInsertDepth.get(env) ?? 0;
+ if (depth >= EVENT_INSERT_MAX_DEPTH) return false;
+ eventInsertDepth.set(env, depth + 1);
+ return true;
+}
+
+/**
+ * 退出一层事件插入
+ */
+export function exitEventInsert(env: IBlockEventEnv): void {
+ eventInsertDepth.delete(env);
+}
diff --git a/packages-user/data-state/src/replay/commands.test.ts b/packages-user/data-state/src/replay/commands.test.ts
index ff0dfbb..9d4e707 100644
--- a/packages-user/data-state/src/replay/commands.test.ts
+++ b/packages-user/data-state/src/replay/commands.test.ts
@@ -9,7 +9,6 @@ import {
logReplaySafetyDetail,
shouldReplay
} from '@user/data-common';
-import { EquipStatus } from '@user/data-base';
import { readFileSync } from 'node:fs';
import { describe, expect, it, vi } from 'vitest';
import { createCoreState } from '../core';
@@ -197,56 +196,35 @@ describe('replay commands', () => {
});
// 验证装备指令复用既有装备边界并区分三种失败位置
+ // 验证装备指令复用既有装备边界并以最终槽位校验结果
it('equips through the existing equipment boundary', async () => {
const state = createCoreState();
const equipment = state.hero.equip;
const error = vi.spyOn(logger, 'error');
const getEquipped = vi.spyOn(equipment, 'getEquipped');
- const canEquipTo = vi
- .spyOn(equipment, 'canEquipTo')
- .mockReturnValue(EquipStatus.CanEquip);
const equip = vi
.spyOn(equipment, 'equip')
.mockImplementation(() => undefined);
const command = new ReplayEquipCommand(state);
- // 已经装备在目标槽位
+ // 装备后槽位为指定 uid
getEquipped.mockReturnValueOnce(99);
await expect(
command.execute(step(ReplayCommandCode.Equip, [99, 0, true]))
).resolves.toBe(true);
- expect(canEquipTo).not.toHaveBeenCalled();
+ expect(equip).toHaveBeenCalledWith(99, 0, true);
- // 正常装备并校验结果
- getEquipped.mockReturnValueOnce(undefined).mockReturnValueOnce(99);
+ // 装备后槽位未变为指定 uid
+ getEquipped.mockReturnValueOnce(undefined);
await expect(
command.execute(step(ReplayCommandCode.Equip, [99, 1, false]))
- ).resolves.toBe(true);
- expect(canEquipTo).toHaveBeenCalledWith(99, 1);
- expect(equip).toHaveBeenCalledWith(99, 1, false);
-
- // 无法装备
- getEquipped.mockReturnValueOnce(undefined);
- canEquipTo.mockReturnValueOnce(EquipStatus.CannotEquip);
- await expect(
- command.execute(step(ReplayCommandCode.Equip, [99, 2, true]))
).resolves.toBe(false);
- expect(error).toHaveBeenCalledWith(2007, '99', '2');
-
- // 装备未生效
- getEquipped
- .mockReturnValueOnce(undefined)
- .mockReturnValueOnce(undefined);
- canEquipTo.mockReturnValueOnce(EquipStatus.CanEquip);
- await expect(
- command.execute(step(ReplayCommandCode.Equip, [99, 3, true]))
- ).resolves.toBe(false);
- expect(error).toHaveBeenCalledWith(2008, '99', '3');
+ expect(error).toHaveBeenCalledWith(2007, '99', '1');
error.mockRestore();
});
- // 验证卸下指令区分未装备与未生效两种失败位置
+ // 验证卸下指令复用既有装备边界并以最终槽位校验结果
it('unequips through the existing equipment boundary', async () => {
const state = createCoreState();
const equipment = state.hero.equip;
@@ -257,27 +235,19 @@ describe('replay commands', () => {
.mockImplementation(() => undefined);
const command = new ReplayUnequipCommand(state);
- // 目标槽位本来就没有装备
+ // 卸下后槽位为空
getEquipped.mockReturnValueOnce(undefined);
await expect(
command.execute(step(ReplayCommandCode.Unequip, [0]))
- ).resolves.toBe(false);
- expect(error).toHaveBeenCalledWith(2009, '0');
- expect(unequip).not.toHaveBeenCalled();
+ ).resolves.toBe(true);
+ expect(unequip).toHaveBeenCalledWith(0);
- // 正常卸下并校验槽位已清空
- getEquipped.mockReturnValueOnce(88).mockReturnValueOnce(undefined);
+ // 卸下后槽位仍有装备
+ getEquipped.mockReturnValueOnce(88);
await expect(
command.execute(step(ReplayCommandCode.Unequip, [1]))
- ).resolves.toBe(true);
- expect(unequip).toHaveBeenCalledWith(1);
-
- // 卸下未生效
- getEquipped.mockReturnValueOnce(88).mockReturnValueOnce(88);
- await expect(
- command.execute(step(ReplayCommandCode.Unequip, [2]))
).resolves.toBe(false);
- expect(error).toHaveBeenCalledWith(2010, '2');
+ expect(error).toHaveBeenCalledWith(2008, '1');
error.mockRestore();
});
@@ -366,7 +336,7 @@ describe('replay commands', () => {
);
expect(core).not.toContain('createReplayCommandItems');
expect(core).not.toContain('registerReplayCommandItems');
- expect(core).toContain('private registerReplayCommand()');
+ expect(core).toContain('private registerReplayCommands()');
expect((core.match(/new ReplayMoveCommand\(this,/g) ?? []).length).toBe(
4
);
diff --git a/packages-user/data-state/test/fixtures/closed-loop.ts b/packages-user/data-state/test/fixtures/closed-loop.ts
index c8cead7..0281eda 100644
--- a/packages-user/data-state/test/fixtures/closed-loop.ts
+++ b/packages-user/data-state/test/fixtures/closed-loop.ts
@@ -1,4 +1,4 @@
-import { Statement, StatementType } from 'anon-tokyo';
+import { Statement, StatementType } from '@motajs/anon-tokyo';
import {
EventTrigger,
FaceDirection,
@@ -82,7 +82,7 @@ export function createClosedLoopFixture(): IClosedLoopFixture {
const rawEvent: Statement[] = [
{
type: StatementType.Call,
- functionName: 'eventSetBlock',
+ functionName: 'setBlock',
builtIn: true,
async: false,
parameters: { x: 1, y: 0, tile: 2 }