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