mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-11 15:47:06 +08:00
refactor: src/core/main 全部移入 monorepo
This commit is contained in:
parent
9ec13e5314
commit
960dbd429a
1
.gitignore
vendored
1
.gitignore
vendored
@ -49,3 +49,4 @@ script/people.ts
|
||||
docs/
|
||||
user.ts
|
||||
.antlr
|
||||
graph.svg
|
12
.madgerc
Normal file
12
.madgerc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"fileExtensions": [
|
||||
"ts",
|
||||
"tsx"
|
||||
],
|
||||
"tsConfig": "./tsconfig.json",
|
||||
"detectiveOptions": {
|
||||
"ts": {
|
||||
"skipTypeImports": true
|
||||
}
|
||||
}
|
||||
}
|
@ -63,6 +63,7 @@
|
||||
"glob": "^11.0.1",
|
||||
"globals": "^15.14.0",
|
||||
"less": "^4.2.0",
|
||||
"madge": "^8.0.0",
|
||||
"postcss-preset-env": "^9.6.0",
|
||||
"rollup": "^3.29.4",
|
||||
"terser": "^5.31.6",
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './keyCodes';
|
||||
export * from './types';
|
||||
|
4
packages/client-base/src/types.ts
Normal file
4
packages/client-base/src/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface ResponseBase {
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
6
packages/legacy-system/package.json
Normal file
6
packages/legacy-system/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@motajs/legacy-system",
|
||||
"dependencies": {
|
||||
"@motajs/system-action": "workspace:*"
|
||||
}
|
||||
}
|
2
packages/legacy-system/src/index.ts
Normal file
2
packages/legacy-system/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './misc';
|
||||
export * from './storage';
|
@ -1,5 +1,5 @@
|
||||
import { Keyboard } from '../custom/keyboard';
|
||||
import KeyboardUI from '@/panel/keyboard.vue';
|
||||
import { Keyboard } from '@motajs/system-action';
|
||||
import KeyboardUI from '../panel/keyboard.vue';
|
||||
|
||||
interface VirtualKeyProps {
|
||||
keyboard: Keyboard;
|
@ -4,6 +4,8 @@
|
||||
"@motajs/render": "workspace:*",
|
||||
"@motajs/common": "workspace:*",
|
||||
"@motajs/client": "workspace:*",
|
||||
"@motajs/client-base": "workspace:*"
|
||||
"@motajs/client-base": "workspace:*",
|
||||
"@motajs/legacy-system": "workspace:*",
|
||||
"@motajs/system-action": "workspace:*"
|
||||
}
|
||||
}
|
6
packages/legacy-ui/src/components/index.ts
Normal file
6
packages/legacy-ui/src/components/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export { default as Box } from './box.vue';
|
||||
export { default as BoxAnimate } from './boxAnimate.vue';
|
||||
export { default as Column } from './colomn.vue';
|
||||
export { default as EnemyOne } from './enemyOne.vue';
|
||||
export { default as Minimap } from './minimap.vue';
|
||||
export { default as Scroll } from './scroll.vue';
|
@ -8,7 +8,7 @@ import { requireUniqueSymbol } from '../utils';
|
||||
import { MinimapDrawer, getArea } from '../tools/fly';
|
||||
import { useDrag, useWheel } from '../use';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
action?: boolean;
|
||||
@ -168,6 +168,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
const hook = Mota.require('var', 'hook');
|
||||
hook.off('afterChangeFloor', onChange);
|
||||
hook.off('afterBattle', afterBattle);
|
||||
});
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Component, shallowReactive } from 'vue';
|
||||
import { EventEmitter } from '@motajs/legacy-common';
|
||||
import { Hotkey } from './hotkey';
|
||||
|
||||
interface FocusEvent<T> {
|
||||
focus: (before: T | null, after: T) => void;
|
||||
@ -125,6 +124,7 @@ type UiVBind = Record<string, any>;
|
||||
interface MountedVBind {
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
controller: UiController;
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
@ -132,14 +132,12 @@ export class GameUi extends EventEmitter<GameUiEvent> {
|
||||
static uiList: GameUi[] = [];
|
||||
|
||||
component: Component;
|
||||
hotkey?: Hotkey;
|
||||
id: string;
|
||||
symbol: symbol = Symbol();
|
||||
|
||||
constructor(id: string, component: Component, hotkey?: Hotkey) {
|
||||
constructor(id: string, component: Component) {
|
||||
super();
|
||||
this.component = component;
|
||||
this.hotkey = hotkey;
|
||||
this.id = id;
|
||||
GameUi.uiList.push(this);
|
||||
}
|
||||
@ -273,6 +271,7 @@ export class UiController extends Focus<IndexedGameUi> {
|
||||
const bind = {
|
||||
num,
|
||||
ui,
|
||||
controller: this,
|
||||
...(vBind ?? {})
|
||||
};
|
||||
const sui = ui.with(bind, vOn);
|
@ -1,18 +1,10 @@
|
||||
import BoxAnimate from '@/components/boxAnimate.vue';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { logger } from '@motajs/common';
|
||||
import { ResponseBase } from '@/core/interface';
|
||||
import {
|
||||
deleteWith,
|
||||
ensureArray,
|
||||
getIconHeight,
|
||||
parseCss,
|
||||
tip
|
||||
} from '@motajs/legacy-ui';
|
||||
import { deleteWith, ensureArray, parseCss, tip } from '@motajs/legacy-ui';
|
||||
import { ResponseBase } from '@motajs/client-base';
|
||||
import axios, { AxiosResponse, toFormData } from 'axios';
|
||||
import { Component, VNode, h, shallowReactive } from 'vue';
|
||||
import { VNode, h, shallowReactive } from 'vue';
|
||||
// /* @__PURE__ */ import { id, password } from '../../../../user';
|
||||
import { mainSetting } from '../setting';
|
||||
|
||||
type CSSObj = Partial<Record<CanParseCss, string>>;
|
||||
|
||||
@ -213,8 +205,8 @@ export class Danmaku extends EventEmitter<DanmakuEvent> {
|
||||
* @param stroke 描边颜色
|
||||
*/
|
||||
color(fill?: string, stroke?: string) {
|
||||
fill && (this.textColor = fill);
|
||||
stroke && (this.strokeColor = stroke);
|
||||
if (fill) this.textColor = fill;
|
||||
if (stroke) this.strokeColor = stroke;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -492,41 +484,3 @@ export class Danmaku extends EventEmitter<DanmakuEvent> {
|
||||
this.specList[type] = fn;
|
||||
}
|
||||
}
|
||||
|
||||
// 图标类型
|
||||
Danmaku.registerSpecContent('i', content => {
|
||||
const height = getIconHeight(content as AllIds);
|
||||
|
||||
return h(BoxAnimate as Component, {
|
||||
id: content,
|
||||
noborder: true,
|
||||
noAnimate: true,
|
||||
width: 32,
|
||||
height
|
||||
});
|
||||
});
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
Danmaku.backend = `/danmaku`;
|
||||
}
|
||||
|
||||
Mota.require('var', 'hook').once('reset', () => {
|
||||
Danmaku.fetch();
|
||||
});
|
||||
|
||||
// 勇士移动后显示弹幕
|
||||
Mota.require('var', 'hook').on('moveOneStep', (x, y, floor) => {
|
||||
const enabled = mainSetting.getValue('ui.danmaku', true);
|
||||
if (!enabled) return;
|
||||
const f = Danmaku.allInPos[floor];
|
||||
if (f) {
|
||||
const danmaku = f[`${x},${y}`];
|
||||
if (danmaku) {
|
||||
danmaku.forEach(v => {
|
||||
setTimeout(() => {
|
||||
v.show();
|
||||
}, Math.random() * 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -1,7 +1,12 @@
|
||||
export * as UI from './ui';
|
||||
export * as Components from './components';
|
||||
export * from './preset';
|
||||
export * from './tools';
|
||||
|
||||
export * from './animateController';
|
||||
export * from './controller';
|
||||
export * from './danmaku';
|
||||
export * from './mark';
|
||||
export * from './setting';
|
||||
export * from './use';
|
||||
export * from './utils';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { fixedUi } from '@/core/main/init/ui';
|
||||
import { fixedUi } from './preset/ui';
|
||||
import type { DamageEnemy } from '@/game/enemy/damage';
|
||||
import { tip } from './utils';
|
||||
import { ref, Ref } from 'vue';
|
||||
|
43
packages/legacy-ui/src/preset/danmaku.ts
Normal file
43
packages/legacy-ui/src/preset/danmaku.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { Component, h } from 'vue';
|
||||
import { mainSetting } from './ui';
|
||||
import { getIconHeight } from '../utils';
|
||||
import { BoxAnimate } from '../components';
|
||||
|
||||
// 图标类型
|
||||
Danmaku.registerSpecContent('i', content => {
|
||||
const height = getIconHeight(content as AllIds);
|
||||
|
||||
return h(BoxAnimate as Component, {
|
||||
id: content,
|
||||
noborder: true,
|
||||
noAnimate: true,
|
||||
width: 32,
|
||||
height
|
||||
});
|
||||
});
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
Danmaku.backend = `/danmaku`;
|
||||
}
|
||||
|
||||
Mota.require('var', 'hook').once('reset', () => {
|
||||
Danmaku.fetch();
|
||||
});
|
||||
|
||||
// 勇士移动后显示弹幕
|
||||
Mota.require('var', 'hook').on('moveOneStep', (x, y, floor) => {
|
||||
const enabled = mainSetting.getValue('ui.danmaku', true);
|
||||
if (!enabled) return;
|
||||
const f = Danmaku.allInPos[floor];
|
||||
if (f) {
|
||||
const danmaku = f[`${x},${y}`];
|
||||
if (danmaku) {
|
||||
danmaku.forEach(v => {
|
||||
setTimeout(() => {
|
||||
v.show();
|
||||
}, Math.random() * 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -18,7 +18,7 @@ const showFixed = debounce((block: Block) => {
|
||||
if (!enemy) return;
|
||||
fixedUi.open(
|
||||
'fixed',
|
||||
{ enemy, close, loc: [cx, cy] },
|
||||
{ enemy, close, loc: [cx, cy], hovered },
|
||||
{ close: closeFixed }
|
||||
);
|
||||
}, 200);
|
@ -1,5 +1,5 @@
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { gameKey, HotkeyJSON } from '../custom/hotkey';
|
||||
import { gameKey, HotkeyJSON } from '@motajs/system-action';
|
||||
import {
|
||||
openDanmakuPoster,
|
||||
tip,
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from '@motajs/legacy-ui';
|
||||
import { hovered } from './fixed';
|
||||
import { mainUi } from './ui';
|
||||
import { GameStorage } from '../storage';
|
||||
import { GameStorage } from '@motajs/legacy-system';
|
||||
|
||||
export const mainScope = Symbol.for('@key_main');
|
||||
|
6
packages/legacy-ui/src/preset/index.ts
Normal file
6
packages/legacy-ui/src/preset/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './ui';
|
||||
export * from './settings';
|
||||
export * from './danmaku';
|
||||
export * from './fixed';
|
||||
export * from './hotkey';
|
||||
export * from './keyboard';
|
@ -1,5 +1,5 @@
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { Keyboard } from '../custom/keyboard';
|
||||
import { Keyboard } from '@motajs/system-action';
|
||||
|
||||
const qweKey = new Keyboard('qwe'); // 字母键盘,A-Z
|
||||
const numKey = new Keyboard('num'); // 数字键盘,1-0
|
||||
@ -239,7 +239,7 @@ numKey
|
||||
y: 0,
|
||||
width: 45,
|
||||
height: 45,
|
||||
text: `<span style='font-size: 80%'>\$<br />4</span>`
|
||||
text: `<span style='font-size: 80%'>$<br />4</span>`
|
||||
})
|
||||
.add({
|
||||
key: KeyCode.Digit5,
|
@ -1,7 +1,7 @@
|
||||
import type { SettingComponent, SettingComponentProps } from '../setting';
|
||||
import { Button, InputNumber, Radio } from 'ant-design-vue';
|
||||
import { mainUi } from './ui';
|
||||
import { gameKey } from '../custom/hotkey';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
|
||||
interface Components {
|
||||
Default: SettingComponent;
|
||||
@ -28,7 +28,7 @@ export function createSettingComponents() {
|
||||
return com;
|
||||
}
|
||||
|
||||
function DefaultSetting(props: SettingComponentProps) {
|
||||
function DefaultSetting(_props: SettingComponentProps) {
|
||||
return (
|
||||
<div>
|
||||
<span> 未知的设置类型 </span>
|
||||
@ -149,7 +149,7 @@ function showSpecialSetting(id: string, vBind?: any) {
|
||||
mainUi.open(id, vBind);
|
||||
}
|
||||
|
||||
function HotkeySetting(props: SettingComponentProps) {
|
||||
function HotkeySetting(_props: SettingComponentProps) {
|
||||
return (
|
||||
<div style="display: flex; justify-content: center">
|
||||
<Button
|
||||
@ -168,7 +168,7 @@ function HotkeySetting(props: SettingComponentProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function ToolbarEditor(props: SettingComponentProps) {
|
||||
function ToolbarEditor(_props: SettingComponentProps) {
|
||||
return (
|
||||
<div style="display: flex; justify-content: center">
|
||||
<Button
|
||||
@ -183,7 +183,7 @@ function ToolbarEditor(props: SettingComponentProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function PerformanceSetting(props: SettingComponentProps) {
|
||||
function PerformanceSetting(_props: SettingComponentProps) {
|
||||
return (
|
||||
<div style="display: flex; justify-content: center">
|
||||
<Button
|
364
packages/legacy-ui/src/preset/ui.ts
Normal file
364
packages/legacy-ui/src/preset/ui.ts
Normal file
@ -0,0 +1,364 @@
|
||||
import { GameStorage, VirtualKey } from '@motajs/legacy-system';
|
||||
import {
|
||||
createSettingComponents,
|
||||
GameUi,
|
||||
isMobile,
|
||||
MotaSetting,
|
||||
triggerFullscreen,
|
||||
UI,
|
||||
UiController
|
||||
} from '@motajs/legacy-ui';
|
||||
import { bgmController, soundPlayer } from '@/module';
|
||||
import settingsText from '@/data/settings.json';
|
||||
|
||||
//#region legacy-ui
|
||||
export const mainUi = new UiController();
|
||||
mainUi.register(
|
||||
new GameUi('book', UI.Book),
|
||||
new GameUi('toolbox', UI.Toolbox),
|
||||
new GameUi('equipbox', UI.Equipbox),
|
||||
new GameUi('settings', UI.Settings),
|
||||
new GameUi('desc', UI.Desc),
|
||||
new GameUi('skill', UI.Skill),
|
||||
new GameUi('skillTree', UI.SkillTree),
|
||||
new GameUi('fly', UI.Fly),
|
||||
new GameUi('fixedDetail', UI.FixedDetail),
|
||||
new GameUi('shop', UI.Shop),
|
||||
new GameUi('achievement', UI.Achievement),
|
||||
new GameUi('hotkey', UI.Hotkey),
|
||||
new GameUi('toolEditor', UI.ToolEditor),
|
||||
new GameUi('virtualKey', VirtualKey)
|
||||
// todo: 把游戏主 div 加入到 mainUi 里面
|
||||
);
|
||||
mainUi.showAll();
|
||||
|
||||
export const fixedUi = new UiController(true);
|
||||
fixedUi.register(
|
||||
new GameUi('markedEnemy', UI.Marked),
|
||||
new GameUi('fixed', UI.Fixed),
|
||||
new GameUi('chapter', UI.Chapter),
|
||||
new GameUi('completeAchi', UI.CompleteAchi),
|
||||
new GameUi('start', UI.Start),
|
||||
new GameUi('toolbar', UI.Toolbar),
|
||||
new GameUi('load', UI.Load),
|
||||
new GameUi('danmaku', UI.Danmaku),
|
||||
new GameUi('danmakuEditor', UI.DanmakuEditor),
|
||||
new GameUi('tips', UI.Tips)
|
||||
);
|
||||
fixedUi.showAll();
|
||||
|
||||
const hook = Mota.require('var', 'hook');
|
||||
hook.once('mounted', () => {
|
||||
const ui = document.getElementById('ui-main')!;
|
||||
const fixed = document.getElementById('ui-fixed')!;
|
||||
|
||||
const blur = mainSetting.getSetting('screen.blur');
|
||||
|
||||
mainUi.on('start', () => {
|
||||
ui.style.display = 'flex';
|
||||
if (blur?.value) {
|
||||
ui.style.backdropFilter = 'blur(5px)';
|
||||
ui.style.backgroundColor = 'rgba(0,0,0,0.7333)';
|
||||
} else {
|
||||
ui.style.backdropFilter = 'none';
|
||||
ui.style.backgroundColor = 'rgba(0,0,0,0.85)';
|
||||
}
|
||||
core.lockControl();
|
||||
});
|
||||
mainUi.on('end', noClosePanel => {
|
||||
ui.style.display = 'none';
|
||||
if (!noClosePanel) {
|
||||
core.closePanel();
|
||||
}
|
||||
});
|
||||
fixedUi.on('start', () => {
|
||||
fixed.style.display = 'block';
|
||||
});
|
||||
fixedUi.on('end', () => {
|
||||
fixed.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region legacy-setting
|
||||
|
||||
const COM = createSettingComponents();
|
||||
|
||||
export const mainSetting = new MotaSetting();
|
||||
// 添加不参与全局存储的设置
|
||||
MotaSetting.noStorage.push('action.autoSkill', 'screen.fullscreen');
|
||||
|
||||
const storage = new GameStorage(GameStorage.fromAuthor('AncTe', 'setting'));
|
||||
|
||||
export { storage as settingStorage };
|
||||
|
||||
// ----- 监听设置修改
|
||||
mainSetting.on('valueChange', (key, n, o) => {
|
||||
if (!MotaSetting.noStorage.includes(key)) {
|
||||
storage.setValue(key, n);
|
||||
}
|
||||
|
||||
const [root, setting] = key.split('.');
|
||||
|
||||
if (root === 'screen') {
|
||||
handleScreenSetting(setting, n, o);
|
||||
} else if (root === 'action') {
|
||||
handleActionSetting(setting, n, o);
|
||||
} else if (root === 'audio') {
|
||||
handleAudioSetting(setting, n, o);
|
||||
} else if (root === 'ui') {
|
||||
handleUiSetting(setting, n, o);
|
||||
}
|
||||
});
|
||||
|
||||
const root = document.getElementById('root') as HTMLDivElement;
|
||||
|
||||
function handleScreenSetting<T extends number | boolean>(
|
||||
key: string,
|
||||
n: T,
|
||||
_o: T
|
||||
) {
|
||||
if (key === 'fullscreen') {
|
||||
// 全屏
|
||||
triggerFullscreen(n as boolean);
|
||||
} else if (key === 'heroDetail') {
|
||||
// 勇士显伤
|
||||
core.drawHero();
|
||||
} else if (key === 'fontSize') {
|
||||
// 字体大小
|
||||
root.style.fontSize = `${n}px`;
|
||||
const absoluteSize = (n as number) * devicePixelRatio;
|
||||
storage.setValue('@@absoluteFontSize', absoluteSize);
|
||||
storage.write();
|
||||
} else if (key === 'fontSizeStatus') {
|
||||
// fontSize.value = n as number;
|
||||
}
|
||||
}
|
||||
|
||||
function handleActionSetting<T extends number | boolean>(
|
||||
key: string,
|
||||
n: T,
|
||||
_o: T
|
||||
) {
|
||||
if (key === 'autoSkill') {
|
||||
// 自动切换技能
|
||||
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
|
||||
HeroSkill.setAutoSkill(n as boolean);
|
||||
core.status.route.push(`set:autoSkill:${n}`);
|
||||
}
|
||||
}
|
||||
|
||||
function handleAudioSetting<T extends number | boolean>(
|
||||
key: string,
|
||||
n: T,
|
||||
_o: T
|
||||
) {
|
||||
if (key === 'bgmEnabled') {
|
||||
bgmController.setEnabled(n as boolean);
|
||||
core.checkBgm();
|
||||
} else if (key === 'bgmVolume') {
|
||||
bgmController.setVolume((n as number) / 100);
|
||||
} else if (key === 'soundEnabled') {
|
||||
soundPlayer.setEnabled(n as boolean);
|
||||
} else if (key === 'soundVolume') {
|
||||
soundPlayer.setVolume((n as number) / 100);
|
||||
}
|
||||
}
|
||||
|
||||
function handleUiSetting<T extends number | boolean>(key: string, n: T, _o: T) {
|
||||
if (key === 'danmaku') {
|
||||
if (n) {
|
||||
fixedUi.open('danmaku');
|
||||
} else {
|
||||
fixedUi.closeByName('danmaku');
|
||||
}
|
||||
} else if (key === 'tips') {
|
||||
if (n && core.isPlaying()) {
|
||||
fixedUi.open('tips');
|
||||
} else {
|
||||
fixedUi.closeByName('tips');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- 游戏的所有设置项
|
||||
// todo: 虚拟键盘缩放,小地图楼传缩放
|
||||
mainSetting
|
||||
.register(
|
||||
'screen',
|
||||
'显示设置',
|
||||
new MotaSetting()
|
||||
.register('fullscreen', '全屏游戏', false, COM.Boolean)
|
||||
.register('halo', '光环显示', true, COM.Boolean)
|
||||
.register('itemDetail', '宝石血瓶显伤', true, COM.Boolean)
|
||||
.register('heroDetail', '勇士显伤', false, COM.Boolean)
|
||||
.register('transition', '界面动画', false, COM.Boolean)
|
||||
.register('fontSize', '字体大小', 16, COM.Number, [2, 48, 1])
|
||||
.register(
|
||||
'fontSizeStatus',
|
||||
'状态栏字体',
|
||||
16,
|
||||
COM.Number,
|
||||
[10, 300, 10]
|
||||
)
|
||||
.register('smoothView', '平滑镜头', true, COM.Boolean)
|
||||
.register('criticalGem', '临界显示方式', false, COM.Boolean)
|
||||
.setDisplayFunc('criticalGem', value => (value ? '宝石数' : '攻击'))
|
||||
.register('keyScale', '虚拟键盘缩放', 100, COM.Number, [25, 5, 500])
|
||||
.register('blur', '背景虚化', !isMobile, COM.Boolean)
|
||||
)
|
||||
.register(
|
||||
'action',
|
||||
'操作设置',
|
||||
new MotaSetting()
|
||||
.register('autoSkill', '自动切换技能', true, COM.Boolean)
|
||||
.register('fixed', '定点查看', true, COM.Boolean)
|
||||
.register('hotkey', '快捷键', false, COM.HotkeySetting)
|
||||
.setDisplayFunc('hotkey', () => '')
|
||||
)
|
||||
.register(
|
||||
'audio',
|
||||
'音频设置',
|
||||
new MotaSetting()
|
||||
.register('bgmEnabled', '开启音乐', true, COM.Boolean)
|
||||
.register('bgmVolume', '音乐音量', 80, COM.Number, [0, 100, 5])
|
||||
.register('soundEnabled', '开启音效', true, COM.Boolean)
|
||||
.register('soundVolume', '音效音量', 80, COM.Number, [0, 100, 5])
|
||||
)
|
||||
.register(
|
||||
'utils',
|
||||
'系统设置',
|
||||
new MotaSetting()
|
||||
.register('betterLoad', '优化加载', true, COM.Boolean)
|
||||
.register('autoScale', '自动放缩', true, COM.Boolean)
|
||||
)
|
||||
.register(
|
||||
'fx',
|
||||
'特效设置',
|
||||
new MotaSetting()
|
||||
.register('paraLight', '野外阴影', true, COM.Boolean)
|
||||
.register('frag', '打怪特效', true, COM.Boolean)
|
||||
.register('portalParticle', '传送门特效', true, COM.Boolean)
|
||||
)
|
||||
.register(
|
||||
'ui',
|
||||
'ui设置',
|
||||
new MotaSetting()
|
||||
.register('mapScale', '小地图缩放', 100, COM.Number, [50, 1000, 50])
|
||||
.setDisplayFunc('mapScale', value => `${value}%`)
|
||||
.register('mapLazy', '小地图懒更新', false, COM.Boolean)
|
||||
.register(
|
||||
'bookScale',
|
||||
'怪物手册缩放',
|
||||
100,
|
||||
COM.Number,
|
||||
[10, 500, 10]
|
||||
)
|
||||
.setDisplayFunc('bookScale', value => `${value}%`)
|
||||
.register('danmaku', '显示弹幕', true, COM.Boolean)
|
||||
.register('danmakuSpeed', '弹幕速度', 60, COM.Number, [10, 1000, 5])
|
||||
.register('tips', '小贴士', true, COM.Boolean)
|
||||
);
|
||||
|
||||
const loading = Mota.require('var', 'loading');
|
||||
loading.once('coreInit', () => {
|
||||
mainSetting.reset({
|
||||
'screen.fullscreen': !!document.fullscreenElement,
|
||||
'screen.halo': !!storage.getValue('screen.showHalo', true),
|
||||
'screen.itemDetail': !!storage.getValue('screen.itemDetail', true),
|
||||
'screen.heroDetail': !!storage.getValue('screen.heroDetail', false),
|
||||
'screen.transition': !!storage.getValue('screen.transition', false),
|
||||
'screen.fontSize': storage.getValue(
|
||||
'screen.fontSize',
|
||||
isMobile ? 9 : 16
|
||||
),
|
||||
'screen.smoothView': !!storage.getValue('screen.smoothView', true),
|
||||
'screen.criticalGem': !!storage.getValue('screen.criticalGem', false),
|
||||
'screen.fontSizeStatus': storage.getValue('screen.fontSizeStatus', 100),
|
||||
'action.fixed': !!storage.getValue('action.fixed', true),
|
||||
'audio.bgmEnabled': !!storage.getValue('audio.bgmEnabled', true),
|
||||
'audio.bgmVolume': storage.getValue('audio.bgmVolume', 80),
|
||||
'audio.soundEnabled': !!storage.getValue('audio.soundEnabled', true),
|
||||
'audio.soundVolume': storage.getValue('audio.soundVolume', 80),
|
||||
'utils.betterLoad': !!storage.getValue('utils.betterLoad', true),
|
||||
'utils.autoScale': !!storage.getValue('utils.autoScale', true),
|
||||
'fx.paraLight': !!storage.getValue('fx.paraLight', true),
|
||||
'fx.frag': !!storage.getValue('fx.frag', true),
|
||||
'fx.portalParticle': !!storage.getValue('fx.portalParticle', true),
|
||||
'ui.mapScale': storage.getValue(
|
||||
'ui.mapScale',
|
||||
isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50
|
||||
),
|
||||
'ui.mapLazy': storage.getValue('ui.mapLazy', false),
|
||||
'ui.toolbarScale': storage.getValue(
|
||||
'ui.toolbarScale',
|
||||
isMobile ? 50 : Math.floor((window.innerWidth / 1700) * 10) * 10
|
||||
),
|
||||
'ui.bookScale': storage.getValue('ui.bookScale', isMobile ? 100 : 80),
|
||||
'ui.danmaku': storage.getValue('ui.danmaku', true),
|
||||
'ui.danmakuSpeed': storage.getValue(
|
||||
'ui.danmakuSpeed',
|
||||
Math.floor(window.innerWidth / 30) * 5
|
||||
),
|
||||
'ui.tips': storage.getValue('ui.tips', true)
|
||||
});
|
||||
});
|
||||
|
||||
interface SettingTextData {
|
||||
[x: string]: string[] | SettingTextData;
|
||||
}
|
||||
|
||||
function getSettingText(obj: SettingTextData, key?: string) {
|
||||
for (const [k, value] of Object.entries(obj)) {
|
||||
const setKey = key ? key + '.' + k : k;
|
||||
if (value instanceof Array) {
|
||||
mainSetting.setDescription(setKey, value.join('\n'));
|
||||
} else {
|
||||
getSettingText(value, setKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
getSettingText(settingsText);
|
||||
|
||||
mainSetting
|
||||
.setDescription('audio.bgmEnabled', `是否开启背景音乐`)
|
||||
.setDescription('audio.bgmVolume', `背景音乐的音量`)
|
||||
.setDescription('audio.soundEnabled', `是否开启音效`)
|
||||
.setDescription('audio.soundVolume', `音效的音量`)
|
||||
.setDescription('ui.mapScale', `楼传小地图的缩放,百分比格式`)
|
||||
.setDescription(
|
||||
'ui.mapLazy',
|
||||
`是否启用小地图懒更新模式,此模式下剩余怪物数量不会实时更新而变成切换地图后更新,打开小地图时出现卡顿可以尝试开启此设置`
|
||||
)
|
||||
.setDescription('ui.toolbarScale', `自定义工具栏的缩放比例`)
|
||||
.setDescription(
|
||||
'ui.bookScale',
|
||||
`怪物手册界面中每个怪物框体的高度缩放,最小值限定为 20% 屏幕高度`
|
||||
)
|
||||
.setDescription('ui.danmaku', '是否显示弹幕')
|
||||
.setDescription('ui.danmakuSpeed', '弹幕速度,刷新或开关弹幕显示后起效')
|
||||
.setDescription('ui.tips', `是否在游戏画面右上角常亮显示小贴士`)
|
||||
.setDescription('screen.fontSizeStatus', `修改状态栏的字体大小`)
|
||||
.setDescription(
|
||||
'screen.blur',
|
||||
'打开任意ui界面时是否有背景虚化效果,移动端打开后可能会有掉帧或者发热现象。关闭ui后生效'
|
||||
)
|
||||
.setDescription(
|
||||
'fx.portalParticle',
|
||||
'是否启用苍蓝之殿的传送门粒子特效,启用后可能对性能及设备发热有所影响'
|
||||
);
|
||||
|
||||
function setFontSize() {
|
||||
const absoluteSize = storage.getValue(
|
||||
'@@absoluteFontSize',
|
||||
16 * devicePixelRatio
|
||||
);
|
||||
const size = Math.round(absoluteSize / devicePixelRatio);
|
||||
mainSetting.setValue('screen.fontSize', size);
|
||||
}
|
||||
setFontSize();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
setFontSize();
|
||||
});
|
308
packages/legacy-ui/src/setting.ts
Normal file
308
packages/legacy-ui/src/setting.ts
Normal file
@ -0,0 +1,308 @@
|
||||
import { FunctionalComponent, reactive } from 'vue';
|
||||
import { EventEmitter } from '@motajs/legacy-common';
|
||||
import { has } from './utils';
|
||||
import { createSettingComponents } from './preset';
|
||||
|
||||
const COM = createSettingComponents();
|
||||
|
||||
export interface SettingComponentProps {
|
||||
item: MotaSettingItem;
|
||||
setting: MotaSetting;
|
||||
displayer: SettingDisplayer;
|
||||
}
|
||||
|
||||
export type SettingComponent = FunctionalComponent<SettingComponentProps>;
|
||||
type MotaSettingType = boolean | number | MotaSetting;
|
||||
|
||||
export interface MotaSettingItem<T extends MotaSettingType = MotaSettingType> {
|
||||
name: string;
|
||||
key: string;
|
||||
value: T;
|
||||
controller: SettingComponent;
|
||||
description?: string;
|
||||
defaults?: boolean | number;
|
||||
step?: [number, number, number];
|
||||
display?: (value: T) => string;
|
||||
}
|
||||
|
||||
interface SettingEvent {
|
||||
valueChange: <T extends boolean | number>(
|
||||
key: string,
|
||||
newValue: T,
|
||||
oldValue: T
|
||||
) => void;
|
||||
}
|
||||
|
||||
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> {
|
||||
static noStorage: string[] = [];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个数字型设置
|
||||
* @param key 设置的键名
|
||||
* @param value 设置的值
|
||||
*/
|
||||
register(
|
||||
key: string,
|
||||
name: string,
|
||||
value: number,
|
||||
com?: SettingComponent,
|
||||
step?: [number, number, number]
|
||||
): this;
|
||||
/**
|
||||
* 注册一个非数字型设置
|
||||
* @param key 设置的键名
|
||||
* @param value 设置的值
|
||||
*/
|
||||
register(
|
||||
key: string,
|
||||
name: string,
|
||||
value: boolean | MotaSetting,
|
||||
com?: SettingComponent
|
||||
): this;
|
||||
register(
|
||||
key: string,
|
||||
name: string,
|
||||
value: MotaSettingType,
|
||||
com: SettingComponent = COM.Default,
|
||||
step: [number, number, number] = [0, 100, 1]
|
||||
) {
|
||||
const setting: MotaSettingItem = {
|
||||
name,
|
||||
value,
|
||||
key,
|
||||
controller: com
|
||||
};
|
||||
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}`
|
||||
);
|
||||
}
|
||||
const old = setting.value as boolean | number;
|
||||
setting.value = value;
|
||||
|
||||
this.emit('valueChange', key, value, old);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一个设置的值
|
||||
* @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}`
|
||||
);
|
||||
}
|
||||
const old = setting.value as boolean | number;
|
||||
setting.value += value;
|
||||
this.emit('valueChange', key, old, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个设置的值,如果获取到的是一个MotaSetting实例,那么返回undefined
|
||||
* @param key 要获取的设置
|
||||
*/
|
||||
getValue(key: string): boolean | number | undefined;
|
||||
/**
|
||||
* 获取一个设置的值,如果获取到的是一个MotaSetting实例,那么返回defaultValue
|
||||
* @param key 要获取的设置
|
||||
* @param defaultValue 设置的默认值
|
||||
*/
|
||||
getValue<T extends boolean | number>(key: string, defaultValue: T): T;
|
||||
getValue<T extends boolean | number>(
|
||||
key: string,
|
||||
defaultValue?: T
|
||||
): T | undefined {
|
||||
const setting = this.getSetting(key);
|
||||
if (!has(setting) && !has(defaultValue)) return void 0;
|
||||
if (setting instanceof MotaSetting) {
|
||||
if (has(setting)) return defaultValue;
|
||||
return void 0;
|
||||
} else {
|
||||
return has(setting) ? (setting.value as T) : (defaultValue as T);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个设置的值显示函数
|
||||
* @param key 要设置的设置的键
|
||||
* @param func 显示函数
|
||||
*/
|
||||
setDisplayFunc(key: string, func: (value: MotaSettingType) => string) {
|
||||
const setting = this.getSettingBy(key.split('.'));
|
||||
setting.display = func;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个设置的修改部分组件
|
||||
* @param key 要设置的设置的键
|
||||
* @param com 设置修改部分的组件
|
||||
*/
|
||||
setValueController(key: string, com: SettingComponent) {
|
||||
const setting = this.getSettingBy(key.split('.'));
|
||||
setting.controller = com;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个设置的说明
|
||||
* @param key 要设置的设置的id
|
||||
* @param desc 设置的说明
|
||||
*/
|
||||
setDescription(key: string, desc: string) {
|
||||
const setting = this.getSettingBy(key.split('.'));
|
||||
setting.description = desc;
|
||||
return this;
|
||||
}
|
||||
|
||||
private getSettingBy(list: string[]) {
|
||||
let now: MotaSetting = this;
|
||||
|
||||
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]}'`
|
||||
);
|
||||
}
|
||||
now = item;
|
||||
}
|
||||
|
||||
return now.list[list.at(-1)!] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
interface SettingDisplayerEvent {
|
||||
update: (stack: string[], display: SettingDisplayInfo[]) => void;
|
||||
}
|
||||
|
||||
export class SettingDisplayer extends EventEmitter<SettingDisplayerEvent> {
|
||||
setting: MotaSetting;
|
||||
/** 选项选中栈 */
|
||||
selectStack: string[] = [];
|
||||
displayInfo: SettingDisplayInfo[] = reactive([]);
|
||||
|
||||
constructor(setting: MotaSetting) {
|
||||
super();
|
||||
this.setting = setting;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
const last = now.list[list.at(-1)!];
|
||||
if (last) {
|
||||
const desc = last.description;
|
||||
const text = desc ? desc.split('\n') : ['请选择设置'];
|
||||
|
||||
this.displayInfo.push({
|
||||
item: last,
|
||||
text,
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { downloadCanvasImage, has, tip } from '../utils';
|
||||
|
||||
type BFSFromString = `${FloorIds},${number},${number},${Dir}`;
|
||||
|
@ -46,10 +46,10 @@ import BookDetail from './bookDetail.vue';
|
||||
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { ToShowEnemy, detailInfo } from '../tools/book';
|
||||
import { getDetailedEnemy } from '../tools/fixed';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { isMobile } from '../use';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -83,7 +83,7 @@ import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
|
||||
import EnemyCritical from '../panel/enemyCritical.vue';
|
||||
import EnemyTarget from '../panel/enemyTarget.vue';
|
||||
import { detailInfo } from '../tools/book';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
|
||||
const props = defineProps<{
|
||||
fromBook?: boolean;
|
||||
|
@ -9,8 +9,8 @@
|
||||
import { Animation, hyper, sleep } from 'mutate-animate';
|
||||
import { onMounted } from 'vue';
|
||||
import { has } from '../utils';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { fixedUi } from '@/core/main/init/ui';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -23,8 +23,8 @@ import { computed, onMounted, ref } from 'vue';
|
||||
import Box from '../components/box.vue';
|
||||
import list from '../data/achievement.json';
|
||||
import { AchievementType, getNowPoint, totalPoint } from '../tools/achievement';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { fixedUi } from '@/core/main/init/ui';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
|
||||
const height = window.innerHeight;
|
||||
|
||||
|
@ -28,9 +28,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onUnmounted, reactive, watch } from 'vue';
|
||||
import { Danmaku } from '@/core/main/custom/danmaku';
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { LikeFilled } from '@ant-design/icons-vue';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
interface ElementMap {
|
||||
|
@ -158,17 +158,17 @@ import {
|
||||
SendOutlined,
|
||||
UpOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { Danmaku } from '@/core/main/custom/danmaku';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { Danmaku } from '../danmaku';
|
||||
import { GameUi } from '../controller';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { fixedUi } from '@/core/main/init/ui';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
import { calStringSize, tip } from '../utils';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { stringifyCSS, parseCss, getIconHeight } from '../utils';
|
||||
import { logger, LogLevel } from '@motajs/common';
|
||||
import Scroll from '@/components/scroll.vue';
|
||||
import BoxAnimate from '@/components/boxAnimate.vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -22,9 +22,9 @@ import { computed, onUnmounted, ref } from 'vue';
|
||||
import desc from '../data/desc.json';
|
||||
import { splitText } from '../utils';
|
||||
import Colomn from '../components/colomn.vue';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -186,10 +186,10 @@ import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { has, tip, type } from '../utils';
|
||||
import { cancelGlobalDrag, isMobile, useDrag } from '../use';
|
||||
import { hyper } from 'mutate-animate';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { getStatusLabel } from '../utils';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -31,7 +31,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUpdated, Ref, ref, watch } from 'vue';
|
||||
import Box from '../components/box.vue';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { GameUi } from '../controller';
|
||||
import type { DamageEnemy, EnemyInfo } from '@/game/enemy/damage';
|
||||
import { nextFrame } from '../utils';
|
||||
|
||||
|
@ -12,9 +12,9 @@
|
||||
import { getDetailedEnemy } from '../tools/fixed';
|
||||
import BookDetail from './bookDetail.vue';
|
||||
import { detailInfo } from '../tools/book';
|
||||
import { hovered } from '@/core/main/init/fixed';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { hovered } from '../preset/fixed';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -99,12 +99,12 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { tip } from '../utils';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { createChangable } from '../tools/common';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { GameStorage } from '@/core/main/storage';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { GameStorage } from '@motajs/legacy-system';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -36,15 +36,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Hotkey } from '@/core/main/custom/hotkey';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { Hotkey } from '@motajs/system-action';
|
||||
import { GameUi } from '../controller';
|
||||
import Column from '../components/colomn.vue';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { KeyCode, KeyCodeUtils } from '@motajs/client-base';
|
||||
import { generateBinary, keycode } from '../utils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
|
||||
interface HotkeyKeys {
|
||||
index: number;
|
||||
|
@ -31,10 +31,10 @@ import {
|
||||
loadDefaultResource,
|
||||
LoadTask
|
||||
} from '@motajs/legacy-common';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { GameUi } from '../controller';
|
||||
import { formatSize } from '../utils';
|
||||
import { logger } from '@motajs/common';
|
||||
import { fixedUi } from '@/core/main/init/ui';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
import { sleep } from 'mutate-animate';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -56,8 +56,8 @@ import { MarkInfo, unmarkEnemy } from '../mark';
|
||||
import Box from '../components/box.vue';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { fixedUi } from '@/core/main/init/ui';
|
||||
import { GameUi } from '../controller';
|
||||
import { fixedUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -73,20 +73,20 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onUnmounted, ref, shallowRef } from 'vue';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import {
|
||||
mainSetting,
|
||||
MotaSetting,
|
||||
MotaSettingItem,
|
||||
SettingDisplayer,
|
||||
SettingDisplayInfo
|
||||
} from '@/core/main/setting';
|
||||
} from '../setting';
|
||||
import { RightOutlined, LeftOutlined } from '@ant-design/icons-vue';
|
||||
import { splitText } from '../utils';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { isMobile } from '../use';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
info?: MotaSetting;
|
||||
|
@ -172,9 +172,9 @@ import {
|
||||
import { splitText, tip } from '../utils';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import BoxAnimate from '../components/boxAnimate.vue';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -21,7 +21,7 @@ import { computed, ref } from 'vue';
|
||||
import skills from '../data/skill.json';
|
||||
import { has } from '../utils';
|
||||
import Column from '../components/colomn.vue';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -84,9 +84,9 @@ import Scroll from '../components/scroll.vue';
|
||||
import { has, splitText, tip } from '../utils';
|
||||
import { isMobile } from '../use';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { GameUi } from '../controller';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import type { Chapter } from '@/plugin/game/skillTree';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -67,11 +67,10 @@ import { sleep } from 'mutate-animate';
|
||||
import { doByInterval } from '../utils';
|
||||
import { triggerFullscreen } from '../utils';
|
||||
import { isMobile } from '../use';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { CustomToolbar } from '@/core/main/custom/toolbar';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
import { mainSetting } from '../preset/ui';
|
||||
import { mat4 } from 'gl-matrix';
|
||||
// todo: 改了
|
||||
import { bgmController } from '@/module';
|
||||
@ -323,7 +322,6 @@ onMounted(async () => {
|
||||
main = document.getElementById('start-main') as HTMLDivElement;
|
||||
start = document.getElementById('start') as HTMLDivElement;
|
||||
background = document.getElementById('background') as HTMLImageElement;
|
||||
CustomToolbar.closeAll();
|
||||
|
||||
window.addEventListener('resize', resize);
|
||||
resize();
|
||||
|
@ -1,586 +0,0 @@
|
||||
<template>
|
||||
<div id="tools">
|
||||
<span class="button-text" @click="exit"><left-outlined /> 返回</span>
|
||||
</div>
|
||||
<div id="tool-editor">
|
||||
<div id="tool-left">
|
||||
<Scroll class="tool-list-scroll">
|
||||
<div id="tool-list">
|
||||
<div
|
||||
v-for="(item, i) of list"
|
||||
class="tool-list-item selectable"
|
||||
:selected="i === selected"
|
||||
@click="selected = i"
|
||||
>
|
||||
<span>{{ item.id }}</span>
|
||||
<a-button
|
||||
type="danger"
|
||||
class="tool-list-delete"
|
||||
@click.stop="deleteTool(item.id)"
|
||||
>删除</a-button
|
||||
>
|
||||
</div>
|
||||
<div id="tool-list-add">
|
||||
<div
|
||||
id="tool-add-div"
|
||||
@click="addingTool = true"
|
||||
v-if="!addingTool"
|
||||
>
|
||||
<PlusOutlined></PlusOutlined>
|
||||
<span>新增工具栏</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-input
|
||||
style="
|
||||
height: 100%;
|
||||
font-size: 80%;
|
||||
width: 100%;
|
||||
"
|
||||
v-model:value="addingToolId"
|
||||
@blur="addTool"
|
||||
></a-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Scroll>
|
||||
<a-divider
|
||||
type="vertical"
|
||||
dashed
|
||||
v-if="isMobile"
|
||||
style="height: 100%; border-color: #ddd4"
|
||||
></a-divider>
|
||||
<div id="tool-preview" v-if="!!bar && isMobile">
|
||||
<div id="tool-preview-container">
|
||||
<div class="tool-preview-item" v-for="item of bar.items">
|
||||
<component
|
||||
:is="CustomToolbar.info[item.type].show as any"
|
||||
:item="item"
|
||||
:toolbar="bar"
|
||||
></component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider
|
||||
class="divider"
|
||||
dashed
|
||||
style="border-color: #ddd4"
|
||||
:type="isMobile ? 'horizontal' : 'vertical'"
|
||||
></a-divider>
|
||||
<div id="tool-info">
|
||||
<div id="tool-detail" v-if="!!bar">
|
||||
<Scroll class="tool-item-list-scroll">
|
||||
<div class="tool-item-list">
|
||||
<div
|
||||
class="tool-item-list-item"
|
||||
v-for="item of bar.items"
|
||||
>
|
||||
<div
|
||||
class="tool-item-header"
|
||||
:folded="!unfolded[item.id]"
|
||||
@click="triggerFold(item.id)"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="tool-fold"
|
||||
:folded="!unfolded[item.id]"
|
||||
>
|
||||
<RightOutlined></RightOutlined>
|
||||
</span>
|
||||
<span class="tool-name">
|
||||
<span
|
||||
v-if="editId !== item.id"
|
||||
style="cursor: text"
|
||||
@click.stop="editName(item.id)"
|
||||
>
|
||||
{{ item.id }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<a-input
|
||||
@blur="editNameSuccess(item.id)"
|
||||
@click.stop=""
|
||||
v-model:value="editValue"
|
||||
class="tool-name-edit"
|
||||
></a-input>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<a-button
|
||||
type="danger"
|
||||
class="tool-item-delete"
|
||||
@click.stop="deleteItem(item)"
|
||||
>删除</a-button
|
||||
>
|
||||
</div>
|
||||
<div v-if="!!unfolded[item.id]">
|
||||
<component
|
||||
:is="CustomToolbar.info[item.type].editor"
|
||||
:item="item"
|
||||
:toolbar="bar"
|
||||
></component>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tool-item-add">
|
||||
<div
|
||||
id="tool-item-add-div"
|
||||
v-if="!addingItem"
|
||||
@click="addingItem = true"
|
||||
>
|
||||
<PlusOutlined></PlusOutlined>
|
||||
<span>新增工具</span>
|
||||
</div>
|
||||
<div id="tool-item-add-type" v-else>
|
||||
<span>工具类型</span>
|
||||
<a-select
|
||||
v-model:value="addingType"
|
||||
style="
|
||||
width: 120px;
|
||||
height: 100%;
|
||||
font-size: 80%;
|
||||
background-color: #222;
|
||||
"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(
|
||||
info, type
|
||||
) of CustomToolbar.info"
|
||||
:value="type"
|
||||
>{{ info.name }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
<a-button
|
||||
type="primary"
|
||||
style="font-size: 80%; height: 100%"
|
||||
@click="addItem(true)"
|
||||
>确定</a-button
|
||||
>
|
||||
<a-button
|
||||
@click="addItem(false)"
|
||||
style="
|
||||
background-color: #222;
|
||||
font-size: 80%;
|
||||
height: 100%;
|
||||
"
|
||||
>取消</a-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Scroll>
|
||||
</div>
|
||||
<a-divider dashed v-if="!isMobile"></a-divider>
|
||||
<div id="tool-preview" v-if="!!bar && !isMobile">
|
||||
<div id="tool-preview-container">
|
||||
<div class="tool-preview-item" v-for="item of bar.items">
|
||||
<component
|
||||
:is="CustomToolbar.info[item.type].show as any"
|
||||
:item="item"
|
||||
:toolbar="bar"
|
||||
></component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CustomToolbar } from '@/core/main/custom/toolbar';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { computed, onUnmounted, reactive, ref } from 'vue';
|
||||
import {
|
||||
PlusOutlined,
|
||||
RightOutlined,
|
||||
LeftOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { isMobile } from '../use';
|
||||
import Scroll from '../components/scroll.vue';
|
||||
import { deleteWith, tip } from '../utils';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { ToolbarItemType } from '@/core/main/init/toolbar';
|
||||
|
||||
const props = defineProps<{
|
||||
ui: GameUi;
|
||||
num: number;
|
||||
}>();
|
||||
|
||||
const scale = mainSetting.getValue('ui.toolbarScale', 100) / 100;
|
||||
|
||||
const list = CustomToolbar.list;
|
||||
|
||||
const selected = ref(0);
|
||||
const bar = computed(() => list[selected.value]);
|
||||
|
||||
const unfolded = reactive<Record<string, boolean>>({});
|
||||
const editId = ref<string>();
|
||||
const editValue = ref<string>();
|
||||
|
||||
// 添加自定义工具
|
||||
const addingItem = ref(false);
|
||||
const addingType = ref<ToolbarItemType>('item');
|
||||
|
||||
// 添加自定义工具栏
|
||||
const addingTool = ref(false);
|
||||
const addingToolId = ref('');
|
||||
|
||||
/**
|
||||
* 编辑名称
|
||||
*/
|
||||
function editName(id: string) {
|
||||
editId.value = id;
|
||||
editValue.value = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑名称完成
|
||||
*/
|
||||
function editNameSuccess(id: string) {
|
||||
if (bar.value.items.some(v => v.id === editId.value)) {
|
||||
tip('error', '名称重复!');
|
||||
} else {
|
||||
const item = bar.value.items.find(v => v.id === id)!;
|
||||
item.id = editValue.value!;
|
||||
}
|
||||
editId.value = void 0;
|
||||
editValue.value = void 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除自定义工具
|
||||
*/
|
||||
function deleteItem(item: any) {
|
||||
Modal.confirm({
|
||||
title: '确定要删除这个自定义工具吗?',
|
||||
onOk() {
|
||||
deleteWith(bar.value.items, item);
|
||||
},
|
||||
onCancel() {}
|
||||
});
|
||||
}
|
||||
|
||||
function addItem(add: boolean) {
|
||||
if (add) {
|
||||
bar.value.add(
|
||||
// @ts-ignore
|
||||
CustomToolbar.info[addingType.value].onCreate({
|
||||
id: `tool-item-${bar.value.items.length}`,
|
||||
type: addingType.value
|
||||
})
|
||||
);
|
||||
}
|
||||
addingItem.value = false;
|
||||
addingType.value = 'item';
|
||||
}
|
||||
|
||||
/**
|
||||
* 更改折叠
|
||||
*/
|
||||
function triggerFold(id: string) {
|
||||
unfolded[id] = !unfolded[id];
|
||||
}
|
||||
|
||||
function exit() {
|
||||
mainUi.close(props.num);
|
||||
}
|
||||
|
||||
function deleteTool(id: string) {
|
||||
Modal.confirm({
|
||||
title: '确定要删除这个自定义工具栏吗?',
|
||||
onOk() {
|
||||
const index = CustomToolbar.list.findIndex(v => v.id === id);
|
||||
if (index !== -1) {
|
||||
CustomToolbar.list[index].closeAll();
|
||||
CustomToolbar.list.splice(index, 1);
|
||||
}
|
||||
selected.value = 0;
|
||||
},
|
||||
onCancel() {}
|
||||
});
|
||||
}
|
||||
|
||||
function addTool() {
|
||||
if (addingToolId.value === '') {
|
||||
addingToolId.value = '';
|
||||
addingTool.value = false;
|
||||
return;
|
||||
}
|
||||
if (CustomToolbar.list.some(v => v.id === addingToolId.value)) {
|
||||
tip('error', '工具栏名称重复!');
|
||||
return;
|
||||
} else {
|
||||
const bar = new CustomToolbar(addingToolId.value);
|
||||
}
|
||||
addingToolId.value = '';
|
||||
addingTool.value = false;
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
CustomToolbar.save();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#tools {
|
||||
width: 100%;
|
||||
font-family: 'normal';
|
||||
font-size: 3.2vh;
|
||||
height: 5vh;
|
||||
position: fixed;
|
||||
left: 10vw;
|
||||
top: 5vh;
|
||||
}
|
||||
|
||||
#tool-editor {
|
||||
width: 70%;
|
||||
height: 70%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: 'normal';
|
||||
font-size: 150%;
|
||||
user-select: none;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.tool-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tool-list-scroll {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#tool-left {
|
||||
flex-basis: 30%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#tool-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#tool-list-add {
|
||||
margin-top: 5%;
|
||||
padding: 1% 3% 1% 3%;
|
||||
|
||||
#tool-add-div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
padding: 1% 4%;
|
||||
transition: background-color linear 0.1s;
|
||||
border-radius: 5px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#tool-add-div:hover {
|
||||
background-color: rgba(39, 251, 209, 0.316);
|
||||
}
|
||||
|
||||
#tool-add-div:active {
|
||||
background-color: rgba(39, 251, 209, 0.202);
|
||||
}
|
||||
}
|
||||
|
||||
.tool-list-delete {
|
||||
font-size: 80%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#tool-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
#tool-detail {
|
||||
height: 60%;
|
||||
|
||||
.tool-item-header {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-item-header[folded='false'] {
|
||||
border-bottom: 0.5px solid #888;
|
||||
}
|
||||
|
||||
.tool-item-list-scroll {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-item-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tool-item-list-item {
|
||||
border: 1px solid #ddd8;
|
||||
margin-bottom: 3%;
|
||||
background-color: #222;
|
||||
padding-left: 2%;
|
||||
}
|
||||
|
||||
.tool-fold {
|
||||
::v-deep(span) {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.tool-fold[folded='false'] {
|
||||
::v-deep(span) {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.tool-name {
|
||||
margin-left: 3%;
|
||||
width: 40%;
|
||||
display: flex;
|
||||
|
||||
.tool-name-edit {
|
||||
width: 100%;
|
||||
font-size: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.tool-item-delete {
|
||||
height: 100%;
|
||||
justify-self: end;
|
||||
font-size: 80%;
|
||||
padding: 2px 15px;
|
||||
}
|
||||
|
||||
#tool-item-add-div {
|
||||
padding: 1% 3% 1% 3%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
padding: 1% 4%;
|
||||
transition: background-color linear 0.1s;
|
||||
border-radius: 5px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#tool-item-add-div:hover {
|
||||
background-color: rgba(39, 251, 209, 0.316);
|
||||
}
|
||||
|
||||
#tool-item-add-div:active {
|
||||
background-color: rgba(39, 251, 209, 0.202);
|
||||
}
|
||||
|
||||
#tool-item-add-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
padding: 1% 4%;
|
||||
justify-content: space-between;
|
||||
border-radius: 5px;
|
||||
background-color: rgba(39, 251, 209, 0.316);
|
||||
}
|
||||
}
|
||||
|
||||
#tool-preview {
|
||||
height: 40%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
|
||||
#tool-preview-container {
|
||||
width: 90%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border: 2px solid #ddd9;
|
||||
background-color: #0009;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tool-preview-item {
|
||||
display: flex;
|
||||
margin: v-bind('5 * scale + "px"');
|
||||
min-width: v-bind('50 * scale + "px"');
|
||||
min-height: v-bind('50 * scale + "px"');
|
||||
background-color: #222;
|
||||
border: 1.5px solid #ddd8;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.1s linear;
|
||||
|
||||
::v-deep(*) {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
#tool-editor {
|
||||
padding-top: 15%;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#tool-left {
|
||||
width: 100%;
|
||||
flex-basis: 40%;
|
||||
max-height: 40vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.tool-list-scroll {
|
||||
height: 100%;
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
#tool-preview {
|
||||
flex-basis: 50%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#tool-info {
|
||||
width: 100%;
|
||||
flex-basis: 60%;
|
||||
|
||||
#tool-detail {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,138 +0,0 @@
|
||||
<template>
|
||||
<Box
|
||||
class="toolbar-container"
|
||||
:dragable="true"
|
||||
:resizable="true"
|
||||
v-model:left="box.x"
|
||||
v-model:top="box.y"
|
||||
v-model:height="box.height"
|
||||
v-model:width="box.width"
|
||||
:key="num"
|
||||
>
|
||||
<div class="toolbar">
|
||||
<div
|
||||
class="toolbar-item"
|
||||
v-for="item of bar.items"
|
||||
@click.stop="click"
|
||||
:noaction="!!item.noDefaultAction"
|
||||
>
|
||||
<component
|
||||
:is="CustomToolbar.info[item.type].show as any"
|
||||
:item="item"
|
||||
:toolbar="bar"
|
||||
></component>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Box from '../components/box.vue';
|
||||
import { CustomToolbar } from '@/core/main/custom/toolbar';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { onUnmounted, reactive, watch } from 'vue';
|
||||
|
||||
interface BoxData {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
ui: GameUi;
|
||||
bar: CustomToolbar;
|
||||
}>();
|
||||
|
||||
const bar = props.bar;
|
||||
const scale = mainSetting.getValue('ui.toolbarScale', 100) / 100;
|
||||
|
||||
const box = reactive<BoxData>({
|
||||
x: bar.x,
|
||||
y: bar.y,
|
||||
width: bar.width,
|
||||
height: bar.height
|
||||
});
|
||||
|
||||
watch(box, ({ x, y, width, height }) => {
|
||||
bar.x = x;
|
||||
bar.y = y;
|
||||
bar.width = width;
|
||||
bar.height = height;
|
||||
});
|
||||
|
||||
function click() {
|
||||
// pass
|
||||
}
|
||||
|
||||
function posChange() {
|
||||
box.x = bar.x;
|
||||
box.y = bar.y;
|
||||
box.width = bar.width;
|
||||
box.height = bar.height;
|
||||
}
|
||||
|
||||
bar.on('posChange', posChange);
|
||||
|
||||
onUnmounted(() => {
|
||||
bar.off('posChange', posChange);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.toolbar-container {
|
||||
background-color: #0009;
|
||||
padding: v-bind('scale * 5 + "px"');
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.toolbar-item {
|
||||
display: flex;
|
||||
margin: v-bind('scale * 5 + "px"');
|
||||
min-width: v-bind('scale * 50 + "px"');
|
||||
min-height: v-bind('scale * 50 + "px"');
|
||||
background-color: #222;
|
||||
border: 1.5px solid #ddd8;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.1s linear;
|
||||
}
|
||||
|
||||
.toolbar-item[noaction='false'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toolbar-item::v-deep(> *) {
|
||||
height: 100%;
|
||||
min-width: v-bind('scale * 50 + "px"');
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
text-overflow: clip;
|
||||
text-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toolbar-item::v-deep(.button-text)[active='true'] {
|
||||
color: gold;
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.toolbar-item:hover[noaction='false'] {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
.toolbar-item:active[noaction='false'] {
|
||||
background-color: #333;
|
||||
}
|
||||
</style>
|
@ -116,9 +116,9 @@ import { isMobile } from '../use';
|
||||
import { type, has } from '../utils';
|
||||
import { hyper } from 'mutate-animate';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { GameUi } from '@/core/main/custom/ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { GameUi } from '../controller';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { mainUi } from '../preset/ui';
|
||||
|
||||
const props = defineProps<{
|
||||
num: number;
|
||||
|
@ -6,9 +6,8 @@ import { Ref, ref } from 'vue';
|
||||
import { EVENT_KEY_CODE_MAP, KeyCode } from '@motajs/client-base';
|
||||
import axios from 'axios';
|
||||
import { decompressFromBase64 } from 'lz-string';
|
||||
import { Keyboard, KeyboardEmits } from '@/core/main/custom/keyboard';
|
||||
import { fixedUi, mainUi } from '@/core/main/init/ui';
|
||||
import { isAssist } from '@/core/main/custom/hotkey';
|
||||
import { Keyboard, KeyboardEmits, isAssist } from '@motajs/system-action';
|
||||
import { fixedUi, mainUi } from './preset/ui';
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
type CanParseCss = keyof {
|
||||
|
@ -4,6 +4,7 @@
|
||||
"@motajs/common": "workspace:*",
|
||||
"@motajs/render-core": "workspace:*",
|
||||
"@motajs/render-elements": "workspace:*",
|
||||
"@motajs/render-style": "workspace:*"
|
||||
"@motajs/render-style": "workspace:*",
|
||||
"@motajs/system-action": "workspace:*"
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { Animation, Ticker, Transition } from 'mutate-animate';
|
||||
import { ERenderItemEvent, RenderItem } from '@motajs/render-core';
|
||||
// todo: 改为 monorepo
|
||||
import { gameKey, Hotkey } from '@/core/main/custom/hotkey';
|
||||
import { gameKey, Hotkey } from '@motajs/system-action';
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
|
||||
|
6
packages/system-action/package.json
Normal file
6
packages/system-action/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@motajs/system-action",
|
||||
"dependencies": {
|
||||
"@motajs/common": "workspace:*"
|
||||
}
|
||||
}
|
2
packages/system-action/src/index.ts
Normal file
2
packages/system-action/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './hotkey';
|
||||
export * from './keyboard';
|
@ -105,10 +105,10 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
|
||||
this.scopeStack.push(symbol);
|
||||
const ev: Listener<VirtualKeyEmitFn>[] = [];
|
||||
this.onEmitKey[symbol] = ev;
|
||||
// @ts-ignore
|
||||
// @ts-expect-error 无法推导
|
||||
this.events = ev;
|
||||
this.emit('scopeCreate', symbol);
|
||||
if (!!last) {
|
||||
if (last) {
|
||||
this.scopeAssist[symbol] = this.assist;
|
||||
}
|
||||
this.assist = 0;
|
||||
@ -133,7 +133,7 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
|
||||
if (!symbol) return;
|
||||
this.scope = symbol;
|
||||
this.assist = this.scopeAssist[symbol];
|
||||
// @ts-ignore
|
||||
// @ts-expect-error 无法推导
|
||||
this.events = this.onEmitKey[symbol];
|
||||
}
|
||||
|
||||
@ -189,7 +189,7 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
export function generateKeyboardEvent(key: KeyCode, assist: number) {
|
||||
export function generateKeyboardEvent(_key: KeyCode, assist: number) {
|
||||
const { ctrl, alt, shift } = unwarpBinary(assist);
|
||||
const ev = new KeyboardEvent('keyup', {
|
||||
ctrlKey: ctrl,
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@motajs/system",
|
||||
"dependencies": {
|
||||
"@motajs/system-ui": "workspace:*"
|
||||
"@motajs/system-ui": "workspace:*",
|
||||
"@motajs/system-action": "workspace:*"
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
export * as Action from '@motajs/system-action';
|
||||
export * as UI from '@motajs/system-ui';
|
||||
|
1187
pnpm-lock.yaml
1187
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { mainUi, fixedUi } from './core/main/init/ui';
|
||||
import { mainUi, fixedUi } from '@motajs/legacy-ui';
|
||||
|
||||
onMounted(() => {
|
||||
const { hook } = Mota.requireAll('var');
|
||||
|
@ -1,35 +1,12 @@
|
||||
// todo: 这个引入不加会报错,应该是循环引用导致的
|
||||
import '@/plugin/utils';
|
||||
import { Focus, GameUi, UiController } from './main/custom/ui';
|
||||
import { GameStorage } from './main/storage';
|
||||
import * as LegacyUI from '@motajs/legacy-ui';
|
||||
import * as LegacySystem from '@motajs/legacy-system';
|
||||
import './main/init/';
|
||||
import './main/custom/toolbar';
|
||||
import { fixedUi, mainUi } from './main/init/ui';
|
||||
import {
|
||||
MotaSetting,
|
||||
SettingDisplayer,
|
||||
mainSetting,
|
||||
settingStorage
|
||||
} from './main/setting';
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import '@/plugin';
|
||||
import './package';
|
||||
import { CustomToolbar } from './main/custom/toolbar';
|
||||
import {
|
||||
Hotkey,
|
||||
checkAssist,
|
||||
isAssist,
|
||||
unwarpBinary,
|
||||
gameKey
|
||||
} from './main/custom/hotkey';
|
||||
import { Keyboard, generateKeyboardEvent } from './main/custom/keyboard';
|
||||
import './main/layout';
|
||||
import { createSettingComponents } from './main/init/settings';
|
||||
import {
|
||||
createToolbarComponents,
|
||||
createToolbarEditorComponents
|
||||
} from './main/init/toolbar';
|
||||
import { VirtualKey } from './main/init/misc';
|
||||
import * as System from '@motajs/system';
|
||||
import { UI } from '@motajs/legacy-ui';
|
||||
import Box from '@/components/box.vue';
|
||||
import BoxAnimate from '@/components/boxAnimate.vue';
|
||||
@ -41,47 +18,20 @@ import EnemySpecial from '@/panel/enemySpecial.vue';
|
||||
import EnemyTarget from '@/panel/enemyTarget.vue';
|
||||
import KeyboardPanel from '@/panel/keyboard.vue';
|
||||
import { logger } from '@motajs/common';
|
||||
import { Danmaku } from './main/custom/danmaku';
|
||||
import * as Shadow from './fx/shadow';
|
||||
import { Render } from '@motajs/client';
|
||||
import { HeroKeyMover } from './main/action/move';
|
||||
import { HeroKeyMover } from '../module/action/move';
|
||||
import * as Animation from 'mutate-animate';
|
||||
import '@/module';
|
||||
|
||||
// ----- 类注册
|
||||
Mota.register('class', 'CustomToolbar', CustomToolbar);
|
||||
Mota.register('class', 'Focus', Focus);
|
||||
Mota.register('class', 'GameStorage', GameStorage);
|
||||
Mota.register('class', 'GameUi', GameUi);
|
||||
Mota.register('class', 'Hotkey', Hotkey);
|
||||
Mota.register('class', 'Keyboard', Keyboard);
|
||||
Mota.register('class', 'MotaSetting', MotaSetting);
|
||||
Mota.register('class', 'SettingDisplayer', SettingDisplayer);
|
||||
Mota.register('class', 'UiController', UiController);
|
||||
Mota.register('class', 'Danmaku', Danmaku);
|
||||
// ----- 函数注册
|
||||
Mota.register('fn', 'unwrapBinary', unwarpBinary);
|
||||
Mota.register('fn', 'checkAssist', checkAssist);
|
||||
Mota.register('fn', 'isAssist', isAssist);
|
||||
Mota.register('fn', 'generateKeyboardEvent', generateKeyboardEvent);
|
||||
// ----- 变量注册
|
||||
Mota.register('var', 'mainUi', mainUi);
|
||||
Mota.register('var', 'fixedUi', fixedUi);
|
||||
Mota.register('var', 'gameKey', gameKey);
|
||||
Mota.register('var', 'mainSetting', mainSetting);
|
||||
Mota.register('var', 'KeyCode', KeyCode);
|
||||
Mota.register('var', 'settingStorage', settingStorage);
|
||||
Mota.register('var', 'status', status);
|
||||
Mota.register('var', 'logger', logger);
|
||||
// ----- 模块注册
|
||||
Mota.register('module', 'CustomComponents', {
|
||||
createSettingComponents,
|
||||
createToolbarComponents,
|
||||
createToolbarEditorComponents
|
||||
});
|
||||
Mota.register('module', 'MiscComponents', {
|
||||
VirtualKey
|
||||
});
|
||||
Mota.register('module', 'LegacyUI', LegacyUI);
|
||||
Mota.register('module', 'System', System);
|
||||
Mota.register('module', 'LegacySystem', LegacySystem);
|
||||
Mota.register('module', 'RenderUtils', utils);
|
||||
Mota.register('module', 'UI', UI);
|
||||
Mota.register('module', 'UIComponents', {
|
||||
|
@ -1,21 +0,0 @@
|
||||
export interface Undoable<T> {
|
||||
stack: T[];
|
||||
redoStack: T[];
|
||||
|
||||
/**
|
||||
* 撤销
|
||||
*/
|
||||
undo(): T | undefined;
|
||||
|
||||
/**
|
||||
* 重做
|
||||
*/
|
||||
redo(): T | undefined;
|
||||
}
|
||||
|
||||
export interface ResponseBase {
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type CSSObj = Partial<Record<CanParseCss, string>>;
|
@ -1,386 +0,0 @@
|
||||
import { EventEmitter } from '@motajs/legacy-common';
|
||||
import { deleteWith, has } from '@motajs/legacy-ui';
|
||||
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';
|
||||
|
||||
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();
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
import './fixed';
|
||||
import './keyboard';
|
||||
import './hotkey';
|
File diff suppressed because it is too large
Load Diff
@ -1,74 +0,0 @@
|
||||
import { UI } from '@motajs/legacy-ui';
|
||||
import * as MiscUI from './misc';
|
||||
import { GameUi, UiController } from '../custom/ui';
|
||||
import { mainSetting } from '../setting';
|
||||
|
||||
export const mainUi = new UiController();
|
||||
mainUi.register(
|
||||
new GameUi('book', UI.Book),
|
||||
new GameUi('toolbox', UI.Toolbox),
|
||||
new GameUi('equipbox', UI.Equipbox),
|
||||
new GameUi('settings', UI.Settings),
|
||||
new GameUi('desc', UI.Desc),
|
||||
new GameUi('skill', UI.Skill),
|
||||
new GameUi('skillTree', UI.SkillTree),
|
||||
new GameUi('fly', UI.Fly),
|
||||
new GameUi('fixedDetail', UI.FixedDetail),
|
||||
new GameUi('shop', UI.Shop),
|
||||
new GameUi('achievement', UI.Achievement),
|
||||
new GameUi('bgm', UI.BgmList),
|
||||
new GameUi('hotkey', UI.Hotkey),
|
||||
new GameUi('toolEditor', UI.ToolEditor),
|
||||
new GameUi('virtualKey', MiscUI.VirtualKey)
|
||||
// todo: 把游戏主 div 加入到 mainUi 里面
|
||||
);
|
||||
mainUi.showAll();
|
||||
|
||||
export const fixedUi = new UiController(true);
|
||||
fixedUi.register(
|
||||
new GameUi('markedEnemy', UI.Marked),
|
||||
new GameUi('fixed', UI.Fixed),
|
||||
new GameUi('chapter', UI.Chapter),
|
||||
new GameUi('completeAchi', UI.CompleteAchi),
|
||||
new GameUi('start', UI.Start),
|
||||
new GameUi('toolbar', UI.Toolbar),
|
||||
new GameUi('load', UI.Load),
|
||||
new GameUi('danmaku', UI.Danmaku),
|
||||
new GameUi('danmakuEditor', UI.DanmakuEditor),
|
||||
new GameUi('tips', UI.Tips)
|
||||
);
|
||||
fixedUi.showAll();
|
||||
|
||||
const hook = Mota.require('var', 'hook');
|
||||
hook.once('mounted', () => {
|
||||
const ui = document.getElementById('ui-main')!;
|
||||
const fixed = document.getElementById('ui-fixed')!;
|
||||
|
||||
const blur = mainSetting.getSetting('screen.blur');
|
||||
|
||||
mainUi.on('start', () => {
|
||||
ui.style.display = 'flex';
|
||||
if (blur?.value) {
|
||||
ui.style.backdropFilter = 'blur(5px)';
|
||||
ui.style.backgroundColor = 'rgba(0,0,0,0.7333)';
|
||||
} else {
|
||||
ui.style.backdropFilter = 'none';
|
||||
ui.style.backgroundColor = 'rgba(0,0,0,0.85)';
|
||||
}
|
||||
core.lockControl();
|
||||
});
|
||||
mainUi.on('end', noClosePanel => {
|
||||
ui.style.display = 'none';
|
||||
if (!noClosePanel) {
|
||||
try {
|
||||
core.closePanel();
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
fixedUi.on('start', () => {
|
||||
fixed.style.display = 'block';
|
||||
});
|
||||
fixedUi.on('end', () => {
|
||||
fixed.style.display = 'none';
|
||||
});
|
||||
});
|
@ -1,574 +0,0 @@
|
||||
import { FunctionalComponent, reactive } from 'vue';
|
||||
import { EventEmitter } from '@motajs/legacy-common';
|
||||
import { GameStorage } from './storage';
|
||||
import { has, triggerFullscreen, isMobile } from '@motajs/legacy-ui';
|
||||
import { createSettingComponents } from './init/settings';
|
||||
import settingsText from '@/data/settings.json';
|
||||
import { CustomToolbar } from './custom/toolbar';
|
||||
import { fixedUi } from './init/ui';
|
||||
import { bgmController, soundPlayer } from '@/module';
|
||||
|
||||
export interface SettingComponentProps {
|
||||
item: MotaSettingItem;
|
||||
setting: MotaSetting;
|
||||
displayer: SettingDisplayer;
|
||||
}
|
||||
|
||||
export type SettingComponent = FunctionalComponent<SettingComponentProps>;
|
||||
type MotaSettingType = boolean | number | MotaSetting;
|
||||
|
||||
export interface MotaSettingItem<T extends MotaSettingType = MotaSettingType> {
|
||||
name: string;
|
||||
key: string;
|
||||
value: T;
|
||||
controller: SettingComponent;
|
||||
description?: string;
|
||||
defaults?: boolean | number;
|
||||
step?: [number, number, number];
|
||||
display?: (value: T) => string;
|
||||
}
|
||||
|
||||
interface SettingEvent {
|
||||
valueChange: <T extends boolean | number>(
|
||||
key: string,
|
||||
newValue: T,
|
||||
oldValue: T
|
||||
) => void;
|
||||
}
|
||||
|
||||
export type SettingText = {
|
||||
[key: string]: string[] | SettingText;
|
||||
};
|
||||
|
||||
export interface SettingDisplayInfo {
|
||||
item: MotaSettingItem | null;
|
||||
list: Record<string, MotaSettingItem>;
|
||||
text: string[];
|
||||
}
|
||||
|
||||
const COM = createSettingComponents();
|
||||
|
||||
export class MotaSetting extends EventEmitter<SettingEvent> {
|
||||
static noStorage: string[] = [];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个数字型设置
|
||||
* @param key 设置的键名
|
||||
* @param value 设置的值
|
||||
*/
|
||||
register(
|
||||
key: string,
|
||||
name: string,
|
||||
value: number,
|
||||
com?: SettingComponent,
|
||||
step?: [number, number, number]
|
||||
): this;
|
||||
/**
|
||||
* 注册一个非数字型设置
|
||||
* @param key 设置的键名
|
||||
* @param value 设置的值
|
||||
*/
|
||||
register(
|
||||
key: string,
|
||||
name: string,
|
||||
value: boolean | MotaSetting,
|
||||
com?: SettingComponent
|
||||
): this;
|
||||
register(
|
||||
key: string,
|
||||
name: string,
|
||||
value: MotaSettingType,
|
||||
com: SettingComponent = COM.Default,
|
||||
step: [number, number, number] = [0, 100, 1]
|
||||
) {
|
||||
const setting: MotaSettingItem = {
|
||||
name,
|
||||
value,
|
||||
key,
|
||||
controller: com
|
||||
};
|
||||
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}`
|
||||
);
|
||||
}
|
||||
const old = setting.value as boolean | number;
|
||||
setting.value = value;
|
||||
|
||||
this.emit('valueChange', key, value, old);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一个设置的值
|
||||
* @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}`
|
||||
);
|
||||
}
|
||||
const old = setting.value as boolean | number;
|
||||
setting.value += value;
|
||||
this.emit('valueChange', key, old, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个设置的值,如果获取到的是一个MotaSetting实例,那么返回undefined
|
||||
* @param key 要获取的设置
|
||||
*/
|
||||
getValue(key: string): boolean | number | undefined;
|
||||
/**
|
||||
* 获取一个设置的值,如果获取到的是一个MotaSetting实例,那么返回defaultValue
|
||||
* @param key 要获取的设置
|
||||
* @param defaultValue 设置的默认值
|
||||
*/
|
||||
getValue<T extends boolean | number>(key: string, defaultValue: T): T;
|
||||
getValue<T extends boolean | number>(
|
||||
key: string,
|
||||
defaultValue?: T
|
||||
): T | undefined {
|
||||
const setting = this.getSetting(key);
|
||||
if (!has(setting) && !has(defaultValue)) return void 0;
|
||||
if (setting instanceof MotaSetting) {
|
||||
if (has(setting)) return defaultValue;
|
||||
return void 0;
|
||||
} else {
|
||||
return has(setting) ? (setting.value as T) : (defaultValue as T);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个设置的值显示函数
|
||||
* @param key 要设置的设置的键
|
||||
* @param func 显示函数
|
||||
*/
|
||||
setDisplayFunc(key: string, func: (value: MotaSettingType) => string) {
|
||||
const setting = this.getSettingBy(key.split('.'));
|
||||
setting.display = func;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个设置的修改部分组件
|
||||
* @param key 要设置的设置的键
|
||||
* @param com 设置修改部分的组件
|
||||
*/
|
||||
setValueController(key: string, com: SettingComponent) {
|
||||
const setting = this.getSettingBy(key.split('.'));
|
||||
setting.controller = com;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个设置的说明
|
||||
* @param key 要设置的设置的id
|
||||
* @param desc 设置的说明
|
||||
*/
|
||||
setDescription(key: string, desc: string) {
|
||||
const setting = this.getSettingBy(key.split('.'));
|
||||
setting.description = desc;
|
||||
return this;
|
||||
}
|
||||
|
||||
private getSettingBy(list: string[]) {
|
||||
let now: MotaSetting = this;
|
||||
|
||||
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]}'`
|
||||
);
|
||||
}
|
||||
now = item;
|
||||
}
|
||||
|
||||
return now.list[list.at(-1)!] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
interface SettingDisplayerEvent {
|
||||
update: (stack: string[], display: SettingDisplayInfo[]) => void;
|
||||
}
|
||||
|
||||
export class SettingDisplayer extends EventEmitter<SettingDisplayerEvent> {
|
||||
setting: MotaSetting;
|
||||
/** 选项选中栈 */
|
||||
selectStack: string[] = [];
|
||||
displayInfo: SettingDisplayInfo[] = reactive([]);
|
||||
|
||||
constructor(setting: MotaSetting) {
|
||||
super();
|
||||
this.setting = setting;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
const last = now.list[list.at(-1)!];
|
||||
if (last) {
|
||||
const desc = last.description;
|
||||
const text = desc ? desc.split('\n') : ['请选择设置'];
|
||||
|
||||
this.displayInfo.push({
|
||||
item: last,
|
||||
text,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export const mainSetting = new MotaSetting();
|
||||
// 添加不参与全局存储的设置
|
||||
MotaSetting.noStorage.push('action.autoSkill', 'screen.fullscreen');
|
||||
|
||||
const storage = new GameStorage(GameStorage.fromAuthor('AncTe', 'setting'));
|
||||
|
||||
export { storage as settingStorage };
|
||||
|
||||
// ----- 监听设置修改
|
||||
mainSetting.on('valueChange', (key, n, o) => {
|
||||
if (!MotaSetting.noStorage.includes(key)) {
|
||||
storage.setValue(key, n);
|
||||
}
|
||||
|
||||
const [root, setting] = key.split('.');
|
||||
|
||||
if (root === 'screen') {
|
||||
handleScreenSetting(setting, n, o);
|
||||
} else if (root === 'action') {
|
||||
handleActionSetting(setting, n, o);
|
||||
} else if (root === 'audio') {
|
||||
handleAudioSetting(setting, n, o);
|
||||
} else if (root === 'ui') {
|
||||
handleUiSetting(setting, n, o);
|
||||
}
|
||||
});
|
||||
|
||||
const root = document.getElementById('root') as HTMLDivElement;
|
||||
|
||||
function handleScreenSetting<T extends number | boolean>(
|
||||
key: string,
|
||||
n: T,
|
||||
_o: T
|
||||
) {
|
||||
if (key === 'fullscreen') {
|
||||
// 全屏
|
||||
triggerFullscreen(n as boolean);
|
||||
} else if (key === 'heroDetail') {
|
||||
// 勇士显伤
|
||||
core.drawHero();
|
||||
} else if (key === 'fontSize') {
|
||||
// 字体大小
|
||||
root.style.fontSize = `${n}px`;
|
||||
const absoluteSize = (n as number) * devicePixelRatio;
|
||||
storage.setValue('@@absoluteFontSize', absoluteSize);
|
||||
storage.write();
|
||||
} else if (key === 'fontSizeStatus') {
|
||||
// fontSize.value = n as number;
|
||||
}
|
||||
}
|
||||
|
||||
function handleActionSetting<T extends number | boolean>(
|
||||
key: string,
|
||||
n: T,
|
||||
_o: T
|
||||
) {
|
||||
if (key === 'autoSkill') {
|
||||
// 自动切换技能
|
||||
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
|
||||
HeroSkill.setAutoSkill(n as boolean);
|
||||
core.status.route.push(`set:autoSkill:${n}`)
|
||||
}
|
||||
}
|
||||
|
||||
function handleAudioSetting<T extends number | boolean>(
|
||||
key: string,
|
||||
n: T,
|
||||
_o: T
|
||||
) {
|
||||
if (key === 'bgmEnabled') {
|
||||
bgmController.setEnabled(n as boolean);
|
||||
core.checkBgm();
|
||||
} else if (key === 'bgmVolume') {
|
||||
bgmController.setVolume((n as number) / 100);
|
||||
} else if (key === 'soundEnabled') {
|
||||
soundPlayer.setEnabled(n as boolean);
|
||||
} else if (key === 'soundVolume') {
|
||||
soundPlayer.setVolume((n as number) / 100)
|
||||
}
|
||||
}
|
||||
|
||||
function handleUiSetting<T extends number | boolean>(key: string, n: T, o: T) {
|
||||
if (key === 'toolbarScale') {
|
||||
const scale = (n as number) / (o as number);
|
||||
CustomToolbar.list.forEach(v => {
|
||||
v.setSize(v.width * scale, v.height * scale);
|
||||
});
|
||||
CustomToolbar.refreshAll(true);
|
||||
} else if (key === 'danmaku') {
|
||||
if (n) {
|
||||
fixedUi.open('danmaku');
|
||||
} else {
|
||||
fixedUi.closeByName('danmaku');
|
||||
}
|
||||
} else if (key === 'tips') {
|
||||
if (n && core.isPlaying()) {
|
||||
fixedUi.open('tips');
|
||||
} else {
|
||||
fixedUi.closeByName('tips')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----- 游戏的所有设置项
|
||||
// todo: 虚拟键盘缩放,小地图楼传缩放
|
||||
mainSetting
|
||||
.register(
|
||||
'screen',
|
||||
'显示设置',
|
||||
new MotaSetting()
|
||||
.register('fullscreen', '全屏游戏', false, COM.Boolean)
|
||||
.register('halo', '光环显示', true, COM.Boolean)
|
||||
.register('itemDetail', '宝石血瓶显伤', true, COM.Boolean)
|
||||
.register('heroDetail', '勇士显伤', false, COM.Boolean)
|
||||
.register('transition', '界面动画', false, COM.Boolean)
|
||||
.register('fontSize', '字体大小', 16, COM.Number, [2, 48, 1])
|
||||
.register('fontSizeStatus', '状态栏字体', 16, COM.Number, [10, 300, 10])
|
||||
.register('smoothView', '平滑镜头', true, COM.Boolean)
|
||||
.register('criticalGem', '临界显示方式', false, COM.Boolean)
|
||||
.setDisplayFunc('criticalGem', value => (value ? '宝石数' : '攻击'))
|
||||
.register('keyScale', '虚拟键盘缩放', 100, COM.Number, [25, 5, 500])
|
||||
.register('blur', '背景虚化', !isMobile, COM.Boolean)
|
||||
)
|
||||
.register(
|
||||
'action',
|
||||
'操作设置',
|
||||
new MotaSetting()
|
||||
.register('autoSkill', '自动切换技能', true, COM.Boolean)
|
||||
.register('fixed', '定点查看', true, COM.Boolean)
|
||||
.register('hotkey', '快捷键', false, COM.HotkeySetting)
|
||||
.setDisplayFunc('hotkey', () => '')
|
||||
.register('toolbar', '自定义工具栏', false, COM.ToolbarEditor)
|
||||
.setDisplayFunc('toolbar', () => '')
|
||||
)
|
||||
.register(
|
||||
'audio',
|
||||
'音频设置',
|
||||
new MotaSetting()
|
||||
.register('bgmEnabled', '开启音乐', true, COM.Boolean)
|
||||
.register('bgmVolume', '音乐音量', 80, COM.Number, [0, 100, 5])
|
||||
.register('soundEnabled', '开启音效', true, COM.Boolean)
|
||||
.register('soundVolume', '音效音量', 80, COM.Number, [0, 100, 5])
|
||||
)
|
||||
.register(
|
||||
'utils',
|
||||
'系统设置',
|
||||
new MotaSetting()
|
||||
.register('betterLoad', '优化加载', true, COM.Boolean)
|
||||
.register('autoScale', '自动放缩', true, COM.Boolean)
|
||||
)
|
||||
.register(
|
||||
'fx',
|
||||
'特效设置',
|
||||
new MotaSetting()
|
||||
.register('paraLight', '野外阴影', true, COM.Boolean)
|
||||
.register('frag', '打怪特效', true, COM.Boolean)
|
||||
.register('portalParticle', '传送门特效', true, COM.Boolean)
|
||||
)
|
||||
.register(
|
||||
'ui',
|
||||
'ui设置',
|
||||
new MotaSetting()
|
||||
.register('mapScale', '小地图缩放', 100, COM.Number, [50, 1000, 50])
|
||||
.setDisplayFunc('mapScale', value => `${value}%`)
|
||||
.register('mapLazy', '小地图懒更新', false, COM.Boolean)
|
||||
.register('toolbarScale', '工具栏缩放', 100, COM.Number, [10, 500, 10])
|
||||
.setDisplayFunc('toolbarScale', value => `${value}%`)
|
||||
.register('bookScale', '怪物手册缩放', 100, COM.Number, [10, 500, 10])
|
||||
.setDisplayFunc('bookScale', value => `${value}%`)
|
||||
.register('danmaku', '显示弹幕', true, COM.Boolean)
|
||||
.register('danmakuSpeed', '弹幕速度', 60, COM.Number, [10, 1000, 5])
|
||||
.register('tips', '小贴士', true, COM.Boolean)
|
||||
);
|
||||
|
||||
const loading = Mota.require('var', 'loading');
|
||||
loading.once('coreInit', () => {
|
||||
mainSetting.reset({
|
||||
'screen.fullscreen': !!document.fullscreenElement,
|
||||
'screen.halo': !!storage.getValue('screen.showHalo', true),
|
||||
'screen.itemDetail': !!storage.getValue('screen.itemDetail', true),
|
||||
'screen.heroDetail': !!storage.getValue('screen.heroDetail', false),
|
||||
'screen.transition': !!storage.getValue('screen.transition', false),
|
||||
'screen.fontSize': storage.getValue('screen.fontSize', isMobile ? 9 : 16),
|
||||
'screen.smoothView': !!storage.getValue('screen.smoothView', true),
|
||||
'screen.criticalGem': !!storage.getValue('screen.criticalGem', false),
|
||||
'screen.fontSizeStatus': storage.getValue('screen.fontSizeStatus', 100),
|
||||
'action.fixed': !!storage.getValue('action.fixed', true),
|
||||
'audio.bgmEnabled': !!storage.getValue('audio.bgmEnabled', true),
|
||||
'audio.bgmVolume': storage.getValue('audio.bgmVolume', 80),
|
||||
'audio.soundEnabled': !!storage.getValue('audio.soundEnabled', true),
|
||||
'audio.soundVolume': storage.getValue('audio.soundVolume', 80),
|
||||
'utils.betterLoad': !!storage.getValue('utils.betterLoad', true),
|
||||
'utils.autoScale': !!storage.getValue('utils.autoScale', true),
|
||||
'fx.paraLight': !!storage.getValue('fx.paraLight', true),
|
||||
'fx.frag': !!storage.getValue('fx.frag', true),
|
||||
'fx.portalParticle': !!storage.getValue('fx.portalParticle', true),
|
||||
'ui.mapScale': storage.getValue(
|
||||
'ui.mapScale',
|
||||
isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50
|
||||
),
|
||||
'ui.mapLazy': storage.getValue('ui.mapLazy', false),
|
||||
'ui.toolbarScale': storage.getValue(
|
||||
'ui.toolbarScale',
|
||||
isMobile ? 50 : Math.floor((window.innerWidth / 1700) * 10) * 10
|
||||
),
|
||||
'ui.bookScale': storage.getValue('ui.bookScale', isMobile ? 100 : 80),
|
||||
'ui.danmaku': storage.getValue('ui.danmaku', true),
|
||||
'ui.danmakuSpeed': storage.getValue(
|
||||
'ui.danmakuSpeed',
|
||||
Math.floor(window.innerWidth / 30) * 5
|
||||
),
|
||||
'ui.tips': storage.getValue('ui.tips', true)
|
||||
});
|
||||
});
|
||||
|
||||
interface SettingTextData {
|
||||
[x: string]: string[] | SettingTextData;
|
||||
}
|
||||
|
||||
function getSettingText(obj: SettingTextData, key?: string) {
|
||||
for (const [k, value] of Object.entries(obj)) {
|
||||
const setKey = key ? key + '.' + k : k;
|
||||
if (value instanceof Array) {
|
||||
mainSetting.setDescription(setKey, value.join('\n'));
|
||||
} else {
|
||||
getSettingText(value, setKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
getSettingText(settingsText);
|
||||
|
||||
mainSetting
|
||||
.setDescription('audio.bgmEnabled', `是否开启背景音乐`)
|
||||
.setDescription('audio.bgmVolume', `背景音乐的音量`)
|
||||
.setDescription('audio.soundEnabled', `是否开启音效`)
|
||||
.setDescription('audio.soundVolume', `音效的音量`)
|
||||
.setDescription('ui.mapScale', `楼传小地图的缩放,百分比格式`)
|
||||
.setDescription('ui.mapLazy', `是否启用小地图懒更新模式,此模式下剩余怪物数量不会实时更新而变成切换地图后更新,打开小地图时出现卡顿可以尝试开启此设置`)
|
||||
.setDescription('ui.toolbarScale', `自定义工具栏的缩放比例`)
|
||||
.setDescription('ui.bookScale', `怪物手册界面中每个怪物框体的高度缩放,最小值限定为 20% 屏幕高度`)
|
||||
.setDescription('ui.danmaku', '是否显示弹幕')
|
||||
.setDescription('ui.danmakuSpeed', '弹幕速度,刷新或开关弹幕显示后起效')
|
||||
.setDescription('ui.tips', `是否在游戏画面右上角常亮显示小贴士`)
|
||||
.setDescription('screen.fontSizeStatus', `修改状态栏的字体大小`)
|
||||
.setDescription('screen.blur', '打开任意ui界面时是否有背景虚化效果,移动端打开后可能会有掉帧或者发热现象。关闭ui后生效')
|
||||
.setDescription('fx.portalParticle', '是否启用苍蓝之殿的传送门粒子特效,启用后可能对性能及设备发热有所影响')
|
||||
|
||||
function setFontSize() {
|
||||
const absoluteSize = storage.getValue(
|
||||
'@@absoluteFontSize',
|
||||
16 * devicePixelRatio
|
||||
);
|
||||
const size = Math.round(absoluteSize / devicePixelRatio);
|
||||
mainSetting.setValue('screen.fontSize', size);
|
||||
}
|
||||
setFontSize();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
setFontSize();
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
import * as axios from 'axios';
|
||||
import * as chart from 'chart.js';
|
||||
import jszip from 'jszip';
|
||||
import * as lodash from 'lodash-es';
|
||||
import * as lzstring from 'lz-string';
|
||||
import * as animate from 'mutate-animate';
|
||||
import * as vue from 'vue';
|
||||
|
||||
// Mota.Package.register('axios', axios);
|
||||
// Mota.Package.register('chart.js', chart);
|
||||
// Mota.Package.register('jszip', jszip);
|
||||
// Mota.Package.register('lodash', lodash);
|
||||
// Mota.Package.register('lz-string', lzstring);
|
||||
// Mota.Package.register('mutate-animate', animate);
|
||||
// Mota.Package.register('vue', vue);
|
@ -1,7 +1,6 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep, isNil } from 'lodash-es';
|
||||
import { ItemState } from './item';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { HeroSkill, NightSpecial } from '../mechanism/misc';
|
||||
|
||||
/**
|
||||
@ -343,122 +342,3 @@ interface IHeroItem {
|
||||
*/
|
||||
hasItem(item: AllIdsOf<'items'>): boolean;
|
||||
}
|
||||
|
||||
interface HeroEvent {
|
||||
beforeMove: [dir: Dir2];
|
||||
afterMove: [dir: Dir2];
|
||||
beforeMoveDirectly: [x: number, y: number];
|
||||
afterMoveDirectly: [x: number, y: number];
|
||||
stateChange: [state: HeroState<any>];
|
||||
}
|
||||
|
||||
export class Hero<T extends object = IHeroStatusDefault>
|
||||
extends EventEmitter<HeroEvent>
|
||||
implements IHeroItem
|
||||
{
|
||||
x: number;
|
||||
y: number;
|
||||
floorId: FloorIds;
|
||||
dir: Dir2;
|
||||
|
||||
readonly items: Map<AllIdsOf<'items'>, number> = new Map();
|
||||
readonly id: string;
|
||||
|
||||
state: HeroState<T>;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
x: number,
|
||||
y: number,
|
||||
floorId: FloorIds,
|
||||
state: HeroState<T>
|
||||
) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.floorId = floorId;
|
||||
this.state = state;
|
||||
this.dir = 'down';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置勇士状态,效果为直接将一个状态替换为另一个状态,不应当经常使用,仅应当在不得不使用的时候使用
|
||||
* @param state 要设置为的勇士状态
|
||||
*/
|
||||
setState(state: HeroState<T>): void {
|
||||
this.state = state;
|
||||
this.emit('stateChange', state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取勇士状态
|
||||
*/
|
||||
getState(): HeroState<T> {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 见 {@link HeroState.refreshStatus}
|
||||
*/
|
||||
refreshState(key?: keyof T) {
|
||||
return this.state.refreshStatus(key);
|
||||
}
|
||||
|
||||
setItem(item: AllIdsOf<'items'>, value: number): boolean {
|
||||
this.items.set(item, value < 0 ? 0 : value);
|
||||
return true;
|
||||
}
|
||||
|
||||
addItem(item: AllIdsOf<'items'>, value: number): boolean {
|
||||
return this.setItem(item, (this.items.get(item) ?? 0) + value);
|
||||
}
|
||||
|
||||
useItem(item: AllIdsOf<'items'>): boolean {
|
||||
const state = ItemState.item(item);
|
||||
return !!state?.use(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得一个物品
|
||||
* @param item 物品id
|
||||
* @param num 获得的数量
|
||||
*/
|
||||
getItem(item: AllIdsOf<'items'>, num: number): boolean;
|
||||
/**
|
||||
* 获得一个物品
|
||||
* @param item 物品id,填写坐标时无效
|
||||
* @param x 物品所在x坐标
|
||||
* @param y 物品所在y坐标
|
||||
* @param floorId 物品所在楼层
|
||||
* @param num 获得的数量
|
||||
*/
|
||||
getItem(x: number, y: number, floorId?: FloorIds, num?: number): boolean;
|
||||
getItem(
|
||||
item: AllIdsOf<'items'> | number,
|
||||
y: number,
|
||||
floorId: FloorIds = this.floorId,
|
||||
num: number = 1
|
||||
): boolean {
|
||||
if (!isNil(floorId) && typeof item === 'number') {
|
||||
// 如果指定了坐标
|
||||
const block = core.getBlock(item as number, y, floorId);
|
||||
const id = block.event.id as AllIdsOf<'items'>;
|
||||
const cls = core.material.items[id]?.cls;
|
||||
if (cls === void 0) {
|
||||
logger.warn(15, item.toString(), y.toString(), floorId);
|
||||
return false;
|
||||
}
|
||||
return this.addItem(id, num!);
|
||||
}
|
||||
return this.addItem(item as AllIdsOf<'items'>, num!);
|
||||
}
|
||||
|
||||
itemCount(item: AllIdsOf<'items'>): number {
|
||||
return this.items.get(item) ?? 0;
|
||||
}
|
||||
|
||||
hasItem(item: AllIdsOf<'items'>): boolean {
|
||||
return this.itemCount(item) > 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import type { Hero } from './hero';
|
||||
import { GameState, gameStates } from './state';
|
||||
import { loading } from '../game';
|
||||
|
||||
type EffectFn = (state: GameState, hero: Hero<any>) => void;
|
||||
type CanUseEffectFn = (state: GameState, hero: Hero<any>) => boolean;
|
||||
type EffectFn = () => void;
|
||||
type CanUseEffectFn = () => boolean;
|
||||
|
||||
interface ItemStateEvent {
|
||||
use: [hero: Hero<any>];
|
||||
use: [];
|
||||
}
|
||||
|
||||
export class ItemState<
|
||||
@ -56,7 +54,6 @@ export class ItemState<
|
||||
this.canUseItemEffect = item.canUseItemEffect;
|
||||
|
||||
this.compileFunction();
|
||||
this.compileEvent();
|
||||
}
|
||||
|
||||
private compileFunction() {
|
||||
@ -85,48 +82,6 @@ export class ItemState<
|
||||
}
|
||||
}
|
||||
|
||||
private compileEvent() {
|
||||
// todo
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用这个物品
|
||||
* @param hero 使用物品的勇士
|
||||
*/
|
||||
use(hero: Hero<any>): boolean {
|
||||
if (!this.canUse(hero)) return false;
|
||||
if (!gameStates.now) return false;
|
||||
const state = gameStates.now;
|
||||
this.useItemEffectFn?.(state, hero);
|
||||
if (this.useItemEvent) core.insertAction(this.useItemEvent);
|
||||
if (!this.noRoute) {
|
||||
core.status.route.push(`item:${this.id}`);
|
||||
}
|
||||
|
||||
hero.addItem(this.id, -1);
|
||||
this.emit('use', hero);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以使用一个物品
|
||||
* @param hero 使用物品的勇士
|
||||
*/
|
||||
canUse(hero: Hero<any>, num: number = 1): boolean {
|
||||
if (num <= 0) return false;
|
||||
if (hero.itemCount(this.id) < num) return false;
|
||||
if (!gameStates.now) return false;
|
||||
return !!this.canUseItemEffectFn?.(gameStates.now, hero);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记当前物品为不进入录像,也就是录像中不会使用该道具
|
||||
*/
|
||||
markNoRoute() {
|
||||
this.noRoute = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个道具的信息
|
||||
* @param id 要获取的道具id
|
||||
|
@ -11,7 +11,7 @@ import type {
|
||||
LayerMovingRenderable,
|
||||
LayerFloorBinder
|
||||
} from '@motajs/render';
|
||||
import type { HeroKeyMover } from '@/core/main/action/move';
|
||||
import type { HeroKeyMover } from '@/module/action/move';
|
||||
import { BluePalace, MiscData } from '../mechanism/misc';
|
||||
import { sleep } from 'mutate-animate';
|
||||
|
||||
@ -293,7 +293,7 @@ export class BlockMover extends ObjectMoverBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async onMoveStart(controller: IMoveController): Promise<void> {
|
||||
protected async onMoveStart(_controller: IMoveController): Promise<void> {
|
||||
const adapter = BlockMover.adapter;
|
||||
if (adapter) {
|
||||
const list = adapter.items;
|
||||
@ -332,7 +332,7 @@ export class BlockMover extends ObjectMoverBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected async onMoveEnd(controller: IMoveController): Promise<void> {
|
||||
protected async onMoveEnd(_controller: IMoveController): Promise<void> {
|
||||
if (this.renderable) {
|
||||
this.layerItems.forEach(v => {
|
||||
v.moving.delete(this.renderable!);
|
||||
@ -349,7 +349,7 @@ export class BlockMover extends ObjectMoverBase {
|
||||
|
||||
protected async onStepStart(
|
||||
step: MoveStepDir,
|
||||
controller: IMoveController
|
||||
_controller: IMoveController
|
||||
): Promise<BlockMoveCode> {
|
||||
await this.moveAnimate(step);
|
||||
const { x: dx, y: dy } = core.utils.scan2[this.moveDir];
|
||||
@ -360,17 +360,17 @@ export class BlockMover extends ObjectMoverBase {
|
||||
}
|
||||
|
||||
protected async onStepEnd(
|
||||
step: MoveStepDir,
|
||||
code: BlockMoveCode,
|
||||
controller: IMoveController
|
||||
_step: MoveStepDir,
|
||||
_code: BlockMoveCode,
|
||||
_controller: IMoveController
|
||||
): Promise<void> {}
|
||||
|
||||
protected onSetMoveSpeed(
|
||||
speed: number,
|
||||
controller: IMoveController
|
||||
_speed: number,
|
||||
_controller: IMoveController
|
||||
): void {}
|
||||
|
||||
private moveAnimate(step: MoveStepDir) {
|
||||
private moveAnimate(_step: MoveStepDir) {
|
||||
const layer = this.layerItems[0];
|
||||
if (!layer) return;
|
||||
if (!this.renderable) return;
|
||||
@ -521,7 +521,7 @@ export class HeroMover extends ObjectMoverBase {
|
||||
}
|
||||
|
||||
protected async onStepStart(
|
||||
step: MoveStepDir,
|
||||
_step: MoveStepDir,
|
||||
controller: IMoveController
|
||||
): Promise<HeroMoveCode> {
|
||||
const showDir = toDir(this.faceDir);
|
||||
@ -594,7 +594,7 @@ export class HeroMover extends ObjectMoverBase {
|
||||
}
|
||||
|
||||
protected async onStepEnd(
|
||||
step: MoveStepDir,
|
||||
_step: MoveStepDir,
|
||||
code: HeroMoveCode,
|
||||
controller: IMoveController
|
||||
): Promise<void> {
|
||||
@ -661,7 +661,10 @@ export class HeroMover extends ObjectMoverBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected onSetMoveSpeed(speed: number, controller: IMoveController): void {
|
||||
protected onSetMoveSpeed(
|
||||
speed: number,
|
||||
_controller: IMoveController
|
||||
): void {
|
||||
const adapter = HeroMover.adapter;
|
||||
if (!adapter) return;
|
||||
adapter.sync('setMoveSpeed', speed);
|
||||
@ -785,7 +788,7 @@ export class HeroMover extends ObjectMoverBase {
|
||||
const list = adapter.items;
|
||||
const { x: tx, y: ty, dir: toDir } = data;
|
||||
const { x, y, direction } = core.status.hero.loc;
|
||||
const { x: dx, y: dy } = core.utils.scan[direction];
|
||||
const { x: dx } = core.utils.scan[direction];
|
||||
const { x: tdx } = core.utils.scan[toDir];
|
||||
|
||||
const promises = [...list].map(v => {
|
||||
|
@ -1,127 +0,0 @@
|
||||
import { Undoable } from '@/core/interface';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { logger } from '@motajs/common';
|
||||
|
||||
type ToJSONFunction<T> = (data: T) => string;
|
||||
type FromJSONFunction<T> = (data: string) => T;
|
||||
|
||||
export class GameState {
|
||||
state: Map<string, any> = new Map();
|
||||
|
||||
private static states: Set<string> = new Set();
|
||||
private static toJSONFn: Map<string, ToJSONFunction<any>> = new Map();
|
||||
private static fromJSONFn: Map<string, FromJSONFunction<any>> = new Map();
|
||||
|
||||
/**
|
||||
* 序列化游戏状态,可直接用于存储等操作
|
||||
*/
|
||||
toJSON() {
|
||||
const obj: Record<string, string> = {};
|
||||
this.state.forEach((v, k) => {
|
||||
const to = GameState.toJSONFn.get(k);
|
||||
if (to) obj[k] = to(v);
|
||||
else obj[k] = JSON.stringify(v);
|
||||
});
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某个游戏状态
|
||||
* @param key 要获取的状态名称
|
||||
*/
|
||||
get<T>(key: string): T | undefined {
|
||||
return this.state.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置某个游戏状态
|
||||
* @param key 要设置的状态名称
|
||||
* @param data 状态数据
|
||||
*/
|
||||
set(key: string, data: any) {
|
||||
this.state.set(key, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个新的状态,如果重复则会覆盖
|
||||
* @param key 状态名称
|
||||
* @param toJSON 状态的序列化函数,传入状态数据,要求返回序列化后的字符串,
|
||||
* 不填则表示使用JSON.stringify进行序列化
|
||||
* @param fromJSON 状态的反序列化函数,传入序列化后的字符串,要求返回反序列化的状态数据,
|
||||
* 不填表示使用JSON.parse进行反序列化
|
||||
*/
|
||||
static register<T>(
|
||||
key: string,
|
||||
toJSON?: ToJSONFunction<T>,
|
||||
fromJSON?: FromJSONFunction<T>
|
||||
) {
|
||||
if (this.states.has(key)) {
|
||||
logger.warn(16, key);
|
||||
}
|
||||
|
||||
if (toJSON) {
|
||||
this.toJSONFn.set(key, toJSON);
|
||||
} else {
|
||||
this.toJSONFn.delete(key);
|
||||
}
|
||||
if (fromJSON) {
|
||||
this.fromJSONFn.set(key, fromJSON);
|
||||
} else {
|
||||
this.fromJSONFn.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从序列化字符串读取游戏状态
|
||||
* @param json 序列化字符串
|
||||
*/
|
||||
static fromJSON(json: string) {
|
||||
const obj: Record<string, string> = JSON.parse(json);
|
||||
const state = new GameState();
|
||||
for (const [key, data] of Object.entries(obj)) {
|
||||
const from = this.fromJSONFn.get(key);
|
||||
if (from) state.set(key, from(data));
|
||||
else state.set(key, JSON.parse(data));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
interface StateStoreEvent {
|
||||
undo: [state: GameState];
|
||||
redo: [state: GameState];
|
||||
change: [before: GameState | undefined, now: GameState];
|
||||
}
|
||||
|
||||
class StateStore
|
||||
extends EventEmitter<StateStoreEvent>
|
||||
implements Undoable<GameState>
|
||||
{
|
||||
now?: GameState;
|
||||
stack: GameState[] = [];
|
||||
redoStack: GameState[] = [];
|
||||
|
||||
undo(): GameState | undefined {
|
||||
const state = this.stack.pop();
|
||||
if (!state) return void 0;
|
||||
this.redoStack.push(state);
|
||||
this.emit('undo', state);
|
||||
return state;
|
||||
}
|
||||
|
||||
redo(): GameState | undefined {
|
||||
const state = this.redoStack.pop();
|
||||
if (!state) return void 0;
|
||||
this.stack.push(state);
|
||||
this.emit('redo', state);
|
||||
return state;
|
||||
}
|
||||
|
||||
use(state: GameState) {
|
||||
const before = this.now;
|
||||
this.now = state;
|
||||
this.emit('change', before, state);
|
||||
}
|
||||
}
|
||||
|
||||
export const gameStates = new StateStore();
|
@ -23,7 +23,7 @@ import type { Danmaku } from '@/core/main/custom/danmaku';
|
||||
import type * as misc from './mechanism/misc';
|
||||
import type { Render } from '@motajs/client';
|
||||
import type { ItemState } from './state/item';
|
||||
import type { HeroKeyMover } from '@/core/main/action/move';
|
||||
import type { HeroKeyMover } from '@/module/action/move';
|
||||
import type { BlockMover, HeroMover, ObjectMoverBase } from './state/move';
|
||||
import type * as Animation from 'mutate-animate';
|
||||
import type { WeatherController } from '@/module/weather/weather';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
import { Hotkey, HotkeyData } from '../custom/hotkey';
|
||||
import { Hotkey, HotkeyData } from '@motajs/system-action';
|
||||
import type { HeroMover, IMoveController } from '@/game/state/move';
|
||||
import { Ticker } from 'mutate-animate';
|
||||
import { mainScope } from '../init/hotkey';
|
||||
import { mainScope } from '@motajs/legacy-ui';
|
||||
|
||||
type MoveKey = Record<Dir, HotkeyData>;
|
||||
type MoveKeyConfig = Record<Dir, string>;
|
@ -1,6 +1,6 @@
|
||||
import { Patch, PatchClass } from '@motajs/legacy-common';
|
||||
import { audioPlayer, bgmController, soundPlayer } from '../audio';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { mainSetting } from '@motajs/legacy-ui';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
SetupComponentOptions,
|
||||
waitbox
|
||||
} from '../components';
|
||||
import { mainUi } from '@/core/main/init/ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { generateKeyboardEvent } from '@/core/main/custom/keyboard';
|
||||
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';
|
||||
|
||||
|
@ -18,8 +18,8 @@ import {
|
||||
getVitualKeyOnce,
|
||||
openDanmakuPoster
|
||||
} from '@motajs/legacy-ui';
|
||||
import { gameKey } from '@/core/main/custom/hotkey';
|
||||
import { generateKeyboardEvent } from '@/core/main/custom/keyboard';
|
||||
import { gameKey } from '@motajs/system-action';
|
||||
import { generateKeyboardEvent } from '@motajs/system-action';
|
||||
import { transitioned } from '../use';
|
||||
import { linear } from 'mutate-animate';
|
||||
import { KeyCode } from '@motajs/client-base';
|
||||
|
@ -7,9 +7,11 @@
|
||||
"@motajs/render": "workspace:*",
|
||||
"@motajs/system": "workspace:*",
|
||||
"@motajs/system-ui": "workspace:*",
|
||||
"@motajs/system-action": "workspace:*",
|
||||
"@motajs/legacy-common": "workspace:*",
|
||||
"@motajs/legacy-client": "workspace:*",
|
||||
"@motajs/legacy-data": "workspace:*",
|
||||
"@motajs/legacy-ui": "workspace:*"
|
||||
"@motajs/legacy-ui": "workspace:*",
|
||||
"@motajs/legacy-system": "workspace:*"
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { MotaOffscreenCanvas2D } from '@motajs/render';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { mainSetting } from '@motajs/legacy-ui';
|
||||
import {
|
||||
LayerGroupFloorBinder,
|
||||
ILayerGroupRenderExtends,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { mainSetting } from '@motajs/legacy-ui';
|
||||
import {
|
||||
Damage,
|
||||
DamageRenderable,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { MotaOffscreenCanvas2D } from '@motajs/render';
|
||||
import { mainSetting, MotaSettingItem } from '@/core/main/setting';
|
||||
import { mainSetting, MotaSettingItem } from '@motajs/legacy-ui';
|
||||
import {
|
||||
LayerGroupFloorBinder,
|
||||
ILayerGroupRenderExtends,
|
||||
|
Loading…
Reference in New Issue
Block a user