diff --git a/docs/dev/map/trigger-impl.md b/docs/dev/map/trigger-impl.md new file mode 100644 index 0000000..0d65120 --- /dev/null +++ b/docs/dev/map/trigger-impl.md @@ -0,0 +1,44 @@ +# 需求综述 + +触发器系统主体已经完成,下一步开始逐步补充内置触发器类型。当前第一批只先设计两个最基础的内置触发器:战斗触发器与开门触发器。 + +本文只确认这两个类型的职责、使用频率预期与当前待补充的设计边界,不展开实现思路,也不列涉及文件。后续如果继续新增对话、商店、自定义事件等内置触发器,再在此文档上继续补充即可。 + +--- + +# 共通约束 + +这两个内置触发器都只是 `ITrigger` 的内置实现。当前阶段不建议先给它们增加额外公共配置成员,怪物信息、门信息原则上应跟随当前触发位置上的图块或运行时载体,而不是在触发器实例里重复保存一份。 + +- `type`:预期频率**低频**。由注册表和类型定义决定,通常只在注册、调试或排查问题时关注。 +- `priority`:预期频率**低频**。大多数情况下使用固定默认值即可,只有极少数“同格混合触发”场景才需要显式调整。 +- `trigger(handler)`:预期频率**中频**。平时更多通过 `ITriggerCollection.trigger(...)` 间接触发,直接持有某个内置触发器并手动调用的场景相对有限。 +- `collection()`:预期频率**中频**。继承 `ITrigger` 的统一包装能力,使用方式与其他触发器一致,这里不做额外语义扩展。 + +--- + +# 类型设计与预期 + +## 战斗触发器(暂定 `IBattleTrigger`) + +- 核心职责:在当前触发位置执行一次与怪物的战斗。 +- `IBattleTrigger.trigger(handler)`:预期频率**中频**。典型使用场景:玩家移动到怪物格后,移动系统收集到该触发器并交给集合统一触发;或脚本手动触发某个战斗事件。 +- 校验要求:触发前必须确认当前指定图块或对应的运行时载体确实是怪物。若不是怪物,发出警告并终止本次触发,不进入战斗流程。 +- 数据要求:需要能从当前触发位置解析出“这是怪物”这一事实,以及后续进入战斗流程所需的怪物信息。 +- 职责边界:该触发器当前只先负责“确认目标是怪物并进入战斗分支”。战斗后怪物移除、地图更新、事件派发等后续职责,先不在本文中拍死。 + +## 开门触发器(暂定 `IOpenDoorTrigger`) + +- 核心职责:在当前触发位置执行一次开门行为。 +- `IOpenDoorTrigger.trigger(handler)`:预期频率**中频**。典型使用场景:玩家撞到门格或主动交互门格后,系统收集到该触发器并尝试开门;或脚本在演出中手动触发某扇门的开启。 +- 校验要求:触发前必须确认当前指定图块或对应的运行时载体包含门相关信息。若不包含,发出警告并终止本次触发,不执行开门行为。 +- 数据要求:需要能从当前触发位置读取门相关信息,并定位到既有的 `openDoor` 流程。 +- 职责边界:该触发器当前只先负责“确认目标是门并进入开门分支”。钥匙校验、动画等待、图块替换、开门后事件等具体流程,先依附既有开门系统,不在本文中提前拆开。 + +--- + +# 当前待补充设计 + +1. 运行时需要一条统一路径,让触发器能从 `handler` 提供的上下文中读取当前位置对应的怪物信息或门信息;这一层目前还没有最终定稿。 +2. 这两个内置触发器原则上都不应在实例上重复保存业务对象;更合理的方向是由当前位置的图块或运行时载体提供数据。若后续发现这条路线不够用,再单独讨论是否为内置触发器补配置成员。 +3. 这两个触发器当前都只先定义“校验 + 进入对应系统”的第一层职责。后续如果需要细分成更多内置类型,例如强制战斗、免钥匙开门、条件开门等,再在这个基础上继续扩展。 diff --git a/packages-user/data-state/src/core.ts b/packages-user/data-state/src/core.ts index 6d497d2..01e7093 100644 --- a/packages-user/data-state/src/core.ts +++ b/packages-user/data-state/src/core.ts @@ -69,23 +69,23 @@ import { logger } from '@motajs/common'; import { ISaveSystem, SaveSystem } from './save'; export class CoreState implements ICoreState { - // Layer 0 内容 + // Layer 0 公共层 readonly roleFace: IRoleFaceBinder; readonly faceManager: IFaceManager; readonly tileStore: ITileStore; - // Layer 1 内容 + // Layer 1 数据层,所有可存档内容都在这 readonly maps: IMapStore; readonly hero: IHeroState; readonly enemyManager: IEnemyManager; readonly flags: IFlagSystem; - // Layer 2 内容 + // Layer 2 执行层,游戏逻辑对象都在这 readonly enemyContext: IEnemyContext; - readonly triggerRegistry: ITriggerRegistry; - readonly triggerCollector: ITriggerCollector; + readonly triggerRegistry: ITriggerRegistry; + readonly triggerCollector: ITriggerCollector; - // 用户层内容 + // 用户层内容,也就是最顶层的内容,一般仅用于初始化 readonly loadProgress: ILoadProgressTotal; readonly dataLoader: IMotaDataLoader; readonly saveSystem: ISaveSystem; @@ -171,8 +171,10 @@ export class CoreState implements ICoreState { this.enemyContext = enemyContext; // 触发器注册与收集器 - const triggerRegistry = new TriggerRegistry(); - const triggerCollector = new TriggerCollector(); + const triggerRegistry = new TriggerRegistry( + this + ); + const triggerCollector = new TriggerCollector(); triggerCollector.attachRegistry(triggerRegistry); this.triggerRegistry = triggerRegistry; this.triggerCollector = triggerCollector; @@ -247,7 +249,6 @@ export class CoreState implements ICoreState { * @param data 旧样板怪物存储对象 */ private initEnemyManager(data: Record) { - // TODO: 修改怪物模板并存入存档,即 core.setEnemy const manager = this.enemyManager; const reference = new Map>(); for (const [id, enemy] of Object.entries(structuredClone(data))) { diff --git a/packages-user/data-system/src/trigger/collection.ts b/packages-user/data-system/src/trigger/collection.ts index 590e753..005a245 100644 --- a/packages-user/data-system/src/trigger/collection.ts +++ b/packages-user/data-system/src/trigger/collection.ts @@ -1,10 +1,13 @@ import { ITrigger, ITriggerCollection, ITriggerHandler } from './types'; -export class TriggerCollection implements ITriggerCollection { +export class TriggerCollection< + TEnemy = unknown, + THero = unknown +> implements ITriggerCollection { /** 当前集合内部维护的触发器列表 */ - private readonly triggerList: ITrigger[]; + private readonly triggerList: ITrigger[]; - constructor(triggers: Iterable) { + constructor(triggers: Iterable>) { this.triggerList = [...triggers]; } @@ -12,17 +15,19 @@ export class TriggerCollection implements ITriggerCollection { return this.triggerList.length; } - async trigger( - handler: ITriggerHandler - ): Promise { + async trigger(handler: ITriggerHandler): Promise { for (const trigger of this.triggerList) { await trigger.trigger(handler); } } - async *triggerIter( + async *triggerIter( handler: ITriggerHandler - ): AsyncGenerator | null> { + ): AsyncGenerator< + ITrigger, + void, + ITriggerHandler | null + > { let currentHandler = handler; for (const trigger of this.triggerList) { await trigger.trigger(currentHandler); @@ -35,19 +40,21 @@ export class TriggerCollection implements ITriggerCollection { } } - iterate(): Iterable { + iterate(): Iterable> { return this.triggerList.values(); } - push(trigger: ITrigger): void { + push(trigger: ITrigger): void { this.triggerList.push(trigger); } - unshift(trigger: ITrigger): void { + unshift(trigger: ITrigger): void { this.triggerList.unshift(trigger); } - concat(...others: ITriggerCollection[]): ITriggerCollection { + concat( + ...others: ITriggerCollection[] + ): ITriggerCollection { const merged = [...this.triggerList]; for (const other of others) { merged.push(...other.iterate()); diff --git a/packages-user/data-system/src/trigger/collector.ts b/packages-user/data-system/src/trigger/collector.ts index 94b13e6..5be8589 100644 --- a/packages-user/data-system/src/trigger/collector.ts +++ b/packages-user/data-system/src/trigger/collector.ts @@ -8,11 +8,18 @@ import { import { logger } from '@motajs/common'; import { TriggerCollection } from './collection'; -export class TriggerCollector implements ITriggerCollector { +export class TriggerCollector< + TEnemy = unknown, + THero = unknown +> implements ITriggerCollector { /** 当前收集器使用的注册对象 */ - private registry: ITriggerRegistry | null = null; + private registry: ITriggerRegistry | null = null; - collect(x: number, y: number, layer: IMapLayer): ITriggerCollection { + collect( + x: number, + y: number, + layer: IMapLayer + ): ITriggerCollection { if (!this.registry) { logger.warn(135); return new TriggerCollection([]); @@ -55,8 +62,8 @@ export class TriggerCollector implements ITriggerCollector { const duplicate = new Set(); if (staticTrigger) { // 有静态触发器 - const lessTriggers: ITrigger[] = []; - const greaterTriggers: ITrigger[] = []; + const lessTriggers: ITrigger[] = []; + const greaterTriggers: ITrigger[] = []; // 先收集所有的触发器,并记录重复情况 for (const tile of layer.dynamicLayer.getDynamicTilesAt(x, y)) { const trigger = this.registry.create(tile.triggerType); @@ -85,7 +92,7 @@ export class TriggerCollector implements ITriggerCollector { return new TriggerCollection(arr); } else { // 没有静态触发器 - const triggers: ITrigger[] = []; + const triggers: ITrigger[] = []; for (const tile of layer.dynamicLayer.getDynamicTilesAt(x, y)) { const trigger = this.registry.create(tile.triggerType); if (trigger) { @@ -106,7 +113,7 @@ export class TriggerCollector implements ITriggerCollector { } } - attachRegistry(registry: ITriggerRegistry | null): void { + attachRegistry(registry: ITriggerRegistry | null): void { this.registry = registry; } } diff --git a/packages-user/data-system/src/trigger/registry.ts b/packages-user/data-system/src/trigger/registry.ts index 6b2a86a..3b161b7 100644 --- a/packages-user/data-system/src/trigger/registry.ts +++ b/packages-user/data-system/src/trigger/registry.ts @@ -1,49 +1,31 @@ import { logger } from '@motajs/common'; -import { - ITrigger, - ITriggerRegistry, - TriggerFactory, - TriggerStringFactory -} from './types'; +import { ITrigger, ITriggerRegistry, TriggerFactory } from './types'; +import { IStateBase } from '@user/data-base'; -export class TriggerRegistry implements ITriggerRegistry { +export class TriggerRegistry< + TEnemy = unknown, + THero = unknown +> implements ITriggerRegistry { /** 数字类型到触发器工厂的映射 */ - private readonly typeMap: Map = new Map(); + private readonly typeMap: Map> = + new Map(); - /** 字符串 id 到触发器工厂的映射 */ - private readonly stringMap: Map = new Map(); + constructor(public readonly state: IStateBase) {} - register(type: number, factory: TriggerFactory): void { + register(type: number, factory: TriggerFactory): void { if (this.typeMap.has(type)) { logger.warn(132, 'type', type.toString()); } this.typeMap.set(type, factory); } - get(type: number): TriggerFactory | null { + get(type: number): TriggerFactory | null { return this.typeMap.get(type) ?? null; } - create(num: number): ITrigger | null { + create(num: number): ITrigger | null { const factory = this.get(num); if (!factory) return null; - return factory(num); - } - - registerString(id: string, factory: TriggerStringFactory): void { - if (this.stringMap.has(id)) { - logger.warn(132, 'id', id); - } - this.stringMap.set(id, factory); - } - - getString(id: string): TriggerStringFactory | null { - return this.stringMap.get(id) ?? null; - } - - createByString(id: string): ITrigger | null { - const factory = this.getString(id); - if (!factory) return null; - return factory(); + return factory(num, this.state); } } diff --git a/packages-user/data-system/src/trigger/types.ts b/packages-user/data-system/src/trigger/types.ts index 2e75673..db4bcff 100644 --- a/packages-user/data-system/src/trigger/types.ts +++ b/packages-user/data-system/src/trigger/types.ts @@ -1,5 +1,10 @@ import { ITileLocator } from '@motajs/common'; -import { ILayerState, IMapLayer, IStateBase } from '@user/data-base'; +import { + ILayerState, + IMapLayer, + IStateBase, + IStateBaseExtended +} from '@user/data-base'; export interface ITriggerHandler { /** 当前全局状态对象 */ @@ -12,11 +17,15 @@ export interface ITriggerHandler { readonly locator?: ITileLocator; } -export type TriggerFactory = (type: number) => ITrigger; +export type TriggerFactory = ( + type: number, + state: IStateBase +) => ITrigger; -export type TriggerStringFactory = () => ITrigger; - -export interface ITrigger { +export interface ITrigger< + TEnemy = unknown, + THero = unknown +> extends IStateBaseExtended { /** 触发器类型标识 */ readonly type: number; /** 触发器优先级 */ @@ -26,57 +35,39 @@ export interface ITrigger { * 使用给定上下文触发当前触发器 * @param handler 触发上下文对象 */ - trigger( - handler: ITriggerHandler - ): Promise; + trigger(handler: ITriggerHandler): Promise; /** * 将当前触发器包装为单元素触发器集合 */ - collection(): ITriggerCollection; + collection(): ITriggerCollection; } -export interface ITriggerRegistry { +export interface ITriggerRegistry< + TEnemy = unknown, + THero = unknown +> extends IStateBaseExtended { /** * 注册一个按类型创建的触发器工厂 * @param type 触发器类型 * @param factory 触发器工厂函数 */ - register(type: number, factory: TriggerFactory): void; + register(type: number, factory: TriggerFactory): void; /** * 获取指定类型的触发器工厂 * @param type 触发器类型 */ - get(type: number): TriggerFactory | null; + get(type: number): TriggerFactory | null; /** * 根据触发器类型创建一个触发器实例,如果对应工厂不存在则返回 `null` * @param num 触发器类型 */ - create(num: number): ITrigger | null; - - /** - * 注册一个按字符串 id 查询的触发器工厂 - * @param id 触发器字符串 id - * @param factory 触发器工厂函数 - */ - registerString(id: string, factory: TriggerStringFactory): void; - - /** - * 获取指定字符串 id 对应的触发器工厂 - * @param id 触发器字符串 id - */ - getString(id: string): TriggerStringFactory | null; - - /** - * 根据字符串 id 创建一个触发器实例,如果对应工厂不存在则返回 `null` - * @param id 触发器字符串 id - */ - createByString(id: string): ITrigger | null; + create(num: number): ITrigger | null; } -export interface ITriggerCollection { +export interface ITriggerCollection { /** * 当前集合中的触发器数量 */ @@ -86,54 +77,62 @@ export interface ITriggerCollection { * 顺序触发当前集合中的所有触发器 * @param handler 初始触发上下文对象 */ - trigger( - handler: ITriggerHandler - ): Promise; + trigger(handler: ITriggerHandler): Promise; /** * 逐个触发当前集合中的触发器,并允许为下一次推进提供新上下文 * @param handler 初始触发上下文对象 */ - triggerIter( + triggerIter( handler: ITriggerHandler - ): AsyncGenerator | null>; + ): AsyncGenerator< + ITrigger, + void, + ITriggerHandler | null + >; /** * 迭代当前集合中的所有触发器 */ - iterate(): Iterable; + iterate(): Iterable>; /** * 向集合末尾追加一个触发器 * @param trigger 要追加的触发器 */ - push(trigger: ITrigger): void; + push(trigger: ITrigger): void; /** * 向集合头部插入一个触发器 * @param trigger 要插入的触发器 */ - unshift(trigger: ITrigger): void; + unshift(trigger: ITrigger): void; /** * 将当前集合与其他集合顺序拼接为一个新集合 * @param others 要拼接的其他集合 */ - concat(...others: ITriggerCollection[]): ITriggerCollection; + concat( + ...others: ITriggerCollection[] + ): ITriggerCollection; } -export interface ITriggerCollector { +export interface ITriggerCollector { /** * 收集指定图层中某一点的所有触发器 * @param x 横坐标 * @param y 纵坐标 * @param layer 目标图层 */ - collect(x: number, y: number, layer: IMapLayer): ITriggerCollection; + collect( + x: number, + y: number, + layer: IMapLayer + ): ITriggerCollection; /** * 绑定或清除当前 collector 使用的注册表 * @param registry 触发器注册表 */ - attachRegistry(registry: ITriggerRegistry | null): void; + attachRegistry(registry: ITriggerRegistry | null): void; } diff --git a/packages-user/data-system/src/types.ts b/packages-user/data-system/src/types.ts index 7160a98..60f075c 100644 --- a/packages-user/data-system/src/types.ts +++ b/packages-user/data-system/src/types.ts @@ -6,9 +6,9 @@ export interface IStateSystem extends IStateBase { /** 怪物上下文 */ readonly enemyContext: IEnemyContext; /** 触发器注册 */ - readonly triggerRegistry: ITriggerRegistry; + readonly triggerRegistry: ITriggerRegistry; /** 触发器收集器 */ - readonly triggerCollector: ITriggerCollector; + readonly triggerCollector: ITriggerCollector; } export interface IStateSystemExtended {