diff --git a/packages-user/client-base/package.json b/packages-user/client-base/package.json new file mode 100644 index 0000000..4dfed65 --- /dev/null +++ b/packages-user/client-base/package.json @@ -0,0 +1,6 @@ +{ + "name": "@user/client-base", + "dependencies": { + "@motajs/render-asset": "workspace:*" + } +} diff --git a/packages-user/client-base/src/index.ts b/packages-user/client-base/src/index.ts new file mode 100644 index 0000000..ddf0bf0 --- /dev/null +++ b/packages-user/client-base/src/index.ts @@ -0,0 +1,7 @@ +import { createMaterial } from './material'; + +export function create() { + createMaterial(); +} + +export * from './material'; diff --git a/packages-user/client-base/src/material/builder.ts b/packages-user/client-base/src/material/builder.ts new file mode 100644 index 0000000..917c349 --- /dev/null +++ b/packages-user/client-base/src/material/builder.ts @@ -0,0 +1,56 @@ +import { + ITextureStore, + ITexture, + ITextureComposedData, + ITextureStreamComposer, + TextureMaxRectsStreamComposer +} from '@motajs/render-assets'; +import { IAssetBuilder } from './types'; +import { logger } from '@motajs/common'; + +export class AssetBuilder implements IAssetBuilder { + readonly composer: ITextureStreamComposer = + new TextureMaxRectsStreamComposer(4096, 4096, 0); + + private output: ITextureStore | null = null; + private started: boolean = false; + + pipe(store: ITextureStore): void { + if (this.started) { + logger.warn(76); + return; + } + this.output = store; + } + + addTexture(texture: ITexture): ITextureComposedData { + this.started = true; + const res = [...this.composer.add([texture])]; + const data = res[0]; + if (this.output) { + if (!this.output.getTexture(data.index)) { + this.output.addTexture(data.index, data.texture); + } + } + return data; + } + + addTextureList( + texture: Iterable + ): Iterable { + this.started = true; + const res = [...this.composer.add(texture)]; + if (this.output) { + res.forEach(v => { + if (!this.output!.getTexture(v.index)) { + this.output!.addTexture(v.index, v.texture); + } + }); + } + return res; + } + + close(): void { + this.composer.close(); + } +} diff --git a/packages-user/client-base/src/material/index.ts b/packages-user/client-base/src/material/index.ts new file mode 100644 index 0000000..756dd11 --- /dev/null +++ b/packages-user/client-base/src/material/index.ts @@ -0,0 +1,3 @@ +export function createMaterial() {} + +export * from './manager'; diff --git a/packages-user/client-base/src/material/manager.ts b/packages-user/client-base/src/material/manager.ts new file mode 100644 index 0000000..a142b4a --- /dev/null +++ b/packages-user/client-base/src/material/manager.ts @@ -0,0 +1,325 @@ +import { + ITexture, + ITextureComposedData, + ITextureRenderable, + ITextureSplitter, + ITextureStore, + SizedCanvasImageSource, + Texture, + TextureColumnAnimater, + TextureGridSplitter, + TextureRowSplitter, + TextureStore +} from '@motajs/render-assets'; +import { + IBlockIdentifier, + IMaterialData, + IMaterialManager, + IIndexedIdentifier, + IMaterialAssetData, + BlockCls, + IBigImageData, + IAssetBuilder +} from './types'; +import { logger } from '@motajs/common'; +import { getClsByString } from './utils'; +import { isNil } from 'lodash-es'; +import { AssetBuilder } from './builder'; + +export class MaterialManager implements IMaterialManager { + readonly tileStore: ITextureStore = new TextureStore(); + readonly tilesetStore: ITextureStore = new TextureStore(); + readonly imageStore: ITextureStore = new TextureStore(); + readonly assetStore: ITextureStore = new TextureStore(); + readonly bigImageStore: ITextureStore = new TextureStore(); + + /** 图集信息存储 */ + readonly assetDataStore: Map = new Map(); + /** 大怪物数据 */ + readonly bigImageData: Map = new Map(); + /** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */ + readonly tilesetOffsetMap: Map = new Map(); + /** 图集打包器 */ + readonly assetBuilder: IAssetBuilder = new AssetBuilder(); + + readonly idNumMap: Map = new Map(); + readonly numIdMap: Map = new Map(); + readonly clsMap: Map = new Map(); + + /** 网格切分器 */ + readonly gridSplitter: TextureGridSplitter = new TextureGridSplitter(); + /** 行切分器 */ + readonly rowSplitter: TextureRowSplitter = new TextureRowSplitter(); + + /** 大怪物贴图的标识符 */ + private bigImageId: number = 0; + + /** + * 添加由分割器和图块映射组成的图像源贴图 + * @param source 图像源 + * @param map 图块 id 与图块数字映射 + * @param store 要添加至的贴图存储对象 + * @param splitter 使用的分割器 + * @param splitterData 传递给分割器的数据 + * @param processTexture 对每个纹理进行处理 + */ + private addMappedSource( + source: SizedCanvasImageSource, + map: ArrayLike, + store: ITextureStore, + splitter: ITextureSplitter, + splitterData: T, + processTexture?: (tex: ITexture) => void + ): Iterable { + const tex = new Texture(source); + const textures = [...splitter.split(tex, splitterData)]; + if (textures.length !== map.length) { + logger.warn(75, textures.length.toString(), map.length.toString()); + } + const res: IMaterialData[] = textures.map((v, i) => { + const { id, num, cls } = map[i]; + store.addTexture(num, v); + store.alias(num, id); + this.clsMap.set(num, getClsByString(cls)); + processTexture?.(v); + const data: IMaterialData = { + store, + texture: v, + identifier: num, + alias: id + }; + return data; + }); + return res; + } + + addGrid( + source: SizedCanvasImageSource, + map: ArrayLike + ): Iterable { + return this.addMappedSource( + source, + map, + this.tileStore, + this.gridSplitter, + [32, 32] + ); + } + + addRowAnimate( + source: SizedCanvasImageSource, + map: ArrayLike, + frames: number + ): Iterable { + return this.addMappedSource( + source, + map, + this.tileStore, + this.rowSplitter, + 32, + (tex: ITexture) => { + tex.animated(new TextureColumnAnimater(), frames); + } + ); + } + + addAutotile( + source: SizedCanvasImageSource, + identifier: IBlockIdentifier + ): IMaterialData { + const texture = new Texture(source); + this.tileStore.addTexture(identifier.num, texture); + this.tileStore.alias(identifier.num, identifier.id); + this.clsMap.set(identifier.num, BlockCls.Autotile); + const data: IMaterialData = { + store: this.tileStore, + texture, + identifier: identifier.num, + alias: identifier.id + }; + return data; + } + + addTileset( + source: SizedCanvasImageSource, + identifier: IIndexedIdentifier + ): IMaterialData { + const tex = new Texture(source); + this.tilesetStore.addTexture(identifier.index, tex); + this.tilesetStore.alias(identifier.index, identifier.alias); + const data: IMaterialData = { + store: this.tilesetStore, + texture: tex, + identifier: identifier.index, + alias: identifier.alias + }; + return data; + } + + addImage( + source: SizedCanvasImageSource, + identifier: IIndexedIdentifier + ): IMaterialData { + const texture = new Texture(source); + this.imageStore.addTexture(identifier.index, texture); + this.imageStore.alias(identifier.index, identifier.alias); + const data: IMaterialData = { + store: this.imageStore, + texture, + identifier: identifier.index, + alias: identifier.alias + }; + return data; + } + + getTile(identifier: number): ITexture | null { + return this.tileStore.getTexture(identifier); + } + + getTileset(identifier: number): ITexture | null { + return this.tilesetStore.getTexture(identifier); + } + + getImage(identifier: number): ITexture | null { + return this.imageStore.getTexture(identifier); + } + + getTileByAlias(alias: string): ITexture | null { + return this.tileStore.fromAlias(alias); + } + + getTilesetByAlias(alias: string): ITexture | null { + return this.tilesetStore.fromAlias(alias); + } + + getImageByAlias(alias: string): ITexture | null { + return this.imageStore.fromAlias(alias); + } + + buildAssets(): Iterable { + this.assetBuilder.pipe(this.assetStore); + const data = this.assetBuilder.addTextureList(this.tileStore.values()); + const arr = [...data]; + const res: IMaterialAssetData[] = []; + arr.forEach((v, i) => { + const alias = `asset-${i}`; + this.assetStore.alias(i, alias); + const data: IMaterialAssetData = { + data: v, + identifier: i, + alias, + store: this.assetStore + }; + for (const tex of v.assetMap.keys()) { + tex.toAsset(v); + } + res.push(data); + }); + return res; + } + + getAsset(identifier: number): ITextureComposedData | null { + return this.assetDataStore.get(identifier) ?? null; + } + + getAssetByAlias(alias: string): ITextureComposedData | null { + const id = this.assetStore.identifierOf(alias); + if (isNil(id)) return null; + return this.assetDataStore.get(id) ?? null; + } + + private getTextureOf(identifier: number, cls: BlockCls): ITexture | null { + if (cls === BlockCls.Unknown) return null; + if (cls !== BlockCls.Tileset) { + return this.tileStore.getTexture(identifier); + } + if (identifier < 10000) return null; + const texture = this.tileStore.getTexture(identifier); + if (texture) return texture; + // 如果 tileset 不存在,那么执行缓存操作 + const offset = Math.floor(identifier / 10000); + const index = this.tilesetOffsetMap.get(offset); + if (isNil(index)) return null; + // 获取对应的 tileset 贴图 + const tileset = this.tilesetStore.getTexture(index); + if (!tileset) return null; + // 计算图块位置 + const rest = identifier - offset * 10000; + const { width, height } = tileset; + const tileWidth = Math.floor(width / 32); + const tileHeight = Math.floor(height / 32); + // 如果图块位置超出了贴图范围 + if (rest > tileWidth * tileHeight) return null; + // 裁剪 tileset,生成贴图 + const x = rest % tileWidth; + const y = Math.floor(rest / tileWidth); + const newTexture = new Texture(tileset.source); + newTexture.clip(x * 32, y * 32, 32, 32); + // 缓存贴图 + this.tileStore.addTexture(identifier, newTexture); + this.idNumMap.set(`X${identifier}`, identifier); + this.numIdMap.set(identifier, `X${identifier}`); + const data = this.assetBuilder.addTexture(newTexture); + newTexture.toAsset(data); + return newTexture; + } + + getRenderable(identifier: number): ITextureRenderable | null { + const cls = this.clsMap.get(identifier); + if (isNil(cls)) return null; + const texture = this.getTextureOf(identifier, cls); + if (!texture) return null; + return texture.static(); + } + + getRenderableByAlias(alias: string): ITextureRenderable | null { + const identifier = this.idNumMap.get(alias); + if (isNil(identifier)) return null; + return this.getRenderable(identifier); + } + + getBlockCls(identifier: number): BlockCls { + return this.clsMap.get(identifier) ?? BlockCls.Unknown; + } + + getBlockClsByAlias(alias: string): BlockCls { + const id = this.idNumMap.get(alias); + if (isNil(id)) return BlockCls.Unknown; + return this.clsMap.get(id) ?? BlockCls.Unknown; + } + + getIdentifierByAlias(alias: string): number | undefined { + return this.idNumMap.get(alias); + } + + getAliasByIdentifier(identifier: number): string | undefined { + return this.numIdMap.get(identifier); + } + + setBigImage(identifier: number, image: ITexture): IBigImageData { + const bigImageId = this.bigImageId++; + this.bigImageStore.addTexture(bigImageId, image); + this.bigImageData.set(identifier, image); + const data: IBigImageData = { + identifier: bigImageId, + store: this.bigImageStore + }; + return data; + } + + isBigImage(identifier: number): boolean { + return this.bigImageData.has(identifier); + } + + getBigImage(identifier: number): ITexture | null { + return this.bigImageData.get(identifier) ?? null; + } + + getBigImageByAlias(alias: string): ITexture | null { + const identifier = this.idNumMap.get(alias); + if (isNil(identifier)) return null; + return this.bigImageData.get(identifier) ?? null; + } +} + +export const materials = new MaterialManager(); diff --git a/packages-user/client-base/src/material/types.ts b/packages-user/client-base/src/material/types.ts new file mode 100644 index 0000000..6af5b18 --- /dev/null +++ b/packages-user/client-base/src/material/types.ts @@ -0,0 +1,330 @@ +import { + IRect, + ITexture, + ITextureComposedData, + ITextureRenderable, + ITextureStore, + SizedCanvasImageSource +} from '@motajs/render-assets'; + +export const enum BlockCls { + Unknown, + Terrains, + Animates, + Enemys, + Npcs, + Items, + Enemy48, + Npc48, + Tileset, + Autotile +} + +export interface IMaterialData { + /** 此素材的贴图对象存入了哪个贴图存储对象 */ + readonly store: ITextureStore; + /** 贴图对象 */ + readonly texture: ITexture; + /** 此素材的贴图对象的数字 id,一般对应到图块数字 */ + readonly identifier: number; + /** 此素材的贴图对象的字符串别名,一般对应到图块 id */ + readonly alias?: string; +} + +export interface IBlockIdentifier { + /** 图块 id */ + readonly id: string; + /** 图块数字 */ + readonly num: number; + /** 图块类型 */ + readonly cls: Cls; +} + +export interface IIndexedIdentifier { + /** 标识符索引 */ + readonly index: number; + /** 标识符别名 */ + readonly alias: string; +} + +export interface IMaterialAssetData { + /** 图集数据 */ + readonly data: ITextureComposedData; + /** 贴图的标识符 */ + readonly identifier: number; + /** 贴图的别名 */ + readonly alias: string; + /** 贴图所属的存储对象 */ + readonly store: ITextureStore; +} + +export interface IAutotileConnection { + /** 连接方式,上方连接是第一位,顺时针旋转位次依次升高 */ + readonly connection: number; + /** 中心自动元件对应的图块数字 */ + readonly center: number; +} + +export interface IAutotileRenderable { + /** 自动元件的图像源 */ + readonly source: SizedCanvasImageSource; + /** 渲染的矩形范围 */ + readonly rects: Readonly[]; +} + +export interface IBigImageData { + /** 大怪物贴图在 store 中的标识符 */ + readonly identifier: number; + /** 存储大怪物贴图的存储对象 */ + readonly store: ITextureStore; +} + +export interface IAutotileProcessor { + /** 该自动元件处理器使用的素材管理器 */ + readonly manager: IMaterialManager; + + /** + * 设置一个自动元件的父元件,一个自动元件可以有多个父元件 + * @param autotile 自动元件 + * @param parent 自动元件的父元件 + */ + setParent(autotile: number, parent: number): void; + + /** + * 获取自动元件的链接情况 + * @param array 地图图块数组 + * @param index 自动元件图块所在的索引 + * @param edge 当前图块的边缘连接情况 + */ + connect( + array: Float32Array, + index: number, + edge: number + ): IAutotileConnection; + + /** + * 获取指定自动元件的可渲染对象 + * @param autotile 自动元件的图块数字 + * @param connection 连接方式,上方连接是第一位,顺时针旋转位次依次升高 + */ + render(autotile: number, connection: number): IAutotileRenderable; + + /** + * 通过可渲染对象输出自动元件经过连接的可渲染对象 + * @param renderable 自动元件的原始可渲染对象 + * @param connection 自动元件的链接方式 + */ + fromRenderable( + renderable: ITextureRenderable, + connection: number + ): IAutotileRenderable; +} + +export interface IMaterialManager { + /** 贴图存储,把 terrains 等内容单独分开存储 */ + readonly tileStore: ITextureStore; + /** tilesets 贴图存储,每个 tileset 是一个贴图对象 */ + readonly tilesetStore: ITextureStore; + /** 存储注册的图像的存储对象 */ + readonly imageStore: ITextureStore; + /** 图集存储,将常用贴图存入其中 */ + readonly assetStore: ITextureStore; + /** bigImage 存储,存储大怪物数据 */ + readonly bigImageStore: ITextureStore; + + /** 图块类型映射 */ + readonly clsMap: Map; + + /** + * 添加网格类型的贴图,包括 terrains 和 items 类型 + * @param source 图像源 + * @param map 贴图字符串 id 与图块数字映射,按照先从左到右,再从上到下的顺序映射 + */ + addGrid( + source: SizedCanvasImageSource, + map: ArrayLike + ): Iterable; + + /** + * 添加行动画的贴图,包括 animates enemys npcs enemy48 npc48 类型 + * @param source 图像源 + * @param map 贴图字符串 id 与图块数字映射,按从上到下的顺序映射 + * @param frames 每一行的帧数 + */ + addRowAnimate( + source: SizedCanvasImageSource, + map: ArrayLike, + frames: number + ): Iterable; + + /** + * 添加自动元件 + * @param source 图像源 + * @param identifier 自动元件的字符串 id 及图块数字 + */ + addAutotile( + source: SizedCanvasImageSource, + identifier: IBlockIdentifier + ): IMaterialData; + + /** + * 添加一个 tileset 类型的素材 + * @param source 图像源 + * @param alias tileset 的标识符,包含其在 tilesets 列表中的索引和图片名称 + */ + addTileset( + source: SizedCanvasImageSource, + identifier: IIndexedIdentifier + ): IMaterialData; + + /** + * 添加一个图片 + * @param source 图像源 + * @param identifier 图片的标识符,包含其在 images 列表中的索引和图片名称 + */ + addImage( + source: SizedCanvasImageSource, + identifier: IIndexedIdentifier + ): IMaterialData; + + /** + * 根据图块数字获取图块,可以获取额外素材,会自动将未缓存的额外素材缓存 + * @param identifier 图块的图块数字 + */ + getTile(identifier: number): ITexture | null; + + /** + * 根据额外素材索引获取额外素材 + * @param identifier 额外素材的索引 + */ + getTileset(identifier: number): ITexture | null; + + /** + * 根据图片的索引获取图片 + * @param identifier 图片的索引 + */ + getImage(identifier: number): ITexture | null; + + /** + * 根据图块 id 获取图块,可以获取额外素材,会自动将未缓存的额外素材缓存 + * @param alias 图块 id + */ + getTileByAlias(alias: string): ITexture | null; + + /** + * 根据额外素材名称获取额外素材 + * @param alias 额外素材名称 + */ + getTilesetByAlias(alias: string): ITexture | null; + + /** + * 根据图片名称获取图片 + * @param alias 图片名称 + */ + getImageByAlias(alias: string): ITexture | null; + + /** + * 把常用素材打包成为图集形式供后续使用 + */ + buildAssets(): Iterable; + + /** + * 根据标识符获取图集信息 + * @param identifier 图集的标识符 + */ + getAsset(identifier: number): ITextureComposedData | null; + + /** + * 根据别名获取图集信息 + * @param alias 图集的别名 + */ + getAssetByAlias(alias: string): ITextureComposedData | null; + + /** + * 根据图块标识符在图集中获取对应的可渲染对象 + * @param identifier 图块标识符,即图块数字 + */ + getRenderable(identifier: number): ITextureRenderable | null; + + /** + * 根据图块别名在图集中获取对应的可渲染对象 + * @param alias 图块的别名,即图块的 id + */ + getRenderableByAlias(alias: string): ITextureRenderable | null; + + /** + * 根据图块标识符获取图块类型 + * @param identifier 图块标识符,即图块数字 + */ + getBlockCls(identifier: number): BlockCls; + + /** + * 根据图块别名获取图块类型 + * @param alias 图块别名,即图块的 id + */ + getBlockClsByAlias(alias: string): BlockCls; + + /** + * 根据图块别名获取图块标识符,即图块数字 + * @param alias 图块别名,即图块的 id + */ + getIdentifierByAlias(alias: string): number | undefined; + + /** + * 根据图块标识符获取图块别名,即图块的 id + * @param identifier 图块标识符,即图块数字 + */ + getAliasByIdentifier(identifier: number): string | undefined; + + /** + * 设置一个图块的 `bigImage` 贴图,即大怪物贴图,但不止怪物能用 + * @param identifier 图块标识符,即图块数字 + * @param image `bigImage` 对应的贴图对象 + */ + setBigImage(identifier: number, image: ITexture): IBigImageData; + + /** + * 判断一个图块是否包含 `bigImage` 贴图,即是否是大怪物 + * @param identifier 图块标识符,即图块数字 + */ + isBigImage(identifier: number): boolean; + + /** + * 根据图块标识符获取一个图块的 `bigImage` 贴图 + * @param identifier 图块标识符,即图块数字 + */ + getBigImage(identifier: number): ITexture | null; + + /** + * 根据图块别名获取一个图块的 `bigImage` 贴图 + * @param alias 图块别名,即图块的 id + */ + getBigImageByAlias(alias: string): ITexture | null; +} + +export interface IAssetBuilder { + /** + * 将图集打包器输出至指定贴图存储对象,只能输出到一个存储对象中,设置多个仅最后一个有效 + * @param store 贴图存储对象 + */ + pipe(store: ITextureStore): void; + + /** + * 添加贴图对象至打包器 + * @param texture 贴图对象 + * @returns 当前打包的贴图对象对应的组合数据 + */ + addTexture(texture: ITexture): ITextureComposedData; + + /** + * 添加一个贴图对象列表至打包器 + * @param texture 贴图对象列表 + * @returns 当前打包的贴图列表的组合数据,每一项代表一个图集,只包含使用到的图集,之前已经打包完成的将不会在列表内 + */ + addTextureList(texture: Iterable): Iterable; + + /** + * 结束此打包器 + */ + close(): void; +} diff --git a/packages-user/client-base/src/material/utils.ts b/packages-user/client-base/src/material/utils.ts new file mode 100644 index 0000000..b6bf0cc --- /dev/null +++ b/packages-user/client-base/src/material/utils.ts @@ -0,0 +1,26 @@ +import { BlockCls } from './types'; + +export function getClsByString(cls: Cls): BlockCls { + switch (cls) { + case 'terrains': + return BlockCls.Terrains; + case 'animates': + return BlockCls.Animates; + case 'autotile': + return BlockCls.Autotile; + case 'enemys': + return BlockCls.Enemys; + case 'items': + return BlockCls.Items; + case 'npcs': + return BlockCls.Npcs; + case 'npc48': + return BlockCls.Npc48; + case 'enemy48': + return BlockCls.Enemy48; + case 'tileset': + return BlockCls.Tileset; + default: + return BlockCls.Unknown; + } +} diff --git a/packages-user/entry-client/package.json b/packages-user/entry-client/package.json index cbb54fd..c5076f1 100644 --- a/packages-user/entry-client/package.json +++ b/packages-user/entry-client/package.json @@ -5,6 +5,7 @@ "@motajs/client-base": "workspace:*", "@motajs/common": "workspace:*", "@motajs/render": "workspace:*", + "@motajs/render-assets": "workspace:*", "@motajs/render-core": "workspace:*", "@motajs/render-elements": "workspace:*", "@motajs/render-style": "workspace:*", @@ -17,7 +18,8 @@ "@motajs/legacy-data": "workspace:*", "@motajs/legacy-ui": "workspace:*", "@motajs/legacy-system": "workspace:*", + "@user/client-base": "workspace:*", "@user/client-modules": "workspace:*", "@user/legacy-plugin-client": "workspace:*" } -} \ No newline at end of file +} diff --git a/packages-user/entry-client/src/create.ts b/packages-user/entry-client/src/create.ts index 19dc306..10c9d6d 100644 --- a/packages-user/entry-client/src/create.ts +++ b/packages-user/entry-client/src/create.ts @@ -5,6 +5,7 @@ import * as LegacyClient from '@motajs/legacy-client'; import * as LegacySystem from '@motajs/legacy-system'; import * as LegacyUI from '@motajs/legacy-ui'; import * as Render from '@motajs/render'; +import * as RenderAssets from '@motajs/render-assets'; import * as RenderCore from '@motajs/render-core'; import * as RenderElements from '@motajs/render-elements'; import * as RenderStyle from '@motajs/render-style'; @@ -12,6 +13,7 @@ import * as RenderVue from '@motajs/render-vue'; import * as System from '@motajs/system'; import * as SystemAction from '@motajs/system-action'; import * as SystemUI from '@motajs/system-ui'; +import * as UserClientBase from '@user/client-base'; import * as ClientModules from '@user/client-modules'; import * as LegacyPluginClient from '@user/legacy-plugin-client'; import * as MutateAnimate from 'mutate-animate'; @@ -28,6 +30,7 @@ export function create() { Mota.register('@motajs/legacy-system', LegacySystem); Mota.register('@motajs/legacy-ui', LegacyUI); Mota.register('@motajs/render', Render); + Mota.register('@motajs/render-assets', RenderAssets); Mota.register('@motajs/render-core', RenderCore); Mota.register('@motajs/render-elements', RenderElements); Mota.register('@motajs/render-style', RenderStyle); @@ -35,6 +38,7 @@ export function create() { Mota.register('@motajs/system', System); Mota.register('@motajs/system-action', SystemAction); Mota.register('@motajs/system-ui', SystemUI); + Mota.register('@user/client-base', UserClientBase); Mota.register('@user/client-modules', ClientModules); Mota.register('@user/legacy-plugin-client', LegacyPluginClient); Mota.register('MutateAnimate', MutateAnimate); @@ -47,6 +51,7 @@ export function create() { async function createModule() { LegacyUI.create(); ClientModules.create(); + UserClientBase.create(); await import('ant-design-vue/dist/antd.dark.css'); main.renderLoaded = true;