diff --git a/packages/legacy-client/package.json b/packages/legacy-client/package.json new file mode 100644 index 0000000..5b20c57 --- /dev/null +++ b/packages/legacy-client/package.json @@ -0,0 +1,6 @@ +{ + "name": "@motajs/legacy-client", + "dependencies": { + "@motajs/common": "workspace:*" + } +} \ No newline at end of file diff --git a/packages/legacy-common/package.json b/packages/legacy-common/package.json new file mode 100644 index 0000000..be6fe10 --- /dev/null +++ b/packages/legacy-common/package.json @@ -0,0 +1,6 @@ +{ + "name": "@motajs/legacy-common", + "dependencies": { + "@motajs/common": "workspace:*" + } +} \ No newline at end of file diff --git a/src/core/common/disposable.ts b/packages/legacy-common/src/disposable.ts similarity index 89% rename from src/core/common/disposable.ts rename to packages/legacy-common/src/disposable.ts index eaf91c4..1b39829 100644 --- a/src/core/common/disposable.ts +++ b/packages/legacy-common/src/disposable.ts @@ -1,9 +1,9 @@ -import { EventEmitter } from './eventEmitter'; +import { EventEmitter } from 'eventemitter3'; interface DisposableEvent { - active: (value: T) => void; - dispose: (value: T) => void; - destroy: () => void; + active: [value: T]; + dispose: [value: T]; + destroy: []; } export class Disposable extends EventEmitter> { diff --git a/packages/legacy-common/src/eventEmitter.ts b/packages/legacy-common/src/eventEmitter.ts new file mode 100644 index 0000000..c48fc2a --- /dev/null +++ b/packages/legacy-common/src/eventEmitter.ts @@ -0,0 +1,209 @@ +import { isNil } from 'lodash-es'; + +export type Callable = (...params: any) => any; + +export interface Listener any> { + fn: T; + once?: boolean; + immediate?: boolean; +} + +export interface ListenerOptions { + once: boolean; + immediate: boolean; +} + +type EmitFn any> = ( + events: Listener[], + ...params: Parameters +) => any; + +type Key = number | string | symbol; + +export class EventEmitter = {}> { + protected events: { + [x: Key]: Listener[]; + } = {}; + private emitted: Set = new Set(); + + protected emitter: { + [x: Key]: EmitFn | undefined; + } = {}; + + /** + * 监听某个事件 + * @param event 要监听的事件类型 + * @param fn 触发事件时执行的函数 + * @param options 监听选项 + */ + on( + event: K, + fn: T[K], + options?: Partial + ): void; + on(event: string, fn: Callable, options?: Partial): void; + on(event: string, fn: Callable, options?: Partial): void { + if (options?.immediate && this.emitted.has(event)) { + fn(); + if (!options.once) { + this.events[event] ??= []; + this.events[event]?.push({ + fn + }); + } + return; + } + this.events[event] ??= []; + this.events[event]?.push({ + fn, + once: options?.once + }); + } + + /** + * 取消监听某个事件 + * @param event 要取消监听的事件类型 + * @param fn 要取消监听的函数 + */ + off(event: K, fn: T[K]): void; + off(event: string, fn: Callable): void; + off(event: string, fn: Callable): void { + const index = this.events[event]?.findIndex(v => v.fn === fn); + if (index === -1 || index === void 0) return; + this.events[event]?.splice(index, 1); + } + + /** + * 监听事件,并只触发一次 + * @param event 要监听的事件 + * @param fn 监听函数 + */ + once(event: K, fn: T[K]): void; + once(event: string, fn: Callable): void; + once(event: string, fn: Callable): void { + this.on(event, fn, { once: true }); + } + + /** + * 触发某个事件 + * @param event 要触发的事件类型 + * @param params 传入的参数 + */ + emit( + event: K, + ...params: Parameters + ): ReturnType[]; + emit(event: string, ...params: any[]): any[]; + emit(event: K, ...params: Parameters): R; + emit(event: string, ...params: any[]): R; + emit(event: string, ...params: any[]): any[] { + this.emitted.add(event); + const events = (this.events[event] ??= []); + if (this.emitter[event]) { + const returns = this.emitter[event]!(events, ...params); + this.events[event] = events.filter(v => !v.once); + return returns; + } else { + const returns: ReturnType[] = []; + for (let i = 0; i < events.length; i++) { + const e = events[i]; + returns.push(e.fn(...(params as any))); + } + this.events[event] = events.filter(v => !v.once); + return returns; + } + } + + /** + * 设置一个事件的执行器(emitter) + * @param event 要设置的事件 + * @param fn 事件执行器,留空以清除触发器 + */ + // @ts-expect-error 无法推导 + setEmitter(event: K, fn?: EmitFn): void; + setEmitter(event: string, fn?: EmitFn): void; + setEmitter(event: string, fn?: EmitFn): void { + this.emitter[event] = fn; + } + + /** + * 取消监听所有的事件,删除所有监听函数 + */ + removeAllListeners(): void; + /** + * 取消监听一个事件的所有函数 + * @param event 要取消监听的事件 + */ + removeAllListeners(event: keyof T): void; + /** + * 取消监听一个事件的所有函数 + * @param event 要取消监听的事件 + */ + removeAllListeners(event: string): void; + removeAllListeners(event?: string | keyof T) { + if (!isNil(event)) this.events[event] = []; + else this.events = {}; + } +} + +type IndexedSymbol = number | string | symbol; + +export class IndexedEventEmitter< + T extends Record +> extends EventEmitter { + private fnMap: { + [P in keyof T]?: Map; + } = {}; + + /** + * 监听事件,并将函数与唯一标识符绑定 + * @param event 事件类型 + * @param symbol 监听函数的唯一标识符 + * @param fn 监听函数 + * @param options 监听配置 + */ + onIndex( + event: K, + symbol: IndexedSymbol, + fn: T[K], + options: Partial + ) { + const map = this.ensureMap(event); + if (map.has(symbol)) { + console.warn( + `监听${String(event)}出错:已存在标识符为${String( + symbol + )}的监听函数,已将其覆盖` + ); + this.offIndex(event, symbol); + } + map.set(symbol, fn); + this.on(event, fn, options); + } + + /** + * 监听事件,绑定唯一标识符,但监听函数只会执行一次 + * @param event 要监听的事件 + * @param symbol 监听函数的唯一标识符 + * @param fn 监听函数 + */ + onceIndex(event: K, symbol: IndexedSymbol, fn: T[K]) { + this.onIndex(event, symbol, fn, { once: true }); + } + + /** + * 取消监听一个事件 + * @param event 要取消监听的事件 + * @param symbol 取消监听的函数的唯一标识符 + */ + offIndex(event: K, symbol: IndexedSymbol) { + const map = this.ensureMap(event); + const fn = map.get(symbol); + if (!fn) return; + this.off(event, fn); + } + + private ensureMap(event: K) { + return this.fnMap[event] ?? new Map(); + } +} diff --git a/packages/legacy-common/src/index.ts b/packages/legacy-common/src/index.ts new file mode 100644 index 0000000..24afaec --- /dev/null +++ b/packages/legacy-common/src/index.ts @@ -0,0 +1,3 @@ +export * from './patch'; +export * from './disposable'; +export * from './eventEmitter'; diff --git a/src/common/patch.ts b/packages/legacy-common/src/patch.ts similarity index 100% rename from src/common/patch.ts rename to packages/legacy-common/src/patch.ts diff --git a/packages/legacy-data/package.json b/packages/legacy-data/package.json new file mode 100644 index 0000000..56463ac --- /dev/null +++ b/packages/legacy-data/package.json @@ -0,0 +1,6 @@ +{ + "name": "@motajs/legacy-data", + "dependencies": { + "@motajs/common": "workspace:*" + } +} \ No newline at end of file diff --git a/src/common/struct.ts b/src/common/struct.ts deleted file mode 100644 index c58c20f..0000000 --- a/src/common/struct.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { EventEmitter } from 'eventemitter3'; - -interface MonoStoreEvent { - change: [before: T | undefined, after: T | undefined]; -} - -/** - * 一个可序列化存储结构,存储多个数据,但是同时只能有一个数据正在使用,适合那些需要切换的场景, - * 例如多勇士,多摄像机等,就可以通过此结构进行存储,然后同时只能操作一个勇士、摄像机等。 - */ -export class MonoStore extends EventEmitter> { - list: Map = new Map(); - using?: T; - usingId?: string; - - /** - * 使用指定id的数据 - * @param id 要使用的数据id - */ - use(id: string) { - const before = this.using; - this.using = this.list.get(id); - this.usingId = id; - this.emit('change', before, this.using); - } - - static toJSON(data: MonoStore) { - return JSON.stringify({ - now: data.usingId, - data: [...data.list] - }); - } - - static fromJSON(data: string): MonoStore { - const d = JSON.parse(data); - const arr: [string, T][] = d.data; - const store = new MonoStore(); - arr.forEach(([key, value]) => { - store.list.set(key, value); - }); - if (d.now) store.use(d.now); - return store; - } -} - -export namespace SerializeUtils { - interface StructSerializer { - toJSON(data: T): string; - fromJSON(data: string): T; - } - - /** - * Map键值对序列化函数,如果使用Map作为state,那么需要使用这里面的函数作为序列化与反序列化函数 - */ - export const mapSerializer: StructSerializer> = { - toJSON(data) { - return JSON.stringify([...data]); - }, - fromJSON(data) { - return new Map(JSON.parse(data)); - } - }; - - /** - * Set集合序列化函数,如果使用Set作为state,那么需要使用这里面的函数作为序列化与反序列化函数 - */ - export const setSerializer: StructSerializer> = { - toJSON(data) { - return JSON.stringify([...data]); - }, - fromJSON(data) { - return new Set(JSON.parse(data)); - } - }; -} diff --git a/src/core/common/eventEmitter.ts b/src/core/common/eventEmitter.ts index 64ea62d..cfca038 100644 --- a/src/core/common/eventEmitter.ts +++ b/src/core/common/eventEmitter.ts @@ -101,7 +101,7 @@ export class EventEmitter = {}> { emit(event: string, ...params: any[]): any[] { this.emitted.add(event); const events = (this.events[event] ??= []); - if (!!this.emitter[event]) { + if (this.emitter[event]) { const returns = this.emitter[event]!(events, ...params); this.events[event] = events.filter(v => !v.once); return returns; @@ -121,7 +121,7 @@ export class EventEmitter = {}> { * @param event 要设置的事件 * @param fn 事件执行器,留空以清除触发器 */ - // @ts-ignore + // @ts-expect-error 无法推导 setEmitter(event: K, fn?: EmitFn): void; setEmitter(event: string, fn?: EmitFn): void; setEmitter(event: string, fn?: EmitFn): void { diff --git a/src/core/common/resource.ts b/src/core/common/resource.ts index 851e596..e43d8a5 100644 --- a/src/core/common/resource.ts +++ b/src/core/common/resource.ts @@ -1,8 +1,7 @@ import axios, { AxiosRequestConfig, ResponseType } from 'axios'; -import { Disposable } from './disposable'; +import { Disposable, EventEmitter } from '@motajs/legacy-common'; import { logger } from '@motajs/common'; import JSZip from 'jszip'; -import { EventEmitter } from './eventEmitter'; type ProgressFn = (now: number, total: number) => void; diff --git a/src/core/index.ts b/src/core/index.ts index 3cc2c60..73428bc 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -55,7 +55,6 @@ import EnemySpecial from '@/panel/enemySpecial.vue'; import EnemyTarget from '@/panel/enemyTarget.vue'; import KeyboardPanel from '@/panel/keyboard.vue'; import { MCGenerator } from './main/layout'; -import { ResourceController } from './loader/controller'; import { logger } from '@motajs/common'; import { Danmaku } from './main/custom/danmaku'; import * as Shadow from './fx/shadow'; @@ -75,7 +74,6 @@ Mota.register('class', 'MotaSetting', MotaSetting); Mota.register('class', 'SettingDisplayer', SettingDisplayer); Mota.register('class', 'UiController', UiController); Mota.register('class', 'MComponent', MComponent); -Mota.register('class', 'ResourceController', ResourceController); Mota.register('class', 'Danmaku', Danmaku); // ----- 函数注册 Mota.register('fn', 'm', m); diff --git a/src/core/loader/controller.ts b/src/core/loader/controller.ts deleted file mode 100644 index 8250a5c..0000000 --- a/src/core/loader/controller.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { EventEmitter } from '../common/eventEmitter'; - -interface ResourceControllerEvent { - add: (uri: string, data: D) => void; - delete: (uri: string, content: T) => void; -} - -export abstract class ResourceController< - D, - T = D -> extends EventEmitter { - list: Record = {}; - - /** - * 添加一个资源 - * @param uri 资源uri - * @param data 资源数据 - */ - abstract add(uri: string, data: D): void; - - /** - * 删除一个资源 - * @param uri 要删除的资源的uri - */ - remove(uri: string) { - const content = this.list[uri]; - delete this.list[uri]; - // this.emit(uri, content); - } -} diff --git a/src/core/main/custom/danmaku.ts b/src/core/main/custom/danmaku.ts index 7de3d09..83064a2 100644 --- a/src/core/main/custom/danmaku.ts +++ b/src/core/main/custom/danmaku.ts @@ -1,5 +1,5 @@ import BoxAnimate from '@/components/boxAnimate.vue'; -import { EventEmitter } from '@/core/common/eventEmitter'; +import { EventEmitter } from 'eventemitter3'; import { logger } from '@motajs/common'; import { ResponseBase } from '@/core/interface'; import { @@ -53,9 +53,9 @@ interface PostLikeResponse extends ResponseBase { } interface DanmakuEvent { - showStart: (danmaku: Danmaku) => void; - showEnd: (danmaku: Danmaku) => void; - like: (liked: boolean, danmaku: Danmaku) => void; + showStart: [danmaku: Danmaku]; + showEnd: [danmaku: Danmaku]; + like: [liked: boolean, danmaku: Danmaku]; } type SpecContentFn = (content: string, type: string) => VNode; diff --git a/src/core/main/custom/keyboard.ts b/src/core/main/custom/keyboard.ts index b008ebf..0c2e20d 100644 --- a/src/core/main/custom/keyboard.ts +++ b/src/core/main/custom/keyboard.ts @@ -1,4 +1,4 @@ -import { EventEmitter, Listener } from '@/core/common/eventEmitter'; +import { EventEmitter, Listener } from '@motajs/legacy-common'; import { KeyCode } from '@/plugin/keyCodes'; import { gameKey } from './hotkey'; import { unwarpBinary } from './hotkey'; diff --git a/src/core/main/custom/toolbar.ts b/src/core/main/custom/toolbar.ts index 104a55c..19858a4 100644 --- a/src/core/main/custom/toolbar.ts +++ b/src/core/main/custom/toolbar.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from '@/core/common/eventEmitter'; +import { EventEmitter } from '@motajs/legacy-common'; import { deleteWith, has } from '@/plugin/utils'; import { Component, nextTick, reactive, shallowReactive } from 'vue'; import { fixedUi } from '../init/ui'; @@ -11,7 +11,6 @@ import type { ToolbarItemMap, ToolbarItemType } from '../init/toolbar'; -import { isMobile } from '@/plugin/use'; interface CustomToolbarEvent { add: (item: ValueOf) => void; diff --git a/src/core/main/custom/ui.ts b/src/core/main/custom/ui.ts index 435f419..5a2ca94 100644 --- a/src/core/main/custom/ui.ts +++ b/src/core/main/custom/ui.ts @@ -1,8 +1,6 @@ import { Component, shallowReactive } from 'vue'; -import { Callable, EventEmitter } from '../../common/eventEmitter'; -import { KeyCode } from '@/plugin/keyCodes'; +import { EventEmitter } from '@motajs/legacy-common'; import { Hotkey } from './hotkey'; -import { generateBinary } from '@/plugin/utils'; interface FocusEvent { focus: (before: T | null, after: T) => void; diff --git a/src/core/main/setting.ts b/src/core/main/setting.ts index 0af05ee..ebfc674 100644 --- a/src/core/main/setting.ts +++ b/src/core/main/setting.ts @@ -1,5 +1,5 @@ import { FunctionalComponent, reactive } from 'vue'; -import { EventEmitter } from '../common/eventEmitter'; +import { EventEmitter } from '@motajs/legacy-common'; import { GameStorage } from './storage'; import { has, triggerFullscreen } from '@/plugin/utils'; import { createSettingComponents } from './init/settings'; diff --git a/src/game/index.ts b/src/game/index.ts index dae7466..46e8afe 100644 --- a/src/game/index.ts +++ b/src/game/index.ts @@ -1,13 +1,12 @@ import './system'; import '../plugin/game/index'; import * as damage from './enemy/damage'; -import { EventEmitter, IndexedEventEmitter } from '@/core/common/eventEmitter'; +import { EventEmitter, IndexedEventEmitter } from '@motajs/legacy-common'; import { specials } from './enemy/special'; import { gameListener, hook, loading } from './game'; import * as battle from './enemy/battle'; import * as hero from './state/hero'; import * as miscMechanism from './mechanism/misc'; -import { registerPresetState } from './state/preset'; import { ItemState } from './state/item'; import { BlockMover, @@ -52,8 +51,6 @@ loading.once('coreInit', () => { Mota.Plugin.init(); }); -registerPresetState(); - window.addEventListener('beforeunload', () => { core.checkAutosave(); }); diff --git a/src/game/state/preset.ts b/src/game/state/preset.ts deleted file mode 100644 index 92a90b5..0000000 --- a/src/game/state/preset.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { MonoStore } from '@/common/struct'; -import { GameState } from './state'; -import { Hero, HeroState } from './hero'; - -export function registerPresetState() { - GameState.register>>('hero', heroToJSON, heroFromJSON); -} - -interface HeroSave { - x: number; - y: number; - floorId: FloorIds; - id: string; - items: [AllIdsOf<'items'>, number][]; - state: { - status: any; - buffable: (string | number | symbol)[]; - buffMap: [string | number | symbol, number][]; - }; -} - -interface HeroSerializable { - now: string | null; - saves: HeroSave[]; -} - -function heroToJSON(data: MonoStore>): string { - const now = data.usingId ?? null; - const saves: HeroSave[] = [...data.list.values()].map(v => { - return { - x: v.x, - y: v.y, - floorId: v.floorId, - id: v.id, - items: [...v.items], - state: { - status: v.state.status, - buffable: [...v.state.buffable], - buffMap: [...v.state.buffMap] - } - }; - }); - const obj: HeroSerializable = { - now, - saves - }; - return JSON.stringify(obj); -} - -function heroFromJSON(data: string): MonoStore> { - const obj: HeroSerializable = JSON.parse(data); - const store = new MonoStore>(); - const saves: [string, Hero][] = obj.saves.map(v => { - const state = new HeroState(v.state.status); - v.state.buffable.forEach(v => state.buffable.add(v)); - v.state.buffMap.forEach(v => state.buffMap.set(v[0], v[1])); - const hero = new Hero(v.id, v.x, v.y, v.floorId, state); - v.items.forEach(v => hero.items.set(v[0], v[1])); - return [hero.id, hero]; - }); - store.list = new Map(saves); - if (obj.now) store.use(obj.now); - return store; -} diff --git a/src/game/system.ts b/src/game/system.ts index 0a26f80..42fc974 100644 --- a/src/game/system.ts +++ b/src/game/system.ts @@ -1,8 +1,8 @@ -import type { Disposable } from '@/core/common/disposable'; import type { + Disposable, EventEmitter, IndexedEventEmitter -} from '@/core/common/eventEmitter'; +} from '@motajs/legacy-common'; import type { loading } from './game'; import type { Hotkey } from '@/core/main/custom/hotkey'; import type { Keyboard } from '@/core/main/custom/keyboard'; diff --git a/src/module/fallback/audio.ts b/src/module/fallback/audio.ts index a678034..6eece71 100644 --- a/src/module/fallback/audio.ts +++ b/src/module/fallback/audio.ts @@ -1,4 +1,4 @@ -import { Patch, PatchClass } from '@/common/patch'; +import { Patch, PatchClass } from '@motajs/legacy-common'; import { audioPlayer, bgmController, soundPlayer } from '../audio'; import { mainSetting } from '@/core/main/setting'; import { sleep } from 'mutate-animate'; diff --git a/src/module/fallback/index.ts b/src/module/fallback/index.ts index a4b2277..d1b1775 100644 --- a/src/module/fallback/index.ts +++ b/src/module/fallback/index.ts @@ -1,4 +1,4 @@ -import { Patch } from '@/common/patch'; +import { Patch } from '@motajs/legacy-common'; import { patchAudio } from './audio'; import { patchWeather } from './weather'; import { patchUI } from './ui'; diff --git a/src/module/fallback/ui.ts b/src/module/fallback/ui.ts index 6b88af5..60cc772 100644 --- a/src/module/fallback/ui.ts +++ b/src/module/fallback/ui.ts @@ -1,4 +1,4 @@ -import { Patch, PatchClass } from '@/common/patch'; +import { Patch, PatchClass } from '@motajs/legacy-common'; import { TipStore } from '../render/components/tip'; export function patchUI() { diff --git a/src/module/fallback/weather.ts b/src/module/fallback/weather.ts index d8fb989..e21bfca 100644 --- a/src/module/fallback/weather.ts +++ b/src/module/fallback/weather.ts @@ -1,4 +1,4 @@ -import { Patch, PatchClass } from '@/common/patch'; +import { Patch, PatchClass } from '@motajs/legacy-common'; import { WeatherController } from '../weather'; import { isNil } from 'lodash-es'; diff --git a/src/package.json b/src/package.json index 9df62d1..28cb944 100644 --- a/src/package.json +++ b/src/package.json @@ -5,6 +5,9 @@ "@motajs/common": "workspace:*", "@motajs/render": "workspace:*", "@motajs/system": "workspace:*", - "@motajs/system-ui": "workspace:*" + "@motajs/system-ui": "workspace:*", + "@motajs/legacy-common": "workspace:*", + "@motajs/legacy-client": "workspace:*", + "@motajs/legacy-data": "workspace:*" } } \ No newline at end of file