diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 87efd544a..919837e59 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -35,7 +35,7 @@ set(client_SRCS eventsSDL/InputSourceText.cpp eventsSDL/InputSourceTouch.cpp eventsSDL/InputSourceGameController.cpp - eventsSDL/GameControllerShortcuts.cpp + eventsSDL/GameControllerConfig.cpp gui/CGuiHandler.cpp gui/CIntObject.cpp @@ -214,7 +214,7 @@ set(client_HEADERS eventsSDL/InputSourceText.h eventsSDL/InputSourceTouch.h eventsSDL/InputSourceGameController.h - eventsSDL/GameControllerShortcuts.h + eventsSDL/GameControllerConfig.h gui/CGuiHandler.h gui/CIntObject.h diff --git a/client/eventsSDL/GameControllerConfig.cpp b/client/eventsSDL/GameControllerConfig.cpp new file mode 100644 index 000000000..d323d776b --- /dev/null +++ b/client/eventsSDL/GameControllerConfig.cpp @@ -0,0 +1,212 @@ +/* +* GameControllerConfig.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include + +#include "StdInc.h" +#include "GameControllerConfig.h" +#include "../gui/CGuiHandler.h" +#include "../gui/ShortcutHandler.h" + + +GameControllerConfig::GameControllerConfig(): leftAxisType(AxisType::NONE), rightAxisType(AxisType::NONE) +{ + load(); +} + +void GameControllerConfig::load() +{ + const JsonNode config = JsonUtils::assembleFromFiles("config/shortcutsConfig"); + for(auto const & entry : config["joystick"].Struct()) + { + std::string configName = entry.first; + if(configName == "leftaxis") + leftAxisType = parseAxis(entry.first, entry.second); + else if (configName == "rightaxis") + rightAxisType = parseAxis(entry.first, entry.second); + else if (configName == "lefttrigger" || configName == "righttrigger") + parseTrigger(entry.first, entry.second); + else + parseButton(entry.first, entry.second); + } +} + +AxisType GameControllerConfig::parseAxis(const std::string & key, const JsonNode & value) +{ + if(!value.isString()) + { + logGlobal->error("The value of joystick config key %s should be a string!", key); + return AxisType::NONE; + } + + std::string featureName = value.String(); + if(featureName == "cursorMotion") + return AxisType::CURSOR_MOTION; + else if(featureName == "mapScroll") + return AxisType::MAP_SCROLL; + else if(featureName != "") + logGlobal->error("Unknown value %s of joystick config key %s!", featureName, key); + return AxisType::NONE; +} + +void GameControllerConfig::parseTrigger(const std::string & key, const JsonNode & value) +{ + std::vector operations = getOperations(key, value); + SDL_GameControllerAxis triggerAxis = key == "lefttrigger" ? + SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT; + std::vector shortcuts; + for(const auto & operation : operations) + { + if(operation == "mouseLeftClick") + { + leftClickTriggerSet.insert(triggerAxis); + } + else if(operation == "mouseRightClick") + { + rightClickTriggerSet.insert(triggerAxis); + } + else + { + EShortcut shortcut = GH.shortcuts().findShortcut(operation); + if(shortcut == EShortcut::NONE) + logGlobal->error("Shortcut %s in joystick config key %s is invalid.", operation, key); + else + shortcuts.push_back(shortcut); + } + } + + if(!shortcuts.empty()) + triggerShortcutsMap.emplace(triggerAxis, std::move(shortcuts)); +} + +void GameControllerConfig::parseButton(const std::string & key, const JsonNode & value) +{ + std::vector operations = getOperations(key, value); + SDL_GameControllerButton button = SDL_GameControllerGetButtonFromString(key.c_str()); + if(button == SDL_CONTROLLER_BUTTON_INVALID) + { + logGlobal->error("Joystick config key %s is invalid.", key); + return; + } + + std::vector shortcuts; + for(const auto & operation : operations) + { + if(operation == "mouseLeftClick") + { + leftClickButtonSet.insert(button); + } + else if(operation == "mouseRightClick") + { + rightClickButtonSet.insert(button); + } + else + { + EShortcut shortcut = GH.shortcuts().findShortcut(operation); + if(shortcut == EShortcut::NONE) + logGlobal->error("Shortcut %s in joystick config key %s is invalid.", operation, key); + else + shortcuts.push_back(shortcut); + } + } + + if(!shortcuts.empty()) + buttonShortcutsMap.emplace(button, std::move(shortcuts)); +} + +const AxisType & GameControllerConfig::getLeftAxisType() +{ + return leftAxisType; +} + +const AxisType & GameControllerConfig::getRightAxisType() +{ + return rightAxisType; +} + +std::vector GameControllerConfig::getOperations(const std::string & key, const JsonNode & value) +{ + std::vector operations; + if(value.isString()) + { + operations.push_back(value.String()); + } + else if(value.isVector()) + { + for(auto const & entryVector : value.Vector()) + { + if(!entryVector.isString()) + logGlobal->error("The vector of joystick config key %s can not contain non-string element.", key); + else + operations.push_back(entryVector.String()); + } + } + else + { + logGlobal->error("The value of joystick config key %s should be string or string vector.", key); + } + return operations; +} + +bool GameControllerConfig::isLeftClickButton(int buttonValue) +{ + SDL_GameControllerButton button = static_cast(buttonValue); + return leftClickButtonSet.find(button) != leftClickButtonSet.end(); +} + +bool GameControllerConfig::isRightClickButton(int buttonValue) +{ + SDL_GameControllerButton button = static_cast(buttonValue); + return rightClickButtonSet.find(button) != rightClickButtonSet.end(); +} + +bool GameControllerConfig::isShortcutsButton(int buttonValue) +{ + SDL_GameControllerButton button = static_cast(buttonValue); + return buttonShortcutsMap.find(button) != buttonShortcutsMap.end(); +} + +const std::vector & GameControllerConfig::getButtonShortcuts(int buttonValue) +{ + SDL_GameControllerButton button = static_cast(buttonValue); + auto it = buttonShortcutsMap.find(button); + if(it != buttonShortcutsMap.end()) + return it->second; + static std::vector emptyVec; + return emptyVec; +} + +bool GameControllerConfig::isLeftClickTrigger(int axisValue) +{ + SDL_GameControllerAxis axis = static_cast(axisValue); + return leftClickTriggerSet.find(axis) != leftClickTriggerSet.end(); +} + +bool GameControllerConfig::isRightClickTrigger(int axisValue) +{ + SDL_GameControllerAxis axis = static_cast(axisValue); + return rightClickTriggerSet.find(axis) != rightClickTriggerSet.end(); +} + +bool GameControllerConfig::isShortcutsTrigger(int axisValue) +{ + SDL_GameControllerAxis axis = static_cast(axisValue); + return triggerShortcutsMap.find(axis) != triggerShortcutsMap.end(); +} + +const std::vector & GameControllerConfig::getTriggerShortcuts(int axisValue) +{ + SDL_GameControllerAxis axis = static_cast(axisValue); + auto it = triggerShortcutsMap.find(axis); + if(it != triggerShortcutsMap.end()) + return it->second; + static std::vector emptyVec; + return emptyVec; +} + diff --git a/client/eventsSDL/GameControllerConfig.h b/client/eventsSDL/GameControllerConfig.h new file mode 100644 index 000000000..13fdc1637 --- /dev/null +++ b/client/eventsSDL/GameControllerConfig.h @@ -0,0 +1,59 @@ +/* +* GameControllerConfig.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include + +#include "../gui/Shortcut.h" +#include "../../lib/json/JsonUtils.h" + +enum AxisType +{ + CURSOR_MOTION, + MAP_SCROLL, + NONE +}; + +class GameControllerConfig { + using ButtonShortcutsMap = std::map >; + using TriggerShortcutsMap = std::map >; + ButtonShortcutsMap buttonShortcutsMap; + TriggerShortcutsMap triggerShortcutsMap; + std::set leftClickButtonSet; + std::set rightClickButtonSet; + std::set leftClickTriggerSet; + std::set rightClickTriggerSet; + AxisType leftAxisType; + AxisType rightAxisType; + + void load(); + std::vector getOperations(const std::string & key, const JsonNode & value); + AxisType parseAxis(const std::string & key, const JsonNode & value); + void parseTrigger(const std::string & key, const JsonNode & value); + void parseButton(const std::string & key, const JsonNode & value); + +public: + GameControllerConfig(); + ~GameControllerConfig() = default; + + const AxisType & getLeftAxisType(); + const AxisType & getRightAxisType(); + + bool isLeftClickButton(int buttonValue); + bool isRightClickButton(int buttonValue); + bool isShortcutsButton(int buttonValue); + const std::vector & getButtonShortcuts(int buttonValue); + + bool isLeftClickTrigger(int axisValue); + bool isRightClickTrigger(int axisValue); + bool isShortcutsTrigger(int axisValue); + const std::vector & getTriggerShortcuts(int axisValue); +}; diff --git a/client/eventsSDL/GameControllerShortcuts.cpp b/client/eventsSDL/GameControllerShortcuts.cpp deleted file mode 100644 index 408e3f4c8..000000000 --- a/client/eventsSDL/GameControllerShortcuts.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* -* GameControllerShortcuts.cpp, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#include - -#include "StdInc.h" -#include "GameControllerShortcuts.h" -#include "../gui/ShortcutHandler.h" - -const ButtonShortcutsMap & getButtonShortcutsMap() { - static const ButtonShortcutsMap buttonShortcutsMap = - { - // SDL_CONTROLLER_BUTTON_A for mouse left click - {SDL_CONTROLLER_BUTTON_B, {EShortcut::GLOBAL_CANCEL, EShortcut::GLOBAL_RETURN, - EShortcut::ADVENTURE_EXIT_WORLD_VIEW}}, - {SDL_CONTROLLER_BUTTON_X, {EShortcut::GLOBAL_ACCEPT, EShortcut::GLOBAL_RETURN, EShortcut::LOBBY_BEGIN_GAME, - EShortcut::LOBBY_LOAD_GAME, EShortcut::LOBBY_SAVE_GAME, - EShortcut::ADVENTURE_VIEW_SELECTED, EShortcut::ADVENTURE_EXIT_WORLD_VIEW, - EShortcut::BATTLE_TACTICS_END}}, - // SDL_CONTROLLER_BUTTON_Y for mouse right click - {SDL_CONTROLLER_BUTTON_LEFTSHOULDER, {EShortcut::ADVENTURE_NEXT_HERO, EShortcut::BATTLE_DEFEND}}, - {SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, {EShortcut::ADVENTURE_NEXT_TOWN, EShortcut::BATTLE_WAIT}}, - {SDL_CONTROLLER_BUTTON_BACK, {EShortcut::GAME_END_TURN, EShortcut::BATTLE_END_WITH_AUTOCOMBAT}}, - {SDL_CONTROLLER_BUTTON_START, {EShortcut::GLOBAL_OPTIONS, EShortcut::ADVENTURE_GAME_OPTIONS}}, - {SDL_CONTROLLER_BUTTON_DPAD_UP, {EShortcut::MOVE_UP, EShortcut::ADVENTURE_VIEW_WORLD, - EShortcut::RECRUITMENT_UPGRADE, - EShortcut::RECRUITMENT_UPGRADE_ALL, - EShortcut::BATTLE_CONSOLE_UP, EShortcut::RECRUITMENT_MAX}}, - {SDL_CONTROLLER_BUTTON_DPAD_DOWN, {EShortcut::MOVE_DOWN, EShortcut::ADVENTURE_KINGDOM_OVERVIEW, - EShortcut::BATTLE_CONSOLE_DOWN, EShortcut::RECRUITMENT_MIN}}, - {SDL_CONTROLLER_BUTTON_DPAD_LEFT, {EShortcut::MOVE_LEFT, EShortcut::ADVENTURE_VIEW_SCENARIO}}, - {SDL_CONTROLLER_BUTTON_DPAD_RIGHT, {EShortcut::MOVE_RIGHT, EShortcut::ADVENTURE_THIEVES_GUILD}}, - {SDL_CONTROLLER_BUTTON_LEFTSTICK, {EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL, - EShortcut::BATTLE_TOGGLE_HEROES_STATS}}, - {SDL_CONTROLLER_BUTTON_RIGHTSTICK, {EShortcut::ADVENTURE_TOGGLE_GRID, EShortcut::BATTLE_TOGGLE_QUEUE}} - }; - return buttonShortcutsMap; -} - -const TriggerShortcutsMap & getTriggerShortcutsMap() -{ - static const TriggerShortcutsMap triggerShortcutsMap = { - {SDL_CONTROLLER_AXIS_TRIGGERLEFT, {EShortcut::ADVENTURE_VISIT_OBJECT, EShortcut::BATTLE_TACTICS_NEXT, - EShortcut::BATTLE_USE_CREATURE_SPELL}}, - {SDL_CONTROLLER_AXIS_TRIGGERRIGHT, {EShortcut::ADVENTURE_CAST_SPELL, EShortcut::BATTLE_CAST_SPELL}} - }; - return triggerShortcutsMap; -} \ No newline at end of file diff --git a/client/eventsSDL/GameControllerShortcuts.h b/client/eventsSDL/GameControllerShortcuts.h deleted file mode 100644 index 02baeabd3..000000000 --- a/client/eventsSDL/GameControllerShortcuts.h +++ /dev/null @@ -1,19 +0,0 @@ -/* -* GameControllerShortcuts.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ - -#pragma once - -#include "../gui/Shortcut.h" - -using ButtonShortcutsMap = std::map >; -using TriggerShortcutsMap = std::map >; - -const ButtonShortcutsMap & getButtonShortcutsMap(); -const TriggerShortcutsMap & getTriggerShortcutsMap(); \ No newline at end of file diff --git a/client/eventsSDL/InputSourceGameController.cpp b/client/eventsSDL/InputSourceGameController.cpp index d1d26bea4..330848ffc 100644 --- a/client/eventsSDL/InputSourceGameController.cpp +++ b/client/eventsSDL/InputSourceGameController.cpp @@ -10,7 +10,6 @@ #include "StdInc.h" #include "InputSourceGameController.h" -#include "GameControllerShortcuts.h" #include "InputHandler.h" #include "../CGameInfo.h" @@ -137,20 +136,57 @@ void InputSourceGameController::dispatchTriggerShortcuts(const std::vector= TRIGGER_PRESS_THRESHOLD) + GH.events().dispatchMouseLeftButtonPressed(position, 0); + else + GH.events().dispatchMouseLeftButtonReleased(position, 0); +} + +void InputSourceGameController::dispatchTriggerRightClick(int axisValue) +{ + const Point & position = GH.input().getCursorPosition(); + if(axisValue >= TRIGGER_PRESS_THRESHOLD) + GH.events().dispatchShowPopup(position, 0); + else + GH.events().dispatchClosePopup(position); +} + void InputSourceGameController::handleEventAxisMotion(const SDL_ControllerAxisEvent & axis) { - const auto & triggerShortcutsMap = getTriggerShortcutsMap(); if(axis.axis == SDL_CONTROLLER_AXIS_LEFTX) { - axisValueX = getRealAxisValue(axis.value); + if(config.getLeftAxisType() == AxisType::CURSOR_MOTION) + axisValueX = getRealAxisValue(axis.value); } else if(axis.axis == SDL_CONTROLLER_AXIS_LEFTY) { - axisValueY = getRealAxisValue(axis.value); + if(config.getLeftAxisType() == AxisType::CURSOR_MOTION) + axisValueY = getRealAxisValue(axis.value); } - else if(triggerShortcutsMap.find(axis.axis) != triggerShortcutsMap.end()) + if(axis.axis == SDL_CONTROLLER_AXIS_RIGHTX) { - const auto & shortcutsVector = triggerShortcutsMap.find(axis.axis)->second; + if(config.getRightAxisType() == AxisType::CURSOR_MOTION) + axisValueX = getRealAxisValue(axis.value); + } + else if(axis.axis == SDL_CONTROLLER_AXIS_RIGHTY) + { + if(config.getRightAxisType() == AxisType::CURSOR_MOTION) + axisValueY = getRealAxisValue(axis.value); + } + else if(config.isLeftClickTrigger(axis.axis)) + { + dispatchTriggerLeftClick(axis.value); + } + else if(config.isRightClickTrigger(axis.axis)) + { + dispatchTriggerRightClick(axis.value); + } + else if(config.isShortcutsTrigger(axis.axis)) + { + const auto & shortcutsVector = config.getTriggerShortcuts(axis.axis); dispatchTriggerShortcuts(shortcutsVector, axis.value); } } @@ -158,20 +194,20 @@ void InputSourceGameController::handleEventAxisMotion(const SDL_ControllerAxisEv void InputSourceGameController::handleEventButtonDown(const SDL_ControllerButtonEvent & button) { const Point & position = GH.input().getCursorPosition(); - const auto & buttonShortcutsMap = getButtonShortcutsMap(); - // TODO: define keys by user - if(button.button == SDL_CONTROLLER_BUTTON_A) + if(config.isLeftClickButton(button.button)) { GH.events().dispatchMouseLeftButtonPressed(position, 0); } - else if(button.button == SDL_CONTROLLER_BUTTON_Y) + + if(config.isRightClickButton(button.button)) { GH.events().dispatchShowPopup(position, 0); } - else if(buttonShortcutsMap.find(button.button) != buttonShortcutsMap.end()) + + if(config.isShortcutsButton(button.button)) { - const auto & shortcutsVector = buttonShortcutsMap.find(button.button)->second; + const auto & shortcutsVector = config.getButtonShortcuts(button.button); GH.events().dispatchShortcutPressed(shortcutsVector); } } @@ -179,18 +215,18 @@ void InputSourceGameController::handleEventButtonDown(const SDL_ControllerButton void InputSourceGameController::handleEventButtonUp(const SDL_ControllerButtonEvent & button) { const Point & position = GH.input().getCursorPosition(); - const auto & buttonShortcutsMap = getButtonShortcutsMap(); - if(button.button == SDL_CONTROLLER_BUTTON_A) + + if(config.isLeftClickButton(button.button)) { GH.events().dispatchMouseLeftButtonReleased(position, 0); } - else if(button.button == SDL_CONTROLLER_BUTTON_Y) + if(config.isRightClickButton(button.button)) { GH.events().dispatchClosePopup(position); } - else if(buttonShortcutsMap.find(button.button) != buttonShortcutsMap.end()) + if(config.isShortcutsButton(button.button)) { - const auto & shortcutsVector = buttonShortcutsMap.find(button.button)->second; + const auto & shortcutsVector = config.getButtonShortcuts(button.button); GH.events().dispatchShortcutReleased(shortcutsVector); } } diff --git a/client/eventsSDL/InputSourceGameController.h b/client/eventsSDL/InputSourceGameController.h index c33f826f3..be6ce05e5 100644 --- a/client/eventsSDL/InputSourceGameController.h +++ b/client/eventsSDL/InputSourceGameController.h @@ -12,6 +12,7 @@ #include +#include "GameControllerConfig.h" #include "../gui/Shortcut.h" @@ -34,11 +35,14 @@ class InputSourceGameController int axisValueY; float planDisX; float planDisY; + GameControllerConfig config; void openGameController(int index); int getJoystickIndex(SDL_GameController * controller); int getRealAxisValue(int value); void dispatchTriggerShortcuts(const std::vector & shortcutsVector, int axisValue); + void dispatchTriggerLeftClick(int axisValue); + void dispatchTriggerRightClick(int axisValue); void doCursorMove(int deltaX, int deltaY); int getMoveDis(float planDis); diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index 680a9ef34..f4a24aa49 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -152,6 +152,7 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const {"adventureZoomIn", EShortcut::ADVENTURE_ZOOM_IN }, {"adventureZoomOut", EShortcut::ADVENTURE_ZOOM_OUT }, {"adventureZoomReset", EShortcut::ADVENTURE_ZOOM_RESET }, + {"battleToggleHeroesStats", EShortcut::BATTLE_TOGGLE_HEROES_STATS}, {"battleToggleQueue", EShortcut::BATTLE_TOGGLE_QUEUE }, {"battleUseCreatureSpell", EShortcut::BATTLE_USE_CREATURE_SPELL }, {"battleSurrender", EShortcut::BATTLE_SURRENDER }, diff --git a/config/shortcutsConfig.json b/config/shortcutsConfig.json index c5d45d3dc..021512da0 100644 --- a/config/shortcutsConfig.json +++ b/config/shortcutsConfig.json @@ -125,5 +125,27 @@ "heroToggleTactics": "B", "spellbookTabAdventure": "A", "spellbookTabCombat": "C" + }, + "joystick": { + "leftaxis": "cursorMotion", + "rightaxis": "mapScroll", + "a": ["globalAccept", "globalReturn", "lobbyBeginGame", "lobbyLoadGame", "lobbySaveGame", + "adventureViewSelected", "adventureExitWorldView", "battleTacticsEnd"], + "b": ["globalCancel", "globalReturn", "adventureExitWorldView"], + "x": "mouseLeftClick", + "y": "mouseRightClick", + "leftshoulder": ["adventureNextHero", "battleDefend"], + "rightshoulder": ["adventureNextTown", "battleWait"], + "lefttrigger": ["adventureVisitObject", "battleTacticsNext", "battleUseCreatureSpell"], + "righttrigger": ["adventureCastSpell", "battleCastSpell"], + "back": ["gameEndTurn", "battleAutocombatEnd"], + "start": ["globalOptions", "adventureGameOptions"], + "dpup": ["moveUp", "adventureViewWorld", "recruitmentUpgrade", "recruitmentUpgradeAll", + "battleConsoleUp", "recruitmentMax"], + "dpdown": ["moveDown", "adventureKingdomOverview", "battleConsoleDown","recruitmentMin"], + "dpleft": ["moveLeft", "adventureViewScenario"], + "dpright": ["moveRight", "adventureThievesGuild"], + "leftstick" : ["adventureToggleMapLevel", "battleToggleHeroesStats"], + "rightstick": ["adventureToggleGrid", "battleToggleQueue"] } }