refactor: 按键系统

This commit is contained in:
unanmed 2023-11-17 23:32:16 +08:00
parent d8c98ad7d5
commit 49e09384fa
2 changed files with 303 additions and 301 deletions

View File

@ -4,336 +4,342 @@ import { deleteWith, generateBinary, has, tip } from '@/plugin/utils';
import { EmitableEvent, EventEmitter } from '../../common/eventEmitter'; import { EmitableEvent, EventEmitter } from '../../common/eventEmitter';
import { GameStorage } from '../storage'; import { GameStorage } from '../storage';
interface HotkeyEvent extends EmitableEvent { interface HotkeyEvent extends EmitableEvent {}
emit: (key: KeyCode, e: KeyboardEvent, ...params: any[]) => void;
keyChange: (data: HotkeyData, before: KeyCode, after: KeyCode) => void; interface AssistHoykey {
ctrl: boolean;
shift: boolean;
alt: boolean;
} }
interface HotkeyData { interface RegisterHotkeyData extends Partial<AssistHoykey> {
id: string; id: string;
name: string; name: string;
defaults: KeyCode; defaults: KeyCode;
}
interface HotkeyData extends Required<RegisterHotkeyData> {
key: KeyCode; key: KeyCode;
ctrl?: boolean; func: Map<symbol, HotkeyFunc>;
alt?: boolean;
shift?: boolean;
group?: string;
func: (code: KeyCode, e: KeyboardEvent) => any;
} }
interface GroupInfo { type HotkeyFunc = (code: KeyCode, ev: KeyboardEvent) => void;
name: string;
includes: string[];
}
type RegisterData = Omit<HotkeyData, 'id' | 'key' | 'name'>;
export class Hotkey extends EventEmitter<HotkeyEvent> { export class Hotkey extends EventEmitter<HotkeyEvent> {
static list: Hotkey[];
id: string;
name: string;
data: Record<string, HotkeyData> = {};
keyMap: Map<KeyCode, HotkeyData[]> = new Map(); keyMap: Map<KeyCode, HotkeyData[]> = new Map();
list: Record<string, HotkeyData> = {};
storage?: GameStorage<Record<string, KeyCode>>;
groups: Record<string, GroupInfo> = {};
constructor(id: string, storage: boolean = true) { private scope: symbol = Symbol();
constructor(id: string, name: string) {
super(); super();
if (storage) { this.id = id;
this.storage = new GameStorage(GameStorage.fromAuthor('AncTe', id)); this.name = name;
}
} }
/** /**
* *
* @param data * @param data
*/ */
register(id: string, name: string, data: RegisterData) { register(data: RegisterHotkeyData) {
const key = { const d: HotkeyData = {
id, ...data,
name, ctrl: !!data.ctrl,
key: this.storage?.getValue(id, data.defaults) ?? data.defaults, shift: !!data.shift,
...data alt: !!data.alt,
key: data.defaults,
func: new Map()
}; };
this.ensureKey(key.key).push(key); this.ensureMap(d.key);
this.list[id] = key; this.data[d.id] = d;
return this; const arr = this.keyMap.get(d.key)!;
arr.push(d);
} }
/** /**
* *
* @param data * @param id id
* @param key * @param func
*/ */
setKey(data: string | HotkeyData, key: KeyCode): void { realize(id: string, func: HotkeyFunc) {
if (typeof data === 'string') { const key = this.data[id];
data = this.list[data]; if (!key.func.has(this.scope)) {
throw new Error(
`Cannot access using scope. Call use before calling realize.`
);
} }
const map = this.keyMap.get(data.key)!; key.func.set(this.scope, func);
deleteWith(map, data); }
this.ensureKey(key).push(data);
const before = data.key; /**
* 使symbol作为当前作用域{@link realize}
* @param symbol symbol
*/
use(symbol: symbol) {
this.scope = symbol;
for (const key of Object.values(this.data)) {
key.func.set(symbol, () => {});
}
}
/**
* {@link realize}{@link use}
* @param symbol symbol
*/
dispose(symbol: symbol) {
for (const key of Object.values(this.data)) {
key.func.delete(symbol);
}
}
/**
*
* @param id id
* @param key
* @param assist `ctrl` `shift` `alt`
*/
set(id: string, key: KeyCode, assist: number) {
const { ctrl, shift, alt } = this.unwarpBinary(assist);
const data = this.data[id];
const before = this.keyMap.get(data.key)!;
deleteWith(before, data);
this.ensureMap(key);
const after = this.keyMap.get(key)!;
after.push(data);
data.key = key; data.key = key;
this.emit('keyChange', data, before, key); data.ctrl = ctrl;
data.shift = shift;
data.alt = alt;
} }
/** /**
* *
* @param key * @param key
* @param assist `ctrl` `shift` `alt`
*/ */
getData(key: KeyCode): HotkeyData[] { emitKey(key: KeyCode, assist: number, ev: KeyboardEvent) {
return this.keyMap.get(key) ?? []; const toEmit = this.keyMap.get(key);
} if (!toEmit) return;
const { ctrl, shift, alt } = this.unwarpBinary(assist);
/** toEmit.forEach(v => {
* if (ctrl === v.ctrl && shift === v.shift && alt === v.alt) {
* @param key const func = v.func.get(this.scope);
*/ if (!func) {
emitKey(key: KeyCode, e: KeyboardEvent, ...params: any[]): any[] { throw new Error(`Emit unknown scope keys.`);
this.emit('emit', key, e, ...params); }
return this.getData(key).map(v => { func(key, ev);
const assist = generateBinary([e.ctrlKey, e.altKey, e.shiftKey]);
const need = generateBinary([!!v.ctrl, !!v.alt, !!v.shift]);
if (assist & need) {
v.func(key, e);
} }
}); });
} }
/** private unwarpBinary(bin: number): AssistHoykey {
* return {
* @param id id ctrl: !!(bin & (1 << 0)),
* @param name shift: !!(bin & (1 << 1)),
* @param ids alt: !!(bin & (1 << 2))
*/
group(id: string, name: string, ids: string[]) {
this.groups[id] = {
name,
includes: ids
}; };
ids.forEach(v => {
const data = this.list[v];
if (has(data.group)) {
deleteWith(this.groups[data.group].includes, v);
}
data.group = id;
});
return this;
} }
/** private ensureMap(key: KeyCode) {
*
* @param id id
* @param name
*/
groupRest(id: string, name: string) {
const rest = Object.values(this.list)
.filter(v => !has(v.group))
.map(v => v.id);
this.group(id, name, rest);
return this;
}
/**
*
* @param hotkey
* @param cover id的按键
*/
extend(hotkey: Hotkey, cover: boolean = false) {
Object.values(hotkey.list).forEach(v => {
if (v.id in this.list && !cover) return;
this.register(v.id, v.name, v);
});
return this;
}
private ensureKey(key: KeyCode) {
if (!this.keyMap.has(key)) { if (!this.keyMap.has(key)) {
this.keyMap.set(key, []); this.keyMap.set(key, []);
} }
return this.keyMap.get(key)!; }
/**
* id获取hotkey实例
* @param id hotkey实例的id
*/
static get(id: string) {
return this.list.find(v => v.id === id);
} }
} }
export const hotkey = new Hotkey('gameKey'); // export const hotkey = new Hotkey('gameKey');
hotkey // hotkey
.register('book', '怪物手册', { // .register('book', '怪物手册', {
defaults: KeyCode.KeyX, // defaults: KeyCode.KeyX,
func: () => { // func: () => {
core.openBook(true); // core.openBook(true);
} // }
}) // })
.register('save', '存档界面', { // .register('save', '存档界面', {
defaults: KeyCode.KeyS, // defaults: KeyCode.KeyS,
func: () => { // func: () => {
core.save(true); // core.save(true);
} // }
}) // })
.register('load', '读档界面', { // .register('load', '读档界面', {
defaults: KeyCode.KeyD, // defaults: KeyCode.KeyD,
func: () => { // func: () => {
core.load(true); // core.load(true);
} // }
}) // })
.register('undo', '回退', { // .register('undo', '回退', {
defaults: KeyCode.KeyA, // defaults: KeyCode.KeyA,
func: () => { // func: () => {
core.doSL('autoSave', 'load'); // core.doSL('autoSave', 'load');
} // }
}) // })
.register('redo', '恢复', { // .register('redo', '恢复', {
defaults: KeyCode.KeyW, // defaults: KeyCode.KeyW,
func: () => { // func: () => {
core.doSL('autoSave', 'reload'); // core.doSL('autoSave', 'reload');
} // }
}) // })
.register('toolbox', '道具栏', { // .register('toolbox', '道具栏', {
defaults: KeyCode.KeyT, // defaults: KeyCode.KeyT,
func: () => { // func: () => {
core.openToolbox(true); // core.openToolbox(true);
} // }
}) // })
.register('equipbox', '装备栏', { // .register('equipbox', '装备栏', {
defaults: KeyCode.KeyQ, // defaults: KeyCode.KeyQ,
func: () => { // func: () => {
core.openEquipbox(true); // core.openEquipbox(true);
} // }
}) // })
.register('fly', '楼层传送', { // .register('fly', '楼层传送', {
defaults: KeyCode.KeyG, // defaults: KeyCode.KeyG,
func: () => { // func: () => {
core.useFly(true); // core.useFly(true);
} // }
}) // })
.register('turn', '勇士转向', { // .register('turn', '勇士转向', {
defaults: KeyCode.KeyZ, // defaults: KeyCode.KeyZ,
func: () => { // func: () => {
core.turnHero(); // core.turnHero();
} // }
}) // })
.register('getNext', '轻按', { // .register('getNext', '轻按', {
defaults: KeyCode.Space, // defaults: KeyCode.Space,
func: () => { // func: () => {
core.getNextItem(); // core.getNextItem();
} // }
}) // })
.register('menu', '菜单', { // .register('menu', '菜单', {
defaults: KeyCode.Escape, // defaults: KeyCode.Escape,
func: () => { // func: () => {
core.openSettings(true); // core.openSettings(true);
} // }
}) // })
.register('replay', '录像回放', { // .register('replay', '录像回放', {
defaults: KeyCode.KeyR, // defaults: KeyCode.KeyR,
func: () => { // func: () => {
core.ui._drawReplay(); // core.ui._drawReplay();
} // }
}) // })
.register('restart', '开始菜单', { // .register('restart', '开始菜单', {
defaults: KeyCode.KeyN, // defaults: KeyCode.KeyN,
func: () => { // func: () => {
core.confirmRestart(); // core.confirmRestart();
} // }
}) // })
.register('shop', '快捷商店', { // .register('shop', '快捷商店', {
defaults: KeyCode.KeyV, // defaults: KeyCode.KeyV,
func: () => { // func: () => {
core.openQuickShop(true); // core.openQuickShop(true);
} // }
}) // })
.register('statistics', '数据统计', { // .register('statistics', '数据统计', {
defaults: KeyCode.KeyB, // defaults: KeyCode.KeyB,
func: () => { // func: () => {
core.ui._drawStatistics(); // core.ui._drawStatistics();
} // }
}) // })
.register('viewMap1', '浏览地图', { // .register('viewMap1', '浏览地图', {
defaults: KeyCode.PageUp, // defaults: KeyCode.PageUp,
func: () => { // func: () => {
core.ui._drawViewMaps(); // core.ui._drawViewMaps();
} // }
}) // })
.register('viewMap2', '浏览地图', { // .register('viewMap2', '浏览地图', {
defaults: KeyCode.PageDown, // defaults: KeyCode.PageDown,
func: () => { // func: () => {
core.ui._drawViewMaps(); // core.ui._drawViewMaps();
} // }
}) // })
.register('comment', '评论区', { // .register('comment', '评论区', {
defaults: KeyCode.KeyP, // defaults: KeyCode.KeyP,
func: () => { // func: () => {
core.actions._clickGameInfo_openComments(); // core.actions._clickGameInfo_openComments();
} // }
}) // })
.register('mark', '标记怪物', { // .register('mark', '标记怪物', {
defaults: KeyCode.KeyM, // defaults: KeyCode.KeyM,
func: () => { // func: () => {
// todo: refactor // // todo: refactor
const [x, y] = flags.mouseLoc ?? []; // const [x, y] = flags.mouseLoc ?? [];
const [mx, my] = getLocFromMouseLoc(x, y); // const [mx, my] = getLocFromMouseLoc(x, y);
} // }
}) // })
.register('skillTree', '技能树', { // .register('skillTree', '技能树', {
defaults: KeyCode.KeyJ, // defaults: KeyCode.KeyJ,
func: () => { // func: () => {
core.useItem('skill1', true); // core.useItem('skill1', true);
} // }
}) // })
.register('desc', '百科全书', { // .register('desc', '百科全书', {
defaults: KeyCode.KeyH, // defaults: KeyCode.KeyH,
func: () => { // func: () => {
core.useItem('I560', true); // core.useItem('I560', true);
} // }
}) // })
.register('special', '鼠标位置怪物属性', { // .register('special', '鼠标位置怪物属性', {
defaults: KeyCode.KeyE, // defaults: KeyCode.KeyE,
func: () => { // func: () => {
const [x, y] = flags.mouseLoc ?? []; // const [x, y] = flags.mouseLoc ?? [];
const [mx, my] = getLocFromMouseLoc(x, y); // const [mx, my] = getLocFromMouseLoc(x, y);
if (core.getBlockCls(mx, my)?.startsWith('enemy')) { // if (core.getBlockCls(mx, my)?.startsWith('enemy')) {
// mota.plugin.fixed.showFixed.value = false; // // mota.plugin.fixed.showFixed.value = false;
mota.ui.main.open('fixedDetail', { // mota.ui.main.open('fixedDetail', {
panel: 'special' // panel: 'special'
}); // });
} // }
} // }
}) // })
.register('critical', '鼠标位置怪物临界', { // .register('critical', '鼠标位置怪物临界', {
defaults: KeyCode.KeyC, // defaults: KeyCode.KeyC,
func: () => { // func: () => {
const [x, y] = flags.mouseLoc ?? []; // const [x, y] = flags.mouseLoc ?? [];
const [mx, my] = getLocFromMouseLoc(x, y); // const [mx, my] = getLocFromMouseLoc(x, y);
if (core.getBlockCls(mx, my)?.startsWith('enemy')) { // if (core.getBlockCls(mx, my)?.startsWith('enemy')) {
// mota.plugin.fixed.showFixed.value = false; // // mota.plugin.fixed.showFixed.value = false;
mota.ui.main.open('fixedDetail', { // mota.ui.main.open('fixedDetail', {
panel: 'critical' // panel: 'critical'
}); // });
} // }
} // }
}) // })
.group('action', '游戏操作', [ // .group('action', '游戏操作', [
'save', // 'save',
'load', // 'load',
'undo', // 'undo',
'redo', // 'redo',
'turn', // 'turn',
'getNext', // 'getNext',
'mark' // 'mark'
]) // ])
.group('view', '快捷查看', [ // .group('view', '快捷查看', [
'book', // 'book',
'toolbox', // 'toolbox',
'equipbox', // 'equipbox',
'fly', // 'fly',
'menu', // 'menu',
'replay', // 'replay',
'shop', // 'shop',
'statistics', // 'statistics',
'viewMap1', // 'viewMap1',
'viewMap2', // 'viewMap2',
'skillTree', // 'skillTree',
'desc', // 'desc',
'special', // 'special',
'critical' // 'critical'
]) // ])
.group('system', '系统按键', ['comment']) // .group('system', '系统按键', ['comment'])
.groupRest('unClassed', '未分类按键'); // .groupRest('unClassed', '未分类按键');

View File

@ -2,6 +2,7 @@ import { Component, shallowReactive } from 'vue';
import { EmitableEvent, EventEmitter } from '../../common/eventEmitter'; import { EmitableEvent, EventEmitter } from '../../common/eventEmitter';
import { KeyCode } from '@/plugin/keyCodes'; import { KeyCode } from '@/plugin/keyCodes';
import { Hotkey } from './hotkey'; import { Hotkey } from './hotkey';
import { generateBinary } from '@/plugin/utils';
interface FocusEvent<T> extends EmitableEvent { interface FocusEvent<T> extends EmitableEvent {
focus: (before: T | null, after: T) => void; focus: (before: T | null, after: T) => void;
@ -136,6 +137,7 @@ export class GameUi extends EventEmitter<GameUiEvent> {
component: Component; component: Component;
hotkey?: Hotkey; hotkey?: Hotkey;
id: string; id: string;
symbol: symbol = Symbol();
constructor(id: string, component: Component, hotkey?: Hotkey) { constructor(id: string, component: Component, hotkey?: Hotkey) {
super(); super();
@ -206,15 +208,6 @@ export class UiController extends Focus<IndexedGameUi> {
this.show = 'all'; this.show = 'all';
} }
/**
*
* @param key KeyCode
* @param e
*/
emitKey(key: KeyCode, e: KeyboardEvent) {
this.focused?.ui.hotkey?.emitKey(key, e, this.focused);
}
/** /**
* id获取到ui * id获取到ui
* @param id ui的id * @param id ui的id
@ -274,7 +267,10 @@ export class UiController extends Focus<IndexedGameUi> {
*/ */
open(id: string, vBind?: UiVBind, vOn?: UiVOn) { open(id: string, vBind?: UiVBind, vOn?: UiVOn) {
const ui = this.get(id); const ui = this.get(id);
if (!ui) return -1; if (!ui) {
console.warn(`Unknown UI: '${id}'.`);
return -1;
}
const num = this.num++; const num = this.num++;
const bind = { const bind = {
num, num,