HumanBreak/src/core/main/setting.ts

422 lines
12 KiB
TypeScript
Raw Normal View History

2023-08-05 12:12:02 +08:00
import { reactive } from 'vue';
2023-08-02 23:25:14 +08:00
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
2023-08-05 12:12:02 +08:00
import { transition } from '../../plugin/uiController';
import { loading } from '../loader/load';
import { hook } from './game';
2023-08-06 17:46:29 +08:00
import { isMobile } from '../../plugin/use';
2023-08-02 23:25:14 +08:00
2023-08-02 23:17:10 +08:00
type MotaSettingType = boolean | number | MotaSetting;
2023-08-05 12:12:02 +08:00
export interface MotaSettingItem<T extends MotaSettingType = MotaSettingType> {
2023-08-02 23:17:10 +08:00
name: string;
2023-08-05 12:12:02 +08:00
key: string;
2023-08-02 23:17:10 +08:00
value: T;
defaults?: boolean | number;
2023-08-05 12:12:02 +08:00
step?: [number, number, number];
2023-08-02 23:17:10 +08:00
display?: (value: T) => string;
2023-08-02 23:27:57 +08:00
special?: string;
2023-08-02 23:17:10 +08:00
}
2023-08-02 23:25:14 +08:00
interface SettingEvent extends EmitableEvent {
valueChange: <T extends boolean | number>(
key: string,
newValue: T,
oldValue: T
) => void;
}
2023-08-05 12:12:02 +08:00
export type SettingText = {
[key: string]: string[] | SettingText;
};
export interface SettingDisplayInfo {
item: MotaSettingItem | null;
list: Record<string, MotaSettingItem>;
text: string[];
}
export class MotaSetting extends EventEmitter<SettingEvent> {
readonly list: Record<string, MotaSettingItem> = {};
/**
*
* @param setting
*/
reset(setting: Record<string, boolean | number>) {
for (const [key, value] of Object.entries(setting)) {
this.setValue(key, value);
}
}
2023-08-02 23:17:10 +08:00
/**
*
*/
2023-08-02 23:27:57 +08:00
markSpecial(key: string, sp: string) {
const setting = this.getSettingBy(key.split('.'));
setting.special = sp;
2023-08-02 23:17:10 +08:00
return this;
}
/**
*
* @param key
* @param value
*/
2023-08-05 12:12:02 +08:00
register(
key: string,
name: string,
value: number,
step?: [number, number, number]
): this;
2023-08-02 23:17:10 +08:00
/**
*
* @param key
* @param value
*/
register(key: string, name: string, value: boolean | MotaSetting): this;
register(
key: string,
name: string,
value: MotaSettingType,
2023-08-05 12:12:02 +08:00
step: [number, number, number] = [0, 100, 1]
2023-08-02 23:17:10 +08:00
) {
const setting: MotaSettingItem = {
name,
2023-08-05 12:12:02 +08:00
value,
key
2023-08-02 23:17:10 +08:00
};
if (!(value instanceof MotaSetting)) setting.defaults = value;
if (typeof value === 'number') setting.step = step;
this.list[key] = setting;
return this;
}
/**
*
* @param key
*/
getSetting(key: string): Readonly<MotaSettingItem | null> {
const list = key.split('.');
return this.getSettingBy(list);
}
/**
*
* @param key
* @param value
*/
setValue(key: string, value: boolean | number) {
const setting = this.getSettingBy(key.split('.'));
if (typeof setting.value !== typeof value) {
throw new Error(
`Setting type mismatch on setting '${key}'.` +
`Expected: ${typeof setting.value}. Recieve: ${typeof value}`
);
}
2023-08-02 23:25:14 +08:00
const old = setting.value as boolean | number;
2023-08-02 23:17:10 +08:00
setting.value = value;
2023-08-05 12:12:02 +08:00
this.emit('valueChange', key, value, old);
2023-08-02 23:17:10 +08:00
}
/**
*
* @param key
* @param value
*/
addValue(key: string, value: number) {
const setting = this.getSettingBy(key.split('.'));
if (typeof setting.value !== 'number') {
throw new Error(
`Cannot execute addValue method on a non-number setting.` +
`Type expected: number. See: ${typeof setting.value}`
);
}
2023-08-02 23:25:14 +08:00
const old = setting.value as boolean | number;
2023-08-02 23:17:10 +08:00
setting.value += value;
2023-08-02 23:27:57 +08:00
this.emit('valueChange', key, old, value);
2023-08-02 23:17:10 +08:00
}
/**
*
* @param key
* @param func
*/
setDisplayFunc(key: string, func: (value: MotaSettingType) => string) {
const setting = this.getSettingBy(key.split('.'));
setting.display = func;
2023-08-05 12:12:02 +08:00
return this;
2023-08-02 23:17:10 +08:00
}
private getSettingBy(list: string[]) {
let now: MotaSetting = this;
for (let i = 0; i < list.length - 1; i++) {
2023-08-05 12:12:02 +08:00
const item = now.list[list[i]].value;
2023-08-02 23:17:10 +08:00
if (!(item instanceof MotaSetting)) {
throw new Error(
`Cannot get setting. The parent isn't a MotaSetting instance.` +
`Key: '${list.join('.')}'. Reading: '${list[i]}'`
);
}
now = item;
}
return now.list[list.at(-1)!] ?? null;
}
}
2023-08-05 12:12:02 +08:00
interface SettingDisplayerEvent extends EmitableEvent {
update: (stack: string[], display: SettingDisplayInfo[]) => void;
}
export class SettingDisplayer extends EventEmitter<SettingDisplayerEvent> {
setting: MotaSetting;
textInfo: SettingText;
/** 选项选中栈 */
selectStack: string[] = [];
displayInfo: SettingDisplayInfo[] = reactive([]);
constructor(setting: MotaSetting, textInfo: SettingText) {
super();
this.setting = setting;
this.textInfo = textInfo;
this.update();
}
/**
*
* @param key
*/
add(key: string) {
this.selectStack.push(...key.split('.'));
this.update();
}
/**
*
* @param index
*/
cut(index: number, noUpdate: boolean = false) {
this.selectStack.splice(index, Infinity);
if (!noUpdate) this.update();
}
update() {
const list = this.selectStack;
let now = this.setting;
let nowText: string[] | SettingText = this.textInfo;
this.displayInfo = [];
for (let i = 0; i < list.length - 1; i++) {
const item = now.list[list[i]].value;
if (!(item instanceof MotaSetting)) {
throw new Error(
`Cannot get setting. The parent isn't a MotaSetting instance.` +
`Key: '${list.join('.')}'. Reading: '${list[i + 1]}'`
);
}
this.displayInfo.push({
item: now.list[list[i]],
text: [],
list: now.list
});
now = item;
if (nowText && !(nowText instanceof Array))
nowText = nowText[list[i]];
}
if (nowText && !(nowText instanceof Array))
nowText = nowText[list.at(-1)!];
const last = now.list[list.at(-1)!];
if (last) {
this.displayInfo.push({
item: last,
text: nowText instanceof Array ? nowText : ['请选择设置'],
list: now.list
});
if (last.value instanceof MotaSetting) {
this.displayInfo.push({
item: null,
text: ['请选择设置'],
list: (last.value as MotaSetting).list
});
}
} else {
this.displayInfo.push({
item: null,
text: ['请选择设置'],
list: this.setting.list
});
}
this.emit('update', this.selectStack, this.displayInfo);
}
}
2023-08-02 23:17:10 +08:00
export const mainSetting = new MotaSetting();
2023-08-05 12:12:02 +08:00
// ----- 监听设置修改
mainSetting.on('valueChange', (key, n, o) => {
const [root, setting] = key.split('.');
if (root === 'screen') {
handleScreenSetting(setting, n, o);
} else if (root === 'action') {
handleActionSetting(setting, n, o);
} else if (root === 'utils') {
handleUtilsSetting(setting, n, o);
}
});
export async function triggerFullscreen(full: boolean) {
const { maxGameScale } = core.plugin.utils;
if (!!document.fullscreenElement && !full) {
await document.exitFullscreen();
requestAnimationFrame(() => {
maxGameScale(1);
});
}
if (full && !document.fullscreenElement) {
await document.body.requestFullscreen();
requestAnimationFrame(() => {
maxGameScale();
});
}
}
const root = document.getElementById('root') as HTMLDivElement;
const root2 = document.getElementById('root2') as HTMLDivElement;
function handleScreenSetting<T extends number | boolean>(
key: string,
n: T,
o: T
) {
if (key === 'fullscreen') {
// 全屏
triggerFullscreen(n as boolean);
} else if (key === 'halo') {
// 光环
core.setLocalStorage('showHalo', n);
} else if (key === 'frag') {
// 打怪特效
core.setLocalStorage('frag', n);
} else if (key === 'itemDetail') {
// 宝石血瓶显伤
core.setLocalStorage('itemDetail', n);
} else if (key === 'transition') {
// 界面动画
core.setLocalStorage('transition', n);
transition.value = n as boolean;
} else if (key === 'antiAlias') {
// 抗锯齿
core.setLocalStorage('antiAlias', n);
for (const canvas of core.dom.gameCanvas) {
if (core.domStyle.hdCanvas.includes(canvas.id)) continue;
if (n) {
canvas.classList.remove('no-anti-aliasing');
} else {
canvas.classList.add('no-anti-aliasing');
}
}
} else if (key === 'fontSize') {
// 字体大小
core.setLocalStorage('fontSize', n);
root.style.fontSize = root2.style.fontSize = `${n}px`;
2023-08-07 17:57:51 +08:00
} else if (key === 'smoothView') {
core.setLocalStorage('smoothView', n);
2023-08-07 18:45:50 +08:00
} else if (key === 'criticalGem') {
core.setLocalStorage('criticalGem', n);
2023-08-05 12:12:02 +08:00
}
}
function handleActionSetting<T extends number | boolean>(
key: string,
n: T,
o: T
) {
if (key === 'autoSkill') {
// 自动切换技能
flags.autoSkill = n;
2023-08-07 18:45:50 +08:00
} else if (key === 'fixed') {
2023-08-05 12:12:02 +08:00
// 定点查看
core.setLocalStorage('fixed', n);
}
}
function handleUtilsSetting<T extends number | boolean>(
key: string,
n: T,
o: T
) {
if (key === 'betterLoad') {
// 加载优化
core.setLocalStorage('betterLoad', n);
2023-08-07 18:45:50 +08:00
} else if (key === 'autoScale') {
// 自动放缩
core.setLocalStorage('autoScale', n);
2023-08-05 12:12:02 +08:00
}
}
2023-08-02 23:17:10 +08:00
// ----- 游戏的所有设置项
mainSetting
.register(
'screen',
'显示设置',
new MotaSetting()
.register('fullscreen', '全屏游戏', false)
.register('halo', '光环显示', true)
.register('frag', '打怪特效', true)
.register('itemDetail', '宝石血瓶显伤', true)
.register('transition', '界面动画', false)
.register('antiAlias', '抗锯齿', false)
2023-08-05 12:12:02 +08:00
.register('fontSize', '字体大小', 16, [8, 28, 1])
2023-08-07 17:57:51 +08:00
.register('smoothView', '平滑镜头', true)
2023-08-07 18:45:50 +08:00
.register('criticalGem', '临界显示方式', false)
.setDisplayFunc('criticalGem', value => (value ? '宝石数' : '攻击'))
2023-08-02 23:17:10 +08:00
)
.register(
'action',
'操作设置',
new MotaSetting()
.register('autoSkill', '自动切换技能', true)
.register('fixed', '定点查看', true)
2023-08-02 23:27:57 +08:00
.register('hotkey', '快捷键', false)
.markSpecial('hotkey', 'hotkey')
2023-08-05 12:12:02 +08:00
.setDisplayFunc('hotkey', () => '')
.register('toolbar', '自定义工具栏', false)
.markSpecial('toolbar', 'toolbar')
.setDisplayFunc('toolbar', () => '')
2023-08-02 23:17:10 +08:00
)
.register(
'utils',
'功能设置',
2023-08-07 18:45:50 +08:00
new MotaSetting()
.register('betterLoad', '优化加载', true)
.register('autoScale', '自动放缩', true)
2023-08-02 23:17:10 +08:00
);
2023-08-05 12:12:02 +08:00
loading.once('coreInit', () => {
2023-08-07 18:54:43 +08:00
const get = core.getLocalStorage;
2023-08-05 12:12:02 +08:00
mainSetting.reset({
'screen.fullscreen': false,
2023-08-07 18:54:43 +08:00
'screen.halo': !!get('showHalo', true),
'screen.frag': !!get('frag', true),
'screen.itemDetail': !!get('itemDetail', true),
'screen.transition': !!get('transition', false),
'screen.antiAlias': !!get('antiAlias', false),
'screen.fontSize': get('fontSize', 16),
'screen.smoothView': !!get('smoothView', true),
'screen.criticalGem': !!get('criticalGem', false),
'action.fixed': !!get('fixed', true),
'utils.betterLoad': !!get('betterLoad', true),
'utils.autoScale': !!get('autoScale', true)
2023-08-05 12:12:02 +08:00
});
});
2023-08-07 18:45:50 +08:00
hook.on('reset', () => {
2023-08-05 12:12:02 +08:00
mainSetting.reset({
'action.autoSkill': flags.autoSkill ?? true
});
});