mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-03-13 18:37:07 +08:00
444 lines
13 KiB
TypeScript
444 lines
13 KiB
TypeScript
import { EventEmitter } from '@/core/common/eventEmitter';
|
||
import { deleteWith, has } from '@/plugin/utils';
|
||
import { Component, nextTick, reactive, shallowReactive } from 'vue';
|
||
import { fixedUi } from '../init/ui';
|
||
import { GameStorage } from '../storage';
|
||
import type {
|
||
CustomToolbarComponent,
|
||
MiscToolbar,
|
||
SettableItemData,
|
||
ToolbarItemBase,
|
||
ToolbarItemMap,
|
||
ToolbarItemType
|
||
} from '../init/toolbar';
|
||
import { isMobile } from '@/plugin/use';
|
||
|
||
interface CustomToolbarEvent {
|
||
add: (item: ValueOf<ToolbarItemMap>) => void;
|
||
delete: (item: ValueOf<ToolbarItemMap>) => void;
|
||
set: (id: string, data: Partial<SettableItemData>) => void;
|
||
emit: (id: string, item: ValueOf<ToolbarItemMap>) => void;
|
||
posChange: (bar: CustomToolbar) => void;
|
||
}
|
||
|
||
interface ToolbarSaveData {
|
||
x: number;
|
||
y: number;
|
||
w: number;
|
||
h: number;
|
||
items: ValueOf<ToolbarItemMap>[];
|
||
}
|
||
|
||
type ToolItemEmitFn<T extends ToolbarItemType> = (
|
||
this: CustomToolbar,
|
||
id: string,
|
||
item: ToolbarItemMap[T]
|
||
) => boolean;
|
||
|
||
interface RegisteredCustomToolInfo {
|
||
name: string;
|
||
onEmit: ToolItemEmitFn<ToolbarItemType>;
|
||
show: CustomToolbarComponent;
|
||
editor: CustomToolbarComponent;
|
||
onCreate: (item: any) => ToolbarItemBase<ToolbarItemType>;
|
||
}
|
||
|
||
type MiscEmitFn = (
|
||
id: string,
|
||
toolbar: CustomToolbar,
|
||
item: MiscToolbar
|
||
) => void;
|
||
type ActivedFn = (info: MiscInfo) => boolean;
|
||
|
||
interface MiscInfo {
|
||
id: string;
|
||
name: string;
|
||
emit: MiscEmitFn;
|
||
display: () => Component;
|
||
activable?: boolean;
|
||
actived?: ActivedFn;
|
||
}
|
||
|
||
interface Misc {
|
||
info: Record<string, MiscInfo>;
|
||
|
||
/**
|
||
* 注册一个杂项工具
|
||
* @param id 杂项工具的id
|
||
* @param name 这个工具的名称
|
||
* @param emit 触发这个杂项工具时执行的函数
|
||
* @param display 这个工具的显示组件
|
||
* @param activable 是否是可以被激活的工具,例如打开小地图后显示为激活状态
|
||
*/
|
||
register(
|
||
this: Misc,
|
||
id: string,
|
||
name: string,
|
||
emit: MiscEmitFn,
|
||
display: () => Component
|
||
): void;
|
||
|
||
/**
|
||
* 为一类杂项工具设置激活信息
|
||
* @param id 杂项工具的id
|
||
* @param activable 是否可激活
|
||
* @param actived 获取当前是否激活的函数
|
||
*/
|
||
bindActivable(id: string, activable: boolean, actived?: ActivedFn): void;
|
||
|
||
/**
|
||
* 刷新所有或指定的包含杂项工具的工具栏
|
||
* @param id 指定包含这个杂项工具的工具栏刷新,例如填drag,则只会刷新包含drag杂项工具的工具栏
|
||
*/
|
||
requestRefresh(id?: string): void;
|
||
}
|
||
|
||
const toolbarStorage = new GameStorage<Record<string, ToolbarSaveData>>(
|
||
GameStorage.fromAuthor('AncTe', 'toolbar')
|
||
);
|
||
|
||
const misc: Misc = {
|
||
info: {},
|
||
register(id, name, emit, display) {
|
||
this.info[id] = { id, name, emit, display };
|
||
},
|
||
bindActivable(id, activable, actived) {
|
||
this.info[id].activable = activable;
|
||
this.info[id].actived = actived;
|
||
},
|
||
requestRefresh(id) {
|
||
if (id) {
|
||
CustomToolbar.list.forEach(v => {
|
||
if (
|
||
v.items.some(v => {
|
||
return v.type === 'misc' && v.items?.includes(id);
|
||
})
|
||
) {
|
||
v.refresh();
|
||
}
|
||
});
|
||
} else {
|
||
CustomToolbar.list.forEach(v => {
|
||
if (v.items.some(v => v.type === 'misc')) {
|
||
v.refresh();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
};
|
||
|
||
export class CustomToolbar extends EventEmitter<CustomToolbarEvent> {
|
||
static num: number = 0;
|
||
static list: CustomToolbar[] = shallowReactive([]);
|
||
static info: Record<string, RegisteredCustomToolInfo> = {};
|
||
|
||
static misc: Misc = misc;
|
||
|
||
items: ValueOf<ToolbarItemMap>[] = reactive([]);
|
||
num: number = CustomToolbar.num++;
|
||
id: string;
|
||
// ----- size
|
||
x: number = 300;
|
||
y: number = 300;
|
||
width: number = 300;
|
||
height: number = 70;
|
||
// ----- other
|
||
assistKey: number = 0;
|
||
showIds: number[] = [];
|
||
|
||
constructor(id: string, noshow: boolean = false) {
|
||
super();
|
||
this.id = id;
|
||
// 按比例设置初始大小
|
||
const setting = Mota.require('var', 'mainSetting');
|
||
const scale = setting.getValue('ui.toolbarScale', 100) / 100;
|
||
this.width *= scale;
|
||
this.height *= scale;
|
||
this.x *= scale;
|
||
this.y *= scale;
|
||
|
||
if (!noshow) this.show();
|
||
CustomToolbar.list.push(this);
|
||
}
|
||
|
||
/**
|
||
* 添加一个自定义项
|
||
* @param item 要添加的自定义工具栏项
|
||
*/
|
||
add<K extends ToolbarItemType>(item: ToolbarItemMap[K]) {
|
||
const index = this.items.findIndex(v => v.id === item.id);
|
||
if (index !== -1) {
|
||
console.warn(`添加了id重复的自定义工具,已将其覆盖`);
|
||
this.items[index] = item;
|
||
} else {
|
||
this.items.push(item);
|
||
}
|
||
this.emit('add', item);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 删除一个自定义项
|
||
* @param id 要删除的项的id
|
||
*/
|
||
delete(id: string) {
|
||
const index = this.items.findIndex(v => v.id === id);
|
||
if (index === -1) return;
|
||
const item = this.items[index];
|
||
this.items.splice(index, 1);
|
||
this.emit('delete', item);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 设置一个项
|
||
* @param id 要设置的项的id
|
||
* @param item 要设置的属性内容
|
||
*/
|
||
set<T extends ToolbarItemType>(
|
||
id: string,
|
||
item: Partial<SettableItemData<T>>
|
||
) {
|
||
const toSet = this.items.find(v => v.id === id);
|
||
if (!toSet) return;
|
||
Object.assign(toSet, item);
|
||
this.emit('set', id, item);
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 触发一个自定义工具
|
||
* @param id 要触发的自定义工具的id
|
||
*/
|
||
emitTool(id: string) {
|
||
const item = this.items.find(v => v.id === id);
|
||
if (!item) return this;
|
||
this.emit('emit', id, item);
|
||
const info = CustomToolbar.info[item.type];
|
||
if (!info) {
|
||
console.warn(`触发了未知的自定义工具类型:'${item.type}'`);
|
||
return this;
|
||
}
|
||
const success = info.onEmit.call(this, id, item);
|
||
if (!success) {
|
||
console.warn(`触发自定义工具失败,id:'${id}',type:${item.type}`);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
/**
|
||
* 强制刷新这个自定义工具栏的所有显示
|
||
*/
|
||
refresh(reopen: boolean = false) {
|
||
if (reopen && this.showIds.length > 0) {
|
||
this.closeAll();
|
||
nextTick(() => {
|
||
this.show();
|
||
});
|
||
} else {
|
||
const items = this.items.splice(0);
|
||
nextTick(() => {
|
||
this.items.push(...items);
|
||
});
|
||
}
|
||
return this;
|
||
}
|
||
|
||
setPos(x?: number, y?: number) {
|
||
has(x) && (this.x = x);
|
||
has(y) && (this.y = y);
|
||
this.emit('posChange', this);
|
||
}
|
||
|
||
setSize(width?: number, height?: number) {
|
||
has(width) && (this.width = width);
|
||
has(height) && (this.height = height);
|
||
this.emit('posChange', this);
|
||
}
|
||
|
||
/**
|
||
* 显示这个自定义工具栏,可以显示多个,且内容互通
|
||
* @param multi 是否允许显示多个,不填时,如果已经存在这个工具栏,那么将不会显示
|
||
*/
|
||
show(multi: boolean = false) {
|
||
if (
|
||
!multi &&
|
||
this.showIds.some(v => fixedUi.stack.some(vv => vv.num === v))
|
||
) {
|
||
return -1;
|
||
}
|
||
const id = fixedUi.open('toolbar', { bar: this });
|
||
this.showIds.push(id);
|
||
return id;
|
||
}
|
||
|
||
/**
|
||
* 关闭一个以此实例为基础显示的自定义工具栏
|
||
* @param id 要关闭的id
|
||
*/
|
||
close(id: number) {
|
||
fixedUi.close(id);
|
||
deleteWith(this.showIds, id);
|
||
}
|
||
|
||
/**
|
||
* 关闭这个自定义工具栏的所有显示
|
||
*/
|
||
closeAll() {
|
||
this.showIds.forEach(v => fixedUi.close(v));
|
||
this.showIds = [];
|
||
}
|
||
|
||
static get(id: string) {
|
||
return this.list.find(v => v.id === id);
|
||
}
|
||
|
||
/**
|
||
* 注册一类自定义工具
|
||
* @param type 要注册的自定义工具类型
|
||
* @param name 该类型的中文名
|
||
* @param onEmit 当触发这个自定义工具的时候执行的函数
|
||
* @param show 这个自定义工具在自定义工具栏的显示组件
|
||
* @param editor 这个自定义工具在编辑时编辑组件
|
||
* @param onCreate 当这个自定义工具在编辑器中被添加时,执行的初始化脚本
|
||
*/
|
||
static register<K extends ToolbarItemType>(
|
||
type: K,
|
||
name: string,
|
||
onEmit: ToolItemEmitFn<K>,
|
||
show: CustomToolbarComponent<K>,
|
||
editor: CustomToolbarComponent<K>,
|
||
onCreate: (item: any) => ToolbarItemMap[K]
|
||
) {
|
||
if (type in this.info) {
|
||
console.warn(`已存在名为'${type}'的自定义工具类型,已将其覆盖!`);
|
||
}
|
||
const info: RegisteredCustomToolInfo = {
|
||
name,
|
||
onEmit: onEmit as ToolItemEmitFn<ToolbarItemType>,
|
||
show: show as CustomToolbarComponent,
|
||
editor: editor as CustomToolbarComponent,
|
||
// @ts-ignore
|
||
onCreate
|
||
};
|
||
this.info[type] = info;
|
||
}
|
||
|
||
static save() {
|
||
toolbarStorage.clear();
|
||
const setting = Mota.require('var', 'mainSetting');
|
||
const scale = setting.getValue('ui.toolbarScale', 100) / 100;
|
||
this.list.forEach(v => {
|
||
const toSave: ToolbarSaveData = {
|
||
x: v.x,
|
||
y: v.y,
|
||
w: v.width / scale,
|
||
h: v.height / scale,
|
||
items: []
|
||
};
|
||
v.items.forEach(v => {
|
||
toSave.items.push(v);
|
||
});
|
||
toolbarStorage.setValue(v.id, toSave);
|
||
});
|
||
toolbarStorage.write();
|
||
}
|
||
|
||
static load() {
|
||
toolbarStorage.read();
|
||
for (const [key, value] of Object.entries(toolbarStorage.data)) {
|
||
const bar = this.get(key) ?? new CustomToolbar(key);
|
||
bar.x = value.x;
|
||
bar.y = value.y;
|
||
bar.width = value.w;
|
||
bar.height = value.h;
|
||
for (const item of value.items) {
|
||
bar.add(item);
|
||
}
|
||
}
|
||
}
|
||
|
||
static refreshAll(reopen: boolean = false): void {
|
||
CustomToolbar.list.forEach(v => v.refresh(reopen));
|
||
}
|
||
|
||
static showAll(): number[] {
|
||
return CustomToolbar.list.map(v => v.show());
|
||
}
|
||
|
||
static closeAll() {
|
||
this.list.forEach(v => v.closeAll());
|
||
}
|
||
}
|
||
|
||
Mota.require('var', 'loading').once('coreInit', () => {
|
||
CustomToolbar.load();
|
||
CustomToolbar.closeAll();
|
||
|
||
window.addEventListener('beforeunload', e => {
|
||
CustomToolbar.save();
|
||
});
|
||
window.addEventListener('blur', () => {
|
||
CustomToolbar.save();
|
||
});
|
||
});
|
||
Mota.require('var', 'hook').on('reset', () => {
|
||
CustomToolbar.showAll();
|
||
});
|
||
|
||
Mota.require('var', 'hook').once('reset', () => {
|
||
const mainStorage = GameStorage.for(GameStorage.fromGame('main'));
|
||
mainStorage.read();
|
||
if (!mainStorage.getValue('played', false)) {
|
||
mainStorage.setValue('played', true);
|
||
let defaultsTool = CustomToolbar.list.find(v => v.id === '@defaults');
|
||
const hasDefaults = !!defaultsTool;
|
||
if (!defaultsTool) {
|
||
defaultsTool = new CustomToolbar('@defaults', true);
|
||
}
|
||
defaultsTool.closeAll();
|
||
defaultsTool.items = reactive([]);
|
||
defaultsTool.add({
|
||
id: '@defaults_misc',
|
||
type: 'misc',
|
||
folded: false,
|
||
noDefaultAction: true,
|
||
items: [
|
||
'book',
|
||
'fly',
|
||
'save',
|
||
'load',
|
||
'toolbox',
|
||
'equipbox',
|
||
'shop',
|
||
'virtualKey',
|
||
'setting',
|
||
'undo',
|
||
'redo',
|
||
'danmaku',
|
||
'minimap'
|
||
]
|
||
});
|
||
// 计算位置,显示在游戏画面下方
|
||
if (!hasDefaults) {
|
||
const game = core.dom.gameDraw;
|
||
const bottom = game.offsetTop + game.offsetHeight;
|
||
const left = game.offsetLeft;
|
||
const width = game.offsetWidth;
|
||
|
||
if (isMobile) {
|
||
// 手机端显示在最下方
|
||
defaultsTool.setPos(16, bottom);
|
||
defaultsTool.setSize(window.innerWidth - 32, 85);
|
||
} else {
|
||
// 电脑显示在屏幕右方
|
||
defaultsTool.setPos(left, bottom);
|
||
defaultsTool.setSize(width, 70);
|
||
}
|
||
}
|
||
|
||
defaultsTool.show();
|
||
CustomToolbar.save();
|
||
}
|
||
});
|