/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ultima/nuvie/keybinding/keys.h" #include "ultima/nuvie/keybinding/key_actions.h" #include "ultima/nuvie/keybinding/key_help_dialog.h" #include "ultima/nuvie/core/nuvie_defs.h" #include "ultima/nuvie/core/game.h" #include "ultima/nuvie/core/player.h" #include "ultima/nuvie/core/events.h" #include "ultima/nuvie/files/utils.h" #include "ultima/nuvie/gui/widgets/msg_scroll.h" #include "ultima/nuvie/conf/configuration.h" #include "ultima/nuvie/files/nuvie_io.h" #include "ultima/nuvie/misc/u6_misc.h" #include "ultima/nuvie/gui/widgets/console.h" #include "ultima/nuvie/core/effect.h" #include "ultima/shared/conf/xml_tree.h" #include "common/hash-str.h" #include "common/system.h" #include "common/hashmap.h" #include "common/translation.h" #include "backends/keymapper/keymapper.h" #include "backends/keymapper/action.h" #include "backends/keymapper/hardware-input.h" #define ENCODE_KEY(key, mod) ((uint32)(key) | ((uint32)(mod) << 24)) namespace Ultima { namespace Nuvie { static const char *whitespace = "\t\n\v\f\r "; typedef void(*ActionFunc)(int); struct Action { const char *kId; ActionFunc func; // called on keydown enum { KeyNotShown = 0, KeyNormal, KeyCheat } keyVisibility; bool allowInVehicle; ActionKeyType keyType; }; const char *const appendAltCodeActionStr = "ALT_CODE"; const char *const toggleAltCodeModeActionStr = "TOGGLE_ALT_CODE_MODE"; const uint toggleAltCodeModeEventID = Common::hashit(toggleAltCodeModeActionStr); // to identify END (KEYUP) events for alt-code mode toggle action const Action NuvieActions[] = { { "WALK_WEST", ActionWalkWest, Action::KeyNormal, true, WEST_KEY }, { "WALK_EAST", ActionWalkEast, Action::KeyNormal, true, EAST_KEY }, { "WALK_NORTH", ActionWalkNorth, Action::KeyNormal, true, NORTH_KEY }, { "WALK_SOUTH", ActionWalkSouth, Action::KeyNormal, true, SOUTH_KEY }, { "WALK_NORTH_EAST", ActionWalkNorthEast, Action::KeyNormal, true, NORTH_EAST_KEY }, { "WALK_SOUTH_EAST", ActionWalkSouthEast, Action::KeyNormal, true, SOUTH_EAST_KEY }, { "WALK_NORTH_WEST", ActionWalkNorthWest, Action::KeyNormal, true, NORTH_WEST_KEY }, { "WALK_SOUTH_WEST", ActionWalkSouthWest, Action::KeyNormal, true, SOUTH_WEST_KEY }, { "CAST", ActionCast, Action::KeyNormal, true, OTHER_KEY }, // allow_in_vehicle is true, so the right message or cancel is done for WOU games { "LOOK", ActionLook, Action::KeyNormal, true, OTHER_KEY }, { "TALK", ActionTalk, Action::KeyNormal, true, OTHER_KEY }, { "USE", ActionUse, Action::KeyNormal, true, OTHER_KEY }, { "GET", ActionGet, Action::KeyNormal, false, OTHER_KEY }, { "MOVE", ActionMove, Action::KeyNormal, false, OTHER_KEY }, { "DROP", ActionDrop, Action::KeyNormal, false, OTHER_KEY }, { "TOGGLE_COMBAT", ActionToggleCombat, Action::KeyNormal, false, OTHER_KEY }, { "ATTACK", ActionAttack, Action::KeyNormal, true, OTHER_KEY }, { "REST", ActionRest, Action::KeyNormal, true, OTHER_KEY }, { "MULTI_USE", ActionMultiUse, Action::KeyNormal, true, OTHER_KEY }, { "SELECT_COMMAND_BAR", ActionSelectCommandBar, Action::KeyNormal, true, OTHER_KEY }, { "NEW_COMMAND_BAR", ActionSelectNewCommandBar, Action::KeyNormal, true, NEW_COMMAND_BAR_KEY }, { "DOLL_GUMP", ActionDollGump, Action::KeyNormal, true, OTHER_KEY }, { "SHOW_STATS", ActionShowStats, Action::KeyNormal, true, OTHER_KEY }, { "INVENTORY", ActionInventory, Action::KeyNormal, true, OTHER_KEY }, { "PREVIOUS_PARTY_MEMBER", ActionPreviousPartyMember, Action::KeyNormal, true, PREVIOUS_PARTY_MEMBER_KEY }, { "NEXT_PARTY_MEMBER", ActionNextPartyMember, Action::KeyNormal, true, NEXT_PARTY_MEMBER_KEY }, { "HOME_KEY", ActionHome, Action::KeyNormal, true, HOME_KEY }, { "END_KEY", ActionEnd, Action::KeyNormal, true, END_KEY }, { "TOGGLE_VIEW", ActionToggleView, Action::KeyNormal, true, OTHER_KEY }, { "PARTY_VIEW", ActionPartyView, Action::KeyNormal, true, OTHER_KEY }, { "SOLO_MODE", ActionSoloMode, Action::KeyNormal, true, OTHER_KEY }, { "PARTY_MODE", ActionPartyMode, Action::KeyNormal, true, OTHER_KEY }, { "SAVE_MENU", ActionSaveDialog, Action::KeyNormal, true, OTHER_KEY }, { "LOAD_LATEST_SAVE", ActionLoadLatestSave, Action::KeyNormal, true, OTHER_KEY }, { "QUICK_SAVE", ActionQuickSave, Action::KeyNormal, true, OTHER_KEY }, { "QUICK_LOAD", ActionQuickLoad, Action::KeyNormal, true, OTHER_KEY }, { "QUIT", ActionQuitDialog, Action::KeyNormal, true, QUIT_KEY }, //{ "QUIT_NO_DIALOG", ActionQuitNODialog, Action::normal_keys, true, QUIT_KEY }, { "GAME_MENU_DIALOG", ActionGameMenuDialog, Action::KeyNormal, true, CANCEL_ACTION_KEY }, //{ "TOGGLE_FULLSCREEN", ActionToggleFullscreen, "Toggle Fullscreen", Action::normal_keys, true, TOGGLE_FULLSCREEN_KEY }, { "TOGGLE_CURSOR", ActionToggleCursor, Action::KeyNormal, true, TOGGLE_CURSOR_KEY }, { "TOGGLE_COMBAT_STRATEGY", ActionToggleCombatStrategy, Action::KeyNormal, true, OTHER_KEY }, { "TOGGLE_FPS_DISPLAY", ActionToggleFps, Action::KeyNormal, true, TOGGLE_FPS_KEY }, { "TOGGLE_AUDIO", ActionToggleAudio, Action::KeyNormal, true, TOGGLE_AUDIO_KEY }, { "TOGGLE_MUSIC", ActionToggleMusic, Action::KeyNormal, true, TOGGLE_MUSIC_KEY }, { "TOGGLE_SFX", ActionToggleSFX, Action::KeyNormal, true, TOGGLE_SFX_KEY }, { "TOGGLE_ORIGINAL_STYLE_COMMAND_BAR", ActionToggleOriginalStyleCommandBar, Action::KeyNormal, true, OTHER_KEY }, { "DO_ACTION", ActionDoAction, Action::KeyNormal, true, DO_ACTION_KEY }, { "CANCEL_ACTION", ActionCancelAction, Action::KeyNormal, true, CANCEL_ACTION_KEY }, { "MSG_SCROLL_UP", ActionMsgScrollUP, Action::KeyNormal, true, MSGSCROLL_UP_KEY }, { "MSG_SCROLL_DOWN", ActionMsgScrollDown, Action::KeyNormal, true, MSGSCROLL_DOWN_KEY }, { "SHOW_KEYS", ActionShowKeys, Action::KeyNormal, true, OTHER_KEY }, { "DECREASE_DEBUG", ActionDecreaseDebug, Action::KeyNormal, true, DECREASE_DEBUG_KEY }, { "INCREASE_DEBUG", ActionIncreaseDebug, Action::KeyNormal, true, INCREASE_DEBUG_KEY }, { "CLOSE_GUMPS", ActionCloseGumps, Action::KeyNormal, true, OTHER_KEY }, { "USE_ITEM", ActionUseItem, Action::KeyNormal, true, OTHER_KEY }, { "ASSET_VIEWER", ActionAssetViewer, Action::KeyNormal, true, OTHER_KEY }, { "SHOW_EGGS", ActionShowEggs, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_HACKMOVE", ActionToggleHackmove, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_EGG_SPAWN", ActionToggleEggSpawn, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_UNLIMITED_CASTING", ActionToggleUnlimitedCasting, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_NO_DARKNESS", ActionToggleNoDarkness, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_PICKPOCKET_MODE", ActionTogglePickpocket, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_GOD_MODE", ActionToggleGodMode, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_ETHEREAL", ActionToggleEthereal, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_X_RAY", ActionToggleX_Ray, Action::KeyCheat, true, OTHER_KEY }, { "HEAL_PARTY", ActionHealParty, Action::KeyCheat, true, OTHER_KEY }, { "TELEPORT_TO_CURSOR", ActionTeleportToCursor, Action::KeyCheat, true, OTHER_KEY }, { "TOGGLE_CHEATS", ActionToggleCheats, Action::KeyNormal, true, OTHER_KEY }, { toggleAltCodeModeActionStr, ActionToggleAltCodeMode, Action::KeyNormal, true, OTHER_KEY }, { appendAltCodeActionStr, ActionAppendAltCode, Action::KeyNormal, true, OTHER_KEY }, { "DO_NOTHING", ActionDoNothing, Action::KeyNotShown, true, OTHER_KEY }, }; const char *const PerPartyMemberActions[] = { "SOLO_MODE", "SHOW_STATS", "INVENTORY", "DOLL_GUMP" }; struct KeycodeString { const char *s; Common::KeyCode k; }; // for additional key mappings outside txt file struct KeycodeToAction { Common::KeyCode keyCode; const char *actionTitle; }; const KeycodeToAction iosKeycodes[] = { { JOY12, "WALK_NORTH" }, { JOY13, "WALK_SOUTH" }, { JOY14, "WALK_WEST" }, { JOY15, "WALK_EAST"} }; // // These don't match the strings in backends/keymapper/hardware-input.cpp // but are from Nuvie before ScummVM integration. Leave them here for // backward compatibility. // const KeycodeString KeycodeStringTable[] = { {"LCTRL", Common::KEYCODE_LCTRL}, {"RCTRL", Common::KEYCODE_RCTRL}, {"LALT", Common::KEYCODE_LALT}, {"RALT", Common::KEYCODE_RALT}, {"LSHIFT", Common::KEYCODE_LSHIFT}, {"RSHIFT", Common::KEYCODE_RSHIFT}, {"BACKSPACE", Common::KEYCODE_BACKSPACE}, {"TAB", Common::KEYCODE_TAB}, {"ENTER", Common::KEYCODE_RETURN}, {"PAUSE", Common::KEYCODE_PAUSE}, {"ESC", Common::KEYCODE_ESCAPE}, {"SPACE", Common::KEYCODE_SPACE}, {"DEL", Common::KEYCODE_DELETE}, {"KP0", Common::KEYCODE_KP0}, {"KP1", Common::KEYCODE_KP1}, {"KP2", Common::KEYCODE_KP2}, {"KP3", Common::KEYCODE_KP3}, {"KP4", Common::KEYCODE_KP4}, {"KP5", Common::KEYCODE_KP5}, {"KP6", Common::KEYCODE_KP6}, {"KP7", Common::KEYCODE_KP7}, {"KP8", Common::KEYCODE_KP8}, {"KP9", Common::KEYCODE_KP9}, {"KP.", Common::KEYCODE_KP_PERIOD}, {"KP/", Common::KEYCODE_KP_DIVIDE}, {"KP*", Common::KEYCODE_KP_MULTIPLY}, {"KP-", Common::KEYCODE_KP_MINUS}, {"KP+", Common::KEYCODE_KP_PLUS}, {"KP_ENTER", Common::KEYCODE_KP_ENTER}, {"UP", Common::KEYCODE_UP}, {"DOWN", Common::KEYCODE_DOWN}, {"RIGHT", Common::KEYCODE_RIGHT}, {"LEFT", Common::KEYCODE_LEFT}, {"INSERT", Common::KEYCODE_INSERT}, {"HOME", Common::KEYCODE_HOME}, {"END", Common::KEYCODE_END}, {"PAGEUP", Common::KEYCODE_PAGEUP}, {"PAGEDOWN", Common::KEYCODE_PAGEDOWN}, {"F1", Common::KEYCODE_F1}, {"F2", Common::KEYCODE_F2}, {"F3", Common::KEYCODE_F3}, {"F4", Common::KEYCODE_F4}, {"F5", Common::KEYCODE_F5}, {"F6", Common::KEYCODE_F6}, {"F7", Common::KEYCODE_F7}, {"F8", Common::KEYCODE_F8}, {"F9", Common::KEYCODE_F9}, {"F10", Common::KEYCODE_F10}, {"F11", Common::KEYCODE_F11}, {"F12", Common::KEYCODE_F12}, {"F13", Common::KEYCODE_F13}, {"F14", Common::KEYCODE_F14}, {"F15", Common::KEYCODE_F15}, // hackishly map joystick to unused keycode values {"JOY_UP", JOY_UP}, {"JOY_DOWN", JOY_DOWN}, {"JOY_LEFT", JOY_LEFT}, {"JOY_RIGHT", JOY_RIGHT}, {"JOY_RIGHTUP", JOY_RIGHTUP}, {"JOY_RIGHTDOWN", JOY_RIGHTDOWN}, {"JOY_LEFTUP", JOY_LEFTUP}, {"JOY_LEFTDOWN", JOY_LEFTDOWN}, {"JOY_UP2", JOY_UP2}, {"JOY_DOWN2", JOY_DOWN2}, {"JOY_LEFT2", JOY_LEFT2}, {"JOY_RIGHT2", JOY_RIGHT2}, {"JOY_RIGHTUP2", JOY_RIGHTUP2}, {"JOY_RIGHTDOWN2", JOY_RIGHTDOWN2}, {"JOY_LEFTUP2", JOY_LEFTUP2}, {"JOY_LEFTDOWN2", JOY_LEFTDOWN2}, {"JOY_UP3", JOY_UP3}, {"JOY_DOWN3", JOY_DOWN3}, {"JOY_LEFT3", JOY_LEFT3}, {"JOY_RIGHT3", JOY_RIGHT3}, {"JOY_RIGHTUP3", JOY_RIGHTUP3}, {"JOY_RIGHTDOWN3", JOY_RIGHTDOWN3}, {"JOY_LEFTUP3", JOY_LEFTUP3}, {"JOY_LEFTDOWN3", JOY_LEFTDOWN3}, {"JOY_UP4", JOY_UP4}, {"JOY_DOWN4", JOY_DOWN4}, {"JOY_LEFT4", JOY_LEFT4}, {"JOY_RIGHT4", JOY_RIGHT4}, {"JOY_RIGHTUP4", JOY_RIGHTUP4}, {"JOY_RIGHTDOWN4", JOY_RIGHTDOWN4}, {"JOY_LEFTUP4", JOY_LEFTUP4}, {"JOY_LEFTDOWN4", JOY_LEFTDOWN4}, {"JOY_HAT_UP", JOY_HAT_UP}, {"JOY_HAT_DOWN", JOY_HAT_DOWN}, {"JOY_HAT_LEFT", JOY_HAT_LEFT}, {"JOY_HAT_RIGHT", JOY_HAT_RIGHT}, {"JOY_HAT_RIGHTUP", JOY_HAT_RIGHTUP}, {"JOY_HAT_RIGHTDOWN", JOY_HAT_RIGHTDOWN}, {"JOY_HAT_LEFTUP", JOY_HAT_LEFTUP}, {"JOY_HAT_LEFTDOWN", JOY_HAT_LEFTDOWN}, {"JOY0", JOY0}, {"JOY1", JOY1}, {"JOY2", JOY2}, {"JOY3", JOY3}, {"JOY4", JOY4}, {"JOY5", JOY5}, {"JOY6", JOY6}, {"JOY7", JOY7}, {"JOY8", JOY8}, {"JOY9", JOY9}, {"JOY10", JOY10}, {"JOY11", JOY11}, {"JOY12", JOY12}, {"JOY13", JOY13}, {"JOY14", JOY14}, {"JOY15", JOY15}, {"JOY16", JOY16}, {"JOY17", JOY17}, {"JOY18", JOY18}, {"JOY19", JOY19}, }; const Action doNothingAction = { "DO_NOTHING", ActionDoNothing, Action::KeyNotShown, true, OTHER_KEY }; KeyBinder::KeyBinder(const Configuration *config) : enable_joystick(false) { FillParseMaps(); Std::string keyfilename, dir; config->value("config/keys", keyfilename, "(default)"); bool key_file_exists = fileExists(keyfilename.c_str()); if (keyfilename != "(default)" && !key_file_exists) ::error("Couldn't find the default key setting at %s - trying defaultkeys.txt in the data directory\n", keyfilename.c_str()); if (keyfilename == "(default)" || !key_file_exists) { config->value("config/datadir", dir, "./data"); keyfilename = dir + "/defaultkeys.txt"; } LoadFromFile(keyfilename.c_str()); LoadGameSpecificKeys(); // won't load if file isn't found LoadFromPatch(); // won't load if file isn't found int config_int; uint16 max_delay = 10000; // 10 seconds but means no repeat config->value("config/joystick/repeat_hat", repeat_hat, false); config->value("config/joystick/repeat_delay", config_int, 50); joy_repeat_delay = config_int < max_delay ? config_int : max_delay; if (joy_repeat_delay == max_delay) joy_repeat_enabled = false; else joy_repeat_enabled = true; Common::fill(_joyAxisPositions, _joyAxisPositions + 8, 0); // AXES_PAIR1 config->value("config/joystick/axes_pair1/x_axis", config_int, 0); x_axis = config_int < 255 ? config_int : 255; config->value("config/joystick/axes_pair1/y_axis", config_int, 1); y_axis = config_int < 255 ? config_int : 255; config->value("config/joystick/axes_pair1/delay", config_int, 110); pair1_delay = config_int < max_delay ? config_int : max_delay; // AXES_PAIR2 config->value("config/joystick/axes_pair2/x_axis", config_int, 3); x_axis2 = config_int < 255 ? config_int : 255; config->value("config/joystick/axes_pair2/y_axis", config_int, 2); y_axis2 = config_int < 255 ? config_int : 255; config->value("config/joystick/axes_pair2/delay", config_int, 110); pair2_delay = config_int < max_delay ? config_int : max_delay; // AXES_PAIR3 config->value("config/joystick/axes_pair3/x_axis", config_int, 4); x_axis3 = config_int < 255 ? config_int : 255; config->value("config/joystick/axes_pair3/y_axis", config_int, 5); y_axis3 = config_int < 255 ? config_int : 255; config->value("config/joystick/axes_pair3/delay", config_int, 110); pair3_delay = config_int < max_delay ? config_int : max_delay; // AXES_PAIR4 config->value("config/joystick/axes_pair4/x_axis", config_int, 6); x_axis4 = config_int < 255 ? config_int : 255; config->value("config/joystick/axes_pair4/y_axis", config_int, 7); y_axis4 = config_int < 255 ? config_int : 255; config->value("config/joystick/axes_pair4/delay", config_int, 110); pair4_delay = config_int < max_delay ? config_int : max_delay; next_axes_pair_update = next_axes_pair2_update = next_axes_pair3_update = 0; next_axes_pair4_update = next_joy_repeat_time = 0; AddIosBindings(); } void KeyBinder::AddIosBindings() { unsigned long i; for (i=0; i < sizeof(iosKeycodes) / sizeof(KeycodeToAction); i++) { KeycodeToAction ka = iosKeycodes[i]; if (!_bindings.contains(ka.keyCode)) AddKeyBinding(ka.keyCode, 0, _actions.getVal(ka.actionTitle), 0, 0); } } KeyBinder::~KeyBinder() { } void KeyBinder::AddKeyBinding(Common::KeyCode key, byte mod, const Action *action, int nparams, int param) { Common::KeyState k; ActionType a; a.action = action; assert(nparams == 0 || nparams == 1); if (nparams == 1) a.param = param; else a.param = -1; _bindings[ENCODE_KEY(key, mod)] = a; } ActionType KeyBinder::get_ActionType(const Common::KeyState &key) { KeyMap::iterator sdlkey_index = get_sdlkey_index(key); if (sdlkey_index == _bindings.end()) { ActionType actionType = {&doNothingAction, 0}; return actionType; } return (*sdlkey_index)._value; } ActionKeyType KeyBinder::GetActionKeyType(ActionType a) { return a.action->keyType; } bool KeyBinder::DoAction(ActionType const &a) const { if (!a.action->allowInVehicle && Game::get_game()->get_player()->is_in_vehicle() && Game::get_game()->get_game_type() != NUVIE_GAME_MD) { Game::get_game()->get_event()->display_not_aboard_vehicle(); return true; } else if (a.action->keyVisibility == Action::KeyCheat && !Game::get_game()->are_cheats_enabled()) { new TextEffect("Cheats are disabled"); return true; } a.action->func(a.param); return true; } KeyMap::iterator KeyBinder::get_sdlkey_index(const Common::KeyState &key) { return _bindings.find(ENCODE_KEY(key.keycode, key.flags)); } bool KeyBinder::HandleEvent(const Common::Event *ev) { Common::KeyState key = ev->kbd; if (ev->type != Common::EVENT_KEYDOWN) return false; key.flags &= ~Common::KBD_STICKY; KeyMap::iterator sdlkey_index = get_sdlkey_index(key); if (sdlkey_index != _bindings.end()) return DoAction((*sdlkey_index)._value); // Avoid modifier keys being detected as invalid input if (ev->kbd.keycode != Common::KEYCODE_LALT && ev->kbd.keycode != Common::KEYCODE_RALT && ev->kbd.keycode != Common::KEYCODE_LCTRL && ev->kbd.keycode != Common::KEYCODE_RCTRL && ev->kbd.keycode != Common::KEYCODE_LSHIFT && ev->kbd.keycode != Common::KEYCODE_RSHIFT) { handle_wrong_key_pressed(); } return false; } bool KeyBinder::handleScummVMBoundEvent(const Common::Event *ev) { if (ev->type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) { ParseHashedActionMap::iterator iter = _actionsHashed.find(ev->customType); if (iter != _actionsHashed.end()) { const ActionType action = iter->_value; return DoAction(action); } } else if (ev->type == Common::EVENT_CUSTOM_ENGINE_ACTION_END && ev->customType == toggleAltCodeModeEventID) { // Evaluate and reset alt code when corresponding key is released ActionToggleAltCodeMode(kAltCodeModeEnd); return true; } return false; } void KeyBinder::handle_wrong_key_pressed() { if (Game::get_game()->get_event()->get_mode() != MOVE_MODE) Game::get_game()->get_event()->cancelAction(); else { Game::get_game()->get_scroll()->display_string("what?\n\n"); Game::get_game()->get_scroll()->display_prompt(); } } bool KeyBinder::handle_always_available_keys(ActionType a) { switch (a.action->keyType) { case TOGGLE_AUDIO_KEY: // FIXME - Shutting off the music in cutscenes will not have any music play when case TOGGLE_MUSIC_KEY: // you toggle it back on. It has to wait for the engine to play another song case TOGGLE_SFX_KEY: case TOGGLE_FPS_KEY: // fps is not available in intro or when viewing ending with command line cheat case TOGGLE_FULLSCREEN_KEY: case DECREASE_DEBUG_KEY: case INCREASE_DEBUG_KEY: case QUIT_KEY: // FIXME - doesn't currently work in intro or when viewing ending with command line cheat a.action->func(a.param); return true; default: return false; } } Common::Array KeyBinder::buildKeyHelp() const { Common::Array keyHelp; Common::HashMap keyActionMap; for (const Action &action : NuvieActions) { keyActionMap.setVal(action.kId, &action); } Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper(); for (const Common::Keymap *map : keymapper->getKeymaps()) { if (!map->isEnabled() || map->getType() != Common::Keymap::kKeymapTypeGame) continue; for (const Common::Action *action : map->getActions()) { if (keyActionMap.contains(action->id) && keyActionMap[action->id]->keyVisibility != Action::KeyNormal) continue; Common::Array inputs = map->getActionMapping(action); if (inputs.size() > 0) { Common::U32String desc; // The * can't be bolded easily in markdown.. if (inputs[0].description == "*") desc = "*"; else desc = Common::U32String("**") + inputs[0].description + Common::U32String("**"); desc += Common::U32String(" - ") + action->description; keyHelp.push_back(desc); } } } return keyHelp; } void KeyBinder::ShowKeys() const { Common::Array keyHelp = buildKeyHelp(); Common::U32String helpStr; for (const Common::U32String &help : keyHelp) { helpStr += Common::U32String("\n") + help; } KeyHelpDialog helpDialog(helpStr); helpDialog.runModal(); } void KeyBinder::ParseText(char *text, int len) { char *ptr, *end; const char LF = '\n'; ptr = text; // last (useful) line must end with LF while ((ptr - text) < len && (end = strchr(ptr, LF)) != 0) { *end = '\0'; ParseLine(ptr); ptr = end + 1; } } static void skipspace(string &s) { size_t i = s.findFirstNotOf(whitespace); if (i && i != string::npos) s.erase(0, i); } void KeyBinder::ParseLine(const char *line) { size_t i; Common::KeyState k(Common::KEYCODE_INVALID); string s = line; string keycode; skipspace(s); // comments and empty lines if (s.empty() || s.hasPrefix("#")) return; string u = s; u.toUppercase(); // get key while (!s.empty() && !Common::isSpace(s[0])) { // check modifiers if (u.hasPrefix("ALT-")) { k.flags = k.flags | Common::KBD_ALT; s.erase(0, 4); u.erase(0, 4); } else if (u.hasPrefix("CTRL-")) { k.flags = k.flags | Common::KBD_CTRL; s.erase(0, 5); u.erase(0, 5); } else if (u.hasPrefix("SHIFT-")) { k.flags = k.flags | Common::KBD_SHIFT; s.erase(0, 6); u.erase(0, 6); } else { i = s.findFirstOf(whitespace); keycode = s.substr(0, i); s.erase(0, i); string t = keycode; t.toUppercase(); if (t.empty()) { ::error("Keybinder: parse error in line: %s", s.c_str()); } else if (t.size() == 1) { // translate 1-letter keys straight to Common::KeyCode char c = t[0]; if (c >= 33 && c <= 122 && c != 37) { if (c >= 'A' && c <= 'Z') c += 32; // need lowercase k.keycode = static_cast(c); } else { ::error("Keybinder: unsupported key: %s", keycode.c_str()); } } else { // lookup in table ParseKeyMap::iterator key_index; key_index = _keys.find(t); if (key_index != _keys.end()) { k.keycode = (*key_index)._value; } else { ::error("Keybinder: unsupported key: %s", keycode.c_str()); } } } } if (k.keycode == Common::KEYCODE_INVALID) { ::error("Keybinder: parse error in line: %s", s.c_str()); } // get function skipspace(s); i = s.findFirstOf(whitespace); string t = s.substr(0, i); s.erase(0, i); t.toUppercase(); ParseActionMap::const_iterator action_index = _actions.find(t); ActionType a; if (action_index != _actions.end()) { a.action = action_index->_value; } else { ::warning("Keybinder: unsupported action: %s", t.c_str()); return; } // get params skipspace(s); int np = 0; a.param = -1; if (!s.empty() && s[0] != '#') { i = s.findFirstOf(whitespace); string tmp = s.substr(0, i); s.erase(0, i); skipspace(s); int p = atoi(tmp.c_str()); a.param = p; np = 1; } // bind key AddKeyBinding(k.keycode, k.flags, a.action, np, a.param); } void KeyBinder::LoadFromFileInternal(const char *filename) { Common::ReadStream *keyfile = nullptr; openFile(keyfile, filename); if (!keyfile) ::error("Keybinder: can't open file %s", filename); char temp[1024]; // 1024 should be long enough while (!keyfile->eos()) { strgets(temp, 1024, keyfile); if (strlen(temp) >= 1023) { ::error("Keybinder: parse error: line too long. Skipping rest of file"); } ParseLine(temp); } delete keyfile; } void KeyBinder::LoadFromFile(const char *filename) { Flush(); ConsoleAddInfo("Loading keybindings from file %s", filename); LoadFromFileInternal(filename); } void KeyBinder::LoadGameSpecificKeys() { string key_path_str; string default_key_path; const Configuration *config = Game::get_game()->get_config(); config->value("config/datadir", default_key_path, "./data"); nuvie_game_t game_type = get_game_type(config); if (game_type == NUVIE_GAME_U6) default_key_path += "/u6keys.txt"; else if (game_type == NUVIE_GAME_MD) default_key_path += "/mdkeys.txt"; else // SE default_key_path += "/sekeys.txt"; config->value(config_get_game_key(config) + "/game_specific_keys", key_path_str, default_key_path.c_str()); const char *key_path; if (key_path_str == "(default)") key_path = default_key_path.c_str(); else key_path = key_path_str.c_str(); if (fileExists(key_path)) { ConsoleAddInfo("Loading %s", key_path); LoadFromFileInternal(key_path); } else // These aren't critical so failing to load doesn't matter much ConsoleAddInfo("Couldn't find %s", key_path); } void KeyBinder::LoadFromPatch() { // FIXME default should probably be system specific string PATCH_KEYS; const Configuration *config = Game::get_game()->get_config(); config->value(config_get_game_key(config) + "/patch_keys", PATCH_KEYS, "./patchkeys.txt"); if (fileExists(PATCH_KEYS.c_str())) { ConsoleAddInfo("Loading patch keybindings"); LoadFromFileInternal(PATCH_KEYS.c_str()); } } // codes used in keybindings-files. (use uppercase here) void KeyBinder::FillParseMaps() { for (const KeycodeString keycodeStr : KeycodeStringTable) _keys[keycodeStr.s] = keycodeStr.k; for (const Action &action : NuvieActions) { const Common::String keyId(action.kId); _actions[keyId] = &action; ActionType at; at.action = &action; at.param = 0; _actionsHashed[keyId.hash()] = at; } for (const char *perMemberAction : PerPartyMemberActions) { if (!_actions.contains(perMemberAction)) error("No base definition for per-party-member action %s", perMemberAction); for (int i = 1; i <= 9; i++) { Common::String actionId = Common::String::format("%s_%d", perMemberAction, i); const Action *action = _actions[perMemberAction]; ActionType at; at.action = action; at.param = i; _actionsHashed[actionId.hash()] = at; } } if (!_actions.contains(appendAltCodeActionStr)) error("No base definition for alt-code action %s", appendAltCodeActionStr); for (int i = 0; i <= 9; ++i) { Common::String actionId = Common::String::format("%s_%d", appendAltCodeActionStr, i); const Action *action = _actions[appendAltCodeActionStr]; ActionType at; at.action = action; at.param = i; _actionsHashed[actionId.hash()] = at; } } uint8 KeyBinder::get_axis(uint8 index) const { switch (index) { case 0: return x_axis; case 1: return y_axis; case 2: return x_axis2; case 3: return y_axis2; case 4: return x_axis3; case 5: return y_axis3; case 6: return x_axis4; case 7: default: return y_axis4; } } void KeyBinder::set_axis(uint8 index, uint8 value) { switch (index) { case 0: x_axis = value; break; case 1: y_axis = value; break; case 2: x_axis2 = value; break; case 3: y_axis2 = value; break; case 4: x_axis3 = value; break; case 5: y_axis3 = value; break; case 6: x_axis4 = value; break; case 7: default: y_axis4 = value; break; } } joy_axes_pairs KeyBinder::get_axes_pair(int axis) const { if (axis == x_axis || axis == y_axis) return AXES_PAIR1; else if (axis == x_axis2 || axis == y_axis2) return AXES_PAIR2; else if (axis == x_axis3 || axis == y_axis3) return AXES_PAIR3; else if (axis == x_axis4 || axis == y_axis4) return AXES_PAIR4; else return UNHANDLED_AXES_PAIR; } Common::KeyCode KeyBinder::get_key_from_joy_axis_motion(int axis, bool repeating) { joy_axes_pairs axes_pair = get_axes_pair(axis); if (axes_pair == UNHANDLED_AXES_PAIR) return Common::KEYCODE_INVALID; sint8 xoff = 0; sint8 yoff = 0; int xaxis, yaxis; switch (axes_pair) { case AXES_PAIR1: xaxis = x_axis; yaxis = y_axis; break; case AXES_PAIR2: xaxis = x_axis2; yaxis = y_axis2; break; case AXES_PAIR3: xaxis = x_axis3; yaxis = y_axis3; break; case AXES_PAIR4: xaxis = x_axis4; yaxis = y_axis4; break; default: return Common::KEYCODE_INVALID; // shouldn't happen } if (xaxis != 255 && _joyAxisPositions[xaxis] != 0) xoff = _joyAxisPositions[xaxis] < 0 ? -1 : 1; if (yaxis != 255 && _joyAxisPositions[yaxis] != 0) yoff = _joyAxisPositions[yaxis] < 0 ? -1 : 1; NuvieDir dir = get_direction_code(xoff, yoff); if (axes_pair == AXES_PAIR1) { if (dir == NUVIE_DIR_NONE) { next_axes_pair_update = 0; // centered so okay to reset if (!repeat_hat) next_joy_repeat_time = SDL_GetTicks() + joy_repeat_delay; return Common::KEYCODE_INVALID; } else if ((repeating && next_joy_repeat_time > SDL_GetTicks()) || (!repeating && next_axes_pair_update > SDL_GetTicks())) // don't repeat too fast return Common::KEYCODE_INVALID; next_axes_pair_update = SDL_GetTicks() + pair1_delay; if (!repeat_hat) next_joy_repeat_time = SDL_GetTicks() + joy_repeat_delay; switch (dir) { case NUVIE_DIR_N: return JOY_UP; case NUVIE_DIR_E: return JOY_RIGHT; case NUVIE_DIR_S: return JOY_DOWN; case NUVIE_DIR_W: return JOY_LEFT; case NUVIE_DIR_NE: return JOY_RIGHTUP; case NUVIE_DIR_SE: return JOY_RIGHTDOWN; case NUVIE_DIR_SW: return JOY_LEFTDOWN; case NUVIE_DIR_NW: return JOY_LEFTUP; default: return Common::KEYCODE_INVALID; // shouldn't happen } } else if (axes_pair == AXES_PAIR2) { if (dir == NUVIE_DIR_NONE) { next_axes_pair2_update = 0; // centered so okay to reset return Common::KEYCODE_INVALID; } else if (next_axes_pair2_update > SDL_GetTicks()) // don't repeat too fast return Common::KEYCODE_INVALID; else next_axes_pair2_update = SDL_GetTicks() + pair2_delay; switch (dir) { case NUVIE_DIR_N: return JOY_UP2; case NUVIE_DIR_E: return JOY_RIGHT2; case NUVIE_DIR_S: return JOY_DOWN2; case NUVIE_DIR_W: return JOY_LEFT2; case NUVIE_DIR_NE: return JOY_RIGHTUP2; case NUVIE_DIR_SE: return JOY_RIGHTDOWN2; case NUVIE_DIR_SW: return JOY_LEFTDOWN2; case NUVIE_DIR_NW: return JOY_LEFTUP2; default: return Common::KEYCODE_INVALID; // shouldn't happen } } else if (axes_pair == AXES_PAIR3) { if (dir == NUVIE_DIR_NONE) { next_axes_pair3_update = 0; // centered so okay to reset return Common::KEYCODE_INVALID; } else if (next_axes_pair3_update > SDL_GetTicks()) // don't repeat too fast return Common::KEYCODE_INVALID; else next_axes_pair3_update = SDL_GetTicks() + pair3_delay; switch (dir) { case NUVIE_DIR_N: return JOY_UP3; case NUVIE_DIR_E: return JOY_RIGHT3; case NUVIE_DIR_S: return JOY_DOWN3; case NUVIE_DIR_W: return JOY_LEFT3; case NUVIE_DIR_NE: return JOY_RIGHTUP3; case NUVIE_DIR_SE: return JOY_RIGHTDOWN3; case NUVIE_DIR_SW: return JOY_LEFTDOWN3; case NUVIE_DIR_NW: return JOY_LEFTUP3; default: return Common::KEYCODE_INVALID; // shouldn't happen } } else { // AXES_PAIR4 if (dir == NUVIE_DIR_NONE) { next_axes_pair4_update = 0; // centered so okay to reset return Common::KEYCODE_INVALID; } else if (next_axes_pair4_update > SDL_GetTicks()) // don't repeat too fast return Common::KEYCODE_INVALID; next_axes_pair4_update = SDL_GetTicks() + pair4_delay; switch (dir) { case NUVIE_DIR_N: return JOY_UP4; case NUVIE_DIR_E: return JOY_RIGHT4; case NUVIE_DIR_S: return JOY_DOWN4; case NUVIE_DIR_W: return JOY_LEFT4; case NUVIE_DIR_NE: return JOY_RIGHTUP4; case NUVIE_DIR_SE: return JOY_RIGHTDOWN4; case NUVIE_DIR_SW: return JOY_LEFTDOWN4; case NUVIE_DIR_NW: return JOY_LEFTUP4; default: return Common::KEYCODE_INVALID; // shouldn't happen } } } Common::KeyCode KeyBinder::get_key_from_joy_button(uint8 button) { switch (button) { case 0: return JOY0; case 1: return JOY1; case 2: return JOY2; case 3: return JOY3; case 4: return JOY4; case 5: return JOY5; case 6: return JOY6; case 7: return JOY7; case 8: return JOY8; case 9: return JOY9; case 10: return JOY10; case 11: return JOY11; case 12: return JOY12; case 13: return JOY13; case 14: return JOY14; case 15: return JOY15; case 16: return JOY16; case 17: return JOY17; case 18: return JOY18; case 19: return JOY19; default: return Common::KEYCODE_INVALID; // unhandled button } } Common::KeyCode KeyBinder::get_key_from_joy_events(Common::Event *event) { if (event->type == Common::EVENT_JOYBUTTON_UP) { return get_key_from_joy_button(event->joystick.button); } else if (event->type == Common::EVENT_JOYAXIS_MOTION && event->joystick.axis < 8) { _joyAxisPositions[event->joystick.axis] = event->joystick.position; return get_key_from_joy_axis_motion(event->joystick.axis, false); } else { return Common::KEYCODE_INVALID; } } char get_ascii_char_from_keysym(Common::KeyState keysym) { char ascii = 0; if (keysym.keycode < 128) { ascii = (char)keysym.keycode; if (ascii >= 97 && ascii <= 122 && keysym.flags & (Common::KBD_SHIFT | Common::KBD_CAPS)) { ascii -= 32; } } return ascii; } } // End of namespace Nuvie } // End of namespace Ultima