diff --git a/index.html b/index.html
index dd3f98e..90046da 100644
--- a/index.html
+++ b/index.html
@@ -142,7 +142,7 @@
-
+
diff --git a/src/core/fx/shadow.ts b/src/core/fx/shadow.ts
index 176bcf1..a072755 100644
--- a/src/core/fx/shadow.ts
+++ b/src/core/fx/shadow.ts
@@ -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());
})
diff --git a/src/core/plugin.ts b/src/core/plugin.ts
index 6da8b90..4e54ee9 100644
--- a/src/core/plugin.ts
+++ b/src/core/plugin.ts
@@ -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);
diff --git a/src/core/render/index.ts b/src/core/render/index.ts
index aa061f6..963a760 100644
--- a/src/core/render/index.ts
+++ b/src/core/render/index.ts
@@ -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', () => {
diff --git a/src/core/render/preset/damage.ts b/src/core/render/preset/damage.ts
index 5a04609..77dd9c2 100644
--- a/src/core/render/preset/damage.ts
+++ b/src/core/render/preset/damage.ts
@@ -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;
}
diff --git a/src/core/render/preset/layer.ts b/src/core/render/preset/layer.ts
index 8908488..3faf837 100644
--- a/src/core/render/preset/layer.ts
+++ b/src/core/render/preset/layer.ts
@@ -83,6 +83,14 @@ export interface ILayerGroupRenderExtends {
export type FloorLayer = 'bg' | 'bg2' | 'event' | 'fg' | 'fg2';
+const layerZIndex: Record = {
+ bg: 10,
+ bg2: 20,
+ event: 30,
+ fg: 40,
+ fg2: 50
+};
+
export class LayerGroup extends Container implements IAnimateFrame {
/** 地图组列表 */
// static list: Set = 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()) {
diff --git a/src/plugin/fx/frag.ts b/src/plugin/fx/frag.ts
index d093d97..788f260 100644
--- a/src/plugin/fx/frag.ts
+++ b/src/plugin/fx/frag.ts
@@ -1,6 +1,8 @@
import { Animation, linear, sleep } from 'mutate-animate';
import { has } from '../utils';
+// todo: 移植到渲染树
+
interface SplittedImage {
canvas: HTMLCanvasElement;
x: number;
diff --git a/src/plugin/fx/gameCanvas.ts b/src/plugin/fx/gameCanvas.ts
index d7d3505..0dcf775 100644
--- a/src/plugin/fx/gameCanvas.ts
+++ b/src/plugin/fx/gameCanvas.ts
@@ -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) {
diff --git a/src/plugin/fx/halo.ts b/src/plugin/fx/halo.ts
new file mode 100644
index 0000000..7ecd1d6
--- /dev/null
+++ b/src/plugin/fx/halo.ts
@@ -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 = {
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/plugin/fx/itemDetail.ts b/src/plugin/fx/itemDetail.ts
index 1d13681..8013c95 100644
--- a/src/plugin/fx/itemDetail.ts
+++ b/src/plugin/fx/itemDetail.ts
@@ -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,
diff --git a/src/plugin/fx/noise.ts b/src/plugin/fx/noise.ts
deleted file mode 100644
index a22c3f4..0000000
--- a/src/plugin/fx/noise.ts
+++ /dev/null
@@ -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;
-}
diff --git a/src/plugin/game/enemy/checkblock.ts b/src/plugin/game/enemy/checkblock.ts
index d3fc6b9..fc00440 100644
--- a/src/plugin/game/enemy/checkblock.ts
+++ b/src/plugin/game/enemy/checkblock.ts
@@ -1,5 +1,4 @@
import { has, ofDir } from '@/plugin/game/utils';
-import { drawHalo } from '../fx/halo';
export function init() {
// 伤害弹出
diff --git a/src/plugin/game/fallback.ts b/src/plugin/game/fallback.ts
index 9666ecf..4f52280 100644
--- a/src/plugin/game/fallback.ts
+++ b/src/plugin/game/fallback.ts
@@ -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 {
diff --git a/src/plugin/game/fx/halo.ts b/src/plugin/game/fx/halo.ts
deleted file mode 100644
index 4e7b71a..0000000
--- a/src/plugin/game/fx/halo.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-const haloColor: Record = {
- 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();
-}
diff --git a/src/plugin/game/fx/heroDetail.ts b/src/plugin/game/fx/heroDetail.ts
deleted file mode 100644
index 2a854c5..0000000
--- a/src/plugin/game/fx/heroDetail.ts
+++ /dev/null
@@ -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 };
diff --git a/src/plugin/game/fx/heroFourFrames.ts b/src/plugin/game/fx/heroFourFrames.ts
deleted file mode 100644
index a1cdf60..0000000
--- a/src/plugin/game/fx/heroFourFrames.ts
+++ /dev/null
@@ -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;
- };
-}
diff --git a/src/plugin/game/fx/itemDetail.ts b/src/plugin/game/fx/itemDetail.ts
index 8ab4cb7..ba3812d 100644
--- a/src/plugin/game/fx/itemDetail.ts
+++ b/src/plugin/game/fx/itemDetail.ts
@@ -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 = {};
- const before = core.status.hero;
- const hero = core.clone(core.status.hero);
- const handler: ProxyHandler = {
- 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 = core.clone(
- item.equip.value ?? {}
- );
- const per = item.equip.percentage ?? {};
- for (const name in per) {
- diff[name + 'per'] =
- per[name as SelectKey].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++;
- }
-}
diff --git a/src/plugin/game/index.ts b/src/plugin/game/index.ts
index 6ce77c3..c5f201b 100644
--- a/src/plugin/game/index.ts
+++ b/src/plugin/game/index.ts
@@ -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);
diff --git a/src/plugin/layout/layout.ts b/src/plugin/layout/layout.ts
deleted file mode 100644
index 262818d..0000000
--- a/src/plugin/layout/layout.ts
+++ /dev/null
@@ -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): 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();
- }
-}
diff --git a/src/plugin/layout/layoutGame.ts b/src/plugin/layout/layoutGame.ts
deleted file mode 100644
index 7e84604..0000000
--- a/src/plugin/layout/layoutGame.ts
+++ /dev/null
@@ -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();
- }
-}
diff --git a/src/plugin/shadow/gameShadow.ts b/src/plugin/shadow/gameShadow.ts
deleted file mode 100644
index d0ec7a7..0000000
--- a/src/plugin/shadow/gameShadow.ts
+++ /dev/null
@@ -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> = {
- 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> = {
- MT50: pColor('#0006'),
- MT51: pColor('#0004'),
- MT52: pColor('#0004')
-};
-const blurInfo: Partial> = {};
-const immersionInfo: Partial> = {};
-const shadowCache: Partial> = {};
-
-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();
-}
diff --git a/src/plugin/shadow/polygon.ts b/src/plugin/shadow/polygon.ts
deleted file mode 100644
index 1a031d2..0000000
--- a/src/plugin/shadow/polygon.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-export class Polygon {
- /**
- * 多边形的节点
- */
- nodes: LocArr[];
-
- private cache: Record = {};
-
- 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;
- }
-}
diff --git a/src/plugin/shadow/shadow.ts b/src/plugin/shadow/shadow.ts
deleted file mode 100644
index a28234e..0000000
--- a/src/plugin/shadow/shadow.ts
+++ /dev/null
@@ -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;
- /** 执行渐变的属性 */
- _transition?: Record;
- /** 表示是否是代理,只有设置渐变后才会变为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 = {};
-const transitionList: Record = {};
-
-/**
- * 初始化阴影画布
- */
-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) {
- 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] = 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>(
- 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] = 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>(
- 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 = {
- 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] = 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] = 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);
-}