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 { static list: Keyboard[] = []; id: string; keys: KeyboardItem[] = []; assist: number = 0; fontSize: number = 18; scope: symbol = Symbol(); private scopeStack: symbol[] = []; private onEmitKey: Record[]> = {}; private scopeAssist: Record = {}; 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[] = []; 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; }