mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-04 07:02:58 +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='fg'></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' id='ui'></canvas>
 | 
			
		||||
                <canvas class='gameCanvas' id='data'>此浏览器不支持HTML5</canvas>
 | 
			
		||||
 | 
			
		||||
@ -121,6 +121,7 @@ hook.once('reset', () => {
 | 
			
		||||
    Mota.rewrite(core.control, 'loadData', 'add', () => {
 | 
			
		||||
        if (!main.replayChecking) {
 | 
			
		||||
            Shadow.update(true);
 | 
			
		||||
            LayerShadowExtends.shadowList.forEach(v => v.update());
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    // Mota.require('var', 'hook').on('changingFloor', (floorId) => {
 | 
			
		||||
@ -141,7 +142,7 @@ hook.on('setBlock', () => {
 | 
			
		||||
})
 | 
			
		||||
hook.on('changingFloor', floorId => {        
 | 
			
		||||
    Shadow.clearBuffer();
 | 
			
		||||
    Shadow.update();
 | 
			
		||||
    Shadow.update(true);
 | 
			
		||||
    // setCanvasFilterByFloorId(floorId);
 | 
			
		||||
    LayerShadowExtends.shadowList.forEach(v => v.update());
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -17,8 +17,6 @@
 | 
			
		||||
// import frag from '@/plugin/fx/frag';
 | 
			
		||||
// 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 chase from '@/plugin/chase/chase';
 | 
			
		||||
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 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('chase_r', chase);
 | 
			
		||||
Mota.Plugin.register('completion_r', completion, completion.init);
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import { LayerShadowExtends } from '../fx/shadow';
 | 
			
		||||
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
 | 
			
		||||
import { LayerGroupAnimate } from './preset/animate';
 | 
			
		||||
import { LayerGroupPortal } from '@/plugin/fx/portal';
 | 
			
		||||
import { LayerGroupHalo } from '@/plugin/fx/halo';
 | 
			
		||||
 | 
			
		||||
let main: MotaRenderer;
 | 
			
		||||
 | 
			
		||||
@ -35,16 +36,19 @@ Mota.require('var', 'loading').once('loaded', () => {
 | 
			
		||||
    const filter = new LayerGroupFilter();
 | 
			
		||||
    const animate = new LayerGroupAnimate();
 | 
			
		||||
    const portal = new LayerGroupPortal();
 | 
			
		||||
    const halo = new LayerGroupHalo();
 | 
			
		||||
    layer.extends(damage);
 | 
			
		||||
    layer.extends(detail);
 | 
			
		||||
    layer.extends(filter);
 | 
			
		||||
    layer.extends(portal);
 | 
			
		||||
    layer.extends(halo);
 | 
			
		||||
    layer.getLayer('event')?.extends(hero);
 | 
			
		||||
    layer.getLayer('event')?.extends(door);
 | 
			
		||||
    layer.getLayer('event')?.extends(shadow);
 | 
			
		||||
    layer.extends(animate);
 | 
			
		||||
 | 
			
		||||
    render.appendChild(layer);
 | 
			
		||||
    // console.log(render);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Mota.require('var', 'hook').on('reset', () => {
 | 
			
		||||
 | 
			
		||||
@ -56,6 +56,7 @@ export class FloorDamageExtends
 | 
			
		||||
    private create() {
 | 
			
		||||
        if (this.sprite) return;
 | 
			
		||||
        const sprite = new Damage();
 | 
			
		||||
        sprite.setZIndex(80);
 | 
			
		||||
        this.group.appendChild(sprite);
 | 
			
		||||
        this.sprite = sprite;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -83,6 +83,14 @@ export interface ILayerGroupRenderExtends {
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
    /** 地图组列表 */
 | 
			
		||||
    // static list: Set<LayerGroup> = new Set();
 | 
			
		||||
@ -190,6 +198,7 @@ export class LayerGroup extends Container implements IAnimateFrame {
 | 
			
		||||
        const l = new Layer();
 | 
			
		||||
        l.layer = layer;
 | 
			
		||||
        if (l.layer) this.layers.set(l.layer, l);
 | 
			
		||||
        l.setZIndex(layerZIndex[layer]);
 | 
			
		||||
        this.appendChild(l);
 | 
			
		||||
 | 
			
		||||
        for (const ex of this.extend.values()) {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
import { Animation, linear, sleep } from 'mutate-animate';
 | 
			
		||||
import { has } from '../utils';
 | 
			
		||||
 | 
			
		||||
// todo: 移植到渲染树
 | 
			
		||||
 | 
			
		||||
interface SplittedImage {
 | 
			
		||||
    canvas: HTMLCanvasElement;
 | 
			
		||||
    x: number;
 | 
			
		||||
 | 
			
		||||
@ -5,10 +5,6 @@ import {
 | 
			
		||||
    LayerGroup
 | 
			
		||||
} from '@/core/render/preset/layer';
 | 
			
		||||
 | 
			
		||||
export default function init() {
 | 
			
		||||
    return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const filterMap: [FloorIds[], string][] = [];
 | 
			
		||||
 | 
			
		||||
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)) {
 | 
			
		||||
                if (!value) continue;
 | 
			
		||||
                const color = FloorItemDetail.detailColor[key] ?? '#fff';
 | 
			
		||||
                const text = value.toString();
 | 
			
		||||
                const text = Math.floor(value).toString();
 | 
			
		||||
                const renderable: DamageRenderable = {
 | 
			
		||||
                    x: x * this.sprite.cellSize + 2,
 | 
			
		||||
                    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 { drawHalo } from '../fx/halo';
 | 
			
		||||
 | 
			
		||||
export function init() {
 | 
			
		||||
    // 伤害弹出
 | 
			
		||||
 | 
			
		||||
@ -986,11 +986,8 @@ export function init() {
 | 
			
		||||
                core.setHeroLoc('y', ny);
 | 
			
		||||
                core.setHeroLoc('direction', action);
 | 
			
		||||
            }
 | 
			
		||||
            if (!main.replayChecking) {
 | 
			
		||||
                setTimeout(core.replay, 100);
 | 
			
		||||
            } else {
 | 
			
		||||
                core.replay();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setTimeout(core.replay, 100);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        } 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);
 | 
			
		||||
 | 
			
		||||
        getItemDetail(floorId, onMap); // 宝石血瓶详细信息
 | 
			
		||||
        // getItemDetail(floorId, onMap); // 宝石血瓶详细信息
 | 
			
		||||
        // 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'; // 仅开发会用到
 | 
			
		||||
import * as fiveLayer from './fiveLayer';
 | 
			
		||||
import * as heroFourFrames from './fx/heroFourFrames';
 | 
			
		||||
import * as itemDetail from './fx/itemDetail';
 | 
			
		||||
import * as replay from './replay';
 | 
			
		||||
import * as ui from './ui';
 | 
			
		||||
import * as rewrite from './fx/rewrite';
 | 
			
		||||
import * as halo from './fx/halo';
 | 
			
		||||
import * as loopMap from './loopMap';
 | 
			
		||||
import * as removeMap from './removeMap';
 | 
			
		||||
import * as shop from './shop';
 | 
			
		||||
@ -29,10 +27,8 @@ Mota.Plugin.register('chase_g', chase);
 | 
			
		||||
Mota.Plugin.register('skill_g', skill);
 | 
			
		||||
Mota.Plugin.register('towerBoss_g', towerBoss);
 | 
			
		||||
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('itemDetail_g', itemDetail, itemDetail.init);
 | 
			
		||||
Mota.Plugin.register('halo_g', halo);
 | 
			
		||||
// Mota.Plugin.register('study_g', study);
 | 
			
		||||
Mota.Plugin.register('remainEnemy_g', remainEnemy);
 | 
			
		||||
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