From c4bcecdf8aceef3cea749d1c8902478224765cab Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 29 Jun 2026 19:57:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8B=87=E5=A3=AB=E6=92=9E=E5=87=BB?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages-user/data-base/src/hero/mover.ts | 242 +++++++++++------- packages-user/data-base/src/hero/types.ts | 65 ++++- packages-user/data-base/src/map/mover.ts | 14 +- packages-user/data-base/src/types.ts | 2 +- packages-user/data-common/src/common/mover.ts | 64 ++--- packages-user/data-common/src/common/utils.ts | 4 + packages-user/data-common/src/store/types.ts | 5 + .../data-state/src/hero/passChecker.ts | 152 ++++++----- packages-user/data-state/src/legacy/tile.ts | 14 +- packages-user/data-system/src/combat/types.ts | 26 +- .../data-system/src/trigger/types.ts | 6 +- prompt.md | 40 +-- 12 files changed, 380 insertions(+), 254 deletions(-) diff --git a/packages-user/data-base/src/hero/mover.ts b/packages-user/data-base/src/hero/mover.ts index 9d4ba7f..d319d3f 100644 --- a/packages-user/data-base/src/hero/mover.ts +++ b/packages-user/data-base/src/hero/mover.ts @@ -1,18 +1,21 @@ import { ITileLocator } from '@motajs/common'; import { FaceDirection, - getFaceMovement, IFaceHandler, IMoverController, ObjectMoveStep, ObjectMover, - ObjectMoveStepType + ObjectMoveType, + IDataCommon } from '@user/data-common'; import { HeroMoveCode, + IHeroHitAction, + IHeroHitHandler, IHeroLocation, IHeroMover, IHeroMoverConfig, + IPassCheckerHandler, ITerrainPassChecker } from './types'; import { isNil } from 'lodash-es'; @@ -21,20 +24,26 @@ export class HeroMover extends ObjectMover implements IHeroMover { + readonly state: IDataCommon; + /** 本次移动是否不记录进路线系统 */ private noRoute: boolean = false; /** 本次移动是否忽略地形碰撞检测 */ private ignoreTerrain: boolean = false; /** 本次移动是否在特定时机触发自动存档 */ private autoSave: boolean = false; - /** 自定义地形通行判定器 */ + + /** 地形通行判定器 */ private terrainChecker: ITerrainPassChecker | null = null; + /** 撞击行为对象 */ + private hitAction: IHeroHitAction | null = null; constructor( readonly tile: T, faceHandler: IFaceHandler ) { super(faceHandler); + this.state = tile.state; } config(config: Readonly): this { @@ -62,38 +71,108 @@ export class HeroMover this.terrainChecker = checker; } - protected async onMoveStart( - _tile: IHeroLocation, - _controller: Readonly - ): Promise {} + useHitAction(action: IHeroHitAction | null): void { + this.hitAction = action; + } - protected async onMoveEnd( - _tile: IHeroLocation, - _controller: Readonly - ): Promise {} + /** + * 创建通行性检查信息对象 + * @param curr 勇士所在位置 + * @param dir 勇士移动方向 + * @param floorId 当前楼层 id + */ + private createPassHandler( + curr: ITileLocator, + dir: FaceDirection, + floorId: string | undefined + ): IPassCheckerHandler { + const { x, y } = this.faceHandler.movement(dir); + const nx = curr.x + x; + const ny = curr.y + y; + return { + currLoc: curr, + nextLoc: { x: nx, y: ny }, + direction: dir, + floorId, + face: this.faceHandler, + state: this.state + }; + } + + /** + * 创建通行性检查信息对象 + * @param curr 勇士所在位置 + * @param dir 勇士移动方向 + * @param floorId 当前楼层 id + */ + private createHitHandler( + curr: ITileLocator, + dir: FaceDirection, + floorId: string | undefined + ): IHeroHitHandler { + const { x, y } = this.faceHandler.movement(dir); + const nx = curr.x + x; + const ny = curr.y + y; + return { + currLoc: curr, + nextLoc: { x: nx, y: ny }, + direction: dir, + floorId, + state: this.state + }; + } + + protected async onMoveStart(): Promise {} + + protected async onMoveEnd(): Promise {} protected async onStepStart( - step: ObjectMoveStep, - tile: IHeroLocation, - controller: Readonly + step: Readonly, + tile: IHeroLocation ): Promise { + if (!this.terrainChecker) return HeroMoveCode.CannotMove; + const type = step.type; if ( - type !== ObjectMoveStepType.Dir && - type !== ObjectMoveStepType.DirFace && - type !== ObjectMoveStepType.Special + type === ObjectMoveType.Dir || + type === ObjectMoveType.DirFace || + type === ObjectMoveType.Special ) { - return HeroMoveCode.Step; + const dir = this.moveDirection; + const handler = this.createPassHandler(tile, dir, tile.floorId); + const { x: nx, y: ny } = handler.nextLoc; + const inBound = this.terrainChecker.inBound(nx, ny, tile.floorId); + // 目标点不在地图内 + if (!inBound) return HeroMoveCode.CannotMove; + + if (this.ignoreTerrain) { + return HeroMoveCode.Step; + } else { + const canPass = this.terrainChecker.canPass(handler); + if (canPass) { + const hit = this.terrainChecker.shouldHit(handler); + if (hit) return HeroMoveCode.Hit; + else return HeroMoveCode.Step; + } else { + return HeroMoveCode.CannotMove; + } + } } - if (!this.ignoreTerrain) { - const result = this.checkTerrain(tile, controller); - if (result !== HeroMoveCode.Step) return result; - } - - if (this.autoSave) { - // TODO: 检查当前步是否需要触发自动存档(如进入/离开地图伤害区域) + // 跳跃和瞬移仅需要进行边界判断 + if (type === ObjectMoveType.Jump || type === ObjectMoveType.Teleport) { + const nx = step.rel ? tile.x + step.x : step.x; + const ny = step.rel ? tile.y + step.y : step.y; + if (this.ignoreTerrain) { + return HeroMoveCode.Step; + } else { + if (this.terrainChecker.inBound(nx, ny, tile.floorId)) { + return HeroMoveCode.Step; + } else { + return HeroMoveCode.Stop; + } + } } return HeroMoveCode.Step; @@ -101,89 +180,60 @@ export class HeroMover protected async onStepEnd( code: number, - step: ObjectMoveStep, + step: Readonly, tile: IHeroLocation, controller: Readonly ): Promise { const type = step.type; - const isMovementStep = - type === ObjectMoveStepType.Dir || - type === ObjectMoveStepType.DirFace || - type === ObjectMoveStepType.Special; - - if ( - !isMovementStep && - type !== ObjectMoveStepType.Teleport && - type !== ObjectMoveStepType.Jump - ) { + // 不能移动或停止 + if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Stop) { + // 这里不能 await,因为其 Promise 会在当前步结束后兑现,如果 await 就会卡住 + controller.stop(); return { x: tile.x, y: tile.y }; } - if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Hit) { - void controller.stop(); - // TODO: 处理撞击图块逻辑 + // 撞击时也使用当前位置,同时处理撞击行为 + if (code === HeroMoveCode.Hit) { + // 执行撞击行为 + if (this.hitAction) { + const dir = this.moveDirection; + const handler = this.createHitHandler(tile, dir, tile.floorId); + await this.hitAction.hit(handler); + } + // 这里同样不能 await + controller.stop(); return { x: tile.x, y: tile.y }; } - if (code === HeroMoveCode.Stop) { - void controller.stop(); - // TODO: 清除自动寻路 - return { x: tile.x, y: tile.y }; + // 正常移动 + if (code === HeroMoveCode.Step) { + if ( + type === ObjectMoveType.Teleport || + type === ObjectMoveType.Jump + ) { + // 瞬移行为 + if (step.rel) { + return { x: tile.x + step.x, y: tile.y + step.y }; + } else { + return { x: step.x, y: step.y }; + } + } else if ( + type === ObjectMoveType.Dir || + type === ObjectMoveType.DirFace || + type === ObjectMoveType.Special + ) { + // 移动行为 + const { x, y } = this.faceHandler.movement(this.moveDirection); + const nx = tile.x + x; + const ny = tile.y + y; + return { x: nx, y: ny }; + } else { + // 其他行为 + return { x: tile.x, y: tile.y }; + } } - if (type === ObjectMoveStepType.Teleport) { - return this.calcPos(tile, step.x, step.y, step.rel); - } - - if (type === ObjectMoveStepType.Jump) { - return this.calcPos(tile, step.x, step.y, step.rel); - } - - const offset = getFaceMovement(this.moveDirection); - const nx = tile.x + offset.x; - const ny = tile.y + offset.y; - - // TODO: 路线记录 - // TODO: 中毒伤害处理 - // TODO: 步数累计 - - return { x: nx, y: ny }; - } - - /** - * 计算传送 / 跳跃的目标坐标 - * @param tile 当前所在图块 - * @param x 目标横坐标或偏移量 - * @param y 目标纵坐标或偏移量 - * @param rel 是否使用相对模式 - */ - private calcPos( - tile: IHeroLocation, - x: number, - y: number, - rel: boolean - ): ITileLocator { - if (rel) { - return { x: tile.x + x, y: tile.y + y }; - } - return { x, y }; - } - - /** - * 检测当前移动方向前方一格的地形是否可通行 - */ - private checkTerrain( - tile: IHeroLocation, - _controller: Readonly - ): HeroMoveCode { - if (!this.terrainChecker) return HeroMoveCode.CannotMove; - const locator: ITileLocator = { x: tile.x, y: tile.y }; - const pass = this.terrainChecker.canPass( - locator, - this.moveDirection, - tile.floorId - ); - return pass ? HeroMoveCode.Step : HeroMoveCode.CannotMove; + return { x: tile.x, y: tile.y }; } } diff --git a/packages-user/data-base/src/hero/types.ts b/packages-user/data-base/src/hero/types.ts index 023b9aa..4207fc2 100644 --- a/packages-user/data-base/src/hero/types.ts +++ b/packages-user/data-base/src/hero/types.ts @@ -7,6 +7,7 @@ import { import { FaceDirection, IDataCommonExtended, + IFaceHandler, IObjectMovable, IObjectMover, ISaveableContent @@ -229,21 +230,52 @@ export const enum HeroMoveCode { CannotMove } +export interface IHeroMoveHandlerBase extends IDataCommonExtended { + /** 当前位置 */ + readonly currLoc: ITileLocator; + /** 要移动至的位置 */ + readonly nextLoc: ITileLocator; + /** 移动方向 */ + readonly direction: FaceDirection; + /** 当前楼层 id */ + readonly floorId: string | undefined; +} + +export interface IPassCheckerHandler extends IHeroMoveHandlerBase { + /** 朝向管理对象 */ + readonly face: IFaceHandler; +} + +export interface IHeroHitHandler extends IHeroMoveHandlerBase {} + export interface ITerrainPassChecker { - /** 对应的勇士状态对象 */ - readonly hero: IHeroState; + /** + * 检查目标位置是否在地图范围内 + * @param x 横坐标 + * @param y 纵坐标 + * @param floorId 楼层 id + */ + inBound(x: number, y: number, floorId: string | undefined): boolean; /** * 判断在指定楼层中,从指定坐标向指定方向移动一格是否可通行 - * @param locator 当前位置 - * @param direction 移动方向 - * @param floorId 当前楼层 id,可能为 undefined + * @param handler 通行性检查对象 */ - canPass( - locator: ITileLocator, - direction: FaceDirection, - floorId: string | undefined - ): boolean; + canPass(handler: IPassCheckerHandler): boolean; + + /** + * 判断在指定楼层中,从指定坐标向指定方向移动时是否应该产生撞击,撞击将会触发目标位置的触发器 + * @param handler 通行性检查对象 + */ + shouldHit(handler: IPassCheckerHandler): boolean; +} + +export interface IHeroHitAction { + /** + * 勇士撞击某一个图块时执行的内容,一般用于触发目标位置的触发器 + * @param handler 撞击行为对象 + */ + hit(handler: IHeroHitHandler): Promise; } export interface IHeroMoverConfig { @@ -255,7 +287,8 @@ export interface IHeroMoverConfig { autoSave?: boolean; } -export interface IHeroMover extends IObjectMover { +export interface IHeroMover + extends IObjectMover, IDataCommonExtended { /** * 配置本次移动的行为模式 * @param config 配置对象,未传入的字段保持当前值 @@ -268,10 +301,16 @@ export interface IHeroMover extends IObjectMover { getConfig(): Readonly; /** - * 设置地形通行判定器,传入 null 移除判定器 - * @param checker 地形判定器,null 表示移除 + * 设置地形通行判定器,传入 `null` 移表示移除 + * @param checker 地形判定器 */ useTerrainChecker(checker: ITerrainPassChecker | null): void; + + /** + * 设置勇士撞击行为的执行对象,传入 `null` 表示移除。 + * @param action 撞击行为对象 + */ + useHitAction(action: IHeroHitAction | null): void; } //#endregion diff --git a/packages-user/data-base/src/map/mover.ts b/packages-user/data-base/src/map/mover.ts index 8415a1a..a603865 100644 --- a/packages-user/data-base/src/map/mover.ts +++ b/packages-user/data-base/src/map/mover.ts @@ -3,7 +3,7 @@ import { getFaceMovement, ObjectMover, ObjectMoveStep, - ObjectMoveStepType + ObjectMoveType } from '@user/data-common'; import { IDynamicTile } from './types'; import { DYNAMIC_MOVER_FACE } from '../shared'; @@ -47,33 +47,33 @@ export class DynamicTileMover extends ObjectMover { y: tile.y }; switch (step.type) { - case ObjectMoveStepType.Dir: { + case ObjectMoveType.Dir: { const { x, y } = getFaceMovement(step.move); tile.setFaceDirection(step.move); locator.x += x; locator.y += y; break; } - case ObjectMoveStepType.DirFace: { + case ObjectMoveType.DirFace: { const { x, y } = getFaceMovement(step.move); tile.setFaceDirection(step.face); locator.x += x; locator.y += y; break; } - case ObjectMoveStepType.Face: { + case ObjectMoveType.Face: { tile.setFaceDirection(step.value); break; } - case ObjectMoveStepType.Special: { + case ObjectMoveType.Special: { const { x, y } = getFaceMovement(this.moveDirection); tile.setFaceDirection(this.faceDirection); locator.x += x; locator.y += y; break; } - case ObjectMoveStepType.Teleport: - case ObjectMoveStepType.Jump: { + case ObjectMoveType.Teleport: + case ObjectMoveType.Jump: { const { x, y, rel } = step; tile.setFaceDirection(this.faceDirection); if (rel) { diff --git a/packages-user/data-base/src/types.ts b/packages-user/data-base/src/types.ts index 5ac7cc1..781c5fa 100644 --- a/packages-user/data-base/src/types.ts +++ b/packages-user/data-base/src/types.ts @@ -40,7 +40,7 @@ export interface IStateBase extends IDataCommon { getSaveableContent(id: string): ISaveableContent | null; } -export interface IStateBaseExtended { +export interface IDataBaseExtended { /** 当前对象对应的数据层对象(Layer 1 对象) */ readonly state: IStateBase; } diff --git a/packages-user/data-common/src/common/mover.ts b/packages-user/data-common/src/common/mover.ts index 8b71412..65b8546 100644 --- a/packages-user/data-common/src/common/mover.ts +++ b/packages-user/data-common/src/common/mover.ts @@ -11,7 +11,7 @@ import { IFaceHandler } from './faceManager'; //#region 对象移动 -export const enum ObjectMoveStepType { +export const enum ObjectMoveType { /** 绝对方向步,同步更新移动方向与朝向 */ Dir, /** 绝对方向步,显式指定朝向 */ @@ -65,14 +65,14 @@ export interface IObjectMovable { export interface IObjectMoveStepDir { /** 步骤类型 */ - type: ObjectMoveStepType.Dir; + type: ObjectMoveType.Dir; /** 本步移动方向 */ move: FaceDirection; } export interface IObjectMoveStepDirFace { /** 步骤类型 */ - type: ObjectMoveStepType.DirFace; + type: ObjectMoveType.DirFace; /** 本步移动方向 */ move: FaceDirection; /** 本步显式朝向 */ @@ -81,35 +81,35 @@ export interface IObjectMoveStepDirFace { export interface IObjectMoveStepSpeed { /** 步骤类型 */ - type: ObjectMoveStepType.Speed; + type: ObjectMoveType.Speed; /** 后续移动的每格耗时,单位为 ms */ value: number; } export interface IObjectMoveStepFace { /** 步骤类型 */ - type: ObjectMoveStepType.Face; + type: ObjectMoveType.Face; /** 要设置的朝向 */ value: FaceDirection; } export interface IObjectMoveStepSpecial { /** 步骤类型 */ - type: ObjectMoveStepType.Special; + type: ObjectMoveType.Special; /** 特殊步方向 */ direction: ObjectSpecialStep; } export interface IObjectMoveAnimDir { /** 步骤类型 */ - type: ObjectMoveStepType.AnimDir; + type: ObjectMoveType.AnimDir; /** 动画播放方向 */ dir: ObjectAnimDirection; } export interface IObjectMoveTP { /** 步骤类型 */ - type: ObjectMoveStepType.Teleport; + type: ObjectMoveType.Teleport; /** 目标横坐标 */ x: number; /** 目标纵坐标 */ @@ -120,7 +120,7 @@ export interface IObjectMoveTP { export interface IObjectMoveJump { /** 步骤类型 */ - type: ObjectMoveStepType.Jump; + type: ObjectMoveType.Jump; /** 目标横坐标 */ x: number; /** 目标纵坐标 */ @@ -149,13 +149,13 @@ export interface IMoverController { * 向当前移动队列末尾追加步骤 * @param steps 要追加的步骤列表 */ - push(...steps: ObjectMoveStep[]): void; + push(...steps: Readonly[]): void; /** * 在当前步移动之后立刻插入指定步,顺序为参数传入的顺序 * @param steps 要插入的步骤列表 */ - insert(...steps: ObjectMoveStep[]): void; + insert(...steps: Readonly[]): void; /** * 停止当前移动,在当前步骤完成后兑现 @@ -187,7 +187,7 @@ export interface IObjectMoverHooks extends IHookBase { */ onStepStart?( code: number, - step: ObjectMoveStep, + step: Readonly, tile: T, mover: IObjectMover ): Promise; @@ -201,7 +201,7 @@ export interface IObjectMoverHooks extends IHookBase { */ onStepEnd?( code: number, - step: ObjectMoveStep, + step: Readonly, tile: T, mover: IObjectMover ): Promise; @@ -409,7 +409,7 @@ export abstract class ObjectMover * @param controller 移动控制器 */ protected abstract onStepStart( - step: ObjectMoveStep, + step: Readonly, tile: T, controller: Readonly ): Promise; @@ -423,7 +423,7 @@ export abstract class ObjectMover */ protected abstract onStepEnd( code: number, - step: ObjectMoveStep, + step: Readonly, tile: T, controller: Readonly ): Promise; @@ -451,20 +451,20 @@ export abstract class ObjectMover * 根据步骤内容预先同步移动器内部状态 * @param step 当前步骤 */ - private prepareStep(step: ObjectMoveStep): void { + private prepareStep(step: Readonly): void { switch (step.type) { - case ObjectMoveStepType.Dir: + case ObjectMoveType.Dir: this.moveDirection = step.move; this.faceDirection = step.move; break; - case ObjectMoveStepType.DirFace: + case ObjectMoveType.DirFace: this.moveDirection = step.move; this.faceDirection = step.face; break; - case ObjectMoveStepType.Face: + case ObjectMoveType.Face: this.faceDirection = step.value; break; - case ObjectMoveStepType.Special: { + case ObjectMoveType.Special: { const dir = this.getCurrentDirection(); if (step.direction === ObjectSpecialStep.Backward) { const opposite = this.faceHandler.opposite(dir); @@ -476,10 +476,10 @@ export abstract class ObjectMover } break; } - case ObjectMoveStepType.AnimDir: + case ObjectMoveType.AnimDir: this.currAnimDir = step.dir; break; - case ObjectMoveStepType.Speed: + case ObjectMoveType.Speed: this.currentSpeed = step.value; break; } @@ -508,7 +508,7 @@ export abstract class ObjectMover tp(x: number, y: number, rel: boolean = false): this { this.pushStep({ - type: ObjectMoveStepType.Teleport, + type: ObjectMoveType.Teleport, x, y, rel @@ -518,7 +518,7 @@ export abstract class ObjectMover jump(x: number, y: number, rel: boolean = false): this { this.pushStep({ - type: ObjectMoveStepType.Jump, + type: ObjectMoveType.Jump, x, y, rel @@ -529,7 +529,7 @@ export abstract class ObjectMover step(dir: FaceDirection, count: number = 1): this { for (let i = 0; i < count; i++) { this.pushStep({ - type: ObjectMoveStepType.Dir, + type: ObjectMoveType.Dir, move: dir }); } @@ -543,7 +543,7 @@ export abstract class ObjectMover ): this { for (let i = 0; i < count; i++) { this.pushStep({ - type: ObjectMoveStepType.DirFace, + type: ObjectMoveType.DirFace, move, face }); @@ -554,7 +554,7 @@ export abstract class ObjectMover forward(count: number = 1): this { for (let i = 0; i < count; i++) { this.pushStep({ - type: ObjectMoveStepType.Special, + type: ObjectMoveType.Special, direction: ObjectSpecialStep.Forward }); } @@ -564,7 +564,7 @@ export abstract class ObjectMover backward(count: number = 1): this { for (let i = 0; i < count; i++) { this.pushStep({ - type: ObjectMoveStepType.Special, + type: ObjectMoveType.Special, direction: ObjectSpecialStep.Backward }); } @@ -573,7 +573,7 @@ export abstract class ObjectMover speed(value: number): this { this.pushStep({ - type: ObjectMoveStepType.Speed, + type: ObjectMoveType.Speed, value }); return this; @@ -581,7 +581,7 @@ export abstract class ObjectMover face(dir: FaceDirection): this { this.pushStep({ - type: ObjectMoveStepType.Face, + type: ObjectMoveType.Face, value: dir }); return this; @@ -589,7 +589,7 @@ export abstract class ObjectMover animDir(dir: ObjectAnimDirection): this { this.pushStep({ - type: ObjectMoveStepType.AnimDir, + type: ObjectMoveType.AnimDir, dir }); return this; @@ -605,7 +605,7 @@ export abstract class ObjectMover * @param queue 移动队列 */ private async moveProgress( - queue: ObjectMoveStep[], + queue: Readonly[], controller: Readonly ) { // 移动开始 diff --git a/packages-user/data-common/src/common/utils.ts b/packages-user/data-common/src/common/utils.ts index eff640e..bd14769 100644 --- a/packages-user/data-common/src/common/utils.ts +++ b/packages-user/data-common/src/common/utils.ts @@ -1,6 +1,7 @@ import { FaceDirection } from './types'; /** + * @deprecated 用 `IFaceHandler` 接口 * 获取指定朝向的坐标偏移量 * @param dir 朝向 */ @@ -28,6 +29,7 @@ export function getFaceMovement(dir: FaceDirection): Loc { } /** + * @deprecated 用 `IFaceHandler` 接口 * 将八方向朝向降级为四方向朝向 * @param dir 朝向 * @param unknown 如果朝向是 `FaceDirection.Unknown`,那么会返回什么,默认值是未知 @@ -52,6 +54,7 @@ export function degradeFace( } /** + * @deprecated 用 `IFaceHandler` 接口 * 获取指定朝向旋转后的朝向 * @param dir 当前朝向 * @param anticlockwise 是否逆时针旋转,默认顺时针 @@ -155,6 +158,7 @@ export function nextFaceDirection( } /** + * @deprecated 用 `IFaceHandler` 接口 * 根据朝向字符串获取朝向枚举值 * @param dir 朝向字符串 */ diff --git a/packages-user/data-common/src/store/types.ts b/packages-user/data-common/src/store/types.ts index 3bb1e79..945ceee 100644 --- a/packages-user/data-common/src/store/types.ts +++ b/packages-user/data-common/src/store/types.ts @@ -52,6 +52,11 @@ export interface ITileRawData { readonly type: TileType; /** 图块的通行性对象 */ readonly pass: ITilePassData; + /** + * 事件可通行性,当为 `false` 时,玩家会通过撞击触发图块的触发器, + * 当为 `true` 时,玩家会通过走上去触发图块的触发器。类似于旧样板的 `noPass` + */ + readonly eventPass: boolean; } export interface ITileLegacyConverter { diff --git a/packages-user/data-state/src/hero/passChecker.ts b/packages-user/data-state/src/hero/passChecker.ts index 53bc0e3..fc2e9ab 100644 --- a/packages-user/data-state/src/hero/passChecker.ts +++ b/packages-user/data-state/src/hero/passChecker.ts @@ -1,73 +1,18 @@ -import { ITileLocator } from '@motajs/common'; import { FaceDirection, PassBit } from '@user/data-common'; -import { IHeroState, IStateBase, ITerrainPassChecker } from '@user/data-base'; +import { + IHeroState, + IMapStore, + IPassCheckerHandler, + ITerrainPassChecker +} from '@user/data-base'; import { isNil } from 'lodash-es'; export class DefaultPassChecker implements ITerrainPassChecker { constructor( - readonly state: IStateBase, + readonly maps: IMapStore, readonly hero: IHeroState ) {} - canPass( - locator: ITileLocator, - direction: FaceDirection, - floorId: string | undefined - ): boolean { - if (isNil(floorId)) return false; - if ( - direction === FaceDirection.LeftDown || - direction === FaceDirection.LeftUp || - direction === FaceDirection.RightDown || - direction === FaceDirection.RightUp - ) { - return true; - } - - const layerState = this.state.maps.getLayerState(floorId); - if (!layerState) return false; - const eventLayer = layerState.eventLayer; - if (!eventLayer) return false; - - const { x, y } = locator; - const face = this.hero.location.mover.faceHandler; - const { x: dx, y: dy } = face.movement(direction); - const nx = x + dx; - const ny = y + dy; - const opposite = face.opposite(direction); - const leaveMask = this.directionToPassBit(direction); - const enterMask = this.directionToPassBit(opposite); - - let canLeave = true; - let canEnter = true; - - const eventCurr = eventLayer.getLocationData(x, y); - const eventNext = eventLayer.getLocationData(nx, ny); - if (eventCurr && eventCurr.raw) { - canLeave = !!(leaveMask & eventCurr.raw.pass.outPass); - } - if (eventNext && eventNext.raw) { - canEnter = !!(enterMask & eventNext.raw.pass.inPass); - } - - if (!canLeave || !canEnter) return false; - - for (const layer of layerState.layerList) { - if (layer === eventLayer) continue; - const curr = layer.getLocationData(x, y); - const next = layer.getLocationData(nx, ny); - if (curr && curr.raw && curr.raw.pass.onlyEvents) { - canLeave = !!(leaveMask & curr.raw.pass.outPass); - } - if (next && next.raw && next.raw.pass.onlyEvents) { - canEnter = !!(enterMask & next.raw.pass.inPass); - } - if (!canLeave || !canEnter) return false; - } - - return true; - } - /** * 将朝向转换为对应的通行性位掩码。 * @param dir 朝向 @@ -86,4 +31,87 @@ export class DefaultPassChecker implements ITerrainPassChecker { return 0; } } + + inBound(x: number, y: number, floorId: string | undefined): boolean { + if (isNil(floorId)) return false; + const layerState = this.maps.getLayerState(floorId); + if (!layerState) return false; + const { width, height } = layerState; + return x >= 0 && y >= 0 && x < width && y < height; + } + + canPass(handler: IPassCheckerHandler): boolean { + const { currLoc, nextLoc, direction, floorId, face } = handler; + if (isNil(floorId)) return false; + + // 四角朝向直接判定为可通行 + if ( + direction === FaceDirection.LeftDown || + direction === FaceDirection.LeftUp || + direction === FaceDirection.RightDown || + direction === FaceDirection.RightUp + ) { + return true; + } + + const layerState = this.maps.getLayerState(floorId); + if (!layerState) return false; + const eventLayer = layerState.eventLayer; + if (!eventLayer) return false; + + const { x, y } = currLoc; + const { x: nx, y: ny } = nextLoc; + + const opposite = face.opposite(direction); + const leaveMask = this.directionToPassBit(direction); + const enterMask = this.directionToPassBit(opposite); + + let canLeave = true; + let canEnter = true; + + // 判断事件层 + const curr = eventLayer.getLocationData(x, y); + const next = eventLayer.getLocationData(nx, ny); + if (curr && curr.raw) { + canLeave = !!(leaveMask & curr.raw.pass.outPass); + } + if (next && next.raw) { + canEnter = !!(enterMask & next.raw.pass.inPass); + } + + if (!canLeave || !canEnter) return false; + + // 判断其他层 + for (const layer of layerState.layerList) { + if (layer === eventLayer) continue; + const curr = layer.getLocationData(x, y); + const next = layer.getLocationData(nx, ny); + let canLeave = true; + let canEnter = true; + if (curr && curr.raw && curr.raw.pass.onlyEvents) { + canLeave = !!(leaveMask & curr.raw.pass.outPass); + } + if (next && next.raw && next.raw.pass.onlyEvents) { + canEnter = !!(enterMask & next.raw.pass.inPass); + } + if (!canLeave || !canEnter) return false; + } + + return true; + } + + shouldHit(handler: IPassCheckerHandler): boolean { + const { nextLoc, floorId } = handler; + if (isNil(floorId)) return false; + const layerState = this.maps.getLayerState(floorId); + if (!layerState) return false; + const eventLayer = layerState.eventLayer; + if (!eventLayer) return false; + + const { x: nx, y: ny } = nextLoc; + + const next = eventLayer.getLocationData(nx, ny); + if (!next || !next.raw) return false; + return !next.raw.eventPass; + } } diff --git a/packages-user/data-state/src/legacy/tile.ts b/packages-user/data-state/src/legacy/tile.ts index 083294b..48131ab 100644 --- a/packages-user/data-state/src/legacy/tile.ts +++ b/packages-user/data-state/src/legacy/tile.ts @@ -36,25 +36,22 @@ export class TileLegacyBridge implements ITileLegacyConverter { private getPass(num: number, legacy: LegacyTileData): ITilePassData { if (num === 0) { + // 空图块 return { onlyEvents: true, inPass: 0b1111, outPass: 0b1111 }; } else if (num === 17) { + // 空气墙 return { onlyEvents: false, inPass: 0b0000, outPass: 0b0000 }; } else { - if (legacy.noPass) { - return { - onlyEvents: true, - inPass: 0b0000, - outPass: 0b1111 - }; - } else if (legacy.cannotIn && legacy.cannotOut) { + // 正常图块 + if (legacy.cannotIn && legacy.cannotOut) { let inPass = 0b1111; let outPass = 0b1111; if (legacy.cannotIn.includes('up')) { @@ -102,7 +99,8 @@ export class TileLegacyBridge implements ITileLegacyConverter { id: legacy.id, trigger: -1, type: this.getTileType(num, legacy), - pass: this.getPass(num, legacy) + pass: this.getPass(num, legacy), + eventPass: !legacy.noPass }; } } diff --git a/packages-user/data-system/src/combat/types.ts b/packages-user/data-system/src/combat/types.ts index 8ef1ecd..10a9cd3 100644 --- a/packages-user/data-system/src/combat/types.ts +++ b/packages-user/data-system/src/combat/types.ts @@ -5,14 +5,13 @@ import { ISpecial, IReadonlyHeroAttribute, IHeroAttribute, - IStateBase, - IStateBaseExtended + IDataBaseExtended } from '@user/data-base'; import { ILocationHelper } from '@user/data-common'; //#region 辅助接口 -export interface IEnemyHandler { +export interface IEnemyHandler extends IDataBaseExtended { /** 怪物属性信息 */ readonly enemy: IEnemy; /** 怪物上下文 */ @@ -21,11 +20,12 @@ export interface IEnemyHandler { readonly locator: ITileLocator; /** 勇士属性信息 */ readonly hero: IReadonlyHeroAttribute; - /** 当前全局状态对象 */ - readonly state: IStateBase; } -export interface IReadonlyEnemyHandler { +export interface IReadonlyEnemyHandler< + TEnemy, + THero +> extends IDataBaseExtended { /** 怪物属性信息 */ readonly enemy: IReadonlyEnemy; /** 怪物上下文 */ @@ -34,8 +34,6 @@ export interface IReadonlyEnemyHandler { readonly locator: ITileLocator; /** 勇士属性信息 */ readonly hero: IReadonlyHeroAttribute; - /** 当前全局状态对象 */ - readonly state: IStateBase; } //#endregion @@ -283,7 +281,7 @@ export interface IMapDamageReducer { ): Readonly; } -export interface IMapDamage extends IStateBaseExtended { +export interface IMapDamage extends IDataBaseExtended { /** 当前绑定的怪物上下文 */ readonly context: IEnemyContext; @@ -406,7 +404,7 @@ export interface IDamageCalculator { ): number; } -export interface IDamageContext extends IStateBaseExtended { +export interface IDamageContext extends IDataBaseExtended { /** * 获取战斗伤害信息 * @param enemy 怪物视图 @@ -499,7 +497,7 @@ export interface IDamageSystem extends IDamageContext< export interface IReadonlyEnemyContext< TEnemy, THero -> extends IStateBaseExtended { +> extends IDataBaseExtended { /** 怪物上下文宽度 */ readonly width: number; /** 怪物上下文高度 */ @@ -739,7 +737,7 @@ export interface IEnemyContext extends IReadonlyEnemyContext< //#region 战斗流程 -export interface ICombatFlowHandler { +export interface ICombatFlowHandler extends IDataBaseExtended { /** 战斗的怪物是否在地图上 */ readonly onMap: boolean; /** 可修改勇士对象 */ @@ -750,8 +748,6 @@ export interface ICombatFlowHandler { readonly context: IEnemyContext; /** 怪物位置 */ readonly locator: ITileLocator; - /** 全局状态对象 */ - readonly state: IStateBase; } export interface ICombatFlowHook extends IHookBase { @@ -803,7 +799,7 @@ export interface ICombatResult { } export interface ICombatFlow - extends IHookable>, IStateBaseExtended { + extends IHookable>, IDataBaseExtended { /** 勇士属性对象 */ readonly hero: IReadonlyHeroAttribute | null; /** 怪物上下文对象 */ diff --git a/packages-user/data-system/src/trigger/types.ts b/packages-user/data-system/src/trigger/types.ts index ee25119..8b11ec7 100644 --- a/packages-user/data-system/src/trigger/types.ts +++ b/packages-user/data-system/src/trigger/types.ts @@ -3,7 +3,7 @@ import { ILayerState, IMapLayer, IStateBase, - IStateBaseExtended + IDataBaseExtended } from '@user/data-base'; export interface ITriggerHandler { @@ -19,7 +19,7 @@ export interface ITriggerHandler { export type TriggerFactory = (type: number, state: IStateBase) => ITrigger; -export interface ITrigger extends IStateBaseExtended { +export interface ITrigger extends IDataBaseExtended { /** 触发器类型标识 */ readonly type: number; /** 触发器优先级 */ @@ -37,7 +37,7 @@ export interface ITrigger extends IStateBaseExtended { collection(): ITriggerCollection; } -export interface ITriggerRegistry extends IStateBaseExtended { +export interface ITriggerRegistry extends IDataBaseExtended { /** * 注册一个按类型创建的触发器工厂 * @param type 触发器类型 diff --git a/prompt.md b/prompt.md index 96d98bb..1a106a7 100644 --- a/prompt.md +++ b/prompt.md @@ -1,26 +1,32 @@ +# 核心规则 + +你的身份不是架构设计者,而是长期维护该项目的核心开发者。 + +该项目的架构已经经过大量设计与验证,你不需要深刻理解架构,当前架构已经是经过深思熟虑的结果。除非明确要求,否则不要试图重新设计架构,也不要主动优化已有设计。你的职责是在保持整体风格和架构稳定的前提下,完成需求并自然扩展已有系统。 + +1. **遵循最小修改原则**: + - 默认不要重新设计已有代码。 + - 默认不要重构已有实现。 + - 默认不要为了"更优雅"而改变已有模式。 + - 新代码应当尽可能像是在原有代码基础上的自然延续,而不是重新实现 +2. **现有代码最重要**。当前文件就是最高优先级规范。当 Prompt、已有代码、自身知识冲突时,优先遵循已有代码。 +3. **不要主动创造新的模式**。遵循已有的设计与代码风格,不要在不同风格之间来回跳跃。 +4. **优先优化维护成本,而不是代码长度**。允许局部重复,允许多几个变量,允许多几个 if。但绝不鼓励:为了减少几行代码增加抽象;为了减少重复增加函数跳转;为了"现代 TS"使用复杂语法。 +5. **当不确定时,选择最保守的方案**。如果两种方案都能实现需求,默认选择修改最少、风险最低、最接近已有实现的方案。 + # 必须规则 以下规则必须时刻遵守,任何情况下都不允许违反。 1. **不擅自修改已有内容**:将我已经写好的内容视为绝对正确。除非我**明确允许**,否则**不允许任何修改**,哪怕因为接口变化或其他原因导致其中出现类型错误。若认为我的代码存在错误,应在对话中提出,而不是直接修改。 2. **不恢复我的修改**:我做的任何代码修改都是有原因的。若我在两次对话期间新增、删除或修改了部分代码,不要将其恢复。 -3. **以目的驱动,而非以接口驱动**:实现前先想清楚我为什么要这样设计接口、这个接口设计的目的是什么,而不是单纯地以将接口填满为目标。 -4. **遇到歧义立即提问**:若思考或实现时遇到任何问题——例如描述模糊、接口不清晰、某些地方存在歧义等——应立即向我提问,而不是按自己的想法去写。 -5. **接口缺失时停止并提问**:若完成需求所需的接口尚不存在,应立即停止实现,向我提出疑问,而不是擅自新增接口来推进。 -6. **按我说的方式重构**:重构时**不要**擅自新增任何参数、方法或接口,**不要**仅通过新增兼容层应对重构。 - - **彻底性重构**(新旧接口完全没有重合):按正常方式全新实现,旧代码仅作逻辑参考。 - - **结构性重构**(新旧接口基本一致,细节有差距):将旧代码搬移到新接口上后进行微调。 -7. **不要有任何"顺手"的想法**:任何时候,都不要出现顺手的想法,包括但不限于发现了一个 bug 然后**顺手**修复、发现一处类型错误然后**顺手**修复等,这种情况下应当遵循规则 1。 -8. **修改文件前重读文件,修改后说明内容**:修改文件前务必重新阅读以确保内容是最新版。修改后必须在对话中说明所有改动的文件及具体改动内容。在修改文件时,**不得修改**文档的 `涉及文件` 节未提及的文件。 -9. **关于 `dev.md` 的强度升级**:`dev.md` 中"一般不建议/尽量避免"的条目在此处视为**绝对禁止**;"建议/最好"的条目在此处视为**必须遵守**。`as` 关键字不受此升级约束。 -10. **参数最小化 / 级联获取**:不从外部传入可通过已有引用获取的对象。若对象已持有对父对象或相关对象的引用(如 `controller`),应直接通过该引用获取所需数据(如 `controller.state`),不应再单独传递参数。即"非必要不传参"。 -11. **转换下沉**:数据的格式或类型转换(如 `string` 的 id 转 `number`)应放在最终消费该值的对象中完成,而非在调用方预先转换后再传入。这样可以减少调用方的职责并避免重复转换。 -12. **通过公有接口操作,不直接赋值内部属性**:对某个对象内部状态的修改,必须通过该对象对外暴露的公有方法进行(如用 `mover.face(dir).start()` 设置朝向并发起移动),而不是通过类型断言访问其内部属性直接赋值(如 `mover.faceDirection = dir`)。 -13. **map/filter 优于 for 循环**:对数组进行数据收集或转换时(如收集 Promise),使用 `map`、`filter` 等函数式写法,比 `for` 循环 + `push` 的可读性更高。 -14. **渲染端永远处于被动**:任何情况下都不可能会出现数据端主动通知渲染端进行更新的场景,渲染端仅通过钩子与数据端通信,通过钩子被动获取信息,并在某些情况下影响数据端行为。 -15. **按设计实现,勿自由发挥**:我已想好功能的基本思路和代码结构,你的任务是还原它,而非自行创造。 -16. **代码首先得看着舒服**:代码的第一读者是人,必须一眼就能看清整体结构,不需要反复来回读才能理顺逻辑。不出现过长的表达式,不进行不必要的函数拆分。不允许出现 3 层以上的 `if` 嵌套。 -17. **关于非空判断**:对于对象,直接使用 `if (!object)` 的方式进行判断,对于字面量,使用 `lodash` 接口 `isNil` 判断 `if (isNil(value))`,不要使用 `if (value === undefined)` 这种方式。 +3. **遇到歧义立即提问**:若思考或实现时遇到任何问题——例如描述模糊、接口不清晰、某些地方存在歧义等——应立即向我提问,而不是按自己的想法去写。若完成需求所需的接口尚不存在,也应当立即停止实现,向我提出疑问,而不是擅自新增接口来推进。 +4. **修改文件前重读文件,修改后说明内容**:修改文件前务必重新阅读以确保内容是最新版。修改后必须在对话中说明所有改动的文件及具体改动内容。在修改文件时,**不得修改**文档的 `涉及文件` 节未提及的文件。 +5. **关于 `dev.md` 的强度升级**:`dev.md` 中"一般不建议/尽量避免"的条目在此处视为**绝对禁止**;"建议/最好"的条目在此处视为**必须遵守**。 +6. **渲染端永远处于被动**:任何情况下都不可能会出现数据端主动通知渲染端进行更新的场景,渲染端仅通过钩子与数据端通信,通过钩子被动获取信息,并在某些情况下影响数据端行为。 +7. **关于非空判断**:对于对象,直接使用 `if (!object)` 的方式进行判断,对于字面量,使用 `lodash` 接口 `isNil` 判断 `if (isNil(value))`,不要使用 `if (value === undefined)` 这种方式。 +8. **方法顺序**:对于类的方法,根据功能进行排序,而不是按照修饰词进行排序。对于较长或功能较多的类,使用 `#region` 进行分区,分区按照方法功能进行划分。所有类的成员必须写到类的开头,并按照功能排序。 +9. **注释要求**:所有私有方法和私有成员必须添加 jsDoc 注释,受保护、公共成员与方法必须在源头处添加注释,继承而来的,除非功能或描述有变化,否则不得添加注释。即要求注释具有唯一性:已经在某些地方添加过注释的,不在其他地方添加。 # 建议规则