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-08 13:29:22 +08:00
|
|
|
import { GameStorage } from './storage';
|
2023-08-08 16:44:43 +08:00
|
|
|
import { triggerFullscreen } from '../../plugin/utils';
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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', () => '')
|
2023-08-07 23:50:23 +08:00
|
|
|
.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
|
|
|
|
2023-08-08 13:29:22 +08:00
|
|
|
interface SettingStorage {
|
|
|
|
showHalo: boolean;
|
|
|
|
frag: boolean;
|
|
|
|
itemDetail: boolean;
|
|
|
|
transition: boolean;
|
|
|
|
antiAlias: boolean;
|
|
|
|
fontSize: number;
|
|
|
|
smoothView: boolean;
|
|
|
|
criticalGem: boolean;
|
|
|
|
fixed: boolean;
|
|
|
|
betterLoad: boolean;
|
|
|
|
autoScale: boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
const storage = new GameStorage<SettingStorage>(
|
|
|
|
GameStorage.fromAncTe('setting')
|
|
|
|
);
|
|
|
|
|
2023-08-05 12:12:02 +08:00
|
|
|
loading.once('coreInit', () => {
|
|
|
|
mainSetting.reset({
|
2023-08-08 13:29:22 +08:00
|
|
|
'screen.fullscreen': !!document.fullscreenElement,
|
|
|
|
'screen.halo': !!storage.getValue('showHalo', true),
|
|
|
|
'screen.frag': !!storage.getValue('frag', true),
|
|
|
|
'screen.itemDetail': !!storage.getValue('itemDetail', true),
|
|
|
|
'screen.transition': !!storage.getValue('transition', false),
|
|
|
|
'screen.antiAlias': !!storage.getValue('antiAlias', false),
|
|
|
|
'screen.fontSize': storage.getValue('fontSize', 16),
|
|
|
|
'screen.smoothView': !!storage.getValue('smoothView', true),
|
|
|
|
'screen.criticalGem': !!storage.getValue('criticalGem', false),
|
|
|
|
'action.fixed': !!storage.getValue('fixed', true),
|
|
|
|
'utils.betterLoad': !!storage.getValue('betterLoad', true),
|
|
|
|
'utils.autoScale': !!storage.getValue('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
|
|
|
|
});
|
|
|
|
});
|