feat: 勇士移动

This commit is contained in:
unanmed 2024-08-21 21:29:47 +08:00
parent fdd4563f45
commit b4159159a7
14 changed files with 355 additions and 45 deletions

View File

@ -410,6 +410,7 @@ actions.prototype._sys_keyDown_lockControl = function (keyCode) {
actions.prototype._sys_keyDown = function (keyCode) {
if (!core.status.played) return true;
return true;
switch (keyCode) {
case 37:
core.moveHero('left');

View File

@ -3022,6 +3022,7 @@ control.prototype.removeSwitch = function (x, y, floorId, name) {
////// 锁定状态栏,常常用于事件处理 //////
control.prototype.lockControl = function () {
console.trace();
core.status.lockControl = true;
};

View File

@ -3146,6 +3146,14 @@ maps.prototype.removeBlockByIndex = function (index, floorId) {
delete core.status.mapBlockObjs[floorId][block.x + ',' + block.y];
core.setMapBlockDisabled(floorId, block.x, block.y, true);
this._updateMapArray(floorId, block.x, block.y);
Mota.require('var', 'hook').emit(
'setBlock',
block.x,
block.y,
floorId,
0,
block?.id ?? 0
);
};
////// 一次性删除多个block //////

View File

@ -3,7 +3,7 @@ import { deleteWith, generateBinary, keycode, spliceBy } from '@/plugin/utils';
import { EventEmitter } from 'eventemitter3';
import { isNil } from 'lodash-es';
// todo: 按下时触发,长按(单次/连续)触发,按下连续触发,按下节流触发,按下加速节流触发
// todo: 按下加速节流触发
interface HotkeyEvent {
set: [id: string, key: KeyCode, assist: number];
@ -235,12 +235,15 @@ export class Hotkey extends EventEmitter<HotkeyEvent> {
// 真正开始触发按键
let emitted = false;
toEmit.forEach(v => {
if (ctrl === v.ctrl && shift === v.shift && alt === v.alt) {
const data = v.emits.get(this.scope);
if (!data) return;
if (type === 'up' && data.onUp) {
data.onUp(v.id, key, ev);
emitted = true;
return;
}
if (!this.canEmit(v.id, key, type, data)) return;
@ -419,6 +422,7 @@ document.addEventListener('keyup', e => {
const code = keycode(e.keyCode);
if (gameKey.emitKey(code, assist, 'up', e)) {
e.preventDefault();
deleteWith(core.status.holdingKeys, e.keyCode);
} else {
// polyfill样板
if (

View File

@ -6,10 +6,10 @@ import * as lzstring from 'lz-string';
import * as animate from 'mutate-animate';
import * as vue from 'vue';
Mota.Package.register('axios', axios);
Mota.Package.register('chart.js', chart);
Mota.Package.register('jszip', jszip);
Mota.Package.register('lodash', lodash);
Mota.Package.register('lz-string', lzstring);
Mota.Package.register('mutate-animate', animate);
Mota.Package.register('vue', vue);
// Mota.Package.register('axios', axios);
// Mota.Package.register('chart.js', chart);
// Mota.Package.register('jszip', jszip);
// Mota.Package.register('lodash', lodash);
// Mota.Package.register('lz-string', lzstring);
// Mota.Package.register('mutate-animate', animate);
// Mota.Package.register('vue', vue);

View File

@ -58,6 +58,7 @@ export class RenderAdapter<T> {
*/
all<R = any>(fn: string, ...params: any[]): Promise<R[]> {
const execute = this.execute.get(fn);
if (!execute) {
return Promise.reject();
} else {
@ -86,7 +87,7 @@ export class RenderAdapter<T> {
*
* @returns
*/
syncAll<R = any>(fn: string, ...params: any[]): R[] {
sync<R = any>(fn: string, ...params: any[]): R[] {
const execute = this.syncExecutes.get(fn);
if (!execute) {
return [];

View File

@ -196,7 +196,8 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
deep: number = 2 ** 31 - 1
) {
const [bx, by] = this.getBlockXY(x, y);
const [ex, ey] = this.getBlockXY(x + width, y + height);
const [ex, ey] = this.getBlockXY(x + width - 1, y + height - 1);
return this.updateArea(bx, by, ex - bx, ey - by, deep);
}
@ -213,6 +214,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
deep: number = 2 ** 31 - 1
) {
const blocks = this.getIndexOf(x, y, width, height);
blocks.forEach(v => {
this.clearCache(v, deep);
});
@ -229,8 +231,8 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
const ex = Math.min(x + width, this.blockData.width);
const ey = Math.min(y + height, this.blockData.height);
for (let nx = sx; nx < ex; nx++) {
for (let ny = sy; ny < ey; ny++) {
for (let nx = sx; nx <= ex; nx++) {
for (let ny = sy; ny <= ey; ny++) {
const index = this.getIndex(nx, ny);
res.add(index);
}

View File

@ -28,6 +28,8 @@ export class FloorDamageExtends implements ILayerGroupRenderExtends {
group!: LayerGroup;
sprite!: Damage;
static listenedDamage: Set<FloorDamageExtends> = new Set();
/**
*
*/
@ -52,12 +54,16 @@ export class FloorDamageExtends implements ILayerGroupRenderExtends {
}
private onUpdate = (floor: FloorIds) => {
this.update(floor);
this.sprite.requestBeforeFrame(() => {
this.update(floor);
});
};
private onSetBlock = (x: number, y: number, floor: FloorIds) => {
if (floor !== this.sprite.enemy?.floorId) return;
this.sprite.updateEnemyOn(x, y);
this.sprite.enemy?.once('extract', () => {
if (floor !== this.sprite.enemy?.floorId) return;
this.sprite.updateEnemyOn(x, y);
});
};
/**
@ -83,12 +89,14 @@ export class FloorDamageExtends implements ILayerGroupRenderExtends {
);
group.removeExtends('floor-damage');
}
FloorDamageExtends.listenedDamage.add(this);
});
}
onDestroy(group: LayerGroup): void {
this.floorBinder.off('update', this.onUpdate);
this.floorBinder.off('setBlock', this.onSetBlock);
FloorDamageExtends.listenedDamage.delete(this);
}
}
@ -242,15 +250,17 @@ export class Damage extends Sprite {
} else {
data.set(index, enemy);
}
this.update(this);
// 渲染懒更新,优化性能表现
if (!this.needUpdateBlock) {
this.needUpdateBlocks.add(block);
this.requestBeforeFrame(() => {
this.needUpdateBlock = false;
this.needUpdateBlocks.forEach(v => this.updateBlock(v, false));
// todo: 阻击夹域等地图伤害检测是否必要更新,例如不包含阻击夹域的怪就不必要更新这个怪物信息
this.extractAllMapDamage();
// this.extractAllMapDamage();
});
this.needUpdateBlock = true;
}
@ -264,8 +274,10 @@ export class Damage extends Sprite {
private updateBlock(block: number, map: boolean = true) {
const data = this.blockData.get(block);
if (!data) return;
this.block.clearCache(block, 1);
const renderable = this.renderable.get(block)!;
renderable.clear();
data.forEach(v => this.extract(v, renderable));
if (map) this.extractMapDamage(block, renderable);
}

View File

@ -117,6 +117,8 @@ export class LayerGroupFloorBinder
if (!ex) return;
ex.setBlock(block, x, y);
console.log(block, x, y);
const floor = this.bindThisFloor ? core.status.floorId : this.floor!;
this.emit('setBlock', x, y, floor, block);
}

View File

@ -6,6 +6,7 @@ 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';
@ -48,8 +49,10 @@ export class HeroRenderer
private moveDir: Move2 = 'down';
/** 上一步走到格子上的时间 */
private lastStepTime: number = 0;
/** 是否已经执行当前步移动 */
/** 执行当前步移动的Promise */
private moveDetached?: Promise<void>;
/** endMove的Promise */
private moveEnding?: Promise<void>;
/**
* {@link moveDir}
* {@link moveDir}
@ -157,8 +160,6 @@ export class HeroRenderer
if (progress >= 1) {
this.renderable.x = x + dx;
this.renderable.y = y + dy;
console.log(x, y, dx, dy, this.renderable.x, this.renderable.y);
this.emit('stepEnd');
} else {
const rx = dx * progress + x;
@ -212,15 +213,21 @@ export class HeroRenderer
*/
endMove(): Promise<void> {
if (this.status !== 'moving') return Promise.reject();
return new Promise<void>(resolve => {
this.once('stepEnd', () => {
this.status = 'stop';
this.movingFrame = 0;
this.layer.removeTicker(this.moveId);
this.render();
resolve();
if (this.moveEnding) return this.moveEnding;
else {
const promise = new Promise<void>(resolve => {
this.once('stepEnd', () => {
this.moveEnding = void 0;
this.status = 'stop';
this.movingFrame = 0;
const { x, y } = core.status.hero.loc;
this.setHeroLoc(x, y);
this.render();
resolve();
});
});
});
return (this.moveEnding = promise);
}
}
/**
@ -242,11 +249,29 @@ export class HeroRenderer
}
}
this.moveDir = dir;
this.stepDir = dir;
if (!this.renderable) return;
this.renderable.render = this.getRenderFromDir(this.moveDir);
this.layer.update(this.layer);
}
/**
*
* @param x
* @param y
*/
setHeroLoc(x?: number, y?: number) {
if (!this.renderable) return;
if (!isNil(x)) {
this.renderable.x = x;
}
if (!isNil(y)) {
this.renderable.y = y;
}
this.layer.update(this.layer);
}
/**
*
* @param x
@ -313,6 +338,7 @@ export class HeroRenderer
this.moveId = layer.delegateTicker(() => {
this.moveTick(Date.now());
});
console.log(this);
}
onDestroy(layer: Layer): void {
@ -346,3 +372,18 @@ adapter.recieve('setMoveSpeed', (item, speed: number) => {
item.setMoveSpeed(speed);
return Promise.resolve();
});
adapter.recieve('setHeroLoc', (item, x?: number, y?: number) => {
item.setHeroLoc(x, y);
return Promise.resolve();
});
adapter.recieve('turn', (item, dir: Dir2) => {
item.turn(dir);
return Promise.resolve();
});
// 同步fallback用于适配现在的样板之后会删除
adapter.recieveSync('setHeroLoc', (item, x?: number, y?: number) => {
item.setHeroLoc(x, y);
});
adapter.recieveSync('turn', (item, dir: Dir2) => {
item.turn(dir);
});

View File

@ -906,7 +906,7 @@ export class Layer extends Container {
this.renderData.push(...data);
} else if (data.length === 1) {
// 特判单个图块的情况
const index = x + y + this.mapWidth;
const index = x + y * this.mapWidth;
this.renderData[index] = data[0];
} else {
// 限定更新区域
@ -929,7 +929,8 @@ export class Layer extends Container {
if (calAutotile) this.calAutotiles(x, y, width, height);
this.updateBlocks(x, y, width, height);
this.updateBigImages(x, y, width, height);
this.update(this);
// this.update(this);
for (const ex of this.extend.values()) {
ex.onDataPut?.(this, data, width, x, y, calAutotile);
@ -1099,6 +1100,7 @@ export class Layer extends Container {
height,
Layer.FRAME_ALL
);
this.update(this);
for (const ex of this.extend.values()) {
@ -1195,7 +1197,7 @@ export class Layer extends Container {
*/
protected renderStatic(camera: Camera, need: NeedRenderData) {
const cell = this.cellSize;
const frame = (RenderItem.animatedFrame % 4) + 1;
const frame = RenderItem.animatedFrame % 4;
const { width } = this.block.blockData;
const blockSize = this.block.blockSize;
const { ctx } = this.staticMap;
@ -1210,7 +1212,7 @@ export class Layer extends Container {
const y = Math.floor(v / width);
const sx = x * blockSize;
const sy = y * blockSize;
const index = v * 4 + frame - 1;
const index = v * 4 + frame;
const cache = this.block.cache.get(index);
if (cache) {
@ -1241,7 +1243,7 @@ export class Layer extends Container {
if (num === 0 || num === 17) continue;
const data = texture.getRenderable(num);
if (!data || data.bigImage) continue;
const f = (frame - 1) % data.frame;
const f = frame % data.frame;
const i = data.animate === -1 ? f : data.animate;
const [isx, isy, w, h] = data.render[i];
const px = (nx - sx) * cell;

View File

@ -7,6 +7,7 @@ import {
has,
manhattan
} from '@/plugin/game/utils';
import EventEmitter from 'eventemitter3';
// todo: 光环划分优先级,从而可以实现光环的多级运算
@ -95,7 +96,14 @@ specialValue
.set(27, ['fireCore'])
.set(28, ['paleShield']);
export class EnemyCollection implements RangeCollection<DamageEnemy> {
interface EnemyCollectionEvent {
extract: [];
}
export class EnemyCollection
extends EventEmitter<EnemyCollectionEvent>
implements RangeCollection<DamageEnemy>
{
floorId: FloorIds;
list: DamageEnemy[] = [];
@ -108,6 +116,7 @@ export class EnemyCollection implements RangeCollection<DamageEnemy> {
translation: [number, number] = [0, 0];
constructor(floorId: FloorIds) {
super();
this.floorId = floorId;
this.extract();
}
@ -129,6 +138,7 @@ export class EnemyCollection implements RangeCollection<DamageEnemy> {
new DamageEnemy(enemy, v.x, v.y, this.floorId, this)
);
});
this.emit('extract');
}
/**

View File

@ -186,7 +186,7 @@ export interface IMota {
* polyfill使
* 使main.replayChecking进行检查使
*/
Package: IPackage;
// Package: IPackage;
/**
*
@ -409,7 +409,7 @@ class Mota {
static r = r;
static rf = rf;
static Plugin = MPlugin;
static Package = MPackage;
// static Package = MPackage;
constructor() {
throw new Error(`System interface class cannot be constructed.`);
@ -548,12 +548,8 @@ function rewrite<O, K extends SelectKey<O, _Func>, T = O>(
* @param fn `Mota.Package.requireAll()`
* @param thisArg `this`
*/
function r<T = undefined>(
fn: (this: T, packages: PackageInterface) => void,
thisArg?: T
) {
if (!main.replayChecking && main.mode === 'play')
fn.call(thisArg as T, MPackage.requireAll());
function r<T = undefined>(fn: (this: T) => void, thisArg?: T) {
if (!main.replayChecking && main.mode === 'play') fn.call(thisArg as T);
}
/**

View File

@ -15,9 +15,182 @@ export function init() {
adapters['hero-adapter'] = hero;
}
function onMoveEnd(callback?: () => void) {
core.setHeroLoc('x', core.nextX(), true);
core.setHeroLoc('y', core.nextY(), true);
let moving: boolean = false;
let stopChian: boolean = false;
let moveDir: Dir;
let stepDir: Dir;
let moveEnding: Promise<any[]>;
const pressedArrow: Set<Dir> = new Set();
Mota.r(() => {
const gameKey = Mota.require('var', 'gameKey');
gameKey
.realize('moveUp', onMoveKeyDown('up'), { type: 'down' })
.realize('moveUp', onMoveKeyUp('up'))
.realize('moveDown', onMoveKeyDown('down'), { type: 'down' })
.realize('moveDown', onMoveKeyUp('down'))
.realize('moveLeft', onMoveKeyDown('left'), { type: 'down' })
.realize('moveLeft', onMoveKeyUp('left'))
.realize('moveRight', onMoveKeyDown('right'), { type: 'down' })
.realize('moveRight', onMoveKeyUp('right'));
});
function onMoveKeyDown(type: Dir) {
return () => {
pressedArrow.add(type);
moveDir = type;
if (!moving) {
stepDir = moveDir;
readyMove();
}
if (moving && stopChian) {
stopChian = false;
}
};
}
function onMoveKeyUp(type: Dir) {
return () => {
pressedArrow.delete(type);
if (pressedArrow.size === 0) {
stopChian = true;
} else {
const arr = [...pressedArrow];
moveDir = arr[0];
if (!moving) {
stepDir = moveDir;
readyMove();
}
}
};
}
async function readyMove() {
const adapter = adapters['hero-adapter'];
if (!adapter) {
return Promise.resolve();
} else {
if (!checkCanMoveStatus()) {
continueAfterEnd();
return Promise.resolve();
}
await adapter.all('readyMove');
moving = true;
stopChian = false;
return startHeroMoveChain();
}
}
async function endMove() {
const adapter = adapters['hero-adapter'];
if (!adapter) {
return Promise.resolve();
} else {
if (moving) {
stopChian = true;
moveEnding = adapter.all('endMove');
await moveEnding;
moving = false;
continueAfterEnd();
}
return Promise.resolve();
}
}
function continueAfterEnd() {
requestAnimationFrame(() => {
if (pressedArrow.size === 0 || moving) {
stopChian = true;
return;
}
if (core.status.lockControl) {
pressedArrow.clear();
stopChian = true;
return;
}
if (checkCanMoveStatus()) {
if (!moving) {
stepDir = moveDir;
readyMove();
}
} else {
continueAfterEnd();
}
});
}
async function startHeroMoveChain(callback?: () => void) {
const adapter = adapters['hero-adapter'];
if (!adapter) {
return Promise.resolve();
} else {
while (moving || !stopChian) {
if (stopChian || core.status.lockControl) break;
stepDir = moveDir;
if (!checkCanMoveStatus(callback)) break;
await adapter.all('move', moveDir);
onMoveEnd(false, callback);
}
endMove();
stopChian = false;
}
}
function checkCanMoveStatus(callback?: () => void) {
core.setHeroLoc('direction', stepDir);
const { noPass, canMove } = checkCanMove();
if (noPass || !canMove) {
onCannotMove(canMove, callback);
if (moving) endMove();
// onMoveEnd(true);
return false;
}
return true;
}
function getNextLoc() {
const { x: dx, y: dy } = core.utils.scan[stepDir];
const { x, y } = core.status.hero.loc;
const nx = x + dx;
const ny = y + dy;
return { nx, ny };
}
/**
*
*/
function checkCanMove() {
const { nx, ny } = getNextLoc();
const noPass = core.noPass(nx, ny);
const canMove = core.canMoveHero(nx, ny, stepDir);
return { noPass, canMove };
}
function onCannotMove(canMove: boolean, callback?: () => void) {
const { nx, ny } = getNextLoc();
core.status.route.push(core.getHeroLoc('direction'));
// @ts-ignore
core.status.automaticRoute.moveStepBeforeStop = [];
// @ts-ignore
core.status.automaticRoute.lastDirection = core.getHeroLoc('direction');
if (canMove) core.trigger(nx, ny);
core.drawHero();
if (core.status.automaticRoute.moveStepBeforeStop.length == 0) {
core.clearContinueAutomaticRoute();
core.stopAutomaticRoute();
}
callback?.();
}
function onMoveEnd(noPass: boolean, callback?: () => void) {
if (!noPass) {
const { nx, ny } = getNextLoc();
core.setHeroLoc('x', nx, true);
core.setHeroLoc('y', ny, true);
}
var direction = core.getHeroLoc('direction');
core.control._moveAction_popAutomaticRoute();
@ -31,11 +204,14 @@ export function init() {
// ----- 勇士移动相关
control.prototype._moveAction_moving = function (callback?: () => void) {
return;
const adapter = adapters['hero-adapter'];
if (!adapter) {
onMoveEnd(callback);
return;
} else {
core.status.heroMoving = 1;
adapter
.all('readyMove')
.then(() => {
@ -43,6 +219,7 @@ export function init() {
})
.then(() => {
onMoveEnd(callback);
core.status.heroMoving = 0;
return adapter.all('endMove');
});
}
@ -64,6 +241,7 @@ export function init() {
time?: number,
callback?: () => void
) {
return;
time = time || core.values.moveSpeed;
// const render = Mota.require('module', 'Render').heroRender;
@ -127,6 +305,7 @@ export function init() {
}
});
await adapter.all('readyMove');
core.status.heroMoving = 1;
for (const dir of steps) {
if (dir.startsWith('speed')) {
const speed = parseInt(dir.slice(6));
@ -196,7 +375,58 @@ export function init() {
core.status.route.push('turn');
core.checkRouteFolding();
if (!main.replayChecking) {
adapters['hero-adapter']?.all('turn', direction);
// adapters['hero-adapter']?.sync('turn', direction);
}
};
control.prototype.setHeroLoc = function (
name: 'x' | 'y' | 'direction',
value: number | Dir,
noGather?: boolean
) {
if (!core.status.hero) return;
// @ts-ignore
core.status.hero.loc[name] = value;
if ((name === 'x' || name === 'y') && !noGather) {
this.gatherFollowers();
}
if (name === 'direction') {
adapters['hero-adapter']?.sync('turn', value);
} else if (name === 'x') {
adapters['hero-adapter']?.sync('setHeroLoc', value);
} else {
adapters['hero-adapter']?.sync('setHeroLoc', void 0, value);
}
};
////// 停止勇士的一切行动等待勇士行动结束后再执行callback //////
control.prototype.waitHeroToStop = function (callback?: () => void) {
var lastDirection = core.status.automaticRoute.lastDirection;
core.stopAutomaticRoute();
core.clearContinueAutomaticRoute();
if (!main.replayChecking) {
moveEnding.then(() => {
callback?.();
});
return;
}
if (callback) {
// @ts-ignore
core.status.replay.animate = true;
core.lockControl();
// @ts-ignore
core.status.automaticRoute.moveDirectly = false;
setTimeout(
function () {
// @ts-ignore
core.status.replay.animate = false;
if (core.isset(lastDirection))
core.setHeroLoc('direction', lastDirection);
core.drawHero();
callback();
},
core.status.replay.speed == 24 ? 1 : 30
);
}
};
}