mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-11 15:47:06 +08:00
255 lines
7.1 KiB
TypeScript
255 lines
7.1 KiB
TypeScript
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);
|
||
}
|
||
}
|
||
});
|
||
}
|