From fdd4563f4551343ecefa2c36de2e062bef72f5ea Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Wed, 21 Aug 2024 14:28:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8C=89=E9=94=AE=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/index.ts | 4 +- src/core/main/custom/hotkey.ts | 171 +++++++++++++++++++++++-------- src/core/main/custom/keyboard.ts | 2 +- src/core/main/init/hotkey.ts | 46 +++------ src/core/main/init/index.ts | 1 + src/core/main/init/settings.tsx | 2 +- src/core/main/init/toolbar.tsx | 2 +- src/plugin/game/fallback.ts | 3 +- src/ui/book.vue | 72 ++++++++----- src/ui/shop.vue | 20 ++-- src/ui/start.vue | 36 ++++--- src/ui/toolbox.vue | 70 ++++++++----- 12 files changed, 271 insertions(+), 158 deletions(-) diff --git a/src/core/index.ts b/src/core/index.ts index 8a3f5ad..0b56f3e 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -5,7 +5,6 @@ import { GameStorage } from './main/storage'; import './main/init/'; import './main/custom/toolbar'; import { fixedUi, mainUi } from './main/init/ui'; -import { gameKey } from './main/init/hotkey'; import { MotaSetting, SettingDisplayer, @@ -22,7 +21,8 @@ import { Hotkey, checkAssist, isAssist, - unwarpBinary + unwarpBinary, + gameKey } from './main/custom/hotkey'; import { Keyboard, generateKeyboardEvent } from './main/custom/keyboard'; import './main/layout'; diff --git a/src/core/main/custom/hotkey.ts b/src/core/main/custom/hotkey.ts index 2b23040..fbd48cc 100644 --- a/src/core/main/custom/hotkey.ts +++ b/src/core/main/custom/hotkey.ts @@ -1,22 +1,27 @@ import { KeyCode } from '@/plugin/keyCodes'; import { deleteWith, generateBinary, keycode, spliceBy } from '@/plugin/utils'; -import { EventEmitter } from '../../common/eventEmitter'; +import { EventEmitter } from 'eventemitter3'; +import { isNil } from 'lodash-es'; // todo: 按下时触发,长按(单次/连续)触发,按下连续触发,按下节流触发,按下加速节流触发 interface HotkeyEvent { - set: (id: string, key: KeyCode, assist: number) => void; - emit: (key: KeyCode, assist: number, type: KeyEmitType) => void; + set: [id: string, key: KeyCode, assist: number]; + emit: [key: KeyCode, assist: number, type: KeyEmitType]; + press: [key: KeyCode]; + release: [key: KeyCode]; } type KeyEmitType = - | 'down' | 'up' + | 'down' | 'down-repeat' | 'down-throttle' - | 'down-accelerate' + // todo: | 'down-accelerate' | 'down-timeout'; +type KeyEventType = 'up' | 'down'; + interface AssistHoykey { ctrl: boolean; shift: boolean; @@ -27,17 +32,17 @@ interface RegisterHotkeyData extends Partial { id: string; name: string; defaults: KeyCode; - type?: KeyEmitType; } interface HotkeyData extends Required { key: KeyCode; - func: Map; + emits: Map; group?: string; } /** * @param id 此处的id包含数字后缀 + * @returns 返回 `@void` 时,表示此次触发没有包含副作用,不会致使 `preventDefault` 被执行 */ type HotkeyFunc = ( id: string, @@ -45,6 +50,12 @@ type HotkeyFunc = ( ev: KeyboardEvent ) => void | '@void'; +interface HotkeyEmitData { + func: HotkeyFunc; + onUp?: HotkeyFunc; + config?: HotkeyEmitConfig; +} + export interface HotkeyJSON { key: KeyCode; assist: number; @@ -64,8 +75,6 @@ export class Hotkey extends EventEmitter { name: string; data: Record = {}; keyMap: Map = new Map(); - /** 每个指令的配置信息 */ - configData: Map = new Map(); /** id to name */ groupName: Record = { none: '未分类按键' @@ -81,6 +90,13 @@ export class Hotkey extends EventEmitter { private scopeStack: symbol[] = []; private grouping: string = 'none'; + /** 当前正在按下的按键 */ + private pressed: Set = new Set(); + /** 按键按下时的时间 */ + private pressTime: Map = new Map(); + /** 按键节流时间 */ + private throttleMap: Map = new Map(); + constructor(id: string, name: string) { super(); this.id = id; @@ -98,9 +114,8 @@ export class Hotkey extends EventEmitter { shift: !!data.shift, alt: !!data.alt, key: data.defaults, - func: new Map(), - group: this.grouping, - type: data.type ?? 'up' + emits: new Map(), + group: this.grouping }; this.ensureMap(d.key); if (d.id in this.data) { @@ -117,7 +132,7 @@ export class Hotkey extends EventEmitter { * 实现一个按键按下时的操作 * @param id 要实现的按键id,可以不包含数字后缀 * @param func 按键按下时执行的函数 - * @param config 按键触发配置,默认为按键抬起时触发 + * @param config 按键的配置信息 */ realize(id: string, func: HotkeyFunc, config?: HotkeyEmitConfig) { const toSet = Object.values(this.data).filter(v => { @@ -129,12 +144,24 @@ export class Hotkey extends EventEmitter { throw new Error(`Realize nonexistent key '${id}'.`); } for (const key of toSet) { - if (!key.func.has(this.scope)) { - throw new Error( - `Cannot access using scope. Call use before calling realize.` - ); + 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; + } } - key.func.set(this.scope, func); } return this; } @@ -148,9 +175,6 @@ export class Hotkey extends EventEmitter { this.scopeStack.push(symbol); this.scope = symbol; this.conditionMap.set(symbol, () => true); - for (const key of Object.values(this.data)) { - key.func.set(symbol, () => '@void'); - } } /** @@ -159,7 +183,7 @@ export class Hotkey extends EventEmitter { */ dispose(symbol: symbol = this.scopeStack.at(-1) ?? Symbol()) { for (const key of Object.values(this.data)) { - key.func.delete(symbol); + key.emits.delete(symbol); } spliceBy(this.scopeStack, symbol); this.scope = this.scopeStack.at(-1) ?? Symbol(); @@ -196,31 +220,106 @@ export class Hotkey extends EventEmitter { emitKey( key: KeyCode, assist: number, - type: KeyEmitType, + type: KeyEventType, ev: KeyboardEvent ): boolean { + // 检查全局启用情况 if (!this.enabled) return false; const when = this.conditionMap.get(this.scope)!; 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 (type !== v.type) return; if (ctrl === v.ctrl && shift === v.shift && alt === v.alt) { - const func = v.func.get(this.scope); - if (!func) { - throw new Error(`Emit unknown scope keys.`); + const data = v.emits.get(this.scope); + if (!data) return; + if (type === 'up' && data.onUp) { + data.onUp(v.id, key, ev); + return; } - const res = func(v.id, key, ev); + 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); + + if (type === 'down') this.checkPress(key); + else this.checkPressEnd(key); + 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 @@ -290,22 +389,6 @@ export class Hotkey extends EventEmitter { } } -// todo -/** - * 热键控制器,用于控制按下时触发等操作 - */ -export class HotkeyController { - /** 所有按下的按键 */ - private pressed: Set = new Set(); - - /** 当前控制器管理的热键实例 */ - hotkey: Hotkey; - - constructor(hotkey: Hotkey) { - this.hotkey = hotkey; - } -} - export function unwarpBinary(bin: number): AssistHoykey { return { ctrl: !!(bin & (1 << 0)), diff --git a/src/core/main/custom/keyboard.ts b/src/core/main/custom/keyboard.ts index a7cdb42..b008ebf 100644 --- a/src/core/main/custom/keyboard.ts +++ b/src/core/main/custom/keyboard.ts @@ -1,6 +1,6 @@ import { EventEmitter, Listener } from '@/core/common/eventEmitter'; import { KeyCode } from '@/plugin/keyCodes'; -import { gameKey } from '../init/hotkey'; +import { gameKey } from './hotkey'; import { unwarpBinary } from './hotkey'; import { deleteWith, flipBinary } from '@/plugin/utils'; import { cloneDeep } from 'lodash-es'; diff --git a/src/core/main/init/hotkey.ts b/src/core/main/init/hotkey.ts index f9d9847..6314c6d 100644 --- a/src/core/main/init/hotkey.ts +++ b/src/core/main/init/hotkey.ts @@ -15,25 +15,21 @@ gameKey .group('game', '游戏按键') .register({ id: 'moveUp', - type: 'down', name: '上移', defaults: KeyCode.UpArrow }) .register({ id: 'moveDown', - type: 'down', name: '下移', defaults: KeyCode.DownArrow }) .register({ id: 'moveLeft', - type: 'down', name: '左移', defaults: KeyCode.LeftArrow }) .register({ id: 'moveRight', - type: 'down', name: '右移', defaults: KeyCode.RightArrow }) @@ -293,78 +289,66 @@ gameKey .register({ id: '@start_up', name: '上移光标', - defaults: KeyCode.UpArrow, - type: 'down' + defaults: KeyCode.UpArrow }) .register({ id: '@start_down', name: '下移光标', - defaults: KeyCode.DownArrow, - type: 'down' + defaults: KeyCode.DownArrow }) // -------------------- .group('@ui_book', '怪物手册') .register({ id: '@book_up', name: '上移光标', - defaults: KeyCode.UpArrow, - type: 'down' + defaults: KeyCode.UpArrow }) .register({ id: '@book_down', name: '下移光标', - defaults: KeyCode.DownArrow, - type: 'down' + defaults: KeyCode.DownArrow }) .register({ id: '@book_pageDown_1', name: '下移5个怪物_1', - defaults: KeyCode.RightArrow, - type: 'down' + defaults: KeyCode.RightArrow }) .register({ id: '@book_pageDown_2', name: '下移5个怪物_2', - defaults: KeyCode.PageDown, - type: 'down' + defaults: KeyCode.PageDown }) .register({ id: '@book_pageUp_1', name: '上移5个怪物_1', - defaults: KeyCode.LeftArrow, - type: 'down' + defaults: KeyCode.LeftArrow }) .register({ id: '@book_pageUp_2', name: '上移5个怪物_2', - defaults: KeyCode.PageUp, - type: 'down' + defaults: KeyCode.PageUp }) // -------------------- .group('@ui_toolbox', '道具栏') .register({ id: '@toolbox_right', name: '光标右移', - defaults: KeyCode.RightArrow, - type: 'down' + defaults: KeyCode.RightArrow }) .register({ id: '@toolbox_left', name: '光标左移', - defaults: KeyCode.LeftArrow, - type: 'down' + defaults: KeyCode.LeftArrow }) .register({ id: '@toolbox_up', name: '光标上移', - defaults: KeyCode.UpArrow, - type: 'down' + defaults: KeyCode.UpArrow }) .register({ id: '@toolbox_down', name: '光标下移', - defaults: KeyCode.DownArrow, - type: 'down' + defaults: KeyCode.DownArrow }) // -------------------- .group('@ui_shop', '商店') @@ -381,14 +365,12 @@ gameKey .register({ id: '@shop_add', name: '增加购买量', - defaults: KeyCode.RightArrow, - type: 'down' + defaults: KeyCode.RightArrow }) .register({ id: '@shop_min', name: '减少购买量', - defaults: KeyCode.LeftArrow, - type: 'down' + defaults: KeyCode.LeftArrow }) // -------------------- .group('@ui_fly', '楼层传送') diff --git a/src/core/main/init/index.ts b/src/core/main/init/index.ts index 7e2a8e9..572e52e 100644 --- a/src/core/main/init/index.ts +++ b/src/core/main/init/index.ts @@ -1,2 +1,3 @@ import './fixed'; import './keyboard'; +import './hotkey'; diff --git a/src/core/main/init/settings.tsx b/src/core/main/init/settings.tsx index d5bb0c1..3538796 100644 --- a/src/core/main/init/settings.tsx +++ b/src/core/main/init/settings.tsx @@ -1,7 +1,7 @@ import type { SettingComponent, SettingComponentProps } from '../setting'; import { Button, InputNumber, Radio } from 'ant-design-vue'; import { mainUi } from './ui'; -import { gameKey } from './hotkey'; +import { gameKey } from '../custom/hotkey'; interface Components { Default: SettingComponent; diff --git a/src/core/main/init/toolbar.tsx b/src/core/main/init/toolbar.tsx index f59bd00..f6efa2f 100644 --- a/src/core/main/init/toolbar.tsx +++ b/src/core/main/init/toolbar.tsx @@ -17,7 +17,7 @@ import { } from 'ant-design-vue'; import { MotaSettingItem, mainSetting } from '../setting'; import Minimap from '@/components/minimap.vue'; -import { gameKey } from './hotkey'; +import { gameKey } from '../custom/hotkey'; import { FunctionalComponent, StyleValue, h } from 'vue'; import { mainUi } from './ui'; import { isMobile } from '@/plugin/use'; diff --git a/src/plugin/game/fallback.ts b/src/plugin/game/fallback.ts index 676d9d9..d7fa116 100644 --- a/src/plugin/game/fallback.ts +++ b/src/plugin/game/fallback.ts @@ -1,6 +1,5 @@ import type { RenderAdapter } from '@/core/render/adapter'; import type { HeroRenderer } from '@/core/render/preset/hero'; -import { backDir } from './utils'; interface Adapters { 'hero-adapter'?: RenderAdapter; @@ -9,7 +8,7 @@ interface Adapters { const adapters: Adapters = {}; export function init() { - if (!main.replayChecking) { + if (!main.replayChecking && main.mode === 'play') { const Adapter = Mota.require('module', 'Render').RenderAdapter; const hero = Adapter.get('hero-adapter'); diff --git a/src/ui/book.vue b/src/ui/book.vue index 92d140f..d4b70c1 100644 --- a/src/ui/book.vue +++ b/src/ui/book.vue @@ -151,34 +151,50 @@ function checkScroll() { setTimeout(() => { gameKey.use(props.ui.symbol); gameKey - .realize('@book_up', () => { - if (selected.value > 0) { - selected.value--; - } - checkScroll(); - }) - .realize('@book_down', () => { - if (selected.value < enemy.length - 1) { - selected.value++; - } - checkScroll(); - }) - .realize('@book_pageDown', () => { - if (selected.value <= 4) { - selected.value = 0; - } else { - selected.value -= 5; - } - checkScroll(); - }) - .realize('@book_pageUp', () => { - if (selected.value >= enemy.length - 5) { - selected.value = enemy.length - 1; - } else { - selected.value += 5; - } - checkScroll(); - }) + .realize( + '@book_up', + () => { + if (selected.value > 0) { + selected.value--; + } + checkScroll(); + }, + { type: 'down-repeat' } + ) + .realize( + '@book_down', + () => { + if (selected.value < enemy.length - 1) { + selected.value++; + } + checkScroll(); + }, + { type: 'down-repeat' } + ) + .realize( + '@book_pageDown', + () => { + if (selected.value <= 4) { + selected.value = 0; + } else { + selected.value -= 5; + } + checkScroll(); + }, + { type: 'down-repeat' } + ) + .realize( + '@book_pageUp', + () => { + if (selected.value >= enemy.length - 5) { + selected.value = enemy.length - 1; + } else { + selected.value += 5; + } + checkScroll(); + }, + { type: 'down-repeat' } + ) .realize('exit', () => { exit(); }) diff --git a/src/ui/shop.vue b/src/ui/shop.vue index 3f0c816..45101b8 100644 --- a/src/ui/shop.vue +++ b/src/ui/shop.vue @@ -289,12 +289,20 @@ gameKey selected.value++; } }) - .realize('@shop_add', () => { - count.value++; - }) - .realize('@shop_min', () => { - count.value--; - }) + .realize( + '@shop_add', + () => { + count.value++; + }, + { type: 'down-repeat' } + ) + .realize( + '@shop_min', + () => { + count.value--; + }, + { type: 'down-repeat' } + ) .realize('exit', () => { exit(); }) diff --git a/src/ui/start.vue b/src/ui/start.vue index b9b4999..9230204 100644 --- a/src/ui/start.vue +++ b/src/ui/start.vue @@ -192,20 +192,28 @@ function movein(button: HTMLElement, i: number) { gameKey.use(props.ui.symbol); gameKey - .realize('@start_up', () => { - const i = toshow.indexOf(selected.value); - const next = toshow[i + 1]; - if (!next) return; - selected.value = next; - setCursor(buttons[toshow.length - i - 2], toshow.length - i - 2); - }) - .realize('@start_down', () => { - const i = toshow.indexOf(selected.value); - const next = toshow[i - 1]; - if (!next) return; - selected.value = next; - setCursor(buttons[toshow.length - i], toshow.length - i); - }) + .realize( + '@start_up', + () => { + const i = toshow.indexOf(selected.value); + const next = toshow[i + 1]; + if (!next) return; + selected.value = next; + setCursor(buttons[toshow.length - i - 2], toshow.length - i - 2); + }, + { type: 'down-repeat' } + ) + .realize( + '@start_down', + () => { + const i = toshow.indexOf(selected.value); + const next = toshow[i - 1]; + if (!next) return; + selected.value = next; + setCursor(buttons[toshow.length - i], toshow.length - i); + }, + { type: 'down-repeat' } + ) .realize('confirm', () => { clickStartButton(selected.value); }); diff --git a/src/ui/toolbox.vue b/src/ui/toolbox.vue index 6e51d90..5bcc8c9 100644 --- a/src/ui/toolbox.vue +++ b/src/ui/toolbox.vue @@ -205,35 +205,51 @@ async function toEquip() { gameKey.use(props.ui.symbol); gameKey - .realize('@toolbox_right', () => { - const constants = items.constants.length; - if (mode.value === 'tools') { - if (index.value >= constants) { - index.value = constants - 1; + .realize( + '@toolbox_right', + () => { + const constants = items.constants.length; + if (mode.value === 'tools') { + if (index.value >= constants) { + index.value = constants - 1; + } + mode.value = 'constants'; } - mode.value = 'constants'; - } - }) - .realize('@toolbox_left', () => { - const constants = items.tools.length; - if (mode.value === 'constants') { - if (index.value >= constants) { - index.value = constants - 1; + }, + { type: 'down-repeat' } + ) + .realize( + '@toolbox_left', + () => { + const constants = items.tools.length; + if (mode.value === 'constants') { + if (index.value >= constants) { + index.value = constants - 1; + } + mode.value = 'tools'; } - mode.value = 'tools'; - } - }) - .realize('@toolbox_up', () => { - if (index.value > 0) { - index.value--; - } - }) - .realize('@toolbox_down', () => { - const total = items[mode.value].length; - if (index.value < total - 1) { - index.value++; - } - }) + }, + { type: 'down-repeat' } + ) + .realize( + '@toolbox_up', + () => { + if (index.value > 0) { + index.value--; + } + }, + { type: 'down-repeat' } + ) + .realize( + '@toolbox_down', + () => { + const total = items[mode.value].length; + if (index.value < total - 1) { + index.value++; + } + }, + { type: 'down-repeat' } + ) .realize('exit', () => { exit(); })