mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-06-09 00:38:00 +08:00
fix: 光环显示 & 删除无用内容
This commit is contained in:
parent
9597831d74
commit
470be5edba
@ -142,7 +142,7 @@
|
|||||||
<canvas class='gameCanvas draw-canvas hide' id='event2'></canvas>
|
<canvas class='gameCanvas draw-canvas hide' id='event2'></canvas>
|
||||||
<canvas class='gameCanvas draw-canvas hide' id='fg'></canvas>
|
<canvas class='gameCanvas draw-canvas hide' id='fg'></canvas>
|
||||||
<canvas class='gameCanvas hide' id='damage'></canvas>
|
<canvas class='gameCanvas hide' id='damage'></canvas>
|
||||||
<canvas class='gameCanvas' id='animate'></canvas>
|
<canvas class='gameCanvas hide' id='animate'></canvas>
|
||||||
<canvas class='gameCanvas hide' id='curtain'></canvas>
|
<canvas class='gameCanvas hide' id='curtain'></canvas>
|
||||||
<canvas class='gameCanvas' id='ui'></canvas>
|
<canvas class='gameCanvas' id='ui'></canvas>
|
||||||
<canvas class='gameCanvas' id='data'>此浏览器不支持HTML5</canvas>
|
<canvas class='gameCanvas' id='data'>此浏览器不支持HTML5</canvas>
|
||||||
|
@ -121,6 +121,7 @@ hook.once('reset', () => {
|
|||||||
Mota.rewrite(core.control, 'loadData', 'add', () => {
|
Mota.rewrite(core.control, 'loadData', 'add', () => {
|
||||||
if (!main.replayChecking) {
|
if (!main.replayChecking) {
|
||||||
Shadow.update(true);
|
Shadow.update(true);
|
||||||
|
LayerShadowExtends.shadowList.forEach(v => v.update());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Mota.require('var', 'hook').on('changingFloor', (floorId) => {
|
// Mota.require('var', 'hook').on('changingFloor', (floorId) => {
|
||||||
@ -141,7 +142,7 @@ hook.on('setBlock', () => {
|
|||||||
})
|
})
|
||||||
hook.on('changingFloor', floorId => {
|
hook.on('changingFloor', floorId => {
|
||||||
Shadow.clearBuffer();
|
Shadow.clearBuffer();
|
||||||
Shadow.update();
|
Shadow.update(true);
|
||||||
// setCanvasFilterByFloorId(floorId);
|
// setCanvasFilterByFloorId(floorId);
|
||||||
LayerShadowExtends.shadowList.forEach(v => v.update());
|
LayerShadowExtends.shadowList.forEach(v => v.update());
|
||||||
})
|
})
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
// import frag from '@/plugin/fx/frag';
|
// import frag from '@/plugin/fx/frag';
|
||||||
// import { Mota } from '.';
|
// import { Mota } from '.';
|
||||||
|
|
||||||
import * as shadow from '@/plugin/shadow/shadow';
|
|
||||||
import * as gameShadow from '@/plugin/shadow/gameShadow';
|
|
||||||
import * as fly from '@/plugin/ui/fly';
|
import * as fly from '@/plugin/ui/fly';
|
||||||
import * as chase from '@/plugin/chase/chase';
|
import * as chase from '@/plugin/chase/chase';
|
||||||
import * as completion from '@/plugin/completion';
|
import * as completion from '@/plugin/completion';
|
||||||
@ -29,8 +27,6 @@ import * as gameCanvas from '@/plugin/fx/gameCanvas';
|
|||||||
import * as smooth from '@/plugin/fx/smoothView';
|
import * as smooth from '@/plugin/fx/smoothView';
|
||||||
import * as animateController from '@/plugin/animateController';
|
import * as animateController from '@/plugin/animateController';
|
||||||
|
|
||||||
Mota.Plugin.register('shadow_r', shadow, shadow.init);
|
|
||||||
Mota.Plugin.register('gameShadow_r', gameShadow, gameShadow.init);
|
|
||||||
Mota.Plugin.register('fly_r', fly);
|
Mota.Plugin.register('fly_r', fly);
|
||||||
Mota.Plugin.register('chase_r', chase);
|
Mota.Plugin.register('chase_r', chase);
|
||||||
Mota.Plugin.register('completion_r', completion, completion.init);
|
Mota.Plugin.register('completion_r', completion, completion.init);
|
||||||
|
@ -8,6 +8,7 @@ import { LayerShadowExtends } from '../fx/shadow';
|
|||||||
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
|
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
|
||||||
import { LayerGroupAnimate } from './preset/animate';
|
import { LayerGroupAnimate } from './preset/animate';
|
||||||
import { LayerGroupPortal } from '@/plugin/fx/portal';
|
import { LayerGroupPortal } from '@/plugin/fx/portal';
|
||||||
|
import { LayerGroupHalo } from '@/plugin/fx/halo';
|
||||||
|
|
||||||
let main: MotaRenderer;
|
let main: MotaRenderer;
|
||||||
|
|
||||||
@ -35,16 +36,19 @@ Mota.require('var', 'loading').once('loaded', () => {
|
|||||||
const filter = new LayerGroupFilter();
|
const filter = new LayerGroupFilter();
|
||||||
const animate = new LayerGroupAnimate();
|
const animate = new LayerGroupAnimate();
|
||||||
const portal = new LayerGroupPortal();
|
const portal = new LayerGroupPortal();
|
||||||
|
const halo = new LayerGroupHalo();
|
||||||
layer.extends(damage);
|
layer.extends(damage);
|
||||||
layer.extends(detail);
|
layer.extends(detail);
|
||||||
layer.extends(filter);
|
layer.extends(filter);
|
||||||
layer.extends(portal);
|
layer.extends(portal);
|
||||||
|
layer.extends(halo);
|
||||||
layer.getLayer('event')?.extends(hero);
|
layer.getLayer('event')?.extends(hero);
|
||||||
layer.getLayer('event')?.extends(door);
|
layer.getLayer('event')?.extends(door);
|
||||||
layer.getLayer('event')?.extends(shadow);
|
layer.getLayer('event')?.extends(shadow);
|
||||||
layer.extends(animate);
|
layer.extends(animate);
|
||||||
|
|
||||||
render.appendChild(layer);
|
render.appendChild(layer);
|
||||||
|
// console.log(render);
|
||||||
});
|
});
|
||||||
|
|
||||||
Mota.require('var', 'hook').on('reset', () => {
|
Mota.require('var', 'hook').on('reset', () => {
|
||||||
|
@ -56,6 +56,7 @@ export class FloorDamageExtends
|
|||||||
private create() {
|
private create() {
|
||||||
if (this.sprite) return;
|
if (this.sprite) return;
|
||||||
const sprite = new Damage();
|
const sprite = new Damage();
|
||||||
|
sprite.setZIndex(80);
|
||||||
this.group.appendChild(sprite);
|
this.group.appendChild(sprite);
|
||||||
this.sprite = sprite;
|
this.sprite = sprite;
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,14 @@ export interface ILayerGroupRenderExtends {
|
|||||||
|
|
||||||
export type FloorLayer = 'bg' | 'bg2' | 'event' | 'fg' | 'fg2';
|
export type FloorLayer = 'bg' | 'bg2' | 'event' | 'fg' | 'fg2';
|
||||||
|
|
||||||
|
const layerZIndex: Record<FloorLayer, number> = {
|
||||||
|
bg: 10,
|
||||||
|
bg2: 20,
|
||||||
|
event: 30,
|
||||||
|
fg: 40,
|
||||||
|
fg2: 50
|
||||||
|
};
|
||||||
|
|
||||||
export class LayerGroup extends Container implements IAnimateFrame {
|
export class LayerGroup extends Container implements IAnimateFrame {
|
||||||
/** 地图组列表 */
|
/** 地图组列表 */
|
||||||
// static list: Set<LayerGroup> = new Set();
|
// static list: Set<LayerGroup> = new Set();
|
||||||
@ -190,6 +198,7 @@ export class LayerGroup extends Container implements IAnimateFrame {
|
|||||||
const l = new Layer();
|
const l = new Layer();
|
||||||
l.layer = layer;
|
l.layer = layer;
|
||||||
if (l.layer) this.layers.set(l.layer, l);
|
if (l.layer) this.layers.set(l.layer, l);
|
||||||
|
l.setZIndex(layerZIndex[layer]);
|
||||||
this.appendChild(l);
|
this.appendChild(l);
|
||||||
|
|
||||||
for (const ex of this.extend.values()) {
|
for (const ex of this.extend.values()) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Animation, linear, sleep } from 'mutate-animate';
|
import { Animation, linear, sleep } from 'mutate-animate';
|
||||||
import { has } from '../utils';
|
import { has } from '../utils';
|
||||||
|
|
||||||
|
// todo: 移植到渲染树
|
||||||
|
|
||||||
interface SplittedImage {
|
interface SplittedImage {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -5,10 +5,6 @@ import {
|
|||||||
LayerGroup
|
LayerGroup
|
||||||
} from '@/core/render/preset/layer';
|
} from '@/core/render/preset/layer';
|
||||||
|
|
||||||
export default function init() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterMap: [FloorIds[], string][] = [];
|
const filterMap: [FloorIds[], string][] = [];
|
||||||
|
|
||||||
function getCanvasFilterByFloorId(floorId: FloorIds = core.status.floorId) {
|
function getCanvasFilterByFloorId(floorId: FloorIds = core.status.floorId) {
|
||||||
|
106
src/plugin/fx/halo.ts
Normal file
106
src/plugin/fx/halo.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { logger } from '@/core/common/logger';
|
||||||
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
|
import { mainSetting } from '@/core/main/setting';
|
||||||
|
import { LayerGroupFloorBinder } from '@/core/render/preset/floor';
|
||||||
|
import {
|
||||||
|
ILayerGroupRenderExtends,
|
||||||
|
LayerGroup
|
||||||
|
} from '@/core/render/preset/layer';
|
||||||
|
import { Sprite } from '@/core/render/sprite';
|
||||||
|
import { Transform } from '@/core/render/transform';
|
||||||
|
|
||||||
|
export class LayerGroupHalo implements ILayerGroupRenderExtends {
|
||||||
|
id: string = 'halo';
|
||||||
|
|
||||||
|
group!: LayerGroup;
|
||||||
|
binder!: LayerGroupFloorBinder;
|
||||||
|
halo!: Halo;
|
||||||
|
|
||||||
|
awake(group: LayerGroup): void {
|
||||||
|
this.group = group;
|
||||||
|
const ex = group.getExtends('floor-binder');
|
||||||
|
if (ex instanceof LayerGroupFloorBinder) {
|
||||||
|
this.binder = ex;
|
||||||
|
this.halo = new Halo();
|
||||||
|
this.halo.setHD(true);
|
||||||
|
this.halo.size(group.width, group.height);
|
||||||
|
this.halo.setZIndex(75);
|
||||||
|
this.halo.binder = ex;
|
||||||
|
group.appendChild(this.halo);
|
||||||
|
} else {
|
||||||
|
logger.error(
|
||||||
|
1401,
|
||||||
|
`Halo extends needs 'floor-binder' extends as dependency.`
|
||||||
|
);
|
||||||
|
group.removeExtends('halo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(group: LayerGroup): void {
|
||||||
|
this.halo?.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const haloColor: Record<number, string[]> = {
|
||||||
|
21: ['cyan'],
|
||||||
|
25: ['purple'],
|
||||||
|
26: ['blue'],
|
||||||
|
27: ['red'],
|
||||||
|
29: ['#3CFF49']
|
||||||
|
};
|
||||||
|
|
||||||
|
class Halo extends Sprite {
|
||||||
|
/** 单元格大小 */
|
||||||
|
cellSize: number = 32;
|
||||||
|
/** 当前楼层,用于获取有哪些光环 */
|
||||||
|
binder!: LayerGroupFloorBinder;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('static', false);
|
||||||
|
|
||||||
|
this.setRenderFn((canvas, transform) => {
|
||||||
|
this.drawHalo(canvas, transform);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
drawHalo(canvas: MotaOffscreenCanvas2D, transform: Transform) {
|
||||||
|
if (!mainSetting.getValue('screen.halo', true)) return;
|
||||||
|
const floorId = this.binder.getFloor();
|
||||||
|
if (!floorId) return;
|
||||||
|
const col = core.status.maps[floorId].enemy;
|
||||||
|
if (!col) return;
|
||||||
|
const [dx, dy] = col.translation;
|
||||||
|
const list = col.haloList.concat(
|
||||||
|
Object.keys(flags[`melt_${floorId}`] ?? {}).map(v => {
|
||||||
|
const [x, y] = v.split(',').map(v => parseInt(v));
|
||||||
|
return {
|
||||||
|
type: 'square',
|
||||||
|
data: {
|
||||||
|
x: x + dx,
|
||||||
|
y: y + dy,
|
||||||
|
d: 3
|
||||||
|
},
|
||||||
|
special: 25
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const { ctx } = canvas;
|
||||||
|
const cell = this.cellSize;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for (const halo of list) {
|
||||||
|
if (halo.type === 'square') {
|
||||||
|
const { x, y, d } = halo.data;
|
||||||
|
const [color, border] = haloColor[halo.special];
|
||||||
|
const r = Math.floor(d / 2);
|
||||||
|
const left = x - r;
|
||||||
|
const top = y - r;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.strokeStyle = border ?? color;
|
||||||
|
ctx.globalAlpha = 0.1;
|
||||||
|
ctx.fillRect(left * cell, top * cell, d * cell, d * cell);
|
||||||
|
ctx.globalAlpha = 0.6;
|
||||||
|
ctx.strokeRect(left * cell, top * cell, d * cell, d * cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -227,7 +227,7 @@ export class FloorItemDetail implements ILayerGroupRenderExtends {
|
|||||||
for (const [key, value] of Object.entries(diff)) {
|
for (const [key, value] of Object.entries(diff)) {
|
||||||
if (!value) continue;
|
if (!value) continue;
|
||||||
const color = FloorItemDetail.detailColor[key] ?? '#fff';
|
const color = FloorItemDetail.detailColor[key] ?? '#fff';
|
||||||
const text = value.toString();
|
const text = Math.floor(value).toString();
|
||||||
const renderable: DamageRenderable = {
|
const renderable: DamageRenderable = {
|
||||||
x: x * this.sprite.cellSize + 2,
|
x: x * this.sprite.cellSize + 2,
|
||||||
y: y * this.sprite.cellSize + 31 - n * 10,
|
y: y * this.sprite.cellSize + 31 - n * 10,
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
export default function init() {
|
|
||||||
return { createGaussNoise };
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GaussNoiseConfig {
|
|
||||||
/** 分辨率,最小为1,最大为画布长宽的最大值,默认为画布长宽最大值的一半,过大可能会卡顿 */
|
|
||||||
resolution?: number;
|
|
||||||
/** 画布宽度 */
|
|
||||||
width?: number;
|
|
||||||
/** 画布高度 */
|
|
||||||
height?: number;
|
|
||||||
/** 噪声灰度的均值,范围 0 ~ 255 */
|
|
||||||
expectation: number;
|
|
||||||
/** 噪声灰度方差 */
|
|
||||||
deviation: number;
|
|
||||||
/** 目标画布,如果不指定会创建一个新的 */
|
|
||||||
canvas?: HTMLCanvasElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建一个高斯噪声
|
|
||||||
* @param config 噪声选项
|
|
||||||
* @returns 噪声画布
|
|
||||||
*/
|
|
||||||
export function createGaussNoise(config: GaussNoiseConfig): HTMLCanvasElement {
|
|
||||||
const canvas = config.canvas ?? document.createElement('canvas');
|
|
||||||
canvas.width = config.width ?? core._PX_;
|
|
||||||
canvas.height = config.height ?? core._PY_;
|
|
||||||
canvas.style.imageRendering = 'pixelated';
|
|
||||||
const ctx = canvas.getContext('2d')!;
|
|
||||||
ctx.imageSmoothingEnabled = false;
|
|
||||||
|
|
||||||
const max = Math.max(canvas.width, canvas.height);
|
|
||||||
const resolution = Math.min(max, config.resolution ?? max / 2);
|
|
||||||
|
|
||||||
const step =
|
|
||||||
max === canvas.width
|
|
||||||
? canvas.width / resolution
|
|
||||||
: canvas.height / resolution;
|
|
||||||
|
|
||||||
for (let x = 0; x < canvas.width; x += step) {
|
|
||||||
for (let y = 0; y < canvas.height; y += step) {
|
|
||||||
const random =
|
|
||||||
Math.sqrt(Math.log(Math.random()) * -2) *
|
|
||||||
Math.sin(2 * Math.PI * Math.random());
|
|
||||||
const gray = 255 - random * config.deviation - config.expectation;
|
|
||||||
|
|
||||||
ctx.fillStyle = `rgba(${gray},${gray},${gray},${gray / 255})`;
|
|
||||||
ctx.fillRect(x, y, step, step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return canvas;
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import { has, ofDir } from '@/plugin/game/utils';
|
import { has, ofDir } from '@/plugin/game/utils';
|
||||||
import { drawHalo } from '../fx/halo';
|
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
// 伤害弹出
|
// 伤害弹出
|
||||||
|
@ -986,11 +986,8 @@ export function init() {
|
|||||||
core.setHeroLoc('y', ny);
|
core.setHeroLoc('y', ny);
|
||||||
core.setHeroLoc('direction', action);
|
core.setHeroLoc('direction', action);
|
||||||
}
|
}
|
||||||
if (!main.replayChecking) {
|
|
||||||
setTimeout(core.replay, 100);
|
setTimeout(core.replay, 100);
|
||||||
} else {
|
|
||||||
core.replay();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
const haloColor: Record<number, string[]> = {
|
|
||||||
21: ['cyan'],
|
|
||||||
25: ['purple'],
|
|
||||||
26: ['blue'],
|
|
||||||
27: ['red'],
|
|
||||||
29: ['#3CFF49']
|
|
||||||
};
|
|
||||||
|
|
||||||
export function drawHalo(
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
onMap: boolean,
|
|
||||||
floorId: FloorIds
|
|
||||||
) {
|
|
||||||
if (main.replayChecking) return;
|
|
||||||
const setting = Mota.require('var', 'mainSetting');
|
|
||||||
if (!setting.getValue('screen.halo', true)) return;
|
|
||||||
Mota.require('fn', 'ensureFloorDamage')(floorId);
|
|
||||||
const col = core.status.maps[floorId].enemy;
|
|
||||||
const [dx, dy] = col.translation;
|
|
||||||
const list = col.haloList.concat(
|
|
||||||
Object.keys(flags[`melt_${floorId}`] ?? {}).map(v => {
|
|
||||||
const [x, y] = v.split(',').map(v => parseInt(v));
|
|
||||||
return {
|
|
||||||
type: 'square',
|
|
||||||
data: {
|
|
||||||
x: x + dx,
|
|
||||||
y: y + dy,
|
|
||||||
d: 3
|
|
||||||
},
|
|
||||||
special: 25
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
ctx.save();
|
|
||||||
for (const halo of list) {
|
|
||||||
if (halo.type === 'square') {
|
|
||||||
const { x, y, d } = halo.data;
|
|
||||||
const [color, border] = haloColor[halo.special];
|
|
||||||
const r = Math.floor(d / 2);
|
|
||||||
let left = x - r,
|
|
||||||
right = x + r,
|
|
||||||
top = y - r,
|
|
||||||
bottom = y + r;
|
|
||||||
if (onMap && core.bigmap.v2) {
|
|
||||||
left -= core.bigmap.posX;
|
|
||||||
top -= core.bigmap.posY;
|
|
||||||
right -= core.bigmap.posX;
|
|
||||||
bottom -= core.bigmap.posY;
|
|
||||||
if (
|
|
||||||
right < -1 ||
|
|
||||||
left > core._PX_ / 32 + 1 ||
|
|
||||||
top < -1 ||
|
|
||||||
bottom > core._PY_ / 32 + 1
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.fillStyle = color;
|
|
||||||
ctx.strokeStyle = border ?? color;
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.globalAlpha = 0.1;
|
|
||||||
ctx.fillRect(left * 32, top * 32, d * 32, d * 32);
|
|
||||||
ctx.globalAlpha = 0.6;
|
|
||||||
ctx.strokeRect(left * 32, top * 32, d * 32, d * 32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
function drawHeroDetail(px: number, py: number) {
|
|
||||||
const setting = Mota.require('var', 'mainSetting');
|
|
||||||
if (!setting.getValue('screen.heroDetail', false)) return;
|
|
||||||
const { hp, atk, def } = core.status.hero;
|
|
||||||
const toDraw = {
|
|
||||||
atk: {
|
|
||||||
value: atk,
|
|
||||||
color: '#FF7A7A'
|
|
||||||
},
|
|
||||||
def: {
|
|
||||||
value: def,
|
|
||||||
color: '#00E6F1'
|
|
||||||
},
|
|
||||||
hp: {
|
|
||||||
value: hp,
|
|
||||||
color: '#F9FFFF0'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
for (const [key, value] of Object.entries(toDraw)) {
|
|
||||||
const ctx = core.canvas['hero'];
|
|
||||||
core.fillBoldText(
|
|
||||||
ctx,
|
|
||||||
core.formatBigNumber(value.value),
|
|
||||||
px,
|
|
||||||
py - 10 * i,
|
|
||||||
value.color
|
|
||||||
);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { drawHeroDetail };
|
|
@ -1,60 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
var heroMoving = timestamp => {
|
|
||||||
if (core.status.heroMoving <= 0) return;
|
|
||||||
if (timestamp - core.animateFrame.moveTime > core.values.moveSpeed) {
|
|
||||||
core.animateFrame.leftLeg++;
|
|
||||||
core.animateFrame.moveTime = timestamp;
|
|
||||||
}
|
|
||||||
core.drawHero(
|
|
||||||
['stop', 'leftFoot', 'midFoot', 'rightFoot'][
|
|
||||||
core.animateFrame.leftLeg % 4
|
|
||||||
],
|
|
||||||
4 * core.status.heroMoving
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function init() {
|
|
||||||
['up', 'down', 'left', 'right'].forEach(one => {
|
|
||||||
// 指定中间帧动画
|
|
||||||
core.material.icons.hero[one].midFoot = 2;
|
|
||||||
});
|
|
||||||
|
|
||||||
core.registerAnimationFrame('heroMoving', true, heroMoving);
|
|
||||||
|
|
||||||
core.events._eventMoveHero_moving = function (step, moveSteps) {
|
|
||||||
var curr = moveSteps[0];
|
|
||||||
var direction = curr[0],
|
|
||||||
x = core.getHeroLoc('x'),
|
|
||||||
y = core.getHeroLoc('y');
|
|
||||||
// ------ 前进/后退
|
|
||||||
var o = direction == 'backward' ? -1 : 1;
|
|
||||||
if (direction == 'forward' || direction == 'backward')
|
|
||||||
direction = core.getHeroLoc('direction');
|
|
||||||
var faceDirection = direction;
|
|
||||||
if (direction == 'leftup' || direction == 'leftdown')
|
|
||||||
faceDirection = 'left';
|
|
||||||
if (direction == 'rightup' || direction == 'rightdown')
|
|
||||||
faceDirection = 'right';
|
|
||||||
core.setHeroLoc('direction', direction);
|
|
||||||
if (curr[1] <= 0) {
|
|
||||||
core.setHeroLoc('direction', faceDirection);
|
|
||||||
moveSteps.shift();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (step <= 4) core.drawHero('stop', 4 * o * step);
|
|
||||||
else if (step <= 8) core.drawHero('leftFoot', 4 * o * step);
|
|
||||||
else if (step <= 12) core.drawHero('midFoot', 4 * o * (step - 8));
|
|
||||||
else if (step <= 16) core.drawHero('rightFoot', 4 * o * (step - 8)); // if (step == 8) {
|
|
||||||
if (step == 8 || step == 16) {
|
|
||||||
core.setHeroLoc('x', x + o * core.utils.scan2[direction].x, true);
|
|
||||||
core.setHeroLoc('y', y + o * core.utils.scan2[direction].y, true);
|
|
||||||
core.updateFollowers();
|
|
||||||
curr[1]--;
|
|
||||||
if (curr[1] <= 0) moveSteps.shift();
|
|
||||||
core.setHeroLoc('direction', faceDirection);
|
|
||||||
return step == 16;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
@ -27,118 +27,7 @@ export function init() {
|
|||||||
|
|
||||||
// floor.enemy.render(true);
|
// floor.enemy.render(true);
|
||||||
|
|
||||||
getItemDetail(floorId, onMap); // 宝石血瓶详细信息
|
// getItemDetail(floorId, onMap); // 宝石血瓶详细信息
|
||||||
// this.drawDamage(ctx, floorId);
|
// this.drawDamage(ctx, floorId);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取宝石信息 并绘制
|
|
||||||
function getItemDetail(floorId: FloorIds, onMap: boolean) {
|
|
||||||
const setting = Mota.require('var', 'mainSetting');
|
|
||||||
if (!setting.getValue('screen.itemDetail')) return;
|
|
||||||
floorId ??= core.status.thisMap.floorId;
|
|
||||||
let diff: Record<string | symbol, number | undefined> = {};
|
|
||||||
const before = core.status.hero;
|
|
||||||
const hero = core.clone(core.status.hero);
|
|
||||||
const handler: ProxyHandler<any> = {
|
|
||||||
set(target, key, v) {
|
|
||||||
diff[key] = v - (target[key] || 0);
|
|
||||||
if (!diff[key]) diff[key] = void 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
core.status.hero = new Proxy(hero, handler);
|
|
||||||
|
|
||||||
core.status.maps[floorId].blocks.forEach(function (block) {
|
|
||||||
if (block.event.cls !== 'items' || block.disable) return;
|
|
||||||
const x = block.x,
|
|
||||||
y = block.y;
|
|
||||||
// v2优化,只绘制范围内的部分
|
|
||||||
if (onMap && core.bigmap.v2) {
|
|
||||||
if (
|
|
||||||
x < core.bigmap.posX - core.bigmap.extend ||
|
|
||||||
x > core.bigmap.posX + core._PX_ + core.bigmap.extend ||
|
|
||||||
y < core.bigmap.posY - core.bigmap.extend ||
|
|
||||||
y > core.bigmap.posY + core._PY_ + core.bigmap.extend
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diff = {};
|
|
||||||
const id = block.event.id as AllIdsOf<'items'>;
|
|
||||||
const item = core.material.items[id];
|
|
||||||
if (item.cls === 'equips') {
|
|
||||||
// 装备也显示
|
|
||||||
const diff: Record<string, any> = core.clone(
|
|
||||||
item.equip.value ?? {}
|
|
||||||
);
|
|
||||||
const per = item.equip.percentage ?? {};
|
|
||||||
for (const name in per) {
|
|
||||||
diff[name + 'per'] =
|
|
||||||
per[name as SelectKey<HeroStatus, number>].toString() + '%';
|
|
||||||
}
|
|
||||||
drawItemDetail(diff, x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 跟数据统计原理一样 执行效果 前后比较
|
|
||||||
core.setFlag('__statistics__', true);
|
|
||||||
try {
|
|
||||||
eval(item.itemEffect!);
|
|
||||||
} catch (error) {}
|
|
||||||
drawItemDetail(diff, x, y);
|
|
||||||
});
|
|
||||||
core.status.hero = before;
|
|
||||||
window.hero = before;
|
|
||||||
window.flags = before.flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制
|
|
||||||
function drawItemDetail(diff: any, x: number, y: number) {
|
|
||||||
const px = 32 * x + 2,
|
|
||||||
py = 32 * y + 31;
|
|
||||||
let content = '';
|
|
||||||
// 获得数据和颜色
|
|
||||||
let i = 0;
|
|
||||||
for (const name in diff) {
|
|
||||||
if (!diff[name]) continue;
|
|
||||||
let color = '#fff';
|
|
||||||
|
|
||||||
if (typeof diff[name] === 'number')
|
|
||||||
content = core.formatBigNumber(Math.round(diff[name]), true);
|
|
||||||
else content = diff[name];
|
|
||||||
|
|
||||||
switch (name) {
|
|
||||||
case 'atk':
|
|
||||||
case 'atkper':
|
|
||||||
color = '#FF7A7A';
|
|
||||||
break;
|
|
||||||
case 'def':
|
|
||||||
case 'defper':
|
|
||||||
color = '#00E6F1';
|
|
||||||
break;
|
|
||||||
case 'mdef':
|
|
||||||
case 'mdefper':
|
|
||||||
color = '#6EFF83';
|
|
||||||
break;
|
|
||||||
case 'hp':
|
|
||||||
color = '#A4FF00';
|
|
||||||
break;
|
|
||||||
case 'hpmax':
|
|
||||||
case 'hpmaxper':
|
|
||||||
color = '#F9FF00';
|
|
||||||
break;
|
|
||||||
case 'manaper':
|
|
||||||
case 'mana':
|
|
||||||
color = '#c66';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// 绘制
|
|
||||||
core.status.damage.data.push({
|
|
||||||
text: content,
|
|
||||||
px: px,
|
|
||||||
py: py - 10 * i,
|
|
||||||
color: color as Color
|
|
||||||
});
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
/* @__PURE__ */ import './dev/hotReload'; // 仅开发会用到
|
/* @__PURE__ */ import './dev/hotReload'; // 仅开发会用到
|
||||||
import * as fiveLayer from './fiveLayer';
|
import * as fiveLayer from './fiveLayer';
|
||||||
import * as heroFourFrames from './fx/heroFourFrames';
|
|
||||||
import * as itemDetail from './fx/itemDetail';
|
import * as itemDetail from './fx/itemDetail';
|
||||||
import * as replay from './replay';
|
import * as replay from './replay';
|
||||||
import * as ui from './ui';
|
import * as ui from './ui';
|
||||||
import * as rewrite from './fx/rewrite';
|
import * as rewrite from './fx/rewrite';
|
||||||
import * as halo from './fx/halo';
|
|
||||||
import * as loopMap from './loopMap';
|
import * as loopMap from './loopMap';
|
||||||
import * as removeMap from './removeMap';
|
import * as removeMap from './removeMap';
|
||||||
import * as shop from './shop';
|
import * as shop from './shop';
|
||||||
@ -29,10 +27,8 @@ Mota.Plugin.register('chase_g', chase);
|
|||||||
Mota.Plugin.register('skill_g', skill);
|
Mota.Plugin.register('skill_g', skill);
|
||||||
Mota.Plugin.register('towerBoss_g', towerBoss);
|
Mota.Plugin.register('towerBoss_g', towerBoss);
|
||||||
Mota.Plugin.register('fiveLayer_g', fiveLayer, fiveLayer.init);
|
Mota.Plugin.register('fiveLayer_g', fiveLayer, fiveLayer.init);
|
||||||
Mota.Plugin.register('heroFourFrames_g', heroFourFrames, heroFourFrames.init);
|
|
||||||
Mota.Plugin.register('rewrite_g', rewrite, rewrite.init);
|
Mota.Plugin.register('rewrite_g', rewrite, rewrite.init);
|
||||||
Mota.Plugin.register('itemDetail_g', itemDetail, itemDetail.init);
|
Mota.Plugin.register('itemDetail_g', itemDetail, itemDetail.init);
|
||||||
Mota.Plugin.register('halo_g', halo);
|
|
||||||
// Mota.Plugin.register('study_g', study);
|
// Mota.Plugin.register('study_g', study);
|
||||||
Mota.Plugin.register('remainEnemy_g', remainEnemy);
|
Mota.Plugin.register('remainEnemy_g', remainEnemy);
|
||||||
Mota.Plugin.register('checkBlock_g', checkBlock, checkBlock.init);
|
Mota.Plugin.register('checkBlock_g', checkBlock, checkBlock.init);
|
||||||
|
@ -1,345 +0,0 @@
|
|||||||
import { has } from '../utils';
|
|
||||||
|
|
||||||
type CanvasStyle = string | CanvasPattern | CanvasGradient;
|
|
||||||
|
|
||||||
export class Layout {
|
|
||||||
/** 画布 */
|
|
||||||
canvas: HTMLCanvasElement;
|
|
||||||
/** 绘制上下文 */
|
|
||||||
ctx: CanvasRenderingContext2D;
|
|
||||||
|
|
||||||
static readonly CLEAR: number = 1;
|
|
||||||
static readonly MASK: number = 2;
|
|
||||||
static readonly IMAGE: number = 4;
|
|
||||||
|
|
||||||
static readonly FILL: number = 1;
|
|
||||||
static readonly STROKE: number = 2;
|
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement) {
|
|
||||||
this.canvas = canvas;
|
|
||||||
this.ctx = canvas.getContext('2d')!;
|
|
||||||
}
|
|
||||||
|
|
||||||
image(layout: Layout | CanvasImageSource | Path2D, type: number): this;
|
|
||||||
image(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D,
|
|
||||||
type: number,
|
|
||||||
x: number,
|
|
||||||
y: number
|
|
||||||
): this;
|
|
||||||
image(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D,
|
|
||||||
type: number,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
w: number,
|
|
||||||
h: number
|
|
||||||
): this;
|
|
||||||
image(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D,
|
|
||||||
type: number,
|
|
||||||
sx: number,
|
|
||||||
sy: number,
|
|
||||||
sw: number,
|
|
||||||
sh: number,
|
|
||||||
dx: number,
|
|
||||||
dy: number,
|
|
||||||
dw: number,
|
|
||||||
dh: number
|
|
||||||
): this;
|
|
||||||
image(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D,
|
|
||||||
type: number,
|
|
||||||
sx: number = 0,
|
|
||||||
sy: number = 0,
|
|
||||||
sw?: number,
|
|
||||||
sh?: number,
|
|
||||||
dx?: number,
|
|
||||||
dy?: number,
|
|
||||||
dw?: number,
|
|
||||||
dh?: number
|
|
||||||
) {
|
|
||||||
const img = layout instanceof Layout ? layout.canvas : layout;
|
|
||||||
const fill = () => {
|
|
||||||
if (img instanceof Path2D) {
|
|
||||||
this.ctx.fill(img);
|
|
||||||
} else {
|
|
||||||
if (!has(sw)) {
|
|
||||||
this.ctx.drawImage(img, sx, sy);
|
|
||||||
} else if (!has(dx)) {
|
|
||||||
this.ctx.drawImage(img, sx, sy, sw, sh!);
|
|
||||||
} else {
|
|
||||||
this.ctx.drawImage(img, sx, sy, sw, sh!, dx, dy!, dw!, dh!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (type & Layout.IMAGE) {
|
|
||||||
// 绘制图片
|
|
||||||
fill();
|
|
||||||
}
|
|
||||||
if (type & Layout.CLEAR) {
|
|
||||||
// 按照图片清除一个区域
|
|
||||||
this.ctx.save();
|
|
||||||
this.ctx.globalCompositeOperation = 'destination-out';
|
|
||||||
fill();
|
|
||||||
this.ctx.restore();
|
|
||||||
}
|
|
||||||
if (type & Layout.MASK) {
|
|
||||||
// 蒙版,只显示蒙版内的东西
|
|
||||||
this.ctx.save();
|
|
||||||
this.ctx.globalCompositeOperation = 'destination-in';
|
|
||||||
fill();
|
|
||||||
this.ctx.restore();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 擦除一个矩形
|
|
||||||
*/
|
|
||||||
clear(x: number, y: number, w: number, h: number): this {
|
|
||||||
this.ctx.clearRect(x, y, w, h);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制文字
|
|
||||||
* @param str 文字
|
|
||||||
* @param type 绘制类型,FILL表示填充,STROKE表示描边,FILL | STROKE 表示既填充又描边
|
|
||||||
* @param x 横坐标
|
|
||||||
* @param y 纵坐标
|
|
||||||
* @param maxWidth 最大宽度
|
|
||||||
*/
|
|
||||||
text(
|
|
||||||
str: string,
|
|
||||||
type: number,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
maxWidth?: number
|
|
||||||
): this {
|
|
||||||
if (type & Layout.FILL) this.ctx.fillText(str, x, y, maxWidth);
|
|
||||||
if (type & Layout.STROKE) this.ctx.strokeText(str, x, y, maxWidth);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据路径进行绘制
|
|
||||||
* @param path 路径
|
|
||||||
* @param type 绘制类型
|
|
||||||
*/
|
|
||||||
path(path: Path2D, type: number, rule?: CanvasFillRule): this {
|
|
||||||
if (type & Layout.FILL) this.ctx.fill(path, rule);
|
|
||||||
if (type & Layout.STROKE) this.ctx.stroke(path);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存画布状态
|
|
||||||
*/
|
|
||||||
save(): this {
|
|
||||||
this.ctx.save();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 回退画布状态
|
|
||||||
*/
|
|
||||||
restore(): this {
|
|
||||||
this.ctx.restore();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置填充样式
|
|
||||||
* @param style 样式
|
|
||||||
*/
|
|
||||||
fillStyle(style: CanvasStyle): this {
|
|
||||||
this.ctx.fillStyle = style;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置描边样式
|
|
||||||
* @param style 样式
|
|
||||||
*/
|
|
||||||
strokeStyle(style: CanvasStyle): this {
|
|
||||||
this.ctx.strokeStyle = style;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置文本对齐
|
|
||||||
* @param align 文本左右对齐方式
|
|
||||||
*/
|
|
||||||
textAlign(align: CanvasTextAlign): this {
|
|
||||||
this.ctx.textAlign = align;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置文本基线
|
|
||||||
* @param align 文本基线,即文本上下对齐方式
|
|
||||||
*/
|
|
||||||
textBaseline(align: CanvasTextBaseline): this {
|
|
||||||
this.ctx.textBaseline = align;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置滤镜
|
|
||||||
* @param filter 滤镜
|
|
||||||
*/
|
|
||||||
filter(filter: string): this {
|
|
||||||
this.ctx.filter = filter;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置阴影信息
|
|
||||||
* @param shadow 阴影信息
|
|
||||||
*/
|
|
||||||
shadow(shadow: Partial<CanvasShadowStyles>): this {
|
|
||||||
for (const [p, v] of Object.entries(shadow)) {
|
|
||||||
// @ts-ignore
|
|
||||||
this.ctx[p] = v;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置线宽(描边宽度,包括字体描边)
|
|
||||||
* @param width 宽度
|
|
||||||
*/
|
|
||||||
lineWidth(width: number): this {
|
|
||||||
this.ctx.lineWidth = width;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置线尾样式
|
|
||||||
* @param cap 线尾样式
|
|
||||||
*/
|
|
||||||
lineCap(cap: CanvasLineCap): this {
|
|
||||||
this.ctx.lineCap = cap;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置线段连接方式样式
|
|
||||||
* @param join 线段连接方式
|
|
||||||
*/
|
|
||||||
lineJoin(join: CanvasLineJoin): this {
|
|
||||||
this.ctx.lineJoin = join;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置画布的字体
|
|
||||||
* @param font 字体
|
|
||||||
*/
|
|
||||||
font(font: string): this {
|
|
||||||
this.ctx.font = font;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置画布之后绘制的不透明度
|
|
||||||
* @param alpha 不透明度
|
|
||||||
*/
|
|
||||||
alpha(alpha: number): this {
|
|
||||||
this.ctx.globalAlpha = alpha;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置虚线样式
|
|
||||||
* @param dash 虚线样式
|
|
||||||
*/
|
|
||||||
lineDash(dash: number[]): this {
|
|
||||||
this.ctx.setLineDash(dash);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 放缩画布
|
|
||||||
* @param x 横向放缩量
|
|
||||||
* @param y 纵向放缩量
|
|
||||||
*/
|
|
||||||
scale(x: number, y: number): this {
|
|
||||||
this.ctx.scale(x, y);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 旋转画布
|
|
||||||
* @param rad 顺时针旋转的弧度数
|
|
||||||
*/
|
|
||||||
rotate(rad: number): this {
|
|
||||||
this.ctx.rotate(rad);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 平移画布
|
|
||||||
* @param x 水平平移量
|
|
||||||
* @param y 竖直平移量
|
|
||||||
*/
|
|
||||||
translate(x: number, y: number): this {
|
|
||||||
this.ctx.translate(x, y);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重设变换矩阵
|
|
||||||
*/
|
|
||||||
transform(): Layout;
|
|
||||||
/**
|
|
||||||
* 叠加变换矩阵(当前画布的矩阵乘以传入的矩阵)
|
|
||||||
* 矩阵说明:
|
|
||||||
* [a c e]
|
|
||||||
* [b d f]
|
|
||||||
* [0 0 0]
|
|
||||||
* @param a 水平缩放
|
|
||||||
* @param b 垂直倾斜
|
|
||||||
* @param c 水平倾斜
|
|
||||||
* @param d 垂直缩放
|
|
||||||
* @param e 水平移动
|
|
||||||
* @param f 垂直移动
|
|
||||||
*/
|
|
||||||
transform(
|
|
||||||
a: number,
|
|
||||||
b: number,
|
|
||||||
c: number,
|
|
||||||
d: number,
|
|
||||||
e: number,
|
|
||||||
f: number
|
|
||||||
): Layout;
|
|
||||||
transform(
|
|
||||||
a?: number,
|
|
||||||
b?: number,
|
|
||||||
c?: number,
|
|
||||||
d?: number,
|
|
||||||
e?: number,
|
|
||||||
f?: number
|
|
||||||
) {
|
|
||||||
if (!has(a)) this.ctx.resetTransform();
|
|
||||||
else this.ctx.transform(a, b!, c!, d!, e!, f!);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置混合方式,像image的蒙版功能与擦除功能本质上也是通过设置混合方式实现的
|
|
||||||
* @param value 混合方式
|
|
||||||
*/
|
|
||||||
composite(value: GlobalCompositeOperation): this {
|
|
||||||
this.ctx.globalCompositeOperation = value;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除这个布局
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
this.canvas.remove();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,255 +0,0 @@
|
|||||||
import { Layout } from './layout';
|
|
||||||
|
|
||||||
export class LayoutGame extends Layout {
|
|
||||||
/** 画布id */
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
id: string,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
w: number,
|
|
||||||
h: number,
|
|
||||||
z: number
|
|
||||||
) {
|
|
||||||
const ctx = core.createCanvas(id, x, y, w, h, z);
|
|
||||||
super(ctx.canvas);
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
image2(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
|
||||||
type: number
|
|
||||||
): LayoutGame;
|
|
||||||
image2(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
|
||||||
type: number,
|
|
||||||
x: number,
|
|
||||||
y: number
|
|
||||||
): LayoutGame;
|
|
||||||
image2(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
|
||||||
type: number,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
w: number,
|
|
||||||
h: number
|
|
||||||
): LayoutGame;
|
|
||||||
image2(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
|
||||||
type: number,
|
|
||||||
sx: number,
|
|
||||||
sy: number,
|
|
||||||
sw: number,
|
|
||||||
sh: number,
|
|
||||||
dx: number,
|
|
||||||
dy: number,
|
|
||||||
dw: number,
|
|
||||||
dh: number
|
|
||||||
): LayoutGame;
|
|
||||||
image2(
|
|
||||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
|
||||||
type: number,
|
|
||||||
sx?: number,
|
|
||||||
sy?: number,
|
|
||||||
sw?: number,
|
|
||||||
sh?: number,
|
|
||||||
dx?: number,
|
|
||||||
dy?: number,
|
|
||||||
dw?: number,
|
|
||||||
dh?: number
|
|
||||||
): this {
|
|
||||||
const img =
|
|
||||||
typeof layout === 'string'
|
|
||||||
? core.material.images.images[layout]
|
|
||||||
: layout;
|
|
||||||
|
|
||||||
this.image(img, type, sx!, sy!, sw!, sh!, dx!, dy!, dw!, dh!);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制一个图标
|
|
||||||
* @param id 图标id
|
|
||||||
* @param x 横坐标
|
|
||||||
* @param y 纵坐标
|
|
||||||
* @param w 宽度
|
|
||||||
* @param h 高度
|
|
||||||
* @param frame 绘制第几帧
|
|
||||||
*/
|
|
||||||
icon(
|
|
||||||
id: AllIds,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
w?: number,
|
|
||||||
h?: number,
|
|
||||||
frame?: number
|
|
||||||
): this {
|
|
||||||
core.drawIcon(this.ctx, id, x, y, w, h, frame);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制WindowSkin
|
|
||||||
* @param direction 指向箭头的方向
|
|
||||||
*/
|
|
||||||
winskin(
|
|
||||||
background: any,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
w: number,
|
|
||||||
h: number,
|
|
||||||
direction?: 'up' | 'down',
|
|
||||||
px?: number,
|
|
||||||
py?: number
|
|
||||||
): this {
|
|
||||||
core.drawWindowSkin(
|
|
||||||
background,
|
|
||||||
this.ctx,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
direction,
|
|
||||||
px,
|
|
||||||
py
|
|
||||||
);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制一个箭头
|
|
||||||
* @param x1 起始点横坐标
|
|
||||||
* @param y1 起始点纵坐标
|
|
||||||
* @param x2 终止点横坐标
|
|
||||||
* @param y2 终止点纵坐标
|
|
||||||
*/
|
|
||||||
arrow(x1: number, y1: number, x2: number, y2: number): this {
|
|
||||||
core.drawArrow(this.ctx, x1, y1, x2, y2);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制线段
|
|
||||||
*/
|
|
||||||
line(x1: number, y1: number, x2: number, y2: number): this {
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.moveTo(x1, y1);
|
|
||||||
this.ctx.lineTo(x2, y2);
|
|
||||||
this.ctx.stroke();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制圆弧或扇形
|
|
||||||
* @param type 绘制类型
|
|
||||||
* @param start 起始弧度
|
|
||||||
* @param end 终止弧度
|
|
||||||
* @param anticlockwise 是否逆时针
|
|
||||||
*/
|
|
||||||
arc(
|
|
||||||
type: number,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
r: number,
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
anticlockwise: boolean = false
|
|
||||||
): this {
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.arc(x, y, r, start, end, anticlockwise);
|
|
||||||
this.draw(type);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制一个圆
|
|
||||||
* @param type 绘制类型
|
|
||||||
*/
|
|
||||||
circle(type: number, x: number, y: number, r: number): this {
|
|
||||||
return this.arc(type, x, y, r, 0, Math.PI * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制椭圆
|
|
||||||
*/
|
|
||||||
ellipse(
|
|
||||||
type: number,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
a: number,
|
|
||||||
b: number,
|
|
||||||
rotation: number = 0,
|
|
||||||
start: number = 0,
|
|
||||||
end: number = Math.PI * 2,
|
|
||||||
anticlockwise: boolean = false
|
|
||||||
): this {
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.ellipse(x, y, a, b, rotation, start, end, anticlockwise);
|
|
||||||
this.draw(type);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制多边形
|
|
||||||
* @param type 绘制类型
|
|
||||||
* @param nodes 多边形节点
|
|
||||||
* @param close 是否闭合路径
|
|
||||||
*/
|
|
||||||
polygon(type: number, nodes: LocArr[], close: boolean = true): this {
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.moveTo(nodes[0][0], nodes[0][1]);
|
|
||||||
for (let i = 1; i < nodes.length; i++) {
|
|
||||||
this.ctx.lineTo(nodes[i][0], nodes[i][1]);
|
|
||||||
}
|
|
||||||
if (close) this.ctx.closePath();
|
|
||||||
this.draw(type);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制矩形
|
|
||||||
*/
|
|
||||||
rect(type: number, x: number, y: number, w: number, h: number): this {
|
|
||||||
if (type & Layout.FILL) this.ctx.fillRect(x, y, w, h);
|
|
||||||
if (type & Layout.STROKE) this.ctx.strokeRect(x, y, w, h);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制圆角矩形
|
|
||||||
*/
|
|
||||||
roundRect(
|
|
||||||
type: number,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
w: number,
|
|
||||||
h: number,
|
|
||||||
r: number
|
|
||||||
) {
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.moveTo(x + r, y);
|
|
||||||
this.ctx.lineTo(x + w - r, y);
|
|
||||||
this.ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
||||||
this.ctx.lineTo(x + w, y + h - r);
|
|
||||||
this.ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
|
||||||
this.ctx.lineTo(x + r, y + h);
|
|
||||||
this.ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
|
||||||
this.ctx.lineTo(x, y + r);
|
|
||||||
this.ctx.quadraticCurveTo(x, y, x + r, y);
|
|
||||||
this.ctx.closePath();
|
|
||||||
this.draw(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除这个布局
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
core.deleteCanvas(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private draw(type: number) {
|
|
||||||
if (type & Layout.FILL) this.ctx.fill();
|
|
||||||
if (type & Layout.STROKE) this.ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,243 +0,0 @@
|
|||||||
import { Polygon } from './polygon';
|
|
||||||
import {
|
|
||||||
Light,
|
|
||||||
getAllLights,
|
|
||||||
initShadowCanvas,
|
|
||||||
refreshLight,
|
|
||||||
removeAllLights,
|
|
||||||
setBackground,
|
|
||||||
setBlur,
|
|
||||||
setLightList,
|
|
||||||
setShadowNodes
|
|
||||||
} from './shadow';
|
|
||||||
import { pColor } from '../utils';
|
|
||||||
import { setCanvasFilterByFloorId } from '../fx/gameCanvas';
|
|
||||||
|
|
||||||
export function init() {
|
|
||||||
// 勇士身上的光源
|
|
||||||
// Mota.rewrite(core.control, 'drawHero', 'add', () => {
|
|
||||||
// if (core.getFlag('__heroOpacity__') !== 0) {
|
|
||||||
// getAllLights().forEach(v => {
|
|
||||||
// if (!v.followHero) return;
|
|
||||||
// v._offset ??= { x: v.x, y: v.y };
|
|
||||||
// v.x = core.status.heroCenter.px + v._offset.x;
|
|
||||||
// v.y = core.status.heroCenter.py + v._offset.y;
|
|
||||||
// refreshLight();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// // 更新地形数据
|
|
||||||
// Mota.rewrite(core.maps, 'removeBlock', 'add', success => {
|
|
||||||
// if (success && main.replayChecking) updateShadow(true);
|
|
||||||
// return success;
|
|
||||||
// });
|
|
||||||
// Mota.rewrite(core.maps, 'setBlock', 'add', () => {
|
|
||||||
// if (main.replayChecking) updateShadow(true);
|
|
||||||
// });
|
|
||||||
// Mota.rewrite(core.events, 'changingFloor', 'add', (_, floorId) => {
|
|
||||||
// if (!main.replayChecking) {
|
|
||||||
// updateShadow();
|
|
||||||
// setCanvasFilterByFloorId(floorId);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// // 初始化画布信息
|
|
||||||
// Mota.rewrite(core.ui, 'deleteAllCanvas', 'add', () => {
|
|
||||||
// if (main.mode === 'play' && !main.replayChecking) initShadowCanvas();
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
const shadowInfo: Partial<Record<FloorIds, Light[]>> = {
|
|
||||||
MT50: [
|
|
||||||
{
|
|
||||||
id: 'mt50_1',
|
|
||||||
x: 144,
|
|
||||||
y: 144,
|
|
||||||
decay: 20,
|
|
||||||
r: 150,
|
|
||||||
color: pColor('#e953'),
|
|
||||||
noShelter: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mt50_2',
|
|
||||||
x: 336,
|
|
||||||
y: 144,
|
|
||||||
decay: 20,
|
|
||||||
r: 150,
|
|
||||||
color: pColor('#e953'),
|
|
||||||
noShelter: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mt50_2',
|
|
||||||
x: 336,
|
|
||||||
y: 336,
|
|
||||||
decay: 20,
|
|
||||||
r: 150,
|
|
||||||
color: pColor('#e953'),
|
|
||||||
noShelter: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mt50_2',
|
|
||||||
x: 144,
|
|
||||||
y: 336,
|
|
||||||
decay: 20,
|
|
||||||
r: 150,
|
|
||||||
color: pColor('#e953'),
|
|
||||||
noShelter: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
MT52: [
|
|
||||||
{
|
|
||||||
id: 'mt51_1',
|
|
||||||
x: 13 * 32 + 16,
|
|
||||||
y: 6 * 32 + 16,
|
|
||||||
decay: 20,
|
|
||||||
r: 250,
|
|
||||||
color: pColor('#e953')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mt51_2',
|
|
||||||
x: 1 * 32 + 16,
|
|
||||||
y: 13 * 32 + 16,
|
|
||||||
decay: 20,
|
|
||||||
r: 350,
|
|
||||||
color: pColor('#e953')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
const backgroundInfo: Partial<Record<FloorIds, Color>> = {
|
|
||||||
MT50: pColor('#0006'),
|
|
||||||
MT51: pColor('#0004'),
|
|
||||||
MT52: pColor('#0004')
|
|
||||||
};
|
|
||||||
const blurInfo: Partial<Record<FloorIds, number>> = {};
|
|
||||||
const immersionInfo: Partial<Record<FloorIds, number>> = {};
|
|
||||||
const shadowCache: Partial<Record<FloorIds, Polygon[]>> = {};
|
|
||||||
|
|
||||||
let calMapShadow = true;
|
|
||||||
|
|
||||||
Mota.require('var', 'loading').once('coreInit', () => {
|
|
||||||
core.floorIds.slice(61).forEach(v => {
|
|
||||||
shadowInfo[v] ??= [];
|
|
||||||
shadowInfo[v]?.push({
|
|
||||||
id: `${v}_hero`,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
decay: 50,
|
|
||||||
r: 300,
|
|
||||||
color: 'transparent',
|
|
||||||
followHero: true
|
|
||||||
});
|
|
||||||
core.floors[v].map.forEach((arr, y) => {
|
|
||||||
arr.forEach((num, x) => {
|
|
||||||
if (num === 103) {
|
|
||||||
shadowInfo[v]?.push({
|
|
||||||
id: `${v}_${x}_${y}`,
|
|
||||||
x: x * 32 + 16,
|
|
||||||
y: y * 32 + 16,
|
|
||||||
decay: 20,
|
|
||||||
r: 350,
|
|
||||||
color: pColor('#e953')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export function updateShadow(nocache: boolean = false) {
|
|
||||||
// todo: 需要优化,优化成bfs
|
|
||||||
const floor = core.status.floorId;
|
|
||||||
if (!shadowInfo[floor] || !backgroundInfo[floor]) {
|
|
||||||
removeAllLights();
|
|
||||||
setShadowNodes([]);
|
|
||||||
setBackground('transparent');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const f = core.status.thisMap;
|
|
||||||
const w = f.width;
|
|
||||||
const h = f.height;
|
|
||||||
const nodes: Polygon[] = [];
|
|
||||||
if (calMapShadow) {
|
|
||||||
if (shadowCache[floor] && !nocache) {
|
|
||||||
setShadowNodes(shadowCache[floor]!);
|
|
||||||
} else {
|
|
||||||
core.extractBlocks();
|
|
||||||
const blocks = core.getMapBlocksObj();
|
|
||||||
core.status.maps[floor].blocks.forEach(v => {
|
|
||||||
if (
|
|
||||||
!['terrains', 'autotile', 'tileset', 'animates'].includes(
|
|
||||||
v.event.cls
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v.event.noPass) {
|
|
||||||
const immerse = immersionInfo[floor] ?? 4;
|
|
||||||
const x = v.x;
|
|
||||||
const y = v.y;
|
|
||||||
let left = x * 32 + immerse;
|
|
||||||
let top = y * 32 + immerse;
|
|
||||||
let right = left + 32 - immerse * 2;
|
|
||||||
let bottom = top + 32 - immerse * 2;
|
|
||||||
const l: LocString = `${x - 1},${y}`;
|
|
||||||
const r: LocString = `${x + 1},${y}`;
|
|
||||||
const t: LocString = `${x},${y - 1}`;
|
|
||||||
const b: LocString = `${x},${y + 1}`;
|
|
||||||
|
|
||||||
if (x === 0) left -= immerse * 2;
|
|
||||||
if (x + 1 === w) right += immerse * 2;
|
|
||||||
if (y === 0) top -= immerse * 2;
|
|
||||||
if (y + 1 === h) bottom += immerse * 2;
|
|
||||||
|
|
||||||
if (blocks[l] && blocks[l].event.noPass) {
|
|
||||||
left -= immerse;
|
|
||||||
}
|
|
||||||
if (blocks[r] && blocks[r].event.noPass) {
|
|
||||||
right += immerse;
|
|
||||||
}
|
|
||||||
if (blocks[t] && blocks[t].event.noPass) {
|
|
||||||
top -= immerse;
|
|
||||||
}
|
|
||||||
if (blocks[b] && blocks[b].event.noPass) {
|
|
||||||
bottom += immerse;
|
|
||||||
}
|
|
||||||
nodes.push(
|
|
||||||
new Polygon([
|
|
||||||
[left, top],
|
|
||||||
[right, top],
|
|
||||||
[right, bottom],
|
|
||||||
[left, bottom]
|
|
||||||
])
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
shadowCache[floor] = nodes;
|
|
||||||
setShadowNodes(nodes);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setShadowNodes([]);
|
|
||||||
setBlur(0);
|
|
||||||
}
|
|
||||||
setLightList(shadowInfo[floor]!);
|
|
||||||
setBackground(backgroundInfo[floor]!);
|
|
||||||
setBlur(blurInfo[floor] ?? 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除某一层的墙壁缓存
|
|
||||||
* @param floorId 楼层id
|
|
||||||
*/
|
|
||||||
export function clearShadowCache(floorId: FloorIds) {
|
|
||||||
delete shadowCache[floorId];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置是否不计算墙壁遮挡,对所有灯光有效
|
|
||||||
* @param n 目标值
|
|
||||||
*/
|
|
||||||
export function setCalShadow(n: boolean) {
|
|
||||||
calMapShadow = n;
|
|
||||||
updateShadow();
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
export class Polygon {
|
|
||||||
/**
|
|
||||||
* 多边形的节点
|
|
||||||
*/
|
|
||||||
nodes: LocArr[];
|
|
||||||
|
|
||||||
private cache: Record<string, LocArr[][]> = {};
|
|
||||||
|
|
||||||
static from(...polygons: LocArr[][]) {
|
|
||||||
return polygons.map(v => new Polygon(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(nodes: LocArr[]) {
|
|
||||||
if (nodes.length < 3) {
|
|
||||||
throw new Error(`Nodes number delivered is less than 3!`);
|
|
||||||
}
|
|
||||||
this.nodes = nodes.map(v => [v[0] + 32, v[1] + 32]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取一个点光源下的阴影
|
|
||||||
*/
|
|
||||||
shadowArea(x: number, y: number, r: number): LocArr[][] {
|
|
||||||
const id = `${x},${y}`;
|
|
||||||
if (this.cache[id]) return this.cache[id];
|
|
||||||
const res: LocArr[][] = [];
|
|
||||||
const w = (core._PX_ ?? core.__PIXELS__) + 64;
|
|
||||||
const h = (core._PY_ ?? core.__PIXELS__) + 64;
|
|
||||||
|
|
||||||
const aspect = h / w;
|
|
||||||
|
|
||||||
const intersect = (nx: number, ny: number): LocArr => {
|
|
||||||
const k = (ny - y) / (nx - x);
|
|
||||||
if (k > aspect || k < -aspect) {
|
|
||||||
if (ny < y) {
|
|
||||||
const ix = x + y / k;
|
|
||||||
return [2 * x - ix, 0];
|
|
||||||
} else {
|
|
||||||
const ix = x + (h - y) / k;
|
|
||||||
return [ix, h];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (nx < x) {
|
|
||||||
const iy = y + k * x;
|
|
||||||
return [0, 2 * y - iy];
|
|
||||||
} else {
|
|
||||||
const iy = y + k * (w - x);
|
|
||||||
return [w, iy];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const l = this.nodes.length;
|
|
||||||
let now = intersect(...this.nodes[0]);
|
|
||||||
for (let i = 0; i < l; i++) {
|
|
||||||
const next = (i + 1) % l;
|
|
||||||
const nextInter = intersect(...this.nodes[next]);
|
|
||||||
const start = [this.nodes[i], now];
|
|
||||||
const end = [nextInter, this.nodes[next]];
|
|
||||||
let path: LocArr[];
|
|
||||||
if (
|
|
||||||
(now[0] === 0 && nextInter[1] === 0) ||
|
|
||||||
(now[1] === 0 && nextInter[0] === 0)
|
|
||||||
) {
|
|
||||||
path = [...start, [0, 0], ...end];
|
|
||||||
} else if (
|
|
||||||
(now[0] === 0 && nextInter[1] === h) ||
|
|
||||||
(now[1] === h && nextInter[0] === 0)
|
|
||||||
) {
|
|
||||||
path = [...start, [0, h], ...end];
|
|
||||||
} else if (
|
|
||||||
(now[0] === w && nextInter[1] === 0) ||
|
|
||||||
(now[1] === 0 && nextInter[0] === w)
|
|
||||||
) {
|
|
||||||
path = [...start, [w, 0], ...end];
|
|
||||||
} else if (
|
|
||||||
(now[0] === w && nextInter[1] === h) ||
|
|
||||||
(now[1] === h && nextInter[0] === w)
|
|
||||||
) {
|
|
||||||
path = [...start, [w, h], ...end];
|
|
||||||
} else {
|
|
||||||
path = [...start, ...end];
|
|
||||||
}
|
|
||||||
res.push(path);
|
|
||||||
now = nextInter;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cache[id] = res;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,468 +0,0 @@
|
|||||||
import {
|
|
||||||
Animation,
|
|
||||||
linear,
|
|
||||||
PathFn,
|
|
||||||
TimingFn,
|
|
||||||
Transition
|
|
||||||
} from 'mutate-animate';
|
|
||||||
import { has } from '../utils';
|
|
||||||
import { Polygon } from './polygon';
|
|
||||||
|
|
||||||
interface TransitionInfo {
|
|
||||||
time: number;
|
|
||||||
mode: TimingFn;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Light {
|
|
||||||
id: string;
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
r: number;
|
|
||||||
/** 衰减开始半径 */
|
|
||||||
decay: number;
|
|
||||||
/** 颜色,每个值的范围0.0~1.0 */
|
|
||||||
color: Color;
|
|
||||||
/** 是否可以被物体遮挡 */
|
|
||||||
noShelter?: boolean;
|
|
||||||
/** 是否跟随勇士 */
|
|
||||||
followHero?: boolean;
|
|
||||||
/** 正在动画的属性 */
|
|
||||||
_animating?: Record<string, boolean>;
|
|
||||||
/** 执行渐变的属性 */
|
|
||||||
_transition?: Record<string, TransitionInfo>;
|
|
||||||
/** 表示是否是代理,只有设置渐变后才会变为true */
|
|
||||||
_isProxy?: boolean;
|
|
||||||
/** 跟随勇士的时候的偏移量 */
|
|
||||||
_offset?: Loc;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function init() {
|
|
||||||
core.registerAnimationFrame('shadow', true, () => {
|
|
||||||
if (!needRefresh) return;
|
|
||||||
drawShadow();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let canvas: HTMLCanvasElement;
|
|
||||||
let ctx: CanvasRenderingContext2D;
|
|
||||||
let lights: Light[] = [];
|
|
||||||
let needRefresh = false;
|
|
||||||
let shadowNodes: Polygon[] = [];
|
|
||||||
let background: Color;
|
|
||||||
let blur = 3;
|
|
||||||
const temp1 = document.createElement('canvas');
|
|
||||||
const temp2 = document.createElement('canvas');
|
|
||||||
const temp3 = document.createElement('canvas');
|
|
||||||
const ct1 = temp1.getContext('2d')!;
|
|
||||||
const ct2 = temp2.getContext('2d')!;
|
|
||||||
const ct3 = temp3.getContext('2d')!;
|
|
||||||
|
|
||||||
const animationList: Record<string, Animation> = {};
|
|
||||||
const transitionList: Record<string, Transition> = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化阴影画布
|
|
||||||
*/
|
|
||||||
export function initShadowCanvas() {
|
|
||||||
const w = core._PX_ ?? core.__PIXELS__;
|
|
||||||
const h = core._PY_ ?? core.__PIXELS__;
|
|
||||||
ctx = core.createCanvas('shadow', -32, -32, w + 64, h + 64, 55);
|
|
||||||
canvas = ctx.canvas;
|
|
||||||
|
|
||||||
const s = core.domStyle.scale * devicePixelRatio;
|
|
||||||
temp1.width = (w + 64) * s;
|
|
||||||
temp1.height = (h + 64) * s;
|
|
||||||
temp2.width = (w + 64) * s;
|
|
||||||
temp2.height = (h + 64) * s;
|
|
||||||
temp3.width = (w + 64) * s;
|
|
||||||
temp3.height = (h + 64) * s;
|
|
||||||
ct1.scale(s, s);
|
|
||||||
ct2.scale(s, s);
|
|
||||||
ct3.scale(s, s);
|
|
||||||
canvas.style.filter = `blur(${blur}px)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加一个光源
|
|
||||||
* @param info 光源信息
|
|
||||||
*/
|
|
||||||
export function addLight(info: Light) {
|
|
||||||
lights.push(info);
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除一个光源
|
|
||||||
* @param id 光源id
|
|
||||||
*/
|
|
||||||
export function removeLight(id: string) {
|
|
||||||
const index = lights.findIndex(v => v.id === id);
|
|
||||||
if (index === -1) {
|
|
||||||
throw new ReferenceError(`You are going to remove nonexistent light!`);
|
|
||||||
}
|
|
||||||
lights.splice(index, 1);
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置一个光源的信息
|
|
||||||
* @param id 光源id
|
|
||||||
* @param info 光源信息
|
|
||||||
*/
|
|
||||||
export function setLight(id: string, info: Partial<Light>) {
|
|
||||||
if (has(info.id)) delete info.id;
|
|
||||||
const light = lights.find(v => v.id === id);
|
|
||||||
if (!light) {
|
|
||||||
throw new ReferenceError(`You are going to set nonexistent light!`);
|
|
||||||
}
|
|
||||||
for (const [p, v] of Object.entries(info)) {
|
|
||||||
light[p as SelectKey<Light, number>] = v as number;
|
|
||||||
}
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当前的光源列表
|
|
||||||
* @param list 光源列表
|
|
||||||
*/
|
|
||||||
export function setLightList(list: Light[]) {
|
|
||||||
lights = list;
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 去除所有的光源
|
|
||||||
*/
|
|
||||||
export function removeAllLights() {
|
|
||||||
lights = [];
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取一个灯光
|
|
||||||
* @param id 灯光id
|
|
||||||
*/
|
|
||||||
export function getLight(id: string) {
|
|
||||||
return lights.find(v => v.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有灯光
|
|
||||||
*/
|
|
||||||
export function getAllLights() {
|
|
||||||
return lights;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置背景色
|
|
||||||
* @param color 背景色
|
|
||||||
*/
|
|
||||||
export function setBackground(color: Color) {
|
|
||||||
background = color;
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新灯光信息并重绘
|
|
||||||
*/
|
|
||||||
export function refreshLight() {
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 动画改变一个属性的值
|
|
||||||
* @param id 灯光id
|
|
||||||
* @param key 动画属性,x,y,r,decay,颜色请使用animateLightColor(下个版本会加)
|
|
||||||
* @param n 目标值
|
|
||||||
* @param time 动画时间
|
|
||||||
* @param mode 动画方式,渐变函数,高级动画提供了大量内置的渐变函数
|
|
||||||
* @param relative 相对方式,是绝对还是相对
|
|
||||||
*/
|
|
||||||
export function animateLight<K extends Exclude<keyof Light, 'id'>>(
|
|
||||||
id: string,
|
|
||||||
key: K,
|
|
||||||
n: Light[K],
|
|
||||||
time: number = 1000,
|
|
||||||
mode: TimingFn = linear(),
|
|
||||||
relative: boolean = false
|
|
||||||
) {
|
|
||||||
const light = getLight(id);
|
|
||||||
if (!has(light)) {
|
|
||||||
throw new ReferenceError(`You are going to animate nonexistent light`);
|
|
||||||
}
|
|
||||||
if (typeof n !== 'number') {
|
|
||||||
light[key] = n;
|
|
||||||
}
|
|
||||||
const ani = animationList[id] ?? (animationList[id] = new Animation());
|
|
||||||
if (typeof ani.value[key] !== 'number') {
|
|
||||||
ani.register(key, light[key] as number);
|
|
||||||
} else {
|
|
||||||
ani.time(0)
|
|
||||||
.mode(linear())
|
|
||||||
.absolute()
|
|
||||||
.apply(key, light[key] as number);
|
|
||||||
}
|
|
||||||
ani.time(time)
|
|
||||||
.mode(mode)
|
|
||||||
[relative ? 'relative' : 'absolute']()
|
|
||||||
.apply(key, n as number);
|
|
||||||
const start = Date.now();
|
|
||||||
const fn = () => {
|
|
||||||
if (Date.now() - start > time + 50) {
|
|
||||||
ani.ticker.remove(fn);
|
|
||||||
light._animating![key] = false;
|
|
||||||
}
|
|
||||||
needRefresh = true;
|
|
||||||
light[key as SelectKey<Light, number>] = ani.value[key];
|
|
||||||
};
|
|
||||||
ani.ticker.add(fn);
|
|
||||||
light._animating ??= {};
|
|
||||||
light._animating[key] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 把一个属性设置为渐变模式
|
|
||||||
* @param id 灯光id
|
|
||||||
* @param key 渐变的属性
|
|
||||||
* @param time 渐变时长
|
|
||||||
* @param mode 渐变方式,渐变函数,高级动画提供了大量内置的渐变函数
|
|
||||||
*/
|
|
||||||
export function transitionLight<K extends Exclude<keyof Light, 'id'>>(
|
|
||||||
id: string,
|
|
||||||
key: K,
|
|
||||||
time: number = 1000,
|
|
||||||
mode: TimingFn = linear()
|
|
||||||
) {
|
|
||||||
const index = lights.findIndex(v => v.id === id);
|
|
||||||
if (index === -1) {
|
|
||||||
throw new ReferenceError(`You are going to transite nonexistent light`);
|
|
||||||
}
|
|
||||||
const light = lights[index];
|
|
||||||
if (typeof light[key] !== 'number') return;
|
|
||||||
light._transition ??= {};
|
|
||||||
light._transition[key] = { time, mode };
|
|
||||||
const tran = transitionList[id] ?? (transitionList[id] = new Transition());
|
|
||||||
tran.value[key] = light[key] as number;
|
|
||||||
if (!light._isProxy) {
|
|
||||||
const handler: ProxyHandler<Light> = {
|
|
||||||
set(t, p, v) {
|
|
||||||
if (typeof p === 'symbol') return false;
|
|
||||||
const start = Date.now();
|
|
||||||
if (
|
|
||||||
!light._transition![p] ||
|
|
||||||
light._animating?.[key] ||
|
|
||||||
typeof v !== 'number'
|
|
||||||
) {
|
|
||||||
t[p as SelectKey<Light, number>] = v;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
t[p] = light[p];
|
|
||||||
const info = light._transition![p];
|
|
||||||
tran.mode(info.mode).time(info.time);
|
|
||||||
const fn = () => {
|
|
||||||
if (Date.now() - start > info.time + 50) {
|
|
||||||
tran.ticker.remove(fn);
|
|
||||||
}
|
|
||||||
needRefresh = true;
|
|
||||||
t[p as SelectKey<Light, number>] = tran.value[key];
|
|
||||||
};
|
|
||||||
tran.ticker.add(fn);
|
|
||||||
tran.transition(p, v);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
lights[index] = new Proxy(light, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移动一个灯光
|
|
||||||
* @param id 灯光id
|
|
||||||
* @param x 目标横坐标
|
|
||||||
* @param y 目标纵坐标
|
|
||||||
* @param time 移动时间
|
|
||||||
* @param mode 移动方式,渐变函数
|
|
||||||
* @param relative 相对模式,相对还是绝对
|
|
||||||
*/
|
|
||||||
export function moveLight(
|
|
||||||
id: string,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
time: number = 1000,
|
|
||||||
mode: TimingFn = linear(),
|
|
||||||
relative: boolean = false
|
|
||||||
) {
|
|
||||||
animateLight(id, 'x', x, time, mode, relative);
|
|
||||||
animateLight(id, 'y', y, time, mode, relative);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 以一个路径移动光源
|
|
||||||
* @param id 灯光id
|
|
||||||
* @param time 移动时长
|
|
||||||
* @param path 移动路径
|
|
||||||
* @param mode 移动方式,渐变函数,表示移动的完成度
|
|
||||||
* @param relative 相对模式,相对还是绝对
|
|
||||||
*/
|
|
||||||
export function moveLightAs(
|
|
||||||
id: string,
|
|
||||||
time: number,
|
|
||||||
path: PathFn,
|
|
||||||
mode: TimingFn = linear(),
|
|
||||||
relative: boolean = true
|
|
||||||
) {
|
|
||||||
const light = getLight(id);
|
|
||||||
if (!has(light)) {
|
|
||||||
throw new ReferenceError(`You are going to animate nonexistent light`);
|
|
||||||
}
|
|
||||||
const ani = animationList[id] ?? (animationList[id] = new Animation());
|
|
||||||
ani.mode(linear()).time(0).move(light.x, light.y);
|
|
||||||
ani.time(time)
|
|
||||||
.mode(mode)
|
|
||||||
[relative ? 'relative' : 'absolute']()
|
|
||||||
.moveAs(path);
|
|
||||||
const start = Date.now();
|
|
||||||
const fn = () => {
|
|
||||||
if (Date.now() - start > time + 50) {
|
|
||||||
ani.ticker.remove(fn);
|
|
||||||
light._animating!.x = false;
|
|
||||||
light._animating!.y = false;
|
|
||||||
}
|
|
||||||
needRefresh = true;
|
|
||||||
light.x = ani.x;
|
|
||||||
light.y = ani.y;
|
|
||||||
};
|
|
||||||
ani.ticker.add(fn);
|
|
||||||
light._animating ??= {};
|
|
||||||
light._animating.x = true;
|
|
||||||
light._animating.y = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function animateLightColor(
|
|
||||||
id: string,
|
|
||||||
target: Color,
|
|
||||||
time: number = 1000,
|
|
||||||
mode: TimingFn = linear()
|
|
||||||
) {
|
|
||||||
// todo
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据坐标数组设置物体节点
|
|
||||||
* @param nodes 坐标数组
|
|
||||||
*/
|
|
||||||
export function setShadowNodes(nodes: LocArr[][]): void;
|
|
||||||
/**
|
|
||||||
* 根据多边形数组设置物体节点
|
|
||||||
* @param nodes 多边形数组
|
|
||||||
*/
|
|
||||||
export function setShadowNodes(nodes: Polygon[]): void;
|
|
||||||
export function setShadowNodes(nodes: LocArr[][] | Polygon[]) {
|
|
||||||
if (nodes.length === 0) {
|
|
||||||
shadowNodes = [];
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
if (nodes[0] instanceof Polygon) shadowNodes = nodes as Polygon[];
|
|
||||||
else shadowNodes = Polygon.from(...(nodes as LocArr[][]));
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据坐标数组添加物体节点
|
|
||||||
* @param polygons 坐标数组
|
|
||||||
*/
|
|
||||||
export function addPolygon(...polygons: LocArr[][]): void;
|
|
||||||
/**
|
|
||||||
* 根据多边形数组添加物体节点
|
|
||||||
* @param polygons 多边形数组
|
|
||||||
*/
|
|
||||||
export function addPolygon(...polygons: Polygon[]): void;
|
|
||||||
export function addPolygon(...polygons: Polygon[] | LocArr[][]) {
|
|
||||||
if (polygons.length === 0) return;
|
|
||||||
if (polygons[0] instanceof Polygon)
|
|
||||||
shadowNodes.push(...(polygons as Polygon[]));
|
|
||||||
else shadowNodes.push(...Polygon.from(...(polygons as LocArr[][])));
|
|
||||||
needRefresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置光源的虚化程度
|
|
||||||
* @param n 虚化程度
|
|
||||||
*/
|
|
||||||
export function setBlur(n: number) {
|
|
||||||
blur = n;
|
|
||||||
canvas.style.filter = `blur(${n}px)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制阴影
|
|
||||||
*/
|
|
||||||
export function drawShadow() {
|
|
||||||
const w = (core._PX_ ?? core.__PIXELS__) + 64;
|
|
||||||
const h = (core._PY_ ?? core.__PIXELS__) + 64;
|
|
||||||
needRefresh = false;
|
|
||||||
ctx.clearRect(0, 0, w, h);
|
|
||||||
ct1.clearRect(0, 0, w, h);
|
|
||||||
ct2.clearRect(0, 0, w, h);
|
|
||||||
ct3.clearRect(0, 0, w, h);
|
|
||||||
|
|
||||||
const b = core.arrayToRGBA(background);
|
|
||||||
ctx.globalCompositeOperation = 'source-over';
|
|
||||||
ct3.globalCompositeOperation = 'source-over';
|
|
||||||
|
|
||||||
// 绘制阴影,一个光源一个光源地绘制,然后source-out获得光,然后把光叠加,再source-out获得最终阴影
|
|
||||||
for (let i = 0; i < lights.length; i++) {
|
|
||||||
const { x, y, r, decay, color, noShelter } = lights[i];
|
|
||||||
const rx = x + 32;
|
|
||||||
const ry = y + 32;
|
|
||||||
// 绘制阴影
|
|
||||||
ct1.clearRect(0, 0, w, h);
|
|
||||||
ct2.clearRect(0, 0, w, h);
|
|
||||||
if (!noShelter) {
|
|
||||||
for (const polygon of shadowNodes) {
|
|
||||||
const area = polygon.shadowArea(rx, ry, r);
|
|
||||||
area.forEach(v => {
|
|
||||||
ct1.beginPath();
|
|
||||||
ct1.moveTo(v[0][0], v[0][1]);
|
|
||||||
for (let i = 1; i < v.length; i++) {
|
|
||||||
ct1.lineTo(v[i][0], v[i][1]);
|
|
||||||
}
|
|
||||||
ct1.closePath();
|
|
||||||
ct1.fillStyle = '#000';
|
|
||||||
ct1.globalCompositeOperation = 'source-over';
|
|
||||||
ct1.fill();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 存入ct2,用于绘制真实阴影
|
|
||||||
ct2.globalCompositeOperation = 'source-over';
|
|
||||||
ct2.drawImage(temp1, 0, 0, w, h);
|
|
||||||
ct2.globalCompositeOperation = 'source-out';
|
|
||||||
const gra = ct2.createRadialGradient(rx, ry, decay, rx, ry, r);
|
|
||||||
gra.addColorStop(0, core.arrayToRGBA(color));
|
|
||||||
gra.addColorStop(1, 'transparent');
|
|
||||||
ct2.fillStyle = gra;
|
|
||||||
ct2.beginPath();
|
|
||||||
ct2.arc(rx, ry, r, 0, Math.PI * 2);
|
|
||||||
ct2.fill();
|
|
||||||
ctx.drawImage(temp2, 0, 0, w, h);
|
|
||||||
// 再绘制ct1的阴影,然后绘制到ct3叠加
|
|
||||||
ct1.globalCompositeOperation = 'source-out';
|
|
||||||
const gra2 = ct1.createRadialGradient(rx, ry, decay, rx, ry, r);
|
|
||||||
gra2.addColorStop(0, '#fff');
|
|
||||||
gra2.addColorStop(1, '#fff0');
|
|
||||||
ct1.beginPath();
|
|
||||||
ct1.arc(rx, ry, r, 0, Math.PI * 2);
|
|
||||||
ct1.fillStyle = gra2;
|
|
||||||
ct1.fill();
|
|
||||||
// 绘制到ct3上
|
|
||||||
ct3.drawImage(temp1, 0, 0, w, h);
|
|
||||||
}
|
|
||||||
// 绘制真实阴影
|
|
||||||
ct3.globalCompositeOperation = 'source-out';
|
|
||||||
ct3.fillStyle = b;
|
|
||||||
ct3.fillRect(0, 0, w, h);
|
|
||||||
ctx.globalCompositeOperation = 'destination-over';
|
|
||||||
ctx.drawImage(temp3, 0, 0, w, h);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user