feat: 将 HumanBreak 新内容移植入该样板

This commit is contained in:
AncTe committed 2026-02-28 22:56:59 +08:00
1 parent 6bf32174d8
commit bea2725d6a
128 files changed
+11984 -573

No files matched your search

@@ -0,0 +1,63 @@
import { logger } from '@motajs/common';
import { IFaceData, IRoleFaceBinder } from './types';
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,3 @@
export * from './face';
export * from './types';
export * from './utils';
@@ -0,0 +1,54 @@
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;
}
@@ -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;
}
}
+33
View File
@@ -0,0 +1,33 @@
import { ICoreState, IStateSaveData } from './types';
import { IHeroState, HeroState } from './hero';
import { ILayerState, LayerState } from './map';
import { IRoleFaceBinder, RoleFaceBinder } from './common';
export class CoreState implements ICoreState {
readonly layer: ILayerState;
readonly hero: IHeroState;
readonly roleFace: IRoleFaceBinder;
readonly idNumberMap: Map<string, number>;
readonly numberIdMap: Map<number, string>;
constructor() {
this.layer = new LayerState();
this.hero = new HeroState();
this.roleFace = new RoleFaceBinder();
this.idNumberMap = new Map();
this.numberIdMap = new Map();
}
saveState(): IStateSaveData {
return structuredClone({
followers: this.hero.followers
});
}
loadState(data: IStateSaveData): void {
this.hero.removeAllFollowers();
data?.followers.forEach(v => {
this.hero.addFollower(v.num, v.identifier);
});
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { getHeroStatusOf, getHeroStatusOn } from '../state/hero';
import { getHeroStatusOf, getHeroStatusOn } from '../legacy/hero';
import { Range, ensureArray, has, manhattan } from '@user/data-utils';
import EventEmitter from 'eventemitter3';
import { hook } from '@user/data-base';
@@ -1,5 +1,5 @@
import { EnemyInfo } from '@motajs/types';
import { getHeroStatusOn } from '../state/hero';
import { getHeroStatusOn } from '../legacy/hero';
export interface SpecialDeclaration {
code: number;
@@ -0,0 +1,2 @@
export * from './state';
export * from './types';
+133
View File
@@ -0,0 +1,133 @@
import { Hookable, HookController, IHookController } from '@motajs/common';
import { IHeroFollower, IHeroState, IHeroStateHooks } from './types';
import { FaceDirection, getFaceMovement, nextFaceDirection } from '../common';
import { isNil } from 'lodash-es';
import { DEFAULT_HERO_IMAGE } from '../shared';
export class HeroState extends Hookable<IHeroStateHooks> implements IHeroState {
x: number = 0;
y: number = 0;
direction: FaceDirection = FaceDirection.Down;
image: ImageIds = DEFAULT_HERO_IMAGE;
/** 当前勇士是否正在移动 */
moving: boolean = false;
alpha: number = 1;
readonly followers: IHeroFollower[] = [];
protected createController(
hook: Partial<IHeroStateHooks>
): IHookController<IHeroStateHooks> {
return new HookController(this, hook);
}
setPosition(x: number, y: number): void {
this.x = x;
this.y = y;
this.forEachHook(hook => {
hook.onSetPosition?.(x, y);
});
}
turn(direction?: FaceDirection): void {
const next = isNil(direction)
? nextFaceDirection(this.direction)
: direction;
this.direction = next;
this.forEachHook(hook => {
hook.onTurnHero?.(next);
});
}
startMove(): void {
this.moving = true;
this.forEachHook(hook => {
hook.onStartMove?.();
});
}
async move(dir: FaceDirection, time: number = 100): Promise<void> {
await Promise.all(
this.forEachHook(hook => {
return hook.onMoveHero?.(dir, time);
})
);
const { x, y } = getFaceMovement(dir);
this.x += x;
this.y += y;
}
async endMove(): Promise<void> {
if (!this.moving) return;
await Promise.all(
this.forEachHook(hook => {
return hook.onEndMove?.();
})
);
this.moving = false;
}
async jumpHero(
x: number,
y: number,
time: number = 500,
waitFollower: boolean = false
): Promise<void> {
await Promise.all(
this.forEachHook(hook => {
return hook.onJumpHero?.(x, y, time, waitFollower);
})
);
this.x = x;
this.y = y;
}
setImage(image: ImageIds): void {
this.image = image;
this.forEachHook(hook => {
hook.onSetImage?.(image);
});
}
setAlpha(alpha: number): void {
this.alpha = alpha;
this.forEachHook(hook => {
hook.onSetAlpha?.(alpha);
});
}
setFollowerAlpha(identifier: string, alpha: number): void {
const follower = this.followers.find(v => v.identifier === identifier);
if (!follower) return;
follower.alpha = alpha;
this.forEachHook(hook => {
hook.onSetFollowerAlpha?.(identifier, alpha);
});
}
addFollower(follower: number, identifier: string): void {
this.followers.push({ num: follower, identifier, alpha: 1 });
this.forEachHook(hook => {
hook.onAddFollower?.(follower, identifier);
});
}
removeFollower(identifier: string, animate: boolean = false): void {
const index = this.followers.findIndex(
v => v.identifier === identifier
);
if (index === -1) return;
this.followers.splice(index, 1);
this.forEachHook(hook => {
hook.onRemoveFollower?.(identifier, animate);
});
}
removeAllFollowers(): void {
this.followers.length = 0;
this.forEachHook(hook => {
hook.onRemoveAllFollowers?.();
});
}
}
+204
View File
@@ -0,0 +1,204 @@
import { IHookBase, IHookable } from '@motajs/common';
import { FaceDirection } from '../common/types';
export const enum HeroAnimateDirection {
/** 正向播放动画 */
Forward,
/** 反向播放动画 */
Backward
}
export interface IHeroFollower {
/** 跟随者的图块数字 */
readonly num: number;
/** 跟随者的标识符 */
readonly identifier: string;
/** 跟随者的不透明度 */
alpha: number;
}
export interface IHeroStateHooks extends IHookBase {
/**
* 当设置勇士的坐标时触发
* @param controller 钩子控制器
* @param x 勇士横坐标
* @param y 勇士纵坐标
*/
onSetPosition(x: number, y: number): void;
/**
* 当设置勇士朝向时触发
* @param direction 勇士朝向
*/
onTurnHero(direction: FaceDirection): void;
/**
* 当勇士开始移动时触发
*/
onStartMove(): void;
/**
* 当移动勇士时触发
* @param controller 钩子控制器
* @param direction 移动方向
* @param time 移动动画时长
*/
onMoveHero(direction: FaceDirection, time: number): Promise<void>;
/**
* 当停止移动时触发
*/
onEndMove(): Promise<void>;
/**
* 当勇士跳跃时触发
* @param x 目标点横坐标
* @param y 目标点纵坐标
* @param time 跳跃动画时长
* @param waitFollower 是否等待跟随者跳跃完毕
*/
onJumpHero(
x: number,
y: number,
time: number,
waitFollower: boolean
): Promise<void>;
/**
* 当设置勇士图片时触发
* @param image 勇士图片 id
*/
onSetImage(image: ImageIds): void;
/**
* 当设置勇士不透明度时执行
* @param alpha 不透明度
*/
onSetAlpha(alpha: number): void;
/**
* 添加跟随者时触发
* @param follower 跟随者的图块数字
* @param identifier 跟随者的标识符
*/
onAddFollower(follower: number, identifier: string): void;
/**
* 当移除跟随者时触发
* @param identifier 跟随者的标识符
* @param animate 填 `true` 的话,如果删除了中间的跟随者,后续跟随者会使用移动动画移动到下一格,否则瞬移至下一格
*/
onRemoveFollower(identifier: string, animate: boolean): void;
/**
* 当移除所有跟随者时触发
*/
onRemoveAllFollowers(): void;
/**
* 设置跟随者的不透明度
* @param identifier 跟随者标识符
* @param alpha 跟随者不透明度
*/
onSetFollowerAlpha(identifier: string, alpha: number): void;
}
export interface IHeroState extends IHookable<IHeroStateHooks> {
/** 勇士横坐标 */
readonly x: number;
/** 勇士纵坐标 */
readonly y: number;
/** 勇士朝向 */
readonly direction: FaceDirection;
/** 勇士图片 */
readonly image?: ImageIds;
/** 跟随者列表 */
readonly followers: readonly IHeroFollower[];
/** 勇士当前的不透明度 */
readonly alpha: number;
/**
* 设置勇士位置
* @param x 横坐标
* @param y 纵坐标
*/
setPosition(x: number, y: number): void;
/**
* 设置勇士朝向
* @param direction 勇士朝向,不填表示顺时针旋转
*/
turn(direction?: FaceDirection): void;
/**
* 开始勇士移动,在移动前必须先调用此方法将勇士切换为移动状态
*/
startMove(): void;
/**
* 移动勇士。能否移动的逻辑暂时不在这里,目前作为过渡作用,仅服务于渲染
* @param dir 移动方向
* @param time 移动动画时长,默认 100ms
* @returns 移动的 `Promise`,当相关的移动动画结束后兑现
*/
move(dir: FaceDirection, time?: number): Promise<void>;
/**
* 结束勇士移动
* @returns 当移动动画结束后兑现的 `Promise`
*/
endMove(): Promise<void>;
/**
* 跳跃勇士至目标点
* @param x 目标点横坐标
* @param y 目标点纵坐标
* @param time 跳跃动画时长,默认 500ms
* @param waitFollower 是否等待跟随者跳跃完毕,默认不等待
* @returns 跳跃的 `Promise`,当相关的移动动画结束后兑现
*/
jumpHero(
x: number,
y: number,
time?: number,
waitFollower?: boolean
): Promise<void>;
/**
* 设置勇士图片
* @param image 图片 id
*/
setImage(image: ImageIds): void;
/**
* 设置勇士的不透明度
* @param alpha 不透明度
*/
setAlpha(alpha: number): void;
/**
* 添加一个跟随者
* @param follower 跟随者的图块数字
* @param identifier 跟随者的标识符,可以用来移除
*/
addFollower(follower: number, identifier: string): void;
/**
* 移除指定的跟随者
* @param identifier 跟随者的标识符
* @param animate 填 `true` 的话,如果删除了中间的跟随者,后续跟随者会使用移动动画移动到下一格,否则瞬移至下一格
*/
removeFollower(identifier: string, animate?: boolean): void;
/**
* 移除所有跟随者
*/
removeAllFollowers(): void;
/**
* 设置指定跟随者的不透明度
* @param identifier 跟随者标识符
* @param alpha 跟随者不透明度
*/
setFollowerAlpha(identifier: string, alpha: number): void;
}
+74 -1
View File
@@ -1,2 +1,75 @@
import { loading } from '@user/data-base';
import { CoreState } from './core';
import { isNil } from 'lodash-es';
import { FaceDirection } from './common';
function createCoreState() {
//#region 地图部分
const width = core._WIDTH_;
const height = core._HEIGHT_;
const bg = state.layer.addLayer(width, height);
const bg2 = state.layer.addLayer(width, height);
const event = state.layer.addLayer(width, height);
const fg = state.layer.addLayer(width, height);
const fg2 = state.layer.addLayer(width, height);
state.layer.setLayerAlias(bg, 'bg');
state.layer.setLayerAlias(bg2, 'bg2');
state.layer.setLayerAlias(event, 'event');
state.layer.setLayerAlias(fg, 'fg');
state.layer.setLayerAlias(fg2, 'fg2');
//#endregion
//#region 图块部分
const data = Object.entries(core.maps.blocksInfo);
for (const [key, block] of data) {
const num = Number(key);
state.idNumberMap.set(block.id, num);
state.numberIdMap.set(num, block.id);
}
for (const [key, block] of data) {
if (!block.faceIds) continue;
const { down, up, left, right } = block.faceIds;
const downNum = state.idNumberMap.get(down);
if (downNum !== Number(key)) continue;
const upNum = state.idNumberMap.get(up);
const leftNum = state.idNumberMap.get(left);
const rightNum = state.idNumberMap.get(right);
state.roleFace.malloc(downNum, FaceDirection.Down);
if (!isNil(upNum)) {
state.roleFace.bind(upNum, downNum, FaceDirection.Up);
}
if (!isNil(leftNum)) {
state.roleFace.bind(leftNum, downNum, FaceDirection.Left);
}
if (!isNil(rightNum)) {
state.roleFace.bind(rightNum, downNum, FaceDirection.Right);
}
}
//#endregion
}
export function create() {
loading.once('loaded', () => {
// 加载后初始化全局状态
createCoreState();
});
}
/**
* 数据端核心状态,目前处于过渡阶段,仅服务于渲染,不负责任何逻辑计算,会在后续把核心逻辑逐渐移动至此对象。
* 此对象是数据端状态,本身不负责任何渲染操作,仅会向渲染端发送数据让渲染端渲染,不要把渲染操作直接放到此对象上,
* 否则可能导致录像验证失败。
*/
export const state = new CoreState();
export * from './common';
export * from './core';
export * from './enemy';
export * from './state';
export * from './hero';
export * from './map';
export * from './legacy';
+115
View File
@@ -0,0 +1,115 @@
/**
* 获取勇士在某一点的属性
* @param name 要获取的勇士属性
* @param floorId 勇士所在楼层
*/
export function getHeroStatusOn(name: 'all', floorId?: FloorIds): HeroStatus;
export function getHeroStatusOn(
name: (keyof HeroStatus)[],
floorId?: FloorIds
): Partial<HeroStatus>;
export function getHeroStatusOn<K extends keyof HeroStatus>(
name: K,
floorId?: FloorIds
): HeroStatus[K];
export function getHeroStatusOn(
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId?: FloorIds
) {
// @ts-expect-error 暂时无法推导
return getHeroStatusOf(core.status.hero, name, floorId);
}
/**
* 获取一定状态下的勇士在某一点的属性
* @param status 勇士的状态
* @param name 要获取的勇士属性
* @param floorId 勇士所在楼层
*/
export function getHeroStatusOf(
status: Partial<HeroStatus>,
name: 'all',
floorId?: FloorIds
): HeroStatus;
export function getHeroStatusOf(
status: Partial<HeroStatus>,
name: (keyof HeroStatus)[],
floorId?: FloorIds
): Partial<HeroStatus>;
export function getHeroStatusOf<K extends keyof HeroStatus>(
status: Partial<HeroStatus>,
name: K,
floorId?: FloorIds
): HeroStatus[K];
export function getHeroStatusOf(
status: DeepPartial<HeroStatus>,
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId?: FloorIds
) {
return getRealStatus(status, name, floorId);
}
function getRealStatus(
status: DeepPartial<HeroStatus>,
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId: FloorIds = core.status.floorId
): any {
if (name instanceof Array) {
const res: any = {};
name.forEach(v => {
res[v] = getRealStatus(status, v, floorId);
});
return res;
}
if (name === 'all') {
const res: any = {};
for (const [key, value] of Object.entries(core.status.hero)) {
if (typeof value === 'number') {
res[key] = getRealStatus(
status,
key as keyof HeroStatus,
floorId
);
} else {
res[key] = value;
}
}
return res;
}
let s = (status[name] ?? core.status.hero[name]) as number;
if (s === null || s === void 0) {
throw new ReferenceError(
`Wrong hero status property name is delivered: ${name}`
);
}
if (typeof s !== 'number') return s;
// buff
s *= core.status.hero.buff[name] ?? 1;
s = Math.floor(s);
// 衰弱效果
if ((name === 'atk' || name === 'def') && flags.weak) {
const weak = core.values.weakValue;
if (weak < 1) {
// 百分比衰弱
s *= 1 - weak;
} else {
s -= weak;
}
}
return s;
}
// 下面的内容暂时无用
export interface IHeroStatusDefault {
atk: number;
def: number;
hp: number;
}
@@ -0,0 +1,3 @@
export * from './layerState';
export * from './mapLayer';
export * from './types';
@@ -0,0 +1,139 @@
import {
Hookable,
HookController,
IHookController,
logger
} from '@motajs/common';
import {
ILayerState,
ILayerStateHooks,
IMapLayer,
IMapLayerHookController,
IMapLayerHooks
} from './types';
import { MapLayer } from './mapLayer';
export class LayerState
extends Hookable<ILayerStateHooks>
implements ILayerState
{
readonly layerList: Set<IMapLayer> = new Set();
/** 图层到图层别名映射 */
readonly layerAliasMap: WeakMap<IMapLayer, string> = new WeakMap();
/** 图层别名到图层的映射 */
readonly aliasLayerMap: Map<symbol, IMapLayer> = new Map();
/** 背景图块 */
private backgroundTile: number = 0;
/** 图层钩子映射 */
private layerHookMap: Map<IMapLayer, IMapLayerHookController> = new Map();
addLayer(width: number, height: number): IMapLayer {
const array = new Uint32Array(width * height);
const layer = new MapLayer(array, width, height);
this.layerList.add(layer);
this.forEachHook(hook => {
hook.onUpdateLayer?.(this.layerList);
});
const controller = layer.addHook(new StateMapLayerHook(this, layer));
this.layerHookMap.set(layer, controller);
controller.load();
return layer;
}
removeLayer(layer: IMapLayer): void {
this.layerList.delete(layer);
const alias = this.layerAliasMap.get(layer);
if (alias) {
const symbol = Symbol.for(alias);
this.aliasLayerMap.delete(symbol);
this.layerAliasMap.delete(layer);
}
this.forEachHook(hook => {
hook.onUpdateLayer?.(this.layerList);
});
const controller = this.layerHookMap.get(layer);
if (!controller) return;
controller.unload();
this.layerHookMap.delete(layer);
}
hasLayer(layer: IMapLayer): boolean {
return this.layerList.has(layer);
}
setLayerAlias(layer: IMapLayer, alias: string): void {
const symbol = Symbol.for(alias);
if (this.aliasLayerMap.has(symbol)) {
logger.warn(84, alias);
return;
}
this.layerAliasMap.set(layer, alias);
this.aliasLayerMap.set(symbol, layer);
}
getLayerByAlias(alias: string): IMapLayer | null {
const symbol = Symbol.for(alias);
return this.aliasLayerMap.get(symbol) ?? null;
}
getLayerAlias(layer: IMapLayer): string | undefined {
return this.layerAliasMap.get(layer);
}
resizeLayer(
layer: IMapLayer,
width: number,
height: number,
keepBlock: boolean = false
): void {
if (keepBlock) {
layer.resize(width, height);
} else {
layer.resize2(width, height);
}
}
setBackground(tile: number): void {
this.backgroundTile = tile;
this.forEachHook(hook => {
hook.onChangeBackground?.(tile);
});
}
getBackground(): number {
return this.backgroundTile;
}
protected createController(
hook: Partial<ILayerStateHooks>
): IHookController<ILayerStateHooks> {
return new HookController(this, hook);
}
}
class StateMapLayerHook implements Partial<IMapLayerHooks> {
constructor(
readonly state: LayerState,
readonly layer: IMapLayer
) {}
onUpdateArea(x: number, y: number, width: number, height: number): void {
this.state.forEachHook(hook => {
hook.onUpdateLayerArea?.(this.layer, x, y, width, height);
});
}
onUpdateBlock(block: number, x: number, y: number): void {
this.state.forEachHook(hook => {
hook.onUpdateLayerBlock?.(this.layer, block, x, y);
});
}
onResize(width: number, height: number): void {
this.state.forEachHook(hook => {
hook.onResizeLayer?.(this.layer, width, height);
});
}
}
@@ -0,0 +1,263 @@
import { isNil } from 'lodash-es';
import {
IMapLayer,
IMapLayerData,
IMapLayerHookController,
IMapLayerHooks
} from './types';
import { Hookable, HookController, logger } from '@motajs/common';
// todo: 提供 core.setBlock 等方法的替代方法,同时添加 setBlockList,以及前景背景的接口
export class MapLayer
extends Hookable<IMapLayerHooks, IMapLayerHookController>
implements IMapLayer
{
width: number;
height: number;
empty: boolean = true;
zIndex: number = 0;
/** 地图图块数组 */
private mapArray: Uint32Array;
/** 地图数据引用 */
private mapData: IMapLayerData;
constructor(array: Uint32Array, width: number, height: number) {
super();
this.width = width;
this.height = height;
const area = width * height;
this.mapArray = new Uint32Array(area);
// 超出的裁剪,不足的补零
this.mapArray.set(array);
this.mapData = {
expired: false,
array: this.mapArray
};
}
resize(width: number, height: number): void {
if (this.width === width && this.height === height) {
return;
}
this.mapData.expired = true;
const before = this.mapArray;
const beforeWidth = this.width;
const beforeHeight = this.height;
const beforeArea = beforeWidth * beforeHeight;
this.width = width;
this.height = height;
const area = width * height;
const newArray = new Uint32Array(area);
this.mapArray = newArray;
// 将原来的地图数组赋值给现在的
if (beforeArea > area) {
// 如果地图变小了,那么直接设置,不需要补零
for (let ny = 0; ny < height; ny++) {
const begin = ny * beforeWidth;
newArray.set(before.subarray(begin, begin + width), ny * width);
}
} else {
// 如果地图变大了,那么需要补零。因为新数组本来就是用 0 填充的,实际上只要赋值就可以了
for (let ny = 0; ny < beforeHeight; ny++) {
const begin = ny * beforeWidth;
newArray.set(
before.subarray(begin, begin + beforeWidth),
ny * width
);
}
}
this.mapData = {
expired: false,
array: this.mapArray
};
this.forEachHook(hook => {
hook.onResize?.(width, height);
});
}
resize2(width: number, height: number): void {
if (this.width === width && this.height === height) {
this.mapArray.fill(0);
return;
}
this.mapData.expired = true;
this.width = width;
this.height = height;
this.mapArray = new Uint32Array(width * height);
this.mapData = {
expired: false,
array: this.mapArray
};
this.empty = true;
this.forEachHook(hook => {
hook.onResize?.(width, height);
});
}
setBlock(block: number, x: number, y: number): void {
const index = y * this.width + x;
if (block === this.mapArray[index]) return;
this.mapArray[index] = block;
this.forEachHook(hook => {
hook.onUpdateBlock?.(block, x, y);
});
if (block !== 0) {
this.empty = false;
}
}
getBlock(x: number, y: number): number {
if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
// 不在地图内,返回 -1
return -1;
}
return this.mapArray[y * this.width + x];
}
putMapData(array: Uint32Array, x: number, y: number, width: number): void {
if (array.length % width !== 0) {
logger.warn(8);
}
const height = Math.ceil(array.length / width);
if (width === this.width && height === this.height) {
this.mapArray.set(array);
this.forEachHook(hook => {
hook.onUpdateArea?.(x, y, width, height);
});
return;
}
const w = this.width;
const r = x + width;
const b = y + height;
if (x < 0 || y < 0 || r > w || b > this.height) {
logger.warn(9);
}
const nl = Math.max(x, 0);
const nt = Math.max(y, 0);
const nr = Math.min(r, w);
const nb = Math.min(b, this.height);
const nw = nr - nl;
const nh = nb - nt;
let empty = true;
for (let ny = 0; ny < nh; ny++) {
const start = ny * nw;
const offset = (ny + nt) * w + nl;
const sub = array.subarray(start, start + nw);
if (empty && sub.some(v => v !== 0)) {
// 空地图判断
empty = false;
}
this.mapArray.set(array.subarray(start, start + nw), offset);
}
this.forEachHook(hook => {
hook.onUpdateArea?.(x, y, width, height);
});
this.empty &&= empty;
}
getMapData(): Uint32Array;
getMapData(
x: number,
y: number,
width: number,
height: number
): Uint32Array;
getMapData(
x?: number,
y?: number,
width?: number,
height?: number
): Uint32Array {
if (isNil(x)) {
return new Uint32Array(this.mapArray);
}
if (isNil(y) || isNil(width) || isNil(height)) {
logger.warn(80);
return new Uint32Array();
}
const w = this.width;
const h = this.height;
const r = x + width;
const b = y + height;
if (x < 0 || y < 0 || r > w || b > h) {
logger.warn(81);
}
const res = new Uint32Array(width * height);
const arr = this.mapArray;
const nr = Math.min(r, w);
const nb = Math.min(b, h);
for (let nx = x; nx < nr; nx++) {
for (let ny = y; ny < nb; ny++) {
const origin = ny * w + nx;
const target = (ny - y) * width + (nx - x);
res[target] = arr[origin];
}
}
return res;
}
/**
* 获取地图数据的内部存储直接引用
*/
getMapRef(): IMapLayerData {
return this.mapData;
}
protected createController(
hook: Partial<IMapLayerHooks>
): IMapLayerHookController {
return new MapLayerHookController(this, hook);
}
setZIndex(zIndex: number): void {
this.zIndex = zIndex;
}
async openDoor(x: number, y: number): Promise<void> {
const index = y * this.width + x;
const num = this.mapArray[index];
if (num === 0) return;
await Promise.all(
this.forEachHook(hook => {
return hook.onOpenDoor?.(x, y);
})
);
this.setBlock(0, x, y);
}
async closeDoor(num: number, x: number, y: number): Promise<void> {
const index = y * this.width + x;
const nowNum = this.mapArray[index];
if (nowNum !== 0) {
logger.error(46, x.toString(), y.toString());
return;
}
await Promise.all(
this.forEachHook(hook => {
return hook.onCloseDoor?.(num, x, y);
})
);
this.setBlock(num, x, y);
}
}
class MapLayerHookController
extends HookController<IMapLayerHooks>
implements IMapLayerHookController
{
hookable: MapLayer;
constructor(
readonly layer: MapLayer,
hook: Partial<IMapLayerHooks>
) {
super(layer, hook);
this.hookable = layer;
}
getMapData(): Readonly<IMapLayerData> {
return this.layer.getMapRef();
}
}
+278
View File
@@ -0,0 +1,278 @@
import { IHookable, IHookBase, IHookController } from '@motajs/common';
export interface IMapLayerData {
/** 当前引用是否过期,当地图图层内部的地图数组引用更新时,此项会变为 `true` */
expired: boolean;
/** 地图图块数组,是对内部存储的直接引用 */
array: Uint32Array;
}
export interface IMapLayerHooks extends IHookBase {
/**
* 当地图大小发生变化时执行,如果调用了地图的 `resize` 方法,但是地图大小没变,则不会触发
* @param width 地图宽度
* @param height 地图高度
*/
onResize(width: number, height: number): void;
/**
* 当更新某个区域的图块时执行
* @param x 更新区域左上角横坐标
* @param y 更新区域左上角纵坐标
* @param width 更新区域宽度
* @param height 更新区域高度
*/
onUpdateArea(x: number, y: number, width: number, height: number): void;
/**
* 当更新某个点的图块时执行,如果设置的图块与原先一样,则不会触发此方法
* @param block 更新为的图块数字
* @param x 更新点横坐标
* @param y 更新点纵坐标
*/
onUpdateBlock(block: number, x: number, y: number): void;
/**
* 当开门时触发,返回一个 `Promise`,当相关动画执行完毕后兑现
* @param x 门横坐标
* @param y 门纵坐标
*/
onOpenDoor(x: number, y: number): Promise<void>;
/**
* 当关门时触发,返回一个 `Promise`,当相关动画执行完毕后兑现
* @param num 门的图块数字
* @param x 门横坐标
* @param y 门纵坐标
*/
onCloseDoor(num: number, x: number, y: number): Promise<void>;
}
export interface IMapLayerHookController
extends IHookController<IMapLayerHooks> {
/** 拓展所属的图层对象 */
readonly layer: IMapLayer;
/**
* 获取地图数据,是对内部存储的直接引用
*/
getMapData(): Readonly<IMapLayerData>;
}
export interface IMapLayer
extends IHookable<IMapLayerHooks, IMapLayerHookController> {
/** 地图宽度 */
readonly width: number;
/** 地图高度 */
readonly height: number;
/**
* 地图是否全部空白,此值具有充分性,但不具有必要性,
* 即如果其为 `true`,则地图一定空白,但是如果其为 `false`,那么地图也有可能空白
*/
readonly empty: boolean;
/** 图层纵深 */
readonly zIndex: number;
/**
* 调整地图尺寸,维持原有图块。如果尺寸变大,那么会补零,如果尺寸变小,那么会将当前数组裁剪
* @param width 地图宽度
* @param height 地图高度
*/
resize(width: number, height: number): void;
/**
* 调整地图尺寸,但是将地图全部重置为零,不保留原地图数据
* @param width 地图宽度
* @param height 地图高度
*/
resize2(width: number, height: number): void;
/**
* 设置某一点的图块
* @param block 图块数字
* @param x 图块横坐标
* @param y 图块纵坐标
*/
setBlock(block: number, x: number, y: number): void;
/**
* 获取指定点的图块
* @param x 图块横坐标
* @param y 图块纵坐标
* @returns 指定点的图块,如果没有图块,返回 0,如果不在地图上,返回 -1
*/
getBlock(x: number, y: number): number;
/**
* 设置地图图块
* @param array 地图图块数组
* @param x 数组第一项代表的横坐标
* @param y 数组第一项代表的纵坐标
* @param width 传入数组所表示的矩形范围的宽度
*/
putMapData(array: Uint32Array, x: number, y: number, width: number): void;
/**
* 获取整个地图的地图数组,是对内部地图数组的拷贝,并不能通过修改它来直接修改地图内容
*/
getMapData(): Uint32Array;
/**
* 获取地图指定区域的地图数组,是对内部地图数组的拷贝,并不能通过修改它来直接修改地图内容
* @param x 左上角横坐标
* @param y 左上角纵坐标
* @param width 获取区域的宽度
* @param height 获取区域的高度
*/
getMapData(
x: number,
y: number,
width: number,
height: number
): Uint32Array;
/**
* 获取整个地图的地图数组,是对内部数组的直接引用
*/
getMapRef(): IMapLayerData;
/**
* 设置地图纵深,会影响渲染的遮挡顺序
* @param zIndex 纵深
*/
setZIndex(zIndex: number): void;
/**
* 开启指定位置的门
* @param x 门横坐标
* @param y 门纵坐标
*/
openDoor(x: number, y: number): Promise<void>;
/**
* 在指定位置关门,门的图块数字由参数指定
* @param num 门图块数字
* @param x 门横坐标
* @param y 门纵坐标
*/
closeDoor(num: number, x: number, y: number): Promise<void>;
}
export interface ILayerStateHooks extends IHookBase {
/**
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
* @param tile 背景图块
*/
onChangeBackground(tile: number): void;
/**
* 当地图列表发生变化时执行
* @param layerList 地图图层列表
*/
onUpdateLayer(layerList: Set<IMapLayer>): void;
/**
* 当地图状态对象的某个图层发生区域更新时执行
* @param layer 触发更新的地图图层对象
* @param x 更新区域左上角横坐标
* @param y 更新区域左上角纵坐标
* @param width 更新区域宽度
* @param height 更新区域高度
*/
onUpdateLayerArea(
layer: IMapLayer,
x: number,
y: number,
width: number,
height: number
): void;
/**
* 当地图状态对象的某个图层设置图块时执行,如果设置的图块与原先一样则不会触发
* @param layer 触发更新的地图图层对象
* @param block 设置为的图块
* @param x 图块横坐标
* @param y 图块纵坐标
*/
onUpdateLayerBlock(
layer: IMapLayer,
block: number,
x: number,
y: number
): void;
/**
* 当地图状态对象的某个图层大小发生变化时执行
* @param layer 触发更新的地图图层对象
* @param width 地图的新宽度
* @param height 地图的新高度
*/
onResizeLayer(layer: IMapLayer, width: number, height: number): void;
}
export interface ILayerState extends IHookable<ILayerStateHooks> {
/** 地图列表 */
readonly layerList: Set<IMapLayer>;
/**
* 添加图层
* @param width 地图宽度
* @param height 地图高度
*/
addLayer(width: number, height: number): IMapLayer;
/**
* 移除指定图层
* @param layer 图层对象
*/
removeLayer(layer: IMapLayer): void;
/**
* 当前地图状态对象是否包含指定图层对象
* @param layer 图层对象
*/
hasLayer(layer: IMapLayer): boolean;
/**
* 设置图层别名
* @param layer 图层对象
* @param alias 图层别名
*/
setLayerAlias(layer: IMapLayer, alias: string): void;
/**
* 根据图层别名获取图层对象
* @param alias 图层别名
*/
getLayerByAlias(alias: string): IMapLayer | null;
/**
* 获取图层对象的别名
* @param layer 图层对象
*/
getLayerAlias(layer: IMapLayer): string | undefined;
/**
* 重新设置图层的大小
* @param layer 图层对象
* @param width 新的图层宽度
* @param height 新的图层高度
* @param keepBlock 是否保留原有图块,默认不保留
*/
resizeLayer(
layer: IMapLayer,
width: number,
height: number,
keepBlock?: boolean
): void;
/**
* 设置背景图块
* @param tile 背景图块数字
*/
setBackground(tile: number): void;
/**
* 获取背景图块数字,如果没有设置过,则返回 0
*/
getBackground(): number;
}
+2
View File
@@ -0,0 +1,2 @@
/** 默认的勇士图片 */
export const DEFAULT_HERO_IMAGE: ImageIds = 'hero.png';
-332
View File
@@ -1,332 +0,0 @@
import { logger } from '@motajs/common';
import { EventEmitter } from 'eventemitter3';
import { cloneDeep } from 'lodash-es';
/**
* 获取勇士在某一点的属性
* @param name 要获取的勇士属性
* @param floorId 勇士所在楼层
*/
export function getHeroStatusOn(name: 'all', floorId?: FloorIds): HeroStatus;
export function getHeroStatusOn(
name: (keyof HeroStatus)[],
floorId?: FloorIds
): Partial<HeroStatus>;
export function getHeroStatusOn<K extends keyof HeroStatus>(
name: K,
floorId?: FloorIds
): HeroStatus[K];
export function getHeroStatusOn(
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId?: FloorIds
) {
// @ts-expect-error 暂时无法推导
return getHeroStatusOf(core.status.hero, name, floorId);
}
/**
* 获取一定状态下的勇士在某一点的属性
* @param status 勇士的状态
* @param name 要获取的勇士属性
* @param floorId 勇士所在楼层
*/
export function getHeroStatusOf(
status: Partial<HeroStatus>,
name: 'all',
floorId?: FloorIds
): HeroStatus;
export function getHeroStatusOf(
status: Partial<HeroStatus>,
name: (keyof HeroStatus)[],
floorId?: FloorIds
): Partial<HeroStatus>;
export function getHeroStatusOf<K extends keyof HeroStatus>(
status: Partial<HeroStatus>,
name: K,
floorId?: FloorIds
): HeroStatus[K];
export function getHeroStatusOf(
status: DeepPartial<HeroStatus>,
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId?: FloorIds
) {
return getRealStatus(status, name, floorId);
}
function getRealStatus(
status: DeepPartial<HeroStatus>,
name: keyof HeroStatus | 'all' | (keyof HeroStatus)[],
floorId: FloorIds = core.status.floorId
): any {
if (name instanceof Array) {
const res: any = {};
name.forEach(v => {
res[v] = getRealStatus(status, v, floorId);
});
return res;
}
if (name === 'all') {
const res: any = {};
for (const [key, value] of Object.entries(core.status.hero)) {
if (typeof value === 'number') {
res[key] = getRealStatus(
status,
key as keyof HeroStatus,
floorId
);
} else {
res[key] = value;
}
}
return res;
}
let s = (status[name] ?? core.status.hero[name]) as number;
if (s === null || s === void 0) {
throw new ReferenceError(
`Wrong hero status property name is delivered: ${name}`
);
}
if (typeof s !== 'number') return s;
// buff
s *= core.status.hero.buff[name] ?? 1;
s = Math.floor(s);
// 衰弱效果
if ((name === 'atk' || name === 'def') && flags.weak) {
const weak = core.values.weakValue;
if (weak < 1) {
// 百分比衰弱
s *= 1 - weak;
} else {
s -= weak;
}
}
return s;
}
// 下面的内容暂时无用
export interface IHeroStatusDefault {
atk: number;
def: number;
hp: number;
}
interface HeroStateEvent {
set: [key: string | number | symbol, value: any];
}
type HeroStatusCalculate = (
hero: HeroState<any>,
key: string | number | symbol,
value: any
) => any;
export class HeroState<
T extends object = IHeroStatusDefault
> extends EventEmitter<HeroStateEvent> {
readonly status: T;
readonly computedStatus: T;
readonly buffable: Set<keyof T> = new Set();
readonly buffMap: Map<keyof T, number> = new Map();
private static cal: HeroStatusCalculate = (_0, _1, value) => value;
constructor(init: T) {
super();
this.status = init;
this.computedStatus = cloneDeep(init);
}
/**
* 设置某个属性的值
* @param key 要设置的属性
* @param value 属性值
* @returns 是否设置成功
*/
setStatus<K extends keyof T>(key: K, value: T[K]): boolean {
this.status[key] = value;
this.emit('set', key, value);
return this.refreshStatus(key);
}
/**
* 增加或减少一个属性的值,只对数字型的属性有效
* @param key 要修改的属性
* @param value 属性的增量
* @returns 是否设置成功
*/
addStatus<K extends SelectKey<T, number>>(key: K, value: number): boolean {
if (typeof this.status[key] !== 'number') {
logger.warn(14, String(key));
return false;
}
return this.setStatus<K>(key, (this.status[key] + value) as T[K]);
}
/**
* 获取某个属性的原始值
* @param key 要获取的属性
* @returns 属性的值
*/
getStatus<K extends keyof T>(key: K): T[K] {
return this.status[key];
}
/**
* 获取一个属性计算后的值,也就是2.x所说的勇士真实属性
* @param key 要获取的属性值
*/
getComputedStatus<K extends keyof T>(key: K): T[K] {
return this.computedStatus[key];
}
/**
* 标记某个属性为可以被buff加成
*/
markBuffable(key: SelectKey<T, number>): void {
if (typeof this.status[key] !== 'number') {
logger.warn(12, String(key));
return;
}
this.buffable.add(key);
this.buffMap.set(key, 1);
}
/**
* 设置某个属性的buff值
* @param key 要设置buff的属性
* @param value buff值
* @returns 是否设置成功
*/
setBuff(key: SelectKey<T, number>, value: number): boolean {
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
logger.warn(13, String(key));
return false;
}
this.buffMap.set(key, value);
return this.refreshStatus(key);
}
/**
* 增加或减少一个buff值
* @param key 要修改的buff属性
* @param value buff增量
* @returns 是否修改成功
*/
addBuff(key: SelectKey<T, number>, value: number): boolean {
if (!this.buffable.has(key) || typeof this.status[key] !== 'number') {
logger.warn(13, String(key));
return false;
}
return this.setBuff(key, this.buffMap.get(key)! + value);
}
/**
* 刷新某个或所有属性,重新进行计算
* @param key 要刷新的属性名,不填表示刷新所有属性
* @returns 是否计算成功
*/
refreshStatus(key?: keyof T): boolean {
if (key === void 0) {
for (const [key, value] of Object.entries(this.status)) {
// @ts-expect-error 暂时无法推导
this.computedStatus[key] = HeroState.cal(this, key, value);
}
return true;
}
this.computedStatus[key] = HeroState.cal(this, key, this.status[key]);
return true;
}
/**
* 计算所有可以buff加成的属性
* @returns 是否计算成功
*/
refreshBuffable(): boolean {
for (const key of this.buffable) {
this.computedStatus[key] = HeroState.cal(
this,
key,
this.status[key]
);
}
return true;
}
/**
* 复写属性计算函数,默认函数不进行计算,直接将原属性返回
* @param fn 计算函数,传入两个参数,key表示属性名,value表示属性值,返回值表示计算结果
*/
static overrideCalculate(fn: HeroStatusCalculate) {
this.cal = fn;
}
}
interface _IHeroItem {
items: Map<AllIdsOf<'items'>, number>;
/**
* 设置勇士拥有的物品数量
* @param item 物品id
* @param value 物品数量
* @returns 是否设置成功
*/
setItem(item: AllIdsOf<'items'>, value: number): boolean;
/**
* 增加或减少勇士拥有的物品数量
* @param item 物品id
* @param value 物品数量增量
* @returns 是否设置成功
*/
addItem(item: AllIdsOf<'items'>, value: number): boolean;
/**
* 使用一个物品
* @param item 物品id
* @returns 是否使用成功
*/
useItem(item: AllIdsOf<'items'>, x?: number, y?: number): boolean;
/**
* 获得一个物品
* @param item 物品id
* @param num 获得的数量
*/
getItem(item: AllIdsOf<'items'>, num: number): void;
/**
* 获得一个物品
* @param item 物品id
* @param x 物品所在x坐标
* @param y 物品所在y坐标
* @param floorId 物品所在楼层
* @param num 获得的数量
*/
getItem(
item: AllIdsOf<'items'>,
x: number,
y: number,
floorId?: FloorIds,
num?: number
): void;
/**
* 获取某个物品的数量
* @param item 物品id
*/
itemCount(item: AllIdsOf<'items'>): number;
/**
* 判断勇士是否拥有这个物品
* @param item 物品id
*/
hasItem(item: AllIdsOf<'items'>): boolean;
}
+32
View File
@@ -0,0 +1,32 @@
import { ILayerState } from './map';
import { IHeroFollower, IHeroState } from './hero';
import { IRoleFaceBinder } from './common';
export interface IStateSaveData {
/** 跟随者列表 */
readonly followers: readonly IHeroFollower[];
}
export interface ICoreState {
/** 地图状态 */
readonly layer: ILayerState;
/** 勇士状态 */
readonly hero: IHeroState;
/** 朝向绑定 */
readonly roleFace: IRoleFaceBinder;
/** id 到图块数字的映射 */
readonly idNumberMap: Map<string, number>;
/** 图块数字到 id 的映射 */
readonly numberIdMap: Map<number, string>;
/**
* 保存状态
*/
saveState(): IStateSaveData;
/**
* 加载状态
* @param data 状态对象
*/
loadState(data: IStateSaveData): void;
}