diff --git a/index.html b/index.html index dd3f98e..90046da 100644 --- a/index.html +++ b/index.html @@ -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> 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<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()) { 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<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); + } + } + } +} 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<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(); -} 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<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++; - } -} 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<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(); - } -} 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<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(); -} 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<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; - } -} 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<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); -}