mirror of
https://github.com/motajs/template.git
synced 2026-11-01 06:04:30 +08:00
refactor: 将 Layer 0 单独放到一个包
This commit is contained in:
1 parent
bf781c5ee8
commit
8f4c4b7df2
48 files changed
+159
-97
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { IFaceData, IRoleFaceBinder } from '../common';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { FaceDirection } from '.';
|
||||
|
||||
interface FaceInfo {
|
||||
/** 此图块的朝向 */
|
||||
readonly face: FaceDirection;
|
||||
/** 此图块对应的映射 */
|
||||
readonly map: Map<FaceDirection, number>;
|
||||
}
|
||||
|
||||
export class RoleFaceBinder implements IRoleFaceBinder {
|
||||
/** 每个图块对应的朝向信息 */
|
||||
private faceMap: Map<number, FaceInfo> = new Map();
|
||||
/** 主要朝向映射 */
|
||||
private mainMap: Map<number, FaceDirection> = new Map();
|
||||
|
||||
malloc(identifier: number, main: FaceDirection): void {
|
||||
this.mainMap.set(identifier, main);
|
||||
const map = new Map<FaceDirection, number>();
|
||||
map.set(main, identifier);
|
||||
const info: FaceInfo = { face: main, map };
|
||||
this.faceMap.set(identifier, info);
|
||||
}
|
||||
|
||||
bind(identifier: number, main: number, face: FaceDirection): void {
|
||||
const mainFace = this.mainMap.get(main);
|
||||
if (isNil(mainFace)) {
|
||||
logger.error(43, main.toString());
|
||||
return;
|
||||
}
|
||||
if (mainFace === face) {
|
||||
logger.error(44, main.toString());
|
||||
return;
|
||||
}
|
||||
const { map } = this.faceMap.get(main)!;
|
||||
map.set(face, identifier);
|
||||
const info: FaceInfo = { face, map };
|
||||
this.faceMap.set(identifier, info);
|
||||
this.mainMap.set(identifier, mainFace);
|
||||
}
|
||||
|
||||
getFaceOf(identifier: number, face: FaceDirection): IFaceData | null {
|
||||
const info = this.faceMap.get(identifier);
|
||||
if (!info) return null;
|
||||
const target = info.map.get(face);
|
||||
if (isNil(target)) return null;
|
||||
const data: IFaceData = { identifier: target, face };
|
||||
return data;
|
||||
}
|
||||
|
||||
getFaceDirection(identifier: number): FaceDirection | undefined {
|
||||
return this.faceMap.get(identifier)?.face;
|
||||
}
|
||||
|
||||
getMainFace(identifier: number): IFaceData | null {
|
||||
const face = this.mainMap.get(identifier);
|
||||
if (isNil(face)) return null;
|
||||
const data: IFaceData = { identifier, face };
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
import { FaceDirection } from './types';
|
||||
|
||||
//#region 接口与枚举
|
||||
|
||||
export const enum FaceGroup {
|
||||
/** 四方向(上下左右) */
|
||||
Dir4,
|
||||
/** 八方向(上下左右+斜向) */
|
||||
Dir8
|
||||
}
|
||||
|
||||
export interface IFaceDescriptor {
|
||||
/** 横坐标增量 */
|
||||
readonly x: number;
|
||||
/** 纵坐标增量 */
|
||||
readonly y: number;
|
||||
}
|
||||
|
||||
export interface IFaceHandler<T extends number> {
|
||||
/**
|
||||
* 将任意朝向值降级为本组支持的方向。
|
||||
* 对于无法合理降级的方向(包括 `Unknown`),返回 `FaceDirection.Unknown`
|
||||
* @param dir 任意朝向值
|
||||
*/
|
||||
degrade(dir: number): T;
|
||||
|
||||
/**
|
||||
* 获取指定方向的单步坐标偏移量,输入先经过 `degrade`
|
||||
* @param dir 朝向
|
||||
*/
|
||||
movement(dir: number): IFaceDescriptor;
|
||||
|
||||
/**
|
||||
* 获取指定方向走 `count` 步的坐标偏移量,等价于 `movement * count`。
|
||||
* `count` 允许为负数,表示反向位移,输入先经过 `degrade`
|
||||
* @param dir 朝向
|
||||
* @param count 步数,允许为负
|
||||
*/
|
||||
move(dir: number, count: number): IFaceDescriptor;
|
||||
|
||||
/**
|
||||
* 获取本组内的反方向,输入先经过 `degrade`,`Unknown` 返回 `Unknown`
|
||||
* @param dir 朝向
|
||||
*/
|
||||
opposite(dir: number): T;
|
||||
|
||||
/**
|
||||
* 在本组方向集合内顺时针(默认)或逆时针旋转一步,输入先经过 `degrade`,
|
||||
* `Unknown` 返回 `Unknown`
|
||||
* @param dir 朝向
|
||||
* @param anticlockwise 是否逆时针,默认顺时针
|
||||
*/
|
||||
next(dir: number, anticlockwise?: boolean): T;
|
||||
|
||||
/**
|
||||
* 迭代本组支持的所有朝向,包含 `Unknown`
|
||||
*/
|
||||
mapDirection(): Iterable<T>;
|
||||
|
||||
/**
|
||||
* 迭代本组所有朝向及其对应的坐标描述器,包含 `Unknown`(对应 `{ x: 0, y: 0 }`)
|
||||
*/
|
||||
mapMovement(): Iterable<[T, IFaceDescriptor]>;
|
||||
}
|
||||
|
||||
export interface IFaceManager {
|
||||
/**
|
||||
* 以数字 key 注册一个 handler
|
||||
* @param group 数字 key
|
||||
* @param handler 朝向处理器
|
||||
*/
|
||||
register(group: number, handler: IFaceHandler<number>): void;
|
||||
|
||||
/**
|
||||
* 以字符串 id 注册一个 handler
|
||||
* @param id 字符串 id
|
||||
* @param handler 朝向处理器
|
||||
*/
|
||||
registerById(id: string, handler: IFaceHandler<number>): void;
|
||||
|
||||
/**
|
||||
* 按数字 key 查找 handler,未找到返回 `null`
|
||||
* @param group 数字 key
|
||||
*/
|
||||
get<T extends number>(group: number): IFaceHandler<T> | null;
|
||||
|
||||
/**
|
||||
* 按字符串 id 查找 handler,未找到返回 `null`
|
||||
* @param id 字符串 id
|
||||
*/
|
||||
getById<T extends number>(id: string): IFaceHandler<T> | null;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 内置 Handler
|
||||
|
||||
const ZERO_DESCRIPTOR: IFaceDescriptor = { x: 0, y: 0 };
|
||||
|
||||
const DIR8_MOVEMENTS: ReadonlyMap<FaceDirection, IFaceDescriptor> = new Map([
|
||||
[FaceDirection.Unknown, ZERO_DESCRIPTOR],
|
||||
[FaceDirection.Left, { x: -1, y: 0 }],
|
||||
[FaceDirection.Up, { x: 0, y: -1 }],
|
||||
[FaceDirection.Right, { x: 1, y: 0 }],
|
||||
[FaceDirection.Down, { x: 0, y: 1 }],
|
||||
[FaceDirection.LeftUp, { x: -1, y: -1 }],
|
||||
[FaceDirection.RightUp, { x: 1, y: -1 }],
|
||||
[FaceDirection.LeftDown, { x: -1, y: 1 }],
|
||||
[FaceDirection.RightDown, { x: 1, y: 1 }]
|
||||
]);
|
||||
|
||||
/** 顺时针旋转顺序(不含 Unknown) */
|
||||
const DIR8_CW: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
|
||||
[FaceDirection.Up, FaceDirection.RightUp],
|
||||
[FaceDirection.RightUp, FaceDirection.Right],
|
||||
[FaceDirection.Right, FaceDirection.RightDown],
|
||||
[FaceDirection.RightDown, FaceDirection.Down],
|
||||
[FaceDirection.Down, FaceDirection.LeftDown],
|
||||
[FaceDirection.LeftDown, FaceDirection.Left],
|
||||
[FaceDirection.Left, FaceDirection.LeftUp],
|
||||
[FaceDirection.LeftUp, FaceDirection.Up]
|
||||
]);
|
||||
|
||||
/** 逆时针旋转顺序(不含 Unknown) */
|
||||
const DIR8_CCW: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
|
||||
[FaceDirection.Up, FaceDirection.LeftUp],
|
||||
[FaceDirection.LeftUp, FaceDirection.Left],
|
||||
[FaceDirection.Left, FaceDirection.LeftDown],
|
||||
[FaceDirection.LeftDown, FaceDirection.Down],
|
||||
[FaceDirection.Down, FaceDirection.RightDown],
|
||||
[FaceDirection.RightDown, FaceDirection.Right],
|
||||
[FaceDirection.Right, FaceDirection.RightUp],
|
||||
[FaceDirection.RightUp, FaceDirection.Up]
|
||||
]);
|
||||
|
||||
const DIR8_OPPOSITE: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
|
||||
[FaceDirection.Up, FaceDirection.Down],
|
||||
[FaceDirection.Down, FaceDirection.Up],
|
||||
[FaceDirection.Left, FaceDirection.Right],
|
||||
[FaceDirection.Right, FaceDirection.Left],
|
||||
[FaceDirection.LeftUp, FaceDirection.RightDown],
|
||||
[FaceDirection.RightDown, FaceDirection.LeftUp],
|
||||
[FaceDirection.RightUp, FaceDirection.LeftDown],
|
||||
[FaceDirection.LeftDown, FaceDirection.RightUp]
|
||||
]);
|
||||
|
||||
export class Dir8FaceHandler implements IFaceHandler<FaceDirection> {
|
||||
degrade(dir: number): FaceDirection {
|
||||
return dir as FaceDirection;
|
||||
}
|
||||
|
||||
movement(dir: number): IFaceDescriptor {
|
||||
return DIR8_MOVEMENTS.get(this.degrade(dir)) ?? ZERO_DESCRIPTOR;
|
||||
}
|
||||
|
||||
move(dir: number, count: number): IFaceDescriptor {
|
||||
const { x, y } = this.movement(dir);
|
||||
return { x: x * count, y: y * count };
|
||||
}
|
||||
|
||||
opposite(dir: number): FaceDirection {
|
||||
const degraded = this.degrade(dir);
|
||||
return DIR8_OPPOSITE.get(degraded) ?? FaceDirection.Unknown;
|
||||
}
|
||||
|
||||
next(dir: number, anticlockwise: boolean = false): FaceDirection {
|
||||
const degraded = this.degrade(dir);
|
||||
if (degraded === FaceDirection.Unknown) return FaceDirection.Unknown;
|
||||
const map = anticlockwise ? DIR8_CCW : DIR8_CW;
|
||||
return map.get(degraded) ?? FaceDirection.Unknown;
|
||||
}
|
||||
|
||||
mapDirection(): Iterable<FaceDirection> {
|
||||
return DIR8_MOVEMENTS.keys();
|
||||
}
|
||||
|
||||
mapMovement(): Iterable<[FaceDirection, IFaceDescriptor]> {
|
||||
return DIR8_MOVEMENTS.entries();
|
||||
}
|
||||
}
|
||||
|
||||
const DIR4_DEGRADE: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
|
||||
[FaceDirection.Left, FaceDirection.Left],
|
||||
[FaceDirection.Up, FaceDirection.Up],
|
||||
[FaceDirection.Right, FaceDirection.Right],
|
||||
[FaceDirection.Down, FaceDirection.Down],
|
||||
[FaceDirection.LeftUp, FaceDirection.Left],
|
||||
[FaceDirection.LeftDown, FaceDirection.Left],
|
||||
[FaceDirection.RightUp, FaceDirection.Right],
|
||||
[FaceDirection.RightDown, FaceDirection.Right]
|
||||
]);
|
||||
|
||||
const DIR4_MOVEMENTS: ReadonlyMap<FaceDirection, IFaceDescriptor> = new Map([
|
||||
[FaceDirection.Unknown, ZERO_DESCRIPTOR],
|
||||
[FaceDirection.Left, { x: -1, y: 0 }],
|
||||
[FaceDirection.Up, { x: 0, y: -1 }],
|
||||
[FaceDirection.Right, { x: 1, y: 0 }],
|
||||
[FaceDirection.Down, { x: 0, y: 1 }]
|
||||
]);
|
||||
|
||||
const DIR4_CW: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
|
||||
[FaceDirection.Up, FaceDirection.Right],
|
||||
[FaceDirection.Right, FaceDirection.Down],
|
||||
[FaceDirection.Down, FaceDirection.Left],
|
||||
[FaceDirection.Left, FaceDirection.Up]
|
||||
]);
|
||||
|
||||
const DIR4_CCW: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
|
||||
[FaceDirection.Up, FaceDirection.Left],
|
||||
[FaceDirection.Left, FaceDirection.Down],
|
||||
[FaceDirection.Down, FaceDirection.Right],
|
||||
[FaceDirection.Right, FaceDirection.Up]
|
||||
]);
|
||||
|
||||
const DIR4_OPPOSITE: ReadonlyMap<FaceDirection, FaceDirection> = new Map([
|
||||
[FaceDirection.Up, FaceDirection.Down],
|
||||
[FaceDirection.Down, FaceDirection.Up],
|
||||
[FaceDirection.Left, FaceDirection.Right],
|
||||
[FaceDirection.Right, FaceDirection.Left]
|
||||
]);
|
||||
|
||||
export class Dir4FaceHandler implements IFaceHandler<FaceDirection> {
|
||||
degrade(dir: number): FaceDirection {
|
||||
return DIR4_DEGRADE.get(dir as FaceDirection) ?? FaceDirection.Unknown;
|
||||
}
|
||||
|
||||
movement(dir: number): IFaceDescriptor {
|
||||
return DIR4_MOVEMENTS.get(this.degrade(dir)) ?? ZERO_DESCRIPTOR;
|
||||
}
|
||||
|
||||
move(dir: number, count: number): IFaceDescriptor {
|
||||
const { x, y } = this.movement(dir);
|
||||
return { x: x * count, y: y * count };
|
||||
}
|
||||
|
||||
opposite(dir: number): FaceDirection {
|
||||
const degraded = this.degrade(dir);
|
||||
return DIR4_OPPOSITE.get(degraded) ?? FaceDirection.Unknown;
|
||||
}
|
||||
|
||||
next(dir: number, anticlockwise: boolean = false): FaceDirection {
|
||||
const degraded = this.degrade(dir);
|
||||
if (degraded === FaceDirection.Unknown) return FaceDirection.Unknown;
|
||||
const map = anticlockwise ? DIR4_CCW : DIR4_CW;
|
||||
return map.get(degraded) ?? FaceDirection.Unknown;
|
||||
}
|
||||
|
||||
mapDirection(): Iterable<FaceDirection> {
|
||||
return DIR4_MOVEMENTS.keys();
|
||||
}
|
||||
|
||||
mapMovement(): Iterable<[FaceDirection, IFaceDescriptor]> {
|
||||
return DIR4_MOVEMENTS.entries();
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region FaceManager
|
||||
|
||||
export class FaceManager implements IFaceManager {
|
||||
private readonly byGroup: Map<number, IFaceHandler<number>> = new Map();
|
||||
private readonly byId: Map<string, IFaceHandler<number>> = new Map();
|
||||
|
||||
register(group: number, handler: IFaceHandler<number>): void {
|
||||
this.byGroup.set(group, handler);
|
||||
}
|
||||
|
||||
registerById(id: string, handler: IFaceHandler<number>): void {
|
||||
this.byId.set(id, handler);
|
||||
}
|
||||
|
||||
get<T extends number>(group: number): IFaceHandler<T> | null {
|
||||
return (this.byGroup.get(group) as IFaceHandler<T>) ?? null;
|
||||
}
|
||||
|
||||
getById<T extends number>(id: string): IFaceHandler<T> | null {
|
||||
return (this.byId.get(id) as IFaceHandler<T>) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from './face';
|
||||
export * from './faceManager';
|
||||
export * from './indexer';
|
||||
export * from './mover';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
@@ -0,0 +1,61 @@
|
||||
import { ITileLocator } from '@motajs/common';
|
||||
|
||||
//#region 接口定义
|
||||
|
||||
export interface ILocationHelper {
|
||||
/**
|
||||
* 坐标 -> 索引
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
locToIndex(x: number, y: number): number;
|
||||
|
||||
/**
|
||||
* 定位符 -> 索引
|
||||
* @param locator 定位符
|
||||
*/
|
||||
locaterToIndex(locator: ITileLocator): number;
|
||||
|
||||
/**
|
||||
* 索引 -> 定位符
|
||||
* @param index 索引
|
||||
*/
|
||||
indexToLocator(index: number): ITileLocator;
|
||||
}
|
||||
|
||||
export interface ILocationIndexer extends ILocationHelper {
|
||||
/**
|
||||
* 设置地图宽度
|
||||
* @param width 地图宽度
|
||||
*/
|
||||
setWidth(width: number): void;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 默认实现
|
||||
|
||||
export class MapLocIndexer implements ILocationIndexer {
|
||||
private width: number = 0;
|
||||
|
||||
setWidth(width: number): void {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
locToIndex(x: number, y: number): number {
|
||||
return y * this.width + x;
|
||||
}
|
||||
|
||||
locaterToIndex(locator: ITileLocator): number {
|
||||
return locator.y * this.width + locator.x;
|
||||
}
|
||||
|
||||
indexToLocator(index: number): ITileLocator {
|
||||
return {
|
||||
x: index % this.width,
|
||||
y: Math.floor(index / this.width)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -0,0 +1,560 @@
|
||||
import {
|
||||
Hookable,
|
||||
HookController,
|
||||
IHookable,
|
||||
IHookBase,
|
||||
IHookController,
|
||||
ITileLocator
|
||||
} from '@motajs/common';
|
||||
import { FaceDirection } from './types';
|
||||
import { IFaceHandler } from './faceManager';
|
||||
|
||||
//#region 对象移动
|
||||
|
||||
export const enum ObjectMoveStepType {
|
||||
/** 绝对方向步,同步更新移动方向与朝向 */
|
||||
Dir,
|
||||
/** 绝对方向步,显式指定朝向 */
|
||||
DirFace,
|
||||
/** 速度步 */
|
||||
Speed,
|
||||
/** 纯转向步 */
|
||||
Face,
|
||||
/** 特殊步,如前进或后退 */
|
||||
Special,
|
||||
/** 动画方向步 */
|
||||
AnimDir
|
||||
}
|
||||
|
||||
export const enum ObjectSpecialStep {
|
||||
/** 前进 */
|
||||
Forward,
|
||||
/** 后退 */
|
||||
Backward
|
||||
}
|
||||
|
||||
export const enum ObjectAnimDirection {
|
||||
/** 正向播放动画 */
|
||||
Forward,
|
||||
/** 反向播放动画 */
|
||||
Backward
|
||||
}
|
||||
|
||||
export interface IObjectMovable {
|
||||
/** 当前横坐标 */
|
||||
readonly x: number;
|
||||
/** 当前纵坐标 */
|
||||
readonly y: number;
|
||||
|
||||
/**
|
||||
* 设置对象位置
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
setPos(x: number, y: number): void;
|
||||
|
||||
/**
|
||||
* 获取当前朝向
|
||||
*/
|
||||
getCurrentFaceDirection(): FaceDirection;
|
||||
}
|
||||
|
||||
export interface IObjectMoveStepDir {
|
||||
/** 步骤类型 */
|
||||
type: ObjectMoveStepType.Dir;
|
||||
/** 本步移动方向 */
|
||||
move: FaceDirection;
|
||||
}
|
||||
|
||||
export interface IObjectMoveStepDirFace {
|
||||
/** 步骤类型 */
|
||||
type: ObjectMoveStepType.DirFace;
|
||||
/** 本步移动方向 */
|
||||
move: FaceDirection;
|
||||
/** 本步显式朝向 */
|
||||
face: FaceDirection;
|
||||
}
|
||||
|
||||
export interface IObjectMoveStepSpeed {
|
||||
/** 步骤类型 */
|
||||
type: ObjectMoveStepType.Speed;
|
||||
/** 后续移动的每格耗时,单位为 ms */
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface IObjectMoveStepFace {
|
||||
/** 步骤类型 */
|
||||
type: ObjectMoveStepType.Face;
|
||||
/** 要设置的朝向 */
|
||||
value: FaceDirection;
|
||||
}
|
||||
|
||||
export interface IObjectMoveStepSpecial {
|
||||
/** 步骤类型 */
|
||||
type: ObjectMoveStepType.Special;
|
||||
/** 特殊步方向 */
|
||||
direction: ObjectSpecialStep;
|
||||
}
|
||||
|
||||
export interface IObjectMoveAnimDir {
|
||||
/** 步骤类型 */
|
||||
type: ObjectMoveStepType.AnimDir;
|
||||
/** 动画播放方向 */
|
||||
dir: ObjectAnimDirection;
|
||||
}
|
||||
|
||||
export type ObjectMoveStep =
|
||||
| IObjectMoveStepDir
|
||||
| IObjectMoveStepDirFace
|
||||
| IObjectMoveStepSpeed
|
||||
| IObjectMoveStepFace
|
||||
| IObjectMoveStepSpecial
|
||||
| IObjectMoveAnimDir;
|
||||
|
||||
export interface IMoverController {
|
||||
/** 本次移动是否已经全部完成 */
|
||||
done: boolean;
|
||||
/** 当本次移动结束时兑现 */
|
||||
onEnd: Promise<void>;
|
||||
|
||||
/**
|
||||
* 向当前移动队列末尾追加步骤
|
||||
* @param steps 要追加的步骤列表
|
||||
*/
|
||||
push(...steps: ObjectMoveStep[]): void;
|
||||
|
||||
/**
|
||||
* 在当前步移动之后立刻插入指定步,顺序为参数传入的顺序
|
||||
* @param steps 要插入的步骤列表
|
||||
*/
|
||||
insert(...steps: ObjectMoveStep[]): void;
|
||||
|
||||
/**
|
||||
* 停止当前移动,在当前步骤完成后兑现
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IObjectMoverHooks<T extends IObjectMovable> extends IHookBase {
|
||||
/**
|
||||
* 当移动开始时触发
|
||||
* @param tile 移动器绑定的图块
|
||||
* @param mover 当前移动器
|
||||
*/
|
||||
onMoveStart?(tile: T, mover: IObjectMover<T>): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当移动结束时触发
|
||||
* @param tile 移动器绑定的图块
|
||||
* @param mover 当前移动器
|
||||
*/
|
||||
onMoveEnd?(tile: T, mover: IObjectMover<T>): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当单步移动开始前触发,此时对象坐标仍为移动前坐标
|
||||
* @param code 当前步移动代码
|
||||
* @param step 当前步骤
|
||||
* @param tile 移动器绑定的图块
|
||||
* @param mover 当前移动器
|
||||
*/
|
||||
onStepStart?(
|
||||
code: number,
|
||||
step: ObjectMoveStep,
|
||||
tile: T,
|
||||
mover: IObjectMover<T>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当单步移动结束后触发,此时对象坐标已更新为移动后坐标
|
||||
* @param code 当前步移动代码
|
||||
* @param step 当前步骤
|
||||
* @param tile 移动器绑定的图块
|
||||
* @param mover 当前移动器
|
||||
*/
|
||||
onStepEnd?(
|
||||
code: number,
|
||||
step: ObjectMoveStep,
|
||||
tile: T,
|
||||
mover: IObjectMover<T>
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IObjectMover<T extends IObjectMovable> extends IHookable<
|
||||
IObjectMoverHooks<T>
|
||||
> {
|
||||
/** 当前是否正在移动 */
|
||||
readonly moving: boolean;
|
||||
/** 当前绑定的对象 */
|
||||
readonly tile: T;
|
||||
/** 当前朝向 */
|
||||
readonly faceDirection: FaceDirection;
|
||||
/** 当前移动方向 */
|
||||
readonly moveDirection: FaceDirection;
|
||||
/** 当前动画播放方向 */
|
||||
readonly currAnimDir: ObjectAnimDirection;
|
||||
/** 当前移动速度,单位毫秒 */
|
||||
readonly currentSpeed: number;
|
||||
|
||||
/**
|
||||
* 追加若干个绝对方向步,并同步更新移动方向与朝向
|
||||
* @param dir 移动方向
|
||||
* @param count 追加次数,默认 1
|
||||
*/
|
||||
step(dir: FaceDirection, count?: number): this;
|
||||
|
||||
/**
|
||||
* 追加若干个绝对方向步,移动方向与朝向分别指定
|
||||
* @param move 移动方向
|
||||
* @param face 朝向方向
|
||||
* @param count 追加次数,默认 1
|
||||
*/
|
||||
stepFace(move: FaceDirection, face: FaceDirection, count?: number): this;
|
||||
|
||||
/**
|
||||
* 追加若干个前进步,沿当前移动方向移动
|
||||
* @param count 追加次数,默认 1
|
||||
*/
|
||||
forward(count?: number): this;
|
||||
|
||||
/**
|
||||
* 追加若干个后退步,沿当前移动方向的反方向移动
|
||||
* @param count 追加次数,默认 1
|
||||
*/
|
||||
backward(count?: number): this;
|
||||
|
||||
/**
|
||||
* 设置后续移动速度
|
||||
* @param value 每格耗时,单位为 ms
|
||||
*/
|
||||
speed(value: number): this;
|
||||
|
||||
/**
|
||||
* 追加一个纯转向步骤
|
||||
* @param dir 目标朝向
|
||||
*/
|
||||
face(dir: FaceDirection): this;
|
||||
|
||||
/**
|
||||
* 设置后续步骤的动画播放方向
|
||||
* @param dir 动画播放方向
|
||||
*/
|
||||
animDir(dir: ObjectAnimDirection): this;
|
||||
|
||||
/**
|
||||
* 清空尚未执行的步骤队列
|
||||
*/
|
||||
clear(): this;
|
||||
|
||||
/**
|
||||
* 开始执行当前计划队列
|
||||
* @returns 若当前已在移动,则返回 `null`
|
||||
*/
|
||||
start(): Readonly<IMoverController> | null;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 移动基类
|
||||
|
||||
export abstract class ObjectMover<T extends IObjectMovable>
|
||||
extends Hookable<IObjectMoverHooks<T>>
|
||||
implements IObjectMover<T>
|
||||
{
|
||||
abstract readonly tile: T;
|
||||
|
||||
/** 尚未开始执行的移动步骤队列 */
|
||||
protected readonly moveQueue: Readonly<ObjectMoveStep>[] = [];
|
||||
|
||||
/** 当前是否正在移动 */
|
||||
moving: boolean = false;
|
||||
/** 当前朝向 */
|
||||
faceDirection: FaceDirection = FaceDirection.Unknown;
|
||||
/** 当前移动方向 */
|
||||
moveDirection: FaceDirection = FaceDirection.Unknown;
|
||||
/** 当前动画播放方向 */
|
||||
currAnimDir: ObjectAnimDirection = ObjectAnimDirection.Forward;
|
||||
/** 当前移动速度,单位毫秒 */
|
||||
currentSpeed: number = 100;
|
||||
|
||||
/** 是否调用了 `IMoverController.stop` 接口 */
|
||||
private shouldStop: boolean = false;
|
||||
|
||||
/** 朝向处理 */
|
||||
private readonly faceHandler: IFaceHandler<FaceDirection>;
|
||||
|
||||
constructor(faceHandler: IFaceHandler<FaceDirection>) {
|
||||
super();
|
||||
this.faceHandler = faceHandler;
|
||||
}
|
||||
|
||||
protected createController(
|
||||
hook: Partial<IObjectMoverHooks<T>>
|
||||
): IHookController<IObjectMoverHooks<T>> {
|
||||
return new HookController(this, hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当移动开始时执行
|
||||
* @param tile 移动图块
|
||||
* @param controller 移动控制器
|
||||
*/
|
||||
protected abstract onMoveStart(
|
||||
tile: T,
|
||||
controller: Readonly<IMoverController>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当移动结束时触发
|
||||
* @param tile 移动图块
|
||||
* @param controller 移动控制器
|
||||
*/
|
||||
protected abstract onMoveEnd(
|
||||
tile: T,
|
||||
controller: Readonly<IMoverController>
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当单步移动开始时触发,返回移动代码,此移动代码将会传递至 {@link onStepEnd}
|
||||
* @param step 当前移动步对象
|
||||
* @param tile 移动图块
|
||||
* @param controller 移动控制器
|
||||
*/
|
||||
protected abstract onStepStart(
|
||||
step: ObjectMoveStep,
|
||||
tile: T,
|
||||
controller: Readonly<IMoverController>
|
||||
): Promise<number>;
|
||||
|
||||
/**
|
||||
* 当单步移动结束时触发,返回坐标对象代表这一步移动结果
|
||||
* @param code 移动代码,由 {@link onStepStart} 返回值传递而来
|
||||
* @param step 当前移动步对象
|
||||
* @param tile 移动图块
|
||||
* @param controller 移动控制器
|
||||
*/
|
||||
protected abstract onStepEnd(
|
||||
code: number,
|
||||
step: ObjectMoveStep,
|
||||
tile: T,
|
||||
controller: Readonly<IMoverController>
|
||||
): Promise<ITileLocator>;
|
||||
|
||||
/**
|
||||
* 向计划队列末尾追加一个步骤
|
||||
* @param step 要追加的步骤
|
||||
*/
|
||||
protected pushStep(step: Readonly<ObjectMoveStep>): void {
|
||||
this.moveQueue.push(step);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前应当作为相对移动基准的方向
|
||||
*/
|
||||
private getCurrentDirection(): FaceDirection {
|
||||
if (this.moveDirection !== FaceDirection.Unknown) {
|
||||
return this.moveDirection;
|
||||
} else {
|
||||
return this.faceDirection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据步骤内容预先同步移动器内部状态
|
||||
* @param step 当前步骤
|
||||
*/
|
||||
private prepareStep(step: ObjectMoveStep): void {
|
||||
switch (step.type) {
|
||||
case ObjectMoveStepType.Dir:
|
||||
this.moveDirection = step.move;
|
||||
this.faceDirection = step.move;
|
||||
break;
|
||||
case ObjectMoveStepType.DirFace:
|
||||
this.moveDirection = step.move;
|
||||
this.faceDirection = step.face;
|
||||
break;
|
||||
case ObjectMoveStepType.Face:
|
||||
this.faceDirection = step.value;
|
||||
break;
|
||||
case ObjectMoveStepType.Special: {
|
||||
const dir = this.getCurrentDirection();
|
||||
if (step.direction === ObjectSpecialStep.Backward) {
|
||||
const opposite = this.faceHandler.opposite(dir);
|
||||
this.moveDirection = opposite;
|
||||
this.faceDirection = opposite;
|
||||
} else {
|
||||
this.moveDirection = dir;
|
||||
this.faceDirection = dir;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ObjectMoveStepType.AnimDir:
|
||||
this.currAnimDir = step.dir;
|
||||
break;
|
||||
case ObjectMoveStepType.Speed:
|
||||
this.currentSpeed = step.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
step(dir: FaceDirection, count: number = 1): this {
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.pushStep({
|
||||
type: ObjectMoveStepType.Dir,
|
||||
move: dir
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
stepFace(
|
||||
move: FaceDirection,
|
||||
face: FaceDirection,
|
||||
count: number = 1
|
||||
): this {
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.pushStep({
|
||||
type: ObjectMoveStepType.DirFace,
|
||||
move,
|
||||
face
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
forward(count: number = 1): this {
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.pushStep({
|
||||
type: ObjectMoveStepType.Special,
|
||||
direction: ObjectSpecialStep.Forward
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
backward(count: number = 1): this {
|
||||
for (let i = 0; i < count; i++) {
|
||||
this.pushStep({
|
||||
type: ObjectMoveStepType.Special,
|
||||
direction: ObjectSpecialStep.Backward
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
speed(value: number): this {
|
||||
this.pushStep({
|
||||
type: ObjectMoveStepType.Speed,
|
||||
value
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
face(dir: FaceDirection): this {
|
||||
this.pushStep({
|
||||
type: ObjectMoveStepType.Face,
|
||||
value: dir
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
animDir(dir: ObjectAnimDirection): this {
|
||||
this.pushStep({
|
||||
type: ObjectMoveStepType.AnimDir,
|
||||
dir
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
clear(): this {
|
||||
this.moveQueue.length = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始移动流程
|
||||
* @param queue 移动队列
|
||||
*/
|
||||
private async moveProgress(
|
||||
queue: ObjectMoveStep[],
|
||||
controller: Readonly<IMoverController>
|
||||
) {
|
||||
// 移动开始
|
||||
await this.onMoveStart(this.tile, controller);
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => {
|
||||
return hook.onMoveStart?.(this.tile, this);
|
||||
})
|
||||
);
|
||||
|
||||
// 移动流程
|
||||
while (queue.length > 0) {
|
||||
const step = queue.shift();
|
||||
if (!step || !this.moving || this.shouldStop) break;
|
||||
this.prepareStep(step);
|
||||
const code = await this.onStepStart(step, this.tile, controller);
|
||||
const stepStartHooks = this.forEachHook(hook =>
|
||||
hook.onStepStart?.(code, step, this.tile, this)
|
||||
);
|
||||
await Promise.all(stepStartHooks);
|
||||
const loc = await this.onStepEnd(code, step, this.tile, controller);
|
||||
this.tile.setPos(loc.x, loc.y);
|
||||
const stepEndHooks = this.forEachHook(hook =>
|
||||
hook.onStepEnd?.(code, step, this.tile, this)
|
||||
);
|
||||
await Promise.all(stepEndHooks);
|
||||
}
|
||||
|
||||
// 移动结束
|
||||
await this.onMoveEnd(this.tile, controller);
|
||||
const moveEndHooks = this.forEachHook(hook =>
|
||||
hook.onMoveEnd?.(this.tile, this)
|
||||
);
|
||||
await Promise.all(moveEndHooks);
|
||||
}
|
||||
|
||||
start(): IMoverController | null {
|
||||
if (this.moving) return null;
|
||||
|
||||
const queue = this.moveQueue.slice();
|
||||
this.clear();
|
||||
this.shouldStop = false;
|
||||
|
||||
this.faceDirection = this.tile.getCurrentFaceDirection();
|
||||
this.moveDirection = FaceDirection.Unknown;
|
||||
|
||||
const { promise, resolve } = Promise.withResolvers<void>();
|
||||
|
||||
const controller: IMoverController = {
|
||||
done: false,
|
||||
onEnd: promise,
|
||||
push: (...steps: ObjectMoveStep[]) => {
|
||||
if (!this.moving || this.shouldStop || controller.done) {
|
||||
return;
|
||||
}
|
||||
queue.push(...steps);
|
||||
},
|
||||
insert: (...steps: ObjectMoveStep[]) => {
|
||||
if (!this.moving || this.shouldStop || controller.done) {
|
||||
return;
|
||||
}
|
||||
queue.unshift(...steps);
|
||||
},
|
||||
stop: () => {
|
||||
this.shouldStop = true;
|
||||
return controller.onEnd;
|
||||
}
|
||||
};
|
||||
|
||||
this.moving = true;
|
||||
const moving = this.moveProgress(queue, controller);
|
||||
moving.then(() => {
|
||||
this.moving = false;
|
||||
controller.done = true;
|
||||
resolve();
|
||||
});
|
||||
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -0,0 +1,82 @@
|
||||
export const enum FaceDirection {
|
||||
Unknown,
|
||||
Left,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
LeftUp,
|
||||
RightUp,
|
||||
LeftDown,
|
||||
RightDown
|
||||
}
|
||||
|
||||
export interface IFaceData {
|
||||
/** 图块数字 */
|
||||
readonly identifier: number;
|
||||
/** 图块朝向 */
|
||||
readonly face: FaceDirection;
|
||||
}
|
||||
|
||||
export interface IRoleFaceBinder {
|
||||
/**
|
||||
* 给指定的图块分配朝向绑定
|
||||
* @param identifier 图块数字
|
||||
* @param main 主图块朝向,一般是朝下
|
||||
*/
|
||||
malloc(identifier: number, main: FaceDirection): void;
|
||||
|
||||
/**
|
||||
* 将一个图块与另一个图块绑定朝向,需要注意要先调用 {@link malloc} 分配朝向信息
|
||||
* @param identifier 当前图块数字
|
||||
* @param main 主图块数字,即当前图块与目标图块属于主图块的另一个朝向
|
||||
* @param face 当前图块的朝向方向
|
||||
*/
|
||||
bind(identifier: number, main: number, face: FaceDirection): void;
|
||||
|
||||
/**
|
||||
* 获取一个图块指定朝向的图块数字
|
||||
* @param identifier 图块数字,可以是任意朝向的图块数字
|
||||
* @param face 要获取的朝向
|
||||
*/
|
||||
getFaceOf(identifier: number, face: FaceDirection): IFaceData | null;
|
||||
|
||||
/**
|
||||
* 获取指定图块数字是哪个朝向
|
||||
* @param identifier 图块数字
|
||||
*/
|
||||
getFaceDirection(identifier: number): FaceDirection | undefined;
|
||||
|
||||
/**
|
||||
* 获取指定图块数字绑定至的主朝向
|
||||
* @param identifier 图块数字,可以是任意朝向的图块数字
|
||||
*/
|
||||
getMainFace(identifier: number): IFaceData | null;
|
||||
}
|
||||
|
||||
//#region 功能接口
|
||||
|
||||
export const enum SaveCompression {
|
||||
/** 不进行压缩,仅提取必要数据 */
|
||||
NoCompression,
|
||||
/** 进行小幅度压缩,以性能为主要考虑目标 */
|
||||
LowCompression,
|
||||
/** 进行大幅度压缩,以体积为主要考虑目标 */
|
||||
HighCompression
|
||||
}
|
||||
|
||||
export interface ISaveableContent<T> {
|
||||
/**
|
||||
* 保存对象状态,返回的对象应该经过深拷贝(即 `structuedClone`)
|
||||
* @param compression 压缩级别
|
||||
*/
|
||||
saveState(compression: SaveCompression): T;
|
||||
|
||||
/**
|
||||
* 读取对象状态
|
||||
* @param state 状态对象
|
||||
* @param compression 压缩级别
|
||||
*/
|
||||
loadState(state: T, compression: SaveCompression): void;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -0,0 +1,182 @@
|
||||
import { FaceDirection } from './types';
|
||||
|
||||
/**
|
||||
* 获取指定朝向的坐标偏移量
|
||||
* @param dir 朝向
|
||||
*/
|
||||
export function getFaceMovement(dir: FaceDirection): Loc {
|
||||
switch (dir) {
|
||||
case FaceDirection.Left:
|
||||
return { x: -1, y: 0 };
|
||||
case FaceDirection.Right:
|
||||
return { x: 1, y: 0 };
|
||||
case FaceDirection.Up:
|
||||
return { x: 0, y: -1 };
|
||||
case FaceDirection.Down:
|
||||
return { x: 0, y: 1 };
|
||||
case FaceDirection.LeftUp:
|
||||
return { x: -1, y: -1 };
|
||||
case FaceDirection.RightUp:
|
||||
return { x: 1, y: -1 };
|
||||
case FaceDirection.LeftDown:
|
||||
return { x: -1, y: 1 };
|
||||
case FaceDirection.RightDown:
|
||||
return { x: 1, y: 1 };
|
||||
case FaceDirection.Unknown:
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将八方向朝向降级为四方向朝向
|
||||
* @param dir 朝向
|
||||
* @param unknown 如果朝向是 `FaceDirection.Unknown`,那么会返回什么,默认值是未知
|
||||
*/
|
||||
export function degradeFace(
|
||||
dir: FaceDirection,
|
||||
unknown: FaceDirection = FaceDirection.Unknown
|
||||
): FaceDirection {
|
||||
switch (dir) {
|
||||
case FaceDirection.LeftUp:
|
||||
return FaceDirection.Left;
|
||||
case FaceDirection.LeftDown:
|
||||
return FaceDirection.Left;
|
||||
case FaceDirection.RightUp:
|
||||
return FaceDirection.Right;
|
||||
case FaceDirection.RightDown:
|
||||
return FaceDirection.Right;
|
||||
case FaceDirection.Unknown:
|
||||
return unknown;
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定朝向旋转后的朝向
|
||||
* @param dir 当前朝向
|
||||
* @param anticlockwise 是否逆时针旋转,默认顺时针
|
||||
* @param face8 是否使用八朝向。为 `false` 时,旋转为九十度旋转,即 上->右->下->左,左上->右上->右下->左下。
|
||||
* 为 `true` 时,旋转为四十五度旋转,即 上->右上->右->右下->下->左下->左->左上。逆时针反过来旋转。
|
||||
*/
|
||||
export function nextFaceDirection(
|
||||
dir: FaceDirection,
|
||||
anticlockwise: boolean = false,
|
||||
face8: boolean = false
|
||||
): FaceDirection {
|
||||
if (face8) {
|
||||
if (anticlockwise) {
|
||||
switch (dir) {
|
||||
case FaceDirection.Left:
|
||||
return FaceDirection.LeftDown;
|
||||
case FaceDirection.LeftDown:
|
||||
return FaceDirection.Down;
|
||||
case FaceDirection.Down:
|
||||
return FaceDirection.RightDown;
|
||||
case FaceDirection.RightDown:
|
||||
return FaceDirection.Right;
|
||||
case FaceDirection.Right:
|
||||
return FaceDirection.RightUp;
|
||||
case FaceDirection.RightUp:
|
||||
return FaceDirection.Up;
|
||||
case FaceDirection.Up:
|
||||
return FaceDirection.LeftUp;
|
||||
case FaceDirection.LeftUp:
|
||||
return FaceDirection.Left;
|
||||
case FaceDirection.Unknown:
|
||||
return FaceDirection.Unknown;
|
||||
}
|
||||
} else {
|
||||
switch (dir) {
|
||||
case FaceDirection.Left:
|
||||
return FaceDirection.LeftUp;
|
||||
case FaceDirection.LeftUp:
|
||||
return FaceDirection.Up;
|
||||
case FaceDirection.Up:
|
||||
return FaceDirection.RightUp;
|
||||
case FaceDirection.RightUp:
|
||||
return FaceDirection.Right;
|
||||
case FaceDirection.Right:
|
||||
return FaceDirection.RightDown;
|
||||
case FaceDirection.RightDown:
|
||||
return FaceDirection.Down;
|
||||
case FaceDirection.Down:
|
||||
return FaceDirection.LeftDown;
|
||||
case FaceDirection.LeftDown:
|
||||
return FaceDirection.Left;
|
||||
case FaceDirection.Unknown:
|
||||
return FaceDirection.Unknown;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (anticlockwise) {
|
||||
switch (dir) {
|
||||
case FaceDirection.Left:
|
||||
return FaceDirection.Down;
|
||||
case FaceDirection.Down:
|
||||
return FaceDirection.Right;
|
||||
case FaceDirection.Right:
|
||||
return FaceDirection.Up;
|
||||
case FaceDirection.Up:
|
||||
return FaceDirection.Left;
|
||||
case FaceDirection.LeftUp:
|
||||
return FaceDirection.LeftDown;
|
||||
case FaceDirection.LeftDown:
|
||||
return FaceDirection.RightDown;
|
||||
case FaceDirection.RightDown:
|
||||
return FaceDirection.RightUp;
|
||||
case FaceDirection.RightUp:
|
||||
return FaceDirection.LeftUp;
|
||||
case FaceDirection.Unknown:
|
||||
return FaceDirection.Unknown;
|
||||
}
|
||||
} else {
|
||||
switch (dir) {
|
||||
case FaceDirection.Left:
|
||||
return FaceDirection.Up;
|
||||
case FaceDirection.Up:
|
||||
return FaceDirection.Right;
|
||||
case FaceDirection.Right:
|
||||
return FaceDirection.Down;
|
||||
case FaceDirection.Down:
|
||||
return FaceDirection.Left;
|
||||
case FaceDirection.LeftUp:
|
||||
return FaceDirection.RightUp;
|
||||
case FaceDirection.RightUp:
|
||||
return FaceDirection.RightDown;
|
||||
case FaceDirection.RightDown:
|
||||
return FaceDirection.LeftDown;
|
||||
case FaceDirection.LeftDown:
|
||||
return FaceDirection.LeftUp;
|
||||
case FaceDirection.Unknown:
|
||||
return FaceDirection.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据朝向字符串获取朝向枚举值
|
||||
* @param dir 朝向字符串
|
||||
*/
|
||||
export function fromDirectionString(dir: Dir2): FaceDirection {
|
||||
switch (dir) {
|
||||
case 'left':
|
||||
return FaceDirection.Left;
|
||||
case 'right':
|
||||
return FaceDirection.Right;
|
||||
case 'up':
|
||||
return FaceDirection.Up;
|
||||
case 'down':
|
||||
return FaceDirection.Down;
|
||||
case 'leftup':
|
||||
return FaceDirection.LeftUp;
|
||||
case 'rightup':
|
||||
return FaceDirection.RightUp;
|
||||
case 'leftdown':
|
||||
return FaceDirection.LeftDown;
|
||||
case 'rightdown':
|
||||
return FaceDirection.RightDown;
|
||||
default:
|
||||
return FaceDirection.Unknown;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './common';
|
||||
export * from './store';
|
||||
|
||||
export * from './types';
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './tileStore';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,86 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { logger } from '@motajs/common';
|
||||
import {
|
||||
ITileLegacyConverter,
|
||||
ITileRawData,
|
||||
ITileStore,
|
||||
TileType
|
||||
} from './types';
|
||||
|
||||
export class TileStore<TLegacy = unknown> implements ITileStore<TLegacy> {
|
||||
/** 以图块数字为键的原始图块定义表 */
|
||||
private readonly dataMap: Map<number, ITileRawData> = new Map();
|
||||
|
||||
/** 由图块 id 反查图块数字的映射表 */
|
||||
private readonly idMap: Map<string, number> = new Map();
|
||||
|
||||
/** 由图块数字反查图块 id 的映射表 */
|
||||
private readonly numMap: Map<number, string> = new Map();
|
||||
|
||||
/** 当前挂载的旧样板图块转换器 */
|
||||
private legacyConverter: ITileLegacyConverter<TLegacy> | null = null;
|
||||
|
||||
getData(num: number): ITileRawData | null {
|
||||
return this.dataMap.get(num) ?? null;
|
||||
}
|
||||
|
||||
getTrigger(num: number): number {
|
||||
return this.dataMap.get(num)?.trigger ?? -1;
|
||||
}
|
||||
|
||||
getType(num: number): TileType {
|
||||
return this.dataMap.get(num)?.type ?? TileType.Unknown;
|
||||
}
|
||||
|
||||
addTile(data: ITileRawData): void {
|
||||
const oldData = this.dataMap.get(data.num);
|
||||
const oldNum = this.idMap.get(data.id);
|
||||
if (oldData) {
|
||||
logger.warn(133, data.num.toString(), oldData.id);
|
||||
this.deleteBy(oldData.num, oldData.id);
|
||||
}
|
||||
if (!isNil(oldNum) && oldNum !== data.num) {
|
||||
logger.warn(134, data.id, oldNum.toString());
|
||||
const oldIdData = this.dataMap.get(oldNum);
|
||||
if (oldIdData) {
|
||||
this.deleteBy(oldIdData.num, oldIdData.id);
|
||||
} else {
|
||||
this.idMap.delete(data.id);
|
||||
this.numMap.delete(oldNum);
|
||||
}
|
||||
}
|
||||
this.dataMap.set(data.num, data);
|
||||
this.idMap.set(data.id, data.num);
|
||||
this.numMap.set(data.num, data.id);
|
||||
}
|
||||
|
||||
idToNumber(id: string): number | null {
|
||||
return this.idMap.get(id) ?? null;
|
||||
}
|
||||
|
||||
numberToId(num: number): string | null {
|
||||
return this.numMap.get(num) ?? null;
|
||||
}
|
||||
|
||||
attachLegacyConverter(converter: ITileLegacyConverter<TLegacy>): void {
|
||||
this.legacyConverter = converter;
|
||||
}
|
||||
|
||||
fromLegacy(num: number, legacy: TLegacy): ITileRawData {
|
||||
const converter = this.legacyConverter;
|
||||
if (!converter) {
|
||||
logger.error(56);
|
||||
throw new Error('Expected a tile legacy converter');
|
||||
}
|
||||
const data = converter.fromLegacy(num, legacy);
|
||||
this.addTile(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/** 删除一组旧的图块定义及其双向索引 */
|
||||
private deleteBy(num: number, id: string): void {
|
||||
this.dataMap.delete(num);
|
||||
this.idMap.delete(id);
|
||||
this.numMap.delete(num);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
export const enum TileType {
|
||||
/** 未知或尚未归类的图块 */
|
||||
Unknown,
|
||||
/** 空白图块 */
|
||||
None,
|
||||
/** 地形类图块 */
|
||||
Terrain,
|
||||
/** 动画类图块 */
|
||||
Animate,
|
||||
/** 道具类图块 */
|
||||
Item,
|
||||
/** 怪物类图块 */
|
||||
Enemy,
|
||||
/** NPC 类图块 */
|
||||
Npc,
|
||||
/** 自动元件 */
|
||||
Autotile,
|
||||
/** Tileset 切片图块 */
|
||||
Tileset
|
||||
}
|
||||
|
||||
export interface ITileRawData {
|
||||
/** 图块数字 */
|
||||
readonly num: number;
|
||||
/** 图块字符串 id */
|
||||
readonly id: string;
|
||||
/** 默认触发器类型 */
|
||||
readonly trigger: number;
|
||||
/** 图块逻辑类型 */
|
||||
readonly type: TileType;
|
||||
}
|
||||
|
||||
export interface ITileLegacyConverter<TLegacy> {
|
||||
/**
|
||||
* 将旧样板图块定义转换为新的图块原始数据
|
||||
* @param num 图块数字
|
||||
* @param legacy 旧样板图块定义
|
||||
*/
|
||||
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
|
||||
}
|
||||
|
||||
export interface ITileStore<TLegacy = unknown> {
|
||||
/**
|
||||
* 获取指定图块数字对应的完整原始定义
|
||||
* @param num 图块数字
|
||||
*/
|
||||
getData(num: number): ITileRawData | null;
|
||||
|
||||
/**
|
||||
* 获取指定图块数字对应的默认触发器类型
|
||||
* @param num 图块数字
|
||||
*/
|
||||
getTrigger(num: number): number;
|
||||
|
||||
/**
|
||||
* 获取指定图块数字对应的图块类型
|
||||
* @param num 图块数字
|
||||
*/
|
||||
getType(num: number): TileType;
|
||||
|
||||
/**
|
||||
* 添加一个图块定义;若 `num` 或 `id` 冲突则警告并覆盖
|
||||
* @param data 图块原始定义
|
||||
*/
|
||||
addTile(data: ITileRawData): void;
|
||||
|
||||
/**
|
||||
* 根据图块 id 查询对应图块数字
|
||||
* @param id 图块 id
|
||||
*/
|
||||
idToNumber(id: string): number | null;
|
||||
|
||||
/**
|
||||
* 根据图块数字查询对应图块 id
|
||||
* @param num 图块数字
|
||||
*/
|
||||
numberToId(num: number): string | null;
|
||||
|
||||
/**
|
||||
* 挂载一个旧样板转换器
|
||||
* @param converter 旧样板转换器
|
||||
*/
|
||||
attachLegacyConverter(converter: ITileLegacyConverter<TLegacy>): void;
|
||||
|
||||
/**
|
||||
* 使用当前转换器转换并写入一个旧样板图块定义
|
||||
* @param num 图块数字
|
||||
* @param legacy 旧样板图块定义
|
||||
*/
|
||||
fromLegacy(num: number, legacy: TLegacy): ITileRawData;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IFaceManager, IRoleFaceBinder } from './common';
|
||||
import { ITileStore } from './store';
|
||||
|
||||
export interface IDataCommon {
|
||||
/** 图块定义存储 */
|
||||
readonly tileStore: ITileStore<MapDataOf<keyof NumberToId>>;
|
||||
/** 朝向绑定 */
|
||||
readonly roleFace: IRoleFaceBinder;
|
||||
/** 朝向管理 */
|
||||
readonly faceManager: IFaceManager;
|
||||
}
|
||||
|
||||
export interface IDataCommonExtended {
|
||||
/** 当前对象对应的基本数据端对象(Layer 0 对象) */
|
||||
readonly state: IDataCommon;
|
||||
}
|
||||
Reference in new issue
Block a user