mirror of
https://github.com/motajs/template.git
synced 2026-09-27 21:50:19 +08:00
refactor: 加载系统重构
This commit is contained in:
parent
d12790280a
commit
cc434a6c66
@ -1,3 +0,0 @@
|
||||
export * from './loader';
|
||||
export * from './processor';
|
||||
export * from './types';
|
||||
@ -1,63 +0,0 @@
|
||||
import {
|
||||
ILoadManager,
|
||||
LoadDataType,
|
||||
ILoadTask,
|
||||
ILoadTaskProcessor
|
||||
} from '@motajs/loader';
|
||||
import { CustomLoadFunc, IMotaDataLoader } from './types';
|
||||
import { LoadJSONProcessor } from './processor';
|
||||
|
||||
interface LoadTaskStore<T extends LoadDataType = LoadDataType, R = any> {
|
||||
/** 加载任务对象 */
|
||||
readonly task: ILoadTask<T, R>;
|
||||
/** 当 `onLoaded` 兑现后兑现的 `Promise` */
|
||||
readonly loadPromise: Promise<R>;
|
||||
/** 兑现 `loadPromise` */
|
||||
readonly loadResolve: (data: R) => void;
|
||||
/** 当加载任务完成时执行的函数 */
|
||||
readonly onLoaded: CustomLoadFunc<R>;
|
||||
}
|
||||
|
||||
export class MotaDataLoader implements IMotaDataLoader {
|
||||
/** 当前已添加的加载任务 */
|
||||
private readonly tasks: Set<LoadTaskStore> = new Set();
|
||||
|
||||
readonly jsonProcessor: ILoadTaskProcessor<LoadDataType.JSON, any>;
|
||||
|
||||
constructor(readonly progress: ILoadManager) {
|
||||
this.jsonProcessor = new LoadJSONProcessor();
|
||||
}
|
||||
|
||||
//#region 对外接口
|
||||
|
||||
initSystemLoadTask(): void {}
|
||||
|
||||
addCustomLoadTask<R>(
|
||||
task: ILoadTask<LoadDataType, R>,
|
||||
onLoaded: CustomLoadFunc<R>
|
||||
): Promise<R> {
|
||||
this.progress.addTask(task);
|
||||
const { promise, resolve } = Promise.withResolvers<R>();
|
||||
const store: LoadTaskStore<LoadDataType, R> = {
|
||||
task,
|
||||
onLoaded,
|
||||
loadPromise: promise,
|
||||
loadResolve: resolve
|
||||
};
|
||||
this.tasks.add(store);
|
||||
return promise;
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
const tasks = [...this.tasks].map(async task => {
|
||||
task.task.start();
|
||||
const data = await task.task.loaded();
|
||||
await task.onLoaded(data);
|
||||
task.loadResolve(data);
|
||||
return data;
|
||||
});
|
||||
await Promise.all(tasks);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
import { IMotaAudioContext } from '@motajs/audio';
|
||||
import { ILoadTask, ILoadTaskProcessor, LoadDataType } from '@motajs/loader';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
export class LoadImageProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.Blob,
|
||||
ImageBitmap
|
||||
> {
|
||||
process(response: Blob): Promise<ImageBitmap> {
|
||||
return createImageBitmap(response);
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadAudioProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.Uint8Array,
|
||||
AudioBuffer | null
|
||||
> {
|
||||
constructor(private readonly ac: IMotaAudioContext) {}
|
||||
|
||||
process(response: Uint8Array<ArrayBuffer>): Promise<AudioBuffer | null> {
|
||||
return this.ac.decodeToAudioBuffer(response);
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadFontProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.ArrayBuffer,
|
||||
FontFace
|
||||
> {
|
||||
process(
|
||||
response: ArrayBuffer,
|
||||
task: ILoadTask<LoadDataType.ArrayBuffer, FontFace>
|
||||
): Promise<FontFace> {
|
||||
const font = new FontFace(task.identifier, response);
|
||||
if (font.status === 'loaded') return Promise.resolve(font);
|
||||
else return font.load();
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadZipProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.ArrayBuffer,
|
||||
JSZip
|
||||
> {
|
||||
async process(response: ArrayBuffer): Promise<JSZip> {
|
||||
const zip = new JSZip();
|
||||
await zip.loadAsync(response);
|
||||
return zip;
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadTextProcessor implements ILoadTaskProcessor<
|
||||
LoadDataType.Text,
|
||||
string
|
||||
> {
|
||||
process(response: string): Promise<string> {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadJSONProcessor<T> implements ILoadTaskProcessor<
|
||||
LoadDataType.JSON,
|
||||
T
|
||||
> {
|
||||
process(response: any): Promise<T> {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
import {
|
||||
ILoadManager,
|
||||
ILoadTask,
|
||||
ILoadTaskProcessor,
|
||||
LoadDataType
|
||||
} from '@motajs/loader';
|
||||
|
||||
export type CustomLoadFunc<R> = (data: R) => Promise<void>;
|
||||
|
||||
export interface IMotaDataLoader {
|
||||
/** 加载进度对象 */
|
||||
readonly progress: ILoadManager;
|
||||
|
||||
/** json 处理器 */
|
||||
readonly jsonProcessor: ILoadTaskProcessor<LoadDataType.JSON, any>;
|
||||
|
||||
/**
|
||||
* 初始化系统加载任务
|
||||
*/
|
||||
initSystemLoadTask(): void;
|
||||
|
||||
/**
|
||||
* 添加自定义加载任务
|
||||
* @param task 自定义加载任务
|
||||
* @param onLoad 当任务加载完成时执行
|
||||
* @returns 一个 `Promise`,当添加的任务加载完毕,且 `onLoad` 返回的 `Promise` 兑现后兑现
|
||||
*/
|
||||
addCustomLoadTask<R>(
|
||||
task: ILoadTask<LoadDataType, R>,
|
||||
onLoad: CustomLoadFunc<R>
|
||||
): Promise<R>;
|
||||
|
||||
/**
|
||||
* 开始所有加载任务的加载工作
|
||||
* @returns 一个 `Promise`,当所有加载任务加载完成后兑现
|
||||
*/
|
||||
load(): Promise<void>;
|
||||
}
|
||||
93
packages-user/data-state/src/common.ts
Normal file
93
packages-user/data-state/src/common.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { IHeroAttr } from '@user/data-common';
|
||||
|
||||
//#region 数据端主配置
|
||||
|
||||
export interface ICoreStateContentDefaultConfig {
|
||||
/** 怪物默认对象路径 */
|
||||
readonly enemyDefault: string;
|
||||
/** 道具默认对象路径 */
|
||||
readonly itemDefault: string;
|
||||
/** 地图默认对象路径 */
|
||||
readonly mapDefault: string;
|
||||
/** 图块默认对象路径 */
|
||||
readonly tileDefault: string;
|
||||
}
|
||||
|
||||
export interface ICoreStateContentConfig {
|
||||
/** 配置的默认对象配置路径 */
|
||||
readonly defaults: ICoreStateContentDefaultConfig;
|
||||
/** 地图文件夹路径 */
|
||||
readonly mapsDir: string;
|
||||
/** 需要加载的地图列表文件的文件路径 */
|
||||
readonly mapListDir: string;
|
||||
/** 怪物数据文件路径 */
|
||||
readonly enemyDir: string;
|
||||
/** 道具数据文件路径 */
|
||||
readonly itemDir: string;
|
||||
/** 道具效果文件路径 */
|
||||
readonly itemEffectDir: string;
|
||||
/** 图库数据文件路径 */
|
||||
readonly tileDir: string;
|
||||
}
|
||||
|
||||
export interface ICoreStateHeroInitConfig<T> {
|
||||
/** 勇士的初始横坐标 */
|
||||
readonly x: number;
|
||||
/** 勇士的初始纵坐标 */
|
||||
readonly y: number;
|
||||
/** 勇士初始所在楼层 */
|
||||
readonly floorId: string;
|
||||
/** 勇士的初始装备栏 */
|
||||
readonly equipSlots: string[];
|
||||
/** 勇士的初始属性 */
|
||||
readonly attribute: Readonly<T>;
|
||||
}
|
||||
|
||||
export interface ICoreStateInitConfig {
|
||||
/** 初始事件 id,游戏开始时将在执行完毕此事件后正式开始 */
|
||||
readonly startEvent: string;
|
||||
/** 勇士初始状态,包含起始位置、初始属性等 */
|
||||
readonly hero: ICoreStateHeroInitConfig<IHeroAttr>;
|
||||
}
|
||||
|
||||
export interface ICoreStateFlagConfig {
|
||||
/** 楼层传送是否要求在楼梯边才能使用 */
|
||||
readonly flyNearStair: boolean;
|
||||
/** 楼层传送是否为平面塔模式,即传送时会传送至上一次离开此位置的位置而非楼梯口 */
|
||||
readonly flyRecordLocation: boolean;
|
||||
/** 道具在首次拾取时是否显示其文字说明 */
|
||||
readonly itemHint: boolean;
|
||||
/** 是否启用负伤机制,开启后怪物总伤害可以变为负值 */
|
||||
readonly enableNegativeDamage: boolean;
|
||||
/** 是否限制夹击最高伤害为两夹击怪物中伤害最高者 */
|
||||
readonly betweenDamageMax: boolean;
|
||||
/** 是否启用轻按机制,即可以按下空格来拾取玩家四周的物品 */
|
||||
readonly enablePick: boolean;
|
||||
/** 若在寻路时需要穿过楼梯,是否允许穿越 */
|
||||
readonly ignoreChangeFLoor: boolean;
|
||||
/** 若继续前进会导致勇士受到地图伤害而死亡,是否允许前进 */
|
||||
readonly allowDeadZone: boolean;
|
||||
/** 是否允许点击瞬移 */
|
||||
readonly enableTeleport: boolean;
|
||||
/** 是否开启录像折叠,这可以使走路的录像大幅减少,但可能导致某些机制出错,可查看文档详细了解 */
|
||||
readonly enableRouteFolding: boolean;
|
||||
/** 是否开启瞬移合并机制,开启后,连续瞬移会在录像中合并为单次瞬移,但可能导致某些机制出错 */
|
||||
readonly enableTeleportMerge: boolean;
|
||||
}
|
||||
|
||||
export interface ICoreStateCoreConfig {
|
||||
/** 游戏的唯一标识符 */
|
||||
readonly name: string;
|
||||
/** 呈现给玩家的游戏名称 */
|
||||
readonly title: string;
|
||||
/** 游戏版本号,仅呈现作用,对游戏本身没有任何影响 */
|
||||
readonly version: string;
|
||||
/** 游戏数据配置 */
|
||||
readonly content: ICoreStateContentConfig;
|
||||
/** 游戏的初始状态数据 */
|
||||
readonly init: ICoreStateInitConfig;
|
||||
/** 游戏的全局设置 */
|
||||
readonly flag: ICoreStateFlagConfig;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@ -1,5 +1,4 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { ILoadManager, LoadManager } from '@motajs/loader';
|
||||
import {
|
||||
IRoleFaceBinder,
|
||||
IFaceManager,
|
||||
@ -33,8 +32,6 @@ import {
|
||||
IHeroState,
|
||||
IFlagSystem,
|
||||
FlagSystem,
|
||||
IMotaDataLoader,
|
||||
MotaDataLoader,
|
||||
IMapState,
|
||||
MapState
|
||||
} from '@user/data-base';
|
||||
@ -70,6 +67,10 @@ import {
|
||||
ReplayUnequip,
|
||||
ReplayUseItem
|
||||
} from './replay';
|
||||
import { IMotaDataLoader } from './loader/types';
|
||||
import { MotaDataLoader } from './loader/loader';
|
||||
import { ILoadManager, LoadManager } from '@motajs/loader';
|
||||
import { DefaultDataLoaderHook } from './loader/hook';
|
||||
|
||||
export class CoreState implements ICoreState {
|
||||
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
|
||||
@ -93,8 +94,8 @@ export class CoreState implements ICoreState {
|
||||
readonly pathfinding: IPathfindingSystem;
|
||||
|
||||
// Layer 3 用户层,也就是最顶层的内容,一般仅用于初始化以及仅供渲染端调用的顶层模块
|
||||
readonly loadProgress: ILoadManager;
|
||||
readonly dataLoader: IMotaDataLoader;
|
||||
readonly loader: IMotaDataLoader;
|
||||
readonly loadManager: ILoadManager;
|
||||
|
||||
/** 可存档对象映射 */
|
||||
private readonly saveables: Map<string, ISaveableContent<any>> = new Map();
|
||||
@ -143,9 +144,6 @@ export class CoreState implements ICoreState {
|
||||
const heroState = new HeroState(this, dir8, heroAttribute);
|
||||
this.hero = heroState;
|
||||
|
||||
this.loadProgress = new LoadManager();
|
||||
this.dataLoader = new MotaDataLoader(this.loadProgress);
|
||||
|
||||
// 怪物管理器
|
||||
const comparer = new MainEnemyComparer();
|
||||
const enemyManager = new EnemyManager(new EnemyLegacyBridge());
|
||||
@ -201,6 +199,15 @@ export class CoreState implements ICoreState {
|
||||
|
||||
//#region L3 初始化
|
||||
|
||||
// 加载
|
||||
this.loadManager = new LoadManager();
|
||||
this.loader = new MotaDataLoader(
|
||||
this.loadManager,
|
||||
config.coreURL,
|
||||
config.loadStarter
|
||||
);
|
||||
this.loader.addHook(new DefaultDataLoaderHook());
|
||||
|
||||
// 存档内容
|
||||
this.addSaveableContent('@system/hero', this.hero);
|
||||
this.addSaveableContent('@system/flags', this.flags);
|
||||
|
||||
26
packages-user/data-state/src/loader/hook.ts
Normal file
26
packages-user/data-state/src/loader/hook.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { ICoreStateCoreConfig } from '../common';
|
||||
import { IMotaDataLoader, IMotaDataLoaderHooks } from './types';
|
||||
|
||||
export class DefaultDataLoaderHook implements IMotaDataLoaderHooks {
|
||||
awake(): void {}
|
||||
|
||||
destroy(): void {}
|
||||
|
||||
onCoreConfigLoaded(
|
||||
coreConfig: ICoreStateCoreConfig,
|
||||
loader: IMotaDataLoader
|
||||
): Promise<void> {
|
||||
const content = coreConfig.content;
|
||||
loader.addExtraConfig('enemy', content.enemyDir);
|
||||
loader.addExtraConfig('item', content.itemDir);
|
||||
loader.addExtraConfig('map-list', content.mapListDir);
|
||||
loader.addExtraConfig('tile', content.tileDir);
|
||||
const defaults = content.defaults;
|
||||
loader.addExtraConfig('enemy-default', defaults.enemyDefault);
|
||||
loader.addExtraConfig('item-default', defaults.itemDefault);
|
||||
loader.addExtraConfig('map-default', defaults.mapDefault);
|
||||
loader.addExtraConfig('tile-default', defaults.tileDefault);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
0
packages-user/data-state/src/loader/index.ts
Normal file
0
packages-user/data-state/src/loader/index.ts
Normal file
29
packages-user/data-state/src/loader/jsoncProcessor.ts
Normal file
29
packages-user/data-state/src/loader/jsoncProcessor.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { ILoadTask, ILoadTaskProcessor, LoadDataType } from '@motajs/loader';
|
||||
|
||||
export class PrefixedJSONCProcessor<T> implements ILoadTaskProcessor<
|
||||
LoadDataType.Text,
|
||||
T
|
||||
> {
|
||||
constructor(readonly prefixSplit: string) {}
|
||||
|
||||
process(
|
||||
response: string,
|
||||
task: ILoadTask<LoadDataType.Text, T>
|
||||
): Promise<T> {
|
||||
const splitted = response.split(this.prefixSplit);
|
||||
if (splitted.length !== 2) {
|
||||
logger.error(66, task.url, this.prefixSplit);
|
||||
return Promise.resolve({} as T);
|
||||
}
|
||||
|
||||
const raw = splitted[1];
|
||||
try {
|
||||
const obj = JSON.parse(raw);
|
||||
return Promise.resolve(obj as T);
|
||||
} catch (e) {
|
||||
logger.error(67, task.url, String(e));
|
||||
return Promise.resolve({} as T);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
packages-user/data-state/src/loader/loader.ts
Normal file
145
packages-user/data-state/src/loader/loader.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import {
|
||||
ILoadManager,
|
||||
ILoadTask,
|
||||
ILoadTaskStarter,
|
||||
LoadDataType,
|
||||
LoadTask
|
||||
} from '@motajs/loader';
|
||||
import { IMotaDataLoader, IMotaDataLoaderHooks } from './types';
|
||||
import { PrefixedJSONCProcessor } from './jsoncProcessor';
|
||||
import { ICoreStateCoreConfig } from '../common';
|
||||
import {
|
||||
Hookable,
|
||||
HookController,
|
||||
IHookController,
|
||||
logger
|
||||
} from '@motajs/common';
|
||||
|
||||
export class MotaDataLoader
|
||||
extends Hookable<IMotaDataLoaderHooks>
|
||||
implements IMotaDataLoader
|
||||
{
|
||||
/** 加载完毕时兑现的 `Promise` */
|
||||
private loadedPromise: Promise<void>;
|
||||
/** 调用以使 `loadedPromise` 兑现 */
|
||||
private loadResolve: () => void;
|
||||
/** 当前是否正在加载 */
|
||||
private loading: boolean = false;
|
||||
/** 当前是否已经加载完毕 */
|
||||
private dataLoaded: boolean = false;
|
||||
|
||||
/** 额外配置对象的路径 */
|
||||
private extraConfigs: Map<string, string> = new Map();
|
||||
/** 配置文件列表 */
|
||||
private configMap: Map<string, any> = new Map();
|
||||
|
||||
/** 自定义加载任务 */
|
||||
private customTask: Set<ILoadTask<LoadDataType, unknown>> = new Set();
|
||||
|
||||
constructor(
|
||||
readonly manager: ILoadManager,
|
||||
readonly coreDir: string,
|
||||
readonly starter: ILoadTaskStarter
|
||||
) {
|
||||
super();
|
||||
const { promise, resolve } = Promise.withResolvers<void>();
|
||||
this.loadedPromise = promise;
|
||||
this.loadResolve = resolve;
|
||||
}
|
||||
|
||||
protected createController(
|
||||
hook: Partial<IMotaDataLoaderHooks>
|
||||
): IHookController<IMotaDataLoaderHooks> {
|
||||
return new HookController(this, hook);
|
||||
}
|
||||
|
||||
addExtraConfig(identifier: string, url: string): void {
|
||||
this.extraConfigs.set(identifier, url);
|
||||
}
|
||||
|
||||
addCustomTask<T>(task: LoadTask<LoadDataType, T>): Promise<T> {
|
||||
this.customTask.add(task);
|
||||
return task.loaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析加载路径,根据运行环境确认应当使用什么路径格式
|
||||
* @param url 加载路径
|
||||
*/
|
||||
private resolveURL(url: string) {
|
||||
if (import.meta.env.DEV) {
|
||||
// 开发时 content 文件都在 src 文件夹下,因此前置 src
|
||||
return `${import.meta.env.BASE_URL}src/${url}`;
|
||||
} else {
|
||||
// 而构建后就会把文件移动至游戏根目录下,因此不需要前置 src
|
||||
return `${import.meta.env.BASE_URL}${url}`;
|
||||
}
|
||||
}
|
||||
|
||||
async *start(): AsyncIterable<number> {
|
||||
if (this.loading || this.dataLoaded) return;
|
||||
|
||||
// 首先加载核心配置
|
||||
const processor = new PrefixedJSONCProcessor<ICoreStateCoreConfig>(
|
||||
'// --- SYSTEM PREFIX END --- //'
|
||||
);
|
||||
const coreTask = new LoadTask<LoadDataType.Text, ICoreStateCoreConfig>({
|
||||
identifier: 'data-core-config',
|
||||
url: this.resolveURL(this.coreDir),
|
||||
dataType: LoadDataType.Text
|
||||
});
|
||||
coreTask.setProcessor(processor);
|
||||
coreTask.setStarter(this.starter);
|
||||
this.manager.addTask(coreTask);
|
||||
yield* this.manager.load();
|
||||
|
||||
// 然后加载其他配置文件
|
||||
const coreConfig = await coreTask.loaded();
|
||||
this.configMap.set('core', coreConfig);
|
||||
await Promise.all(
|
||||
this.forEachHook(hook =>
|
||||
hook.onCoreConfigLoaded?.(coreConfig, this)
|
||||
)
|
||||
);
|
||||
|
||||
for (const [id, url] of this.extraConfigs) {
|
||||
const task = new LoadTask({
|
||||
identifier: id,
|
||||
url: this.resolveURL(`content/${url}`),
|
||||
dataType: LoadDataType.Text
|
||||
});
|
||||
task.setProcessor(processor);
|
||||
task.setStarter(this.starter);
|
||||
this.manager.addTask(task);
|
||||
task.loaded().then(obj => {
|
||||
if (this.configMap.has(id)) {
|
||||
logger.error(68, id);
|
||||
return;
|
||||
}
|
||||
this.configMap.set(id, obj);
|
||||
});
|
||||
}
|
||||
|
||||
yield* this.manager.load();
|
||||
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => hook.onExtraConfigLoaded?.(this))
|
||||
);
|
||||
|
||||
for (const task of this.customTask) {
|
||||
this.manager.addTask(task);
|
||||
}
|
||||
|
||||
yield* this.manager.load();
|
||||
|
||||
this.loadResolve();
|
||||
}
|
||||
|
||||
getConfig<T>(identifier: string): T | null {
|
||||
return this.configMap.get(identifier) ?? null;
|
||||
}
|
||||
|
||||
loaded(): Promise<void> {
|
||||
return this.loadedPromise;
|
||||
}
|
||||
}
|
||||
62
packages-user/data-state/src/loader/types.ts
Normal file
62
packages-user/data-state/src/loader/types.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { IHookable, IHookBase } from '@motajs/common';
|
||||
import { ILoadManager, LoadDataType, LoadTask } from '@motajs/loader';
|
||||
import { ICoreStateCoreConfig } from '../common';
|
||||
|
||||
export interface IMotaDataLoaderHooks extends IHookBase {
|
||||
/**
|
||||
* 当核心配置对象加载完毕时触发,往往用于添加自定义配置文件
|
||||
* @param coreConfig 核心配置对象
|
||||
* @param loader 数据端加载对象
|
||||
*/
|
||||
onCoreConfigLoaded?(
|
||||
coreConfig: ICoreStateCoreConfig,
|
||||
loader: IMotaDataLoader
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当所有的额外配置对象加载完毕后执行,往往用于根据配置文件添加额外加载任务
|
||||
* @param loader 数据端加载对象
|
||||
*/
|
||||
onExtraConfigLoaded?(loader: IMotaDataLoader): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IMotaDataLoader extends IHookable<IMotaDataLoaderHooks> {
|
||||
/** 当前使用的加载管理器 */
|
||||
readonly manager: ILoadManager;
|
||||
|
||||
/**
|
||||
* 开始执行加载
|
||||
*/
|
||||
start(): AsyncIterable<number>;
|
||||
|
||||
/**
|
||||
* 获取当加载执行完毕时兑现的 `Promise`
|
||||
*/
|
||||
loaded(): Promise<void>;
|
||||
|
||||
/**
|
||||
* 添加额外配置文件,仅允许在 `onCoreConfigLoaded` 中调用或在加载前调用,加载中及加载后后调用无效。
|
||||
* 文件必须放在 `src/content` 下,并填写相对于 `src/content` 文件夹的路径,文件包含如下要求:
|
||||
*
|
||||
* - 文件必须为 `jsonc` 格式
|
||||
* - 文件的注释必须全部集中在开头
|
||||
* - 文件中的最后一行注释必须是 `// --- SYSTEM PREFIX END --- //`
|
||||
* @param identifier 加载任务与配置文件的标识符
|
||||
* @param url 额外配置文件路径
|
||||
*/
|
||||
addExtraConfig(identifier: string, url: string): void;
|
||||
|
||||
/**
|
||||
* 添加自定义加载任务,仅允许在 `onCoreConfigLoaded` 和 `onExtraConfigLoaded` 中调用或在加载前调用,
|
||||
* 加载中及加载后后调用无效。所有的自定义加载任务都会在配置文件加载完毕后开始加载。
|
||||
* @param task 自定义加载任务实例
|
||||
* @returns 任务加载完毕后兑现,兑现值为经过加载处理器处理后的值
|
||||
*/
|
||||
addCustomTask<T>(task: LoadTask<LoadDataType, T>): Promise<T>;
|
||||
|
||||
/**
|
||||
* 获取指定配置文件,例如核心配置文件是 `core`,怪物配置文件是 `enemy` 等等
|
||||
* @param identifier 配置文件标识符
|
||||
*/
|
||||
getConfig<T>(identifier: string): T | null;
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
import { IMotaDataLoader, IStateBase } from '@user/data-base';
|
||||
import { IStateBase } from '@user/data-base';
|
||||
import { ILoadManager, ILoadTaskStarter } from '@motajs/loader';
|
||||
import { IStateSystem } from '@user/data-system';
|
||||
import { IHeroAttr, ISaveableContent } from '@user/data-common';
|
||||
import { ISaveableContent } from '@user/data-common';
|
||||
import { IMotaDataLoader } from './loader/types';
|
||||
|
||||
//#region 数据端主对象
|
||||
|
||||
@ -26,10 +27,10 @@ export interface ISaveableExecutor<T> {
|
||||
|
||||
export interface ICoreState
|
||||
extends IStateSystem, ISaveableContent<ReadonlyMap<string, unknown>> {
|
||||
/** 加载进度对象 */
|
||||
readonly loadProgress: ILoadManager;
|
||||
/** 数据端加载对象 */
|
||||
readonly dataLoader: IMotaDataLoader;
|
||||
readonly loader: IMotaDataLoader;
|
||||
/** 加载管理器 */
|
||||
readonly loadManager: ILoadManager;
|
||||
|
||||
/**
|
||||
* 将某个存档执行器绑定至指定的可存档对象,一个可存档对象只能绑定一个执行器,
|
||||
@ -44,80 +45,3 @@ export interface ICoreState
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 数据端主配置
|
||||
|
||||
export interface ICoreStateContentConfig {
|
||||
/** 地图文件夹路径 */
|
||||
readonly mapsDir: string;
|
||||
/** 需要加载的地图列表文件的文件路径 */
|
||||
readonly mapListDir: string;
|
||||
/** 怪物数据文件路径 */
|
||||
readonly enemyDir: string;
|
||||
/** 道具数据文件路径 */
|
||||
readonly itemDir: string;
|
||||
/** 图库数据文件路径 */
|
||||
readonly tileDir: string;
|
||||
}
|
||||
|
||||
export interface ICoreStateHeroInitConfig<T> {
|
||||
/** 勇士的初始横坐标 */
|
||||
readonly x: number;
|
||||
/** 勇士的初始纵坐标 */
|
||||
readonly y: number;
|
||||
/** 勇士初始所在楼层 */
|
||||
readonly floorId: string;
|
||||
/** 勇士的初始装备栏 */
|
||||
readonly equipSlots: string[];
|
||||
/** 勇士的初始属性 */
|
||||
readonly attribute: Readonly<T>;
|
||||
}
|
||||
|
||||
export interface ICoreStateInitConfig {
|
||||
/** 初始事件 id,游戏开始时将在执行完毕此事件后正式开始 */
|
||||
readonly startEvent: string;
|
||||
/** 勇士初始状态,包含起始位置、初始属性等 */
|
||||
readonly hero: ICoreStateHeroInitConfig<IHeroAttr>;
|
||||
}
|
||||
|
||||
export interface ICoreStateFlagConfig {
|
||||
/** 楼层传送是否要求在楼梯边才能使用 */
|
||||
readonly flyNearStair: boolean;
|
||||
/** 楼层传送是否为平面塔模式,即传送时会传送至上一次离开此位置的位置而非楼梯口 */
|
||||
readonly flyRecordLocation: boolean;
|
||||
/** 道具在首次拾取时是否显示其文字说明 */
|
||||
readonly itemHint: boolean;
|
||||
/** 是否启用负伤机制,开启后怪物总伤害可以变为负值 */
|
||||
readonly enableNegativeDamage: boolean;
|
||||
/** 是否限制夹击最高伤害为两夹击怪物中伤害最高者 */
|
||||
readonly betweenDamageMax: boolean;
|
||||
/** 是否启用轻按机制,即可以按下空格来拾取玩家四周的物品 */
|
||||
readonly enablePick: boolean;
|
||||
/** 若在寻路时需要穿过楼梯,是否允许穿越 */
|
||||
readonly ignoreChangeFLoor: boolean;
|
||||
/** 若继续前进会导致勇士受到地图伤害而死亡,是否允许前进 */
|
||||
readonly allowDeadZone: boolean;
|
||||
/** 是否允许点击瞬移 */
|
||||
readonly enableTeleport: boolean;
|
||||
/** 是否开启录像折叠,这可以使走路的录像大幅减少,但可能导致某些机制出错,可查看文档详细了解 */
|
||||
readonly enableRouteFolding: boolean;
|
||||
/** 是否开启瞬移合并机制,开启后,连续瞬移会在录像中合并为单次瞬移,但可能导致某些机制出错 */
|
||||
readonly enableTeleportMerge: boolean;
|
||||
}
|
||||
|
||||
export interface ICoreStateCoreConfig {
|
||||
/** 游戏的唯一标识符 */
|
||||
readonly name: string;
|
||||
/** 呈现给玩家的游戏名称 */
|
||||
readonly title: string;
|
||||
/** 游戏版本号,仅呈现作用,对游戏本身没有任何影响 */
|
||||
readonly version: string;
|
||||
/** 游戏数据配置 */
|
||||
readonly content: ICoreStateContentConfig;
|
||||
/** 游戏的初始状态数据 */
|
||||
readonly init: ICoreStateInitConfig;
|
||||
/** 游戏的全局设置 */
|
||||
readonly flag: ICoreStateFlagConfig;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@ -65,6 +65,9 @@
|
||||
"63": "Cannot create game map '$2' from raw data since the '$1' container is missing, null, or not an object.",
|
||||
"64": "Cannot create game map '$2' from raw data since the '$1' value has an invalid type or range.",
|
||||
"65": "Cannot start pathfinding movement because the mover is already active.",
|
||||
"66": "Load url '$1' error, since specified a prefixed jsonc parser, but no(or more than one) prefix split string '$2' found in response string.",
|
||||
"67": "Load url '$1' error since the raw json string splitted by prefix split string cannot be parsed to a valid JavaScript object. Parse error info: $2",
|
||||
"68": "Extra config identifier '$1' has conflicts. More than one extra config use this identifier, and the final load result is unpredictable since network decides which one to be loaded fast.",
|
||||
"2001": "Replay($1): Replay parameter count mismatch: expected $2, got $3.",
|
||||
"2002": "Replay($1): Replay parameter type mismatch: index $2, expected $3, got $4.",
|
||||
"2003": "Replay(move): Expected hero to be stopped before execute moving step.",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export * from './progress';
|
||||
export * from './manager';
|
||||
export * from './starter';
|
||||
export * from './stream';
|
||||
export * from './task';
|
||||
|
||||
@ -49,6 +49,11 @@ export class LoadManager
|
||||
}
|
||||
|
||||
async *load(): AsyncIterable<number> {
|
||||
for (const task of this.addedTasks) {
|
||||
if (!task.contentLoaded) {
|
||||
task.start();
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
if (this.loadedTasks.size === this.addedTasks.size) {
|
||||
return;
|
||||
@ -1 +1,2 @@
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
{}
|
||||
|
||||
@ -1,14 +1,22 @@
|
||||
// Type: packages-user/data-state/src/types.ts ICoreStateCoreConfig
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
{
|
||||
"name": "template",
|
||||
"title": "魔塔样板",
|
||||
"version": "Ver 2.C",
|
||||
"content": {
|
||||
"mapsDir": "content/maps",
|
||||
"mapListDir": "content/maps/.list.jsonc",
|
||||
"enemyDir": "content/enemy.jsonc",
|
||||
"itemDir": "content/item.jsonc",
|
||||
"tileDir": "content/tile.jsonc"
|
||||
"mapsDir": "maps",
|
||||
"mapListDir": "maps/.list.jsonc",
|
||||
"enemyDir": "enemy.jsonc",
|
||||
"itemDir": "item.jsonc",
|
||||
"itemEffectDir": "item.js",
|
||||
"tileDir": "tile.jsonc",
|
||||
"defaults": {
|
||||
"enemyDefault": "defaults/enemy.jsonc",
|
||||
"itemDefault": "defaults/item.jsonc",
|
||||
"mapDefault": "defaults/map.jsonc",
|
||||
"tileDefault": "defaults/tile.jsonc"
|
||||
}
|
||||
},
|
||||
"init": {
|
||||
"startEvent": "game-start",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Type: packages-user/data-common/src/store/types IEnemyRawData
|
||||
// "@required" means the property is required.
|
||||
// For objects, system will compare every properties.
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
{
|
||||
"num": "@required",
|
||||
"attribute": {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Type: packages-user/data-common/src/store/types IItemRawData
|
||||
// "@required" means the property is required.
|
||||
// For objects, system will compare every properties.
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
{
|
||||
"num": "@required",
|
||||
"category": 0,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Type: packages-user/data-common/src/store/types IMapRawData
|
||||
// "@required" means the property is required.
|
||||
// For objects, system will compare every properties.
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
{
|
||||
"floorId": "@required",
|
||||
"width": 13,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Type: packages-user/data-common/src/store/types ITileRawData
|
||||
// "@required" means the property is required.
|
||||
// For objects, system will compare every properties.
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
{
|
||||
"num": "@required",
|
||||
"id": "@required",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// Type: packages-user/data-common/src/store/types IEnemyRawData
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
[
|
||||
{
|
||||
"num": 100,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// Type: packages-user/data-common/src/store/types IItemRawData
|
||||
// ItemEffect: ./item.ts
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
[
|
||||
{
|
||||
"num": 200,
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
// Type: string[], without `.jsonc` suffix
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
["MT0"]
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// Type: packages-user/data-common/src/store/types IMapRawData
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
{
|
||||
"floorId": "MT0",
|
||||
"width": 13,
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
// Type: type define required
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
{}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
// Type: packages-user/data-common/src/store/types ITileRawData
|
||||
// --- SYSTEM PREFIX END --- //
|
||||
[
|
||||
{
|
||||
"num": 1,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user