refactor: src/core/main 全部移入 monorepo

This commit is contained in:
unanmed 2025-03-05 21:16:37 +08:00
parent 9ec13e5314
commit 960dbd429a
79 changed files with 1872 additions and 3616 deletions

1
.gitignore vendored
View File

@ -49,3 +49,4 @@ script/people.ts
docs/
user.ts
.antlr
graph.svg

12
.madgerc Normal file
View File

@ -0,0 +1,12 @@
{
"fileExtensions": [
"ts",
"tsx"
],
"tsConfig": "./tsconfig.json",
"detectiveOptions": {
"ts": {
"skipTypeImports": true
}
}
}

View File

@ -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",

View File

@ -1 +1,2 @@
export * from './keyCodes';
export * from './types';

View File

@ -0,0 +1,4 @@
export interface ResponseBase {
code: number;
message: string;
}

View File

@ -0,0 +1,6 @@
{
"name": "@motajs/legacy-system",
"dependencies": {
"@motajs/system-action": "workspace:*"
}
}

View File

@ -0,0 +1,2 @@
export * from './misc';
export * from './storage';

View File

@ -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;

View File

@ -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:*"
}
}

View 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';

View File

@ -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);
});

View File

@ -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);

View File

@ -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);
});
}
}
});

View File

@ -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';

View File

@ -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';

View 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);
});
}
}
});

View File

@ -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);

View File

@ -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');

View File

@ -0,0 +1,6 @@
export * from './ui';
export * from './settings';
export * from './danmaku';
export * from './fixed';
export * from './hotkey';
export * from './keyboard';

View File

@ -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,

View File

@ -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

View 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();
});

View 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);
}
}

View File

@ -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}`;

View File

@ -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<{

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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';

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<{

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<{

View File

@ -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();

View File

@ -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>&nbsp;&nbsp;
<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>&nbsp;&nbsp;
<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>

View File

@ -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>

View File

@ -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;

View File

@ -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 {

View File

@ -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:*"
}
}

View File

@ -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';

View File

@ -0,0 +1,6 @@
{
"name": "@motajs/system-action",
"dependencies": {
"@motajs/common": "workspace:*"
}
}

View File

@ -0,0 +1,2 @@
export * from './hotkey';
export * from './keyboard';

View File

@ -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,

View File

@ -1,6 +1,7 @@
{
"name": "@motajs/system",
"dependencies": {
"@motajs/system-ui": "workspace:*"
"@motajs/system-ui": "workspace:*",
"@motajs/system-action": "workspace:*"
}
}

View File

@ -1 +1,2 @@
export * as Action from '@motajs/system-action';
export * as UI from '@motajs/system-ui';

File diff suppressed because it is too large Load Diff

View File

@ -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');

View File

@ -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', {

View File

@ -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>>;

View File

@ -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();
});

View File

@ -1,3 +0,0 @@
import './fixed';
import './keyboard';
import './hotkey';

File diff suppressed because it is too large Load Diff

View File

@ -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';
});
});

View File

@ -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();
});

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 => {

View File

@ -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();

View File

@ -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';

View File

@ -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>;

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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:*"
}
}

View File

@ -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,

View File

@ -1,5 +1,5 @@
import { logger } from '@motajs/common';
import { mainSetting } from '@/core/main/setting';
import { mainSetting } from '@motajs/legacy-ui';
import {
Damage,
DamageRenderable,

View File

@ -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,