mirror of
https://github.com/unanmed/HumanBreak.git
synced 2026-11-11 16:14:30 +08:00
fix: 迁移过程中产生的所有报错
This commit is contained in:
1 parent
0679bbb1a5
commit
4fda246a9a
72 files changed
+225
-189
No files matched your search
@@ -0,0 +1,599 @@
|
||||
import {
|
||||
ERenderItemEvent,
|
||||
RenderItem,
|
||||
MotaOffscreenCanvas2D,
|
||||
Transform,
|
||||
transformCanvas
|
||||
} from '@motajs/render-core';
|
||||
import { logger } from '@motajs/common';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { isNil } from 'lodash-es';
|
||||
import {
|
||||
BlockCacher,
|
||||
CanvasCacheItem,
|
||||
ICanvasCacheItem,
|
||||
calNeedRenderOf,
|
||||
ILayerGroupRenderExtends,
|
||||
Layer,
|
||||
LayerGroup,
|
||||
LayerGroupFloorBinder,
|
||||
tagMap
|
||||
} from '@motajs/render';
|
||||
import { IDamageEnemy, IEnemyCollection, MapDamage } from '@motajs/types';
|
||||
import { UserEnemyInfo } from '@user/data-state';
|
||||
|
||||
const { ensureFloorDamage } = Mota.require('@user/data-state');
|
||||
|
||||
/**
|
||||
* 根据伤害大小获取颜色
|
||||
* @param damage 伤害大小
|
||||
*/
|
||||
export function getDamageColor(damage: number): string {
|
||||
if (typeof damage !== 'number') return '#f00';
|
||||
if (damage === 0) return '#2f2';
|
||||
if (damage < 0) return '#7f7';
|
||||
if (damage < core.status.hero.hp / 3) return '#fff';
|
||||
if (damage < (core.status.hero.hp * 2) / 3) return '#ff4';
|
||||
if (damage < core.status.hero.hp) return '#f93';
|
||||
return '#f22';
|
||||
}
|
||||
|
||||
interface EFloorDamageEvent {
|
||||
update: [floor: FloorIds];
|
||||
}
|
||||
|
||||
export class FloorDamageExtends
|
||||
extends EventEmitter<EFloorDamageEvent>
|
||||
implements ILayerGroupRenderExtends
|
||||
{
|
||||
id: string = 'floor-damage';
|
||||
|
||||
floorBinder!: LayerGroupFloorBinder;
|
||||
group!: LayerGroup;
|
||||
sprite!: Damage;
|
||||
|
||||
/**
|
||||
* 立刻刷新伤害渲染
|
||||
*/
|
||||
update(floor: FloorIds) {
|
||||
if (!this.sprite || !floor) return;
|
||||
const map = core.status.maps[floor];
|
||||
this.sprite.setMapSize(map.width, map.height);
|
||||
ensureFloorDamage(floor);
|
||||
const enemy = core.status.maps[floor].enemy;
|
||||
|
||||
this.sprite.updateCollection(enemy);
|
||||
this.emit('update', floor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建显伤层
|
||||
*/
|
||||
private create() {
|
||||
if (this.sprite) return;
|
||||
const sprite = new Damage();
|
||||
sprite.setZIndex(80);
|
||||
this.group.appendChild(sprite);
|
||||
this.sprite = sprite;
|
||||
}
|
||||
|
||||
private onUpdate = (floor: FloorIds) => {
|
||||
if (!this.floorBinder.bindThisFloor) {
|
||||
ensureFloorDamage(floor);
|
||||
core.status.maps[floor].enemy.calRealAttribute();
|
||||
}
|
||||
this.update(floor);
|
||||
};
|
||||
|
||||
// private onSetBlock = (x: number, y: number, floor: FloorIds) => {
|
||||
// this.sprite.enemy?.once('extract', () => {
|
||||
// if (floor !== this.sprite.enemy?.floorId) return;
|
||||
// this.sprite.updateBlocks();
|
||||
// });
|
||||
// if (!this.floorBinder.bindThisFloor) {
|
||||
// this.sprite.enemy?.extract();
|
||||
// }
|
||||
// };
|
||||
|
||||
/**
|
||||
* 进行楼层更新监听
|
||||
*/
|
||||
private listen() {
|
||||
this.floorBinder.on('update', this.onUpdate);
|
||||
// this.floorBinder.on('setBlock', this.onSetBlock);
|
||||
}
|
||||
|
||||
awake(group: LayerGroup): void {
|
||||
const ex = group.getExtends('floor-binder');
|
||||
if (ex instanceof LayerGroupFloorBinder) {
|
||||
this.floorBinder = ex;
|
||||
this.group = group;
|
||||
this.create();
|
||||
this.listen();
|
||||
} else {
|
||||
logger.warn(17);
|
||||
group.removeExtends('floor-damage');
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(_group: LayerGroup): void {
|
||||
this.floorBinder.off('update', this.onUpdate);
|
||||
// this.floorBinder.off('setBlock', this.onSetBlock);
|
||||
}
|
||||
}
|
||||
|
||||
export interface DamageRenderable {
|
||||
x: number;
|
||||
y: number;
|
||||
align: CanvasTextAlign;
|
||||
baseline: CanvasTextBaseline;
|
||||
text: string;
|
||||
color: CanvasStyle;
|
||||
font?: string;
|
||||
stroke?: CanvasStyle;
|
||||
strokeWidth?: number;
|
||||
}
|
||||
|
||||
export interface EDamageEvent extends ERenderItemEvent {
|
||||
setMapSize: [width: number, height: number];
|
||||
beforeDamageRender: [need: Set<number>, transform: Transform];
|
||||
updateBlocks: [blocks: Set<number>];
|
||||
dirtyUpdate: [block: number];
|
||||
}
|
||||
|
||||
export class Damage extends RenderItem<EDamageEvent> {
|
||||
mapWidth: number = 0;
|
||||
mapHeight: number = 0;
|
||||
|
||||
block: BlockCacher<ICanvasCacheItem>;
|
||||
/** 键表示分块索引,值表示在这个分块上的渲染信息(当然实际渲染位置可以不在这个分块上) */
|
||||
renderable: Map<number, Set<DamageRenderable>> = new Map();
|
||||
|
||||
/** 当前渲染怪物列表 */
|
||||
enemy?: IEnemyCollection;
|
||||
/** 每个分块中包含的怪物集合 */
|
||||
blockData: Map<number, Map<number, IDamageEnemy>> = new Map();
|
||||
/** 单元格大小 */
|
||||
cellSize: number = 32;
|
||||
|
||||
/** 默认伤害字体 */
|
||||
font: string = '300 9px Verdana';
|
||||
/** 默认描边样式,当伤害文字不存在描边属性时会使用此属性 */
|
||||
strokeStyle: CanvasStyle = '#000';
|
||||
/** 默认描边宽度 */
|
||||
strokeWidth: number = 2;
|
||||
|
||||
/** 要懒更新的所有分块 */
|
||||
private dirtyBlocks: Set<number> = new Set();
|
||||
|
||||
constructor() {
|
||||
super('absolute', false, true);
|
||||
|
||||
this.block = new BlockCacher(0, 0, core._WIDTH_, 1);
|
||||
this.type = 'absolute';
|
||||
this.size(core._PX_, core._PY_);
|
||||
this.setHD(true);
|
||||
this.setAntiAliasing(true);
|
||||
}
|
||||
|
||||
protected render(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): void {
|
||||
this.renderDamage(canvas, transform);
|
||||
}
|
||||
|
||||
private onExtract = () => {
|
||||
if (this.enemy) this.updateCollection(this.enemy);
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置地图大小,后面应紧跟更新怪物列表
|
||||
*/
|
||||
setMapSize(width: number, height: number) {
|
||||
this.mapWidth = width;
|
||||
this.mapHeight = height;
|
||||
this.enemy = void 0;
|
||||
this.blockData.clear();
|
||||
this.renderable.clear();
|
||||
this.block.size(width, height);
|
||||
|
||||
// 预留blockData
|
||||
const w = this.block.blockData.width;
|
||||
const h = this.block.blockData.height;
|
||||
const num = w * h;
|
||||
for (let i = 0; i < num; i++) {
|
||||
this.blockData.set(i, new Map());
|
||||
this.renderable.set(i, new Set());
|
||||
this.dirtyBlocks.add(i);
|
||||
}
|
||||
|
||||
this.emit('setMapSize', width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置每个图块的大小
|
||||
*/
|
||||
setCellSize(size: number) {
|
||||
this.cellSize = size;
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新怪物列表。更新后,{@link Damage.enemy} 会丢失原来的怪物列表引用,换为传入的列表引用
|
||||
* @param enemy 怪物列表
|
||||
*/
|
||||
updateCollection(enemy: IEnemyCollection) {
|
||||
if (this.enemy !== enemy) {
|
||||
this.enemy?.off('calculated', this.onExtract);
|
||||
enemy.on('calculated', this.onExtract);
|
||||
}
|
||||
this.enemy = enemy;
|
||||
this.blockData.forEach(v => v.clear());
|
||||
this.renderable.forEach(v => v.clear());
|
||||
this.block.clearAllCache();
|
||||
const w = this.block.blockData.width;
|
||||
const h = this.block.blockData.height;
|
||||
const num = w * h;
|
||||
for (let i = 0; i < num; i++) {
|
||||
this.dirtyBlocks.add(i);
|
||||
}
|
||||
|
||||
enemy.list.forEach(v => {
|
||||
if (isNil(v.x) || isNil(v.y)) return;
|
||||
const index = this.block.getIndexByLoc(v.x, v.y);
|
||||
this.blockData.get(index)?.set(v.y * this.mapWidth + v.x, v);
|
||||
});
|
||||
// this.updateBlocks();
|
||||
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指定矩形区域内的渲染信息
|
||||
* @param x 左上角横坐标
|
||||
* @param y 左上角纵坐标
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
*/
|
||||
updateRenderable(x: number, y: number, width: number, height: number) {
|
||||
this.updateBlocks(this.block.updateElementArea(x, y, width, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指定分块
|
||||
* @param blocks 要更新的分块集合
|
||||
* @param map 是否更新地图伤害
|
||||
*/
|
||||
updateBlocks(blocks?: Set<number>) {
|
||||
if (blocks) {
|
||||
blocks.forEach(v => this.dirtyBlocks.add(v));
|
||||
this.emit('updateBlocks', blocks);
|
||||
} else {
|
||||
this.blockData.forEach((_v, i) => {
|
||||
this.dirtyBlocks.add(i);
|
||||
});
|
||||
this.emit('updateBlocks', new Set(this.blockData.keys()));
|
||||
}
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指定位置的怪物信息
|
||||
*/
|
||||
updateEnemyOn(x: number, y: number) {
|
||||
const enemy = this.enemy?.get(x, y);
|
||||
const block = this.block.getIndexByLoc(x, y);
|
||||
const data = this.blockData.get(block);
|
||||
const index = x + y * this.mapWidth;
|
||||
if (!data) return;
|
||||
if (!enemy) {
|
||||
data.delete(index);
|
||||
} else {
|
||||
data.set(index, enemy);
|
||||
}
|
||||
|
||||
this.update(this);
|
||||
|
||||
// 渲染懒更新,优化性能表现
|
||||
this.dirtyBlocks.add(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单个分块
|
||||
* @param block 更新的分块
|
||||
* @param map 是否更新地图伤害
|
||||
*/
|
||||
private updateBlock(block: number, map: boolean = true) {
|
||||
const data = this.blockData.get(block);
|
||||
if (!data) return;
|
||||
|
||||
this.block.clearCache(block, 1);
|
||||
const renderable = this.renderable.get(block)!;
|
||||
|
||||
renderable.clear();
|
||||
data.forEach(v => this.extract(v, renderable));
|
||||
if (map) this.extractMapDamage(block, renderable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将怪物解析为renderable的伤害
|
||||
* @param enemy 怪物
|
||||
* @param block 怪物所属分块
|
||||
*/
|
||||
private extract(enemy: IDamageEnemy, block: Set<DamageRenderable>) {
|
||||
if (enemy.progress !== 4) return;
|
||||
const x = enemy.x!;
|
||||
const y = enemy.y!;
|
||||
const { damage } = enemy.calDamage();
|
||||
const cri = enemy.calCritical(1)[0]?.atkDelta ?? Infinity;
|
||||
const real = enemy.getRealInfo() as UserEnemyInfo;
|
||||
|
||||
const dam1: DamageRenderable = {
|
||||
align: 'left',
|
||||
baseline: 'alphabetic',
|
||||
text: isFinite(damage) ? core.formatBigNumber(damage, true) : '???',
|
||||
color: getDamageColor(damage),
|
||||
x: x * this.cellSize + 1,
|
||||
y: y * this.cellSize + this.cellSize - 1
|
||||
};
|
||||
const dam2: DamageRenderable = {
|
||||
align: 'left',
|
||||
baseline: 'alphabetic',
|
||||
text: isFinite(cri) ? core.formatBigNumber(cri, true) : '?',
|
||||
color: '#fff',
|
||||
x: x * this.cellSize + 1,
|
||||
y: y * this.cellSize + this.cellSize - 11
|
||||
};
|
||||
block.add(dam1).add(dam2);
|
||||
|
||||
const hasHorn = real.special.has(33);
|
||||
if (real.special.has(8) && real.togetherNum && !hasHorn) {
|
||||
const dam3: DamageRenderable = {
|
||||
align: 'right',
|
||||
baseline: 'top',
|
||||
text: real.togetherNum.toString(),
|
||||
color: '#09FF5B',
|
||||
x: x * this.cellSize + this.cellSize - 1,
|
||||
y: y * this.cellSize + 2,
|
||||
strokeWidth: 3
|
||||
};
|
||||
block.add(dam3);
|
||||
}
|
||||
if (real.special.has(30)) {
|
||||
const dam4: DamageRenderable = {
|
||||
align: 'left',
|
||||
baseline: 'top',
|
||||
text: '乾',
|
||||
color: '#FDCD0B',
|
||||
x: x * this.cellSize + 1,
|
||||
y: y * this.cellSize + 2,
|
||||
strokeWidth: 2,
|
||||
font: '500 10px Verdana'
|
||||
};
|
||||
block.add(dam4);
|
||||
}
|
||||
if (enemy.col && hasHorn) {
|
||||
const dam5: DamageRenderable = {
|
||||
align: 'right',
|
||||
baseline: 'top',
|
||||
text: enemy.col.list.size.toString(),
|
||||
color: '#fff866',
|
||||
x: x * this.cellSize + this.cellSize - 1,
|
||||
y: y * this.cellSize + 2,
|
||||
strokeWidth: 3
|
||||
};
|
||||
block.add(dam5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析指定分块的地图伤害
|
||||
* @param block 分块索引
|
||||
*/
|
||||
private extractMapDamage(block: number, renderable: Set<DamageRenderable>) {
|
||||
if (!this.enemy) return;
|
||||
const damage = this.enemy.mapDamage;
|
||||
const [sx, sy, ex, ey] = this.block.getRectOfIndex(block);
|
||||
for (let x = sx; x < ex; x++) {
|
||||
for (let y = sy; y < ey; y++) {
|
||||
const loc = `${x},${y}`;
|
||||
const dam = damage[loc];
|
||||
if (!dam) continue;
|
||||
this.pushMapDamage(x, y, renderable, dam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析所有地图伤害
|
||||
*/
|
||||
private extractAllMapDamage() {
|
||||
// todo: 测试性能,这样真的会更快吗?或许能更好的优化?或者是根本不需要这个函数?
|
||||
if (!this.enemy) return;
|
||||
for (const [loc, enemy] of Object.entries(this.enemy.mapDamage)) {
|
||||
const [sx, sy] = loc.split(',');
|
||||
const x = Number(sx);
|
||||
const y = Number(sy);
|
||||
const block = this.renderable.get(this.block.getIndexByLoc(x, y))!;
|
||||
this.pushMapDamage(x, y, block, enemy);
|
||||
}
|
||||
}
|
||||
|
||||
private pushMapDamage(
|
||||
x: number,
|
||||
y: number,
|
||||
block: Set<DamageRenderable>,
|
||||
dam: MapDamage
|
||||
) {
|
||||
// todo: 这个应当可以自定义,通过地图伤害注册实现
|
||||
let text = '';
|
||||
let color = '#fa3';
|
||||
let font = '300 9px Verdana';
|
||||
if (dam.damage > 0) {
|
||||
text = core.formatBigNumber(dam.damage, true);
|
||||
} else if (dam.mockery) {
|
||||
dam.mockery.sort((a, b) =>
|
||||
a[0] === b[0] ? a[1] - b[1] : a[0] - b[0]
|
||||
);
|
||||
const [tx, ty] = dam.mockery[0];
|
||||
const dir = x > tx ? '←' : x < tx ? '→' : y > ty ? '↑' : '↓';
|
||||
text = '嘲' + dir;
|
||||
color = '#fd4';
|
||||
font = '500 11px Verdana';
|
||||
} else if (dam.hunt) {
|
||||
text = '猎';
|
||||
color = '#fd4';
|
||||
font = '500 11px Verdana';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const mapDam: DamageRenderable = {
|
||||
align: 'center',
|
||||
baseline: 'middle',
|
||||
text,
|
||||
color,
|
||||
font,
|
||||
x: x * this.cellSize + this.cellSize / 2,
|
||||
y: y * this.cellSize + this.cellSize / 2
|
||||
};
|
||||
block.add(mapDam);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算需要渲染哪些块
|
||||
*/
|
||||
calNeedRender(transform: Transform) {
|
||||
if (this.parent instanceof LayerGroup) {
|
||||
// 如果处于地图组中,每个地图的渲染区域应该是一样的,因此可以缓存优化
|
||||
return this.parent.cacheNeedRender(transform, this.block);
|
||||
} else if (this.parent instanceof Layer) {
|
||||
// 如果是地图的子元素,直接调用Layer的计算函数
|
||||
return this.parent.calNeedRender(transform);
|
||||
} else {
|
||||
return calNeedRenderOf(transform, this.cellSize, this.block);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染伤害层
|
||||
* @param transform 变换矩阵
|
||||
*/
|
||||
renderDamage(canvas: MotaOffscreenCanvas2D, transform: Transform) {
|
||||
// console.time('damage');
|
||||
const { ctx } = canvas;
|
||||
transformCanvas(canvas, transform);
|
||||
|
||||
const render = this.calNeedRender(transform);
|
||||
const block = this.block;
|
||||
const cell = this.cellSize;
|
||||
const size = cell * block.blockSize;
|
||||
|
||||
this.emit('beforeDamageRender', render, transform);
|
||||
|
||||
render.forEach(v => {
|
||||
const [x, y] = block.getBlockXYByIndex(v);
|
||||
const bx = x * block.blockSize;
|
||||
const by = y * block.blockSize;
|
||||
const px = bx * cell;
|
||||
const py = by * cell;
|
||||
|
||||
// todo: 是否真的需要缓存
|
||||
// 检查有没有缓存
|
||||
const cache = block.cache.get(v);
|
||||
if (cache && cache.symbol === cache.canvas.symbol) {
|
||||
ctx.drawImage(cache.canvas.canvas, px, py, size, size);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.dirtyBlocks.has(v)) {
|
||||
this.updateBlock(v, true);
|
||||
}
|
||||
this.emit('dirtyUpdate', v);
|
||||
|
||||
// 否则依次渲染并写入缓存
|
||||
const temp = block.cache.get(v)?.canvas ?? this.requireCanvas();
|
||||
temp.clear();
|
||||
temp.setHD(true);
|
||||
temp.setAntiAliasing(true);
|
||||
temp.withGameScale(true);
|
||||
temp.size(size, size);
|
||||
const { ctx: ct } = temp;
|
||||
|
||||
ct.translate(-px, -py);
|
||||
|
||||
const render = this.renderable.get(v);
|
||||
|
||||
render?.forEach(v => {
|
||||
if (!v) return;
|
||||
ct.fillStyle = v.color;
|
||||
ct.textAlign = v.align;
|
||||
ct.textBaseline = v.baseline;
|
||||
ct.font = v.font ?? this.font;
|
||||
ct.strokeStyle = v.stroke ?? this.strokeStyle;
|
||||
ct.lineWidth = v.strokeWidth ?? this.strokeWidth;
|
||||
ct.strokeText(v.text, v.x, v.y);
|
||||
ct.fillText(v.text, v.x, v.y);
|
||||
});
|
||||
|
||||
ctx.drawImage(temp.canvas, px, py, size, size);
|
||||
block.cache.set(v, new CanvasCacheItem(temp, temp.symbol));
|
||||
});
|
||||
ctx.restore();
|
||||
// console.timeEnd('damage');
|
||||
}
|
||||
|
||||
protected handleProps(
|
||||
key: string,
|
||||
_prevValue: any,
|
||||
nextValue: any
|
||||
): boolean {
|
||||
switch (key) {
|
||||
case 'mapWidth':
|
||||
if (!this.assertType(nextValue, 'number', key)) return false;
|
||||
this.setMapSize(nextValue, this.mapHeight);
|
||||
return true;
|
||||
case 'mapHeight':
|
||||
if (!this.assertType(nextValue, 'number', key)) return false;
|
||||
this.setMapSize(this.mapWidth, nextValue);
|
||||
return true;
|
||||
case 'cellSize':
|
||||
if (!this.assertType(nextValue, 'number', key)) return false;
|
||||
this.setCellSize(nextValue);
|
||||
return true;
|
||||
case 'enemy':
|
||||
if (!this.assertType(nextValue, 'object', key)) return false;
|
||||
this.updateCollection(nextValue);
|
||||
return true;
|
||||
case 'font':
|
||||
if (!this.assertType(nextValue, 'string', key)) return false;
|
||||
this.font = nextValue;
|
||||
this.update();
|
||||
return true;
|
||||
case 'strokeStyle':
|
||||
this.strokeStyle = nextValue;
|
||||
this.update();
|
||||
return true;
|
||||
case 'strokeWidth':
|
||||
if (!this.assertType(nextValue, 'number', key)) return false;
|
||||
this.strokeWidth = nextValue;
|
||||
this.update();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
super.destroy();
|
||||
this.block.destroy();
|
||||
this.enemy?.off('extract', this.onExtract);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册为内部元素
|
||||
tagMap.register<EDamageEvent, Damage>('damage', (_0, _1, _props) => {
|
||||
return new Damage();
|
||||
});
|
||||
|
||||
// const adapter = new RenderAdapter<Damage>('damage');
|
||||
@@ -3,7 +3,7 @@ import { defineComponent } from 'vue';
|
||||
import { UIController } from '@motajs/system-ui';
|
||||
import { mainSceneUI } from './ui/main';
|
||||
import { MAIN_HEIGHT, MAIN_WIDTH } from './shared';
|
||||
import { TextboxStore } from './components';
|
||||
import { hook } from '@user/data-base';
|
||||
|
||||
export function create() {
|
||||
const main = new MotaRenderer();
|
||||
@@ -23,21 +23,17 @@ export function create() {
|
||||
main.hide();
|
||||
createApp(App).mount(main);
|
||||
|
||||
Mota.require('var', 'hook').on('reset', () => {
|
||||
hook.on('reset', () => {
|
||||
main.show();
|
||||
});
|
||||
|
||||
Mota.require('var', 'hook').on('restart', () => {
|
||||
hook.on('restart', () => {
|
||||
main.hide();
|
||||
});
|
||||
|
||||
console.log(main);
|
||||
}
|
||||
|
||||
Mota.register('module', 'MainUI', {
|
||||
TextboxStore
|
||||
});
|
||||
|
||||
export * from './components';
|
||||
export * from './ui';
|
||||
export * from './use';
|
||||
@@ -0,0 +1,267 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { mainSetting } from '@motajs/legacy-ui';
|
||||
import {
|
||||
LayerGroupFloorBinder,
|
||||
ILayerGroupRenderExtends,
|
||||
LayerGroup
|
||||
} from '@motajs/render';
|
||||
import { hook } from '@user/data-base';
|
||||
import { ItemState } from '@user/data-state';
|
||||
import { Damage, DamageRenderable, FloorDamageExtends } from './damage';
|
||||
|
||||
interface ItemDetailData {
|
||||
x: number;
|
||||
y: number;
|
||||
diff: Record<string | symbol, number | undefined>;
|
||||
}
|
||||
|
||||
interface ItemData {
|
||||
id: AllIdsOf<'items'>;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
hook.on('setBlock', (x, y, floorId, block) => {
|
||||
FloorItemDetail.listened.forEach(v => {
|
||||
v.setBlock(block, x, y);
|
||||
});
|
||||
});
|
||||
|
||||
export class FloorItemDetail implements ILayerGroupRenderExtends {
|
||||
id: string = 'item-detail';
|
||||
|
||||
group!: LayerGroup;
|
||||
floorBinder!: LayerGroupFloorBinder;
|
||||
damage!: FloorDamageExtends;
|
||||
sprite!: Damage;
|
||||
|
||||
/** 每个分块中包含的物品信息 */
|
||||
blockData: Map<number, Map<number, ItemData>> = new Map();
|
||||
|
||||
/** 需要更新的分块 */
|
||||
private dirtyBlock: Set<number> = new Set();
|
||||
/** 道具详细信息 */
|
||||
private detailData: Map<number, Map<number, ItemDetailData>> = new Map();
|
||||
|
||||
static detailColor: Record<string, CanvasStyle> = {
|
||||
atk: '#FF7A7A',
|
||||
atkper: '#FF7A7A',
|
||||
def: '#00E6F1',
|
||||
defper: '#00E6F1',
|
||||
mdef: '#6EFF83',
|
||||
mdefper: '#6EFF83',
|
||||
hp: '#A4FF00',
|
||||
hpmax: '#F9FF00',
|
||||
hpmaxper: '#F9FF00',
|
||||
mana: '#c66',
|
||||
manaper: '#c66'
|
||||
};
|
||||
|
||||
static listened: Set<FloorItemDetail> = new Set();
|
||||
|
||||
private onBeforeDamageRender = (block: number) => {
|
||||
if (!mainSetting.getValue('screen.itemDetail')) return;
|
||||
if (this.dirtyBlock.has(block)) {
|
||||
this.sprite.block.clearCache(block, 1);
|
||||
}
|
||||
this.render(block);
|
||||
};
|
||||
|
||||
private onUpdateMapSize = (width: number, height: number) => {
|
||||
this.updateMapSize(width, height);
|
||||
};
|
||||
|
||||
private onUpdate = () => {
|
||||
this.updateItems();
|
||||
};
|
||||
|
||||
private onUpdateBlocks = (blocks: Set<number>) => {
|
||||
blocks.forEach(v => {
|
||||
this.dirtyBlock.add(v);
|
||||
});
|
||||
};
|
||||
|
||||
private listen() {
|
||||
this.sprite.on('dirtyUpdate', this.onBeforeDamageRender);
|
||||
this.sprite.on('setMapSize', this.onUpdateMapSize);
|
||||
this.sprite.on('updateBlocks', this.onUpdateBlocks);
|
||||
this.damage.on('update', this.onUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新地图大小
|
||||
*/
|
||||
updateMapSize(width: number, height: number) {
|
||||
this.blockData.clear();
|
||||
|
||||
// 预留blockData
|
||||
this.sprite.block.size(width, height);
|
||||
const data = this.sprite.block.blockData;
|
||||
const num = data.width * data.height;
|
||||
for (let i = 0; i <= num; i++) {
|
||||
this.blockData.set(i, new Map());
|
||||
this.detailData.set(i, new Map());
|
||||
this.dirtyBlock.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新全地图的物品,并进行分块存储
|
||||
*/
|
||||
updateItems() {
|
||||
const floor = this.floorBinder.getFloor();
|
||||
if (!floor) return;
|
||||
core.extractBlocks(floor);
|
||||
|
||||
core.status.maps[floor].blocks.forEach(v => {
|
||||
if (v.event.cls !== 'items' || v.disable) return;
|
||||
const id = v.event.id as AllIdsOf<'items'>;
|
||||
const item = core.material.items[id];
|
||||
if (item.cls === 'constants' || item.cls === 'tools') return;
|
||||
const x = v.x;
|
||||
const y = v.y;
|
||||
const block = this.sprite.block.getIndexByLoc(x, y);
|
||||
const index = x + y * this.sprite.mapWidth;
|
||||
const blockData = this.blockData.get(block);
|
||||
blockData?.set(index, { x, y, id });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图块
|
||||
* @param block 图块数字
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
setBlock(block: AllNumbers, x: number, y: number) {
|
||||
const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
|
||||
const index = this.sprite.block.getIndexByLoc(x, y);
|
||||
const itemIndex = x + y * this.sprite.mapWidth;
|
||||
const blockData = this.blockData.get(index);
|
||||
this.dirtyBlock.add(index);
|
||||
if (block === 0) {
|
||||
blockData?.delete(itemIndex);
|
||||
return;
|
||||
}
|
||||
const cls = map[block].cls;
|
||||
if (cls !== 'items') {
|
||||
blockData?.delete(itemIndex);
|
||||
return;
|
||||
}
|
||||
const id = map[block].id;
|
||||
blockData?.set(itemIndex, { x, y, id });
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定分块中的物品信息
|
||||
* @param block 要计算的分块
|
||||
*/
|
||||
calAllItems(block: Set<number>) {
|
||||
if (!core.status.thisMap) return;
|
||||
if (this.dirtyBlock.size === 0 || block.size === 0) return;
|
||||
let diff: Record<string | symbol, number | undefined> = {};
|
||||
const before = core.status.hero;
|
||||
const hero = structuredClone(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.setFlag('__statistics__', true);
|
||||
block.forEach(v => {
|
||||
if (!this.dirtyBlock.has(v)) return;
|
||||
const data = this.blockData.get(v);
|
||||
const detail = this.detailData.get(v);
|
||||
detail?.clear();
|
||||
if (!data) return;
|
||||
data.forEach(v => {
|
||||
const { id, x, y } = v;
|
||||
const index = x + y * this.sprite.mapWidth;
|
||||
diff = {};
|
||||
|
||||
const item = core.material.items[id];
|
||||
if (item.cls === 'equips') {
|
||||
// 装备也显示
|
||||
const diff: Record<string, any> = {
|
||||
...(item.equip.value ?? {})
|
||||
};
|
||||
const per = item.equip.percentage ?? {};
|
||||
for (const name of Object.keys(per)) {
|
||||
const n = name as SelectKey<HeroStatus, number>;
|
||||
diff[name + 'per'] = per[n].toString() + '%';
|
||||
}
|
||||
detail?.set(index, { x, y, diff });
|
||||
return;
|
||||
}
|
||||
|
||||
ItemState.item(id)?.itemEffectFn?.();
|
||||
detail?.set(index, { x, y, diff });
|
||||
});
|
||||
});
|
||||
core.status.hero = before;
|
||||
window.hero = before;
|
||||
window.flags = before.flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算并渲染指定格子里面的物品
|
||||
* @param block 需要渲染的格子
|
||||
*/
|
||||
render(block: number) {
|
||||
if (this.dirtyBlock.has(block)) {
|
||||
this.calAllItems(new Set([block]));
|
||||
}
|
||||
const data = this.detailData;
|
||||
this.dirtyBlock.delete(block);
|
||||
const info = data.get(block);
|
||||
if (!info) return;
|
||||
info.forEach(({ x, y, diff }) => {
|
||||
let n = 0;
|
||||
for (const [key, value] of Object.entries(diff)) {
|
||||
if (!value) continue;
|
||||
const color = FloorItemDetail.detailColor[key] ?? '#fff';
|
||||
const text = core.formatBigNumber(value, 4);
|
||||
const renderable: DamageRenderable = {
|
||||
x: x * this.sprite.cellSize + 2,
|
||||
y: y * this.sprite.cellSize + 31 - n * 10,
|
||||
text,
|
||||
color,
|
||||
align: 'left',
|
||||
baseline: 'alphabetic'
|
||||
};
|
||||
this.sprite.renderable.get(block)?.add(renderable);
|
||||
n++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
awake(group: LayerGroup): void {
|
||||
this.group = group;
|
||||
|
||||
const binder = group.getExtends('floor-binder');
|
||||
const damage = group.getExtends('floor-damage');
|
||||
if (
|
||||
binder instanceof LayerGroupFloorBinder &&
|
||||
damage instanceof FloorDamageExtends
|
||||
) {
|
||||
this.floorBinder = binder;
|
||||
this.damage = damage;
|
||||
this.sprite = damage.sprite;
|
||||
this.listen();
|
||||
FloorItemDetail.listened.add(this);
|
||||
} else {
|
||||
logger.warn(1001);
|
||||
group.removeExtends('item-detail');
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(group: LayerGroup): void {
|
||||
this.sprite.off('beforeDamageRender', this.onBeforeDamageRender);
|
||||
this.sprite.off('setMapSize', this.onUpdateMapSize);
|
||||
FloorItemDetail.listened.delete(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
Container,
|
||||
LayerGroupFloorBinder,
|
||||
FloorLayer,
|
||||
LayerGroup,
|
||||
FloorViewport,
|
||||
MotaRenderer
|
||||
} from '@motajs/render';
|
||||
import { hook } from '@user/data-base';
|
||||
import { MiscData } from '@user/data-state';
|
||||
import { FloorDamageExtends } from './damage';
|
||||
import { FloorItemDetail } from './itemDetail';
|
||||
|
||||
const loopMaps = MiscData.loopMaps;
|
||||
|
||||
let loopLayer: LayerGroup;
|
||||
let show: boolean = false;
|
||||
/** 循环式地图中,更新视角的委托ticker */
|
||||
let delegation: number = -1;
|
||||
|
||||
hook.on('changingFloor', (floorId, heroLoc) => {
|
||||
enableLoopMapElement(floorId);
|
||||
});
|
||||
|
||||
function createLayer() {
|
||||
const group = new LayerGroup();
|
||||
['bg', 'bg2', 'event', 'fg', 'fg2'].forEach(v => {
|
||||
group.addLayer(v as FloorLayer);
|
||||
});
|
||||
|
||||
const damage = new FloorDamageExtends();
|
||||
const detail = new FloorItemDetail();
|
||||
group.id = 'layer-loop';
|
||||
group.extends(damage);
|
||||
group.extends(detail);
|
||||
|
||||
loopLayer = group;
|
||||
group.setZIndex(20);
|
||||
}
|
||||
|
||||
function enableLoopMapElement(floorId: FloorIds) {
|
||||
if (!loopMaps.has(floorId)) {
|
||||
disableLoopMapElement();
|
||||
return;
|
||||
}
|
||||
if (!loopLayer) createLayer();
|
||||
const render = MotaRenderer.get('render-main');
|
||||
const draw = render?.getElementById('map-draw') as Container;
|
||||
const group = render?.getElementById('layer-main') as LayerGroup;
|
||||
if (!draw || !group) return;
|
||||
const ex = loopLayer.getExtends('floor-binder') as LayerGroupFloorBinder;
|
||||
const viewport = group.getExtends('viewport') as FloorViewport;
|
||||
if (!ex || !viewport) return;
|
||||
ex.bindFloor(floorId);
|
||||
|
||||
draw.appendChild(loopLayer);
|
||||
show = true;
|
||||
|
||||
const floor = core.status.maps[floorId];
|
||||
viewport.setAutoBound(false);
|
||||
const transform = group.camera;
|
||||
const width = floor.width;
|
||||
const testPos = width * loopLayer.cellSize;
|
||||
|
||||
loopLayer.removeTicker(delegation);
|
||||
delegation = loopLayer.delegateTicker(() => {
|
||||
const [x1] = transform.transformed(0, 0);
|
||||
const camera = loopLayer.camera;
|
||||
if (x1 > 0) {
|
||||
// 这个是计算循环地图应该显示在哪
|
||||
const [, y2] = transform.transformed(x1 - testPos, 0);
|
||||
camera.reset();
|
||||
camera.translate(core._PX_ - testPos, y2);
|
||||
loopLayer.pos(transform.x - core._PX_, 0);
|
||||
loopLayer.show();
|
||||
loopLayer.update(loopLayer);
|
||||
} else {
|
||||
const [x2, y2] = transform.transformed(testPos, 0);
|
||||
if (x2 < core._PX_) {
|
||||
// 这个不用做其他运算,可以直接显示
|
||||
camera.reset();
|
||||
camera.translate(0, y2);
|
||||
loopLayer.pos(x2, 0);
|
||||
loopLayer.show();
|
||||
loopLayer.update(loopLayer);
|
||||
} else {
|
||||
loopLayer.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function disableLoopMapElement() {
|
||||
if (!show) return;
|
||||
show = false;
|
||||
loopLayer.remove();
|
||||
|
||||
const render = MotaRenderer.get('render-main');
|
||||
const group = render?.getElementById('layer-main') as LayerGroup;
|
||||
if (!group) return;
|
||||
const viewport = group.getExtends('viewport') as FloorViewport;
|
||||
if (!viewport) return;
|
||||
|
||||
viewport.setAutoBound(true);
|
||||
loopLayer.removeTicker(delegation);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { LayerShadowExtends } from '@/core/fx/shadow';
|
||||
import { LayerShadowExtends } from '@motajs/legacy-ui';
|
||||
import {
|
||||
ILayerGroupRenderExtends,
|
||||
FloorDamageExtends,
|
||||
LayerGroupAnimate,
|
||||
FloorViewport,
|
||||
ILayerRenderExtends,
|
||||
@@ -11,13 +10,14 @@ import {
|
||||
LayerGroup,
|
||||
Font
|
||||
} from '@motajs/render';
|
||||
import { WeatherController } from '@/module/weather';
|
||||
import { FloorChange } from '@/plugin/fallback';
|
||||
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
|
||||
import { LayerGroupHalo } from '@/plugin/fx/halo';
|
||||
import { FloorItemDetail } from '@/plugin/fx/itemDetail';
|
||||
import { PopText } from '@/plugin/fx/pop';
|
||||
import { LayerGroupPortal } from '@/plugin/fx/portal';
|
||||
import { WeatherController } from '../../weather';
|
||||
import {
|
||||
FloorChange,
|
||||
LayerGroupFilter,
|
||||
LayerGroupHalo,
|
||||
LayerGroupPortal,
|
||||
PopText
|
||||
} from '@user/legacy-plugin-client';
|
||||
import { defineComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { Textbox, Tip } from '../components';
|
||||
import { GameUI, UIController } from '@motajs/system-ui';
|
||||
@@ -35,6 +35,11 @@ import {
|
||||
} from './statusBar';
|
||||
import { onLoaded } from '../use';
|
||||
import { ReplayingStatus } from './toolbar';
|
||||
import { getHeroStatusOn, HeroSkill, NightSpecial } from '@user/data-state';
|
||||
import { jumpIgnoreFloor } from '@user/legacy-plugin-data';
|
||||
import { hook } from '@user/data-base';
|
||||
import { FloorDamageExtends } from '../damage';
|
||||
import { FloorItemDetail } from '../itemDetail';
|
||||
|
||||
const MainScene = defineComponent(() => {
|
||||
const layerGroupExtends: ILayerGroupRenderExtends[] = [
|
||||
@@ -112,8 +117,6 @@ const MainScene = defineComponent(() => {
|
||||
night: 0
|
||||
});
|
||||
|
||||
const { getHeroStatusOn } = Mota.requireAll('fn');
|
||||
|
||||
const updateStatus = () => {
|
||||
if (!core.status || !core.status.hero || !core.status.floorId) return;
|
||||
hideStatus.value = core.getFlag('hideStatusBar', false);
|
||||
@@ -135,7 +138,6 @@ const MainScene = defineComponent(() => {
|
||||
leftStatus.exAtk = getHeroStatusOn('mana');
|
||||
leftStatus.magicDef = getHeroStatusOn('magicDef');
|
||||
|
||||
const { HeroSkill, NightSpecial } = Mota.require('module', 'Mechanism');
|
||||
rightStatus.autoSkill = HeroSkill.getAutoSkill();
|
||||
rightStatus.skillName = HeroSkill.getSkillName();
|
||||
rightStatus.skillDesc = HeroSkill.getSkillDesc();
|
||||
@@ -148,7 +150,7 @@ const MainScene = defineComponent(() => {
|
||||
replayStatus.played = totalList.length - toReplay.length;
|
||||
replayStatus.total = totalList.length;
|
||||
if (HeroSkill.learnedSkill(HeroSkill.Jump)) {
|
||||
if (Mota.Plugin.require('skill_g').jumpIgnoreFloor.has(floor)) {
|
||||
if (jumpIgnoreFloor.has(floor)) {
|
||||
rightStatus.jumpCount = -2;
|
||||
} else {
|
||||
rightStatus.jumpCount = 3 - (flags[`jump_${floor}`] ?? 0);
|
||||
@@ -168,7 +170,7 @@ const MainScene = defineComponent(() => {
|
||||
loaded.value = true;
|
||||
});
|
||||
|
||||
Mota.require('var', 'hook').on('statusBarUpdate', updateStatus);
|
||||
hook.on('statusBarUpdate', updateStatus);
|
||||
|
||||
return () => (
|
||||
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { mainUi } from '@motajs/legacy-ui';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { generateKeyboardEvent } from '@motajs/system-action';
|
||||
import { getVitualKeyOnce } from '@motajs/legacy-ui';
|
||||
import { getAllSavesData, getSaveData } from '@/module/utils';
|
||||
import { getAllSavesData, getSaveData } from '../../utils';
|
||||
|
||||
export interface SettingsProps extends Partial<ChoicesProps>, UIComponentProps {
|
||||
loc: ElementLocator;
|
||||
@@ -410,7 +410,9 @@ export const DownloadSaveSelect = defineComponent<SettingsProps>(props => {
|
||||
{ text: '请等待处理完毕' }
|
||||
);
|
||||
core.download(
|
||||
`${core.firstData.name}_${core.formatDate2(new Date())}.h5save`,
|
||||
`${core.firstData.name}_${core.formatDate2(
|
||||
new Date()
|
||||
)}.h5save`,
|
||||
data
|
||||
);
|
||||
}
|
||||
@@ -426,7 +428,9 @@ export const DownloadSaveSelect = defineComponent<SettingsProps>(props => {
|
||||
if (confirm) {
|
||||
const data = await getSaveData(core.saves.saveIndex);
|
||||
core.download(
|
||||
`${core.firstData.name}_${core.formatDate2(new Date())}.h5save`,
|
||||
`${core.firstData.name}_${core.formatDate2(
|
||||
new Date()
|
||||
)}.h5save`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
ReplayingStatus,
|
||||
ReplayingToolbar
|
||||
} from './toolbar';
|
||||
import { HeroSkill } from '@user/data-state';
|
||||
|
||||
export interface ILeftHeroStatus {
|
||||
hp: number;
|
||||
@@ -275,7 +276,6 @@ export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
|
||||
};
|
||||
|
||||
const changeAutoSkill = () => {
|
||||
const { HeroSkill } = Mota.require('module', 'Mechanism');
|
||||
const auto = !s.autoSkill;
|
||||
HeroSkill.setAutoSkill(auto);
|
||||
core.status.route.push(`set:autoSkill:${auto}`);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { loading } from '@user/data-base';
|
||||
import { TimingFn, Transition } from 'mutate-animate';
|
||||
import {
|
||||
ComponentInternalInstance,
|
||||
@@ -59,7 +60,6 @@ export function onOrientationChange(hook: OrientationHook) {
|
||||
* @param hook 当游戏加载完成时执行的函数
|
||||
*/
|
||||
export function onLoaded(hook: () => void) {
|
||||
const loading = Mota.require('var', 'loading');
|
||||
if (!loading.loaded) {
|
||||
loading.once('loaded', hook);
|
||||
} else {
|
||||
|
||||
Reference in new issue
Block a user