mirror of
https://github.com/motajs/template.git
synced 2026-11-03 07:24:30 +08:00
feat: 将 HumanBreak 新内容移植入该样板
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
@@ -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,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';
|
||||
@@ -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?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/** 默认的勇士图片 */
|
||||
export const DEFAULT_HERO_IMAGE: ImageIds = 'hero.png';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user