From 3de6aac6a1cbdd162442c9dac72c36f2ad1f6475 Mon Sep 17 00:00:00 2001
From: unanmed <1319491857@qq.com>
Date: Wed, 25 Sep 2024 16:15:46 +0800
Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=8B=87=E5=A3=AB=E7=A7=BB?=
=?UTF-8?q?=E5=8A=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/core/index.ts | 4 +
src/core/main/action/move.ts | 159 +++++++++++++
src/core/main/custom/danmaku.ts | 2 +-
src/core/main/custom/hotkey.ts | 2 +-
src/core/render/preset/hero.ts | 197 ++++++++++------
src/core/render/preset/layer.ts | 9 +-
src/game/index.ts | 5 +-
src/game/state/move.ts | 397 ++++++++++++++++++++++++++++++++
src/game/state/utils.ts | 35 +++
src/game/system.ts | 7 +
src/plugin/game/fallback.ts | 153 ++++++++----
src/plugin/utils.ts | 2 +-
src/types/status.d.ts | 2 +-
13 files changed, 856 insertions(+), 118 deletions(-)
create mode 100644 src/core/main/action/move.ts
create mode 100644 src/game/state/move.ts
create mode 100644 src/game/state/utils.ts
diff --git a/src/core/index.ts b/src/core/index.ts
index 075f320..f8137df 100644
--- a/src/core/index.ts
+++ b/src/core/index.ts
@@ -72,6 +72,7 @@ import { RenderAdapter } from './render/adapter';
import { getMainRenderer } from './render';
import { Layer } from './render/preset/layer';
import { LayerGroupFloorBinder } from './render/preset/floor';
+import { HeroKeyMover } from './main/action/move';
// ----- 类注册
Mota.register('class', 'AudioPlayer', AudioPlayer);
@@ -162,6 +163,9 @@ Mota.register('module', 'Render', {
Layer,
LayerGroupFloorBinder
});
+Mota.register('module', 'Action', {
+ HeroKeyMover
+});
main.renderLoaded = true;
Mota.require('var', 'hook').emit('renderLoaded');
diff --git a/src/core/main/action/move.ts b/src/core/main/action/move.ts
new file mode 100644
index 0000000..8fac7cc
--- /dev/null
+++ b/src/core/main/action/move.ts
@@ -0,0 +1,159 @@
+import { KeyCode } from '@/plugin/keyCodes';
+import { Hotkey, HotkeyData } from '../custom/hotkey';
+import type { HeroMover, IMoveController } from '@/game/state/move';
+import { Ticker } from 'mutate-animate';
+import { mainScope } from '../init/hotkey';
+
+type MoveKey = Record
;
+type MoveKeyConfig = Record;
+
+export class HeroKeyMover {
+ /** 当前按下的键 */
+ private pressedKey: Set = new Set();
+ /** 当前的移动方向 */
+ private moveDir: Dir = 'down';
+ /** 当前是否正在使用按键移动 */
+ private moving: boolean = false;
+ /** 当前移动的控制器 */
+ private controller?: IMoveController;
+
+ /** 按键接续ticker */
+ private ticker = new Ticker();
+
+ /** 当前移动实例绑定的热键 */
+ hotkey: Hotkey;
+ /** 当前热键的移动按键信息 */
+ hotkeyData: MoveKey;
+ /** 移动实例 */
+ mover: HeroMover;
+ /** 移动可触发的作用域 */
+ scope: symbol = mainScope;
+
+ constructor(hotkey: Hotkey, mover: HeroMover, config?: MoveKeyConfig) {
+ this.hotkey = hotkey;
+ this.mover = mover;
+ hotkey.on('press', this.onPressKey);
+ hotkey.on('release', this.onReleaseKey);
+
+ const data = hotkey.data;
+
+ this.hotkeyData = {
+ left: data[config?.left ?? 'moveLeft'],
+ right: data[config?.right ?? 'moveRight'],
+ up: data[config?.up ?? 'moveUp'],
+ down: data[config?.down ?? 'moveDown']
+ };
+
+ this.ticker.add(() => {
+ if (!this.moving) {
+ if (this.pressedKey.size > 0) {
+ const dir = [...this.pressedKey].at(-1);
+ if (!dir) return;
+ this.moveDir = dir;
+ this.tryStartMove();
+ }
+ }
+ });
+ }
+
+ private onPressKey = (code: KeyCode) => {
+ if (code === this.hotkeyData.left.key) this.press('left');
+ else if (code === this.hotkeyData.right.key) this.press('right');
+ else if (code === this.hotkeyData.up.key) this.press('up');
+ else if (code === this.hotkeyData.down.key) this.press('down');
+ };
+
+ private onReleaseKey = (code: KeyCode) => {
+ if (code === this.hotkeyData.left.key) this.release('left');
+ else if (code === this.hotkeyData.right.key) this.release('right');
+ else if (code === this.hotkeyData.up.key) this.release('up');
+ else if (code === this.hotkeyData.down.key) this.release('down');
+ };
+
+ /**
+ * 设置按键触发作用域
+ */
+ setScope(scope: symbol) {
+ this.scope = scope;
+ }
+
+ /**
+ * 按下某个方向键
+ * @param dir 移动方向
+ */
+ press(dir: Dir) {
+ if (this.hotkey.scope !== this.scope || core.status.lockControl) return;
+ this.pressedKey.add(dir);
+ this.moveDir = dir;
+ if (!this.moving) {
+ this.tryStartMove();
+ }
+ }
+
+ /**
+ * 松开方向键
+ * @param dir 移动方向
+ */
+ release(dir: Dir) {
+ this.pressedKey.delete(dir);
+ if (this.pressedKey.size > 0) {
+ this.moveDir = [...this.pressedKey][0];
+ } else {
+ this.endMove();
+ }
+ }
+
+ /**
+ * 尝试开始移动
+ * @returns 是否成功开始移动
+ */
+ tryStartMove() {
+ if (this.moving || core.status.lockControl) return false;
+
+ this.mover.oneStep(this.moveDir);
+ const controller = this.mover.startMove();
+ if (!controller) return;
+
+ this.controller = controller;
+ controller.onEnd.then(() => {
+ this.moving = false;
+ this.controller = void 0;
+ this.mover.off('stepEnd', this.onStepEnd);
+ });
+ this.moving = true;
+
+ this.mover.on('stepEnd', this.onStepEnd);
+ return true;
+ }
+
+ /**
+ * 停止本次按键移动
+ */
+ endMove() {
+ this.controller?.stop();
+ }
+
+ private onStepEnd = () => {
+ const con = this.controller;
+ if (!con) return;
+ if (!this.moving) {
+ con.stop();
+ return;
+ }
+
+ if (this.pressedKey.size > 0) {
+ if (con.queue.length === 0) {
+ con.push({ type: 'dir', value: this.moveDir });
+ }
+ } else {
+ con.stop();
+ }
+ };
+
+ destroy() {
+ this.hotkey.off('press', this.onPressKey);
+ this.hotkey.off('release', this.onReleaseKey);
+ this.mover.off('stepEnd', this.onStepEnd);
+ this.ticker.destroy();
+ }
+}
diff --git a/src/core/main/custom/danmaku.ts b/src/core/main/custom/danmaku.ts
index a53dd12..45dd45d 100644
--- a/src/core/main/custom/danmaku.ts
+++ b/src/core/main/custom/danmaku.ts
@@ -251,7 +251,7 @@ export class Danmaku extends EventEmitter {
} else {
logger.error(
8,
- `Post not allowed css danmaku. Allow info: ${allow.join(',')}`
+ `Post danmaku with not allowed css. Info: ${allow.join(',')}`
);
}
}
diff --git a/src/core/main/custom/hotkey.ts b/src/core/main/custom/hotkey.ts
index 899fe03..9df772c 100644
--- a/src/core/main/custom/hotkey.ts
+++ b/src/core/main/custom/hotkey.ts
@@ -34,7 +34,7 @@ interface RegisterHotkeyData extends Partial {
defaults: KeyCode;
}
-interface HotkeyData extends Required {
+export interface HotkeyData extends Required {
key: KeyCode;
emits: Map;
group?: string;
diff --git a/src/core/render/preset/hero.ts b/src/core/render/preset/hero.ts
index 29432e7..197ddea 100644
--- a/src/core/render/preset/hero.ts
+++ b/src/core/render/preset/hero.ts
@@ -5,10 +5,10 @@ import { logger } from '@/core/common/logger';
import EventEmitter from 'eventemitter3';
import { texture } from '../cache';
import { TimingFn } from 'mutate-animate';
-import { backDir } from '@/plugin/game/utils';
import { isNil } from 'lodash-es';
-type HeroMovingStatus = 'stop' | 'moving' | 'moving-as';
+// type HeroMovingStatus = 'stop' | 'moving' | 'moving-as';
+// export const enum HeroMovingStatus {}
interface HeroRenderEvent {
stepEnd: [];
@@ -31,25 +31,27 @@ export class HeroRenderer
renderable?: LayerMovingRenderable;
layer!: Layer;
- // hero?: Hero;
-
- /** 勇士移动状态 */
- status: HeroMovingStatus = 'stop';
/** 当前移动帧数 */
movingFrame: number = 0;
+ /** 是否正在移动 */
+ moving: boolean = false;
+ /** 是否正在播放动画,与移动分开以实现原地踏步 */
+ animate: boolean = false;
/** 勇士移动速度 */
speed: number = 100;
- /** 当前勇士朝向 */
- // todo: 删了这个属性
- dir: Dir = 'down';
+
+ /** 当前的移动方向 */
+ moveDir: Dir2 = 'down';
+ /** 当前移动的勇士显示方向 */
+ showDir: Dir = 'down';
+ /** 帧动画是否反向播放,例如后退时就应该设为true */
+ animateReverse: boolean = false;
/** 勇士移动定时器id */
private moveId: number = -1;
/** 上一次帧数切换的时间 */
private lastFrameTime: number = 0;
- /** 当前的移动方向 */
- moveDir: Move2 = 'down';
/** 上一步走到格子上的时间 */
private lastStepTime: number = 0;
/** 执行当前步移动的Promise */
@@ -63,8 +65,6 @@ export class HeroRenderer
stepDir: Dir2 = 'down';
/** 每步的格子增量 */
private stepDelta: Loc = { x: 0, y: 1 };
- /** 动画显示的方向,用于适配后退 */
- // private animateDir: Dir = 'down';
/**
* 设置勇士所用的图片资源
@@ -76,6 +76,10 @@ export class HeroRenderer
this.layer.update(this.layer);
}
+ /**
+ * 设置移动的移动速度
+ * @param speed 移动速度
+ */
setMoveSpeed(speed: number) {
this.speed = speed;
}
@@ -102,7 +106,7 @@ export class HeroRenderer
zIndex: core.status.hero.loc.y,
autotile: false,
bigImage: true,
- render: this.getRenderFromDir(this.moveDir),
+ render: this.getRenderFromDir(this.showDir),
animate: 0
};
}
@@ -111,53 +115,90 @@ export class HeroRenderer
* 根据方向获取勇士的裁切信息
* @param dir 方向
*/
- getRenderFromDir(dir: Move2): [number, number, number, number][] {
+ getRenderFromDir(dir: Dir): [number, number, number, number][] {
if (!this.cellWidth || !this.cellHeight) return [];
- let resolved: Dir2;
- if (dir === 'forward') resolved = this.dir;
- else if (dir === 'backward') resolved = backDir(this.dir);
- else resolved = dir;
- const index = texture.characterDirection[resolved];
+ const index = texture.characterDirection[dir];
const y = index * this.cellHeight;
const res: [number, number, number, number][] = [0, 1, 2, 3].map(v => {
return [v * this.cellWidth!, y, this.cellWidth!, this.cellHeight!];
});
- if (dir === 'backward') return res.reverse();
+ if (this.animateReverse) return res.reverse();
else return res;
}
/**
- * 设置当前勇士
- * @param hero 绑定的勇士
+ * 开始勇士帧动画
*/
- // setHero(hero: Hero) {
- // this.hero = hero;
- // }
+ startAnimate() {
+ this.animate = true;
+ this.lastFrameTime = Date.now();
+ }
/**
- * 准备开始移动
+ * 结束勇士帧动画
*/
- readyMove() {
- if (this.status !== 'stop') return;
- this.status = 'moving';
- this.lastFrameTime = Date.now();
+ endAnimate() {
+ this.animate = false;
+ this.resetRenderable(false);
+ }
+
+ /**
+ * 设置帧动画是否反向播放
+ * @param reverse 帧动画是否反向播放
+ */
+ setAnimateReversed(reverse: boolean) {
+ this.animateReverse = reverse;
+ this.resetRenderable(true);
+ }
+
+ /**
+ * 设置勇士的动画显示朝向
+ * @param dir 显示朝向
+ */
+ setAnimateDir(dir: Dir) {
+ if (dir !== this.showDir) {
+ this.showDir = dir;
+ this.resetRenderable(true);
+ }
+ }
+
+ /**
+ * 重置renderable状态,恢复至第一帧状态
+ * @param getInfo 是否重新获取渲染信息
+ */
+ resetRenderable(getInfo: boolean) {
+ this.movingFrame = 0;
+ console.trace();
+
+ if (this.renderable) {
+ this.renderable.animate = 0;
+ if (getInfo) {
+ this.renderable.render = this.getRenderFromDir(this.showDir);
+ }
+ }
+ this.layer.update(this.layer);
+ }
+
+ /**
+ * 勇士帧动画定时器
+ */
+ private animateTick(time: number) {
+ if (!this.animate) return;
+ if (time - this.lastFrameTime > this.speed) {
+ this.lastFrameTime = time;
+ this.movingFrame++;
+ this.movingFrame %= 4;
+ if (this.renderable) this.renderable.animate = this.movingFrame;
+ }
+ this.layer.update(this.layer);
}
/**
* 勇士移动定时器
*/
private moveTick(time: number) {
- if (this.status !== 'moving') return;
- if (!this.renderable) {
- this.emit('stepEnd');
- return;
- }
-
- if (time - this.lastFrameTime > this.speed) {
- this.lastFrameTime = time;
- this.movingFrame++;
- this.movingFrame %= 4;
- }
+ if (!this.moving) return;
+ if (!this.renderable) return;
const progress = (time - this.lastStepTime) / this.speed;
@@ -174,7 +215,6 @@ export class HeroRenderer
this.renderable.y = ry;
}
this.emit('moveTick', this.renderable.x, this.renderable.y);
- this.renderable.animate = this.movingFrame;
this.layer.update(this.layer);
}
@@ -182,18 +222,23 @@ export class HeroRenderer
* 进行下一步的移动准备,设置移动信息
*/
private step() {
- if (this.moveDir === 'backward') this.stepDir = backDir(this.stepDir);
- else if (this.moveDir !== 'forward') this.stepDir = this.moveDir;
this.lastStepTime = Date.now();
this.stepDelta = core.utils.scan2[this.stepDir];
this.turn(this.stepDir);
}
+ /**
+ * 准备开始移动
+ */
+ readyMove() {
+ this.moving = true;
+ }
+
/**
* 移动勇士
*/
- move(dir: Move2): Promise {
- if (this.status !== 'moving') {
+ move(dir: Dir2): Promise {
+ if (!this.moving) {
logger.error(
12,
`Cannot move while status is not 'moving'. Call 'readyMove' first.`
@@ -202,16 +247,16 @@ export class HeroRenderer
}
this.moveDir = dir;
-
if (this.moveDetached) return this.moveDetached;
else {
this.step();
- return (this.moveDetached = new Promise(resolve => {
+ this.moveDetached = new Promise(res => {
this.once('stepEnd', () => {
this.moveDetached = void 0;
- resolve();
+ res();
});
- }));
+ });
+ return this.moveDetached;
}
}
@@ -219,14 +264,13 @@ export class HeroRenderer
* 结束勇士的移动过程
*/
endMove(): Promise {
- if (this.status !== 'moving') return Promise.reject();
+ if (!this.moving) return Promise.reject();
if (this.moveEnding) return this.moveEnding;
else {
const promise = new Promise(resolve => {
this.once('stepEnd', () => {
this.moveEnding = void 0;
- this.status = 'stop';
- this.movingFrame = 0;
+ this.moving = false;
const { x, y } = core.status.hero.loc;
this.setHeroLoc(x, y);
this.render();
@@ -259,7 +303,7 @@ export class HeroRenderer
this.stepDir = dir;
if (!this.renderable) return;
- this.renderable.render = this.getRenderFromDir(this.moveDir);
+ this.renderable.render = this.getRenderFromDir(this.showDir);
this.layer.update(this.layer);
}
@@ -291,9 +335,8 @@ export class HeroRenderer
* 因为此举会导致层级的重新排序,降低渲染性能。
*/
moveAs(x: number, y: number, time: number, fn: TimingFn<3>): Promise {
- if (this.status !== 'stop') return Promise.reject();
+ if (!this.moving) return Promise.reject();
if (!this.renderable) return Promise.reject();
- this.status = 'moving-as';
let nowZIndex = fn(0)[2];
let startTime = Date.now();
return new Promise(res => {
@@ -307,18 +350,15 @@ export class HeroRenderer
this.renderable.y = ny;
this.renderable.zIndex = nz;
if (nz !== nowZIndex) {
- this.layer.movingRenderable.sort(
- (a, b) => a.zIndex - b.zIndex
- );
+ this.layer.sortMovingRenderable();
}
this.emit('moveTick', this.renderable.x, this.renderable.y);
this.layer.update(this.layer);
},
time,
() => {
- this.status = 'stop';
+ this.moving = false;
if (!this.renderable) return res();
- this.renderable.animate = 0;
this.renderable.x = x;
this.renderable.y = y;
this.emit('moveTick', this.renderable.x, this.renderable.y);
@@ -334,7 +374,7 @@ export class HeroRenderer
*/
render() {
if (!this.renderable) return;
- if (this.status === 'stop') {
+ if (!this.animate) {
this.renderable.animate = -1;
} else {
this.renderable.animate = this.movingFrame;
@@ -346,7 +386,9 @@ export class HeroRenderer
this.layer = layer;
adapter.add(this);
this.moveId = layer.delegateTicker(() => {
- this.moveTick(Date.now());
+ const time = Date.now();
+ this.animateTick(time);
+ this.moveTick(time);
});
}
@@ -380,10 +422,6 @@ adapter.receive(
return item.moveAs(x, y, time, fn);
}
);
-adapter.receive('setMoveSpeed', (item, speed: number) => {
- item.setMoveSpeed(speed);
- return Promise.resolve();
-});
adapter.receive('setHeroLoc', (item, x?: number, y?: number) => {
item.setHeroLoc(x, y);
return Promise.resolve();
@@ -392,11 +430,28 @@ adapter.receive('turn', (item, dir: Dir2) => {
item.turn(dir);
return Promise.resolve();
});
-adapter.receive('setImage', (item, image: SizedCanvasImageSource) => {
+
+// 同步适配函数,这些函数用于同步设置信息等
+adapter.receiveSync('setImage', (item, image: SizedCanvasImageSource) => {
item.setImage(image);
- return Promise.resolve();
});
-// 同步fallback,用于适配现在的样板,之后会删除
+adapter.receiveSync('setMoveSpeed', (item, speed: number) => {
+ item.setMoveSpeed(speed);
+});
+adapter.receiveSync('setAnimateReversed', (item, reverse: boolean) => {
+ item.setAnimateReversed(reverse);
+});
+adapter.receiveSync('startAnimate', item => {
+ item.startAnimate();
+});
+adapter.receiveSync('endAnimate', item => {
+ item.endAnimate();
+});
+adapter.receiveSync('setAnimateDir', (item, dir: Dir) => {
+ item.setAnimateDir(dir);
+});
+
+// 不分同步fallback,用于适配现在的样板,之后会删除
adapter.receiveSync('setHeroLoc', (item, x?: number, y?: number) => {
item.setHeroLoc(x, y);
});
diff --git a/src/core/render/preset/layer.ts b/src/core/render/preset/layer.ts
index 2cab014..f5395f0 100644
--- a/src/core/render/preset/layer.ts
+++ b/src/core/render/preset/layer.ts
@@ -1123,6 +1123,13 @@ export class Layer extends Container {
for (const ex of this.extend.values()) {
ex.onMovingUpdate?.(this, this.movingRenderable);
}
+ this.sortMovingRenderable();
+ }
+
+ /**
+ * 对移动层按照z坐标排序
+ */
+ sortMovingRenderable() {
this.movingRenderable.sort((a, b) => a.zIndex - b.zIndex);
}
@@ -1277,7 +1284,7 @@ export class Layer extends Container {
* 渲染移动/大怪物层
*/
protected renderMoving(transform: Transform) {
- const frame = (RenderItem.animatedFrame % 4) + 1;
+ const frame = RenderItem.animatedFrame;
const cell = this.cellSize;
const halfCell = cell / 2;
const { ctx } = this.movingMap;
diff --git a/src/game/index.ts b/src/game/index.ts
index 2715dd2..20b18ad 100644
--- a/src/game/index.ts
+++ b/src/game/index.ts
@@ -11,6 +11,7 @@ import * as miscMechanism from './mechanism/misc';
import * as study from './mechanism/study';
import { registerPresetState } from './state/preset';
import { ItemState } from './state/item';
+import { HeroMover, ObjectMoverBase } from './state/move';
// ----- 类注册
Mota.register('class', 'DamageEnemy', damage.DamageEnemy);
@@ -34,7 +35,9 @@ Mota.register('module', 'Mechanism', {
Study: study
});
Mota.register('module', 'State', {
- ItemState
+ ItemState,
+ HeroMover,
+ ObjectMoverBase
});
main.loading = loading;
diff --git a/src/game/state/move.ts b/src/game/state/move.ts
new file mode 100644
index 0000000..777de45
--- /dev/null
+++ b/src/game/state/move.ts
@@ -0,0 +1,397 @@
+import EventEmitter from 'eventemitter3';
+import { backDir, toDir } from './utils';
+import { loading } from '../game';
+import type { RenderAdapter } from '@/core/render/adapter';
+import type { HeroRenderer } from '@/core/render/preset/hero';
+import type { FloorViewport } from '@/core/render/preset/viewport';
+import type { KeyCode } from '@/plugin/keyCodes';
+
+interface MoveStepDir {
+ type: 'dir';
+ value: Move2;
+}
+
+interface MoveStepSpeed {
+ type: 'speed';
+ value: number;
+}
+
+type MoveStep = MoveStepDir | MoveStepSpeed;
+
+export interface IMoveController {
+ /** 本次移动是否完成 */
+ done: boolean;
+ /** 当前移动队列 */
+ queue: MoveStep[];
+ /** 当本次移动完成时兑现 */
+ onEnd: Promise;
+
+ /**
+ * 停止本次移动,停止后将不再能继续移动
+ */
+ stop(): Promise;
+
+ /**
+ * 向队列末尾添加移动信息
+ * @param step 移动信息
+ */
+ push(...step: MoveStep[]): void;
+}
+
+interface EObjectMovingEvent {
+ stepEnd: [step: MoveStep];
+ moveEnd: [];
+ moveStart: [queue: MoveStep[]];
+}
+
+export abstract class ObjectMoverBase extends EventEmitter {
+ /** 移动队列 */
+ private moveQueue: MoveStep[] = [];
+
+ /** 当前的移动速度 */
+ moveSpeed: number = 100;
+ /** 当前的移动方向 */
+ moveDir: Dir2 = 'down';
+ /** 当前是否正在移动 */
+ moving: boolean = false;
+
+ /**
+ * 当本次移动开始时执行的函数
+ * @param controller 本次移动的控制对象
+ */
+ protected abstract onMoveStart(controller: IMoveController): Promise;
+
+ /**
+ * 当本次移动结束时执行的函数
+ * @param controller 本次移动的控制对象
+ */
+ protected abstract onMoveEnd(controller: IMoveController): Promise;
+
+ /**
+ * 当这一步开始移动时执行的函数,可以用来播放移动动画等
+ * @param step 这一步的移动信息
+ * @param controller 本次移动的控制对象
+ * @returns 一个Promise,其值表示本次移动的代码,并将其值传入这一步移动后的操作,即{@link onStepEnd}
+ */
+ protected abstract onStepStart(
+ step: MoveStepDir,
+ controller: IMoveController
+ ): Promise;
+
+ /**
+ * 当这一步移动结束后执行的函数(即{@link onStepStart}成功兑现后),可以用于设置物体坐标等
+ * @param step 这一步的移动信息
+ * @param code 本步移动的代码,即onMoveStart的返回值
+ * @param controller 本次移动的控制对象
+ * @returns 一个Promise,当其兑现时,本步移动全部完成,将会开始下一步的移动
+ */
+ protected abstract onStepEnd(
+ step: MoveStepDir,
+ code: number,
+ controller: IMoveController
+ ): Promise;
+
+ private getMoveDir(move: Move2): Dir2 {
+ if (move === 'forward') return this.moveDir;
+ if (move === 'backward') return backDir(this.moveDir);
+ return move;
+ }
+
+ /**
+ * 开始移动,这个操作后,本次移动将不能通过{@link insertMove}添加移动步骤,
+ * 只能通过{@link IMoveController.push}添加,而通过前者添加的移动步骤会在下次移动生效
+ */
+ startMove(): IMoveController | null {
+ if (this.moving) return null;
+ const queue = this.moveQueue.slice();
+ this.clearMoveQueue();
+
+ this.moving = true;
+ let stopMove = false;
+
+ const start = async () => {
+ // 等待宏任务执行完成,不然controller会在首次调用中未定义
+ await new Promise(res => res());
+ this.emit('moveStart', queue);
+ await this.onMoveStart(controller);
+ while (queue.length > 0) {
+ if (stopMove || !this.moving) break;
+ const step = queue.shift();
+ if (!step) break;
+ if (step.type === 'dir') {
+ this.moveDir = this.getMoveDir(step.value);
+ const code = await this.onStepStart(step, controller);
+ await this.onStepEnd(step, code, controller);
+ } else {
+ this.moveSpeed = step.value;
+ }
+ this.emit('stepEnd', step);
+ }
+ this.moving = false;
+ this.emit('moveEnd');
+
+ await this.onMoveEnd(controller);
+ };
+
+ const onEnd = start();
+ const controller: IMoveController = {
+ done: false,
+ onEnd,
+ queue,
+ push(...step) {
+ queue.push(...step);
+ },
+ stop() {
+ stopMove = true;
+ return onEnd;
+ }
+ };
+
+ return controller;
+ }
+
+ /**
+ * 向移动队列末尾插入移动实例
+ * @param move 移动实例
+ */
+ insertMove(...move: MoveStep[]) {
+ this.moveQueue.push(...move);
+ }
+
+ /**
+ * 清空移动队列
+ */
+ clearMoveQueue() {
+ this.moveQueue = [];
+ }
+
+ /**
+ * 向移动队列末尾插入一个移动操作
+ * @param step 移动方向
+ */
+ oneStep(step: Move2) {
+ this.moveQueue.push({ type: 'dir', value: step });
+ }
+
+ /**
+ * 按照传入的数组移动物体,插入至移动队列末尾
+ * @param steps 移动步骤
+ */
+ moveAs(steps: MoveStep[]) {
+ this.moveQueue.push(...steps);
+ }
+}
+
+interface CanMoveStatus {
+ /** 由CannotIn和CannotOut计算出的信息,不可移动时不会触发触发器 */
+ canMove: boolean;
+ /** 由图块的noPass计算出的信息,不可移动时会触发触发器 */
+ noPass: boolean;
+}
+
+const enum HeroMoveCode {
+ Step,
+ Stop,
+ /** 不能移动,并撞击前面一格的图块,触发其触发器 */
+ Hit,
+ /** 不能移动,同时当前格有CannotOut,或目标格有CannotIn,不会触发前面一格的触发器 */
+ CannotMove
+}
+
+export class HeroMover extends ObjectMoverBase {
+ /** 勇士渲染适配器,用于等待动画等操作 */
+ static adapter?: RenderAdapter;
+ /** 视角适配器 */
+ static viewport?: RenderAdapter;
+
+ /** 当前移动是否忽略地形 */
+ private ignoreTerrain: boolean = false;
+ /** 当前移动是否不计入录像 */
+ private noRoute: boolean = false;
+ /** 当前移动是否是在lockControl条件下开始的 */
+ private inLockControl: boolean = false;
+
+ override startMove(
+ ignoreTerrain: boolean = false,
+ noRoute: boolean = false,
+ inLockControl: boolean = false
+ ): IMoveController | null {
+ this.ignoreTerrain = ignoreTerrain;
+ this.noRoute = noRoute;
+ this.inLockControl = inLockControl;
+ return super.startMove();
+ }
+
+ protected async onMoveStart(controller: IMoveController): Promise {
+ const adapter = HeroMover.adapter;
+ if (!adapter) return;
+ await adapter.all('readyMove');
+ adapter.sync('startAnimate');
+ }
+
+ protected async onMoveEnd(controller: IMoveController): Promise {
+ const adapter = HeroMover.adapter;
+ if (!adapter) return;
+ await adapter.all('endMove');
+ adapter.sync('endAnimate');
+ }
+
+ protected async onStepStart(
+ step: MoveStepDir,
+ controller: IMoveController
+ ): Promise {
+ const showDir = toDir(this.moveDir);
+ core.setHeroLoc('direction', showDir);
+ const adapter = HeroMover.adapter;
+ adapter?.sync('setAnimateDir', showDir);
+
+ const { x, y } = core.status.hero.loc;
+ const { x: nx, y: ny } = this.nextLoc(x, y, this.moveDir);
+
+ if (!this.inLockControl && core.status.lockControl) {
+ controller.stop();
+ return HeroMoveCode.Stop;
+ }
+
+ if (!this.ignoreTerrain || !this.noRoute) {
+ this.moveDir = showDir;
+ }
+
+ const dir = this.moveDir;
+ if (!this.ignoreTerrain) {
+ const { noPass, canMove } = this.checkCanMove(x, y, showDir);
+
+ // 不能移动
+ if (noPass || !canMove) {
+ if (!canMove) return HeroMoveCode.CannotMove;
+ else return HeroMoveCode.Hit;
+ }
+ }
+
+ // 可以移动,显示移动动画
+ await this.moveAnimate(nx, ny, showDir, dir);
+
+ return HeroMoveCode.Step;
+ }
+
+ protected async onStepEnd(
+ step: MoveStepDir,
+ code: HeroMoveCode,
+ controller: IMoveController
+ ): Promise {
+ const { x, y } = core.status.hero.loc;
+ const { x: nx, y: ny } = this.nextLoc(x, y, this.moveDir);
+ const showDir = toDir(this.moveDir);
+
+ // 前方不能移动
+ if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Hit) {
+ controller.stop();
+ this.onCannotMove(showDir);
+ if (code === HeroMoveCode.Hit) {
+ core.trigger(nx, ny);
+ }
+ return;
+ }
+
+ // 本次移动停止
+ if (code === HeroMoveCode.Stop) {
+ controller.stop();
+ return;
+ }
+
+ // 本次移动正常完成
+ if (code === HeroMoveCode.Step) {
+ core.setHeroLoc('x', nx, true);
+ core.setHeroLoc('y', ny, true);
+
+ const direction = core.getHeroLoc('direction');
+ core.control._moveAction_popAutomaticRoute();
+ if (!this.noRoute) core.status.route.push(direction);
+
+ core.moveOneStep();
+ core.checkRouteFolding();
+ }
+ }
+
+ /**
+ * 移动动画
+ * @param x 目标横坐标
+ * @param y 目标纵坐标
+ * @param showDir 显示方向
+ * @param moveDir 移动方向
+ */
+ private async moveAnimate(
+ x: number,
+ y: number,
+ showDir: Dir,
+ moveDir: Dir2
+ ) {
+ const adapter = HeroMover.adapter;
+ const viewport = HeroMover.viewport;
+ if (!adapter || !viewport) return;
+ viewport.all('moveTo', x, y);
+ await adapter.all('move', moveDir);
+ }
+
+ /**
+ * 当前方一格不能走时,执行的函数
+ * @param dir 移动方向
+ */
+ private onCannotMove(dir: Dir) {
+ if (!this.noRoute) core.status.route.push(dir);
+ core.status.automaticRoute.moveStepBeforeStop = [];
+ core.status.automaticRoute.lastDirection = dir;
+
+ if (core.status.automaticRoute.moveStepBeforeStop.length == 0) {
+ core.clearContinueAutomaticRoute();
+ core.stopAutomaticRoute();
+ }
+ }
+
+ /**
+ * 检查前方是否可以移动
+ * @param x 当前横坐标
+ * @param y 当前纵坐标
+ * @param dir 移动方向
+ */
+ private checkCanMove(x: number, y: number, dir: Dir): CanMoveStatus {
+ const { x: nx, y: ny } = this.nextLoc(x, y, dir);
+ const noPass = core.noPass(nx, ny);
+ const canMove = core.canMoveHero(x, y, dir);
+ return { noPass, canMove };
+ }
+
+ /**
+ * 获取前方一格的坐标
+ * @param x 当前横坐标
+ * @param y 当前纵坐标
+ * @param dir 移动方向
+ */
+ private nextLoc(x: number, y: number, dir: Dir2): Loc {
+ const { x: dx, y: dy } = core.utils.scan2[dir];
+ const nx = x + dx;
+ const ny = y + dy;
+ return { x: nx, y: ny };
+ }
+}
+
+// 初始化基础勇士移动实例
+export const heroMover = new HeroMover();
+loading.once('coreInit', () => {
+ // 注册按键操作
+ Mota.r(() => {
+ const { HeroKeyMover } = Mota.require('module', 'Action');
+ const gameKey = Mota.require('var', 'gameKey');
+ const keyMover = new HeroKeyMover(gameKey, heroMover);
+ });
+});
+
+// Adapter初始化
+loading.once('coreInit', () => {
+ if (main.replayChecking || main.mode === 'editor') return;
+ const Adapter = Mota.require('module', 'Render').RenderAdapter;
+ const adapter = Adapter.get('hero-adapter');
+ const viewport = Adapter.get('viewport');
+ HeroMover.adapter = adapter;
+ HeroMover.viewport = viewport;
+});
diff --git a/src/game/state/utils.ts b/src/game/state/utils.ts
new file mode 100644
index 0000000..d658847
--- /dev/null
+++ b/src/game/state/utils.ts
@@ -0,0 +1,35 @@
+const dirMap: Record = {
+ down: 'down',
+ left: 'left',
+ leftdown: 'left',
+ leftup: 'left',
+ right: 'right',
+ rightdown: 'right',
+ rightup: 'right',
+ up: 'up'
+};
+
+/**
+ * 将八方向转换为四方向,一般用于斜角移动时的方向显示
+ * @param dir 八方向
+ */
+export function toDir(dir: Dir2): Dir {
+ return dirMap[dir];
+}
+
+const backDirMap: Record = {
+ up: 'down',
+ down: 'up',
+ left: 'right',
+ right: 'left',
+ leftup: 'rightdown',
+ rightup: 'leftdown',
+ leftdown: 'rightup',
+ rightdown: 'leftup'
+};
+
+export function backDir(dir: Dir): Dir;
+export function backDir(dir: Dir2): Dir2;
+export function backDir(dir: Dir2): Dir2 {
+ return backDirMap[dir];
+}
diff --git a/src/game/system.ts b/src/game/system.ts
index a5592a4..22ab4ba 100644
--- a/src/game/system.ts
+++ b/src/game/system.ts
@@ -37,6 +37,8 @@ import type { RenderAdapter } from '@/core/render/adapter';
import type { ItemState } from './state/item';
import type { Layer } from '@/core/render/preset/layer';
import type { LayerGroupFloorBinder } from '@/core/render/preset/floor';
+import type { HeroKeyMover } from '@/core/main/action/move';
+import type { HeroMover, ObjectMoverBase } from './state/move';
interface ClassInterface {
// 渲染进程与游戏进程通用
@@ -121,6 +123,11 @@ interface ModuleInterface {
};
State: {
ItemState: typeof ItemState;
+ HeroMover: typeof HeroMover;
+ ObjectMoverBase: typeof ObjectMoverBase;
+ };
+ Action: {
+ HeroKeyMover: typeof HeroKeyMover;
};
}
diff --git a/src/plugin/game/fallback.ts b/src/plugin/game/fallback.ts
index def3dbc..6a053c5 100644
--- a/src/plugin/game/fallback.ts
+++ b/src/plugin/game/fallback.ts
@@ -50,7 +50,7 @@ export function init() {
}
let moving: boolean = false;
- let stopChian: boolean = false;
+ let stopChain: boolean = false;
let moveDir: Dir;
let stepDir: Dir;
let moveEnding: Promise = Promise.resolve([]);
@@ -65,24 +65,24 @@ export function init() {
let portal: boolean = false;
const pressedArrow: Set = new Set();
- Mota.r(() => {
- const gameKey = Mota.require('var', 'gameKey');
- const { moveUp, moveDown, moveLeft, moveRight } = gameKey.data;
- const symbol = Symbol.for('@key_main');
- gameKey.on('press', code => {
- if (core.status.lockControl || gameKey.scope !== symbol) return;
- if (code === moveUp.key) onMoveKeyDown('up');
- if (code === moveDown.key) onMoveKeyDown('down');
- if (code === moveLeft.key) onMoveKeyDown('left');
- if (code === moveRight.key) onMoveKeyDown('right');
- });
- gameKey.on('release', code => {
- if (code === moveUp.key) onMoveKeyUp('up');
- if (code === moveDown.key) onMoveKeyUp('down');
- if (code === moveLeft.key) onMoveKeyUp('left');
- if (code === moveRight.key) onMoveKeyUp('right');
- });
- });
+ // Mota.r(() => {
+ // const gameKey = Mota.require('var', 'gameKey');
+ // const { moveUp, moveDown, moveLeft, moveRight } = gameKey.data;
+ // const symbol = Symbol.for('@key_main');
+ // gameKey.on('press', code => {
+ // if (core.status.lockControl || gameKey.scope !== symbol) return;
+ // if (code === moveUp.key) onMoveKeyDown('up');
+ // if (code === moveDown.key) onMoveKeyDown('down');
+ // if (code === moveLeft.key) onMoveKeyDown('left');
+ // if (code === moveRight.key) onMoveKeyDown('right');
+ // });
+ // gameKey.on('release', code => {
+ // if (code === moveUp.key) onMoveKeyUp('up');
+ // if (code === moveDown.key) onMoveKeyUp('down');
+ // if (code === moveLeft.key) onMoveKeyUp('left');
+ // if (code === moveRight.key) onMoveKeyUp('right');
+ // });
+ // });
function onMoveKeyDown(type: Dir) {
pressedArrow.add(type);
@@ -91,15 +91,15 @@ export function init() {
stepDir = moveDir;
readyMove();
}
- if (moving && stopChian) {
- stopChian = false;
+ if (moving && stopChain) {
+ stopChain = false;
}
}
function onMoveKeyUp(type: Dir) {
pressedArrow.delete(type);
if (pressedArrow.size === 0) {
- stopChian = true;
+ stopChain = true;
} else {
const arr = [...pressedArrow];
moveDir = arr[0];
@@ -127,7 +127,7 @@ export function init() {
}
await adapter.all('readyMove');
moving = true;
- stopChian = false;
+ stopChain = false;
startHeroMoveChain(ignoreTerrain, noRoute, inLockControl, callback);
}
@@ -143,7 +143,7 @@ export function init() {
return Promise.resolve();
} else {
if (moving) {
- stopChian = true;
+ stopChain = true;
moveEnding = adapter.all('endMove');
await moveEnding;
moveEmit.emit('moveEnd');
@@ -161,7 +161,9 @@ export function init() {
function continueAfterEnd() {
requestAnimationFrame(() => {
if (pressedArrow.size === 0 || moving) {
- stopChian = true;
+ // console.log(6);
+
+ stopChain = true;
return;
}
if (checkCanMoveStatus()) {
@@ -185,8 +187,8 @@ export function init() {
if (!adapter) {
return Promise.resolve();
} else {
- while (moving || !stopChian) {
- if (stopChian || (!inLockControl && core.status.lockControl)) {
+ while (moving || !stopChain) {
+ if (stopChain || (!inLockControl && core.status.lockControl)) {
break;
}
@@ -215,7 +217,7 @@ export function init() {
}
endMove();
- stopChian = false;
+ stopChain = false;
}
}
@@ -297,6 +299,30 @@ export function init() {
callback?.();
}
+ async function moveHeroAs(step: DiredLoc[]) {
+ if (moving) return;
+ // console.log(step);
+
+ let now = step.shift();
+ if (!now) return;
+
+ stepDir = now.direction;
+ await readyMove();
+ while (now) {
+ if (!moving) break;
+ stepDir = now.direction;
+
+ await new Promise(res => {
+ moveEmit.once('stepEnd', () => {
+ now = step.shift();
+ if (!now) endMove();
+ console.log(now?.direction);
+ res();
+ });
+ });
+ }
+ }
+
// ----- 移动 - 传送门
function checkPortal() {
const map = BluePalace.portalMap.get(core.status.floorId);
@@ -437,8 +463,8 @@ export function init() {
}
});
- const step0 = steps.find(v => !v.startsWith('speed')) as Move2;
- return { steps, start: step0 };
+ const step0 = moveSteps.find(v => !v.startsWith('speed')) as Move2;
+ return { steps: moveSteps, start: step0 };
}
/**
@@ -495,7 +521,7 @@ export function init() {
control.prototype.moveAction = async function (callback?: () => void) {
// await endMove(false);
moveEmit.once('stepEnd', () => {
- stopChian = true;
+ stopChain = true;
endMove(false);
});
readyMove(false, true, true, callback);
@@ -570,14 +596,14 @@ export function init() {
readyMove(true, true, true);
while (++pointer < len) {
const dir = moveSteps[pointer];
- if (dir === 'backward') moveDir = backDir(moveDir);
+ if (dir === 'backward') stepDir = backDir(stepDir);
else if (dir.startsWith('speed')) {
const speed = parseInt(dir.slice(6));
list.forEach(v => v.setMoveSpeed(speed));
} else if (dir !== 'forward') {
- moveDir = dir as Dir;
+ stepDir = dir as Dir;
}
- const { x, y } = core.utils.scan[moveDir];
+ const { x, y } = core.utils.scan[stepDir];
nx += x;
ny += y;
await new Promise(res => {
@@ -617,7 +643,9 @@ export function init() {
control.prototype.waitHeroToStop = function (callback?: () => void) {
core.stopAutomaticRoute();
core.clearContinueAutomaticRoute();
- moveEnding.then(() => {
+
+ stopChain = true;
+ stepEnding.then(() => {
callback?.();
});
};
@@ -639,7 +667,8 @@ export function init() {
moveDir = direction;
stepDir = direction;
await readyMove();
- stopChian = true;
+
+ stopChain = true;
callback?.();
};
@@ -647,13 +676,52 @@ export function init() {
events.prototype.setHeroIcon = function (name: ImageIds) {
const img = core.material.images.images[name];
if (!img) return;
- adapters['hero-adapter']?.all('setImage', img);
+ adapters['hero-adapter']?.sync('setImage', img);
};
control.prototype.isMoving = function () {
return moving;
};
+ ////// 设置自动寻路路线 //////
+ control.prototype.setAutomaticRoute = function (
+ destX: number,
+ destY: number,
+ stepPostfix: DiredLoc[]
+ ) {
+ if (!core.status.played || core.status.lockControl) return;
+ if (this._setAutomaticRoute_isMoving(destX, destY)) return;
+ if (this._setAutomaticRoute_isTurning(destX, destY, stepPostfix))
+ return;
+ if (
+ this._setAutomaticRoute_clickMoveDirectly(
+ destX,
+ destY,
+ stepPostfix
+ )
+ )
+ return;
+ // 找寻自动寻路路线
+ const moveStep = core.automaticRoute(destX, destY);
+ if (
+ moveStep.length == 0 &&
+ (destX != core.status.hero.loc.x ||
+ destY != core.status.hero.loc.y ||
+ stepPostfix.length == 0)
+ )
+ return;
+ moveStep.push(...stepPostfix);
+ core.status.automaticRoute.destX = destX;
+ core.status.automaticRoute.destY = destY;
+ this._setAutomaticRoute_drawRoute(moveStep);
+ this._setAutomaticRoute_setAutoSteps(moveStep);
+ // 立刻移动
+ // core.setAutoHeroMove();
+ console.log(moveStep);
+
+ moveHeroAs(moveStep.slice());
+ };
+
hook.on('reset', () => {
moveDir = core.status.hero.loc.direction;
stepDir = moveDir;
@@ -868,10 +936,11 @@ export function init() {
for (const step of moveSteps) {
if (step === 'backward') stepDir = backDir(stepDir);
- if (step.startsWith('speed')) {
+ else if (step.startsWith('speed')) {
time = parseInt(step.slice(6));
continue;
- }
+ } else stepDir = step as Dir2;
+
const { x, y } = core.utils.scan2[stepDir];
const tx = nx + x;
const ty = ny + y;
@@ -983,8 +1052,10 @@ export function init() {
destY: number,
ignoreSteps: number
) {
- adapters.viewport?.all('mutateTo', destX, destY);
- return this.controldata.moveDirectly(destX, destY, ignoreSteps);
+ const data = this.controldata;
+ const success = data.moveDirectly(destX, destY, ignoreSteps);
+ if (success) adapters.viewport?.all('mutateTo', destX, destY);
+ return success;
};
});
diff --git a/src/plugin/utils.ts b/src/plugin/utils.ts
index 62c4160..0f8ed0d 100644
--- a/src/plugin/utils.ts
+++ b/src/plugin/utils.ts
@@ -2,7 +2,7 @@ import { message } from 'ant-design-vue';
import { MessageApi } from 'ant-design-vue/lib/message';
import { isNil } from 'lodash-es';
import { Animation, sleep, TimingFn } from 'mutate-animate';
-import { ref } from 'vue';
+import { Ref, ref } from 'vue';
import { EVENT_KEY_CODE_MAP, KeyCode } from './keyCodes';
import axios from 'axios';
import { decompressFromBase64 } from 'lz-string';
diff --git a/src/types/status.d.ts b/src/types/status.d.ts
index 4f9cb66..8c18611 100644
--- a/src/types/status.d.ts
+++ b/src/types/status.d.ts
@@ -641,7 +641,7 @@ interface InitGameStatus {
/**
* 自动寻路状态
*/
- automaticRoute: DeepReadonly;
+ automaticRoute: AutomaticRouteStatus;
/**
* 按键按下的时间,用于判定双击