HumanBreak/src/plugin/chase/chase.ts
2023-02-23 23:18:38 +08:00

255 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Animation, sleep, TimingFn } from 'mutate-animate';
import { completeAchievement } from '../ui/achievement';
import { has } from '../utils';
import { ChaseCameraData, ChasePath, getChaseDataByIndex } from './data';
export default function init() {
return { startChase };
}
export function shake2(power: number, timing: TimingFn): TimingFn {
let r = 0;
return t => {
r += Math.PI / 2;
return Math.sin(r) * power * timing(t);
};
}
export class Chase {
/**
* 动画实例
*/
ani: Animation = new Animation();
/**
* 追逐战的路径
*/
path: ChasePath;
/**
* 是否展示路径
*/
showPath: boolean = false;
endFn?: () => void;
/**
* 开始一个追逐战
* @param index 追逐战索引
* @param path 追逐战的路线
* @param fn 开始时执行的函数
*/
constructor(
path: ChasePath,
fns: ((chase: Chase) => void)[],
camera: ChaseCameraData[],
showPath: boolean = false
) {
this.path = path;
flags.__lockViewport__ = true;
flags.onChase = true;
flags.chaseTime = {
[core.status.floorId]: Date.now()
};
this.ani
.absolute()
.time(0)
.move(core.bigmap.offsetX / 32, core.bigmap.offsetY / 32);
fns.forEach(v => v(this));
const added: FloorIds[] = [];
const ctx = core.createCanvas('chasePath', 0, 0, 0, 0, 35);
for (const [id, x, y, start, time, mode, path] of camera) {
if (!added.includes(id)) {
this.on(
id,
0,
() => {
flags.__lockViewport__ = false;
core.drawHero();
flags.__lockViewport__ = true;
this.ani
.time(0)
.move(
core.bigmap.offsetX / 32,
core.bigmap.offsetY / 32
);
},
true
);
added.push(id);
}
if (!has(path)) {
this.on(id, start, () => {
this.ani.time(time).mode(mode).move(x, y);
});
} else {
this.on(id, start, () => {
this.ani.time(time).mode(mode).moveAs(path);
});
}
}
this.ani.ticker.add(() => {
if (!flags.floorChanging) {
core.setViewport(this.ani.x * 32, this.ani.y * 32);
core.relocateCanvas(ctx, -this.ani.x * 32, -this.ani.y * 32);
}
});
if (showPath) {
for (const [id, p] of Object.entries(path) as [
FloorIds,
LocArr[]
][]) {
this.on(id, 0, () => {
const floor = core.status.maps[id];
core.resizeCanvas(ctx, floor.width * 32, floor.height * 32);
ctx.beginPath();
ctx.moveTo(p[0][0] * 32 + 16, p[1][1] * 32 + 24);
ctx.lineJoin = 'round';
ctx.lineWidth = 4;
ctx.strokeStyle = 'cyan';
ctx.globalAlpha = 0.3;
p.forEach((v, i, a) => {
if (i === 0) return;
const [x, y] = v;
ctx.lineTo(x * 32 + 16, y * 32 + 24);
});
ctx.stroke();
});
}
}
}
/**
* 在追逐战的某个时刻执行函数
* @param floorId 楼层id
* @param time 该楼层中经过的时间
* @param fn 执行的函数
*/
on(
floorId: FloorIds,
time: number,
fn: (chase: Chase) => void,
first: boolean = false
) {
const func = () => {
if (!flags.chaseTime?.[floorId]) return;
if (Date.now() - (flags.chaseTime?.[floorId] ?? 0) >= time) {
fn(this);
this.ani.ticker.remove(func);
}
};
this.ani.ticker.add(func, first);
}
/**
* 当勇士移动到某个点上时执行函数
* @param x 横坐标
* @param y 纵坐标
* @param floorId 楼层id
* @param fn 执行的函数
* @param mode 为0时当传入数组时表示勇士在任意一个位置都执行否则是每个位置执行一次
*/
onHeroLoc(
floorId: FloorIds,
fn: (chase: Chase) => void,
x?: number | number[],
y?: number | number[],
mode: 0 | 1 = 0
) {
if (mode === 1) {
if (typeof x === 'number') x = [x];
if (typeof y === 'number') y = [y];
x!.forEach(v => {
(y as number[]).forEach(vv => {
this.onHeroLoc(floorId, fn, v, vv);
});
});
return;
}
const judge = () => {
if (core.status.floorId !== floorId) return false;
if (has(x)) {
if (typeof x === 'number') {
if (core.status.hero.loc.x !== x) return false;
} else {
if (!x.includes(core.status.hero.loc.x)) return false;
}
}
if (has(y)) {
if (typeof y === 'number') {
if (core.status.hero.loc.y !== y) return false;
} else {
if (!y.includes(core.status.hero.loc.y)) return false;
}
}
return true;
};
const func = () => {
if (judge()) {
fn(this);
try {
this.ani.ticker.remove(func);
} catch {}
}
};
this.ani.ticker.add(func);
}
/**
* 设置路径显示状态
* @param show 是否显示路径
*/
setPathShowStatus(show: boolean) {
this.showPath = show;
}
/**
* 当追逐战结束后执行函数
* @param fn 执行的函数
*/
onEnd(fn: () => void) {
this.endFn = fn;
}
/**
* 结束这个追逐战
*/
end() {
this.ani.ticker.destroy();
delete flags.onChase;
delete flags.chase;
delete flags.chaseTime;
delete flags.chaseHard;
delete flags.chaseIndex;
flags.__lockViewport__ = false;
core.deleteCanvas('chasePath');
if (this.endFn) this.endFn();
}
}
export async function startChase(index: number) {
const data = getChaseDataByIndex(index);
flags.chaseIndex = index;
flags.onChase = true;
await sleep(20);
const chase = new Chase(
data.path,
data.fns,
data.camera,
flags.chaseHard === 0
);
flags.chase = chase;
// 成就
chase.onEnd(() => {
if (flags.chaseHard === 1) {
if (index === 1) {
completeAchievement('challenge', 0);
}
}
});
}