mirror of
https://github.com/motajs/template.git
synced 2026-11-15 08:14:30 +08:00
init: 2.B
This commit is contained in:
commit
6bf32174d8
610 files changed
+151444
No files matched your search
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@user/legacy-plugin-data",
|
||||
"dependencies": {
|
||||
"@user/data-state": "workspace:*",
|
||||
"@user/data-base": "workspace:*",
|
||||
"@user/data-utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { DamageEnemy } from '@user/data-state';
|
||||
import { findDir, ofDir } from '@user/data-utils';
|
||||
|
||||
export function createCheckBlock() {
|
||||
// 地图伤害在这实现。2.C 会修改实现方式
|
||||
control.prototype.checkBlock = function () {
|
||||
const heroLoc = core.status.hero.loc;
|
||||
const { x, y } = heroLoc;
|
||||
const loc = `${x},${y}`;
|
||||
const col = core.status.thisMap.enemy;
|
||||
const info = col.mapDamage[loc];
|
||||
if (!info) return;
|
||||
const damage = info.damage;
|
||||
|
||||
// 阻击夹域伤害
|
||||
if (damage) {
|
||||
core.status.hero.hp -= damage;
|
||||
const type = [...info.type];
|
||||
const text = type.join(',') || '伤害';
|
||||
core.drawTip('受到' + text + damage + '点');
|
||||
core.drawHeroAnimate('zone');
|
||||
this._checkBlock_disableQuickShop();
|
||||
core.status.hero.statistics.extraDamage += damage;
|
||||
if (core.status.hero.hp <= 0) {
|
||||
core.status.hero.hp = 0;
|
||||
core.updateStatusBar();
|
||||
core.events.lose();
|
||||
return;
|
||||
} else {
|
||||
core.updateStatusBar();
|
||||
}
|
||||
}
|
||||
|
||||
const actions: MotaAction[] = [];
|
||||
|
||||
// 阻击效果
|
||||
if (info.repulse) {
|
||||
for (const [x, y] of info.repulse) {
|
||||
const loc2 = { x, y };
|
||||
const dir = findDir(heroLoc, loc2);
|
||||
if (dir === 'none') continue;
|
||||
const [nx, ny] = ofDir(x, y, dir);
|
||||
if (core.noPass(nx, ny) || !core.canMoveHero(x, y, dir)) {
|
||||
continue;
|
||||
}
|
||||
actions.push({
|
||||
type: 'move',
|
||||
time: 250,
|
||||
keep: true,
|
||||
loc: [x, y],
|
||||
steps: [`${dir}:1`],
|
||||
async: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 存储要和哪些捕捉怪战斗 */
|
||||
const ambushEnemies: DamageEnemy[] = [];
|
||||
|
||||
// 捕捉效果
|
||||
if (info.ambush) {
|
||||
for (const [x, y] of info.ambush) {
|
||||
const loc2 = { x, y };
|
||||
const dir = findDir(loc2, heroLoc);
|
||||
if (dir === 'none') continue;
|
||||
actions.push({
|
||||
type: 'move',
|
||||
time: 250,
|
||||
keep: false,
|
||||
loc: [x, y],
|
||||
steps: [`${dir}:1`],
|
||||
async: true
|
||||
});
|
||||
const enemy = col.get(x, y);
|
||||
if (enemy) {
|
||||
ambushEnemies.push(enemy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.length > 0) {
|
||||
actions.push({ type: 'waitAsync' });
|
||||
// 与捕捉怪战斗
|
||||
core.insertAction(actions, void 0, void 0, () => {
|
||||
ambushEnemies.forEach(v => {
|
||||
core.battle(v, v.y, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { createCheckBlock } from './checkblock';
|
||||
|
||||
export function createEnemy() {
|
||||
createCheckBlock();
|
||||
}
|
||||
|
||||
export * from './checkblock';
|
||||
@@ -0,0 +1,741 @@
|
||||
import type { RenderAdapter } from '@motajs/render';
|
||||
import type { TimingFn } from 'mutate-animate';
|
||||
import { BlockMover, heroMoveCollection, MoveStep } from '@user/data-state';
|
||||
import { hook, loading } from '@user/data-base';
|
||||
import { Patch, PatchClass } from '@motajs/legacy-common';
|
||||
import type {
|
||||
HeroRenderer,
|
||||
LayerDoorAnimate,
|
||||
LayerGroupAnimate,
|
||||
Layer,
|
||||
FloorViewport,
|
||||
LayerFloorBinder,
|
||||
LayerGroup
|
||||
} from '@user/client-modules';
|
||||
|
||||
// 向后兼容用,会充当两个版本间过渡的作用
|
||||
|
||||
interface Adapters {
|
||||
'hero-adapter'?: RenderAdapter<HeroRenderer>;
|
||||
'door-animate'?: RenderAdapter<LayerDoorAnimate>;
|
||||
animate?: RenderAdapter<LayerGroupAnimate>;
|
||||
layer?: RenderAdapter<Layer>;
|
||||
viewport?: RenderAdapter<FloorViewport>;
|
||||
}
|
||||
|
||||
const adapters: Adapters = {};
|
||||
|
||||
export function initFallback() {
|
||||
let fallbackIds: number = 1e8;
|
||||
|
||||
if (!main.replayChecking && main.mode === 'play') {
|
||||
const Adapter = Mota.require('@motajs/render').RenderAdapter;
|
||||
const hero = Adapter.get<HeroRenderer>('hero-adapter');
|
||||
const doorAnimate = Adapter.get<LayerDoorAnimate>('door-animate');
|
||||
const animate = Adapter.get<LayerGroupAnimate>('animate');
|
||||
const layer = Adapter.get<Layer>('layer');
|
||||
const viewport = Adapter.get<FloorViewport>('viewport');
|
||||
|
||||
adapters['hero-adapter'] = hero;
|
||||
adapters['door-animate'] = doorAnimate;
|
||||
adapters['animate'] = animate;
|
||||
adapters['layer'] = layer;
|
||||
adapters['viewport'] = viewport;
|
||||
}
|
||||
|
||||
const { mover: heroMover } = heroMoveCollection;
|
||||
|
||||
// ----- 工具函数
|
||||
|
||||
/**
|
||||
* 根据事件中给出的移动数组解析出全部的移动步骤
|
||||
*/
|
||||
function getMoveSteps(steps: string[]) {
|
||||
const moveSteps: string[] = [];
|
||||
steps.forEach(v => {
|
||||
const [type, number] = v.split(':');
|
||||
if (!number) moveSteps.push(type);
|
||||
else {
|
||||
if (type === 'speed') moveSteps.push(v);
|
||||
else {
|
||||
moveSteps.push(...Array(Number(number)).fill(type));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return moveSteps;
|
||||
}
|
||||
|
||||
function setHeroDirection(dir: Dir) {
|
||||
heroMover.setFaceDir(dir);
|
||||
heroMover.setMoveDir(dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成跳跃函数
|
||||
*/
|
||||
function generateJumpFn(dx: number, dy: number): TimingFn<3> {
|
||||
const distance = Math.hypot(dx, dy);
|
||||
const peak = 3 + distance;
|
||||
|
||||
return (progress: number) => {
|
||||
const x = dx * progress;
|
||||
const y = progress * dy + (progress ** 2 - progress) * peak;
|
||||
|
||||
return [x, y, Math.ceil(y)];
|
||||
};
|
||||
}
|
||||
|
||||
Mota.r(() => {
|
||||
// ----- 引入
|
||||
const { MotaRenderer: Renderer } = Mota.require('@motajs/render');
|
||||
const { Camera } = Mota.require('@user/client-modules');
|
||||
const Animation = Mota.require('MutateAnimate');
|
||||
|
||||
const patch = new Patch(PatchClass.Control);
|
||||
const patch2 = new Patch(PatchClass.Events);
|
||||
const patch3 = new Patch(PatchClass.Maps);
|
||||
|
||||
//#region 勇士移动相关
|
||||
patch.add('moveAction', async function (callback?: () => void) {
|
||||
heroMover.clearMoveQueue();
|
||||
heroMover.oneStep('forward');
|
||||
const lock = core.status.lockControl;
|
||||
const controller = heroMover.startMove(false, true, lock);
|
||||
controller?.onEnd.then(() => {
|
||||
callback?.();
|
||||
});
|
||||
heroMover.once('stepEnd', () => {
|
||||
controller?.stop();
|
||||
});
|
||||
});
|
||||
|
||||
patch.add('_moveAction_moving', () => {});
|
||||
|
||||
patch2.add(
|
||||
'_action_moveAction',
|
||||
function (data: any, x: number, y: number, prefix: any) {
|
||||
if (core.canMoveHero()) {
|
||||
const nx = core.nextX(),
|
||||
ny = core.nextY();
|
||||
// 检查noPass决定是撞击还是移动
|
||||
if (core.noPass(nx, ny)) {
|
||||
core.insertAction([{ type: 'trigger', loc: [nx, ny] }]);
|
||||
} else {
|
||||
// 先移动一格,然后尝试触发事件
|
||||
core.insertAction([
|
||||
{
|
||||
type: 'function',
|
||||
function:
|
||||
'function() { core.moveAction(core.doAction); }',
|
||||
async: true
|
||||
},
|
||||
{ type: '_label' }
|
||||
]);
|
||||
}
|
||||
}
|
||||
core.doAction();
|
||||
}
|
||||
);
|
||||
|
||||
patch2.add(
|
||||
'eventMoveHero',
|
||||
async function (
|
||||
steps: string[],
|
||||
time: number = 500,
|
||||
callback?: () => void
|
||||
) {
|
||||
if (heroMover.moving) return;
|
||||
const moveSteps = getMoveSteps(steps);
|
||||
|
||||
const resolved = moveSteps.map<MoveStep>(v => {
|
||||
if (v.startsWith('speed')) {
|
||||
return { type: 'speed', value: Number(v.slice(6)) };
|
||||
} else {
|
||||
return { type: 'dir', value: v as Move2 };
|
||||
}
|
||||
});
|
||||
const start: MoveStep = { type: 'speed', value: time };
|
||||
|
||||
heroMover.insertMove(...[start, ...resolved]);
|
||||
const controller = heroMover.startMove(true, true, true, false);
|
||||
if (!controller) {
|
||||
callback?.();
|
||||
return;
|
||||
}
|
||||
controller.onEnd.then(() => {
|
||||
callback?.();
|
||||
});
|
||||
|
||||
const animate = fallbackIds++;
|
||||
|
||||
core.animateFrame.lastAsyncId = animate;
|
||||
core.animateFrame.asyncId[animate] = controller.stop;
|
||||
}
|
||||
);
|
||||
|
||||
patch.add(
|
||||
'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) {
|
||||
core.control.gatherFollowers();
|
||||
}
|
||||
if (name === 'direction') {
|
||||
adapters['hero-adapter']?.sync('turn', value);
|
||||
adapters['hero-adapter']?.sync('setAnimateDir', value);
|
||||
setHeroDirection(value as Dir);
|
||||
} else if (name === 'x') {
|
||||
// 为了防止逆天样板出问题
|
||||
core.bigmap.posX = value as number;
|
||||
adapters['hero-adapter']?.sync('setHeroLoc', value);
|
||||
} else {
|
||||
// 为了防止逆天样板出问题
|
||||
core.bigmap.posY = value as number;
|
||||
adapters['hero-adapter']?.sync('setHeroLoc', void 0, value);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
patch.add('waitHeroToStop', function (callback?: () => void) {
|
||||
core.stopAutomaticRoute();
|
||||
core.clearContinueAutomaticRoute();
|
||||
heroMover.controller?.stop();
|
||||
if (callback) {
|
||||
core.status.replay.animate = true;
|
||||
core.lockControl();
|
||||
core.status.automaticRoute.moveDirectly = false;
|
||||
setTimeout(
|
||||
function () {
|
||||
core.status.replay.animate = false;
|
||||
callback();
|
||||
},
|
||||
core.status.replay.speed === 24 ? 1 : 30
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
patch.add(
|
||||
'moveHero',
|
||||
async function (
|
||||
direction?: Dir,
|
||||
callback?: () => void,
|
||||
noRoute: boolean = false
|
||||
) {
|
||||
if (heroMover.moving) return;
|
||||
heroMover.clearMoveQueue();
|
||||
heroMover.oneStep(direction ?? 'forward');
|
||||
const lock = core.status.lockControl;
|
||||
const controller = heroMover.startMove(false, noRoute, lock);
|
||||
controller?.onEnd.then(() => {
|
||||
callback?.();
|
||||
});
|
||||
heroMover.once('stepEnd', () => {
|
||||
controller?.stop();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
patch2.add('setHeroIcon', function (name: ImageIds) {
|
||||
const img = core.material.images.images[name];
|
||||
if (!img) return;
|
||||
core.status.hero.image = name;
|
||||
adapters['hero-adapter']?.sync('setImage', img);
|
||||
});
|
||||
|
||||
patch.add('isMoving', function () {
|
||||
return heroMover.moving;
|
||||
});
|
||||
|
||||
patch.add(
|
||||
'setAutomaticRoute',
|
||||
function (destX: number, destY: number, stepPostfix: DiredLoc[]) {
|
||||
if (heroMover.moving) return;
|
||||
if (!core.status.played || core.status.lockControl) return;
|
||||
if (core.control._setAutomaticRoute_isMoving(destX, destY))
|
||||
return;
|
||||
if (
|
||||
core.control._setAutomaticRoute_isTurning(
|
||||
destX,
|
||||
destY,
|
||||
stepPostfix
|
||||
)
|
||||
)
|
||||
return;
|
||||
if (
|
||||
core.control._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;
|
||||
core.control._setAutomaticRoute_drawRoute(moveStep);
|
||||
core.control._setAutomaticRoute_setAutoSteps(moveStep);
|
||||
|
||||
// ???
|
||||
core.setAutoHeroMove();
|
||||
|
||||
// 执行移动
|
||||
const steps: MoveStep[] = moveStep.map(v => {
|
||||
return { type: 'dir', value: v.direction };
|
||||
});
|
||||
heroMover.clearMoveQueue();
|
||||
heroMover.insertMove(...steps);
|
||||
heroMover.startMove();
|
||||
}
|
||||
);
|
||||
|
||||
//#region 开关门
|
||||
|
||||
patch2.add(
|
||||
'openDoor',
|
||||
function (
|
||||
x: number,
|
||||
y: number,
|
||||
needKey: boolean,
|
||||
callback?: () => void
|
||||
) {
|
||||
const block = core.getBlock(x, y);
|
||||
core.saveAndStopAutomaticRoute();
|
||||
if (!core.events._openDoor_check(block, x, y, needKey)) {
|
||||
const locked = core.status.lockControl;
|
||||
core.waitHeroToStop(function () {
|
||||
if (!locked) core.unlockControl();
|
||||
if (callback) callback();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (core.status.replay.speed === 24) {
|
||||
core.status.replay.animate = true;
|
||||
core.removeBlock(x, y);
|
||||
setTimeout(function () {
|
||||
core.status.replay.animate = false;
|
||||
hook.emit(
|
||||
'afterOpenDoor',
|
||||
block.event.id as AllIdsOf<'animates'>,
|
||||
x,
|
||||
y
|
||||
);
|
||||
if (callback) callback();
|
||||
}, 1); // +1是为了录像检测系统
|
||||
} else {
|
||||
const locked = core.status.lockControl;
|
||||
core.lockControl();
|
||||
core.status.replay.animate = true;
|
||||
core.removeBlock(x, y);
|
||||
|
||||
const cb = () => {
|
||||
core.maps._removeBlockFromMap(
|
||||
core.status.floorId,
|
||||
block
|
||||
);
|
||||
if (!locked) core.unlockControl();
|
||||
core.status.replay.animate = false;
|
||||
hook.emit(
|
||||
'afterOpenDoor',
|
||||
block.event.id as AllIdsOf<'animates'>,
|
||||
x,
|
||||
y
|
||||
);
|
||||
callback?.();
|
||||
delete core.animateFrame.asyncId[animate];
|
||||
};
|
||||
|
||||
adapters['door-animate']?.all('openDoor', block).then(cb);
|
||||
|
||||
const animate = fallbackIds++;
|
||||
core.animateFrame.lastAsyncId = animate;
|
||||
core.animateFrame.asyncId[animate] = cb;
|
||||
// this._openDoor_animate(block, x, y, callback);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
patch2.add(
|
||||
'closeDoor',
|
||||
function (x: number, y: number, id: AllIds, callback?: () => void) {
|
||||
id = id || '';
|
||||
if (
|
||||
// @ts-ignore
|
||||
(core.material.icons.animates[id] == null &&
|
||||
// @ts-ignore
|
||||
core.material.icons.npc48[id] == null) ||
|
||||
core.getBlock(x, y) != null
|
||||
) {
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
const block = core.getBlockById(id);
|
||||
const doorInfo = (block.event || {}).doorInfo;
|
||||
if (!doorInfo) {
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
core.playSound(doorInfo.closeSound);
|
||||
|
||||
const locked = core.status.lockControl;
|
||||
core.lockControl();
|
||||
core.status.replay.animate = true;
|
||||
const cb = function () {
|
||||
if (!locked) core.unlockControl();
|
||||
core.status.replay.animate = false;
|
||||
core.setBlock(id, x, y);
|
||||
core.showBlock(x, y);
|
||||
callback?.();
|
||||
};
|
||||
|
||||
if (core.status.replay.speed === 24) {
|
||||
cb();
|
||||
} else {
|
||||
adapters['door-animate']
|
||||
?.all('closeDoor', block)
|
||||
.then(() => {
|
||||
cb();
|
||||
});
|
||||
|
||||
const animate = fallbackIds++;
|
||||
core.animateFrame.lastAsyncId = animate;
|
||||
core.animateFrame.asyncId[animate] = cb;
|
||||
core.events._openDoor_animate(block, x, y, callback);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
//#region 动画
|
||||
|
||||
patch3.add(
|
||||
'drawAnimate',
|
||||
function (
|
||||
name: AnimationIds,
|
||||
x: number,
|
||||
y: number,
|
||||
alignWindow?: boolean,
|
||||
callback?: () => void
|
||||
) {
|
||||
// @ts-ignore
|
||||
name = core.getMappedName(name);
|
||||
|
||||
// 正在播放录像:不显示动画
|
||||
if (
|
||||
core.isReplaying() ||
|
||||
!core.material.animates[name] ||
|
||||
x == null ||
|
||||
y == null
|
||||
) {
|
||||
if (callback) callback();
|
||||
return -1;
|
||||
}
|
||||
|
||||
adapters.animate
|
||||
?.all(
|
||||
'drawAnimate',
|
||||
name,
|
||||
x * 32 + 16,
|
||||
y * 32 + 16,
|
||||
alignWindow ?? false
|
||||
)
|
||||
.then(() => {
|
||||
callback?.();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
patch3.add(
|
||||
'drawHeroAnimate',
|
||||
function (name: AnimationIds, callback?: () => void) {
|
||||
// @ts-ignore
|
||||
name = core.getMappedName(name);
|
||||
|
||||
// 正在播放录像或动画不存在:不显示动画
|
||||
if (core.isReplaying() || !core.material.animates[name]) {
|
||||
if (callback) callback();
|
||||
return -1;
|
||||
}
|
||||
|
||||
adapters.animate?.global('drawHeroAnimate', name).then(() => {
|
||||
callback?.();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
patch3.add(
|
||||
'moveBlock',
|
||||
async function (
|
||||
x: number,
|
||||
y: number,
|
||||
steps: string[],
|
||||
time: number = 500,
|
||||
keep: boolean = false,
|
||||
callback?: () => void
|
||||
) {
|
||||
if (!steps || steps.length === 0) {
|
||||
callback?.();
|
||||
return;
|
||||
}
|
||||
const block = core.getBlock(x, y);
|
||||
if (!block) {
|
||||
callback?.();
|
||||
return;
|
||||
}
|
||||
const mover = new BlockMover(
|
||||
x,
|
||||
y,
|
||||
core.status.floorId,
|
||||
'event'
|
||||
);
|
||||
const moveSteps = getMoveSteps(steps);
|
||||
const resolved = moveSteps.map<MoveStep>(v => {
|
||||
if (v.startsWith('speed')) {
|
||||
return { type: 'speed', value: Number(v.slice(6)) };
|
||||
} else {
|
||||
return { type: 'dir', value: v as Move2 };
|
||||
}
|
||||
});
|
||||
const start: MoveStep = { type: 'speed', value: time };
|
||||
mover.insertMove(...[start, ...resolved]);
|
||||
const controller = mover.startMove();
|
||||
|
||||
const id = fallbackIds++;
|
||||
core.animateFrame.asyncId[id] = () => {
|
||||
if (!keep) {
|
||||
core.removeBlock(mover.x, mover.y);
|
||||
}
|
||||
};
|
||||
|
||||
if (controller) {
|
||||
await controller.onEnd;
|
||||
}
|
||||
|
||||
delete core.animateFrame.asyncId[id];
|
||||
|
||||
if (!keep) {
|
||||
core.removeBlock(mover.x, mover.y);
|
||||
}
|
||||
callback?.();
|
||||
}
|
||||
);
|
||||
|
||||
patch3.add(
|
||||
'jumpBlock',
|
||||
async function (
|
||||
sx: number,
|
||||
sy: number,
|
||||
ex: number,
|
||||
ey: number,
|
||||
time: number = 500,
|
||||
keep: boolean = false,
|
||||
callback?: () => void
|
||||
) {
|
||||
const block = core.getBlock(sx, sy);
|
||||
if (!block) {
|
||||
callback?.();
|
||||
return;
|
||||
}
|
||||
time /= core.status.replay.speed;
|
||||
if (core.status.replay.speed === 24) time = 1;
|
||||
const dx = ex - sx;
|
||||
const dy = ey - sy;
|
||||
|
||||
const fn = generateJumpFn(dx, dy);
|
||||
|
||||
const list = adapters.layer?.items ?? [];
|
||||
const items = [...list].filter(v => {
|
||||
if (v.layer !== 'event') return false;
|
||||
const ex = v.getExtends('floor-binder') as LayerFloorBinder;
|
||||
if (!ex) return false;
|
||||
return ex.getFloor() === core.status.floorId;
|
||||
});
|
||||
const width = core.status.thisMap.width;
|
||||
const index = sx + sy * width;
|
||||
|
||||
const promise = Promise.all(
|
||||
items.map(v => {
|
||||
return v.moveAs(index, ex, ey, fn, time, keep);
|
||||
})
|
||||
);
|
||||
|
||||
core.updateStatusBar();
|
||||
core.removeBlock(sx, sy);
|
||||
|
||||
const id = fallbackIds++;
|
||||
core.animateFrame.asyncId[id] = () => {
|
||||
if (keep) {
|
||||
core.setBlock(block.id, ex, ey);
|
||||
}
|
||||
};
|
||||
|
||||
await promise;
|
||||
|
||||
delete core.animateFrame.asyncId[id];
|
||||
|
||||
if (keep) {
|
||||
core.setBlock(block.id, ex, ey);
|
||||
}
|
||||
core.updateStatusBar();
|
||||
|
||||
callback?.();
|
||||
}
|
||||
);
|
||||
|
||||
patch2.add(
|
||||
'jumpHero',
|
||||
async function (
|
||||
ex: number,
|
||||
ey: number,
|
||||
time: number = 500,
|
||||
callback?: () => void
|
||||
) {
|
||||
if (heroMover.moving) return;
|
||||
|
||||
const sx = core.getHeroLoc('x');
|
||||
const sy = core.getHeroLoc('y');
|
||||
adapters.viewport?.all('mutateTo', ex, ey, time);
|
||||
|
||||
const locked = core.status.lockControl;
|
||||
core.lockControl();
|
||||
const list = adapters['hero-adapter']?.items ?? [];
|
||||
const items = [...list];
|
||||
|
||||
time /= core.status.replay.speed;
|
||||
if (core.status.replay.speed === 24) time = 1;
|
||||
const fn = generateJumpFn(ex - sx, ey - sy);
|
||||
await Promise.all(
|
||||
items.map(v => {
|
||||
if (!v.renderable) return Promise.reject();
|
||||
return v.layer.moveRenderable(
|
||||
v.renderable,
|
||||
sx,
|
||||
sy,
|
||||
fn,
|
||||
time
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
if (!locked) core.unlockControl();
|
||||
core.setHeroLoc('x', ex);
|
||||
core.setHeroLoc('y', ey);
|
||||
callback?.();
|
||||
}
|
||||
);
|
||||
|
||||
//#region 视角处理
|
||||
|
||||
patch.add(
|
||||
'moveDirectly',
|
||||
function (destX: number, destY: number, ignoreSteps: number) {
|
||||
const data = core.control.controldata;
|
||||
const success = data.moveDirectly(destX, destY, ignoreSteps);
|
||||
if (success) adapters.viewport?.all('mutateTo', destX, destY);
|
||||
return success;
|
||||
}
|
||||
);
|
||||
|
||||
patch.add(
|
||||
'moveViewport',
|
||||
function (
|
||||
x: number,
|
||||
y: number,
|
||||
_moveMode: EaseMode,
|
||||
time: number = 1,
|
||||
callback?: () => void
|
||||
) {
|
||||
const main = Renderer.get('render-main');
|
||||
const layer = main?.getElementById('layer-main') as LayerGroup;
|
||||
if (!layer) return;
|
||||
const camera = Camera.for(layer);
|
||||
camera.clearOperation();
|
||||
const translate = camera.addTranslate();
|
||||
|
||||
const animateTime =
|
||||
time / Math.max(core.status.replay.speed, 1);
|
||||
const animate = new Animation.Animation();
|
||||
animate
|
||||
.absolute()
|
||||
.time(1)
|
||||
.mode(Animation.linear())
|
||||
.move(core.bigmap.offsetX, core.bigmap.offsetY);
|
||||
animate.time(animateTime).move(x * 32, y * 32);
|
||||
|
||||
camera.applyTranslateAnimation(
|
||||
translate,
|
||||
animate,
|
||||
animateTime + 50
|
||||
);
|
||||
camera.transform = layer.camera;
|
||||
|
||||
const end = () => {
|
||||
core.bigmap.offsetX = x * 32;
|
||||
core.bigmap.offsetY = y * 32;
|
||||
camera.destroy();
|
||||
callback?.();
|
||||
};
|
||||
|
||||
const timeout = window.setTimeout(end, animateTime + 50);
|
||||
|
||||
const id = fallbackIds++;
|
||||
core.animateFrame.lastAsyncId = id;
|
||||
core.animateFrame.asyncId[id] = () => {
|
||||
end();
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
loading.once('loaded', () => {
|
||||
for (const animate of Object.values(core.material.animates)) {
|
||||
animate.se ??= {};
|
||||
if (typeof animate.se === 'string') {
|
||||
animate.se = { 1: animate.se };
|
||||
}
|
||||
animate.pitch ??= {};
|
||||
}
|
||||
});
|
||||
loading.once('coreInit', () => {
|
||||
const moveAction = new Set<string>(['up', 'down', 'left', 'right']);
|
||||
// 复写录像的移动
|
||||
core.registerReplayAction('move', action => {
|
||||
if (moveAction.has(action)) {
|
||||
if (!heroMover.moving) {
|
||||
heroMover.startMove();
|
||||
}
|
||||
if (!heroMover.controller) {
|
||||
return false;
|
||||
}
|
||||
heroMover.controller.push({
|
||||
type: 'dir',
|
||||
value: action as Dir
|
||||
});
|
||||
|
||||
heroMover.controller.onEnd.then(() => {
|
||||
core.replay();
|
||||
});
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// 创建新图层
|
||||
function createCanvas(name, zIndex) {
|
||||
if (!name) return;
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.id = name;
|
||||
canvas.className = 'gameCanvas no-anti-aliasing';
|
||||
// 编辑器模式下设置zIndex会导致加入的图层覆盖优先级过高
|
||||
if (main.mode != 'editor') canvas.style.zIndex = zIndex || 0;
|
||||
// 将图层插入进游戏内容
|
||||
document.getElementById('gameDraw').appendChild(canvas);
|
||||
var ctx = canvas.getContext('2d');
|
||||
core.canvas[name] = ctx;
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
export function initFiveLayer() {
|
||||
// 大地图适配
|
||||
|
||||
core.initStatus.bg2maps = {};
|
||||
core.initStatus.fg2maps = {};
|
||||
|
||||
if (main.mode == 'editor') {
|
||||
var bg2Canvas = createCanvas('bg2', 20);
|
||||
var fg2Canvas = createCanvas('fg2', 63);
|
||||
core.bigmap.canvas = [
|
||||
'bg2',
|
||||
'fg2',
|
||||
'bg',
|
||||
'event',
|
||||
'event2',
|
||||
'fg',
|
||||
'damage'
|
||||
];
|
||||
/*插入编辑器的图层 不做此步新增图层无法在编辑器显示*/
|
||||
// 编辑器图层覆盖优先级 eui > efg > fg(前景层) > event2(48*32图块的事件层) > event(事件层) > bg(背景层)
|
||||
// 背景层2(bg2) 插入事件层(event)之前(即bg与event之间)
|
||||
document
|
||||
.getElementById('mapEdit')
|
||||
.insertBefore(bg2Canvas, document.getElementById('event'));
|
||||
// 前景层2(fg2) 插入编辑器前景(efg)之前(即fg之后)
|
||||
document
|
||||
.getElementById('mapEdit')
|
||||
.insertBefore(fg2Canvas, document.getElementById('ebm'));
|
||||
// 原本有三个图层 从4开始添加
|
||||
var num = 4;
|
||||
// 新增图层存入editor.dom中
|
||||
editor.dom.bg2c = core.canvas.bg2.canvas;
|
||||
editor.dom.bg2Ctx = core.canvas.bg2;
|
||||
editor.dom.fg2c = core.canvas.fg2.canvas;
|
||||
editor.dom.fg2Ctx = core.canvas.fg2;
|
||||
editor.dom.maps.push('bg2map', 'fg2map');
|
||||
editor.dom.canvas.push('bg2', 'fg2');
|
||||
|
||||
// 创建编辑器上的按钮
|
||||
var createCanvasBtn = name => {
|
||||
// 电脑端创建按钮
|
||||
var input = document.createElement('input');
|
||||
// layerMod4/layerMod5
|
||||
var id = 'layerMod' + num++;
|
||||
// bg2map/fg2map
|
||||
var value = name + 'map';
|
||||
input.type = 'radio';
|
||||
input.name = 'layerMod';
|
||||
input.id = id;
|
||||
input.value = value;
|
||||
editor.dom[id] = input;
|
||||
input.onchange = () => {
|
||||
editor.uifunctions.setLayerMod(value);
|
||||
};
|
||||
return input;
|
||||
};
|
||||
|
||||
var createCanvasBtn_mobile = name => {
|
||||
// 手机端往选择列表中添加子选项
|
||||
var input = document.createElement('option');
|
||||
var id = 'layerMod' + num++;
|
||||
var value = name + 'map';
|
||||
input.name = 'layerMod';
|
||||
input.value = value;
|
||||
editor.dom[id] = input;
|
||||
return input;
|
||||
};
|
||||
if (!editor.isMobile) {
|
||||
var input = createCanvasBtn('bg2');
|
||||
var input2 = createCanvasBtn('fg2');
|
||||
// 获取事件层及其父节点
|
||||
var child = document.getElementById('layerMod'),
|
||||
parent = child.parentNode;
|
||||
// 背景层2插入事件层前
|
||||
parent.insertBefore(input, child);
|
||||
// 不能直接更改背景层2的innerText 所以创建文本节点
|
||||
var txt = document.createTextNode('背2');
|
||||
// 插入事件层前(即新插入的背景层2前)
|
||||
parent.insertBefore(txt, child);
|
||||
// 向最后插入前景层2(即插入前景层后)
|
||||
parent.appendChild(input2);
|
||||
var txt2 = document.createTextNode('前2');
|
||||
parent.appendChild(txt2);
|
||||
} else {
|
||||
var input = createCanvasBtn_mobile('bg2');
|
||||
var input2 = createCanvasBtn_mobile('fg2');
|
||||
// 手机端因为是选项 所以可以直接改innerText
|
||||
input.innerText = '背景2';
|
||||
input2.innerText = '前景2';
|
||||
var parent = document.getElementById('layerMod');
|
||||
parent.insertBefore(input, parent.children[1]);
|
||||
parent.appendChild(input2);
|
||||
}
|
||||
}
|
||||
|
||||
maps.prototype._loadFloor_doNotCopy = function () {
|
||||
return [
|
||||
'firstArrive',
|
||||
'eachArrive',
|
||||
'blocks',
|
||||
'parallelDo',
|
||||
'map',
|
||||
'bgmap',
|
||||
'fgmap',
|
||||
'bg2map',
|
||||
'fg2map',
|
||||
'events',
|
||||
'changeFloor',
|
||||
'afterBattle',
|
||||
'afterGetItem',
|
||||
'afterOpenDoor',
|
||||
'cannotMove',
|
||||
'enemy'
|
||||
];
|
||||
};
|
||||
////// 绘制背景和前景层 //////
|
||||
maps.prototype._drawBg_draw = function (
|
||||
floorId,
|
||||
toDrawCtx,
|
||||
cacheCtx,
|
||||
config
|
||||
) {
|
||||
config.ctx = cacheCtx;
|
||||
core.maps._drawBg_drawBackground(floorId, config);
|
||||
// ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制背景图块;后绘制的覆盖先绘制的。
|
||||
core.maps._drawFloorImages(
|
||||
floorId,
|
||||
config.ctx,
|
||||
'bg',
|
||||
null,
|
||||
null,
|
||||
config.onMap
|
||||
);
|
||||
core.maps._drawBgFgMap(floorId, 'bg', config);
|
||||
if (config.onMap) {
|
||||
core.drawImage(
|
||||
toDrawCtx,
|
||||
cacheCtx.canvas,
|
||||
core.bigmap.v2 ? -32 : 0,
|
||||
core.bigmap.v2 ? -32 : 0
|
||||
);
|
||||
core.clearMap('bg2');
|
||||
core.clearMap(cacheCtx);
|
||||
}
|
||||
core.maps._drawBgFgMap(floorId, 'bg2', config);
|
||||
if (config.onMap)
|
||||
core.drawImage(
|
||||
'bg2',
|
||||
cacheCtx.canvas,
|
||||
core.bigmap.v2 ? -32 : 0,
|
||||
core.bigmap.v2 ? -32 : 0
|
||||
);
|
||||
config.ctx = toDrawCtx;
|
||||
};
|
||||
maps.prototype._drawFg_draw = function (
|
||||
floorId,
|
||||
toDrawCtx,
|
||||
cacheCtx,
|
||||
config
|
||||
) {
|
||||
config.ctx = cacheCtx;
|
||||
// ------ 调整这两行的顺序来控制是先绘制贴图还是先绘制前景图块;后绘制的覆盖先绘制的。
|
||||
core.maps._drawFloorImages(
|
||||
floorId,
|
||||
config.ctx,
|
||||
'fg',
|
||||
null,
|
||||
null,
|
||||
config.onMap
|
||||
);
|
||||
core.maps._drawBgFgMap(floorId, 'fg', config);
|
||||
if (config.onMap) {
|
||||
core.drawImage(
|
||||
toDrawCtx,
|
||||
cacheCtx.canvas,
|
||||
core.bigmap.v2 ? -32 : 0,
|
||||
core.bigmap.v2 ? -32 : 0
|
||||
);
|
||||
core.clearMap('fg2');
|
||||
core.clearMap(cacheCtx);
|
||||
}
|
||||
core.maps._drawBgFgMap(floorId, 'fg2', config);
|
||||
if (config.onMap)
|
||||
core.drawImage(
|
||||
'fg2',
|
||||
cacheCtx.canvas,
|
||||
core.bigmap.v2 ? -32 : 0,
|
||||
core.bigmap.v2 ? -32 : 0
|
||||
);
|
||||
config.ctx = toDrawCtx;
|
||||
};
|
||||
////// 移动判定 //////
|
||||
maps.prototype._generateMovableArray_arrays = function (floorId) {
|
||||
return {
|
||||
bgArray: this.getBgMapArray(floorId),
|
||||
fgArray: this.getFgMapArray(floorId),
|
||||
eventArray: this.getMapArray(floorId),
|
||||
bg2Array: this._getBgFgMapArray('bg2', floorId),
|
||||
fg2Array: this._getBgFgMapArray('fg2', floorId)
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { hook } from '@user/data-base';
|
||||
|
||||
const potionItems: AllIdsOf<'items'>[] = [
|
||||
'redPotion',
|
||||
'bluePotion',
|
||||
'yellowPotion',
|
||||
'greenPotion'
|
||||
];
|
||||
|
||||
export function createHook() {
|
||||
hook.on('afterGetItem', (itemId, x, y, isGentleClick) => {
|
||||
// 获得一个道具后触发的事件
|
||||
// itemId:获得的道具ID;x和y是该道具所在的坐标
|
||||
// isGentleClick:是否是轻按触发的
|
||||
if (potionItems.includes(itemId)) core.playSound('回血');
|
||||
else core.playSound('获得道具');
|
||||
|
||||
const todo: any[] = [];
|
||||
// 检查该点的获得道具后事件。
|
||||
if (core.status.floorId == null) return;
|
||||
const event =
|
||||
core.floors[core.status.floorId].afterGetItem[`${x},${y}`];
|
||||
if (
|
||||
event &&
|
||||
(event instanceof Array ||
|
||||
!isGentleClick ||
|
||||
!event.disableOnGentleClick)
|
||||
) {
|
||||
core.unshift(todo, event as any[]);
|
||||
}
|
||||
if (core.hasFlag('spring')) {
|
||||
if (!core.hasFlag('springCount')) core.setFlag('springCount', 0);
|
||||
if (potionItems.includes(itemId)) {
|
||||
core.addFlag('springCount', 1);
|
||||
}
|
||||
if (core.getFlag<number>('springCount', 0) === 50) {
|
||||
core.setFlag('springCount', 0);
|
||||
core.status.hero.hpmax += core.getNakedStatus('hpmax') * 0.1;
|
||||
}
|
||||
core.updateStatusBar();
|
||||
}
|
||||
|
||||
if (todo.length > 0) core.insertAction(todo, x, y);
|
||||
});
|
||||
|
||||
hook.on('afterOpenDoor', (doorId, x, y) => {
|
||||
// 开一个门后触发的事件
|
||||
|
||||
const todo: any[] = [];
|
||||
// 检查该点的获得开门后事件。
|
||||
if (core.status.floorId == null) return;
|
||||
const event =
|
||||
core.floors[core.status.floorId].afterOpenDoor[`${x},${y}`];
|
||||
if (event) core.unshift(todo, event as any[]);
|
||||
|
||||
if (todo.length > 0) core.insertAction(todo, x, y);
|
||||
|
||||
if (core.status.event.id == null) core.continueAutomaticRoute();
|
||||
else core.clearContinueAutomaticRoute();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { loading } from '@user/data-base';
|
||||
import { initFallback } from './fallback';
|
||||
import { initFiveLayer } from './fiveLayer';
|
||||
import { createHook } from './hook';
|
||||
import { initReplay } from './replay';
|
||||
import { initUI } from './ui';
|
||||
import { createEnemy } from './enemy';
|
||||
|
||||
export function createLegacy() {
|
||||
initFallback();
|
||||
loading.once('coreInit', () => {
|
||||
createEnemy();
|
||||
initFiveLayer();
|
||||
createHook();
|
||||
initReplay();
|
||||
initUI();
|
||||
});
|
||||
}
|
||||
|
||||
export * from './enemy';
|
||||
export * from './fallback';
|
||||
export * from './fiveLayer';
|
||||
export * from './removeMap';
|
||||
export * from './replay';
|
||||
export * from './shop';
|
||||
export * from './ui';
|
||||
@@ -0,0 +1,111 @@
|
||||
export function removeMaps(
|
||||
fromId: FloorIds,
|
||||
toId: FloorIds,
|
||||
force: boolean = false
|
||||
) {
|
||||
toId = toId || fromId;
|
||||
var fromIndex = core.floorIds.indexOf(fromId),
|
||||
toIndex = core.floorIds.indexOf(toId);
|
||||
if (toIndex < 0) toIndex = core.floorIds.length - 1;
|
||||
// @ts-ignore
|
||||
flags.__visited__ ??= {};
|
||||
flags.__removed__ ??= [];
|
||||
flags.__disabled__ ??= {};
|
||||
// @ts-ignore
|
||||
flags.__leaveLoc__ ??= {};
|
||||
flags.__forceDelete__ ??= {};
|
||||
let deleted = false;
|
||||
for (var i = fromIndex; i <= toIndex; ++i) {
|
||||
var floorId = core.floorIds[i];
|
||||
if (core.status.maps[floorId].deleted) continue;
|
||||
delete flags.__visited__[floorId];
|
||||
flags.__removed__.push(floorId);
|
||||
delete flags.__disabled__[floorId];
|
||||
delete flags.__leaveLoc__[floorId];
|
||||
(core.status.autoEvents || []).forEach(event => {
|
||||
if (event.floorId == floorId && event.currentFloor) {
|
||||
core.autoEventExecuting(event.symbol, false);
|
||||
core.autoEventExecuted(event.symbol, false);
|
||||
}
|
||||
});
|
||||
core.status.maps[floorId].deleted = true;
|
||||
core.status.maps[floorId].canFlyTo = false;
|
||||
core.status.maps[floorId].canFlyFrom = false;
|
||||
core.status.maps[floorId].cannotViewMap = true;
|
||||
if (force) {
|
||||
core.status.maps[floorId].forceDelete = true;
|
||||
flags.__forceDelete__[floorId] = true;
|
||||
}
|
||||
deleteFlags(floorId);
|
||||
deleted = true;
|
||||
}
|
||||
if (deleted && !main.replayChecking) {
|
||||
Mota.require('@motajs/legacy-ui').splitArea();
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteFlags(floorId: FloorIds) {
|
||||
delete flags[`jump_${floorId}`];
|
||||
delete flags[`inte_${floorId}`];
|
||||
delete flags[`loop_${floorId}`];
|
||||
delete flags[`melt_${floorId}`];
|
||||
delete flags[`night_${floorId}`];
|
||||
}
|
||||
|
||||
// 恢复楼层
|
||||
// core.plugin.removeMap.resumeMaps("MT1", "MT300") 恢复MT1~MT300之间的全部层
|
||||
// core.plugin.removeMap.resumeMaps("MT10") 只恢复MT10层
|
||||
export function resumeMaps(fromId: FloorIds, toId: FloorIds) {
|
||||
toId = toId || fromId;
|
||||
var fromIndex = core.floorIds.indexOf(fromId),
|
||||
toIndex = core.floorIds.indexOf(toId);
|
||||
if (toIndex < 0) toIndex = core.floorIds.length - 1;
|
||||
flags.__removed__ = flags.__removed__ || [];
|
||||
for (var i = fromIndex; i <= toIndex; ++i) {
|
||||
var floorId = core.floorIds[i];
|
||||
if (!core.status.maps[floorId].deleted) continue;
|
||||
if (
|
||||
core.status.maps[floorId].forceDelete ||
|
||||
flags.__forceDelete__[floorId]
|
||||
)
|
||||
continue;
|
||||
// @ts-ignore
|
||||
flags.__removed__ = flags.__removed__.filter(f => {
|
||||
return f != floorId;
|
||||
});
|
||||
// @ts-ignore
|
||||
core.status.maps[floorId] = core.loadFloor(floorId);
|
||||
}
|
||||
}
|
||||
|
||||
// 分区砍层相关
|
||||
var inAnyPartition = (floorId: FloorIds) => {
|
||||
var inPartition = false;
|
||||
(core.floorPartitions || []).forEach(floor => {
|
||||
var fromIndex = core.floorIds.indexOf(floor[0]);
|
||||
var toIndex = core.floorIds.indexOf(floor[1]!);
|
||||
var index = core.floorIds.indexOf(floorId);
|
||||
if (fromIndex < 0 || index < 0) return;
|
||||
if (toIndex < 0) toIndex = core.floorIds.length - 1;
|
||||
if (index >= fromIndex && index <= toIndex) inPartition = true;
|
||||
});
|
||||
return inPartition;
|
||||
};
|
||||
|
||||
// 分区砍层
|
||||
export function autoRemoveMaps(floorId: FloorIds) {
|
||||
if (main.mode != 'play' || !inAnyPartition(floorId)) return;
|
||||
// 根据分区信息自动砍层与恢复
|
||||
(core.floorPartitions || []).forEach(floor => {
|
||||
var fromIndex = core.floorIds.indexOf(floor[0]);
|
||||
var toIndex = core.floorIds.indexOf(floor[1]!);
|
||||
var index = core.floorIds.indexOf(floorId);
|
||||
if (fromIndex < 0 || index < 0) return;
|
||||
if (toIndex < 0) toIndex = core.floorIds.length - 1;
|
||||
if (index >= fromIndex && index <= toIndex) {
|
||||
resumeMaps(core.floorIds[fromIndex], core.floorIds[toIndex]);
|
||||
} else {
|
||||
removeMaps(core.floorIds[fromIndex], core.floorIds[toIndex]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { canOpenShop } from './shop';
|
||||
import { hook } from '@user/data-base';
|
||||
|
||||
let cliping = false;
|
||||
let startIndex = 0;
|
||||
|
||||
export function readyClip() {
|
||||
cliping = true;
|
||||
return (startIndex = core.status.route.length - 1);
|
||||
}
|
||||
|
||||
export function clip(...replace: string[]) {
|
||||
if (!cliping) return;
|
||||
cliping = false;
|
||||
|
||||
core.status.route.splice(startIndex);
|
||||
core.status.route.push(...replace);
|
||||
}
|
||||
|
||||
export function initReplay() {
|
||||
function tipAndWait(content: string, time: number) {
|
||||
const speed = core.status.replay.speed;
|
||||
if (main.replayChecking || speed === 24) return Promise.resolve();
|
||||
const { tip } = Mota.require('@motajs/legacy-ui');
|
||||
tip('info', '录像播放操作:' + content);
|
||||
return new Promise<void>(res => {
|
||||
setTimeout(res, time / speed);
|
||||
});
|
||||
}
|
||||
|
||||
// 商店
|
||||
let shopOpened = false;
|
||||
let openedShopId = '';
|
||||
|
||||
hook.on('reset', () => {
|
||||
shopOpened = false;
|
||||
openedShopId = '';
|
||||
});
|
||||
|
||||
core.registerReplayAction('openShop', name => {
|
||||
if (!name.startsWith('openShop:')) return false;
|
||||
const id = name.slice(9);
|
||||
if (!canOpenShop(id)) return false;
|
||||
if (shopOpened && openedShopId === id) return true;
|
||||
openedShopId = name.slice(9);
|
||||
shopOpened = true;
|
||||
core.status.route.push(name);
|
||||
core.replay();
|
||||
return true;
|
||||
});
|
||||
|
||||
core.registerReplayAction('buy', name => {
|
||||
if (!name.startsWith('buy:') && !name.startsWith('sell:')) return false;
|
||||
if (!shopOpened) return false;
|
||||
if (!openedShopId) return false;
|
||||
const [type, id, n] = name.split(':');
|
||||
const num = parseInt(n);
|
||||
const shop = core.status.shops[openedShopId] as ItemShopEvent;
|
||||
const item = shop.choices.find(v => v.id === id);
|
||||
if (!item) return false;
|
||||
flags.itemShop ??= {};
|
||||
flags.itemShop[openedShopId] ??= {};
|
||||
flags.itemShop[openedShopId][id] ??= 0;
|
||||
if ((num as number) > item.number - flags.itemShop[openedShopId][id]) {
|
||||
return false;
|
||||
}
|
||||
let cost = 0;
|
||||
if (type === 'buy') {
|
||||
cost = parseInt(item.money) * (num as number);
|
||||
} else {
|
||||
cost = -item.sell * (num as number);
|
||||
}
|
||||
if (cost > core.status.hero.money) return false;
|
||||
core.status.hero.money -= cost;
|
||||
flags.itemShop[openedShopId][id] += type === 'buy' ? num : -num;
|
||||
core.addItem(
|
||||
id as AllIdsOf<'items'>,
|
||||
(type === 'buy' ? num : -num) as number
|
||||
);
|
||||
const { name: itemName } = core.material.items[id as AllIdsOf<'items'>];
|
||||
core.status.route.push(name);
|
||||
tipAndWait(`购买物品:${itemName}`, 1000).then(() => {
|
||||
core.replay();
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
core.registerReplayAction('closeShop', name => {
|
||||
if (name !== 'closeShop') return false;
|
||||
shopOpened = false;
|
||||
openedShopId = '';
|
||||
core.status.route.push(name);
|
||||
core.replay();
|
||||
return true;
|
||||
});
|
||||
|
||||
core.registerReplayAction('fly', action => {
|
||||
if (!action.startsWith('fly:')) return false;
|
||||
const floorId = action.slice(4) as FloorIds;
|
||||
if (
|
||||
!core.canUseItem('fly') ||
|
||||
(core.flags.flyNearStair && !core.nearStair())
|
||||
)
|
||||
return false;
|
||||
tipAndWait(`飞往:${floorId}`, 1000).then(() => {
|
||||
if (!core.flyTo(floorId, core.replay)) {
|
||||
core.control._replay_error(action);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// @ts-nocheck
|
||||
|
||||
type Shop = CommonShopEvent | CommonEventShopEvent | ItemShopEvent;
|
||||
|
||||
export function openShop(shopId: string, noRoute: boolean) {
|
||||
const shop = core.status.shops[shopId] as Shop;
|
||||
// Step 1: 检查能否打开此商店
|
||||
if (!canOpenShop(shopId)) {
|
||||
core.drawTip('该商店尚未开启');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!noRoute) {
|
||||
core.status.route.push(`openShop:${shopId}`);
|
||||
}
|
||||
|
||||
// Step2: 检查公共事件商店
|
||||
if (shop.commonEvent) {
|
||||
core.insertCommonEvent(shop.commonEvent, shop.args);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: 检查道具商店
|
||||
if (shop.item) {
|
||||
Mota.r(() => {
|
||||
if (!core.isReplaying()) {
|
||||
Mota.require('@motajs/legacy-ui').mainUi.open('shop', {
|
||||
shopId: shopId
|
||||
});
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Step4 普通商店
|
||||
core.insertAction(convertShop(shop));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 是否访问过某个快捷商店
|
||||
export function isShopVisited(id: string) {
|
||||
flags.__shops__ ??= {};
|
||||
const shops = core.getFlag<any>('__shops__');
|
||||
if (!shops[id]) shops[id] = {};
|
||||
return shops[id].visited;
|
||||
}
|
||||
|
||||
/// 当前应当显示的快捷商店列表
|
||||
export function listShopIds() {
|
||||
return Object.keys(core.status.shops).filter(id => {
|
||||
// @ts-expect-error 无法推导
|
||||
return isShopVisited(id) || !core.status.shops[id].mustEnable;
|
||||
});
|
||||
}
|
||||
|
||||
/// 是否能够打开某个商店
|
||||
export function canOpenShop(id: string) {
|
||||
if (isShopVisited(id)) return true;
|
||||
const shop = core.status.shops[id];
|
||||
// @ts-expect-error 无法推导
|
||||
if (shop.item || shop.commonEvent || shop.mustEnable) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 启用或禁用某个快捷商店
|
||||
export function setShopVisited(id: string, visited: boolean) {
|
||||
if (!core.hasFlag('__shops__')) core.setFlag('__shops__', {});
|
||||
const shops = core.getFlag<any>('__shops__');
|
||||
if (!shops[id]) shops[id] = {};
|
||||
if (visited) shops[id].visited = true;
|
||||
else delete shops[id].visited;
|
||||
}
|
||||
|
||||
/// 能否使用快捷商店
|
||||
export function canUseQuickShop() {
|
||||
// 如果返回一个字符串,表示不能,字符串为不能使用的提示
|
||||
// 返回null代表可以使用
|
||||
|
||||
// 检查当前楼层的canUseQuickShop选项是否为false
|
||||
if (core.status.thisMap.canUseQuickShop === false)
|
||||
return '当前楼层不能使用快捷商店。';
|
||||
return null;
|
||||
}
|
||||
|
||||
////// 将一个全局商店转变成可预览的公共事件 //////
|
||||
function convertShop(shop): MotaEvent {
|
||||
return [
|
||||
{
|
||||
type: 'function',
|
||||
function: "function() {core.addFlag('@temp@shop', 1);}"
|
||||
},
|
||||
{
|
||||
type: 'while',
|
||||
condition: 'true',
|
||||
data: [
|
||||
// 检测能否访问该商店
|
||||
{
|
||||
type: 'if',
|
||||
condition:
|
||||
"Mota.require('@user/legacy-plugin-data').isShopVisited('" +
|
||||
shop.id +
|
||||
"')",
|
||||
true: [
|
||||
// 可以访问,直接插入执行效果
|
||||
{
|
||||
type: 'function',
|
||||
function:
|
||||
"function() { Mota.require('@user/legacy-plugin-data').convertShop_replaceChoices('" +
|
||||
shop.id +
|
||||
"', false) }"
|
||||
}
|
||||
],
|
||||
false: [
|
||||
// 不能访问的情况下:检测能否预览
|
||||
{
|
||||
type: 'if',
|
||||
condition: shop.disablePreview,
|
||||
true: [
|
||||
// 不可预览,提示并退出
|
||||
{ type: 'playSound', name: '操作失败' },
|
||||
'当前无法访问该商店!',
|
||||
{ type: 'break' }
|
||||
],
|
||||
false: [
|
||||
// 可以预览:将商店全部内容进行替换
|
||||
{
|
||||
type: 'tip',
|
||||
text: '当前处于预览模式,不可购买'
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function:
|
||||
"function() { Mota.require('@user/legacy-plugin-data').convertShop_replaceChoices('" +
|
||||
shop.id +
|
||||
"', true) }"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: "function() {core.addFlag('@temp@shop', -1);}"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export function convertShop_replaceChoices(shopId, previewMode) {
|
||||
const shop = core.status.shops[shopId];
|
||||
const choices = (shop.choices || [])
|
||||
.filter(function (choice) {
|
||||
if (choice.condition == null || choice.condition == '') return true;
|
||||
try {
|
||||
return core.calValue(choice.condition);
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map(function (choice) {
|
||||
const ableToBuy = core.calValue(choice.need);
|
||||
return {
|
||||
text: choice.text,
|
||||
icon: choice.icon,
|
||||
color:
|
||||
ableToBuy && !previewMode
|
||||
? choice.color
|
||||
: [153, 153, 153, 1],
|
||||
action:
|
||||
ableToBuy && !previewMode
|
||||
? [{ type: 'playSound', name: '商店' }].concat(
|
||||
choice.action
|
||||
)
|
||||
: [
|
||||
{ type: 'playSound', name: '操作失败' },
|
||||
{
|
||||
type: 'tip',
|
||||
text: previewMode
|
||||
? '预览模式下不可购买'
|
||||
: '购买条件不足'
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
||||
.concat({
|
||||
text: '离开',
|
||||
action: [{ type: 'playSound', name: '取消' }, { type: 'break' }]
|
||||
});
|
||||
|
||||
let text = shop.text.trim();
|
||||
if (text.startsWith('[')) {
|
||||
const end = text.indexOf(']');
|
||||
text = text.slice(end + 1);
|
||||
}
|
||||
core.insertAction({ type: 'choices', text: text, choices: choices });
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// @ts-nocheck
|
||||
|
||||
export function initUI() {
|
||||
if (main.mode === 'editor') return;
|
||||
if (!main.replayChecking) {
|
||||
const { mainUi, fixedUi, mainSetting } =
|
||||
Mota.require('@motajs/legacy-ui');
|
||||
|
||||
ui.prototype.drawBook = function () {
|
||||
if (!core.isReplaying()) return mainUi.open('book');
|
||||
};
|
||||
|
||||
ui.prototype._drawToolbox = function () {
|
||||
if (!core.isReplaying()) return mainUi.open('toolbox');
|
||||
};
|
||||
|
||||
ui.prototype._drawEquipbox = function () {
|
||||
if (!core.isReplaying()) return mainUi.open('equipbox');
|
||||
};
|
||||
|
||||
ui.prototype.drawFly = function () {
|
||||
if (!core.isReplaying()) return mainUi.open('fly');
|
||||
};
|
||||
|
||||
control.prototype.showStatusBar = function () {
|
||||
if (main.mode === 'editor') return;
|
||||
core.removeFlag('hideStatusBar');
|
||||
if (mainSetting.getValue('ui.tips')) {
|
||||
if (!fixedUi.hasName('tips')) {
|
||||
fixedUi.open('tips');
|
||||
}
|
||||
}
|
||||
core.updateStatusBar();
|
||||
};
|
||||
|
||||
control.prototype.hideStatusBar = function (showToolbox) {
|
||||
if (main.mode === 'editor') return;
|
||||
|
||||
// 如果原本就是隐藏的,则先显示
|
||||
if (!core.domStyle.showStatusBar) this.showStatusBar();
|
||||
if (core.isReplaying()) showToolbox = true;
|
||||
fixedUi.closeByName('tips');
|
||||
|
||||
core.setFlag('hideStatusBar', true);
|
||||
core.setFlag('showToolbox', showToolbox || null);
|
||||
core.updateStatusBar();
|
||||
};
|
||||
}
|
||||
|
||||
control.prototype.updateStatusBar_update = function () {
|
||||
core.control.updateNextFrame = false;
|
||||
if (!core.isPlaying() || core.hasFlag('__statistics__')) return;
|
||||
core.control.controldata.updateStatusBar();
|
||||
if (!core.control.noAutoEvents) core.checkAutoEvents();
|
||||
core.control._updateStatusBar_setToolboxIcon();
|
||||
core.control.noAutoEvents = true;
|
||||
Mota.require('@user/data-base').hook.emit('statusBarUpdate');
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user