refactor: 数据端用户结构

This commit is contained in:
unanmed 2026-10-16 17:29:51 +08:00
parent 0a133058ca
commit a0e402728e
41 changed files with 901 additions and 100 deletions

View File

@ -1,14 +1,15 @@
# Phase 10: 文档重构 - Context
**Gathered:** 2026-10-14
**Status:** Discussion only — 待引擎整体完工后再规划/执行
**Updated:** 2026-10-14(改为增量派发)
**Status:** Incremental dispatch — 可先行部分先规划执行,其余待引擎完工
<domain>
## Phase Boundary
重构过老、结构不佳、新人理解难度过高的项目文档。面向多背景读者(完全新手 / 不懂代码的作者 / 有基础的作者 / AI Agent)提供分层指南,并专门为 AI Agent 编写参考文档。
**执行时机(本阶段关键):** 本阶段当前**只记录讨论共识,不规划、不执行**。引擎仍在收尾、部分内容(编辑器、部分重构)尚未完工,文档尚不具备编写条件;待引擎整体完工后再进入 Plan → Execute → Verify。
**执行方式(本阶段关键):** 引擎大部分接口已定稿、短期内不会大改,但编辑器、部分重构与整体收尾尚未完工,故本阶段采用**伞阶段 + 增量派发**:用户每次派发一个具体小任务,AI 据此在 Phase 10 下创建独立计划并执行(Plan → Execute → Verify),做完再由用户派发下一个。**已定稿、可先行的部分先做**;未完工部分(初级文档、高级教程、未定稿接口等)挂起,待条件成熟后由用户再派发。
文档分为六大部分:
@ -47,14 +48,21 @@
### 范围界定
- **D-05:** API 参考**不含 legacy 包**(`@motajs/legacy-*`、`@user/legacy-*`、`@user/data-fallback`)——这部分尚未重构完。
- **D-06:** **初级文档**本期不规划,待**编辑器实装后**再规划;具体范围后续另行说明。
- **D-07:** **高级教程**本期不规划,待**引擎整体完工后**再规划。
- **D-06:** **初级文档**暂缓——待**编辑器实装后**由用户再派发;具体范围届时另行说明。
- **D-07:** **高级教程**暂缓——待**引擎整体完工后**由用户再派发。
- **D-08:** **Agent 参考**放 `docs/agent`,具体内容后续再讨论。
### 验收与流程
- **D-09:** 验收标准 = **用户人工审阅** + **链接全部有效** + **构建通过**。
- **D-10:** 本阶段当前**仅讨论**;待引擎整体完工后再进入 GSD 的 Plan → Execute → Verify。
- **D-10:** 本阶段采用**增量派发**:用户每次派发一个具体小任务,AI 只针对该任务在 Phase 10 下创建独立计划;不得预先把整个阶段规划到底。可先行部分先做,未完工部分挂起待派发。
### 执行方式(本阶段特有,优先级最高)
- **D-11:** 阶段计划随任务累积,可反复追加/重规划;新任务到来时可新增计划而不必重排既有计划。
- **D-12:** 每个计划执行前,第一个 block 点必须**先向用户汇报计划内容**(要做什么、为什么、大致怎么做、影响范围)并等待用户明确回复「可以执行」;未收到「可以执行」前不得开始该计划的任何一步,也不得派发执行子代理。
- **D-13:** 新增或修改任何公共、受保护、私有方法/字段等命名,以及文档目录/文件名,均**必须先向用户反馈并获批准**后方可使用。
- **D-14:** 每个任务的范围由用户界定;AI 不擅自扩大范围。发现的越界内容只记录、不实施。
</decisions>

View File

@ -1,9 +0,0 @@
import { UIController } from '@motajs/system';
import { IGameUIPropsBase } from '@user/client-base';
import { MainBackgroundUI } from './background';
export const mainUIController = new UIController<IGameUIPropsBase>();
export function createMainController() {
mainUIController.setBackground(MainBackgroundUI, {});
}

View File

@ -78,7 +78,7 @@ export interface IFlagSystem extends ISaveableContent<IFlagSystemSave> {
* @param field 字段名称
* @param defaultValue 字段默认值函数
*/
getOrInsertComputed<K extends PropertyKey, T>(
getOrInsertComputed<T, K extends PropertyKey = PropertyKey>(
field: K,
defaultValue: (field: K) => T
): IFlagCommonField<T>;

View File

@ -1,7 +1,8 @@
import { ITileLocator } from '@motajs/common';
import { IFaceManager, IRoleFaceBinder } from './common';
import { IGameEventStore, IItemStore, IMapStore, ITileStore } from './store';
import { IReplaySystem } from './replay';
import { IEnemyView } from '@user/data-system';
import { IHookable, IHookBase } from '@motajs/common';
export interface IEnemyAttr {
/** 怪物名称 */
@ -19,7 +20,7 @@ export interface IEnemyAttr {
/** 怪物加点量 */
point: number;
/** 支援来源怪物坐标索引列表 */
guard: Set<ITileLocator>;
guard: Set<IEnemyView<IEnemyAttr>>;
}
export interface IHeroAttr {
@ -47,7 +48,16 @@ export interface IHeroAttr {
exp: number;
}
export interface IDataCommon {
export interface IDataCommonHooks extends IHookBase {
/**
* 进行一次提示的渲染
* @param tip 提示内容
* @param icon 提示图标
*/
onTip?(tip: string, icon: number): void;
}
export interface IDataCommon extends IHookable<IDataCommonHooks> {
/** 图块定义存储 */
readonly tileStore: ITileStore;
/** 道具定义存储 */
@ -62,6 +72,13 @@ export interface IDataCommon {
readonly faceManager: IFaceManager;
/** 录像系统 */
readonly replaySystem: IReplaySystem;
/**
* 通知渲染端进行一次提示渲染
* @param tip 提示文本
* @param icon 提示图标
*/
emitTip(tip: string, icon: number): void;
}
export interface IDataCommonExtended {

View File

@ -1,4 +1,9 @@
import { logger } from '@motajs/common';
import {
Hookable,
HookController,
IHookController,
logger
} from '@motajs/common';
import { ILoadManager, LoadManager } from '@motajs/loader';
import {
IRoleFaceBinder,
@ -18,7 +23,8 @@ import {
IMapStore,
MapStore,
IReplaySystem,
ReplaySystem
ReplaySystem,
IDataCommonHooks
} from '@user/data-common';
import {
EnemyManager,
@ -31,8 +37,10 @@ import {
MapState
} from '@user/data-base';
import {
CombatFlow,
EnemyContext,
GameEventSystem,
ICombatFlow,
IEnemyContext,
IGameEventSystem,
IPathfindingSystem,
@ -49,7 +57,10 @@ import {
import { IPluginOrderResult, orderPlugins } from './plugin';
import { IMotaDataLoader, MotaDataLoader } from './loader';
export class CoreState implements ICoreState {
export class CoreState
extends Hookable<IDataCommonHooks>
implements ICoreState
{
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
readonly roleFace: IRoleFaceBinder;
readonly faceManager: IFaceManager;
@ -67,6 +78,7 @@ export class CoreState implements ICoreState {
// Layer 2 执行层,游戏逻辑对象都在这,包括一些需要操作数据层的逻辑系统等
readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
readonly combat: ICombatFlow<IEnemyAttr, IHeroAttr>;
readonly eventSystem: IGameEventSystem;
readonly pathfinding: IPathfindingSystem;
@ -92,6 +104,7 @@ export class CoreState implements ICoreState {
>;
constructor(config: Readonly<ICoreStateConfig>) {
super();
// L0 定义
this.roleFace = new RoleFaceBinder();
this.faceManager = new FaceManager();
@ -99,6 +112,7 @@ export class CoreState implements ICoreState {
this.itemStore = new ItemStore<IHeroAttr>(this.tileStore);
this.mapStore = new MapStore();
this.eventStore = new GameEventStore();
this.replaySystem = new ReplaySystem();
// L1 定义
this.flags = new FlagSystem();
@ -108,8 +122,8 @@ export class CoreState implements ICoreState {
// L2 定义
this.enemyContext = new EnemyContext<IEnemyAttr, IHeroAttr>(this);
this.combat = new CombatFlow(this);
this.eventSystem = new GameEventSystem(this, config.events);
this.replaySystem = new ReplaySystem();
this.pathfinding = new PathfindingSystem(this);
// L3 定义
@ -133,6 +147,16 @@ export class CoreState implements ICoreState {
this.awake();
}
protected createController(
hook: Partial<IDataCommonHooks>
): IHookController<IDataCommonHooks> {
return new HookController(this, hook);
}
emitTip(tip: string, icon: number): void {
this.forEachHook(hook => hook.onTip?.(tip, icon));
}
//#region 插件方法
/**

View File

@ -84,10 +84,12 @@ export class CombatFlow<TEnemy, THero>
*/
private createHandler(
enemy: IReadonlyEnemy<TEnemy>,
locator: ITileLocator | null
locator: ITileLocator | null,
force: boolean
): ICombatFlowHandler<TEnemy, THero> {
return {
onMap: locator !== null,
force,
hero: this.hero!,
enemy,
context: this.context!,
@ -205,7 +207,8 @@ export class CombatFlow<TEnemy, THero>
@shouldReplay('Battle with enemy should be replayed.')
battle(
enemy: IEnemyView<TEnemy>
enemy: IEnemyView<TEnemy>,
force: boolean = false
): Promise<IEnemyDamageInfo<TEnemy, THero> | null> {
if (!this.context) {
logger.warn(139, 'an enemy context object');
@ -219,7 +222,7 @@ export class CombatFlow<TEnemy, THero>
const computed = enemy.getComputedEnemy();
const origin = enemy.getModifiableEnemy();
const locator = this.tryGetEnemyLocator(enemy, computed, origin);
const handler = this.createHandler(origin, locator);
const handler = this.createHandler(origin, locator, force);
const eHandler = this.createEnemyHandler(computed, locator);
return this.combatFlow(handler, eHandler);
@ -227,7 +230,8 @@ export class CombatFlow<TEnemy, THero>
@shouldReplay('Battle with enemy should be replayed.')
battleComputed(
enemy: IReadonlyEnemy<TEnemy>
enemy: IReadonlyEnemy<TEnemy>,
force: boolean = false
): Promise<IEnemyDamageInfo<TEnemy, THero> | null> {
if (!this.context) {
logger.warn(139, 'an enemy context object');
@ -243,7 +247,7 @@ export class CombatFlow<TEnemy, THero>
if (view) return this.battle(view);
else {
// 否则走单独的流程
const handler = this.createHandler(enemy, null);
const handler = this.createHandler(enemy, null, force);
const eHandler = this.createEnemyHandler(enemy, null);
return this.combatFlow(handler, eHandler);
}

View File

@ -102,9 +102,14 @@ export class EnemyContext<TEnemy, THero> implements IEnemyContext<
built: boolean = false;
width: number = 0;
height: number = 0;
floor: string | undefined;
constructor(readonly state: IStateBase) {}
setFloor(floor: string | undefined): void {
this.floor = floor;
}
resize(width: number, height: number): void {
this.clear();
this.width = width;

View File

@ -1,3 +1,4 @@
export * from './combat';
export * from './context';
export * from './damage';
export * from './enemy';

View File

@ -537,6 +537,8 @@ export interface IReadonlyEnemyContext<
readonly width: number;
/** 怪物上下文高度 */
readonly height: number;
/** 怪物上下文对应的楼层 */
readonly floor: string | undefined;
/** 此上下文使用的索引对象 */
readonly indexer: ILocationHelper;
@ -612,9 +614,16 @@ export interface IEnemyContext<TEnemy, THero> extends IReadonlyEnemyContext<
readonly width: number;
/** 怪物上下文高度 */
readonly height: number;
/** 此上下文使用的索引对象 */
readonly indexer: ILocationHelper;
/**
* 设置怪物上下文对应的楼层
* @param floor 上下文对应的楼层
*/
setFloor(floor: string | undefined): void;
/**
* 调整上下文尺寸,并清空当前上下文中的所有怪物与状态
* @param width 地图宽度
@ -775,6 +784,8 @@ export interface IEnemyContext<TEnemy, THero> extends IReadonlyEnemyContext<
export interface ICombatFlowHandler<TEnemy, THero> extends IDataBaseExtended {
/** 战斗的怪物是否在地图上 */
readonly onMap: boolean;
/** 是否为强制战斗 */
readonly force: boolean;
/** 可修改勇士对象 */
readonly hero: IHeroAttribute<THero>;
/** 可修改怪物对象 */
@ -863,9 +874,11 @@ export interface ICombatFlow<TEnemy, THero>
/**
* 与指定怪物战斗
* @param enemy 怪物视图对象
* @param force 是否为强制战斗,默认为 `false`。强制战斗下,不再有战败保护
*/
battle(
enemy: IEnemyView<TEnemy>
enemy: IEnemyView<TEnemy>,
force?: boolean
): Promise<IEnemyDamageInfo<TEnemy, THero> | null>;
/**
@ -879,8 +892,12 @@ export interface ICombatFlow<TEnemy, THero>
/**
* 添加战前战后脚本
* @param script 战前战后脚本
* @param force 是否为强制战斗,默认为 `false`。强制战斗下,不再有战败保护
*/
addCombatScript(script: ICombatScript<TEnemy, THero>): void;
addCombatScript(
script: ICombatScript<TEnemy, THero>,
force?: boolean
): void;
}
//#endregion

View File

@ -1,4 +1,10 @@
import { logger } from '@motajs/common';
import {
Hookable,
HookController,
IHookBase,
IHookController,
logger
} from '@motajs/common';
import { IGameEventStore, shouldReplay } from '@user/data-common';
import { AnonTokyoInterpreter } from '@motajs/anon-tokyo';
import {
@ -7,17 +13,29 @@ import {
IGameEventExecutor,
IBlockEventEnv,
IBlockEventParam,
IGameEventInvocation
IGameEventInvocation,
IGameEventExecutorHooks
} from './types';
export class EventExecutor implements IGameEventExecutor {
export class EventExecutor
extends Hookable<IGameEventExecutorHooks>
implements IGameEventExecutor
{
mode: EventExecuteMode = EventExecuteMode.Normal;
reduce: EventReduceMode = EventReduceMode.NoReduce;
constructor(
readonly interpreter: AnonTokyoInterpreter,
private readonly storeRef: () => IGameEventStore | null
) {}
) {
super();
}
protected createController(
hook: Partial<IHookBase>
): IHookController<IHookBase> {
return new HookController(this, hook);
}
setMode(mode: EventExecuteMode): void {
this.mode = mode;
@ -57,6 +75,7 @@ export class EventExecutor implements IGameEventExecutor {
events: IGameEventInvocation[],
param: IBlockEventParam
): Promise<R> {
this.forEachHook(hook => hook.onBeforeExecuteEvent?.(events, param));
// 事件执行
const results: any[] = [];
for (const invocation of events) {
@ -86,6 +105,10 @@ export class EventExecutor implements IGameEventExecutor {
}
}
return this.reduceResults(this.reduce, results);
const reduced = this.reduceResults(this.reduce, results);
this.forEachHook(hook =>
hook.onAfterExecuteEvent?.(events, param, reduced)
);
return reduced;
}
}

View File

@ -1,4 +1,4 @@
import { ITileLocator } from '@motajs/common';
import { IHookable, IHookBase, ITileLocator } from '@motajs/common';
import {
IDataBaseExtended,
IGameMap,
@ -105,7 +105,31 @@ export interface IGameEventInit {
addBuiltinFunction(name: string, func: GameEventBuiltinFunction): void;
}
export interface IGameEventExecutor {
export interface IGameEventExecutorHooks extends IHookBase {
/**
* 当事件执行前触发
* @param events 要执行的事件列表,可以直接修改此列表
* @param param 要传递给事件的参数,可以直接修改此参数
*/
onBeforeExecuteEvent(
events: IGameEventInvocation[],
param: IBlockEventParam
): void;
/**
* 当一个事件执行完毕后触发
* @param events 执行的事件列表
* @param param 事件的执行参数
* @param returns 事件的执行返回值
*/
onAfterExecuteEvent(
events: IGameEventInvocation[],
param: IBlockEventParam,
returns: unknown
): void;
}
export interface IGameEventExecutor extends IHookable<IGameEventExecutorHooks> {
/** 当前的执行器执行模式 */
readonly mode: EventExecuteMode;
/** 当前的执行器返回值折叠方式 */

View File

@ -1,4 +1,10 @@
import { ITileLocator, logger } from '@motajs/common';
import {
Hookable,
HookController,
IHookController,
ITileLocator,
logger
} from '@motajs/common';
import { IObjectMovable, IObjectMover, shouldReplay } from '@user/data-common';
import { IStateBase } from '@user/data-base';
import { PathFinder } from './finder';
@ -7,25 +13,36 @@ import {
IPathfindingController,
IPathfindingResult,
IPathfindingSystem,
IPathfindingSystemHooks,
PathFallbackPolicy,
PathfindingStatus
} from './types';
export class PathfindingSystem implements IPathfindingSystem {
export class PathfindingSystem
extends Hookable<IPathfindingSystemHooks>
implements IPathfindingSystem
{
/** 寻路求解器 */
readonly finder: IPathFinder;
/** 当前绑定的移动器 */
private mover: IObjectMover<IObjectMovable> | null = null;
mover: IObjectMover<IObjectMovable> | null = null;
/** 注入的瞬移回退策略,未注入时必定回退为逐步寻路 */
private policy: PathFallbackPolicy | null = null;
/** 最近一次寻路移动的控制器包装 */
private current: IPathfindingController | null = null;
constructor(readonly state: IStateBase) {
super();
this.finder = new PathFinder(state);
}
protected createController(
hook: Partial<IPathfindingSystemHooks>
): IHookController<IPathfindingSystemHooks> {
return new HookController(this, hook);
}
useMover(mover: IObjectMover<IObjectMovable> | null): void {
this.mover = mover;
}
@ -66,6 +83,7 @@ export class PathfindingSystem implements IPathfindingSystem {
if (teleport) {
const last = array.at(-1)!;
mover.tp(last.to.x, last.to.y);
this.forEachHook(hook => hook.onBeforeTeleportTo?.(path, this));
} else {
for (const step of array) {
mover.step(step.dir);
@ -76,6 +94,11 @@ export class PathfindingSystem implements IPathfindingSystem {
if (!controller) return null;
const result: IPathfindingController = { controller, path };
this.current = result;
controller.onEnd.then(() => {
this.forEachHook(hook => hook.onAfterTeleportTo?.(path, this));
});
return result;
}

View File

@ -1,4 +1,9 @@
import { ITileLocator, IFaceHandler } from '@motajs/common';
import {
ITileLocator,
IFaceHandler,
IHookBase,
IHookable
} from '@motajs/common';
import {
IDataBaseExtended,
ILayerLocation,
@ -156,9 +161,32 @@ export interface IPathFinder extends IDataBaseExtended {
*/
export type PathFallbackPolicy = (path: readonly IPathfindingStep[]) => boolean;
export interface IPathfindingSystem extends IDataBaseExtended {
export interface IPathfindingSystemHooks extends IHookBase {
/**
* 在瞬移自动寻路前触发
* @param path 瞬移的寻路路径
*/
onBeforeTeleportTo?(
path: IPathfindingResult,
system: IPathfindingSystem
): void;
/**
* 在自动寻路之后触发
* @param path 瞬移的寻路路径
*/
onAfterTeleportTo?(
path: IPathfindingResult,
system: IPathfindingSystem
): void;
}
export interface IPathfindingSystem
extends IDataBaseExtended, IHookable<IPathfindingSystemHooks> {
/** 寻路求解器 */
readonly finder: IPathFinder;
/** 当前正在使用的移动寻路器 */
readonly mover: IObjectMover<IObjectMovable> | null;
/**
* 绑定寻路移动器,可绑定勇士或任意 `IObjectMover`,如动态图块移动器

View File

@ -1,5 +1,5 @@
import { IStateBase } from '@user/data-base';
import { IEnemyContext } from './combat';
import { ICombatFlow, IEnemyContext } from './combat';
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import { IGameEventSystem } from './event';
import { IPathfindingSystem } from './path';
@ -7,6 +7,8 @@ import { IPathfindingSystem } from './path';
export interface IStateSystem extends IStateBase {
/** 怪物上下文 */
readonly enemyContext: IEnemyContext<IEnemyAttr, IHeroAttr>;
/** 怪物战斗流程对象 */
readonly combat: ICombatFlow<IEnemyAttr, IHeroAttr>;
/** 游戏事件系统 */
readonly eventSystem: IGameEventSystem;
/** 已绑定勇士移动器的寻路系统 */

View File

@ -1,6 +1,7 @@
import { WebLoadStarter } from '@motajs/loader';
import { createClientCore } from './client/entry';
import { IClientCore } from '@user/client-modules';
import { LoadSceneUI } from '../packages-user/client-modules/src/ui/load';
declare global {
interface Window {
@ -8,5 +9,8 @@ declare global {
}
}
const client = await createClientCore(new WebLoadStarter());
const client = createClientCore(new WebLoadStarter());
// 游戏副作用启动
window.mainClient = client;
client.sceneController.open(LoadSceneUI, {});

View File

@ -41,9 +41,7 @@ export function createClientCoreConfig(
* 创建渲染端主对象,返回未经过异步初始化的主对象实例,需要手动调用 `state.load` 初始化
* @param starter 加载启动器
*/
export async function createClientCore(
starter: ILoadTaskStarter
): Promise<IClientCore> {
export function createClientCore(starter: ILoadTaskStarter): IClientCore {
const config = createClientCoreConfig(starter);
const state = new ClientCore(config);
return state;

View File

@ -1,4 +1,5 @@
import { IClientPluginDefine } from '@user/client-modules';
import { ClientShopPlugin } from './shop';
/** 渲染端插件列表 */
export const clientPlugins: IClientPluginDefine[] = [];
export const clientPlugins: IClientPluginDefine[] = [new ClientShopPlugin()];

View File

@ -0,0 +1,2 @@
export * from './plugin';
export * from './shop';

View File

@ -0,0 +1,23 @@
import { IClientPluginDefine } from '@user/client-modules';
import { Shop, ShopUI } from './shop';
export interface IClientShopPlugin extends IClientPluginDefine {
/** 商店组件 */
readonly Shop: typeof Shop;
/** 商店 UI 对象 */
readonly ShopUI: typeof ShopUI;
}
export class ClientShopPlugin implements IClientShopPlugin {
id: string = 'client-shop-plugin';
dependencies: readonly string[] = [];
Shop: typeof Shop = Shop;
ShopUI: typeof ShopUI = ShopUI;
awake(): void {}
async initialize(): Promise<void> {}
async afterLoad(): Promise<void> {}
}

View File

@ -2,13 +2,13 @@ import { GameUI, SetupComponentOptions } from '@motajs/system';
import { computed, defineComponent, onMounted, ref } from 'vue';
import { Font } from '@motajs/render';
import { Scroll, TextContent } from '@user/client-base';
import { IHeroState, IShopState } from '@user/data-base';
import { IShopSystem } from '@user/data-system';
import { IHeroState } from '@user/data-base';
import { IHeroAttr } from '@user/data-common';
import { FULL_LOC, MAIN_HEIGHT, MAIN_WIDTH } from '../shared';
import { IUIPropsBase } from './types';
import { FULL_LOC, IUIPropsBase, MAIN_WIDTH } from '@user/client-modules';
import { IShopState, IShopSystem } from '@dp/shop';
import { RENDER_WIDTH } from '@/constant';
export interface ShopProps extends IUIPropsBase {
export interface IShopProps extends IUIPropsBase {
/** 商店状态对象 */
shop: IShopState;
/** 勇士状态对象 */
@ -27,7 +27,7 @@ const shopProps = {
'heroState',
'shopSystem'
]
} satisfies SetupComponentOptions<ShopProps>;
} satisfies SetupComponentOptions<IShopProps>;
interface IShopItemEntry {
/** 道具图块数字 */
@ -55,7 +55,7 @@ const LIST_WIDTH = 250;
/** 左侧商店列表的顶部纵坐标 */
const LIST_TOP = MODE_BAR_HEIGHT + 4;
/** 左侧商店列表的高度,底部为退出按钮留出空间 */
const LIST_HEIGHT = MAIN_HEIGHT - LIST_TOP - 16;
const LIST_HEIGHT = RENDER_WIDTH - LIST_TOP - 16;
/** 商店列表中每一行的高度 */
const ROW_HEIGHT = 40;
/** 行内图标的尺寸 */
@ -63,7 +63,7 @@ const ROW_ICON_SIZE = 28;
/** 右侧信息区域的起始横坐标 */
const INFO_LEFT = LIST_WIDTH;
/** 右侧信息区域的宽度 */
const INFO_WIDTH = MAIN_WIDTH - INFO_LEFT;
const INFO_WIDTH = RENDER_WIDTH - INFO_LEFT;
/** 道具名称的纵坐标 */
const NAME_TOP = MODE_BAR_HEIGHT + 10;
/** 道具描述区域的顶部纵坐标 */
@ -89,7 +89,7 @@ const titleFont = Font.defaults({ size: 14, weight: 700 });
const textFont = Font.defaults({ size: 12 });
/** 商店界面组件 */
export const Shop = defineComponent<ShopProps>(props => {
export const Shop = defineComponent<IShopProps>(props => {
/** 商店状态对象 */
const shop = props.shop;
/** 勇士状态对象 */

View File

@ -175,7 +175,7 @@ export class GuardAura implements IEnemyAuraView<
if (locator.x === this.locator.x && locator.y === this.locator.y) {
return;
}
enemy.getAttribute('guard').add(this.locator);
enemy.getAttribute('guard').add(this.sourceView);
}
applySpecial(): IEnemySpecialModifier<IEnemyAttr> | null {

View File

@ -5,7 +5,6 @@ import {
IReadonlyEnemyHandler
} from '@user/data-system';
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import { logger } from '@motajs/common';
import { ISpecial9Value } from './special';
export class MainDamageCalculator implements IDamageCalculator<
@ -83,13 +82,8 @@ export class MainDamageCalculator implements IDamageCalculator<
this.inGuard = true;
// 计算支援怪的伤害,同时把打支援怪花费的回合数加到当前怪物上,因为打支援怪的时候当前怪物也会打你
// 因此回合数需要加上打支援怪的回合数
for (const guard of guards) {
for (const view of guards) {
// 直接把 enemy 传过去,因此支援的 enemy 会吃到其原本所在位置的光环加成
const view = handler.context.getEnemyByLocator(guard);
if (!view) {
logger.warn(137, guard.x.toString(), guard.y.toString());
continue;
}
const extraInfo = this.calculate({
enemy: view.getComputedEnemy(),
context: handler.context,

222
src/data/enemy/hook.ts Normal file
View File

@ -0,0 +1,222 @@
import {
IGameMap,
IHeroLocation,
IHeroLocationHooks,
IHeroModifier,
IHeroMover,
PercentageModifier
} from '@user/data-base';
import {
IEnemyAttr,
IHeroAttr,
IObjectMover,
IObjectMoverHooks,
ObjectMoveStep,
ObjectMoveType
} from '@user/data-common';
import { ICoreState } from '@user/data-state';
import {
ICombatFlowHooks,
IEnemyDamageInfo,
IPathfindingResult,
IPathfindingSystem,
IPathfindingSystemHooks
} from '@user/data-system';
import { ICombatWeakHandler } from './types';
import { isNil } from 'lodash-es';
export class EnemyMoveHooks implements IObjectMoverHooks<IHeroLocation> {
constructor(readonly state: ICoreState) {}
/**
* 处理中毒效果
* @param step 移动步对象
*/
private handlePoison(
step: Readonly<ObjectMoveStep>,
mover: IHeroMover<IHeroLocation>
) {
const poison = this.state.flags.getFieldValueDefaults('poison', 0);
// 中毒伤害小于等于 0,直接返回
if (poison <= 0) return;
const config = mover.getConfig();
// 如果忽略地形,那么也不计算中毒伤害
if (config.ignoreTerrain) return;
// 获取勇士的可修改属性对象
const hero = this.state.hero.getModifiableAttribute();
// 判断是否为常规移动步
if (
step.type === ObjectMoveType.Dir ||
step.type === ObjectMoveType.DirFace ||
step.type === ObjectMoveType.Special
) {
// 给勇士加上 -poison 的生命值,就相当于扣 poison 的生命值
hero.add('hp', -poison);
}
}
async onStepEnd(
_code: number,
step: Readonly<ObjectMoveStep>,
tile: IHeroLocation,
mover: IObjectMover<IHeroLocation>
): Promise<void> {
// 检查状态对象
if (tile.state !== this.state) return;
// 检查移动器是否为勇士移动器
if (mover !== this.state.hero.location.mover) return;
// 处理中毒
this.handlePoison(step, mover as IHeroMover<IHeroLocation>);
}
}
export class EnemyPathfindingHooks implements IPathfindingSystemHooks {
constructor(readonly state: ICoreState) {}
/**
* 处理中毒效果
* @param path 自动寻路路径
*/
private handlePoison(path: IPathfindingResult) {
const poison = this.state.flags.getFieldValueDefaults('poison', 0);
// 中毒伤害小于等于 0,直接返回
if (poison <= 0) return;
const mover = this.state.hero.location.mover;
const config = mover.getConfig();
// 如果忽略地形,那么也不计算中毒伤害
if (config.ignoreTerrain) return;
const hero = this.state.hero.getModifiableAttribute();
// 勇士减少步数乘以单步中毒的伤害。
hero.add('hp', -path.path.length);
}
onAfterTeleportTo(
path: IPathfindingResult,
system: IPathfindingSystem
): void {
// 判断状态对象
if (system.state !== this.state) return;
// 判断当前绑定的是不是勇士移动器
if (system.mover !== this.state.hero.location.mover) return;
// 处理中毒
this.handlePoison(path);
}
}
export class EnemyCombatHooks
implements ICombatFlowHooks<IEnemyAttr, IHeroAttr>, ICombatWeakHandler
{
/** 当前的虚弱值 */
private weakValue: number = 0;
/** 攻击虚弱修饰器 */
private readonly weakAtkModifier: IHeroModifier<number, number, number>;
/** 防御虚弱修饰器 */
private readonly weakDefModifier: IHeroModifier<number, number, number>;
constructor(readonly state: ICoreState) {
const hero = state.hero.getModifiableAttribute();
// 初始化虚弱属性的修饰器,并添加到勇士修饰器中
this.weakAtkModifier = new PercentageModifier(0);
this.weakDefModifier = new PercentageModifier(0);
hero.addModifier('atk', this.weakAtkModifier);
hero.addModifier('def', this.weakDefModifier);
}
setWeakValue(value: number): void {
this.weakValue = value;
// 需要填写负数才能使勇士属性降低
this.weakAtkModifier.setValue(-value);
this.weakDefModifier.setValue(-value);
}
getWeakValue(): number {
return this.weakValue;
}
async onAfterCombat(
info: IEnemyDamageInfo<IEnemyAttr, IHeroAttr>
): Promise<void> {
// 判断状态对象
if (info.handler.state !== this.state) return;
// 判断勇士对象
if (info.handler.hero !== this.state.hero.getAttribute()) return;
// 处理中毒属性
const poison = info.handler.enemy.getSpecial<number>(10);
if (poison) {
// 获取 poison 全局 flag,如果 flag 不存在,则自动插入为 0
const field = this.state.flags.getOrInsert('poison', 0);
const max = Math.max(field.get(), poison.value);
field.set(max);
}
// 处理虚弱属性
const weak = info.handler.enemy.getSpecial<number>(11);
if (weak) {
const max = Math.max(this.weakValue, weak.value);
this.setWeakValue(max);
}
// 处理重生属性
if (info.handler.enemy.hasSpecial(21)) {
const field = this.state.flags.getFieldValueDefaults<
Map<string, [number, number, number][]>
>('rebirth', new Map());
// 如果战斗不发生在任何地图,或者不存在坐标,那么不计入重生
if (isNil(info.handler.context.floor) || !info.handler.locator) {
return;
}
// 获取当前楼层的重生存储,并向其中添加重生标记
const floor = field.getOrInsert(info.handler.context.floor, []);
const { x, y } = info.handler.locator;
floor.push([info.handler.enemy.code, x, y]);
}
}
}
export class EnemyChangeFloorHooks implements IHeroLocationHooks {
constructor(readonly state: ICoreState) {}
/**
* 处理重生属性
* @param floor 目标楼层
*/
private handleRebirth(map: IGameMap) {
// 先获取指定楼层的重生标记
const field = this.state.flags.getFieldValueDefaults<
Map<string, [number, number, number][]>
>('rebirth', new Map());
const list = field.get(map.floorId);
// 如果不存在重生,直接返回
if (!list || list.length === 0) return;
const event = map.eventLayer;
// 如果不存在事件层,直接返回
if (!event) return;
// 把每个重生怪都放到事件层上
for (const [tile, x, y] of list) {
event.setBlock(tile, x, y);
}
// 清空重生怪列表
list.length = 0;
}
onSetFloor(map: IGameMap | null): void {
if (!map) return;
// 判断状态对象
if (map.state !== this.state) return;
// 处理重生
this.handleRebirth(map);
}
}

View File

@ -10,13 +10,23 @@ import { MainEnemyFinalEffect } from './final';
import { MainDamageCalculator } from './calculator';
import { MainEnemyComparer } from './comparer';
import { MainMapDamageConverter, MainMapDamageReducer } from './mapDamage';
import { IEnemyInitializationReturns } from './types';
import {
EnemyChangeFloorHooks,
EnemyCombatHooks,
EnemyMoveHooks,
EnemyPathfindingHooks
} from './hook';
import { MainCombatScript } from './script';
/**
* 初始化怪物相关内容
* @param state 数据端主对象
* @param config 数据端配置
*/
export function initializeEnemy(state: ICoreState) {
export function initializeEnemy(
state: ICoreState
): IEnemyInitializationReturns {
// 怪物管理器初始化
const comparer = new MainEnemyComparer();
state.enemyManager.attachEnemyComparer(comparer);
@ -45,4 +55,16 @@ export function initializeEnemy(state: ICoreState) {
// 注册怪物最终效果
ctx.registerFinalEffect(new MainEnemyFinalEffect());
// 添加默认战前战后脚本
state.combat.addCombatScript(new MainCombatScript());
// 注册相关的钩子
state.hero.location.mover.addHook(new EnemyMoveHooks(state));
state.pathfinding.addHook(new EnemyPathfindingHooks(state));
const combatHook = new EnemyCombatHooks(state);
state.combat.addHook(combatHook);
state.hero.location.addHook(new EnemyChangeFloorHooks(state));
return { weak: combatHook };
}

60
src/data/enemy/script.ts Normal file
View File

@ -0,0 +1,60 @@
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import {
ICombatFlowHandler,
ICombatScript,
IEnemyDamageInfo
} from '@user/data-system';
export class MainCombatScript implements ICombatScript<IEnemyAttr, IHeroAttr> {
priority: number = 0;
async before(
info: IEnemyDamageInfo<IEnemyAttr, IHeroAttr>,
handler: ICombatFlowHandler<IEnemyAttr, IHeroAttr>
): Promise<boolean> {
const hero = handler.hero;
// 如果伤害大于勇士血量,直接不允许战斗
if (!handler.force && hero.getFinalAttribute('hp') < info.damage) {
return false;
}
return true;
}
async after(
info: IEnemyDamageInfo<IEnemyAttr, IHeroAttr>,
handler: ICombatFlowHandler<IEnemyAttr, IHeroAttr>
): Promise<void> {
const hero = handler.hero;
const readonlyEnemy = info.handler.enemy;
// 计算金币和经验,支援怪也算上
const baseExp = readonlyEnemy.getAttribute('exp');
const baseMoney = readonlyEnemy.getAttribute('money');
const [guardExp, guardMoney] = [
...readonlyEnemy.getAttribute('guard')
].reduce(
(prev, curr) => {
const attr = curr.getComputedEnemy();
const exp = attr.getAttribute('exp');
const money = attr.getAttribute('money');
return [prev[0] + exp, prev[1] + money];
},
[0, 0]
);
const totalExp = baseExp + guardExp;
const totalMoney = baseMoney + guardMoney;
// 给勇士添加经验和金币
hero.add('exp', totalExp);
hero.add('money', totalMoney);
// 进行全局提示
handler.state.emitTip(
`打败${readonlyEnemy.getAttribute('name')},金币+${totalMoney},经验+${totalExp}。`,
readonlyEnemy.code
);
}
}

View File

@ -77,10 +77,10 @@ export interface ISpecial26Value {
* 2. 坚固、模仿等仅影响自身基础属性的属性:./final.ts, MainEnemyFinalEffect.apply 方法
* 3. 地图伤害:./mapDamage.ts,具体写法参考文档
* 4. 光环属性:./aura.ts 具体写法参考文档
* 5. 中毒的每步效果:在插件系统中,目前暂未实装
* 6. 中毒的瞬移效果:在插件系统中,目前暂未实装
* 7. 衰弱效果:在插件系统中,目前尚未实装
* 8. 重生属性:在插件系统中,目前尚未实装
* 5. 中毒的每步效果:./hook.ts EnemyMoveHooks.handlePoison 方法
* 6. 中毒的瞬移效果:./hook.ts EnemyPathfindingHooks.handlePoison 方法
* 7. 衰弱效果:./hook.ts EnemyCombatHooks.onAfterCombat 方法
* 8. 重生属性:./hook.ts EnemyCombatHooks.onAfterCombat 和 EnemyChangeFloorHooks.onSetFloor 方法
*/
export function createSpecials(
state: ICoreState
@ -233,18 +233,7 @@ export function createSpecials(
name: '衰弱',
desc: special => {
const weak = special.value;
const suffix = ',多次触发时取最高值。';
if (weak < 1) {
return (
`战斗后,角色陷入衰弱状态,攻防暂时下降${Math.floor(weak * 100)}%` +
suffix
);
} else {
return (
`战斗后,角色陷入衰弱状态,攻防暂时下降${weak}点` +
suffix
);
}
return `战斗后,角色陷入衰弱状态,攻防暂时下降${Math.floor(weak * 100)}%,多次触发时取最高值。`;
},
color: '#F0BBCC'
})

View File

@ -10,3 +10,21 @@ export const enum MapDamageType {
/** 夹击伤害 */
Between
}
export interface IEnemyInitializationReturns {
/** 虚弱处理对象 */
readonly weak: ICombatWeakHandler;
}
export interface ICombatWeakHandler {
/**
* 设置当前的虚弱值
* @param value 虚弱值
*/
setWeakValue(value: number): void;
/**
* 获取当前的虚弱值
*/
getWeakValue(): number;
}

24
src/data/hook/combat.ts Normal file
View File

@ -0,0 +1,24 @@
import { IEnemyAttr, IHeroAttr } from '@user/data-common';
import { ICoreState } from '@user/data-state';
import { ICombatFlowHooks, IEnemyDamageInfo } from '@user/data-system';
export class MainCombatFlowHooks implements ICombatFlowHooks<
IEnemyAttr,
IHeroAttr
> {
constructor(readonly state: ICoreState) {}
async onBeforeCombat(
info: IEnemyDamageInfo<IEnemyAttr, IHeroAttr>
): Promise<void> {
// 删除下面这一行开始编写你自己的战前钩子
void info;
}
async onAfterCombat(
info: IEnemyDamageInfo<IEnemyAttr, IHeroAttr>
): Promise<void> {
// 删除下面这一行开始编写你自己的战后钩子
void info;
}
}

30
src/data/hook/event.ts Normal file
View File

@ -0,0 +1,30 @@
import { ICoreState } from '@user/data-state';
import {
IBlockEventParam,
IGameEventExecutorHooks,
IGameEventInvocation
} from '@user/data-system';
export class MainGameEventExecutorHooks implements IGameEventExecutorHooks {
constructor(readonly state: ICoreState) {}
onBeforeExecuteEvent(
events: IGameEventInvocation[],
param: IBlockEventParam
): void {
// 删除下面这两行注释开始编写你自己的事件执行前钩子
void events;
void param;
}
onAfterExecuteEvent(
events: IGameEventInvocation[],
param: IBlockEventParam,
returns: unknown
): void {
// 删除下面这三行注释开始编写你自己的事件执行后钩子
void events;
void param;
void returns;
}
}

141
src/data/hook/hero.ts Normal file
View File

@ -0,0 +1,141 @@
import {
IGameMap,
IHeroAttributeHooks,
IHeroChangeFloorInfo,
IHeroLocation,
IHeroLocationHooks,
IHeroStateHooks
} from '@user/data-base';
import {
FaceDirection,
IHeroAttr,
IObjectMover,
IObjectMoverHooks,
ObjectMoveStep
} from '@user/data-common';
import { ICoreState } from '@user/data-state';
export class MainHeroAttributeHooks implements IHeroAttributeHooks<IHeroAttr> {
constructor(readonly state: ICoreState) {}
onUpdateAttribute<K extends keyof IHeroAttr>(
name: K,
value: IHeroAttr[K]
): void {
// 删除下面这两行开始编写你自己的勇士属性更新钩子
void name;
void value;
}
}
export class MainHeroLocationHooks implements IHeroLocationHooks {
constructor(readonly state: ICoreState) {}
onSetFloor(map: IGameMap | null): void {
// 删除下面这两行开始编写你自己的切换楼层钩子
void map;
}
onSetPos(x: number, y: number): void {
// 删除下面这两行开始编写你自己的勇士位置设置钩子
void x;
void y;
}
}
export class MainHeroMoverHooks implements IObjectMoverHooks<IHeroLocation> {
constructor(readonly state: ICoreState) {}
async onMoveStart(
tile: IHeroLocation,
mover: IObjectMover<IHeroLocation>
): Promise<void> {
// 删除下面这两行注释开始编写你自己的勇士移动前钩子
void tile;
void mover;
}
async onStepStart(
code: number,
step: Readonly<ObjectMoveStep>,
tile: IHeroLocation,
mover: IObjectMover<IHeroLocation>
): Promise<void> {
// 删除下面这四行注释开始编写你自己的勇士每步开始时钩子
void code;
void step;
void tile;
void mover;
}
async onStepEnd(
code: number,
step: Readonly<ObjectMoveStep>,
tile: IHeroLocation,
mover: IObjectMover<IHeroLocation>
): Promise<void> {
// 删除下面这四行注释开始编写你自己的勇士每步结束后钩子
void code;
void step;
void tile;
void mover;
}
async onMoveEnd(
tile: IHeroLocation,
mover: IObjectMover<IHeroLocation>
): Promise<void> {
// 删除下面这两行注释开始编写你自己的勇士移动前钩子
void tile;
void mover;
}
onSetFaceDir(
dir: FaceDirection,
tile: IHeroLocation,
mover: IObjectMover<IHeroLocation>
): void {
// 删除下面这三行注释开始编写你自己的设置面朝朝向的钩子
void dir;
void tile;
void mover;
}
onSetMoveDir(
dir: FaceDirection,
tile: IHeroLocation,
mover: IObjectMover<IHeroLocation>
): void {
// 删除下面这三行注释开始编写你自己的设置移动朝向的钩子
void dir;
void tile;
void mover;
}
onSetPos(
x: number,
y: number,
tile: IHeroLocation,
mover: IObjectMover<IHeroLocation>
): void {
// 删除下面这三行注释开始编写你自己的设置位置的钩子
void x;
void y;
void tile;
void mover;
}
}
export class MainHeroStateHooks implements IHeroStateHooks {
constructor(readonly state: ICoreState) {}
async onBeforeChangeFloor(info: IHeroChangeFloorInfo): Promise<void> {
// 删除下面这一行注释开始编写你自己的楼层转换前钩子
void info;
}
async onAfterChangeFloor(info: IHeroChangeFloorInfo): Promise<void> {
// 删除下面这一行注释开始编写你自己的楼层转换后钩子
void info;
}
}

6
src/data/hook/index.ts Normal file
View File

@ -0,0 +1,6 @@
export * from './combat';
export * from './event';
export * from './hero';
export * from './initialize';
export * from './loader';
export * from './path';

View File

@ -0,0 +1,26 @@
import { ICoreState } from '@user/data-state';
import { MainCombatFlowHooks } from './combat';
import { MainGameEventExecutorHooks } from './event';
import {
MainHeroAttributeHooks,
MainHeroLocationHooks,
MainHeroMoverHooks,
MainHeroStateHooks
} from './hero';
import { MainMotaDataLoaderHooks } from './loader';
import { MainPathfindingSystemHooks } from './path';
/**
* 初始化数据端主要钩子
* @param state 数据端主对象
*/
export function initializeHooks(state: ICoreState) {
state.combat.addHook(new MainCombatFlowHooks(state));
state.eventSystem.executor.addHook(new MainGameEventExecutorHooks(state));
state.hero.addHook(new MainHeroStateHooks(state));
state.hero.attribute.addHook(new MainHeroAttributeHooks(state));
state.hero.location.addHook(new MainHeroLocationHooks(state));
state.hero.location.mover.addHook(new MainHeroMoverHooks(state));
state.loader.addHook(new MainMotaDataLoaderHooks(state));
state.pathfinding.addHook(new MainPathfindingSystemHooks(state));
}

19
src/data/hook/loader.ts Normal file
View File

@ -0,0 +1,19 @@
import {
ICoreState,
IMotaDataLoader,
IMotaDataLoaderHooks
} from '@user/data-state';
export class MainMotaDataLoaderHooks implements IMotaDataLoaderHooks {
constructor(readonly state: ICoreState) {}
async onCoreConfigLoaded(loader: IMotaDataLoader): Promise<void> {
// 删除下面这两行注释开始编写你自己的核心配置加载后钩子
void loader;
}
async onExtraConfigLoaded(loader: IMotaDataLoader): Promise<void> {
// 删除下面这两行注释开始编写你自己的额外配置加载后钩子
void loader;
}
}

28
src/data/hook/path.ts Normal file
View File

@ -0,0 +1,28 @@
import { ICoreState } from '@user/data-state';
import {
IPathfindingResult,
IPathfindingSystem,
IPathfindingSystemHooks
} from '@user/data-system';
export class MainPathfindingSystemHooks implements IPathfindingSystemHooks {
constructor(readonly state: ICoreState) {}
onBeforeTeleportTo(
path: IPathfindingResult,
system: IPathfindingSystem
): void {
// 删除下面这两行注释开始编写你自己的自动寻路瞬移前钩子,注意非勇士的瞬移也会触发
void path;
void system;
}
onAfterTeleportTo(
path: IPathfindingResult,
system: IPathfindingSystem
): void {
// 删除下面这两行注释开始编写你自己的自动寻路瞬移后钩子,注意非勇士的瞬移也会触发
void path;
void system;
}
}

View File

@ -5,21 +5,21 @@ import {
FaceGroup,
IFacedTileLocator
} from '@user/data-common';
import {
ICoreState,
ICoreStateCoreConfig,
IDataPluginDefine
} from '@user/data-state';
import { initializeEnemy } from './enemy';
import { ICoreState, ICoreStateCoreConfig } from '@user/data-state';
import { ICombatWeakHandler, initializeEnemy } from './enemy';
import { initializeReplay } from './replay';
import { logger } from '@motajs/common';
import { DefaultHeroMoveTopImpl, DefaultPassPredicateImpl } from './hero';
import { DefaultDataLoaderHook } from './loader';
import { IDataMainPlugin } from './types';
import { initializeHooks } from './hook';
export class MainDataPlugin implements IDataPluginDefine {
export class MainDataPlugin implements IDataMainPlugin {
readonly id: string = 'main-data-plugin';
readonly dependencies: string[] = [];
weak!: ICombatWeakHandler;
constructor(readonly heroFace: FaceGroup) {}
awake(state: ICoreState): void {
@ -46,8 +46,11 @@ export class MainDataPlugin implements IDataPluginDefine {
state.loader.addHook(new DefaultDataLoaderHook());
// 其他复杂初始化
initializeEnemy(state);
const enemyReturns = initializeEnemy(state);
initializeReplay(state);
initializeHooks(state);
this.weak = enemyReturns.weak;
// 存档初始化
state.addSaveableContent('@system/hero', state.hero);

View File

@ -1,5 +1,5 @@
import { IDataPluginDefine } from '@user/data-state';
import { ShopPlugin } from './shop';
import { DataShopPlugin } from './shop';
/** 数据端插件列表 */
export const dataPlugins: IDataPluginDefine[] = [new ShopPlugin()];
export const dataPlugins: IDataPluginDefine[] = [new DataShopPlugin()];

View File

@ -1 +1,5 @@
export * from './shop';
export * from './manager';
export * from './plugin';
export * from './state';
export * from './system';
export * from './types';

View File

@ -4,8 +4,8 @@ import { ShopManager } from './manager';
import { ShopState } from './state';
import { ShopSystem } from './system';
export class ShopPlugin implements IShopPlugin {
id: string = 'shop-plugin';
export class DataShopPlugin implements IShopPlugin {
id: string = 'data-shop-plugin';
dependencies: string[] = [];
shop!: IShopSystem;

7
src/data/types.ts Normal file
View File

@ -0,0 +1,7 @@
import { IDataPluginDefine } from '@user/data-state';
import { ICombatWeakHandler } from './enemy';
export interface IDataMainPlugin extends IDataPluginDefine {
/** 虚弱处理对象 */
readonly weak: ICombatWeakHandler;
}

View File

@ -16,7 +16,9 @@
"noEmit": true,
"paths": {
"@motajs/*": ["./packages/*/src"],
"@user/*": ["./packages-user/*/src"]
"@user/*": ["./packages-user/*/src"],
"@dp/*": ["./src/data/plugin/*"],
"@/*": ["./src/*"]
}
},
"include": [
@ -26,11 +28,9 @@
"packages/**/*.ts",
"packages/**/*.d.ts",
"packages/**/*.tsx",
"packages/**/*.vue",
"packages-user/**/*.ts",
"packages-user/**/*.d.ts",
"packages-user/**/*.tsx",
"src/types/**/*.d.ts"
"packages-user/**/*.tsx"
],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -26,6 +26,14 @@ const aliasesUser = glob.sync('packages-user/*/src').map((srcPath) => {
};
});
const aliasesDP = glob.sync('src/data/plugin/*').map((srcPath) => {
const packageName = path.basename(path.dirname(srcPath));
return {
find: `@dp/${packageName}`,
replacement: path.resolve(__dirname, srcPath),
};
});
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
@ -40,7 +48,12 @@ export default defineConfig({
resolve: {
alias: [
...aliases,
...aliasesUser
...aliasesUser,
...aliasesDP,
{
find: '@/',
replacement: path.resolve(__dirname, 'src/')
}
]
},
test: {