import EventEmitter from 'eventemitter3'; import { FloorLayer, ILayerGroupRenderExtends, ILayerRenderExtends, Layer, LayerGroup, LayerMovingRenderable } from './layer'; import { texture } from './cache'; import { sleep } from 'mutate-animate'; import { RenderAdapter } from '@motajs/render-core'; const { hook } = Mota.require('@user/data-base'); hook.on('setBlock', (x, y, floor, block) => { const isNow = floor === core.status.floorId; LayerGroupFloorBinder.activedBinder.forEach(v => { if (floor === v.floor || (isNow && v.bindThisFloor)) { v.setBlock('event', block, x, y); } }); LayerFloorBinder.listenedBinder.forEach(v => { if (v.layer.layer === 'event') { if (v.floor === floor || (isNow && v.bindThisFloor)) { v.setBlock(block, x, y); } } }); }); hook.on('changingFloor', floor => { // 潜在隐患:如果putRenderData改成异步,那么会变成两帧后才能真正刷新并渲染 // 考虑到楼层转换一般不会同时执行很多次,因此这里改为立刻更新 LayerGroupFloorBinder.activedBinder.forEach(v => { if (v.bindThisFloor) v.updateBindData(); v.emit('floorChange', floor); }); LayerFloorBinder.listenedBinder.forEach(v => { if (v.bindThisFloor) v.updateBindData(); }); }); hook.on('setBgFgBlock', (name, number, x, y, floor) => { const isNow = floor === core.status.floorId; LayerGroupFloorBinder.activedBinder.forEach(v => { if (floor === v.floor || (isNow && v.bindThisFloor)) { v.setBlock(name, number, x, y); } }); LayerFloorBinder.listenedBinder.forEach(v => { if (v.layer.layer === name) { if (v.floor === floor || (isNow && v.bindThisFloor)) { v.setBlock(number, x, y); } } }); }); interface LayerGroupBinderEvent { update: [floor: FloorIds]; setBlock: [x: number, y: number, floor: FloorIds, block: AllNumbers]; floorChange: [floor: FloorIds]; } /** * 楼层绑定拓展,用于LayerGroup,将楼层数据传输到渲染系统。 * 添加后,会自动在LayerGroup包含的子Layer上添加LayerFloorBinder拓展,用于后续处理。 * 当移除这个拓展时,其附属的所有子拓展也会一并被移除。 */ export class LayerGroupFloorBinder extends EventEmitter implements ILayerGroupRenderExtends { id: string = 'floor-binder'; bindThisFloor: boolean = true; floor?: FloorIds; group!: LayerGroup; /** 附属的子LayerFloorBinder拓展 */ layerBinders: Set = new Set(); private needUpdate: boolean = false; static activedBinder: Set = new Set(); /** * 绑定楼层为当前楼层,并跟随变化 */ bindThis() { this.floor = void 0; this.bindThisFloor = true; this.layerBinders.forEach(v => v.bindThis()); this.updateBind(); } /** * 绑定楼层为指定楼层 * @param floorId 楼层id */ bindFloor(floorId: FloorIds) { this.bindThisFloor = false; this.floor = floorId; this.layerBinders.forEach(v => v.bindFloor(floorId)); this.updateBind(); } /** * 在下一帧进行绑定数据更新 */ updateBind() { if (this.needUpdate || !this.group) return; this.needUpdate = true; this.group.requestBeforeFrame(() => { this.needUpdate = false; this.updateBindData(); }); } /** * 立刻进行数据绑定更新 */ updateBindData() { this.layerBinders.forEach(v => { v.updateBindData(); }); const floor = this.getFloor(); this.emit('update', floor); } getFloor() { return this.bindThisFloor ? core.status.floorId : this.floor!; } /** * 设置图块 */ setBlock(layer: FloorLayer, block: AllNumbers, x: number, y: number) { const ex = this.group .getLayer(layer) ?.getExtends('floor-binder') as LayerFloorBinder; if (!ex) return; ex.setBlock(block, x, y); const floor = this.bindThisFloor ? core.status.floorId : this.floor!; this.emit('setBlock', x, y, floor, block); } checkLayerExtends(layer: Layer) { const ex = layer.getExtends('floor-binder'); if (!ex) { const extend = new LayerFloorBinder(this); layer.extends(extend); this.layerBinders.add(extend); } else { if (ex instanceof LayerFloorBinder) { ex.setParent(this); this.layerBinders.add(ex); } } } awake(group: LayerGroup) { this.group = group; for (const layer of group.layers.values()) { this.checkLayerExtends(layer); } LayerGroupFloorBinder.activedBinder.add(this); } onLayerAdd(_group: LayerGroup, layer: Layer): void { this.checkLayerExtends(layer); } onDestroy(group: LayerGroup) { LayerGroupFloorBinder.activedBinder.delete(this); group.layers.forEach(v => { v.removeExtends('floor-binder'); }); this.removeAllListeners(); } } /** * 楼层绑定拓展,用于Layer的楼层渲染。 * 注意,如果目标Layer是LayerGroup的子元素,那么会自动检测父元素是否包含LayerGroupFloorBinder拓展, * 如果包含,那么会自动将此拓展附加至父元素的拓展。当父元素的拓展被移除时,此拓展也会一并被移除。 */ export class LayerFloorBinder implements ILayerRenderExtends { id: string = 'floor-binder'; parent?: LayerGroupFloorBinder; layer!: Layer; bindThisFloor: boolean = true; floor?: FloorIds; static listenedBinder: Set = new Set(); private needUpdate: boolean = false; constructor(parent?: LayerGroupFloorBinder) { this.parent = parent; } /** * 绑定楼层为当前楼层,并跟随变化 */ bindThis() { this.floor = void 0; this.bindThisFloor = true; this.updateBind(); } /** * 绑定楼层为指定楼层 * @param floorId 楼层id */ bindFloor(floorId: FloorIds) { this.bindThisFloor = false; this.floor = floorId; this.updateBind(); } getFloor() { return this.bindThisFloor ? core.status.floorId : this.floor!; } /** * 设置这个拓展附属至的父拓展(LayerGroupFloorBinder拓展) * @param parent 父拓展 */ setParent(parent?: LayerGroupFloorBinder) { this.parent = parent; this.checkListen(); } private checkListen() { if (this.parent) LayerFloorBinder.listenedBinder.delete(this); else LayerFloorBinder.listenedBinder.add(this); } /** * 在下一帧进行绑定数据更新 */ updateBind() { if (this.needUpdate) return; this.needUpdate = true; this.layer.requestBeforeFrame(() => { this.needUpdate = false; this.updateBindData(); }); } /** * 设置图块 */ setBlock(block: AllNumbers, x: number, y: number) { this.layer.putRenderData([block], 1, x, y); } /** * 立刻更新绑定数据,而非下一帧 */ updateBindData() { const floor = this.getFloor(); if (!floor) return; core.extractBlocks(floor); const map = core.status.maps[floor]; this.layer.setMapSize(map.width, map.height); const image = core.status.maps[this.getFloor()].images; if (this.layer.layer === 'event') { const m = map.map; this.layer.putRenderData(m.flat(), map.width, 0, 0); } else { const m = core.maps._getBgFgMapArray(this.layer.layer!, floor); this.layer.putRenderData(m.flat(), map.width, 0, 0); } if (this.layer.layer === 'bg') { // 别忘了背景图块 this.layer.setBackground(texture.idNumberMap[map.defaultGround]); } const toDraw = image?.filter(v => v.canvas === this.layer.layer); this.layer.setFloorImage(toDraw ?? []); } awake(layer: Layer) { this.layer = layer; if (!this.parent) { const group = layer.parent; if (group instanceof LayerGroup) { const ex = group.getExtends('floor-binder'); if (ex instanceof LayerGroupFloorBinder) { ex.checkLayerExtends(layer); this.parent = ex; } } } this.checkListen(); } onDestroy(_layer: Layer) { LayerFloorBinder.listenedBinder.delete(this); this.parent?.layerBinders.delete(this); } } interface DoorAnimateRenderable { renderable: LayerMovingRenderable; count: number; perTime: number; } export class LayerDoorAnimate implements ILayerRenderExtends { id: string = 'door-animate'; layer!: Layer; private moving: Set = new Set(); private getRenderable(block: Block): DoorAnimateRenderable | null { const { x, y, id } = block; const renderable = texture.getRenderable(id); if (!renderable) return null; const image = renderable.autotile ? renderable.image[0] : renderable.image; const time = block.event.doorInfo?.time ?? 160; const frame = renderable.render.length; const perTime = time / frame; const data: LayerMovingRenderable = { x, y, zIndex: y, image, autotile: false, animate: 0, frame, bigImage: false, render: renderable.render, alpha: 1 }; return { renderable: data, count: frame, perTime }; } /** * 开门 * @param block 图块信息 */ async openDoor(block: Block) { const renderable = this.getRenderable(block); if (!renderable) return Promise.reject(); const { renderable: data, count: frame, perTime } = renderable; data.animate = 0; this.moving.add(data); this.layer.requestUpdateMoving(); let now = 0; while (now < frame) { await sleep(perTime); data.animate = ++now; this.layer.update(this.layer); } this.moving.delete(data); this.layer.requestUpdateMoving(); return Promise.resolve(); } /** * 关门 * @param block 图块信息 */ async closeDoor(block: Block) { const renderable = this.getRenderable(block); if (!renderable) return Promise.reject(); const { renderable: data, count: frame, perTime } = renderable; data.animate = frame - 1; this.moving.add(data); this.layer.requestUpdateMoving(); let now = 0; while (now >= 0) { await sleep(perTime); data.animate = --now; this.layer.update(this.layer); } this.moving.delete(data); this.layer.requestUpdateMoving(); return Promise.resolve(); } awake(layer: Layer) { this.layer = layer; doorAdapter.add(this); } onMovingUpdate(_layer: Layer, renderable: LayerMovingRenderable[]): void { renderable.push(...this.moving); } onDestroy(_layer: Layer): void { doorAdapter.remove(this); } } const doorAdapter = new RenderAdapter('door-animate'); doorAdapter.receive('openDoor', (item, block: Block) => { return item.openDoor(block); }); doorAdapter.receive('closeDoor', (item, block: Block) => { return item.closeDoor(block); });