1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-01-26 03:52:01 +02:00

make joystick configurable

This commit is contained in:
kdmcser 2024-04-25 22:23:17 +08:00
parent 60c4ddb515
commit 1eea8398b4
9 changed files with 353 additions and 92 deletions

View File

@ -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

View File

@ -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 <SDL.h>
#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<std::string> operations = getOperations(key, value);
SDL_GameControllerAxis triggerAxis = key == "lefttrigger" ?
SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
std::vector<EShortcut> 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<std::string> 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<EShortcut> 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<std::string> GameControllerConfig::getOperations(const std::string & key, const JsonNode & value)
{
std::vector<std::string> 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<SDL_GameControllerButton>(buttonValue);
return leftClickButtonSet.find(button) != leftClickButtonSet.end();
}
bool GameControllerConfig::isRightClickButton(int buttonValue)
{
SDL_GameControllerButton button = static_cast<SDL_GameControllerButton>(buttonValue);
return rightClickButtonSet.find(button) != rightClickButtonSet.end();
}
bool GameControllerConfig::isShortcutsButton(int buttonValue)
{
SDL_GameControllerButton button = static_cast<SDL_GameControllerButton>(buttonValue);
return buttonShortcutsMap.find(button) != buttonShortcutsMap.end();
}
const std::vector<EShortcut> & GameControllerConfig::getButtonShortcuts(int buttonValue)
{
SDL_GameControllerButton button = static_cast<SDL_GameControllerButton>(buttonValue);
auto it = buttonShortcutsMap.find(button);
if(it != buttonShortcutsMap.end())
return it->second;
static std::vector<EShortcut> emptyVec;
return emptyVec;
}
bool GameControllerConfig::isLeftClickTrigger(int axisValue)
{
SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(axisValue);
return leftClickTriggerSet.find(axis) != leftClickTriggerSet.end();
}
bool GameControllerConfig::isRightClickTrigger(int axisValue)
{
SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(axisValue);
return rightClickTriggerSet.find(axis) != rightClickTriggerSet.end();
}
bool GameControllerConfig::isShortcutsTrigger(int axisValue)
{
SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(axisValue);
return triggerShortcutsMap.find(axis) != triggerShortcutsMap.end();
}
const std::vector<EShortcut> & GameControllerConfig::getTriggerShortcuts(int axisValue)
{
SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(axisValue);
auto it = triggerShortcutsMap.find(axis);
if(it != triggerShortcutsMap.end())
return it->second;
static std::vector<EShortcut> emptyVec;
return emptyVec;
}

View File

@ -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 <SDL.h>
#include "../gui/Shortcut.h"
#include "../../lib/json/JsonUtils.h"
enum AxisType
{
CURSOR_MOTION,
MAP_SCROLL,
NONE
};
class GameControllerConfig {
using ButtonShortcutsMap = std::map<SDL_GameControllerButton, std::vector<EShortcut> >;
using TriggerShortcutsMap = std::map<SDL_GameControllerAxis, std::vector<EShortcut> >;
ButtonShortcutsMap buttonShortcutsMap;
TriggerShortcutsMap triggerShortcutsMap;
std::set<SDL_GameControllerButton> leftClickButtonSet;
std::set<SDL_GameControllerButton> rightClickButtonSet;
std::set<SDL_GameControllerAxis> leftClickTriggerSet;
std::set<SDL_GameControllerAxis> rightClickTriggerSet;
AxisType leftAxisType;
AxisType rightAxisType;
void load();
std::vector<std::string> 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<EShortcut> & getButtonShortcuts(int buttonValue);
bool isLeftClickTrigger(int axisValue);
bool isRightClickTrigger(int axisValue);
bool isShortcutsTrigger(int axisValue);
const std::vector<EShortcut> & getTriggerShortcuts(int axisValue);
};

View File

@ -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 <SDL.h>
#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;
}

View File

@ -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<int, std::vector<EShortcut> >;
using TriggerShortcutsMap = std::map<int, std::vector<EShortcut> >;
const ButtonShortcutsMap & getButtonShortcutsMap();
const TriggerShortcutsMap & getTriggerShortcutsMap();

View File

@ -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<EShor
GH.events().dispatchShortcutReleased(shortcutsVector);
}
void InputSourceGameController::dispatchTriggerLeftClick(int axisValue)
{
const Point & position = GH.input().getCursorPosition();
if(axisValue >= 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);
}
}

View File

@ -12,6 +12,7 @@
#include <SDL.h>
#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<EShortcut> & shortcutsVector, int axisValue);
void dispatchTriggerLeftClick(int axisValue);
void dispatchTriggerRightClick(int axisValue);
void doCursorMove(int deltaX, int deltaY);
int getMoveDis(float planDis);

View File

@ -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 },

View File

@ -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"]
}
}