mirror of
https://github.com/motajs/template.git
synced 2026-07-17 01:11:09 +08:00
Compare commits
2 Commits
0508dfb9e4
...
1ce0283743
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ce0283743 | |||
| c4bcecdf8a |
@ -1,18 +1,21 @@
|
|||||||
import { ITileLocator } from '@motajs/common';
|
import { ITileLocator } from '@motajs/common';
|
||||||
import {
|
import {
|
||||||
FaceDirection,
|
FaceDirection,
|
||||||
getFaceMovement,
|
|
||||||
IFaceHandler,
|
IFaceHandler,
|
||||||
IMoverController,
|
IMoverController,
|
||||||
ObjectMoveStep,
|
ObjectMoveStep,
|
||||||
ObjectMover,
|
ObjectMover,
|
||||||
ObjectMoveStepType
|
ObjectMoveType,
|
||||||
|
IDataCommon
|
||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
import {
|
import {
|
||||||
HeroMoveCode,
|
HeroMoveCode,
|
||||||
|
IHeroHitAction,
|
||||||
|
IHeroHitHandler,
|
||||||
IHeroLocation,
|
IHeroLocation,
|
||||||
IHeroMover,
|
IHeroMover,
|
||||||
IHeroMoverConfig,
|
IHeroMoverConfig,
|
||||||
|
IPassCheckerHandler,
|
||||||
ITerrainPassChecker
|
ITerrainPassChecker
|
||||||
} from './types';
|
} from './types';
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
@ -21,20 +24,26 @@ export class HeroMover<T extends IHeroLocation>
|
|||||||
extends ObjectMover<T>
|
extends ObjectMover<T>
|
||||||
implements IHeroMover<T>
|
implements IHeroMover<T>
|
||||||
{
|
{
|
||||||
|
readonly state: IDataCommon;
|
||||||
|
|
||||||
/** 本次移动是否不记录进路线系统 */
|
/** 本次移动是否不记录进路线系统 */
|
||||||
private noRoute: boolean = false;
|
private noRoute: boolean = false;
|
||||||
/** 本次移动是否忽略地形碰撞检测 */
|
/** 本次移动是否忽略地形碰撞检测 */
|
||||||
private ignoreTerrain: boolean = false;
|
private ignoreTerrain: boolean = false;
|
||||||
/** 本次移动是否在特定时机触发自动存档 */
|
/** 本次移动是否在特定时机触发自动存档 */
|
||||||
private autoSave: boolean = false;
|
private autoSave: boolean = false;
|
||||||
/** 自定义地形通行判定器 */
|
|
||||||
|
/** 地形通行判定器 */
|
||||||
private terrainChecker: ITerrainPassChecker | null = null;
|
private terrainChecker: ITerrainPassChecker | null = null;
|
||||||
|
/** 撞击行为对象 */
|
||||||
|
private hitAction: IHeroHitAction | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly tile: T,
|
readonly tile: T,
|
||||||
faceHandler: IFaceHandler<FaceDirection>
|
faceHandler: IFaceHandler<FaceDirection>
|
||||||
) {
|
) {
|
||||||
super(faceHandler);
|
super(faceHandler);
|
||||||
|
this.state = tile.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
config(config: Readonly<IHeroMoverConfig>): this {
|
config(config: Readonly<IHeroMoverConfig>): this {
|
||||||
@ -62,38 +71,108 @@ export class HeroMover<T extends IHeroLocation>
|
|||||||
this.terrainChecker = checker;
|
this.terrainChecker = checker;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async onMoveStart(
|
useHitAction(action: IHeroHitAction | null): void {
|
||||||
_tile: IHeroLocation,
|
this.hitAction = action;
|
||||||
_controller: Readonly<IMoverController>
|
}
|
||||||
): Promise<void> {}
|
|
||||||
|
|
||||||
protected async onMoveEnd(
|
/**
|
||||||
_tile: IHeroLocation,
|
* 创建通行性检查信息对象
|
||||||
_controller: Readonly<IMoverController>
|
* @param curr 勇士所在位置
|
||||||
): Promise<void> {}
|
* @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<void> {}
|
||||||
|
|
||||||
|
protected async onMoveEnd(): Promise<void> {}
|
||||||
|
|
||||||
protected async onStepStart(
|
protected async onStepStart(
|
||||||
step: ObjectMoveStep,
|
step: Readonly<ObjectMoveStep>,
|
||||||
tile: IHeroLocation,
|
tile: IHeroLocation
|
||||||
controller: Readonly<IMoverController>
|
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
|
if (!this.terrainChecker) return HeroMoveCode.CannotMove;
|
||||||
|
|
||||||
const type = step.type;
|
const type = step.type;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
type !== ObjectMoveStepType.Dir &&
|
type === ObjectMoveType.Dir ||
|
||||||
type !== ObjectMoveStepType.DirFace &&
|
type === ObjectMoveType.DirFace ||
|
||||||
type !== ObjectMoveStepType.Special
|
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 (type === ObjectMoveType.Jump || type === ObjectMoveType.Teleport) {
|
||||||
if (result !== HeroMoveCode.Step) return result;
|
const nx = step.rel ? tile.x + step.x : step.x;
|
||||||
}
|
const ny = step.rel ? tile.y + step.y : step.y;
|
||||||
|
if (this.ignoreTerrain) {
|
||||||
if (this.autoSave) {
|
return HeroMoveCode.Step;
|
||||||
// TODO: 检查当前步是否需要触发自动存档(如进入/离开地图伤害区域)
|
} else {
|
||||||
|
if (this.terrainChecker.inBound(nx, ny, tile.floorId)) {
|
||||||
|
return HeroMoveCode.Step;
|
||||||
|
} else {
|
||||||
|
return HeroMoveCode.Stop;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return HeroMoveCode.Step;
|
return HeroMoveCode.Step;
|
||||||
@ -101,89 +180,60 @@ export class HeroMover<T extends IHeroLocation>
|
|||||||
|
|
||||||
protected async onStepEnd(
|
protected async onStepEnd(
|
||||||
code: number,
|
code: number,
|
||||||
step: ObjectMoveStep,
|
step: Readonly<ObjectMoveStep>,
|
||||||
tile: IHeroLocation,
|
tile: IHeroLocation,
|
||||||
controller: Readonly<IMoverController>
|
controller: Readonly<IMoverController>
|
||||||
): Promise<ITileLocator> {
|
): Promise<ITileLocator> {
|
||||||
const type = step.type;
|
const type = step.type;
|
||||||
|
|
||||||
const isMovementStep =
|
// 不能移动或停止
|
||||||
type === ObjectMoveStepType.Dir ||
|
if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Stop) {
|
||||||
type === ObjectMoveStepType.DirFace ||
|
// 这里不能 await,因为其 Promise 会在当前步结束后兑现,如果 await 就会卡住
|
||||||
type === ObjectMoveStepType.Special;
|
controller.stop();
|
||||||
|
|
||||||
if (
|
|
||||||
!isMovementStep &&
|
|
||||||
type !== ObjectMoveStepType.Teleport &&
|
|
||||||
type !== ObjectMoveStepType.Jump
|
|
||||||
) {
|
|
||||||
return { x: tile.x, y: tile.y };
|
return { x: tile.x, y: tile.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Hit) {
|
// 撞击时也使用当前位置,同时处理撞击行为
|
||||||
void controller.stop();
|
if (code === HeroMoveCode.Hit) {
|
||||||
// TODO: 处理撞击图块逻辑
|
// 执行撞击行为
|
||||||
|
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 };
|
return { x: tile.x, y: tile.y };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code === HeroMoveCode.Stop) {
|
// 正常移动
|
||||||
void controller.stop();
|
if (code === HeroMoveCode.Step) {
|
||||||
// TODO: 清除自动寻路
|
if (
|
||||||
return { x: tile.x, y: tile.y };
|
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 { x: tile.x, y: tile.y };
|
||||||
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<IMoverController>
|
|
||||||
): 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
FaceDirection,
|
FaceDirection,
|
||||||
IDataCommonExtended,
|
IDataCommonExtended,
|
||||||
|
IFaceHandler,
|
||||||
IObjectMovable,
|
IObjectMovable,
|
||||||
IObjectMover,
|
IObjectMover,
|
||||||
ISaveableContent
|
ISaveableContent
|
||||||
@ -211,7 +212,7 @@ export interface IHeroLocation
|
|||||||
/** 当前所在楼层 id,undefined 表示尚不处于任何楼层 */
|
/** 当前所在楼层 id,undefined 表示尚不处于任何楼层 */
|
||||||
readonly floorId: string | undefined;
|
readonly floorId: string | undefined;
|
||||||
/** 勇士的移动对象 */
|
/** 勇士的移动对象 */
|
||||||
readonly mover: IObjectMover<this>;
|
readonly mover: IHeroMover<this>;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -229,21 +230,52 @@ export const enum HeroMoveCode {
|
|||||||
CannotMove
|
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<FaceDirection>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHeroHitHandler extends IHeroMoveHandlerBase {}
|
||||||
|
|
||||||
export interface ITerrainPassChecker {
|
export interface ITerrainPassChecker {
|
||||||
/** 对应的勇士状态对象 */
|
/**
|
||||||
readonly hero: IHeroState<unknown>;
|
* 检查目标位置是否在地图范围内
|
||||||
|
* @param x 横坐标
|
||||||
|
* @param y 纵坐标
|
||||||
|
* @param floorId 楼层 id
|
||||||
|
*/
|
||||||
|
inBound(x: number, y: number, floorId: string | undefined): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断在指定楼层中,从指定坐标向指定方向移动一格是否可通行
|
* 判断在指定楼层中,从指定坐标向指定方向移动一格是否可通行
|
||||||
* @param locator 当前位置
|
* @param handler 通行性检查对象
|
||||||
* @param direction 移动方向
|
|
||||||
* @param floorId 当前楼层 id,可能为 undefined
|
|
||||||
*/
|
*/
|
||||||
canPass(
|
canPass(handler: IPassCheckerHandler): boolean;
|
||||||
locator: ITileLocator,
|
|
||||||
direction: FaceDirection,
|
/**
|
||||||
floorId: string | undefined
|
* 判断在指定楼层中,从指定坐标向指定方向移动时是否应该产生撞击,撞击将会触发目标位置的触发器
|
||||||
): boolean;
|
* @param handler 通行性检查对象
|
||||||
|
*/
|
||||||
|
shouldHit(handler: IPassCheckerHandler): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHeroHitAction {
|
||||||
|
/**
|
||||||
|
* 勇士撞击某一个图块时执行的内容,一般用于触发目标位置的触发器
|
||||||
|
* @param handler 撞击行为对象
|
||||||
|
*/
|
||||||
|
hit(handler: IHeroHitHandler): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHeroMoverConfig {
|
export interface IHeroMoverConfig {
|
||||||
@ -255,7 +287,8 @@ export interface IHeroMoverConfig {
|
|||||||
autoSave?: boolean;
|
autoSave?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHeroMover<T extends IHeroLocation> extends IObjectMover<T> {
|
export interface IHeroMover<T extends IHeroLocation>
|
||||||
|
extends IObjectMover<T>, IDataCommonExtended {
|
||||||
/**
|
/**
|
||||||
* 配置本次移动的行为模式
|
* 配置本次移动的行为模式
|
||||||
* @param config 配置对象,未传入的字段保持当前值
|
* @param config 配置对象,未传入的字段保持当前值
|
||||||
@ -268,10 +301,16 @@ export interface IHeroMover<T extends IHeroLocation> extends IObjectMover<T> {
|
|||||||
getConfig(): Readonly<IHeroMoverConfig>;
|
getConfig(): Readonly<IHeroMoverConfig>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置地形通行判定器,传入 null 移除判定器
|
* 设置地形通行判定器,传入 `null` 移表示移除
|
||||||
* @param checker 地形判定器,null 表示移除
|
* @param checker 地形判定器
|
||||||
*/
|
*/
|
||||||
useTerrainChecker(checker: ITerrainPassChecker | null): void;
|
useTerrainChecker(checker: ITerrainPassChecker | null): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置勇士撞击行为的执行对象,传入 `null` 表示移除。
|
||||||
|
* @param action 撞击行为对象
|
||||||
|
*/
|
||||||
|
useHitAction(action: IHeroHitAction | null): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {
|
|||||||
getFaceMovement,
|
getFaceMovement,
|
||||||
ObjectMover,
|
ObjectMover,
|
||||||
ObjectMoveStep,
|
ObjectMoveStep,
|
||||||
ObjectMoveStepType
|
ObjectMoveType
|
||||||
} from '@user/data-common';
|
} from '@user/data-common';
|
||||||
import { IDynamicTile } from './types';
|
import { IDynamicTile } from './types';
|
||||||
import { DYNAMIC_MOVER_FACE } from '../shared';
|
import { DYNAMIC_MOVER_FACE } from '../shared';
|
||||||
@ -47,33 +47,33 @@ export class DynamicTileMover extends ObjectMover<IDynamicTile> {
|
|||||||
y: tile.y
|
y: tile.y
|
||||||
};
|
};
|
||||||
switch (step.type) {
|
switch (step.type) {
|
||||||
case ObjectMoveStepType.Dir: {
|
case ObjectMoveType.Dir: {
|
||||||
const { x, y } = getFaceMovement(step.move);
|
const { x, y } = getFaceMovement(step.move);
|
||||||
tile.setFaceDirection(step.move);
|
tile.setFaceDirection(step.move);
|
||||||
locator.x += x;
|
locator.x += x;
|
||||||
locator.y += y;
|
locator.y += y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObjectMoveStepType.DirFace: {
|
case ObjectMoveType.DirFace: {
|
||||||
const { x, y } = getFaceMovement(step.move);
|
const { x, y } = getFaceMovement(step.move);
|
||||||
tile.setFaceDirection(step.face);
|
tile.setFaceDirection(step.face);
|
||||||
locator.x += x;
|
locator.x += x;
|
||||||
locator.y += y;
|
locator.y += y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObjectMoveStepType.Face: {
|
case ObjectMoveType.Face: {
|
||||||
tile.setFaceDirection(step.value);
|
tile.setFaceDirection(step.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObjectMoveStepType.Special: {
|
case ObjectMoveType.Special: {
|
||||||
const { x, y } = getFaceMovement(this.moveDirection);
|
const { x, y } = getFaceMovement(this.moveDirection);
|
||||||
tile.setFaceDirection(this.faceDirection);
|
tile.setFaceDirection(this.faceDirection);
|
||||||
locator.x += x;
|
locator.x += x;
|
||||||
locator.y += y;
|
locator.y += y;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObjectMoveStepType.Teleport:
|
case ObjectMoveType.Teleport:
|
||||||
case ObjectMoveStepType.Jump: {
|
case ObjectMoveType.Jump: {
|
||||||
const { x, y, rel } = step;
|
const { x, y, rel } = step;
|
||||||
tile.setFaceDirection(this.faceDirection);
|
tile.setFaceDirection(this.faceDirection);
|
||||||
if (rel) {
|
if (rel) {
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export interface IStateBase extends IDataCommon {
|
|||||||
getSaveableContent<T>(id: string): ISaveableContent<T> | null;
|
getSaveableContent<T>(id: string): ISaveableContent<T> | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IStateBaseExtended {
|
export interface IDataBaseExtended {
|
||||||
/** 当前对象对应的数据层对象(Layer 1 对象) */
|
/** 当前对象对应的数据层对象(Layer 1 对象) */
|
||||||
readonly state: IStateBase;
|
readonly state: IStateBase;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { IFaceHandler } from './faceManager';
|
|||||||
|
|
||||||
//#region 对象移动
|
//#region 对象移动
|
||||||
|
|
||||||
export const enum ObjectMoveStepType {
|
export const enum ObjectMoveType {
|
||||||
/** 绝对方向步,同步更新移动方向与朝向 */
|
/** 绝对方向步,同步更新移动方向与朝向 */
|
||||||
Dir,
|
Dir,
|
||||||
/** 绝对方向步,显式指定朝向 */
|
/** 绝对方向步,显式指定朝向 */
|
||||||
@ -65,14 +65,14 @@ export interface IObjectMovable {
|
|||||||
|
|
||||||
export interface IObjectMoveStepDir {
|
export interface IObjectMoveStepDir {
|
||||||
/** 步骤类型 */
|
/** 步骤类型 */
|
||||||
type: ObjectMoveStepType.Dir;
|
type: ObjectMoveType.Dir;
|
||||||
/** 本步移动方向 */
|
/** 本步移动方向 */
|
||||||
move: FaceDirection;
|
move: FaceDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IObjectMoveStepDirFace {
|
export interface IObjectMoveStepDirFace {
|
||||||
/** 步骤类型 */
|
/** 步骤类型 */
|
||||||
type: ObjectMoveStepType.DirFace;
|
type: ObjectMoveType.DirFace;
|
||||||
/** 本步移动方向 */
|
/** 本步移动方向 */
|
||||||
move: FaceDirection;
|
move: FaceDirection;
|
||||||
/** 本步显式朝向 */
|
/** 本步显式朝向 */
|
||||||
@ -81,35 +81,35 @@ export interface IObjectMoveStepDirFace {
|
|||||||
|
|
||||||
export interface IObjectMoveStepSpeed {
|
export interface IObjectMoveStepSpeed {
|
||||||
/** 步骤类型 */
|
/** 步骤类型 */
|
||||||
type: ObjectMoveStepType.Speed;
|
type: ObjectMoveType.Speed;
|
||||||
/** 后续移动的每格耗时,单位为 ms */
|
/** 后续移动的每格耗时,单位为 ms */
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IObjectMoveStepFace {
|
export interface IObjectMoveStepFace {
|
||||||
/** 步骤类型 */
|
/** 步骤类型 */
|
||||||
type: ObjectMoveStepType.Face;
|
type: ObjectMoveType.Face;
|
||||||
/** 要设置的朝向 */
|
/** 要设置的朝向 */
|
||||||
value: FaceDirection;
|
value: FaceDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IObjectMoveStepSpecial {
|
export interface IObjectMoveStepSpecial {
|
||||||
/** 步骤类型 */
|
/** 步骤类型 */
|
||||||
type: ObjectMoveStepType.Special;
|
type: ObjectMoveType.Special;
|
||||||
/** 特殊步方向 */
|
/** 特殊步方向 */
|
||||||
direction: ObjectSpecialStep;
|
direction: ObjectSpecialStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IObjectMoveAnimDir {
|
export interface IObjectMoveAnimDir {
|
||||||
/** 步骤类型 */
|
/** 步骤类型 */
|
||||||
type: ObjectMoveStepType.AnimDir;
|
type: ObjectMoveType.AnimDir;
|
||||||
/** 动画播放方向 */
|
/** 动画播放方向 */
|
||||||
dir: ObjectAnimDirection;
|
dir: ObjectAnimDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IObjectMoveTP {
|
export interface IObjectMoveTP {
|
||||||
/** 步骤类型 */
|
/** 步骤类型 */
|
||||||
type: ObjectMoveStepType.Teleport;
|
type: ObjectMoveType.Teleport;
|
||||||
/** 目标横坐标 */
|
/** 目标横坐标 */
|
||||||
x: number;
|
x: number;
|
||||||
/** 目标纵坐标 */
|
/** 目标纵坐标 */
|
||||||
@ -120,7 +120,7 @@ export interface IObjectMoveTP {
|
|||||||
|
|
||||||
export interface IObjectMoveJump {
|
export interface IObjectMoveJump {
|
||||||
/** 步骤类型 */
|
/** 步骤类型 */
|
||||||
type: ObjectMoveStepType.Jump;
|
type: ObjectMoveType.Jump;
|
||||||
/** 目标横坐标 */
|
/** 目标横坐标 */
|
||||||
x: number;
|
x: number;
|
||||||
/** 目标纵坐标 */
|
/** 目标纵坐标 */
|
||||||
@ -149,13 +149,13 @@ export interface IMoverController {
|
|||||||
* 向当前移动队列末尾追加步骤
|
* 向当前移动队列末尾追加步骤
|
||||||
* @param steps 要追加的步骤列表
|
* @param steps 要追加的步骤列表
|
||||||
*/
|
*/
|
||||||
push(...steps: ObjectMoveStep[]): void;
|
push(...steps: Readonly<ObjectMoveStep>[]): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在当前步移动之后立刻插入指定步,顺序为参数传入的顺序
|
* 在当前步移动之后立刻插入指定步,顺序为参数传入的顺序
|
||||||
* @param steps 要插入的步骤列表
|
* @param steps 要插入的步骤列表
|
||||||
*/
|
*/
|
||||||
insert(...steps: ObjectMoveStep[]): void;
|
insert(...steps: Readonly<ObjectMoveStep>[]): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止当前移动,在当前步骤完成后兑现
|
* 停止当前移动,在当前步骤完成后兑现
|
||||||
@ -187,7 +187,7 @@ export interface IObjectMoverHooks<T extends IObjectMovable> extends IHookBase {
|
|||||||
*/
|
*/
|
||||||
onStepStart?(
|
onStepStart?(
|
||||||
code: number,
|
code: number,
|
||||||
step: ObjectMoveStep,
|
step: Readonly<ObjectMoveStep>,
|
||||||
tile: T,
|
tile: T,
|
||||||
mover: IObjectMover<T>
|
mover: IObjectMover<T>
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
@ -201,7 +201,7 @@ export interface IObjectMoverHooks<T extends IObjectMovable> extends IHookBase {
|
|||||||
*/
|
*/
|
||||||
onStepEnd?(
|
onStepEnd?(
|
||||||
code: number,
|
code: number,
|
||||||
step: ObjectMoveStep,
|
step: Readonly<ObjectMoveStep>,
|
||||||
tile: T,
|
tile: T,
|
||||||
mover: IObjectMover<T>
|
mover: IObjectMover<T>
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
@ -409,7 +409,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
* @param controller 移动控制器
|
* @param controller 移动控制器
|
||||||
*/
|
*/
|
||||||
protected abstract onStepStart(
|
protected abstract onStepStart(
|
||||||
step: ObjectMoveStep,
|
step: Readonly<ObjectMoveStep>,
|
||||||
tile: T,
|
tile: T,
|
||||||
controller: Readonly<IMoverController>
|
controller: Readonly<IMoverController>
|
||||||
): Promise<number>;
|
): Promise<number>;
|
||||||
@ -423,7 +423,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
*/
|
*/
|
||||||
protected abstract onStepEnd(
|
protected abstract onStepEnd(
|
||||||
code: number,
|
code: number,
|
||||||
step: ObjectMoveStep,
|
step: Readonly<ObjectMoveStep>,
|
||||||
tile: T,
|
tile: T,
|
||||||
controller: Readonly<IMoverController>
|
controller: Readonly<IMoverController>
|
||||||
): Promise<ITileLocator>;
|
): Promise<ITileLocator>;
|
||||||
@ -451,20 +451,20 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
* 根据步骤内容预先同步移动器内部状态
|
* 根据步骤内容预先同步移动器内部状态
|
||||||
* @param step 当前步骤
|
* @param step 当前步骤
|
||||||
*/
|
*/
|
||||||
private prepareStep(step: ObjectMoveStep): void {
|
private prepareStep(step: Readonly<ObjectMoveStep>): void {
|
||||||
switch (step.type) {
|
switch (step.type) {
|
||||||
case ObjectMoveStepType.Dir:
|
case ObjectMoveType.Dir:
|
||||||
this.moveDirection = step.move;
|
this.moveDirection = step.move;
|
||||||
this.faceDirection = step.move;
|
this.faceDirection = step.move;
|
||||||
break;
|
break;
|
||||||
case ObjectMoveStepType.DirFace:
|
case ObjectMoveType.DirFace:
|
||||||
this.moveDirection = step.move;
|
this.moveDirection = step.move;
|
||||||
this.faceDirection = step.face;
|
this.faceDirection = step.face;
|
||||||
break;
|
break;
|
||||||
case ObjectMoveStepType.Face:
|
case ObjectMoveType.Face:
|
||||||
this.faceDirection = step.value;
|
this.faceDirection = step.value;
|
||||||
break;
|
break;
|
||||||
case ObjectMoveStepType.Special: {
|
case ObjectMoveType.Special: {
|
||||||
const dir = this.getCurrentDirection();
|
const dir = this.getCurrentDirection();
|
||||||
if (step.direction === ObjectSpecialStep.Backward) {
|
if (step.direction === ObjectSpecialStep.Backward) {
|
||||||
const opposite = this.faceHandler.opposite(dir);
|
const opposite = this.faceHandler.opposite(dir);
|
||||||
@ -476,10 +476,10 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObjectMoveStepType.AnimDir:
|
case ObjectMoveType.AnimDir:
|
||||||
this.currAnimDir = step.dir;
|
this.currAnimDir = step.dir;
|
||||||
break;
|
break;
|
||||||
case ObjectMoveStepType.Speed:
|
case ObjectMoveType.Speed:
|
||||||
this.currentSpeed = step.value;
|
this.currentSpeed = step.value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -508,7 +508,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
|
|
||||||
tp(x: number, y: number, rel: boolean = false): this {
|
tp(x: number, y: number, rel: boolean = false): this {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.Teleport,
|
type: ObjectMoveType.Teleport,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
rel
|
rel
|
||||||
@ -518,7 +518,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
|
|
||||||
jump(x: number, y: number, rel: boolean = false): this {
|
jump(x: number, y: number, rel: boolean = false): this {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.Jump,
|
type: ObjectMoveType.Jump,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
rel
|
rel
|
||||||
@ -529,7 +529,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
step(dir: FaceDirection, count: number = 1): this {
|
step(dir: FaceDirection, count: number = 1): this {
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.Dir,
|
type: ObjectMoveType.Dir,
|
||||||
move: dir
|
move: dir
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -543,7 +543,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
): this {
|
): this {
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.DirFace,
|
type: ObjectMoveType.DirFace,
|
||||||
move,
|
move,
|
||||||
face
|
face
|
||||||
});
|
});
|
||||||
@ -554,7 +554,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
forward(count: number = 1): this {
|
forward(count: number = 1): this {
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.Special,
|
type: ObjectMoveType.Special,
|
||||||
direction: ObjectSpecialStep.Forward
|
direction: ObjectSpecialStep.Forward
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -564,7 +564,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
backward(count: number = 1): this {
|
backward(count: number = 1): this {
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.Special,
|
type: ObjectMoveType.Special,
|
||||||
direction: ObjectSpecialStep.Backward
|
direction: ObjectSpecialStep.Backward
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -573,7 +573,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
|
|
||||||
speed(value: number): this {
|
speed(value: number): this {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.Speed,
|
type: ObjectMoveType.Speed,
|
||||||
value
|
value
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
@ -581,7 +581,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
|
|
||||||
face(dir: FaceDirection): this {
|
face(dir: FaceDirection): this {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.Face,
|
type: ObjectMoveType.Face,
|
||||||
value: dir
|
value: dir
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
@ -589,7 +589,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
|
|
||||||
animDir(dir: ObjectAnimDirection): this {
|
animDir(dir: ObjectAnimDirection): this {
|
||||||
this.pushStep({
|
this.pushStep({
|
||||||
type: ObjectMoveStepType.AnimDir,
|
type: ObjectMoveType.AnimDir,
|
||||||
dir
|
dir
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
@ -605,7 +605,7 @@ export abstract class ObjectMover<T extends IObjectMovable>
|
|||||||
* @param queue 移动队列
|
* @param queue 移动队列
|
||||||
*/
|
*/
|
||||||
private async moveProgress(
|
private async moveProgress(
|
||||||
queue: ObjectMoveStep[],
|
queue: Readonly<ObjectMoveStep>[],
|
||||||
controller: Readonly<IMoverController>
|
controller: Readonly<IMoverController>
|
||||||
) {
|
) {
|
||||||
// 移动开始
|
// 移动开始
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { FaceDirection } from './types';
|
import { FaceDirection } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated 用 `IFaceHandler` 接口
|
||||||
* 获取指定朝向的坐标偏移量
|
* 获取指定朝向的坐标偏移量
|
||||||
* @param dir 朝向
|
* @param dir 朝向
|
||||||
*/
|
*/
|
||||||
@ -28,6 +29,7 @@ export function getFaceMovement(dir: FaceDirection): Loc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated 用 `IFaceHandler` 接口
|
||||||
* 将八方向朝向降级为四方向朝向
|
* 将八方向朝向降级为四方向朝向
|
||||||
* @param dir 朝向
|
* @param dir 朝向
|
||||||
* @param unknown 如果朝向是 `FaceDirection.Unknown`,那么会返回什么,默认值是未知
|
* @param unknown 如果朝向是 `FaceDirection.Unknown`,那么会返回什么,默认值是未知
|
||||||
@ -52,6 +54,7 @@ export function degradeFace(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated 用 `IFaceHandler` 接口
|
||||||
* 获取指定朝向旋转后的朝向
|
* 获取指定朝向旋转后的朝向
|
||||||
* @param dir 当前朝向
|
* @param dir 当前朝向
|
||||||
* @param anticlockwise 是否逆时针旋转,默认顺时针
|
* @param anticlockwise 是否逆时针旋转,默认顺时针
|
||||||
@ -155,6 +158,7 @@ export function nextFaceDirection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated 用 `IFaceHandler` 接口
|
||||||
* 根据朝向字符串获取朝向枚举值
|
* 根据朝向字符串获取朝向枚举值
|
||||||
* @param dir 朝向字符串
|
* @param dir 朝向字符串
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -52,6 +52,11 @@ export interface ITileRawData {
|
|||||||
readonly type: TileType;
|
readonly type: TileType;
|
||||||
/** 图块的通行性对象 */
|
/** 图块的通行性对象 */
|
||||||
readonly pass: ITilePassData;
|
readonly pass: ITilePassData;
|
||||||
|
/**
|
||||||
|
* 事件可通行性,当为 `false` 时,玩家会通过撞击触发图块的触发器,
|
||||||
|
* 当为 `true` 时,玩家会通过走上去触发图块的触发器。类似于旧样板的 `noPass`
|
||||||
|
*/
|
||||||
|
readonly eventPass: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITileLegacyConverter<TLegacy> {
|
export interface ITileLegacyConverter<TLegacy> {
|
||||||
|
|||||||
@ -66,6 +66,8 @@ import { ILoadProgressTotal, LoadProgressTotal } from '@motajs/loader';
|
|||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { logger } from '@motajs/common';
|
import { logger } from '@motajs/common';
|
||||||
import { ISaveSystem, SaveSystem } from './save';
|
import { ISaveSystem, SaveSystem } from './save';
|
||||||
|
import { DefaultPassChecker } from './hero';
|
||||||
|
import { DefaultHitAction } from './hero/hitAction';
|
||||||
|
|
||||||
export class CoreState implements ICoreState {
|
export class CoreState implements ICoreState {
|
||||||
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
|
// Layer 0 公共层,最底层的接口,不会依赖任何其他内容,一般是工具性接口及不需要存档的数据
|
||||||
@ -208,6 +210,16 @@ export class CoreState implements ICoreState {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 勇士顶层初始化
|
||||||
|
const passChecker = new DefaultPassChecker(this.maps);
|
||||||
|
const hitAction = new DefaultHitAction(
|
||||||
|
this.maps,
|
||||||
|
this.triggerCollector,
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.hero.location.mover.useTerrainChecker(passChecker);
|
||||||
|
this.hero.location.mover.useHitAction(hitAction);
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
packages-user/data-state/src/hero/hitAction.ts
Normal file
37
packages-user/data-state/src/hero/hitAction.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
IHeroHitAction,
|
||||||
|
IHeroHitHandler,
|
||||||
|
IMapStore,
|
||||||
|
IStateBase
|
||||||
|
} from '@user/data-base';
|
||||||
|
import { ITriggerCollector, ITriggerHandler } from '@user/data-system';
|
||||||
|
import { isNil } from 'lodash-es';
|
||||||
|
|
||||||
|
export class DefaultHitAction implements IHeroHitAction {
|
||||||
|
constructor(
|
||||||
|
readonly maps: IMapStore,
|
||||||
|
readonly collector: ITriggerCollector,
|
||||||
|
readonly state: IStateBase
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async hit(handler: IHeroHitHandler): Promise<void> {
|
||||||
|
if (isNil(handler.floorId)) return;
|
||||||
|
|
||||||
|
const map = this.maps.getLayerState(handler.floorId);
|
||||||
|
if (!map) return;
|
||||||
|
const event = map.eventLayer;
|
||||||
|
if (!event) return;
|
||||||
|
|
||||||
|
const { x, y } = handler.nextLoc;
|
||||||
|
const triggers = this.collector.collect(x, y, event);
|
||||||
|
|
||||||
|
const triggerHandler: ITriggerHandler = {
|
||||||
|
state: this.state,
|
||||||
|
layer: map,
|
||||||
|
mapLayer: event,
|
||||||
|
locator: handler.nextLoc
|
||||||
|
};
|
||||||
|
|
||||||
|
return triggers.trigger(triggerHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,72 +1,13 @@
|
|||||||
import { ITileLocator } from '@motajs/common';
|
|
||||||
import { FaceDirection, PassBit } from '@user/data-common';
|
import { FaceDirection, PassBit } from '@user/data-common';
|
||||||
import { IHeroState, IStateBase, ITerrainPassChecker } from '@user/data-base';
|
import {
|
||||||
|
IMapStore,
|
||||||
|
IPassCheckerHandler,
|
||||||
|
ITerrainPassChecker
|
||||||
|
} from '@user/data-base';
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
|
|
||||||
export class DefaultPassChecker implements ITerrainPassChecker {
|
export class DefaultPassChecker implements ITerrainPassChecker {
|
||||||
constructor(
|
constructor(readonly maps: IMapStore) {}
|
||||||
readonly state: IStateBase,
|
|
||||||
readonly hero: IHeroState<unknown>
|
|
||||||
) {}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将朝向转换为对应的通行性位掩码。
|
* 将朝向转换为对应的通行性位掩码。
|
||||||
@ -86,4 +27,87 @@ export class DefaultPassChecker implements ITerrainPassChecker {
|
|||||||
return 0;
|
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 map = this.maps.getLayerState(floorId);
|
||||||
|
if (!map) return false;
|
||||||
|
const event = map.eventLayer;
|
||||||
|
if (!event) 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 = event.getLocationData(x, y);
|
||||||
|
const next = event.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 map.layerList) {
|
||||||
|
if (layer === event) 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,25 +36,22 @@ export class TileLegacyBridge implements ITileLegacyConverter<LegacyTileData> {
|
|||||||
|
|
||||||
private getPass(num: number, legacy: LegacyTileData): ITilePassData {
|
private getPass(num: number, legacy: LegacyTileData): ITilePassData {
|
||||||
if (num === 0) {
|
if (num === 0) {
|
||||||
|
// 空图块
|
||||||
return {
|
return {
|
||||||
onlyEvents: true,
|
onlyEvents: true,
|
||||||
inPass: 0b1111,
|
inPass: 0b1111,
|
||||||
outPass: 0b1111
|
outPass: 0b1111
|
||||||
};
|
};
|
||||||
} else if (num === 17) {
|
} else if (num === 17) {
|
||||||
|
// 空气墙
|
||||||
return {
|
return {
|
||||||
onlyEvents: false,
|
onlyEvents: false,
|
||||||
inPass: 0b0000,
|
inPass: 0b0000,
|
||||||
outPass: 0b0000
|
outPass: 0b0000
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (legacy.noPass) {
|
// 正常图块
|
||||||
return {
|
if (legacy.cannotIn && legacy.cannotOut) {
|
||||||
onlyEvents: true,
|
|
||||||
inPass: 0b0000,
|
|
||||||
outPass: 0b1111
|
|
||||||
};
|
|
||||||
} else if (legacy.cannotIn && legacy.cannotOut) {
|
|
||||||
let inPass = 0b1111;
|
let inPass = 0b1111;
|
||||||
let outPass = 0b1111;
|
let outPass = 0b1111;
|
||||||
if (legacy.cannotIn.includes('up')) {
|
if (legacy.cannotIn.includes('up')) {
|
||||||
@ -102,7 +99,8 @@ export class TileLegacyBridge implements ITileLegacyConverter<LegacyTileData> {
|
|||||||
id: legacy.id,
|
id: legacy.id,
|
||||||
trigger: -1,
|
trigger: -1,
|
||||||
type: this.getTileType(num, legacy),
|
type: this.getTileType(num, legacy),
|
||||||
pass: this.getPass(num, legacy)
|
pass: this.getPass(num, legacy),
|
||||||
|
eventPass: !legacy.noPass
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,14 +5,13 @@ import {
|
|||||||
ISpecial,
|
ISpecial,
|
||||||
IReadonlyHeroAttribute,
|
IReadonlyHeroAttribute,
|
||||||
IHeroAttribute,
|
IHeroAttribute,
|
||||||
IStateBase,
|
IDataBaseExtended
|
||||||
IStateBaseExtended
|
|
||||||
} from '@user/data-base';
|
} from '@user/data-base';
|
||||||
import { ILocationHelper } from '@user/data-common';
|
import { ILocationHelper } from '@user/data-common';
|
||||||
|
|
||||||
//#region 辅助接口
|
//#region 辅助接口
|
||||||
|
|
||||||
export interface IEnemyHandler<TEnemy, THero> {
|
export interface IEnemyHandler<TEnemy, THero> extends IDataBaseExtended {
|
||||||
/** 怪物属性信息 */
|
/** 怪物属性信息 */
|
||||||
readonly enemy: IEnemy<TEnemy>;
|
readonly enemy: IEnemy<TEnemy>;
|
||||||
/** 怪物上下文 */
|
/** 怪物上下文 */
|
||||||
@ -21,11 +20,12 @@ export interface IEnemyHandler<TEnemy, THero> {
|
|||||||
readonly locator: ITileLocator;
|
readonly locator: ITileLocator;
|
||||||
/** 勇士属性信息 */
|
/** 勇士属性信息 */
|
||||||
readonly hero: IReadonlyHeroAttribute<THero>;
|
readonly hero: IReadonlyHeroAttribute<THero>;
|
||||||
/** 当前全局状态对象 */
|
|
||||||
readonly state: IStateBase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IReadonlyEnemyHandler<TEnemy, THero> {
|
export interface IReadonlyEnemyHandler<
|
||||||
|
TEnemy,
|
||||||
|
THero
|
||||||
|
> extends IDataBaseExtended {
|
||||||
/** 怪物属性信息 */
|
/** 怪物属性信息 */
|
||||||
readonly enemy: IReadonlyEnemy<TEnemy>;
|
readonly enemy: IReadonlyEnemy<TEnemy>;
|
||||||
/** 怪物上下文 */
|
/** 怪物上下文 */
|
||||||
@ -34,8 +34,6 @@ export interface IReadonlyEnemyHandler<TEnemy, THero> {
|
|||||||
readonly locator: ITileLocator;
|
readonly locator: ITileLocator;
|
||||||
/** 勇士属性信息 */
|
/** 勇士属性信息 */
|
||||||
readonly hero: IReadonlyHeroAttribute<THero>;
|
readonly hero: IReadonlyHeroAttribute<THero>;
|
||||||
/** 当前全局状态对象 */
|
|
||||||
readonly state: IStateBase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -283,7 +281,7 @@ export interface IMapDamageReducer {
|
|||||||
): Readonly<IMapDamageInfo>;
|
): Readonly<IMapDamageInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMapDamage<TEnemy, THero> extends IStateBaseExtended {
|
export interface IMapDamage<TEnemy, THero> extends IDataBaseExtended {
|
||||||
/** 当前绑定的怪物上下文 */
|
/** 当前绑定的怪物上下文 */
|
||||||
readonly context: IEnemyContext<TEnemy, THero>;
|
readonly context: IEnemyContext<TEnemy, THero>;
|
||||||
|
|
||||||
@ -406,7 +404,7 @@ export interface IDamageCalculator<TEnemy, THero> {
|
|||||||
): number;
|
): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDamageContext<TEnemy, THero> extends IStateBaseExtended {
|
export interface IDamageContext<TEnemy, THero> extends IDataBaseExtended {
|
||||||
/**
|
/**
|
||||||
* 获取战斗伤害信息
|
* 获取战斗伤害信息
|
||||||
* @param enemy 怪物视图
|
* @param enemy 怪物视图
|
||||||
@ -499,7 +497,7 @@ export interface IDamageSystem<TEnemy, THero> extends IDamageContext<
|
|||||||
export interface IReadonlyEnemyContext<
|
export interface IReadonlyEnemyContext<
|
||||||
TEnemy,
|
TEnemy,
|
||||||
THero
|
THero
|
||||||
> extends IStateBaseExtended {
|
> extends IDataBaseExtended {
|
||||||
/** 怪物上下文宽度 */
|
/** 怪物上下文宽度 */
|
||||||
readonly width: number;
|
readonly width: number;
|
||||||
/** 怪物上下文高度 */
|
/** 怪物上下文高度 */
|
||||||
@ -739,7 +737,7 @@ export interface IEnemyContext<TEnemy, THero> extends IReadonlyEnemyContext<
|
|||||||
|
|
||||||
//#region 战斗流程
|
//#region 战斗流程
|
||||||
|
|
||||||
export interface ICombatFlowHandler<TEnemy, THero> {
|
export interface ICombatFlowHandler<TEnemy, THero> extends IDataBaseExtended {
|
||||||
/** 战斗的怪物是否在地图上 */
|
/** 战斗的怪物是否在地图上 */
|
||||||
readonly onMap: boolean;
|
readonly onMap: boolean;
|
||||||
/** 可修改勇士对象 */
|
/** 可修改勇士对象 */
|
||||||
@ -750,8 +748,6 @@ export interface ICombatFlowHandler<TEnemy, THero> {
|
|||||||
readonly context: IEnemyContext<TEnemy, THero>;
|
readonly context: IEnemyContext<TEnemy, THero>;
|
||||||
/** 怪物位置 */
|
/** 怪物位置 */
|
||||||
readonly locator: ITileLocator;
|
readonly locator: ITileLocator;
|
||||||
/** 全局状态对象 */
|
|
||||||
readonly state: IStateBase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICombatFlowHook<TEnemy, THero> extends IHookBase {
|
export interface ICombatFlowHook<TEnemy, THero> extends IHookBase {
|
||||||
@ -803,7 +799,7 @@ export interface ICombatResult<TEnemy, THero> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ICombatFlow<TEnemy, THero>
|
export interface ICombatFlow<TEnemy, THero>
|
||||||
extends IHookable<ICombatFlowHook<TEnemy, THero>>, IStateBaseExtended {
|
extends IHookable<ICombatFlowHook<TEnemy, THero>>, IDataBaseExtended {
|
||||||
/** 勇士属性对象 */
|
/** 勇士属性对象 */
|
||||||
readonly hero: IReadonlyHeroAttribute<THero> | null;
|
readonly hero: IReadonlyHeroAttribute<THero> | null;
|
||||||
/** 怪物上下文对象 */
|
/** 怪物上下文对象 */
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import {
|
|||||||
ILayerState,
|
ILayerState,
|
||||||
IMapLayer,
|
IMapLayer,
|
||||||
IStateBase,
|
IStateBase,
|
||||||
IStateBaseExtended
|
IDataBaseExtended
|
||||||
} from '@user/data-base';
|
} from '@user/data-base';
|
||||||
|
|
||||||
export interface ITriggerHandler {
|
export interface ITriggerHandler {
|
||||||
@ -19,7 +19,7 @@ export interface ITriggerHandler {
|
|||||||
|
|
||||||
export type TriggerFactory = (type: number, state: IStateBase) => ITrigger;
|
export type TriggerFactory = (type: number, state: IStateBase) => ITrigger;
|
||||||
|
|
||||||
export interface ITrigger extends IStateBaseExtended {
|
export interface ITrigger extends IDataBaseExtended {
|
||||||
/** 触发器类型标识 */
|
/** 触发器类型标识 */
|
||||||
readonly type: number;
|
readonly type: number;
|
||||||
/** 触发器优先级 */
|
/** 触发器优先级 */
|
||||||
@ -37,7 +37,7 @@ export interface ITrigger extends IStateBaseExtended {
|
|||||||
collection(): ITriggerCollection;
|
collection(): ITriggerCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITriggerRegistry extends IStateBaseExtended {
|
export interface ITriggerRegistry extends IDataBaseExtended {
|
||||||
/**
|
/**
|
||||||
* 注册一个按类型创建的触发器工厂
|
* 注册一个按类型创建的触发器工厂
|
||||||
* @param type 触发器类型
|
* @param type 触发器类型
|
||||||
|
|||||||
40
prompt.md
40
prompt.md
@ -1,26 +1,32 @@
|
|||||||
|
# 核心规则
|
||||||
|
|
||||||
|
你的身份不是架构设计者,而是长期维护该项目的核心开发者。
|
||||||
|
|
||||||
|
该项目的架构已经经过大量设计与验证,你不需要深刻理解架构,当前架构已经是经过深思熟虑的结果。除非明确要求,否则不要试图重新设计架构,也不要主动优化已有设计。你的职责是在保持整体风格和架构稳定的前提下,完成需求并自然扩展已有系统。
|
||||||
|
|
||||||
|
1. **遵循最小修改原则**:
|
||||||
|
- 默认不要重新设计已有代码。
|
||||||
|
- 默认不要重构已有实现。
|
||||||
|
- 默认不要为了"更优雅"而改变已有模式。
|
||||||
|
- 新代码应当尽可能像是在原有代码基础上的自然延续,而不是重新实现
|
||||||
|
2. **现有代码最重要**。当前文件就是最高优先级规范。当 Prompt、已有代码、自身知识冲突时,优先遵循已有代码。
|
||||||
|
3. **不要主动创造新的模式**。遵循已有的设计与代码风格,不要在不同风格之间来回跳跃。
|
||||||
|
4. **优先优化维护成本,而不是代码长度**。允许局部重复,允许多几个变量,允许多几个 if。但绝不鼓励:为了减少几行代码增加抽象;为了减少重复增加函数跳转;为了"现代 TS"使用复杂语法。
|
||||||
|
5. **当不确定时,选择最保守的方案**。如果两种方案都能实现需求,默认选择修改最少、风险最低、最接近已有实现的方案。
|
||||||
|
|
||||||
# 必须规则
|
# 必须规则
|
||||||
|
|
||||||
以下规则必须时刻遵守,任何情况下都不允许违反。
|
以下规则必须时刻遵守,任何情况下都不允许违反。
|
||||||
|
|
||||||
1. **不擅自修改已有内容**:将我已经写好的内容视为绝对正确。除非我**明确允许**,否则**不允许任何修改**,哪怕因为接口变化或其他原因导致其中出现类型错误。若认为我的代码存在错误,应在对话中提出,而不是直接修改。
|
1. **不擅自修改已有内容**:将我已经写好的内容视为绝对正确。除非我**明确允许**,否则**不允许任何修改**,哪怕因为接口变化或其他原因导致其中出现类型错误。若认为我的代码存在错误,应在对话中提出,而不是直接修改。
|
||||||
2. **不恢复我的修改**:我做的任何代码修改都是有原因的。若我在两次对话期间新增、删除或修改了部分代码,不要将其恢复。
|
2. **不恢复我的修改**:我做的任何代码修改都是有原因的。若我在两次对话期间新增、删除或修改了部分代码,不要将其恢复。
|
||||||
3. **以目的驱动,而非以接口驱动**:实现前先想清楚我为什么要这样设计接口、这个接口设计的目的是什么,而不是单纯地以将接口填满为目标。
|
3. **遇到歧义立即提问**:若思考或实现时遇到任何问题——例如描述模糊、接口不清晰、某些地方存在歧义等——应立即向我提问,而不是按自己的想法去写。若完成需求所需的接口尚不存在,也应当立即停止实现,向我提出疑问,而不是擅自新增接口来推进。
|
||||||
4. **遇到歧义立即提问**:若思考或实现时遇到任何问题——例如描述模糊、接口不清晰、某些地方存在歧义等——应立即向我提问,而不是按自己的想法去写。
|
4. **修改文件前重读文件,修改后说明内容**:修改文件前务必重新阅读以确保内容是最新版。修改后必须在对话中说明所有改动的文件及具体改动内容。在修改文件时,**不得修改**文档的 `涉及文件` 节未提及的文件。
|
||||||
5. **接口缺失时停止并提问**:若完成需求所需的接口尚不存在,应立即停止实现,向我提出疑问,而不是擅自新增接口来推进。
|
5. **关于 `dev.md` 的强度升级**:`dev.md` 中"一般不建议/尽量避免"的条目在此处视为**绝对禁止**;"建议/最好"的条目在此处视为**必须遵守**。
|
||||||
6. **按我说的方式重构**:重构时**不要**擅自新增任何参数、方法或接口,**不要**仅通过新增兼容层应对重构。
|
6. **渲染端永远处于被动**:任何情况下都不可能会出现数据端主动通知渲染端进行更新的场景,渲染端仅通过钩子与数据端通信,通过钩子被动获取信息,并在某些情况下影响数据端行为。
|
||||||
- **彻底性重构**(新旧接口完全没有重合):按正常方式全新实现,旧代码仅作逻辑参考。
|
7. **关于非空判断**:对于对象,直接使用 `if (!object)` 的方式进行判断,对于字面量,使用 `lodash` 接口 `isNil` 判断 `if (isNil(value))`,不要使用 `if (value === undefined)` 这种方式。
|
||||||
- **结构性重构**(新旧接口基本一致,细节有差距):将旧代码搬移到新接口上后进行微调。
|
8. **方法顺序**:对于类的方法,根据功能进行排序,而不是按照修饰词进行排序。对于较长或功能较多的类,使用 `#region` 进行分区,分区按照方法功能进行划分。所有类的成员必须写到类的开头,并按照功能排序。
|
||||||
7. **不要有任何"顺手"的想法**:任何时候,都不要出现顺手的想法,包括但不限于发现了一个 bug 然后**顺手**修复、发现一处类型错误然后**顺手**修复等,这种情况下应当遵循规则 1。
|
9. **注释要求**:所有私有方法和私有成员必须添加 jsDoc 注释,受保护、公共成员与方法必须在源头处添加注释,继承而来的,除非功能或描述有变化,否则不得添加注释。即要求注释具有唯一性:已经在某些地方添加过注释的,不在其他地方添加。
|
||||||
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)` 这种方式。
|
|
||||||
|
|
||||||
# 建议规则
|
# 建议规则
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user