ソースを参照

Merge pull request #3749 from IvanSavenko/configurable_shortcuts

Configurable keyboard shortcuts
Ivan Savenko 1 年間 前
コミット
d4594baa05

+ 7 - 3
client/eventsSDL/InputSourceKeyboard.cpp

@@ -33,6 +33,8 @@ InputSourceKeyboard::InputSourceKeyboard()
 
 void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
 {
+	std::string keyName = SDL_GetKeyName(key.keysym.sym);
+	logGlobal->trace("keyboard: key '%s' pressed", keyName);
 	assert(key.state == SDL_PRESSED);
 
 	if (SDL_IsTextInputActive() == SDL_TRUE)
@@ -85,8 +87,7 @@ void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
 		return;
 	}
 
-	auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym);
-
+	auto shortcutsVector = GH.shortcuts().translateKeycode(keyName);
 	GH.events().dispatchShortcutPressed(shortcutsVector);
 }
 
@@ -95,6 +96,9 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key)
 	if(key.repeat != 0)
 		return; // ignore periodic event resends
 
+	std::string keyName = SDL_GetKeyName(key.keysym.sym);
+	logGlobal->trace("keyboard: key '%s' released", keyName);
+
 	if (SDL_IsTextInputActive() == SDL_TRUE)
 	{
 		if (key.keysym.sym >= ' ' && key.keysym.sym < 0x80)
@@ -103,7 +107,7 @@ void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key)
 
 	assert(key.state == SDL_RELEASED);
 
-	auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym);
+	auto shortcutsVector = GH.shortcuts().translateKeycode(keyName);
 
 	GH.events().dispatchShortcutReleased(shortcutsVector);
 }

+ 7 - 0
client/eventsSDL/InputSourceText.cpp

@@ -21,6 +21,13 @@
 
 #include <SDL_events.h>
 
+InputSourceText::InputSourceText()
+{
+	// For whatever reason, in SDL text input is considered to be active by default at least on desktop platforms
+	// Apparently fixed in SDL3, but until then we need a workaround
+	SDL_StopTextInput();
+}
+
 void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
 {
 	GH.events().dispatchTextInput(text.text);

+ 2 - 0
client/eventsSDL/InputSourceText.h

@@ -21,6 +21,8 @@ struct SDL_TextInputEvent;
 class InputSourceText
 {
 public:
+	InputSourceText();
+
 	void handleEventTextInput(const SDL_TextInputEvent & current);
 	void handleEventTextEditing(const SDL_TextEditingEvent & current);
 

+ 1 - 1
client/gui/CGuiHandler.cpp

@@ -72,11 +72,11 @@ void CGuiHandler::init()
 {
 	inGuiThread = true;
 
-	inputHandlerInstance = std::make_unique<InputHandler>();
 	eventDispatcherInstance = std::make_unique<EventDispatcher>();
 	windowHandlerInstance = std::make_unique<WindowHandler>();
 	screenHandlerInstance = std::make_unique<ScreenHandler>();
 	renderHandlerInstance = std::make_unique<RenderHandler>();
+	inputHandlerInstance = std::make_unique<InputHandler>(); // Must be after windowHandlerInstance
 	shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
 	framerateManagerInstance = std::make_unique<FramerateManager>(settings["video"]["targetfps"].Integer());
 }

+ 31 - 140
client/gui/ShortcutHandler.cpp

@@ -12,149 +12,40 @@
 
 #include "ShortcutHandler.h"
 #include "Shortcut.h"
-#include <SDL_keycode.h>
 
-std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
+#include "../../lib/json/JsonUtils.h"
+
+ShortcutHandler::ShortcutHandler()
 {
-	static const std::multimap<SDL_Keycode, EShortcut> keyToShortcut = {
-		{SDLK_RETURN,    EShortcut::GLOBAL_ACCEPT             },
-		{SDLK_KP_ENTER,  EShortcut::GLOBAL_ACCEPT             },
-		{SDLK_ESCAPE,    EShortcut::GLOBAL_CANCEL             },
-		{SDLK_RETURN,    EShortcut::GLOBAL_RETURN             },
-		{SDLK_KP_ENTER,  EShortcut::GLOBAL_RETURN             },
-		{SDLK_ESCAPE,    EShortcut::GLOBAL_RETURN             },
-		{SDLK_F4,        EShortcut::GLOBAL_FULLSCREEN         },
-		{SDLK_BACKSPACE, EShortcut::GLOBAL_BACKSPACE          },
-		{SDLK_TAB,       EShortcut::GLOBAL_MOVE_FOCUS         },
-		{SDLK_o,         EShortcut::GLOBAL_OPTIONS            },
-		{SDLK_LEFT,      EShortcut::MOVE_LEFT                 },
-		{SDLK_RIGHT,     EShortcut::MOVE_RIGHT                },
-		{SDLK_UP,        EShortcut::MOVE_UP                   },
-		{SDLK_DOWN,      EShortcut::MOVE_DOWN                 },
-		{SDLK_HOME,      EShortcut::MOVE_FIRST                },
-		{SDLK_END,       EShortcut::MOVE_LAST                 },
-		{SDLK_PAGEUP,    EShortcut::MOVE_PAGE_UP              },
-		{SDLK_PAGEDOWN,  EShortcut::MOVE_PAGE_DOWN            },
-		{SDLK_1,         EShortcut::SELECT_INDEX_1            },
-		{SDLK_2,         EShortcut::SELECT_INDEX_2            },
-		{SDLK_3,         EShortcut::SELECT_INDEX_3            },
-		{SDLK_4,         EShortcut::SELECT_INDEX_4            },
-		{SDLK_5,         EShortcut::SELECT_INDEX_5            },
-		{SDLK_6,         EShortcut::SELECT_INDEX_6            },
-		{SDLK_7,         EShortcut::SELECT_INDEX_7            },
-		{SDLK_8,         EShortcut::SELECT_INDEX_8            },
-		{SDLK_n,         EShortcut::MAIN_MENU_NEW_GAME        },
-		{SDLK_l,         EShortcut::MAIN_MENU_LOAD_GAME       },
-		{SDLK_h,         EShortcut::MAIN_MENU_HIGH_SCORES     },
-		{SDLK_c,         EShortcut::MAIN_MENU_CREDITS         },
-		{SDLK_q,         EShortcut::MAIN_MENU_QUIT            },
-		{SDLK_b,         EShortcut::MAIN_MENU_BACK            },
-		{SDLK_s,         EShortcut::MAIN_MENU_SINGLEPLAYER    },
-		{SDLK_m,         EShortcut::MAIN_MENU_MULTIPLAYER     },
-		{SDLK_c,         EShortcut::MAIN_MENU_CAMPAIGN        },
-		{SDLK_t,         EShortcut::MAIN_MENU_TUTORIAL        },
-		{SDLK_s,         EShortcut::MAIN_MENU_CAMPAIGN_SOD    },
-		{SDLK_r,         EShortcut::MAIN_MENU_CAMPAIGN_ROE    },
-		{SDLK_a,         EShortcut::MAIN_MENU_CAMPAIGN_AB     },
-		{SDLK_c,         EShortcut::MAIN_MENU_CAMPAIGN_CUSTOM },
-		{SDLK_b,         EShortcut::LOBBY_BEGIN_GAME          },
-		{SDLK_RETURN,    EShortcut::LOBBY_BEGIN_GAME          },
-		{SDLK_KP_ENTER,  EShortcut::LOBBY_BEGIN_GAME          },
-		{SDLK_l,         EShortcut::LOBBY_LOAD_GAME           },
-		{SDLK_RETURN,    EShortcut::LOBBY_LOAD_GAME           },
-		{SDLK_KP_ENTER,  EShortcut::LOBBY_LOAD_GAME           },
-		{SDLK_s,         EShortcut::LOBBY_SAVE_GAME           },
-		{SDLK_RETURN,    EShortcut::LOBBY_SAVE_GAME           },
-		{SDLK_KP_ENTER,  EShortcut::LOBBY_SAVE_GAME           },
-		{SDLK_r,         EShortcut::LOBBY_RANDOM_MAP          },
-		{SDLK_h,         EShortcut::LOBBY_HIDE_CHAT           },
-		{SDLK_a,         EShortcut::LOBBY_ADDITIONAL_OPTIONS  },
-		{SDLK_s,         EShortcut::LOBBY_SELECT_SCENARIO     },
-		{SDLK_e,         EShortcut::GAME_END_TURN             },
-		{SDLK_l,         EShortcut::GAME_LOAD_GAME            },
-		{SDLK_s,         EShortcut::GAME_SAVE_GAME            },
-		{SDLK_r,         EShortcut::GAME_RESTART_GAME         },
-		{SDLK_m,         EShortcut::GAME_TO_MAIN_MENU         },
-		{SDLK_q,         EShortcut::GAME_QUIT_GAME            },
-		{SDLK_b,         EShortcut::GAME_OPEN_MARKETPLACE     },
-		{SDLK_g,         EShortcut::GAME_OPEN_THIEVES_GUILD   },
-		{SDLK_TAB,       EShortcut::GAME_ACTIVATE_CONSOLE     },
-		{SDLK_o,         EShortcut::ADVENTURE_GAME_OPTIONS    },
-		{SDLK_F6,        EShortcut::ADVENTURE_TOGGLE_GRID     },
-		{SDLK_z,         EShortcut::ADVENTURE_SET_HERO_ASLEEP },
-		{SDLK_w,         EShortcut::ADVENTURE_SET_HERO_AWAKE  },
-		{SDLK_m,         EShortcut::ADVENTURE_MOVE_HERO       },
-		{SDLK_SPACE,     EShortcut::ADVENTURE_VISIT_OBJECT    },
-		{SDLK_KP_1,      EShortcut::ADVENTURE_MOVE_HERO_SW    },
-		{SDLK_KP_2,      EShortcut::ADVENTURE_MOVE_HERO_SS    },
-		{SDLK_KP_3,      EShortcut::ADVENTURE_MOVE_HERO_SE    },
-		{SDLK_KP_4,      EShortcut::ADVENTURE_MOVE_HERO_WW    },
-		{SDLK_KP_6,      EShortcut::ADVENTURE_MOVE_HERO_EE    },
-		{SDLK_KP_7,      EShortcut::ADVENTURE_MOVE_HERO_NW    },
-		{SDLK_KP_8,      EShortcut::ADVENTURE_MOVE_HERO_NN    },
-		{SDLK_KP_9,      EShortcut::ADVENTURE_MOVE_HERO_NE    },
-		{SDLK_DOWN,      EShortcut::ADVENTURE_MOVE_HERO_SS    },
-		{SDLK_LEFT,      EShortcut::ADVENTURE_MOVE_HERO_WW    },
-		{SDLK_RIGHT,     EShortcut::ADVENTURE_MOVE_HERO_EE    },
-		{SDLK_UP,        EShortcut::ADVENTURE_MOVE_HERO_NN    },
-		{SDLK_RETURN,    EShortcut::ADVENTURE_VIEW_SELECTED   },
-		{SDLK_KP_ENTER,  EShortcut::ADVENTURE_VIEW_SELECTED   },
- //		{SDLK_,          EShortcut::ADVENTURE_NEXT_OBJECT     },
-		{SDLK_t,         EShortcut::ADVENTURE_NEXT_TOWN       },
-		{SDLK_h,         EShortcut::ADVENTURE_NEXT_HERO       },
- //		{SDLK_,          EShortcut::ADVENTURE_FIRST_TOWN      },
-  //		{SDLK_,          EShortcut::ADVENTURE_FIRST_HERO      },
-		{SDLK_i,         EShortcut::ADVENTURE_VIEW_SCENARIO   },
-		{SDLK_d,         EShortcut::ADVENTURE_DIG_GRAIL       },
-		{SDLK_p,         EShortcut::ADVENTURE_VIEW_PUZZLE     },
-		{SDLK_v,         EShortcut::ADVENTURE_VIEW_WORLD      },
-		{SDLK_1,         EShortcut::ADVENTURE_VIEW_WORLD_X1   },
-		{SDLK_2,         EShortcut::ADVENTURE_VIEW_WORLD_X2   },
-		{SDLK_4,         EShortcut::ADVENTURE_VIEW_WORLD_X4   },
-		{SDLK_u,         EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL},
-		{SDLK_k,         EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
-		{SDLK_q,         EShortcut::ADVENTURE_QUEST_LOG       },
-		{SDLK_c,         EShortcut::ADVENTURE_CAST_SPELL      },
-		{SDLK_g,         EShortcut::ADVENTURE_THIEVES_GUILD   },
-		{SDLK_KP_PLUS,   EShortcut::ADVENTURE_ZOOM_IN         },
-		{SDLK_KP_MINUS,  EShortcut::ADVENTURE_ZOOM_OUT        },
-		{SDLK_BACKSPACE, EShortcut::ADVENTURE_ZOOM_RESET      },
-		{SDLK_q,         EShortcut::BATTLE_TOGGLE_QUEUE       },
-		{SDLK_f,         EShortcut::BATTLE_USE_CREATURE_SPELL },
-		{SDLK_s,         EShortcut::BATTLE_SURRENDER          },
-		{SDLK_r,         EShortcut::BATTLE_RETREAT            },
-		{SDLK_a,         EShortcut::BATTLE_AUTOCOMBAT         },
-		{SDLK_e,         EShortcut::BATTLE_END_WITH_AUTOCOMBAT},
-		{SDLK_c,         EShortcut::BATTLE_CAST_SPELL         },
-		{SDLK_w,         EShortcut::BATTLE_WAIT               },
-		{SDLK_d,         EShortcut::BATTLE_DEFEND             },
-		{SDLK_SPACE,     EShortcut::BATTLE_DEFEND             },
-		{SDLK_UP,        EShortcut::BATTLE_CONSOLE_UP         },
-		{SDLK_DOWN,      EShortcut::BATTLE_CONSOLE_DOWN       },
-		{SDLK_SPACE,     EShortcut::BATTLE_TACTICS_NEXT       },
-		{SDLK_RETURN,    EShortcut::BATTLE_TACTICS_END        },
-		{SDLK_KP_ENTER,  EShortcut::BATTLE_TACTICS_END        },
-		{SDLK_s,         EShortcut::BATTLE_SELECT_ACTION      },
-		{SDLK_i,         EShortcut::BATTLE_TOGGLE_HEROES_STATS},
-		{SDLK_t,         EShortcut::TOWN_OPEN_TAVERN          },
-		{SDLK_SPACE,     EShortcut::TOWN_SWAP_ARMIES          },
-		{SDLK_END,       EShortcut::RECRUITMENT_MAX           },
-		{SDLK_HOME,      EShortcut::RECRUITMENT_MIN           },
-		{SDLK_u,         EShortcut::RECRUITMENT_UPGRADE       },
-		{SDLK_a,         EShortcut::RECRUITMENT_UPGRADE_ALL   },
-		{SDLK_u,         EShortcut::RECRUITMENT_UPGRADE_ALL   },
-		{SDLK_h,         EShortcut::KINGDOM_HEROES_TAB        },
-		{SDLK_t,         EShortcut::KINGDOM_TOWNS_TAB         },
-		{SDLK_d,         EShortcut::HERO_DISMISS              },
-		{SDLK_c,         EShortcut::HERO_COMMANDER            },
-		{SDLK_l,         EShortcut::HERO_LOOSE_FORMATION      },
-		{SDLK_t,         EShortcut::HERO_TIGHT_FORMATION      },
-		{SDLK_b,         EShortcut::HERO_TOGGLE_TACTICS       },
-		{SDLK_a,         EShortcut::SPELLBOOK_TAB_ADVENTURE   },
-		{SDLK_c,         EShortcut::SPELLBOOK_TAB_COMBAT      }
-	};
+	const JsonNode config = JsonUtils::assembleFromFiles("config/shortcutsConfig");
+
+	for (auto const & entry : config["keyboard"].Struct())
+	{
+		std::string shortcutName = entry.first;
+		EShortcut shortcutID = findShortcut(shortcutName);
+
+		if (shortcutID == EShortcut::NONE)
+		{
+			logGlobal->warn("Unknown shortcut '%s' found when loading shortcuts config!", shortcutName);
+			continue;
+		}
 
-	auto range = keyToShortcut.equal_range(key);
+		if (entry.second.isString())
+		{
+			mappedShortcuts.emplace(entry.second.String(), shortcutID);
+		}
+
+		if (entry.second.isVector())
+		{
+			for (auto const & entryVector : entry.second.Vector())
+				mappedShortcuts.emplace(entryVector.String(), shortcutID);
+		}
+	}
+}
+
+std::vector<EShortcut> ShortcutHandler::translateKeycode(const std::string & key) const
+{
+	auto range = mappedShortcuts.equal_range(key);
 
 	// FIXME: some code expects calls to keyPressed / captureThisKey even without defined hotkeys
 	if (range.first == range.second)

+ 4 - 2
client/gui/ShortcutHandler.h

@@ -11,13 +11,15 @@
 #pragma once
 
 enum class EShortcut;
-using SDL_Keycode = int32_t;
 
 class ShortcutHandler
 {
+	std::multimap<std::string, EShortcut> mappedShortcuts;
 public:
+	ShortcutHandler();
+
 	/// returns list of shortcuts assigned to provided SDL keycode
-	std::vector<EShortcut> translateKeycode(SDL_Keycode key) const;
+	std::vector<EShortcut> translateKeycode(const std::string & key) const;
 
 	/// attempts to find shortcut by its unique identifier. Returns EShortcut::NONE on failure
 	EShortcut findShortcut(const std::string & identifier ) const;

+ 5 - 0
client/mainmenu/CMainMenu.cpp

@@ -241,6 +241,11 @@ std::shared_ptr<CButton> CMenuEntry::createButton(CMenuScreen * parent, const Js
 
 	EShortcut shortcut = GH.shortcuts().findShortcut(button["shortcut"].String());
 
+	if (shortcut == EShortcut::NONE && !button["shortcut"].String().empty())
+	{
+		logGlobal->warn("Unknown shortcut '%s' found when loading main menu config!", button["shortcut"].String());
+	}
+
 	auto result = std::make_shared<CButton>(Point(posx, posy), AnimationPath::fromJson(button["name"]), help, command, shortcut);
 
 	if (button["center"].Bool())

+ 3 - 3
config/mainmenu.json

@@ -21,9 +21,9 @@
 				"name" : "main",
 				"buttons":
 				[
-					{"x": 644, "y":  70, "center" : true, "name":"MMENUNG", "shortcut" : "mainMenuNew", "help": 3, "command": "to new"},
-					{"x": 645, "y": 192, "center" : true, "name":"MMENULG", "shortcut" : "mainMenuLoad", "help": 4, "command": "to load"},
-					{"x": 643, "y": 296, "center" : true, "name":"MMENUHS", "shortcut" : "mainMenuScores", "help": 5, "command": "highscores"},
+					{"x": 644, "y":  70, "center" : true, "name":"MMENUNG", "shortcut" : "mainMenuNewGame", "help": 3, "command": "to new"},
+					{"x": 645, "y": 192, "center" : true, "name":"MMENULG", "shortcut" : "mainMenuLoadGame", "help": 4, "command": "to load"},
+					{"x": 643, "y": 296, "center" : true, "name":"MMENUHS", "shortcut" : "mainMenuHighScores", "help": 5, "command": "highscores"},
 					{"x": 643, "y": 414, "center" : true, "name":"MMENUCR", "shortcut" : "mainMenuCredits", "help": 6, "command": "to credits"},
 					{"x": 643, "y": 520, "center" : true, "name":"MMENUQT", "shortcut" : "mainMenuQuit", "help": 7, "command": "exit"}
 				]

+ 129 - 0
config/shortcutsConfig.json

@@ -0,0 +1,129 @@
+// This file defines all shortcuts used by VCMI
+// For modders: create file with same name (Content/config/shortcutsConfig.json) to modify this set in your mod
+// For players (Windows): create file Documents/My Games/vcmi/config/shortcutsConfig.json to modify this set
+// For players (Linux): create file ~/.config/vcmi/shortcutsConfig.json (or ~/.var/app/eu.vcmi.VCMI/config for Flatpak) to modify this set
+//
+// When creating your own config, you can remove all hotkeys that you have not changed and game will read them from this file
+{
+	"keyboard" : {
+		"globalAccept":             [ "Return", "Keypad Enter"],
+		"globalCancel":             "Escape",
+		"globalReturn":             [ "Escape", "Return", "Keypad Enter"],
+		"globalFullscreen":         "F4",
+		"globalOptions":            "O",
+		"globalBackspace":          "Backspace",
+		"globalMoveFocus":          "Tab",
+		"moveLeft":                 "Left",
+		"moveRight":                "Right",
+		"moveUp":                   "Up",
+		"moveDown":                 "Down",
+		"moveFirst":                "Home",
+		"moveLast":                 "End",
+		"movePageUp":               "PageUp",
+		"movePageDown":             "PageDown",
+		"selectIndex1":             "1",
+		"selectIndex2":             "2",
+		"selectIndex3":             "3",
+		"selectIndex4":             "4",
+		"selectIndex5":             "5",
+		"selectIndex6":             "6",
+		"selectIndex7":             "7",
+		"selectIndex8":             "8",
+		"mainMenuNewGame":          "N",
+		"mainMenuLoadGame":         "L",
+		"mainMenuHighScores":       "H",
+		"mainMenuCredits":          "C",
+		"mainMenuQuit":             "Q",
+		"mainMenuBack":             "B",
+		"mainMenuSingleplayer":     "S",
+		"mainMenuMultiplayer":      "M",
+		"mainMenuCampaign":         "C",
+		"mainMenuTutorial":         "T",
+		"mainMenuCampaignSod":      "S",
+		"mainMenuCampaignRoe":      "R",
+		"mainMenuCampaignAb":       "A",
+		"mainMenuCampaignCustom":   "C",
+		"lobbyBeginGame":           [ "B", "Return", "Keypad Enter"],
+		"lobbyLoadGame":            [ "L", "Return", "Keypad Enter"],
+		"lobbySaveGame":            [ "S", "Return", "Keypad Enter"],
+		"lobbyRandomMap":           "R",
+		"lobbyHideChat":            "H",
+		"lobbyAdditionalOptions":   "A",
+		"lobbySelectScenario":      "S",
+		"gameEndTurn":              "E",
+		"gameLoadGame":             "L",
+		"gameSaveGame":             "S",
+		"gameRestartGame":          "R",
+		"gameMainMenu":             "M",
+		"gameQuitGame":             "Q",
+		"gameOpenMarketplace":      "B",
+		"gameOpenThievesGuild":     "G",
+		"gameActivateConsole":      "Tab",
+		"adventureGameOptions":     "O",
+		"adventureToggleGrid":      "F6",
+		"adventureToggleSleep":     [],
+		"adventureSetHeroAsleep":   "Z",
+		"adventureSetHeroAwake":    "W",
+		"adventureMoveHero":        "M",
+		"adventureVisitObject":     "Space",
+		"adventureMoveHeroSW":      [ "Keypad 1" ],
+		"adventureMoveHeroSS":      [ "Keypad 2", "Down" ],
+		"adventureMoveHeroSE":      [ "Keypad 3" ],
+		"adventureMoveHeroWW":      [ "Keypad 4", "Left" ],
+		"adventureMoveHeroEE":      [ "Keypad 6", "Right" ],
+		"adventureMoveHeroNW":      [ "Keypad 7" ],
+		"adventureMoveHeroNN":      [ "Keypad 8", "Up" ],
+		"adventureMoveHeroNE":      [ "Keypad 9" ],
+		"adventureViewSelected":    [ "Return", "Keypad Enter"],
+		"adventureNextObject":      [],
+		"adventureNextTown":        "T",
+		"adventureNextHero":        "H",
+		"adventureFirstTown":       [],
+		"adventureFirstHero":       [],
+		"adventureViewScenario":    "I",
+		"adventureDigGrail":        "D",
+		"adventureViewPuzzle":      "P",
+		"adventureViewWorld":       "V",
+		"adventureViewWorld1":      "1",
+		"adventureViewWorld2":      "2",
+		"adventureViewWorld4":      "4",
+		"adventureToggleMapLevel":  "U",
+		"adventureKingdomOverview": "K",
+		"adventureQuestLog":        "Q",
+		"adventureCastSpell":       "C",
+		"adventureThievesGuild":    "G",
+		"adventureExitWorldView":   [ "Escape", "Return", "Keypad Enter"],
+		"adventureZoomIn":          "Keypad +",
+		"adventureZoomOut":         "Keypad -",
+		"adventureZoomReset":       "Backspace",
+		"battleToggleQueue":        "Q",
+		"battleUseCreatureSpell":   "F",
+		"battleSurrender":          "S",
+		"battleRetreat":            "R",
+		"battleAutocombat":         "A",
+		"battleAutocombatEnd":      "E",
+		"battleCastSpell":          "C",
+		"battleWait":               "W",
+		"battleDefend":             [ "D", "Space"],
+		"battleConsoleUp":          "Up",
+		"battleConsoleDown":        "Down",
+		"battleTacticsNext":        "Space",
+		"battleTacticsEnd":         [ "Return", "Keypad Enter"],
+		"battleSelectAction":       "S",
+		"townOpenTavern":           "T",
+		"townSwapArmies":           "Space",
+		"recruitmentMax":           "End",
+		"recruitmentMin":           "Home",
+		"recruitmentUpgrade":       "U",
+		"recruitmentUpgradeAll":    [ "A", "U" ],
+		"kingdomHeroesTab":         "H",
+		"kingdomTownsTab":          "T",
+		"heroDismiss":              "D",
+		"heroCommander":            "C",
+		"heroLooseFormation":       "L",
+		"heroTightFormation":       "T",
+		"heroToggleTactics":        "B",
+		"spellbookTabAdventure":    "A",
+		"spellbookTabCombat":       "C"
+	}
+}