HumanBreak/src/core/main/custom/hotkey.ts
2023-09-05 18:45:17 +08:00

350 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { KeyCode } from '../../../plugin/keyCodes';
import { getLocFromMouseLoc } from '../../../plugin/ui/fixed';
import { deleteWith, generateBinary, has, tip } from '../../../plugin/utils';
import { EmitableEvent, EventEmitter } from '../../common/eventEmitter';
import { GameStorage } from '../storage';
interface HotkeyEvent extends EmitableEvent {
emit: (key: KeyCode, e: KeyboardEvent, ...params: any[]) => void;
keyChange: (data: HotkeyData, before: KeyCode, after: KeyCode) => void;
}
interface HotkeyData {
id: string;
name: string;
defaults: KeyCode;
key: KeyCode;
ctrl?: boolean;
alt?: boolean;
shift?: boolean;
group?: string;
func: (code: KeyCode, e: KeyboardEvent) => any;
}
interface GroupInfo {
name: string;
includes: string[];
}
type RegisterData = Omit<HotkeyData, 'id' | 'key' | 'name'>;
export class Hotkey extends EventEmitter<HotkeyEvent> {
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) {
super();
if (storage) {
this.storage = new GameStorage(GameStorage.fromAuthor('AncTe', id));
}
}
/**
* 注册一个按键操作
* @param data 按键信息
*/
register(id: string, name: string, data: RegisterData) {
const key = {
id,
name,
key: this.storage?.getValue(id, data.defaults) ?? data.defaults,
...data
};
this.ensureKey(key.key).push(key);
this.list[id] = key;
return this;
}
/**
* 设置一个操作的按键
* @param data 要设置的操作
* @param key 按键
*/
setKey(data: string | HotkeyData, key: KeyCode): void {
if (typeof data === 'string') {
data = this.list[data];
}
const map = this.keyMap.get(data.key)!;
deleteWith(map, data);
this.ensureKey(key).push(data);
const before = data.key;
data.key = key;
this.emit('keyChange', data, before, key);
}
/**
* 获取一个按键可以触发的所有按键操作
* @param key 要获取的按键
*/
getData(key: KeyCode): HotkeyData[] {
return this.keyMap.get(key) ?? [];
}
/**
* 执行一个按键操作
* @param key 按下的按键
*/
emitKey(key: KeyCode, e: KeyboardEvent, ...params: any[]): any[] {
this.emit('emit', key, e, ...params);
return this.getData(key).map(v => {
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);
}
});
}
/**
* 按键分组
* @param id 组id
* @param name 组名称
* @param ids 组包含的内容
*/
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;
}
/**
* 将剩余的按键组成一个组合
* @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)) {
this.keyMap.set(key, []);
}
return this.keyMap.get(key)!;
}
}
export const hotkey = new Hotkey('gameKey');
hotkey
.register('book', '怪物手册', {
defaults: KeyCode.KeyX,
func: () => {
core.openBook(true);
}
})
.register('save', '存档界面', {
defaults: KeyCode.KeyS,
func: () => {
core.save(true);
}
})
.register('load', '读档界面', {
defaults: KeyCode.KeyD,
func: () => {
core.load(true);
}
})
.register('undo', '回退', {
defaults: KeyCode.KeyA,
func: () => {
core.doSL('autoSave', 'load');
}
})
.register('redo', '恢复', {
defaults: KeyCode.KeyW,
func: () => {
core.doSL('autoSave', 'reload');
}
})
.register('toolbox', '道具栏', {
defaults: KeyCode.KeyT,
func: () => {
core.openToolbox(true);
}
})
.register('equipbox', '装备栏', {
defaults: KeyCode.KeyQ,
func: () => {
core.openEquipbox(true);
}
})
.register('fly', '楼层传送', {
defaults: KeyCode.KeyG,
func: () => {
core.useFly(true);
}
})
.register('turn', '勇士转向', {
defaults: KeyCode.KeyZ,
func: () => {
core.turnHero();
}
})
.register('getNext', '轻按', {
defaults: KeyCode.Space,
func: () => {
core.getNextItem();
}
})
.register('menu', '菜单', {
defaults: KeyCode.Escape,
func: () => {
core.openSettings(true);
}
})
.register('replay', '录像回放', {
defaults: KeyCode.KeyR,
func: () => {
core.ui._drawReplay();
}
})
.register('restart', '开始菜单', {
defaults: KeyCode.KeyN,
func: () => {
core.confirmRestart();
}
})
.register('shop', '快捷商店', {
defaults: KeyCode.KeyV,
func: () => {
core.openQuickShop(true);
}
})
.register('statistics', '数据统计', {
defaults: KeyCode.KeyB,
func: () => {
core.ui._drawStatistics();
}
})
.register('viewMap1', '浏览地图', {
defaults: KeyCode.PageUp,
func: () => {
core.ui._drawViewMaps();
}
})
.register('viewMap2', '浏览地图', {
defaults: KeyCode.PageDown,
func: () => {
core.ui._drawViewMaps();
}
})
.register('comment', '评论区', {
defaults: KeyCode.KeyP,
func: () => {
core.actions._clickGameInfo_openComments();
}
})
.register('mark', '标记怪物', {
defaults: KeyCode.KeyM,
func: () => {
const [x, y] = flags.mouseLoc ?? [];
const [mx, my] = getLocFromMouseLoc(x, y);
const blocks = core.getMapBlocksObj();
const block = blocks[`${mx},${my}`];
if (block.event.cls.startsWith('enemy')) {
const id = block.event.id as EnemyIds;
const name = core.material.enemys[id].name;
if (ancTe.plugin.mark.hasMarkedEnemy(id)) {
tip('success', `已取消标记${name}`);
ancTe.plugin.mark.unmarkEnemy(id);
} else {
tip('success', `已标记${name}`);
ancTe.plugin.mark.checkMarkedEnemy(false);
}
}
}
})
.register('skillTree', '技能树', {
defaults: KeyCode.KeyJ,
func: () => {
core.useItem('skill1', true);
}
})
.register('desc', '百科全书', {
defaults: KeyCode.KeyH,
func: () => {
core.useItem('I560', true);
}
})
.register('special', '鼠标位置怪物属性', {
defaults: KeyCode.KeyE,
func: () => {
const [x, y] = flags.mouseLoc ?? [];
const [mx, my] = getLocFromMouseLoc(x, y);
if (core.getBlockCls(mx, my)?.startsWith('enemy')) {
core.plugin.fixedDetailPanel = 'special';
ancTe.plugin.fixed.showFixed.value = false;
ancTe.plugin.ui.fixedDetailOpened.value = true;
}
}
})
.register('critical', '鼠标位置怪物临界', {
defaults: KeyCode.KeyC,
func: () => {
const [x, y] = flags.mouseLoc ?? [];
const [mx, my] = getLocFromMouseLoc(x, y);
if (core.getBlockCls(mx, my)?.startsWith('enemy')) {
core.plugin.fixedDetailPanel = 'critical';
ancTe.plugin.fixed.showFixed.value = false;
ancTe.plugin.ui.fixedDetailOpened.value = true;
}
}
})
.group('action', '游戏操作', [
'save',
'load',
'undo',
'redo',
'turn',
'getNext',
'mark'
])
.group('view', '快捷查看', [
'book',
'toolbox',
'equipbox',
'fly',
'menu',
'replay',
'shop',
'statistics',
'viewMap1',
'viewMap2',
'skillTree',
'desc',
'special',
'critical'
])
.group('system', '系统按键', ['comment'])
.groupRest('unClassed', '未分类按键');