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

This commit is contained in:
AncTe committed 2025-03-05 21:16:37 +08:00
1 parent 9ec13e5314
commit 960dbd429a
79 files changed
+1872 -3616

No files matched your search

+1 -1
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');
+7 -57
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', {
-21
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>>;
-532
View File
@@ -1,532 +0,0 @@
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 axios, { AxiosResponse, toFormData } from 'axios';
import { Component, VNode, h, shallowReactive } from 'vue';
// /* @__PURE__ */ import { id, password } from '../../../../user';
import { mainSetting } from '../setting';
type CSSObj = Partial<Record<CanParseCss, string>>;
interface DanmakuResponse extends ResponseBase {
total: number;
list: DanmakuInfo[];
}
interface DanmakuInfo {
id: string;
comment: string;
tags: string;
love: string;
my_love_type: boolean;
userid: string;
deler: string;
upload_time: string;
tower_name: string;
}
interface DanmakuPostInfo extends Partial<DanmakuContentInfo> {
type: 1 | 2 | 3;
towername: 'HumanBreak';
id?: number;
}
interface DanmakuContentInfo {
comment: string;
tags: string;
}
interface PostDanmakuResponse extends ResponseBase {
id: number;
}
interface PostLikeResponse extends ResponseBase {
liked: boolean;
}
interface DanmakuEvent {
showStart: [danmaku: Danmaku];
showEnd: [danmaku: Danmaku];
like: [liked: boolean, danmaku: Danmaku];
}
type SpecContentFn = (content: string, type: string) => VNode;
interface AllowedCSS {
property: string;
check: (value: string, prop: string) => true | string;
}
const allowedCSS: Partial<Record<CanParseCss, AllowedCSS>> = {
color: {
property: 'color',
check: () => true
},
backgroundColor: {
property: 'backgroundColor',
check: () => true
},
fontSize: {
property: 'fontSize',
check: value => {
if (!/^\d+%$/.test(value)) {
return '字体大小只能设置为百分格式';
}
if (parseInt(value) > 200) {
return '字体最大只能为200%';
}
return true;
}
}
};
export class Danmaku extends EventEmitter<DanmakuEvent> {
static backend: string = `/backend/tower/barrage.php`;
static all: Set<Danmaku> = new Set();
static allInPos: Partial<Record<FloorIds, Record<LocString, Danmaku[]>>> =
{};
static showList: Danmaku[] = shallowReactive([]);
static showMap: Map<number, Danmaku> = new Map();
static specList: Record<string, SpecContentFn> = {};
static lastEditoredDanmaku?: Danmaku;
id: number = -1;
text: string = '';
x: number = 0;
y: number = 0;
floor?: FloorIds;
showing: boolean = false;
likedNum: number = 0;
liked: boolean = false;
style: CSSObj = {};
textColor: string = 'white';
strokeColor: string = 'black';
private posted: boolean = false;
vNode?: VNode;
private posting: boolean = false;
/**
* 发送弹幕
* @returns 弹幕发送的 Axios Post 信息,为 Promise
*/
async post(): Promise<AxiosResponse<PostDanmakuResponse>> {
if (this.posted || this.posting) {
logger.warn(5);
return Promise.reject();
}
const data: DanmakuPostInfo = {
type: 2,
towername: 'HumanBreak',
...this.encode()
};
this.posting = true;
const form = toFormData(data);
// /* @__PURE__ */ form.append('userid', id);
// /* @__PURE__ */ form.append('password', password);
try {
const res = await axios.post<PostDanmakuResponse>(
Danmaku.backend,
form
);
this.id = res.data.id;
this.posting = false;
if (res.data.code === 0) {
this.posted = true;
tip('success', '发送成功');
this.addToList();
} else {
tip('error', res.data.message);
}
return res;
} catch (e) {
this.posted = false;
this.posting = false;
logger.error(1, String(e));
return Promise.reject();
}
}
/**
* 将弹幕整合为可以发送的格式
*/
encode(): DanmakuContentInfo {
const css = this.getEncodedCSS();
return {
comment: this.text,
tags: JSON.stringify([
`!css:${JSON.stringify(css)}`,
`!pos:${this.x},${this.y},${this.floor}`
])
};
}
/**
* 解析弹幕信息
* @param info 要被解析的弹幕信息
*/
decode(info: DanmakuContentInfo) {
this.text = info.comment;
ensureArray(JSON.parse(info.tags) as string[]).forEach(v => {
if (v.startsWith('!css:')) {
this.style = JSON.parse(v.slice(5));
} else if (v.startsWith('!pos:')) {
const [x, y, f] = v.slice(5).split(',');
this.x = parseInt(x);
this.y = parseInt(y);
this.floor = f as FloorIds;
} else {
logger.warn(3, v);
}
});
}
getEncodedCSS() {
const css = JSON.parse(JSON.stringify(this.style)) as CSSObj;
if (!css.color) css.color = this.textColor;
if (!css.textShadow)
css.textShadow = `1px 1px 1px ${this.strokeColor}, 1px -1px 1px ${this.strokeColor}, -1px 1px 1px ${this.strokeColor}, -1px -1px 1px ${this.strokeColor}`;
return { ...css, ...this.style };
}
/**
* 设置文字的颜色
* @param fill 填充颜色
* @param stroke 描边颜色
*/
color(fill?: string, stroke?: string) {
fill && (this.textColor = fill);
stroke && (this.strokeColor = stroke);
}
/**
* 添加一个图标
* @param icon 要显示的图标id
*/
addIcon(icon: AllIds) {
this.text += `[i:${icon}]`;
}
/**
* 设置这个弹幕整体的css信息
* @param str css字符串
* @param overwrite 是否完全覆写原来的css
*/
css(str: string, overwrite?: boolean): void;
/**
* 设置这个弹幕整体的css信息
* @param str css对象,参考 CSSStyleDeclaration
* @param overwrite 是否完全覆写原来的css
*/
css(obj: CSSObj, overwrite?: boolean): void;
css(obj: string | CSSObj, overwrite: boolean = false) {
const res = typeof obj === 'string' ? parseCss(obj) : obj;
const allow = Danmaku.checkCSSAllow(res);
if (allow.length === 0) {
if (overwrite) this.style = res;
else {
this.style = { ...this.style, ...res };
}
} else {
logger.error(8, allow.join(','));
}
}
/**
* 将这个弹幕添加至弹幕列表
*/
addToList() {
Danmaku.all.add(this);
if (!this.floor) return;
Danmaku.allInPos[this.floor] ??= {};
Danmaku.allInPos[this.floor]![`${this.x},${this.y}`] ??= [];
Danmaku.allInPos[this.floor]![`${this.x},${this.y}`].push(this);
}
/**
* 解析这个弹幕为 VNode
*/
parse() {
let pointer = -1;
let ignore = false;
let str = '';
let spec = false;
let specType = '';
let specTypeEnd = false;
let specContent = '';
const children: VNode[] = [];
while (++pointer < this.text.length) {
const char = this.text[pointer];
if (char === '\\' && !ignore) {
ignore = true;
continue;
}
if (ignore) {
str += char;
continue;
}
if (char === '[') {
spec = true;
children.push(h('span', str));
str = '';
continue;
}
if (char === ']') {
if (!spec) {
logger.warn(4);
str += char;
} else {
spec = false;
specTypeEnd = false;
children.push(this.createSpecVNode(specType, specContent));
specType = '';
specContent = '';
}
continue;
}
if (spec) {
if (!specTypeEnd) {
if (char !== ':') {
specType += char;
} else {
specTypeEnd = true;
}
} else {
specContent += char;
}
continue;
}
str += char;
}
if (str.length > 0) {
children.push(h('span', str));
}
return h(
'span',
{ class: 'danmaku', style: this.getEncodedCSS() },
children
);
}
/**
* 获取本弹幕的VNode
*/
getVNode(nocache: boolean = false) {
if (nocache) return (this.vNode = this.parse());
return this.vNode ?? (this.vNode = this.parse());
}
/**
* 显示这个弹幕
*/
show() {
if (this.showing) return;
this.showing = true;
Danmaku.showList.push(this);
Danmaku.showMap.set(this.id, this);
this.emit('showStart', this);
}
/**
* 显示结束这个弹幕
*/
showEnd() {
if (!this.showing) return;
this.showing = false;
deleteWith(Danmaku.showList, this);
Danmaku.showMap.delete(this.id);
this.emit('showEnd', this);
}
/**
* 点赞或取消点赞
*/
async triggerLike() {
const post: DanmakuPostInfo = {
type: 3,
towername: 'HumanBreak',
id: this.id
};
const form = toFormData(post);
// /* @__PURE__ */ form.append('userid', id);
// /* @__PURE__ */ form.append('password', password);
const res = await axios.post<PostLikeResponse>(Danmaku.backend, form);
if (res.data.code !== 0) {
logger.warn(18, this.id.toString());
tip('error', `Error ${res.data.code}. ${res.data.message}`);
} else {
tip('success', res.data.message);
if (res.data.liked) {
this.liked = true;
this.likedNum++;
} else {
this.liked = false;
this.likedNum--;
}
this.emit('like', this.liked, this);
}
return res;
}
/**
* 销毁这个弹幕
*/
destroy() {
this.showEnd();
Danmaku.all.delete(this);
if (this.floor) {
const floor = Danmaku.allInPos[this.floor];
if (floor) {
delete floor[`${this.x},${this.y}`];
}
}
}
private createSpecVNode(type: string, content: string): VNode {
if (Danmaku.specList[type]) {
return Danmaku.specList[type](content, type);
} else {
logger.warn(7, type);
}
return h('span');
}
/**
* 检查CSS内容是否符合发弹幕要求
* @param css 要检查的CSS内容
*/
static checkCSSAllow(css: CSSObj) {
const problem: string[] = [];
for (const [key, value] of Object.entries(css)) {
if (!allowedCSS[key as CanParseCss]) {
problem.push(`不允许的CSS:${key}`);
continue;
} else {
const res = allowedCSS[key as CanParseCss]!.check(value, key);
if (res !== true) {
problem.push(res);
}
}
}
return problem;
}
/**
* 拉取本塔所有弹幕
*/
static async fetch() {
Danmaku.all.clear();
Danmaku.allInPos = {};
const form = toFormData({
type: 1,
towername: 'HumanBreak'
});
// /* @__PURE__ */ form.append('userid', id);
// /* @__PURE__ */ form.append('password', password);
const data = await axios.post<DanmakuResponse>(Danmaku.backend, form);
data.data.list.forEach(v => {
const dan = new Danmaku();
dan.id = parseInt(v.id);
dan.likedNum = parseInt(v.love);
dan.liked = v.my_love_type;
dan.decode(v);
dan.posted = true;
dan.addToList();
});
}
/**
* 显示一个弹幕
* @param dan 要显示的弹幕
*/
static show(dan: Danmaku) {
dan.show();
}
/**
* 注册一个特殊显示内容
* @param type 特殊内容类型
* @param fn 特殊内容显示函数,返回VNode
*/
static registerSpecContent(type: string, fn: SpecContentFn) {
if (this.specList[type]) {
logger.warn(6, type);
}
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);
});
}
}
});
-463
View File
@@ -1,463 +0,0 @@
import { KeyCode } from '@motajs/client-base';
import {
deleteWith,
generateBinary,
keycode,
spliceBy
} from '@motajs/legacy-ui';
import { EventEmitter } from 'eventemitter3';
import { isNil } from 'lodash-es';
// todo: 按下加速节流触发
interface HotkeyEvent {
set: [id: string, key: KeyCode, assist: number];
emit: [key: KeyCode, assist: number, type: KeyEmitType];
press: [key: KeyCode];
release: [key: KeyCode];
}
type KeyEmitType =
| 'up'
| 'down'
| 'down-repeat'
| 'down-throttle'
// todo: | 'down-accelerate'
| 'down-timeout';
type KeyEventType = 'up' | 'down';
interface AssistHoykey {
ctrl: boolean;
shift: boolean;
alt: boolean;
}
interface RegisterHotkeyData extends Partial<AssistHoykey> {
id: string;
name: string;
defaults: KeyCode;
}
export interface HotkeyData extends Required<RegisterHotkeyData> {
key: KeyCode;
emits: Map<symbol, HotkeyEmitData>;
group?: string;
}
/**
* @param id 此处的id包含数字后缀
* @returns 返回 `@void` 时,表示此次触发没有包含副作用,不会致使 `preventDefault` 被执行
*/
type HotkeyFunc = (
id: string,
code: KeyCode,
ev: KeyboardEvent
) => void | '@void';
interface HotkeyEmitData {
func: HotkeyFunc;
onUp?: HotkeyFunc;
config?: HotkeyEmitConfig;
}
export interface HotkeyJSON {
key: KeyCode;
assist: number;
}
export interface HotkeyEmitConfig {
type: KeyEmitType;
throttle?: number;
accelerate?: number;
timeout?: number;
}
export class Hotkey extends EventEmitter<HotkeyEvent> {
static list: Hotkey[];
id: string;
name: string;
data: Record<string, HotkeyData> = {};
keyMap: Map<KeyCode, HotkeyData[]> = new Map();
/** id to name */
groupName: Record<string, string> = {
none: '未分类按键'
};
/** id to keys */
groups: Record<string, string[]> = {
none: []
};
enabled: boolean = false;
conditionMap: Map<symbol, () => boolean> = new Map();
/** 当前作用域 */
scope: symbol = Symbol();
private scopeStack: symbol[] = [];
private grouping: string = 'none';
/** 当前正在按下的按键 */
private pressed: Set<KeyCode> = new Set();
/** 按键按下时的时间 */
private pressTime: Map<KeyCode, number> = new Map();
/** 按键节流时间 */
private throttleMap: Map<KeyCode, number> = new Map();
constructor(id: string, name: string) {
super();
this.id = id;
this.name = name;
}
/**
* 注册一个按键,id可以包含数字后缀,可以显示为同一个按键操作拥有多个按键可以触发
* @param data 要注册的按键信息
*/
register(data: RegisterHotkeyData) {
const d: HotkeyData = {
...data,
ctrl: !!data.ctrl,
shift: !!data.shift,
alt: !!data.alt,
key: data.defaults,
emits: new Map(),
group: this.grouping
};
this.ensureMap(d.key);
if (d.id in this.data) {
console.warn(`已存在id为${d.id}的按键,已将其覆盖`);
}
this.data[d.id] = d;
const arr = this.keyMap.get(d.key)!;
arr.push(d);
this.groups[this.grouping].push(d.id);
return this;
}
/**
* 实现一个按键按下时的操作
* @param id 要实现的按键id,可以不包含数字后缀
* @param func 按键按下时执行的函数
* @param config 按键的配置信息
*/
realize(id: string, func: HotkeyFunc, config?: HotkeyEmitConfig) {
const toSet = Object.values(this.data).filter(v => {
const split = v.id.split('_');
const last = !isNaN(Number(split.at(-1)));
return v.id === id || (last && split.slice(0, -1).join('_') === id);
});
if (toSet.length === 0) {
throw new Error(`Realize nonexistent key '${id}'.`);
}
for (const key of toSet) {
const data = key.emits.get(this.scope);
if (!data) {
key.emits.set(this.scope, { func, config });
} else {
// 同时注册抬起和按下
const dataType = data.config?.type ?? 'up';
const configType = config?.type ?? 'up';
if (dataType === 'up' && configType !== 'up') {
data.onUp = func;
data.config ??= { type: configType };
data.config.type = configType;
} else if (dataType !== 'up' && configType === 'up') {
data.onUp = func;
} else {
data.config = config;
data.func = func;
}
}
}
return this;
}
/**
* 使用一个symbol作为当前作用域,之后调用{@link realize}所实现的按键功能将会添加至此作用域
* @param symbol 当前作用域的symbol
*/
use(symbol: symbol) {
spliceBy(this.scopeStack, symbol);
this.scopeStack.push(symbol);
this.scope = symbol;
this.conditionMap.set(symbol, () => true);
}
/**
* 释放一个作用域,释放后作用域将退回至删除的作用域的上一级
* @param symbol 要释放的作用域的symbol
*/
dispose(symbol: symbol = this.scopeStack.at(-1) ?? Symbol()) {
for (const key of Object.values(this.data)) {
key.emits.delete(symbol);
}
spliceBy(this.scopeStack, symbol);
this.scope = this.scopeStack.at(-1) ?? Symbol();
}
/**
* 设置一个按键信息
* @param id 要设置的按键的id
* @param key 要设置成的按键
* @param assist 辅助按键,三位二进制数据,从低到高依次为`ctrl` `shift` `alt`
* @param emit 是否触发set事件,当且仅当从fromJSON方法调用时为false
*/
set(id: string, key: KeyCode, assist: number, emit: boolean = true) {
const { ctrl, shift, alt } = unwarpBinary(assist);
const data = this.data[id];
if (!data) return;
const before = this.keyMap.get(data?.key ?? KeyCode.Unknown)! ?? [];
deleteWith(before, data);
this.ensureMap(key);
const after = this.keyMap.get(key)!;
after.push(data);
data.key = key;
data.ctrl = ctrl;
data.shift = shift;
data.alt = alt;
if (emit) this.emit('set', id, key, assist);
}
/**
* 触发一个按键
* @param key 要触发的按键
* @param assist 辅助按键,三位二进制数据,从低到高依次为`ctrl` `shift` `alt`
* @returns 是否有按键被触发
*/
emitKey(
key: KeyCode,
assist: number,
type: KeyEventType,
ev: KeyboardEvent
): boolean {
// 检查全局启用情况
if (!this.enabled) return false;
const when = this.conditionMap.get(this.scope)!;
if (type === 'up') this.checkPressEnd(key);
else if (type === 'down') this.checkPress(key);
if (!when()) return false;
const toEmit = this.keyMap.get(key);
if (!toEmit) return false;
// 进行按键初始处理
const { ctrl, shift, alt } = unwarpBinary(assist);
// 真正开始触发按键
let emitted = false;
toEmit.forEach(v => {
if (ctrl === v.ctrl && shift === v.shift && alt === v.alt) {
const data = v.emits.get(this.scope);
if (!data) return;
if (type === 'up' && data.onUp) {
data.onUp(v.id, key, ev);
emitted = true;
return;
}
if (!this.canEmit(v.id, key, type, data)) return;
const res = data.func(v.id, key, ev);
if (res !== '@void') emitted = true;
}
});
this.emit('emit', key, assist, type);
return emitted;
}
/**
* 检查按键按下情况,如果没有按下则添加
* @param keyCode 按下的按键
*/
private checkPress(keyCode: KeyCode) {
if (this.pressed.has(keyCode)) return;
this.pressed.add(keyCode);
this.pressTime.set(keyCode, Date.now());
this.emit('press', keyCode);
}
/**
* 当按键松开时,移除相应的按下配置
* @param keyCode 松开的按键
*/
private checkPressEnd(keyCode: KeyCode) {
if (!this.pressed.has(keyCode)) return;
this.pressed.delete(keyCode);
this.pressTime.delete(keyCode);
this.emit('release', keyCode);
}
/**
* 检查一个按键是否可以被触发
* @param id 触发的按键id
* @param keyCode 按键码
*/
private canEmit(
_id: string,
keyCode: KeyCode,
type: KeyEventType,
data: HotkeyEmitData
) {
const config = data?.config;
// 这时默认为抬起触发,始终可触发
if (type === 'up') {
if (!config || config.type === 'up') return true;
else return false;
}
if (!config) return false;
// 按下单次触发
if (config.type === 'down') return !this.pressed.has(keyCode);
// 按下重复触发
if (config.type === 'down-repeat') return true;
if (config.type === 'down-timeout') {
const time = this.pressTime.get(keyCode);
if (isNil(time) || isNil(config.timeout)) return false;
return Date.now() - time >= config.timeout;
}
if (config.type === 'down-throttle') {
const thorttleTime = this.throttleMap.get(keyCode);
if (isNil(config.throttle)) return false;
if (isNil(thorttleTime)) {
this.throttleMap.set(keyCode, Date.now());
return true;
}
if (Date.now() - thorttleTime >= config.throttle) {
this.throttleMap.set(keyCode, Date.now());
return true;
}
return false;
}
}
/**
* 按键分组,执行后 register 的按键将会加入此分组
* @param id 组的id
* @param name 组的名称
*/
group(id: string, name: string, keys?: RegisterHotkeyData[]) {
this.grouping = id;
this.groupName[id] = name;
this.groups[id] ??= [];
keys?.forEach(v => this.register(v));
return this;
}
/**
* 启用这个按键控制器
*/
enable() {
this.enabled = true;
}
/**
* 禁用这个按键控制器
*/
disable() {
this.enabled = false;
}
/**
* 在当前作用域下,满足什么条件时触发按键
* @param fn 条件函数
*/
when(fn: () => boolean) {
this.conditionMap.set(this.scope, fn);
return this;
}
toJSON() {
const res: Record<string, HotkeyJSON> = {};
for (const [key, data] of Object.entries(this.data)) {
res[key] = {
key: data.key,
assist: generateBinary([data.ctrl, data.shift, data.alt])
};
}
return JSON.stringify(res);
}
fromJSON(data: string) {
const json: Record<string, HotkeyJSON> = JSON.parse(data);
for (const [key, data] of Object.entries(json)) {
this.set(key, data.key, data.assist, false);
}
}
private ensureMap(key: KeyCode) {
if (!this.keyMap.has(key)) {
this.keyMap.set(key, []);
}
}
/**
* 根据id获取hotkey实例
* @param id 要获取的hotkey实例的id
*/
static get(id: string) {
return this.list.find(v => v.id === id);
}
}
export function unwarpBinary(bin: number): AssistHoykey {
return {
ctrl: !!(bin & (1 << 0)),
shift: !!(bin & (1 << 1)),
alt: !!(bin & (1 << 2))
};
}
export function checkAssist(bin: number, key: KeyCode) {
return (
isAssist(key) &&
!!(
(1 << (key === KeyCode.Ctrl ? 0 : key === KeyCode.Shift ? 1 : 2)) &
bin
)
);
}
export function isAssist(key: KeyCode) {
return key === KeyCode.Ctrl || key === KeyCode.Shift || key === KeyCode.Alt;
}
export const gameKey = new Hotkey('gameKey', '游戏按键');
// ----- Listening
document.addEventListener('keyup', e => {
const assist = generateBinary([e.ctrlKey, e.shiftKey, e.altKey]);
const code = keycode(e.keyCode);
if (gameKey.emitKey(code, assist, 'up', e)) {
e.preventDefault();
if (core.status.holdingKeys) {
deleteWith(core.status.holdingKeys, e.keyCode);
}
} else {
// polyfill样板
if (main.dom.inputDiv.style.display == 'block') {
if (e.keyCode == 13) {
setTimeout(function () {
main.dom.inputYes.click();
}, 50);
} else if (e.keyCode == 27) {
setTimeout(function () {
main.dom.inputNo.click();
}, 50);
}
return;
}
if (
core &&
core.isPlaying &&
core.status &&
(core.isPlaying() || core.status.lockControl)
)
core.onkeyUp(e);
}
});
document.addEventListener('keydown', e => {
const assist = generateBinary([e.ctrlKey, e.shiftKey, e.altKey]);
const code = keycode(e.keyCode);
if (gameKey.emitKey(code, assist, 'down', e)) {
e.preventDefault();
}
});
-201
View File
@@ -1,201 +0,0 @@
import { EventEmitter, Listener } from '@motajs/legacy-common';
import { KeyCode } from '@motajs/client-base';
import { gameKey } from './hotkey';
import { unwarpBinary } from './hotkey';
import { deleteWith, flipBinary } from '@motajs/legacy-ui';
import { cloneDeep } from 'lodash-es';
import { shallowReactive } from 'vue';
export interface KeyboardEmits {
key: KeyCode;
assist: number;
}
interface KeyboardItem {
key: KeyCode;
text?: string;
x: number;
y: number;
width: number;
height: number;
}
interface VirtualKeyEmit {
preventDefault(): void;
preventAssist(): void;
}
type VirtualKeyEmitFn = (
item: KeyboardItem,
assist: number,
index: number,
ev: VirtualKeyEmit
) => void;
interface VirtualKeyboardEvent {
add: (item: KeyboardItem) => void;
remove: (item: KeyboardItem) => void;
extend: (extended: Keyboard) => void;
emit: VirtualKeyEmitFn;
scopeCreate: (scope: symbol) => void;
scopeDispose: (scope: symbol) => void;
}
/**
* 虚拟按键,同一个虚拟按键实例应当只能同时操作一个,但可以显示多个
*/
export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
static list: Keyboard[] = [];
id: string;
keys: KeyboardItem[] = [];
assist: number = 0;
fontSize: number = 18;
scope: symbol = Symbol();
private scopeStack: symbol[] = [];
private onEmitKey: Record<symbol, Listener<VirtualKeyEmitFn>[]> = {};
private scopeAssist: Record<symbol, number> = {};
constructor(id: string) {
super();
this.id = id;
Keyboard.list.push(this);
}
/**
* 给虚拟键盘添加一个按键
* @param item 按键信息
*/
add(item: KeyboardItem) {
const i = shallowReactive(item);
this.keys.push(i);
this.emit('add', i);
return this;
}
/**
* 移除一个按键
* @param item 按键信息
*/
remove(item: KeyboardItem) {
deleteWith(this.keys, item);
this.emit('remove', item);
return this;
}
/**
* 创造一个在某些辅助按键已经按下的情况下的作用域,这些被按下的辅助按键还可以被玩家手动取消
* @param assist 辅助按键
*/
withAssist(assist: number) {
const symbol = this.createScope();
this.assist = assist;
return symbol;
}
/**
* 创造一个虚拟按键作用域,所有监听的事件与其他作用域不冲突
* @returns 作用域的唯一标识符
*/
createScope() {
const last = this.scopeStack.at(-1);
const symbol = Symbol();
this.scope = symbol;
this.scopeStack.push(symbol);
const ev: Listener<VirtualKeyEmitFn>[] = [];
this.onEmitKey[symbol] = ev;
// @ts-ignore
this.events = ev;
this.emit('scopeCreate', symbol);
if (!!last) {
this.scopeAssist[symbol] = this.assist;
}
this.assist = 0;
this.scopeAssist[symbol] = 0;
return symbol;
}
/**
* 释放一个作用域,同时删除其中的所有监听器
*/
disposeScope() {
if (this.scopeStack.length === 0) {
throw new Error(
`Cannot dispose virtual key scope since there's no scope to be disposed.`
);
}
const now = this.scopeStack.pop()!;
delete this.onEmitKey[now];
delete this.scopeAssist[now];
const symbol = this.scopeStack.at(-1);
this.emit('scopeDispose', now);
if (!symbol) return;
this.scope = symbol;
this.assist = this.scopeAssist[symbol];
// @ts-ignore
this.events = this.onEmitKey[symbol];
}
/**
* 继承一个按键的按键信息
* @param keyboard 要被继承的按键
* @param offsetX 被继承的按键的横坐标偏移量
* @param offsetY 被继承的按键的纵坐标偏移量
*/
extend(keyboard: Keyboard, offsetX: number = 0, offsetY: number = 0) {
const toClone = cloneDeep(keyboard.keys);
toClone.forEach(v => {
v.x += offsetX;
v.y += offsetY;
});
this.keys.push(...toClone);
this.emit('extend', keyboard);
return this;
}
/**
* 触发按键
* @param key 要触发的按键
*/
emitKey(key: KeyboardItem, index: number) {
let prevent = false;
let preventAss = false;
const preventDefault = () => (prevent = true);
const preventAssist = () => (preventAss = true);
this.emit('emit', key, this.assist, index, {
preventDefault,
preventAssist
});
if (!preventAss) {
if (key.key === KeyCode.Ctrl) {
this.assist = flipBinary(this.assist, 0);
} else if (key.key === KeyCode.Shift) {
this.assist = flipBinary(this.assist, 1);
} else if (key.key === KeyCode.Alt) {
this.assist = flipBinary(this.assist, 2);
}
}
if (!prevent) {
const ev = generateKeyboardEvent(key.key, this.assist);
gameKey.emitKey(key.key, this.assist, 'up', ev);
}
}
static get(id: string) {
return this.list.find(v => v.id === id);
}
}
export function generateKeyboardEvent(key: KeyCode, assist: number) {
const { ctrl, alt, shift } = unwarpBinary(assist);
const ev = new KeyboardEvent('keyup', {
ctrlKey: ctrl,
shiftKey: shift,
altKey: alt
});
return ev;
}
-386
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();
});
-337
View File
@@ -1,337 +0,0 @@
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;
unfocus: (before: T | null) => void;
add: (item: T) => void;
pop: (item: T | null) => void;
splice: (spliced: T[]) => void;
}
export class Focus<T = any> extends EventEmitter<FocusEvent<T>> {
/** 显示列表 */
stack: T[];
focused: T | null = null;
/** 聚焦目标是否平等,在平等时,关闭聚焦目标不再会将其之后的目标全部删除,而是保留 */
readonly equal: boolean;
constructor(react: boolean = false, equal: boolean = false) {
super();
this.stack = react ? shallowReactive([]) : [];
this.equal = equal;
}
/**
* 聚焦于一个目标
* @param target 聚焦目标
* @param add 如果聚焦目标不在显示列表里面,是否自动追加
*/
focus(target: T, add: boolean = false) {
if (target === this.focused) return;
const before = this.focused;
if (!this.stack.includes(target)) {
if (add) {
this.add(target);
this.focused = target;
} else {
console.warn(
`聚焦于一个不存在的目标,同时没有传入自动追加的参数`,
`聚焦目标:${target}`
);
return;
}
} else {
this.focused = target;
}
this.emit('focus', before, this.focused);
}
/**
* 取消聚焦
*/
unfocus() {
const before = this.focused;
this.focused = null;
this.emit('unfocus', before);
}
/**
* 向显示列表中添加物品
* @param item 添加的物品
*/
add(item: T) {
this.stack.push(item);
this.emit('add', item);
}
/**
* 弹出显示列表中的最后一个物品
*/
pop() {
const item = this.stack.pop() ?? null;
const last = this.stack.at(-1) ?? null;
if (!last) this.unfocus();
else this.focus(last);
this.emit('pop', item);
return item;
}
/**
* 从一个位置开始删除显示列表,如果平等,则只会删除一个,否则会将其之后的所有的目标全部删除
* @param item 从哪开始删除,包括此项
*/
splice(item: T) {
const index = this.stack.indexOf(item);
this.spliceIndex(index);
}
/**
* 根据索引在显示列表中删除一项
* @param index 要删除的项的索引
*/
spliceIndex(index: number) {
if (index === -1) {
this.emit('splice', []);
return;
}
if (!this.stack[index]) return;
const last = this.stack.at(-1) ?? null;
if (!last) this.unfocus();
else this.focus(last);
this.emit(
'splice',
this.stack.splice(index, this.equal ? 1 : Infinity)
);
}
}
interface GameUiEvent {
close: () => void;
open: () => void;
}
interface ShowableGameUi {
ui: GameUi;
vOn?: UiVOn;
vBind?: UiVBind;
}
type UiVOn = Record<string, (param?: any) => void>;
type UiVBind = Record<string, any>;
interface MountedVBind {
num: number;
ui: GameUi;
[x: string]: any;
}
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) {
super();
this.component = component;
this.hotkey = hotkey;
this.id = id;
GameUi.uiList.push(this);
}
/**
* 根据 v-on 和 v-bind 创建可以显示的 ui 组件
* @param vOn 监听的事件
* @param vBind 绑定的数据
*/
with(vBind?: UiVBind, vOn?: UiVOn): ShowableGameUi {
return { ui: this, vOn, vBind };
}
}
interface IndexedGameUi extends ShowableGameUi {
num: number;
vBind?: MountedVBind;
}
interface HoldOnController {
end(noClosePanel?: boolean): void;
}
export class UiController extends Focus<IndexedGameUi> {
static list: UiController[] = [];
list: Record<string, GameUi> = {};
num: number = 0;
show: 'end' | 'all' = 'all';
private hold: boolean = false;
constructor(equal?: boolean) {
super(true, equal);
UiController.list.push(this);
this.on('splice', spliced => {
spliced.forEach(v => {
v.ui.emit('close');
});
if (this.stack.length === 0) {
if (!this.hold) this.emit('end', false);
this.hold = false;
}
});
this.on('add', item => {
if (this.stack.length === 1) {
this.emit('start');
}
item.ui.emit('open');
});
}
/**
* 设置为仅显示最后一个ui
*/
showEnd() {
this.show = 'end';
}
/**
* 设置为显示所有ui
*/
showAll() {
this.show = 'all';
}
/**
* 根据id获取到ui
* @param id ui的id
*/
get(id: string) {
return this.list[id];
}
/**
* 暂时保持下一次删除ui不会导致ui整体被关闭,引起ui背景闪烁。
* 例如可以用于道具栏,打开道具时就应当 holdOn,然后通过道具使用钩子来判断接下来是否要隐藏 app:
* ```txt
* hold on -> close -> use item -> hook -> stack.length === 0 ? end() : no action
* ```
*/
holdOn(): HoldOnController {
this.hold = true;
return {
end: (noClosePanel: boolean = false) => {
this.emit('end', noClosePanel);
}
};
}
/**
* 关闭一个ui,注意如果不是平等模式,在其之后的ui都会同时关闭掉
* @param num 要关闭的ui的唯一标识符
*/
close(num: number) {
const ui = this.stack.findIndex(v => v.num === num);
this.spliceIndex(ui);
}
/**
* 根据id关闭所有同id的ui,注意非平等模式下,会将第一个ui后的所有ui都关闭掉
* @param id 要关闭的ui的id
*/
closeByName(id: string) {
if (!this.equal) {
const ui = this.stack.findIndex(v => v.ui.id === id);
this.spliceIndex(ui);
} else {
let ui;
while ((ui = this.stack.findIndex(v => v.ui.id === id)) !== -1) {
this.spliceIndex(ui);
}
}
}
/**
* 打开一个新的ui
* @param id 要打开的ui的id
* @param vOn 监听的事件
* @param vBind 绑定的数据
* @returns ui的唯一标识符
*/
open(id: string, vBind?: UiVBind, vOn?: UiVOn) {
if (core.isReplaying() && !this.equal) return -1;
const ui = this.get(id);
if (!ui) {
console.warn(`Unknown UI: '${id}'.`);
return -1;
}
const num = this.num++;
const bind = {
num,
ui,
...(vBind ?? {})
};
const sui = ui.with(bind, vOn);
this.add({
num,
...sui
} as IndexedGameUi);
return num;
}
/**
* 注册一个ui
* @param id ui的id
* @param ui 对应的GameUi实例
*/
register(...ui: GameUi[]) {
ui.forEach(v => {
const id = v.id;
if (id in this.list) {
console.warn(`已存在id为'${id}'的ui,已将其覆盖`);
}
this.list[id] = v;
});
}
/**
* 取消注册一个ui
* @param id 要取消注册的ui的id
*/
unregister(...id: string[]) {
id.forEach(v => {
delete this.list[v];
});
return this;
}
/**
* 根据ui的唯一标识符进行聚焦
* @param num 要聚焦于的ui的唯一标识符
*/
focusByNum(num: number) {
const ui = this.getByNum(num);
if (!ui) return;
this.focus(ui);
}
/**
* 根据唯一标识符获取对应的ui
* @param num ui的唯一标识符
*/
getByNum(num: number) {
return this.stack.find(v => v.num === num);
}
/**
* 根据ui的唯一标识符来判断当前是否存在某个ui
* @param id ui的唯一标识符
*/
hasName(id: string) {
return this.stack.some(v => v.ui.id === id);
}
}
-64
View File
@@ -1,64 +0,0 @@
import { debounce } from 'lodash-es';
import { fixedUi, mainUi } from './ui';
import { ref } from 'vue';
import { sleep } from 'mutate-animate';
const close = ref(false);
let cx = 0;
let cy = 0;
/**
* 显示定点查看
*/
const showFixed = debounce((block: Block) => {
const e = core.material.enemys[block.event.id as EnemyIds];
if (!e) return;
const enemy = core.status.thisMap.enemy.get(block.x, block.y);
if (!enemy) return;
fixedUi.open(
'fixed',
{ enemy, close, loc: [cx, cy] },
{ close: closeFixed }
);
}, 200);
/**
* 关闭定点查看
*/
const closeFixed = () => {
close.value = true;
sleep(200).then(() => {
fixedUi.closeByName('fixed');
close.value = false;
});
};
// todo: 应当在这里实现查看临界与特殊属性的功能
export let hovered: Block | null;
const { hook, gameListener } = Mota.requireAll('var');
gameListener.on('hoverBlock', block => {
closeFixed();
hovered = block;
});
gameListener.on('leaveBlock', (_, __, leaveGame) => {
showFixed.cancel();
if (!leaveGame) closeFixed();
hovered = null;
});
gameListener.on('mouseMove', e => {
cx = e.clientX;
cy = e.clientY;
showFixed.cancel();
if (hovered) {
showFixed(hovered);
}
});
hook.once('mounted', () => {
mainUi.on('start', () => {
showFixed.cancel();
closeFixed();
});
});
-589
View File
@@ -1,589 +0,0 @@
import { KeyCode } from '@motajs/client-base';
import { gameKey, HotkeyJSON } from '../custom/hotkey';
import {
openDanmakuPoster,
tip,
hasMarkedEnemy,
markEnemy,
unmarkEnemy
} from '@motajs/legacy-ui';
import { hovered } from './fixed';
import { mainUi } from './ui';
import { GameStorage } from '../storage';
export const mainScope = Symbol.for('@key_main');
// todo: 读取上一个手动存档,存档至下一个存档栏
// ----- Register
gameKey
// --------------------
.group('game', '游戏按键')
.register({
id: 'moveUp',
name: '上移',
defaults: KeyCode.UpArrow
})
.register({
id: 'moveDown',
name: '下移',
defaults: KeyCode.DownArrow
})
.register({
id: 'moveLeft',
name: '左移',
defaults: KeyCode.LeftArrow
})
.register({
id: 'moveRight',
name: '右移',
defaults: KeyCode.RightArrow
})
// --------------------
.group('ui', 'ui界面')
.register({
id: 'book',
name: '怪物手册',
defaults: KeyCode.KeyX
})
.register({
id: 'save',
name: '存档界面',
defaults: KeyCode.KeyS
})
.register({
id: 'load',
name: '读档界面',
defaults: KeyCode.KeyD
})
.register({
id: 'toolbox',
name: '道具栏',
defaults: KeyCode.KeyT
})
.register({
id: 'equipbox',
name: '装备栏',
defaults: KeyCode.KeyQ
})
.register({
id: 'fly',
name: '楼层传送',
defaults: KeyCode.KeyG
})
.register({
id: 'menu',
name: '菜单',
defaults: KeyCode.Escape
})
.register({
id: 'replay',
name: '录像回放',
defaults: KeyCode.KeyR
})
.register({
id: 'shop',
name: '快捷商店',
defaults: KeyCode.KeyV
})
.register({
id: 'statistics',
name: '数据统计',
defaults: KeyCode.KeyB
})
.register({
id: 'viewMap_1',
name: '浏览地图_1',
defaults: KeyCode.PageUp
})
.register({
id: 'viewMap_2',
name: '浏览地图_2',
defaults: KeyCode.PageDown
})
.register({
id: 'skillTree',
name: '技能树',
defaults: KeyCode.KeyJ
})
.register({
id: 'desc',
name: '百科全书',
defaults: KeyCode.KeyH
})
// --------------------
.group('function', '功能按键')
.register({
id: 'undo_1',
name: '回退_1',
defaults: KeyCode.KeyA
})
.register({
id: 'undo_2',
name: '回退_2',
defaults: KeyCode.Digit5
})
.register({
id: 'redo_1',
name: '恢复_1',
defaults: KeyCode.KeyW
})
.register({
id: 'redo_2',
name: '恢复_2',
defaults: KeyCode.Digit6
})
.register({
id: 'turn',
name: '勇士转向',
defaults: KeyCode.KeyZ
})
.register({
id: 'getNext_1',
name: '轻按_1',
defaults: KeyCode.Space
})
.register({
id: 'getNext_2',
name: '轻按_2',
defaults: KeyCode.Digit7
})
.register({
id: 'mark',
name: '标记怪物',
defaults: KeyCode.KeyM
})
.register({
id: 'special',
name: '鼠标位置怪物属性',
defaults: KeyCode.KeyE
})
.register({
id: 'critical',
name: '鼠标位置怪物临界',
defaults: KeyCode.KeyC
})
.register({
id: 'danmaku',
name: '发送弹幕',
defaults: KeyCode.KeyA,
ctrl: true
})
.register({
id: 'quickEquip_1',
name: '切换/保存套装_1',
defaults: KeyCode.Digit1,
alt: true
})
.register({
id: 'quickEquip_2',
name: '切换/保存套装_2',
defaults: KeyCode.Digit2,
alt: true
})
.register({
id: 'quickEquip_3',
name: '切换/保存套装_3',
defaults: KeyCode.Digit3,
alt: true
})
.register({
id: 'quickEquip_4',
name: '切换/保存套装_4',
defaults: KeyCode.Digit4,
alt: true
})
.register({
id: 'quickEquip_5',
name: '切换/保存套装_5',
defaults: KeyCode.Digit5,
alt: true
})
.register({
id: 'quickEquip_6',
name: '切换/保存套装_6',
defaults: KeyCode.Digit6,
alt: true
})
.register({
id: 'quickEquip_7',
name: '切换/保存套装_7',
defaults: KeyCode.Digit7,
alt: true
})
.register({
id: 'quickEquip_8',
name: '切换/保存套装_8',
defaults: KeyCode.Digit8,
alt: true
})
.register({
id: 'quickEquip_9',
name: '切换/保存套装_9',
defaults: KeyCode.Digit9,
alt: true
})
.register({
id: 'quickEquip_0',
name: '切换/保存套装_0',
defaults: KeyCode.Digit0,
alt: true
})
// --------------------
.group('skill', '技能按键')
.register({
id: 'skill1',
name: '断灭之刃',
defaults: KeyCode.Digit1
})
.register({
id: 'skill2',
name: '跳跃',
defaults: KeyCode.Digit2
})
.register({
id: 'skill3',
name: '铸剑为盾',
defaults: KeyCode.Digit3
})
// --------------------
.group('system', '系统按键')
.register({
id: 'restart',
name: '回到开始界面',
defaults: KeyCode.KeyN
})
.register({
id: 'comment',
name: '评论区',
defaults: KeyCode.KeyP
})
.register({
id: 'debug',
name: '调试模式',
defaults: KeyCode.F8
})
// --------------------
.group('general', '通用按键')
.register({
id: 'exit_1',
name: '退出ui界面_1',
defaults: KeyCode.KeyX
})
.register({
id: 'exit_2',
name: '退出ui界面_2',
defaults: KeyCode.Escape
})
.register({
id: 'confirm_1',
name: '确认_1',
defaults: KeyCode.Enter
})
.register({
id: 'confirm_2',
name: '确认_2',
defaults: KeyCode.Space
})
.register({
id: 'confirm_3',
name: '确认_3',
defaults: KeyCode.KeyC
})
// --------------------
.group('@ui_start', '开始界面')
.register({
id: '@start_up',
name: '上移光标',
defaults: KeyCode.UpArrow
})
.register({
id: '@start_down',
name: '下移光标',
defaults: KeyCode.DownArrow
})
// --------------------
.group('@ui_book', '怪物手册')
.register({
id: '@book_up',
name: '上移光标',
defaults: KeyCode.UpArrow
})
.register({
id: '@book_down',
name: '下移光标',
defaults: KeyCode.DownArrow
})
.register({
id: '@book_pageDown_1',
name: '下移5个怪物_1',
defaults: KeyCode.RightArrow
})
.register({
id: '@book_pageDown_2',
name: '下移5个怪物_2',
defaults: KeyCode.PageDown
})
.register({
id: '@book_pageUp_1',
name: '上移5个怪物_1',
defaults: KeyCode.LeftArrow
})
.register({
id: '@book_pageUp_2',
name: '上移5个怪物_2',
defaults: KeyCode.PageUp
})
// --------------------
.group('@ui_toolbox', '道具栏')
.register({
id: '@toolbox_right',
name: '光标右移',
defaults: KeyCode.RightArrow
})
.register({
id: '@toolbox_left',
name: '光标左移',
defaults: KeyCode.LeftArrow
})
.register({
id: '@toolbox_up',
name: '光标上移',
defaults: KeyCode.UpArrow
})
.register({
id: '@toolbox_down',
name: '光标下移',
defaults: KeyCode.DownArrow
})
// --------------------
.group('@ui_shop', '商店')
.register({
id: '@shop_up',
name: '上移光标',
defaults: KeyCode.UpArrow
})
.register({
id: '@shop_down',
name: '下移光标',
defaults: KeyCode.DownArrow
})
.register({
id: '@shop_add',
name: '增加购买量',
defaults: KeyCode.RightArrow
})
.register({
id: '@shop_min',
name: '减少购买量',
defaults: KeyCode.LeftArrow
})
// --------------------
.group('@ui_fly', '楼层传送')
.register({
id: '@fly_left',
name: '左移地图',
defaults: KeyCode.LeftArrow
})
.register({
id: '@fly_right',
name: '右移地图',
defaults: KeyCode.RightArrow
})
.register({
id: '@fly_up',
name: '上移地图',
defaults: KeyCode.UpArrow
})
.register({
id: '@fly_down',
name: '下移地图',
defaults: KeyCode.DownArrow
})
.register({
id: '@fly_last',
name: '上一张地图',
defaults: KeyCode.PageDown
})
.register({
id: '@fly_next',
name: '下一张地图',
defaults: KeyCode.PageUp
})
// --------------------
.group('@ui_fly_tradition', '楼层传送-传统按键')
.register({
id: '@fly_down_t',
name: '上一张地图',
defaults: KeyCode.DownArrow
})
.register({
id: '@fly_up_t',
name: '下一张地图',
defaults: KeyCode.UpArrow
})
.register({
id: '@fly_left_t_1',
name: '前10张地图_1',
defaults: KeyCode.LeftArrow
})
.register({
id: '@fly_left_t_2',
name: '前10张地图_2',
defaults: KeyCode.PageDown
})
.register({
id: '@fly_right_t_1',
name: '后10张地图_1',
defaults: KeyCode.RightArrow
})
.register({
id: '@fly_right_t_2',
name: '后10张地图_2',
defaults: KeyCode.PageUp
});
gameKey.enable();
gameKey.use(mainScope);
// ----- Realization
gameKey
.when(
() =>
!core.status.lockControl && !core.isMoving() && !core.isReplaying()
)
.realize('book', () => {
core.openBook(true);
})
.realize('save', () => {
core.save(true);
})
.realize('load', () => {
core.load(true);
})
.realize('toolbox', () => {
core.openToolbox(true);
})
.realize('equipbox', () => {
core.openEquipbox(true);
})
.realize('fly', () => {
core.useFly(true);
})
.realize('menu', () => {
core.openSettings(true);
})
.realize('replay', () => {
core.ui._drawReplay();
})
.realize('shop', () => {
core.openQuickShop(true);
})
.realize('statistics', () => {
core.ui._drawStatistics();
})
.realize('viewMap', () => {
core.ui._drawViewMaps();
})
.realize('skillTree', () => {
core.useItem('skill1', true);
})
.realize('desc', () => {
core.useItem('I560', true);
})
.realize('undo', () => {
core.doSL('autoSave', 'load');
})
.realize('redo', () => {
core.doSL('autoSave', 'reload');
})
.realize('turn', () => {
core.turnHero();
})
.realize('getNext', () => {
core.getNextItem();
})
.realize('mark', () => {
const cls = hovered?.event.cls;
if (cls === 'enemys' || cls === 'enemy48') {
const id = hovered!.event.id as EnemyIds;
if (hasMarkedEnemy(id)) unmarkEnemy(id);
else markEnemy(id);
}
})
.realize('special', () => {
if (hovered) {
const { x, y } = hovered;
const enemy = core.status.thisMap.enemy.get(x, y);
if (enemy) mainUi.open('fixedDetail', { panel: 'special' });
}
})
.realize('critical', () => {
if (hovered) {
const { x, y } = hovered;
const enemy = core.status.thisMap.enemy.get(x, y);
if (enemy) mainUi.open('fixedDetail', { panel: 'critical' });
}
})
.realize('danmaku', () => {
openDanmakuPoster();
})
.realize('restart', () => {
core.confirmRestart();
})
.realize('comment', () => {
core.actions._clickGameInfo_openComments();
})
.realize('skill1', () => {
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
if (!HeroSkill.learnedSkill(HeroSkill.Blade)) return;
if (HeroSkill.getAutoSkill()) {
tip('error', '已开启自动切换技能!');
return;
}
core.playSound('光标移动');
HeroSkill.toggleSkill(HeroSkill.Blade);
core.status.route.push('useSkill:Blade');
core.updateStatusBar();
})
.realize('skill2', () => {
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
if (
!flags.onChase &&
!core.status.floorId.startsWith('tower') &&
HeroSkill.learnedSkill(HeroSkill.Jump)
) {
Mota.Plugin.require('skill_g').jumpSkill();
core.status.route.push('useSkill:Jump');
} else {
if (core.hasItem('pickaxe')) {
core.useItem('pickaxe');
}
}
})
.realize('skill3', () => {
const HeroSkill = Mota.require('module', 'Mechanism').HeroSkill;
if (!HeroSkill.learnedSkill(HeroSkill.Shield)) return;
if (HeroSkill.getAutoSkill()) {
tip('error', '已开启自动切换技能!');
return;
}
core.playSound('光标移动');
HeroSkill.toggleSkill(HeroSkill.Shield);
core.status.route.push('useSkill:Shield');
core.updateStatusBar();
})
.realize('debug', () => {
core.debug();
});
// ----- Storage
const keyStorage = new GameStorage<Record<string, HotkeyJSON>>(
GameStorage.fromAuthor('AncTe', 'gameKey')
);
keyStorage.data = {};
keyStorage.read();
gameKey.on('set', (id, key, assist) => {
keyStorage.setValue(id, { key, assist });
});
gameKey.fromJSON(keyStorage.toJSON());
-3
View File
@@ -1,3 +0,0 @@
import './fixed';
import './keyboard';
import './hotkey';
-888
View File
@@ -1,888 +0,0 @@
import { KeyCode } from '@motajs/client-base';
import { Keyboard } from '../custom/keyboard';
const qweKey = new Keyboard('qwe'); // 字母键盘,A-Z
const numKey = new Keyboard('num'); // 数字键盘,1-0
const charKey = new Keyboard('charKey'); // 符号键盘,!@<>等
const fnKey = new Keyboard('fn'); // Fn键盘,F1-F12
const assistKey = new Keyboard('assist'); // 辅助键盘,tab,capslk,shift,ctrl等
const arrowKey = new Keyboard('arrow'); // 方向键
const numpadKey = new Keyboard('numpad'); // 小键盘
const mediaKey = new Keyboard('media'); // 媒体按键
const toolKey = new Keyboard('tool'); // Home,End,Insert,Delete,PageUp,PageDn
const mainKey = new Keyboard('main'); // 主键盘,不包含小键盘和方向键
const toolArrowKey = new Keyboard('toolArrow'); // 辅助键盘和方向键
const miniKey = new Keyboard('mini'); // 小键盘,包含媒体按键
const fullKey = new Keyboard('full'); // 全部键盘
qweKey
.add({
key: KeyCode.KeyQ,
x: 0,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyW,
x: 50,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyE,
x: 100,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyR,
x: 150,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyT,
x: 200,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyY,
x: 250,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyU,
x: 300,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyI,
x: 350,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyO,
x: 400,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyP,
x: 450,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyA,
x: 10,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyS,
x: 60,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyD,
x: 110,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyF,
x: 160,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyG,
x: 210,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyH,
x: 260,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyJ,
x: 310,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyK,
x: 360,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyL,
x: 410,
y: 50,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyZ,
x: 40,
y: 100,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyX,
x: 90,
y: 100,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyC,
x: 140,
y: 100,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyV,
x: 190,
y: 100,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyB,
x: 240,
y: 100,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyN,
x: 290,
y: 100,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyM,
x: 340,
y: 100,
width: 45,
height: 45
});
numKey
.add({
key: KeyCode.Backquote,
x: 0,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>~<br />\`</span>`
})
.add({
key: KeyCode.Digit1,
x: 50,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>!<br />1</span>`
})
.add({
key: KeyCode.Digit2,
x: 100,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>@<br />2</span>`
})
.add({
key: KeyCode.Digit3,
x: 150,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>#<br />3</span>`
})
.add({
key: KeyCode.Digit4,
x: 200,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>\$<br />4</span>`
})
.add({
key: KeyCode.Digit5,
x: 250,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>%<br />5</span>`
})
.add({
key: KeyCode.Digit6,
x: 300,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>^<br />6</span>`
})
.add({
key: KeyCode.Digit7,
x: 350,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>&amp;<br />7</span>`
})
.add({
key: KeyCode.Digit8,
x: 400,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>*<br />8</span>`
})
.add({
key: KeyCode.Digit9,
x: 450,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>(<br />9</span>`
})
.add({
key: KeyCode.Digit0,
x: 500,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>)<br />0</span>`
})
.add({
key: KeyCode.Minus,
x: 550,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>—<br />-</span>`
})
.add({
key: KeyCode.Equal,
x: 600,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>+<br />=</span>`
})
.add({
key: KeyCode.Backspace,
x: 650,
y: 0,
width: 70,
height: 45,
text: `<span style='font-size: 60%'>Backspace</span>`
});
charKey
.add({
key: KeyCode.BracketLeft,
x: 110,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>{<br />[</span>`
})
.add({
key: KeyCode.BracketRight,
x: 160,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>}<br />]</span>`
})
.add({
key: KeyCode.Backslash,
x: 210,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>|<br />\\</span>`
})
.add({
key: KeyCode.Semicolon,
x: 70,
y: 50,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>:<br />;</span>`
})
.add({
key: KeyCode.Quote,
x: 120,
y: 50,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>"<br />'</span>`
})
.add({
key: KeyCode.Comma,
x: 0,
y: 100,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>&lt;<br />,</span>`
})
.add({
key: KeyCode.Period,
x: 50,
y: 100,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>&gt;<br />.</span>`
})
.add({
key: KeyCode.Slash,
x: 100,
y: 100,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>?<br />/</span>`
});
fnKey
.add({
key: KeyCode.Escape,
x: 0,
y: 0,
width: 45,
height: 45,
text: `Esc`
})
.add({
key: KeyCode.F1,
x: 50 + 25,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F2,
x: 100 + 25,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F3,
x: 150 + 25,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F4,
x: 200 + 25,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F5,
x: 250 + 50,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F6,
x: 300 + 50,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F7,
x: 350 + 50,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F8,
x: 400 + 50,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F9,
x: 450 + 75,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F10,
x: 500 + 75,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F11,
x: 550 + 75,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.F12,
x: 600 + 75,
y: 0,
width: 45,
height: 45
});
assistKey
.add({
key: KeyCode.Tab,
x: 0,
y: 0,
width: 70,
height: 45
})
.add({
key: KeyCode.CapsLock,
x: 0,
y: 50,
width: 80,
height: 45,
text: `<span style='font-size: 80%'>CapsLk</span>`
})
.add({
key: KeyCode.Shift,
x: 0,
y: 100,
width: 110,
height: 45,
text: `<span style='font-size: 80%'>Shift</span>`
})
.add({
key: KeyCode.Ctrl,
x: 0,
y: 150,
width: 75,
height: 45,
text: `<span style='font-size: 80%'>Ctrl</span>`
})
.add({
key: KeyCode.Meta,
x: 80,
y: 150,
width: 45,
height: 45,
text: /* html */ `<span style='font-size: 130%; display: flex; justify-content: center; align-items: center'>
<svg style="width: 1em; height: 1em" viewbox="64 64 896 896" fill="currentColor">
<path d="M523.8 191.4v288.9h382V128.1zm0 642.2l382 62.2v-352h-382zM120.1 480.2H443V201.9l-322.9 53.5zm0 290.4L443 823.2V543.8H120.1z" />
</svg>
</span>`
})
.add({
key: KeyCode.Alt,
x: 130,
y: 150,
width: 70,
height: 45,
text: `<span style='font-size: 80%'>Alt</span>`
})
.add({
key: KeyCode.Space,
x: 205,
y: 150,
width: 320,
height: 45,
text: ``
})
.add({
key: KeyCode.Alt,
x: 530,
y: 150,
width: 70,
height: 45,
text: `<span style='font-size: 80%'>Alt</span>`
})
.add({
key: KeyCode.Meta,
x: 605,
y: 150,
width: 45,
height: 45,
text: /* html */ `<span style='font-size: 130%; display: flex; justify-content: center; align-items: center'>
<svg style="width: 1em; height: 1em" viewbox="64 64 896 896" fill="currentColor">
<path d="M523.8 191.4v288.9h382V128.1zm0 642.2l382 62.2v-352h-382zM120.1 480.2H443V201.9l-322.9 53.5zm0 290.4L443 823.2V543.8H120.1z" />
</svg>
</span>`
})
.add({
key: KeyCode.Ctrl,
x: 655,
y: 150,
width: 65,
height: 45,
text: `<span style='font-size: 80%'>Ctrl</span>`
})
.add({
key: KeyCode.Shift,
x: 615,
y: 100,
width: 105,
height: 45,
text: `<span style='font-size: 80%'>Shift</span>`
})
.add({
key: KeyCode.Enter,
x: 635,
y: 50,
width: 85,
height: 45,
text: `<span style='font-size: 80%'>Enter</span>`
});
arrowKey
.add({
key: KeyCode.UpArrow,
x: 50,
y: 0,
width: 45,
height: 45,
text: `↑`
})
.add({
key: KeyCode.LeftArrow,
x: 0,
y: 50,
width: 45,
height: 45,
text: `←`
})
.add({
key: KeyCode.DownArrow,
x: 50,
y: 50,
width: 45,
height: 45,
text: `↓`
})
.add({
key: KeyCode.RightArrow,
x: 100,
y: 50,
width: 45,
height: 45,
text: `→`
});
toolKey
.add({
key: KeyCode.Insert,
x: 0,
y: 60,
width: 45,
height: 45,
text: `<span style='font-size: 70%'>Insert</span>`
})
.add({
key: KeyCode.Delete,
x: 0,
y: 110,
width: 45,
height: 45,
text: `<span style='font-size: 70%'>Delete</span>`
})
.add({
key: KeyCode.Home,
x: 50,
y: 60,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>Home</span>`
})
.add({
key: KeyCode.End,
x: 50,
y: 110,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>End</span>`
})
.add({
key: KeyCode.PageUp,
x: 100,
y: 60,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>PgUp</span>`
})
.add({
key: KeyCode.PageDown,
x: 100,
y: 110,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>PgDn</span>`
})
.add({
key: KeyCode.ScrollLock,
x: 0,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 80%'>ScrLk</span>`
})
.add({
key: KeyCode.PauseBreak,
x: 100,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 70%'>Pause<br />Break</span>`
});
numpadKey
.add({
key: KeyCode.NumLock,
x: 0,
y: 0,
width: 45,
height: 45,
text: `<span style='font-size: 70%'>NumLk</span>`
})
.add({
key: KeyCode.NumpadDivide,
x: 50,
y: 0,
width: 45,
height: 45,
text: `/`
})
.add({
key: KeyCode.NumpadMultiply,
x: 100,
y: 0,
width: 45,
height: 45,
text: `*`
})
.add({
key: KeyCode.NumpadSubtract,
x: 150,
y: 0,
width: 45,
height: 45,
text: `-`
})
.add({
key: KeyCode.NumpadAdd,
x: 150,
y: 50,
width: 45,
height: 95,
text: `+`
})
.add({
key: KeyCode.Enter,
x: 150,
y: 150,
width: 45,
height: 95,
text: `<span style='font-size: 80%'>Enter</span>`
})
.add({
key: KeyCode.NumpadDecimal,
x: 100,
y: 200,
width: 45,
height: 45,
text: `.`
})
.add({
key: KeyCode.Numpad0,
x: 0,
y: 200,
width: 95,
height: 45,
text: `0`
})
.add({
key: KeyCode.Numpad7,
x: 0,
y: 50,
width: 45,
height: 45,
text: `7`
})
.add({
key: KeyCode.Numpad8,
x: 50,
y: 50,
width: 45,
height: 45,
text: `8`
})
.add({
key: KeyCode.Numpad9,
x: 100,
y: 50,
width: 45,
height: 45,
text: `9`
})
.add({
key: KeyCode.Numpad4,
x: 0,
y: 100,
width: 45,
height: 45,
text: `4`
})
.add({
key: KeyCode.Numpad5,
x: 50,
y: 100,
width: 45,
height: 45,
text: `5`
})
.add({
key: KeyCode.Numpad6,
x: 100,
y: 100,
width: 45,
height: 45,
text: `6`
})
.add({
key: KeyCode.Numpad1,
x: 0,
y: 150,
width: 45,
height: 45,
text: `1`
})
.add({
key: KeyCode.Numpad2,
x: 50,
y: 150,
width: 45,
height: 45,
text: `2`
})
.add({
key: KeyCode.Numpad3,
x: 100,
y: 150,
width: 45,
height: 45,
text: `3`
});
mediaKey
.add({
key: KeyCode.MediaPlayPause,
x: 0,
y: 0,
width: 45,
height: 45,
text: /* html */ `<span style='font-size: 90%; display: flex; justify-content: center; align-items: center'>
<svg style="width: 1em; height: 1em" viewbox="64 64 896 896" fill="currentColor">
<path d="M715.8 493.5L335 165.1c-14.2-12.2-35-1.2-35 18.5v656.8c0 19.7 20.8 30.7 35 18.5l380.8-328.4c10.9-9.4 10.9-27.6 0-37z" />
</svg>
/
<svg style="width: 1em; height: 1em" viewbox="64 64 896 896" fill="currentColor">
<path d="M304 176h80v672h-80zm408 0h-64c-4.4 0-8 3.6-8 8v656c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V184c0-4.4-3.6-8-8-8z" />
</svg>
</span>`
})
.add({
key: KeyCode.MediaStop,
x: 50,
y: 0,
width: 45,
height: 45,
text: /* html */ `<span style='font-size: 80%; display: flex; justify-content: center; align-items: center'>
<svg style="width: 1em; height: 1em" viewbox="64 64 896 896" fill="currentColor">
<rect x="0" y="0" width="100%" height="100%" />
</svg>
</span>`
})
.add({
key: KeyCode.MediaTrackPrevious,
x: 100,
y: 0,
width: 45,
height: 45,
text: /* html */ `<span style='font-size: 110%; display: flex; justify-content: center; align-items: center'>
<svg style="width: 1em; height: 1em" viewbox="64 64 896 896" fill="currentColor">
<path d="M517.6 273.5L230.2 499.3a16.14 16.14 0 000 25.4l287.4 225.8c10.7 8.4 26.4.8 26.4-12.7V286.2c0-13.5-15.7-21.1-26.4-12.7zm320 0L550.2 499.3a16.14 16.14 0 000 25.4l287.4 225.8c10.7 8.4 26.4.8 26.4-12.7V286.2c0-13.5-15.7-21.1-26.4-12.7zm-620-25.5h-51.2c-3.5 0-6.4 2.7-6.4 6v516c0 3.3 2.9 6 6.4 6h51.2c3.5 0 6.4-2.7 6.4-6V254c0-3.3-2.9-6-6.4-6z" />
</svg>
</span>`
})
.add({
key: KeyCode.MediaTrackNext,
x: 150,
y: 0,
width: 45,
height: 45,
text: /* html */ `<span style='font-size: 110%; display: flex; justify-content: center; align-items: center'>
<svg style="width: 1em; height: 1em" viewbox="64 64 896 896" fill="currentColor">
<path d="M793.8 499.3L506.4 273.5c-10.7-8.4-26.4-.8-26.4 12.7v451.6c0 13.5 15.7 21.1 26.4 12.7l287.4-225.8a16.14 16.14 0 000-25.4zm-320 0L186.4 273.5c-10.7-8.4-26.4-.8-26.4 12.7v451.5c0 13.5 15.7 21.1 26.4 12.7l287.4-225.8c4.1-3.2 6.2-8 6.2-12.7 0-4.6-2.1-9.4-6.2-12.6zM857.6 248h-51.2c-3.5 0-6.4 2.7-6.4 6v516c0 3.3 2.9 6 6.4 6h51.2c3.5 0 6.4-2.7 6.4-6V254c0-3.3-2.9-6-6.4-6z" />
</svg>
</span>`
});
toolArrowKey.extend(arrowKey, 0, 210).extend(toolKey, 0, 0);
miniKey.extend(numpadKey, 0, 0);
mainKey
.extend(numKey, 0, 60)
.extend(qweKey, 75, 110)
.extend(charKey, 465, 110)
.extend(fnKey, 0, 0)
.extend(assistKey, 0, 110);
fullKey
.extend(mainKey, 0, 0)
.extend(toolArrowKey, 740, 0)
.extend(miniKey, 905, 60)
.extend(mediaKey, 905, 0);
-15
View File
@@ -1,15 +0,0 @@
import { Keyboard } from '../custom/keyboard';
import KeyboardUI from '@/panel/keyboard.vue';
interface VirtualKeyProps {
keyboard: Keyboard;
}
export function VirtualKey(props: VirtualKeyProps) {
return (
<KeyboardUI
style="align-self: center"
keyboard={props.keyboard}
></KeyboardUI>
);
}
-199
View File
@@ -1,199 +0,0 @@
import type { SettingComponent, SettingComponentProps } from '../setting';
import { Button, InputNumber, Radio } from 'ant-design-vue';
import { mainUi } from './ui';
import { gameKey } from '../custom/hotkey';
interface Components {
Default: SettingComponent;
Boolean: SettingComponent;
Number: SettingComponent;
HotkeySetting: SettingComponent;
ToolbarEditor: SettingComponent;
Radio: (items: string[]) => SettingComponent;
Performance: SettingComponent;
}
export type { Components as SettingDisplayComponents };
export function createSettingComponents() {
const com: Components = {
Default: DefaultSetting,
Boolean: BooleanSetting,
Number: NumberSetting,
HotkeySetting,
ToolbarEditor,
Radio: RadioSetting,
Performance: PerformanceSetting
};
return com;
}
function DefaultSetting(props: SettingComponentProps) {
return (
<div>
<span> 未知的设置类型 </span>
</div>
);
}
function BooleanSetting(props: SettingComponentProps) {
const { setting, displayer, item } = props;
const changeValue = () => {
setting.setValue(displayer.selectStack.join('.'), !item.value);
displayer.update();
};
return (
<div class="editor-boolean">
<span>当前 {item.value ? '开启' : '关闭'}</span>
<Button
class="boolean-button"
type="primary"
size="large"
onClick={changeValue}
>
{item.value ? '关闭' : '开启'}设置
</Button>
</div>
);
}
function NumberSetting(props: SettingComponentProps) {
const { setting, displayer, item } = props;
const changeValue = (value: number) => {
if (typeof value !== 'number') return;
if (value < (item.step?.[0] ?? 0) || value > (item.step?.[1] ?? 100)) {
return;
}
setting.setValue(displayer.selectStack.join('.'), Math.round(value));
displayer.update();
};
return (
<div class="editor-number">
<div class="editor-number-input">
<span> 修改设置: </span>
<InputNumber
class="number-input"
size="large"
// keyboard={true}
min={item.step?.[0] ?? 0}
max={item.step?.[1] ?? 100}
step={item.step?.[2] ?? 1}
value={item.value as number}
onChange={value => changeValue(value as number)}
></InputNumber>
</div>
<div class="editor-number-button">
<Button
class="number-button"
type="primary"
onClick={() =>
changeValue(
(item.value as number) - (item.step?.[2] ?? 1)
)
}
>
减少
</Button>
<Button
class="number-button"
type="primary"
onClick={() =>
changeValue(
(item.value as number) + (item.step?.[2] ?? 1)
)
}
>
增加
</Button>
</div>
</div>
);
}
function RadioSetting(items: string[]) {
return (props: SettingComponentProps) => {
const { setting, displayer, item } = props;
const changeValue = (value: number) => {
if (isNaN(value)) return;
setting.setValue(displayer.selectStack.join('.'), value);
displayer.update();
};
return (
<div>
{items.map((v, i) => {
return (
<Radio
value={i}
checked={i === item.value}
onClick={() => changeValue(i)}
>
{v}
</Radio>
);
})}
</div>
);
};
}
function showSpecialSetting(id: string, vBind?: any) {
const ui = mainUi.get(id);
mainUi.showEnd();
ui.once('close', () => {
mainUi.showAll();
});
mainUi.open(id, vBind);
}
function HotkeySetting(props: SettingComponentProps) {
return (
<div style="display: flex; justify-content: center">
<Button
style="font-size: 75%"
type="primary"
size="large"
onClick={() =>
showSpecialSetting('hotkey', {
hotkey: gameKey
})
}
>
快捷键设置
</Button>
</div>
);
}
function ToolbarEditor(props: SettingComponentProps) {
return (
<div style="display: flex; justify-content: center">
<Button
style="font-size: 75%"
type="primary"
size="large"
onClick={() => showSpecialSetting('toolEditor')}
>
自定义工具栏
</Button>
</div>
);
}
function PerformanceSetting(props: SettingComponentProps) {
return (
<div style="display: flex; justify-content: center">
<Button
style="font-size: 75%"
type="primary"
size="large"
onClick={() => showSpecialSetting('performance')}
>
打开性能界面
</Button>
</div>
);
}
File diff suppressed because it is too large. Load diff
-74
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';
});
});
-574
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();
});
-116
View File
@@ -1,116 +0,0 @@
import { isNil } from 'lodash-es';
export class GameStorage<T extends object = any> {
static list: GameStorage<any>[] = [];
key: string;
data!: Partial<T>;
constructor(key: string) {
this.key = key;
this.read();
GameStorage.list.push(this);
}
/**
* 从本地存储读取
*/
read(): T {
const data = localStorage.getItem(this.key) ?? '{}';
return (this.data = JSON.parse(data));
}
/**
* 写入本地存储
*/
write() {
localStorage.setItem(this.key, JSON.stringify(this.data));
}
/**
* 设置存储的值
* @param key 存储的名称
* @param value 存储的值
*/
setValue<K extends keyof T>(key: K, value: T[K]): void;
setValue(key: string, value: any): void;
setValue<K extends keyof T>(key: K, value: T[K]) {
this.data[key] = value;
}
getValue<K extends keyof T>(key: K): T[K] | null;
getValue<K extends keyof T>(key: K, defaults: T[K]): T[K];
getValue<T>(key: string, defaults?: T): T;
getValue<K extends keyof T>(key: K, defaults?: T[K]) {
if (!isNil(this.data[key])) return this.data[key];
else {
if (defaults !== void 0) {
this.data[key] = defaults;
return defaults;
}
return null;
}
}
toJSON() {
return JSON.stringify(this.data);
}
clear() {
this.data = {};
}
keys() {
return Object.keys(this.data);
}
values() {
return Object.values(this.data);
}
entries() {
return Object.entries(this.data);
}
/**
* 获取本游戏的存储键
* @param key 存储名称
*/
static fromGame(key: string) {
return `HumanBreak_${key}`;
}
/**
* 获取与作者相关联的存储键
* @param author 作者名称
* @param key 存储名称
*/
static fromAuthor(author: string, key: string) {
return `${author}@${key}`;
}
/**
* 根据存储键获取对应的存储实例
* @param key 存储键
* @example Storage.get(Storage.fromAuthor('AncTe', 'setting'));
*/
static get(key: string) {
return this.list.find(v => v.key === key);
}
/**
* 与Symbol.for类似
*/
static for(key: string) {
const s = this.get(key);
if (s) return s;
else return new GameStorage(key);
}
}
window.addEventListener('beforeunload', () => {
GameStorage.list.forEach(v => v.write());
});
window.addEventListener('blur', () => {
GameStorage.list.forEach(v => v.write());
});
-15
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);
+1 -121
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;
}
}
+3 -48
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
+17 -14
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 => {
-127
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();
+1 -1
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';
@@ -1,8 +1,8 @@
import { KeyCode } from '@motajs/client-base';
import { Hotkey, HotkeyData } from '../custom/hotkey';
import { Hotkey, HotkeyData } from '@motajs/system-action';
import type { HeroMover, IMoveController } from '@/game/state/move';
import { Ticker } from 'mutate-animate';
import { mainScope } from '../init/hotkey';
import { mainScope } from '@motajs/legacy-ui';
type MoveKey = Record<Dir, HotkeyData>;
type MoveKeyConfig = Record<Dir, string>;
+1 -1
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';
+3 -3
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';
+2 -2
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';
+3 -1
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:*"
}
}
+1 -1
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,
+1 -1
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,
+1 -1
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,