mirror of
https://github.com/motajs/template.git
synced 2026-10-04 18:50: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';
|
||||
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;
|
||||
|
||||
|
||||
@ -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<typeof createEventRegistrations>[number];
|
||||
|
||||
function invokeBuiltin<TParam>(
|
||||
registration: RegisteredBuiltin,
|
||||
param: TParam,
|
||||
env: IBlockEventEnv
|
||||
): Promise<void> {
|
||||
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) {
|
||||
|
||||
@ -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<IBlockEventEnv, number> = 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<IBlockEventParam, IBlockEventEnv, void>(id)
|
||||
) {
|
||||
continue;
|
||||
if (!enterEventInsert(env)) return;
|
||||
try {
|
||||
const invocations = this.collectInvocations(param.ids, env, store);
|
||||
if (invocations.length === 0) return;
|
||||
await executor.execute<void>(invocations, { custom: {} });
|
||||
} finally {
|
||||
exitEventInsert(env);
|
||||
}
|
||||
invocations.push({ id, env });
|
||||
}
|
||||
return invocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 临时按顺序执行指定事件
|
||||
*/
|
||||
export async function eventInsertEvents(
|
||||
param: IInsertEventsEventParam,
|
||||
env: IBlockEventEnv
|
||||
): Promise<void> {
|
||||
if (param.ids.length === 0) return;
|
||||
const runtime = getEventRuntime(env);
|
||||
if (!runtime) return;
|
||||
const depth = eventInsertDepth.get(env) ?? 0;
|
||||
if (depth >= EVENT_INSERT_MAX_DEPTH) return;
|
||||
const invocations = collectEventInvocations(param.ids, env, runtime.store);
|
||||
if (invocations.length === 0) return;
|
||||
eventInsertDepth.set(env, depth + 1);
|
||||
try {
|
||||
await runtime.executor.execute<void>(invocations, { custom: {} });
|
||||
} finally {
|
||||
eventInsertDepth.delete(env);
|
||||
/**
|
||||
* 过滤存在的事件 id 并构造临时事件调用
|
||||
*/
|
||||
private collectInvocations(
|
||||
ids: readonly string[],
|
||||
env: IBlockEventEnv,
|
||||
store: IGameEventStore
|
||||
): IGameEventInvocation[] {
|
||||
const invocations: IGameEventInvocation[] = [];
|
||||
for (const id of ids) {
|
||||
if (
|
||||
!id ||
|
||||
!store.getEvent<IBlockEventParam, IBlockEventEnv, void>(id)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
invocations.push({ id, env });
|
||||
}
|
||||
return invocations;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 临时直接执行一段事件语句
|
||||
*/
|
||||
export async function eventInsertEvent(
|
||||
param: IInsertEventEventParam,
|
||||
env: IBlockEventEnv
|
||||
): Promise<void> {
|
||||
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'];
|
||||
}
|
||||
|
||||
@ -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<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;
|
||||
}
|
||||
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<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);
|
||||
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<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 {
|
||||
createControlEventBuiltinRegistrations,
|
||||
createEventBuiltinRegistrations,
|
||||
createHeroEventBuiltinRegistrations,
|
||||
createMapEventBuiltinRegistrations
|
||||
} from './registrations';
|
||||
export { createEventRegistrations } from './registrations';
|
||||
export * from './event';
|
||||
export * from './hero';
|
||||
export * from './map';
|
||||
|
||||
@ -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<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);
|
||||
if (param.safe) {
|
||||
layer.transferToStaticIfSafe(dynamic);
|
||||
} else {
|
||||
layer.transferToStatic(dynamic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除目标坐标的静态图块和动态图块
|
||||
*/
|
||||
export async function eventDeleteBlock(
|
||||
param: IDeleteBlockEventParam,
|
||||
env: IBlockEventEnv
|
||||
): Promise<void> {
|
||||
const layer = getPossibleLayer(env);
|
||||
if (!layer || !layer.inMap(param.x, param.y)) return;
|
||||
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'];
|
||||
}
|
||||
|
||||
@ -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<BuiltInFunction> {
|
||||
export function createEventRegistrations(): BuiltInFunction[] {
|
||||
return [
|
||||
new SetBlockEventRegistration(),
|
||||
new MoveBlockEventRegistration(),
|
||||
new DeleteBlockEventRegistration()
|
||||
];
|
||||
}
|
||||
// 地图控制
|
||||
new EventSetBlock(),
|
||||
new EventMoveBlock(),
|
||||
new EventDeleteBlock(),
|
||||
|
||||
/**
|
||||
* 创建勇士控制事件的内建函数注册项
|
||||
*/
|
||||
export function createHeroEventBuiltinRegistrations(): ReadonlyArray<BuiltInFunction> {
|
||||
return [
|
||||
new MoveHeroEventRegistration(),
|
||||
new MoveHeroStepEventRegistration(),
|
||||
new TouchFrontEventRegistration()
|
||||
];
|
||||
}
|
||||
// 玩家控制
|
||||
new EventMoveHero(),
|
||||
new EventMoveHeroStep(),
|
||||
new EventTouchFront(),
|
||||
|
||||
/**
|
||||
* 创建事件控制事件的内建函数注册项
|
||||
*/
|
||||
export function createControlEventBuiltinRegistrations(): ReadonlyArray<BuiltInFunction> {
|
||||
return [
|
||||
new InsertEventsEventRegistration(),
|
||||
new InsertEventEventRegistration()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装八个批准事件 built-in 的稳定注册项
|
||||
*/
|
||||
export function createEventBuiltinRegistrations(): ReadonlyArray<BuiltInFunction> {
|
||||
return [
|
||||
...createMapEventBuiltinRegistrations(),
|
||||
...createHeroEventBuiltinRegistrations(),
|
||||
...createControlEventBuiltinRegistrations()
|
||||
// 事件控制
|
||||
new EventInsertEvents(),
|
||||
new EventInsertEvent()
|
||||
];
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<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,
|
||||
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
|
||||
);
|
||||
|
||||
@ -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 }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user