💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

666 lines
22 KiB

  1. /*
  2. * Copyright (C) 2021 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper source code.
  5. *
  6. * Antkeeper source code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper source code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "game/controls.hpp"
  20. #include "resources/resource-manager.hpp"
  21. #include "resources/json.hpp"
  22. #include "application.hpp"
  23. #include "game/component/transform.hpp"
  24. #include "game/component/constraint/constraint.hpp"
  25. #include "game/component/constraint-stack.hpp"
  26. #include <fstream>
  27. namespace game {
  28. std::filesystem::path gamepad_calibration_path(const game::context& ctx, const input::gamepad& gamepad)
  29. {
  30. return std::filesystem::path("gamepad-" + gamepad.get_guid() + ".json");
  31. }
  32. json default_control_profile()
  33. {
  34. return json();
  35. }
  36. json default_gamepad_calibration()
  37. {
  38. const float activation_min = 0.15f;
  39. const float activation_max = 0.98f;
  40. const bool deadzone_cross = false;
  41. const float deadzone_roundness = 1.0f;
  42. const std::string response_curve = "linear";
  43. json calibration;
  44. calibration["leftx_activation"] = {activation_min, activation_max};
  45. calibration["lefty_activation"] = {activation_min, activation_max};
  46. calibration["rightx_activation"] = {activation_min, activation_max};
  47. calibration["righty_activation"] = {activation_min, activation_max};
  48. calibration["lefttrigger_activation"] = {activation_min, activation_max};
  49. calibration["righttrigger_activation"] = {activation_min, activation_max};
  50. calibration["leftx_response_curve"] = response_curve;
  51. calibration["lefty_response_curve"] = response_curve;
  52. calibration["rightx_response_curve"] = response_curve;
  53. calibration["righty_response_curve"] = response_curve;
  54. calibration["lefttrigger_response_curve"] = response_curve;
  55. calibration["righttrigger_response_curve"] = response_curve;
  56. calibration["left_deadzone_cross"] = deadzone_cross;
  57. calibration["right_deadzone_cross"] = deadzone_cross;
  58. calibration["left_deadzone_roundness"] = deadzone_roundness;
  59. calibration["right_deadzone_roundness"] = deadzone_roundness;
  60. return calibration;
  61. }
  62. json* load_gamepad_calibration(game::context& ctx, const input::gamepad& gamepad)
  63. {
  64. // Determine path to gamepad calibration file
  65. std::filesystem::path path = gamepad_calibration_path(ctx, gamepad);
  66. // Load gamepad calibration file
  67. json* calibration = ctx.resource_manager->load<json>(path.string());
  68. return calibration;
  69. }
  70. bool save_gamepad_calibration(const game::context& ctx, const input::gamepad& gamepad, const json& calibration)
  71. {
  72. // Determine absolute path to gamepad calibration file
  73. std::filesystem::path path = ctx.controls_path / gamepad_calibration_path(ctx, gamepad);
  74. // Open calibration file
  75. std::ofstream stream;
  76. stream.open(path);
  77. if (!stream)
  78. return false;
  79. // Write calibration to file
  80. stream << calibration.dump(1, '\t');
  81. if (stream.bad())
  82. {
  83. stream.close();
  84. return false;
  85. }
  86. // Close calibration file
  87. stream.close();
  88. return true;
  89. }
  90. void apply_control_profile(game::context& ctx, const json& profile)
  91. {
  92. // Map gamepad buttons to strings
  93. const std::unordered_map<std::string, input::gamepad_button> gamepad_button_map =
  94. {
  95. {"a", input::gamepad_button::a},
  96. {"b", input::gamepad_button::b},
  97. {"x", input::gamepad_button::x},
  98. {"y", input::gamepad_button::y},
  99. {"back", input::gamepad_button::back},
  100. {"guide", input::gamepad_button::guide},
  101. {"start", input::gamepad_button::start},
  102. {"leftstick", input::gamepad_button::left_stick},
  103. {"rightstick", input::gamepad_button::right_stick},
  104. {"leftshoulder", input::gamepad_button::left_shoulder},
  105. {"rightshoulder", input::gamepad_button::right_shoulder},
  106. {"dpup", input::gamepad_button::dpad_up},
  107. {"dpdown", input::gamepad_button::dpad_down},
  108. {"dpleft", input::gamepad_button::dpad_left},
  109. {"dpright", input::gamepad_button::dpad_right}
  110. };
  111. // Map gamepad axes to strings
  112. const std::unordered_map<std::string, input::gamepad_axis> gamepad_axis_map =
  113. {
  114. {"leftx", input::gamepad_axis::left_x},
  115. {"lefty", input::gamepad_axis::left_y},
  116. {"rightx", input::gamepad_axis::right_x},
  117. {"righty", input::gamepad_axis::right_y},
  118. {"lefttrigger", input::gamepad_axis::left_trigger},
  119. {"righttrigger", input::gamepad_axis::right_trigger}
  120. };
  121. // Remove all existing input mappings
  122. for (auto control = ctx.controls.begin(); control != ctx.controls.end(); ++control)
  123. {
  124. ctx.input_event_router->remove_mappings(control->second);
  125. }
  126. // Get keyboard and mouse devices
  127. input::keyboard* keyboard = ctx.app->get_keyboard();
  128. input::mouse* mouse = ctx.app->get_mouse();
  129. // Find profile gamepad device
  130. input::gamepad* gamepad = nullptr;
  131. auto gamepad_element = profile.find("gamepad");
  132. if (gamepad_element != profile.end())
  133. {
  134. // Get gamepad GUID
  135. const std::string gamepad_guid = gamepad_element->get<std::string>();
  136. // Find gamepad with matching GUID
  137. for (input::gamepad* device: ctx.app->get_gamepads())
  138. {
  139. if (device->get_guid() == gamepad_guid)
  140. {
  141. gamepad = device;
  142. break;
  143. }
  144. }
  145. }
  146. // Find controls element
  147. auto controls_element = profile.find("controls");
  148. if (controls_element != profile.end())
  149. {
  150. // For each control in the profile
  151. for (auto control_element = controls_element->cbegin(); control_element != controls_element->cend(); ++control_element)
  152. {
  153. // Get the control name
  154. std::string control_name = control_element.key();
  155. // Find or create control
  156. input::control* control;
  157. if (ctx.controls.count(control_name))
  158. {
  159. control = ctx.controls[control_name];
  160. }
  161. else
  162. {
  163. control = new input::control();
  164. ctx.controls[control_name] = control;
  165. }
  166. // For each mapping in the control
  167. for (auto mapping_element = control_element.value().cbegin(); mapping_element != control_element.value().cend(); ++mapping_element)
  168. {
  169. if (!mapping_element->contains("device"))
  170. {
  171. ctx.logger->warning("Control \"" + control_name + "\" not mapped to a device");
  172. continue;
  173. }
  174. // Get the mapping device
  175. const std::string device = (*mapping_element)["device"];
  176. if (device == "keyboard")
  177. {
  178. // Parse key name
  179. if (!mapping_element->contains("key"))
  180. {
  181. ctx.logger->warning("Control \"" + control_name + "\" has invalid keyboard mapping");
  182. continue;
  183. }
  184. std::string key = (*mapping_element)["key"].get<std::string>();
  185. // Get scancode from key name
  186. input::scancode scancode = keyboard->get_scancode_from_name(key.c_str());
  187. if (scancode == input::scancode::unknown)
  188. {
  189. ctx.logger->warning("Control \"" + control_name + "\" mapped to unknown keyboard key \"" + key + "\"");
  190. continue;
  191. }
  192. // Map control to keyboard key
  193. ctx.input_event_router->add_mapping(input::key_mapping(control, keyboard, scancode));
  194. ctx.logger->log("Mapped control \"" + control_name + "\" to keyboard key \"" + key + "\"");
  195. }
  196. else if (device == "mouse")
  197. {
  198. if (mapping_element->contains("button"))
  199. {
  200. // Parse mouse button index
  201. int button = (*mapping_element)["button"].get<int>();
  202. // Map control to mouse button
  203. ctx.input_event_router->add_mapping(input::mouse_button_mapping(control, mouse, button));
  204. ctx.logger->log("Mapped control \"" + control_name + "\" to mouse button " + std::to_string(button));
  205. }
  206. else if (mapping_element->contains("wheel"))
  207. {
  208. // Parse mouse wheel axis
  209. std::string wheel = (*mapping_element)["wheel"].get<std::string>();
  210. input::mouse_wheel_axis axis;
  211. if (wheel == "x+")
  212. axis = input::mouse_wheel_axis::positive_x;
  213. else if (wheel == "x-")
  214. axis = input::mouse_wheel_axis::negative_x;
  215. else if (wheel == "y+")
  216. axis = input::mouse_wheel_axis::positive_y;
  217. else if (wheel == "y-")
  218. axis = input::mouse_wheel_axis::negative_y;
  219. else
  220. {
  221. ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid mouse wheel axis \"" + wheel + "\"");
  222. continue;
  223. }
  224. // Map control to mouse wheel axis
  225. ctx.input_event_router->add_mapping(input::mouse_wheel_mapping(control, mouse, axis));
  226. ctx.logger->log("Mapped control \"" + control_name + "\" to mouse wheel axis " + wheel);
  227. }
  228. else if (mapping_element->contains("motion"))
  229. {
  230. std::string motion = (*mapping_element)["motion"].get<std::string>();
  231. input::mouse_motion_axis axis;
  232. if (motion == "x+")
  233. axis = input::mouse_motion_axis::positive_x;
  234. else if (motion == "x-")
  235. axis = input::mouse_motion_axis::negative_x;
  236. else if (motion == "y+")
  237. axis = input::mouse_motion_axis::positive_y;
  238. else if (motion == "y-")
  239. axis = input::mouse_motion_axis::negative_y;
  240. else
  241. {
  242. ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid mouse motion axis \"" + motion + "\"");
  243. continue;
  244. }
  245. // Map control to mouse motion axis
  246. ctx.input_event_router->add_mapping(input::mouse_motion_mapping(control, mouse, axis));
  247. ctx.logger->log("Mapped control \"" + control_name + "\" to mouse motion axis " + motion);
  248. }
  249. else
  250. {
  251. ctx.logger->warning("Control \"" + control_name + "\" has invalid mouse mapping");
  252. continue;
  253. }
  254. }
  255. else if (device == "gamepad")
  256. {
  257. if (mapping_element->contains("button"))
  258. {
  259. // Parse gamepad button
  260. std::string button = (*mapping_element)["button"].get<std::string>();
  261. auto button_it = gamepad_button_map.find(button);
  262. if (button_it == gamepad_button_map.end())
  263. {
  264. ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid gamepad button \"" + button + "\"");
  265. continue;
  266. }
  267. // Map control to gamepad button
  268. ctx.input_event_router->add_mapping(input::gamepad_button_mapping(control, gamepad, button_it->second));
  269. ctx.logger->log("Mapped control \"" + control_name + "\" to gamepad button " + button);
  270. }
  271. else if (mapping_element->contains("axis"))
  272. {
  273. std::string axis = (*mapping_element)["axis"].get<std::string>();
  274. // Parse gamepad axis name
  275. const std::string axis_name = axis.substr(0, axis.length() - 1);
  276. auto axis_it = gamepad_axis_map.find(axis_name);
  277. if (axis_it == gamepad_axis_map.end())
  278. {
  279. ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid gamepad axis \"" + axis_name + "\"");
  280. continue;
  281. }
  282. // Parse gamepad axis sign
  283. const char axis_sign = axis.back();
  284. if (axis_sign != '-' && axis_sign != '+')
  285. {
  286. ctx.logger->warning("Control \"" + control_name + "\" is mapped to gamepad axis with invalid sign \"" + axis_sign + "\"");
  287. continue;
  288. }
  289. bool axis_negative = (axis_sign == '-');
  290. // Map control to gamepad axis
  291. ctx.input_event_router->add_mapping(input::gamepad_axis_mapping(control, gamepad, axis_it->second, axis_negative));
  292. ctx.logger->log("Mapped control \"" + control_name + "\" to gamepad axis " + axis);
  293. }
  294. else
  295. {
  296. ctx.logger->log("Control \"" + control_name + "\" has invalid gamepad mapping");
  297. continue;
  298. }
  299. }
  300. else
  301. {
  302. ctx.logger->warning("Control \"" + control_name + "\" bound to unknown device \"" + device + "\"");
  303. }
  304. }
  305. }
  306. }
  307. }
  308. void save_control_profile(game::context& ctx)
  309. {
  310. std::filesystem::path path;
  311. if (ctx.config->contains("control_profile"))
  312. path = ctx.config_path / "controls" / (*ctx.config)["control_profile"].get<std::string>();
  313. ctx.logger->push_task("Saving control profile to \"" + path.string() + "\"");
  314. try
  315. {
  316. json control_profile;
  317. // Add controls element
  318. auto& controls_element = control_profile["controls"];
  319. controls_element = json::object();
  320. for (auto controls_it = ctx.controls.begin(); controls_it != ctx.controls.end(); ++controls_it)
  321. {
  322. const std::string& control_name = controls_it->first;
  323. input::control* control = controls_it->second;
  324. // Add control element
  325. auto& control_element = controls_element[control_name];
  326. control_element = json::array();
  327. // Get control mappings
  328. auto mappings = ctx.input_event_router->get_mappings(control);
  329. // Skip unmapped controls
  330. if (!mappings)
  331. continue;
  332. // Add control mapping elements
  333. for (input::mapping* mapping: *mappings)
  334. {
  335. json mapping_element;
  336. switch (mapping->get_type())
  337. {
  338. case input::mapping_type::key:
  339. {
  340. const input::key_mapping* key_mapping = static_cast<const input::key_mapping*>(mapping);
  341. mapping_element["device"] = "keyboard";
  342. mapping_element["key"] = input::keyboard::get_scancode_name(key_mapping->scancode);
  343. break;
  344. }
  345. case input::mapping_type::mouse_wheel:
  346. {
  347. const input::mouse_wheel_mapping* wheel_mapping = static_cast<const input::mouse_wheel_mapping*>(mapping);
  348. mapping_element["device"] = "mouse";
  349. switch (wheel_mapping->axis)
  350. {
  351. case input::mouse_wheel_axis::negative_x:
  352. mapping_element["wheel"] = "x-";
  353. break;
  354. case input::mouse_wheel_axis::positive_x:
  355. mapping_element["wheel"] = "x+";
  356. break;
  357. case input::mouse_wheel_axis::negative_y:
  358. mapping_element["wheel"] = "y-";
  359. break;
  360. case input::mouse_wheel_axis::positive_y:
  361. mapping_element["wheel"] = "y+";
  362. break;
  363. default:
  364. break;
  365. }
  366. break;
  367. }
  368. case input::mapping_type::mouse_motion:
  369. {
  370. const input::mouse_motion_mapping* motion_mapping = static_cast<const input::mouse_motion_mapping*>(mapping);
  371. mapping_element["device"] = "mouse";
  372. switch (motion_mapping->axis)
  373. {
  374. case input::mouse_motion_axis::negative_x:
  375. mapping_element["motion"] = "x-";
  376. break;
  377. case input::mouse_motion_axis::positive_x:
  378. mapping_element["motion"] = "x+";
  379. break;
  380. case input::mouse_motion_axis::negative_y:
  381. mapping_element["motion"] = "y-";
  382. break;
  383. case input::mouse_motion_axis::positive_y:
  384. mapping_element["motion"] = "y+";
  385. break;
  386. default:
  387. break;
  388. }
  389. break;
  390. }
  391. case input::mapping_type::mouse_button:
  392. {
  393. const input::mouse_button_mapping* button_mapping = static_cast<const input::mouse_button_mapping*>(mapping);
  394. mapping_element["device"] = "mouse";
  395. mapping_element["button"] = button_mapping->button;
  396. break;
  397. }
  398. case input::mapping_type::gamepad_axis:
  399. {
  400. const input::gamepad_axis_mapping* axis_mapping = static_cast<const input::gamepad_axis_mapping*>(mapping);
  401. mapping_element["device"] = "gamepad";
  402. switch (axis_mapping->axis)
  403. {
  404. case input::gamepad_axis::left_x:
  405. if (axis_mapping->negative)
  406. mapping_element["axis"] = "leftx-";
  407. else
  408. mapping_element["axis"] = "leftx+";
  409. break;
  410. case input::gamepad_axis::left_y:
  411. if (axis_mapping->negative)
  412. mapping_element["axis"] = "lefty-";
  413. else
  414. mapping_element["axis"] = "lefty+";
  415. break;
  416. case input::gamepad_axis::right_x:
  417. if (axis_mapping->negative)
  418. mapping_element["axis"] = "rightx-";
  419. else
  420. mapping_element["axis"] = "rightx+";
  421. break;
  422. case input::gamepad_axis::right_y:
  423. if (axis_mapping->negative)
  424. mapping_element["axis"] = "righty-";
  425. else
  426. mapping_element["axis"] = "righty+";
  427. break;
  428. case input::gamepad_axis::left_trigger:
  429. mapping_element["axis"] = "lefttrigger+";
  430. break;
  431. case input::gamepad_axis::right_trigger:
  432. mapping_element["axis"] = "righttrigger+";
  433. break;
  434. default:
  435. break;
  436. }
  437. break;
  438. }
  439. case input::mapping_type::gamepad_button:
  440. {
  441. const input::gamepad_button_mapping* button_mapping = static_cast<const input::gamepad_button_mapping*>(mapping);
  442. mapping_element["device"] = "gamepad";
  443. switch (button_mapping->button)
  444. {
  445. case input::gamepad_button::a:
  446. mapping_element["button"] = "a";
  447. break;
  448. case input::gamepad_button::b:
  449. mapping_element["button"] = "b";
  450. break;
  451. case input::gamepad_button::x:
  452. mapping_element["button"] = "x";
  453. break;
  454. case input::gamepad_button::y:
  455. mapping_element["button"] = "y";
  456. break;
  457. case input::gamepad_button::back:
  458. mapping_element["button"] = "back";
  459. break;
  460. case input::gamepad_button::guide:
  461. mapping_element["button"] = "guide";
  462. break;
  463. case input::gamepad_button::start:
  464. mapping_element["button"] = "start";
  465. break;
  466. case input::gamepad_button::left_stick:
  467. mapping_element["button"] = "leftstick";
  468. break;
  469. case input::gamepad_button::right_stick:
  470. mapping_element["button"] = "rightstick";
  471. break;
  472. case input::gamepad_button::left_shoulder:
  473. mapping_element["button"] = "leftshoulder";
  474. break;
  475. case input::gamepad_button::right_shoulder:
  476. mapping_element["button"] = "rightshoulder";
  477. break;
  478. case input::gamepad_button::dpad_up:
  479. mapping_element["button"] = "dpup";
  480. break;
  481. case input::gamepad_button::dpad_down:
  482. mapping_element["button"] = "dpdown";
  483. break;
  484. case input::gamepad_button::dpad_left:
  485. mapping_element["button"] = "dpleft";
  486. break;
  487. case input::gamepad_button::dpad_right:
  488. mapping_element["button"] = "dpright";
  489. break;
  490. default:
  491. break;
  492. }
  493. break;
  494. }
  495. default:
  496. break;
  497. }
  498. control_element.push_back(mapping_element);
  499. }
  500. }
  501. std::ofstream control_profile_file(path);
  502. control_profile_file << control_profile;
  503. }
  504. catch (...)
  505. {
  506. ctx.logger->pop_task(EXIT_FAILURE);
  507. }
  508. ctx.logger->pop_task(EXIT_SUCCESS);
  509. }
  510. void apply_gamepad_calibration(input::gamepad& gamepad, const json& calibration)
  511. {
  512. // Parse and apply activation thresholds
  513. if (calibration.contains("leftx_activation"))
  514. {
  515. float min = calibration["leftx_activation"][0].get<float>();
  516. float max = calibration["leftx_activation"][1].get<float>();
  517. gamepad.set_activation_threshold(input::gamepad_axis::left_x, min, max);
  518. }
  519. if (calibration.contains("lefty_activation"))
  520. {
  521. float min = calibration["lefty_activation"][0].get<float>();
  522. float max = calibration["lefty_activation"][1].get<float>();
  523. gamepad.set_activation_threshold(input::gamepad_axis::left_y, min, max);
  524. }
  525. if (calibration.contains("rightx_activation"))
  526. {
  527. float min = calibration["rightx_activation"][0].get<float>();
  528. float max = calibration["rightx_activation"][1].get<float>();
  529. gamepad.set_activation_threshold(input::gamepad_axis::right_x, min, max);
  530. }
  531. if (calibration.contains("righty_activation"))
  532. {
  533. float min = calibration["righty_activation"][0].get<float>();
  534. float max = calibration["righty_activation"][1].get<float>();
  535. gamepad.set_activation_threshold(input::gamepad_axis::right_y, min, max);
  536. }
  537. if (calibration.contains("lefttrigger_activation"))
  538. {
  539. float min = calibration["lefttrigger_activation"][0].get<float>();
  540. float max = calibration["lefttrigger_activation"][1].get<float>();
  541. gamepad.set_activation_threshold(input::gamepad_axis::left_trigger, min, max);
  542. }
  543. if (calibration.contains("righttrigger_activation"))
  544. {
  545. float min = calibration["righttrigger_activation"][0].get<float>();
  546. float max = calibration["righttrigger_activation"][1].get<float>();
  547. gamepad.set_activation_threshold(input::gamepad_axis::right_trigger, min, max);
  548. }
  549. // Parse and apply deadzone shapes
  550. if (calibration.contains("left_deadzone_cross"))
  551. gamepad.set_left_deadzone_cross(calibration["left_deadzone_cross"].get<bool>());
  552. if (calibration.contains("right_deadzone_cross"))
  553. gamepad.set_right_deadzone_cross(calibration["right_deadzone_cross"].get<bool>());
  554. if (calibration.contains("left_deadzone_roundness"))
  555. gamepad.set_left_deadzone_roundness(calibration["left_deadzone_roundness"].get<float>());
  556. if (calibration.contains("right_deadzone_roundness"))
  557. gamepad.set_right_deadzone_roundness(calibration["right_deadzone_roundness"].get<float>());
  558. auto parse_response_curve = [](const std::string& curve) -> input::gamepad_response_curve
  559. {
  560. if (curve == "square")
  561. return input::gamepad_response_curve::square;
  562. else if (curve == "cube")
  563. return input::gamepad_response_curve::cube;
  564. return input::gamepad_response_curve::linear;
  565. };
  566. // Parse and apply axis response curves
  567. if (calibration.contains("leftx_response_curve"))
  568. {
  569. auto curve = parse_response_curve(calibration["leftx_response_curve"].get<std::string>());
  570. gamepad.set_response_curve(input::gamepad_axis::left_x, curve);
  571. }
  572. if (calibration.contains("lefty_response_curve"))
  573. {
  574. auto curve = parse_response_curve(calibration["lefty_response_curve"].get<std::string>());
  575. gamepad.set_response_curve(input::gamepad_axis::left_y, curve);
  576. }
  577. if (calibration.contains("rightx_response_curve"))
  578. {
  579. auto curve = parse_response_curve(calibration["rightx_response_curve"].get<std::string>());
  580. gamepad.set_response_curve(input::gamepad_axis::right_x, curve);
  581. }
  582. if (calibration.contains("righty_response_curve"))
  583. {
  584. auto curve = parse_response_curve(calibration["righty_response_curve"].get<std::string>());
  585. gamepad.set_response_curve(input::gamepad_axis::right_y, curve);
  586. }
  587. if (calibration.contains("lefttrigger_response_curve"))
  588. {
  589. auto curve = parse_response_curve(calibration["lefttrigger_response_curve"].get<std::string>());
  590. gamepad.set_response_curve(input::gamepad_axis::left_trigger, curve);
  591. }
  592. if (calibration.contains("righttrigger_response_curve"))
  593. {
  594. auto curve = parse_response_curve(calibration["righttrigger_response_curve"].get<std::string>());
  595. gamepad.set_response_curve(input::gamepad_axis::right_trigger, curve);
  596. }
  597. }
  598. } // namespace game