refactor(02-03): extract hero pass predicate

- Move passability and hit checks behind IPassPredicate
- Keep hero mover event dispatch and move codes unchanged
This commit is contained in:
unanmed 2026-09-09 21:06:07 +08:00
parent 3770e56c4d
commit 68a150882c
2 changed files with 80 additions and 52 deletions

View File

@ -180,9 +180,16 @@ export class HeroMover<T extends IHeroLocation>
if (this.ignoreTerrain) {
return HeroMoveCode.Step;
} else {
const canPass = this.topImpl.canPass(handler);
const passHandler = {
currLoc: handler.currLoc,
nextLoc: handler.nextLoc,
direction: handler.direction,
floorId: handler.floorId
};
const predicate = this.topImpl.predicate();
const canPass = predicate.canPass(passHandler);
if (canPass) {
const hit = this.topImpl.shouldHit(handler);
const hit = predicate.shouldHit(passHandler);
if (hit) return HeroMoveCode.Hit;
else return HeroMoveCode.Step;
} else {

View File

@ -7,6 +7,8 @@ import {
IHeroMoveTopHandler,
IHeroMoveTopImpl,
IMapState,
IPassCheckHandler,
IPassPredicate,
IReadonlyTileBase
} from '@user/data-base';
import { EventTrigger, FaceDirection, PassBit } from '@user/data-common';
@ -20,23 +22,11 @@ interface IEventSource {
readonly tile: IReadonlyTileBase | null;
}
export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
/** 地图存储对象 */
private readonly maps: IMapState;
/** 游戏事件执行器 */
private readonly executor: IGameEventExecutor;
interface IDefaultHeroPassPredicate extends IPassPredicate {}
constructor(state: IStateSystem) {
this.maps = state.maps;
this.executor = state.eventSystem.executor;
}
class DefaultHeroPassPredicate implements IDefaultHeroPassPredicate {
constructor(private readonly maps: IMapState) {}
//#region 通行性判断
/**
*
* @param dir
*/
private directionToPassBit(dir: FaceDirection): number {
switch (dir) {
case FaceDirection.Up:
@ -52,19 +42,33 @@ export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
}
}
inBound(x: number, y: number, floorId: string | undefined): boolean {
if (isNil(floorId)) return false;
const layerState = this.maps.getMap(floorId);
if (!layerState) return false;
const { width, height } = layerState;
return x >= 0 && y >= 0 && x < width && y < height;
private oppositeDirection(dir: FaceDirection): FaceDirection {
switch (dir) {
case FaceDirection.Up:
return FaceDirection.Down;
case FaceDirection.Right:
return FaceDirection.Left;
case FaceDirection.Down:
return FaceDirection.Up;
case FaceDirection.Left:
return FaceDirection.Right;
case FaceDirection.LeftUp:
return FaceDirection.RightDown;
case FaceDirection.RightUp:
return FaceDirection.LeftDown;
case FaceDirection.LeftDown:
return FaceDirection.RightUp;
case FaceDirection.RightDown:
return FaceDirection.LeftUp;
default:
return FaceDirection.Unknown;
}
}
canPass(handler: IHeroMoveTopHandler): boolean {
const { currLoc, nextLoc, direction, floorId, face } = handler;
canPass(handler: IPassCheckHandler): boolean {
const { currLoc, nextLoc, direction, floorId } = handler;
if (isNil(floorId)) return false;
// 四角朝向直接判定为可通行
if (
direction === FaceDirection.LeftDown ||
direction === FaceDirection.LeftUp ||
@ -81,37 +85,29 @@ export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
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);
const enterMask = this.directionToPassBit(
this.oppositeDirection(direction)
);
let canLeave = true;
let canEnter = true;
// 判断事件层
const curr = event.getLocationData(x, y);
const next = event.getLocationData(nx, ny);
const currRaw = curr?.static.raw();
const nextRaw = next?.static.raw();
if (currRaw) {
canLeave = !!(leaveMask & currRaw.pass.outPass);
}
if (nextRaw) {
canEnter = !!(enterMask & nextRaw.pass.inPass);
}
const currRaw = curr?.static?.raw();
const nextRaw = next?.static?.raw();
if (currRaw) canLeave = !!(leaveMask & currRaw.pass.outPass);
if (nextRaw) canEnter = !!(enterMask & nextRaw.pass.inPass);
if (!canLeave || !canEnter) return false;
// 判断其他层
for (const layer of map.layerList) {
if (layer === event) continue;
const curr = layer.getLocationData(x, y);
const next = layer.getLocationData(nx, ny);
let canLeave = true;
let canEnter = true;
const currRaw = curr?.static.raw();
const nextRaw = next?.static.raw();
const currRaw = curr?.static?.raw();
const nextRaw = next?.static?.raw();
if (currRaw?.pass.onlyEvents) {
canLeave = !!(leaveMask & currRaw.pass.outPass);
}
@ -124,20 +120,45 @@ export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
return true;
}
shouldHit(handler: IHeroMoveTopHandler): boolean {
shouldHit(handler: IPassCheckHandler): boolean {
const { nextLoc, floorId } = handler;
if (isNil(floorId)) return false;
const map = this.maps.getMap(floorId);
if (!map) return false;
const eventLayer = map.eventLayer;
if (!eventLayer) return false;
const next = eventLayer.getLocationData(nextLoc.x, nextLoc.y);
const nextRaw = next?.static?.raw();
return !!nextRaw && !nextRaw.eventPass;
}
}
export class DefaultHeroMoveTopImpl implements IHeroMoveTopImpl {
/** 地图存储对象 */
private readonly maps: IMapState;
/** 游戏事件执行器 */
private readonly executor: IGameEventExecutor;
/** 勇士移动使用的通行性谓词 */
private readonly passPredicate: IPassPredicate;
constructor(state: IStateSystem) {
this.maps = state.maps;
this.executor = state.eventSystem.executor;
this.passPredicate = new DefaultHeroPassPredicate(this.maps);
}
//#region 通行性判断
inBound(x: number, y: number, floorId: string | undefined): boolean {
if (isNil(floorId)) return false;
const layerState = this.maps.getMap(floorId);
if (!layerState) return false;
const eventLayer = layerState.eventLayer;
if (!eventLayer) return false;
const { width, height } = layerState;
return x >= 0 && y >= 0 && x < width && y < height;
}
const { x: nx, y: ny } = nextLoc;
const next = eventLayer.getLocationData(nx, ny);
const nextRaw = next?.static.raw();
if (!nextRaw) return false;
return !nextRaw.eventPass;
predicate(): IPassPredicate {
return this.passPredicate;
}
//#endregion