Files
scummvm-cursorfix/engines/ultima/nuvie/keybinding/keys.cpp
2026-02-02 04:50:13 +01:00

1040 lines
33 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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<Common::U32String> KeyBinder::buildKeyHelp() const {
Common::Array<Common::U32String> keyHelp;
Common::HashMap<Common::String, const Action*> 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<Common::HardwareInput> 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<Common::U32String> 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<Common::KeyCode>(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