💿🐜 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.

674 lines
22 KiB

  1. /*
  2. * Copyright (C) 2023 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_uuid().string() + ".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. /*
  73. // Determine absolute path to gamepad calibration file
  74. std::filesystem::path path = ctx.controls_path / gamepad_calibration_path(ctx, gamepad);
  75. // Open calibration file
  76. std::ofstream stream;
  77. stream.open(path);
  78. if (!stream)
  79. return false;
  80. // Write calibration to file
  81. stream << calibration.dump(1, '\t');
  82. if (stream.bad())
  83. {
  84. stream.close();
  85. return false;
  86. }
  87. // Close calibration file
  88. stream.close();
  89. */
  90. return true;
  91. }
  92. void apply_control_profile(game::context& ctx, const json& profile)
  93. {
  94. /*
  95. // Map gamepad buttons to strings
  96. const std::unordered_map<std::string, input::gamepad_button> gamepad_button_map =
  97. {
  98. {"a", input::gamepad_button::a},
  99. {"b", input::gamepad_button::b},
  100. {"x", input::gamepad_button::x},
  101. {"y", input::gamepad_button::y},
  102. {"back", input::gamepad_button::back},
  103. {"guide", input::gamepad_button::guide},
  104. {"start", input::gamepad_button::start},
  105. {"leftstick", input::gamepad_button::left_stick},
  106. {"rightstick", input::gamepad_button::right_stick},
  107. {"leftshoulder", input::gamepad_button::left_shoulder},
  108. {"rightshoulder", input::gamepad_button::right_shoulder},
  109. {"dpup", input::gamepad_button::dpad_up},
  110. {"dpdown", input::gamepad_button::dpad_down},
  111. {"dpleft", input::gamepad_button::dpad_left},
  112. {"dpright", input::gamepad_button::dpad_right}
  113. };
  114. // Map gamepad axes to strings
  115. const std::unordered_map<std::string, input::gamepad_axis> gamepad_axis_map =
  116. {
  117. {"leftx", input::gamepad_axis::left_x},
  118. {"lefty", input::gamepad_axis::left_y},
  119. {"rightx", input::gamepad_axis::right_x},
  120. {"righty", input::gamepad_axis::right_y},
  121. {"lefttrigger", input::gamepad_axis::left_trigger},
  122. {"righttrigger", input::gamepad_axis::right_trigger}
  123. };
  124. // Remove all existing input mappings
  125. for (auto control = ctx.controls.begin(); control != ctx.controls.end(); ++control)
  126. {
  127. ctx.input_event_router->remove_mappings(control->second);
  128. }
  129. // Get keyboard and mouse devices
  130. input::keyboard* keyboard = ctx.app->get_device_manager().get_keyboards().front();
  131. input::mouse* mouse = ctx.app->get_device_manager().get_mice().front();
  132. // Find profile gamepad device
  133. input::gamepad* gamepad = nullptr;
  134. auto gamepad_element = profile.find("gamepad");
  135. if (gamepad_element != profile.end())
  136. {
  137. // Get gamepad UUID string
  138. const std::string uuid_string = gamepad_element->get<std::string>();
  139. // Find gamepad with matching UUID
  140. for (input::gamepad* device: ctx.app->get_device_manager().get_gamepads())
  141. {
  142. if (device->get_uuid().string() == uuid_string)
  143. {
  144. gamepad = device;
  145. break;
  146. }
  147. }
  148. }
  149. // Find controls element
  150. auto controls_element = profile.find("controls");
  151. if (controls_element != profile.end())
  152. {
  153. // For each control in the profile
  154. for (auto control_element = controls_element->cbegin(); control_element != controls_element->cend(); ++control_element)
  155. {
  156. // Get the control name
  157. std::string control_name = control_element.key();
  158. // Find or create control
  159. input::control* control;
  160. if (ctx.controls.count(control_name))
  161. {
  162. control = ctx.controls[control_name];
  163. }
  164. else
  165. {
  166. control = new input::control();
  167. ctx.controls[control_name] = control;
  168. }
  169. // For each mapping in the control
  170. for (auto mapping_element = control_element.value().cbegin(); mapping_element != control_element.value().cend(); ++mapping_element)
  171. {
  172. if (!mapping_element->contains("device"))
  173. {
  174. debug::log::warning("Control \"" + control_name + "\" not mapped to a device");
  175. continue;
  176. }
  177. // Get the mapping device
  178. const std::string device = (*mapping_element)["device"];
  179. if (device == "keyboard")
  180. {
  181. // Parse key name
  182. if (!mapping_element->contains("key"))
  183. {
  184. debug::log::warning("Control \"" + control_name + "\" has invalid keyboard mapping");
  185. continue;
  186. }
  187. std::string key = (*mapping_element)["key"].get<std::string>();
  188. // Get scancode from key name
  189. input::scancode scancode = keyboard->get_scancode_from_name(key.c_str());
  190. if (scancode == input::scancode::unknown)
  191. {
  192. debug::log::warning("Control \"" + control_name + "\" mapped to unknown keyboard key \"" + key + "\"");
  193. continue;
  194. }
  195. // Map control to keyboard key
  196. ctx.input_event_router->add_mapping(input::key_mapping(control, keyboard, scancode));
  197. debug::log::info("Mapped control \"" + control_name + "\" to keyboard key \"" + key + "\"");
  198. }
  199. else if (device == "mouse")
  200. {
  201. if (mapping_element->contains("button"))
  202. {
  203. // Parse mouse button index
  204. int button = (*mapping_element)["button"].get<int>();
  205. // Map control to mouse button
  206. ctx.input_event_router->add_mapping(input::mouse_button_mapping(control, mouse, button));
  207. debug::log::info("Mapped control \"" + control_name + "\" to mouse button " + std::to_string(button));
  208. }
  209. else if (mapping_element->contains("wheel"))
  210. {
  211. // Parse mouse wheel axis
  212. std::string wheel = (*mapping_element)["wheel"].get<std::string>();
  213. input::mouse_axis axis;
  214. if (wheel == "x+")
  215. axis = input::mouse_axis::positive_x;
  216. else if (wheel == "x-")
  217. axis = input::mouse_axis::negative_x;
  218. else if (wheel == "y+")
  219. axis = input::mouse_axis::positive_y;
  220. else if (wheel == "y-")
  221. axis = input::mouse_axis::negative_y;
  222. else
  223. {
  224. debug::log::warning("Control \"" + control_name + "\" is mapped to invalid mouse wheel axis \"" + wheel + "\"");
  225. continue;
  226. }
  227. // Map control to mouse wheel axis
  228. ctx.input_event_router->add_mapping(input::mouse_wheel_mapping(control, mouse, axis));
  229. debug::log::info("Mapped control \"" + control_name + "\" to mouse wheel axis " + wheel);
  230. }
  231. else if (mapping_element->contains("motion"))
  232. {
  233. std::string motion = (*mapping_element)["motion"].get<std::string>();
  234. input::mouse_axis axis;
  235. if (motion == "x+")
  236. axis = input::mouse_axis::positive_x;
  237. else if (motion == "x-")
  238. axis = input::mouse_axis::negative_x;
  239. else if (motion == "y+")
  240. axis = input::mouse_axis::positive_y;
  241. else if (motion == "y-")
  242. axis = input::mouse_axis::negative_y;
  243. else
  244. {
  245. debug::log::warning("Control \"" + control_name + "\" is mapped to invalid mouse motion axis \"" + motion + "\"");
  246. continue;
  247. }
  248. // Map control to mouse motion axis
  249. ctx.input_event_router->add_mapping(input::mouse_motion_mapping(control, mouse, axis));
  250. debug::log::info("Mapped control \"" + control_name + "\" to mouse motion axis " + motion);
  251. }
  252. else
  253. {
  254. debug::log::warning("Control \"" + control_name + "\" has invalid mouse mapping");
  255. continue;
  256. }
  257. }
  258. else if (device == "gamepad")
  259. {
  260. if (mapping_element->contains("button"))
  261. {
  262. // Parse gamepad button
  263. std::string button = (*mapping_element)["button"].get<std::string>();
  264. auto button_it = gamepad_button_map.find(button);
  265. if (button_it == gamepad_button_map.end())
  266. {
  267. debug::log::warning("Control \"" + control_name + "\" is mapped to invalid gamepad button \"" + button + "\"");
  268. continue;
  269. }
  270. // Map control to gamepad button
  271. ctx.input_event_router->add_mapping(input::gamepad_button_mapping(control, gamepad, button_it->second));
  272. debug::log::info("Mapped control \"" + control_name + "\" to gamepad button " + button);
  273. }
  274. else if (mapping_element->contains("axis"))
  275. {
  276. std::string axis = (*mapping_element)["axis"].get<std::string>();
  277. // Parse gamepad axis name
  278. const std::string axis_name = axis.substr(0, axis.length() - 1);
  279. auto axis_it = gamepad_axis_map.find(axis_name);
  280. if (axis_it == gamepad_axis_map.end())
  281. {
  282. debug::log::warning("Control \"" + control_name + "\" is mapped to invalid gamepad axis \"" + axis_name + "\"");
  283. continue;
  284. }
  285. // Parse gamepad axis sign
  286. const char axis_sign = axis.back();
  287. if (axis_sign != '-' && axis_sign != '+')
  288. {
  289. debug::log::warning("Control \"" + control_name + "\" is mapped to gamepad axis with invalid sign \"" + axis_sign + "\"");
  290. continue;
  291. }
  292. bool axis_negative = (axis_sign == '-');
  293. // Map control to gamepad axis
  294. ctx.input_event_router->add_mapping(input::gamepad_axis_mapping(control, gamepad, axis_it->second, axis_negative));
  295. debug::log::info("Mapped control \"" + control_name + "\" to gamepad axis " + axis);
  296. }
  297. else
  298. {
  299. debug::log::info("Control \"" + control_name + "\" has invalid gamepad mapping");
  300. continue;
  301. }
  302. }
  303. else
  304. {
  305. debug::log::warning("Control \"" + control_name + "\" bound to unknown device \"" + device + "\"");
  306. }
  307. }
  308. }
  309. }
  310. */
  311. }
  312. void save_control_profile(game::context& ctx)
  313. {
  314. /*
  315. std::filesystem::path path;
  316. if (ctx.config->contains("control_profile"))
  317. path = ctx.shared_config_path / "controls" / (*ctx.config)["control_profile"].get<std::string>();
  318. debug::log::trace("Saving control profile to \"{}\"...", path.string());
  319. try
  320. {
  321. json control_profile;
  322. // Add controls element
  323. auto& controls_element = control_profile["controls"];
  324. controls_element = json::object();
  325. for (auto controls_it = ctx.controls.begin(); controls_it != ctx.controls.end(); ++controls_it)
  326. {
  327. const std::string& control_name = controls_it->first;
  328. input::control* control = controls_it->second;
  329. // Add control element
  330. auto& control_element = controls_element[control_name];
  331. control_element = json::array();
  332. // Get control mappings
  333. auto mappings = ctx.input_event_router->get_mappings(control);
  334. // Skip unmapped controls
  335. if (!mappings)
  336. continue;
  337. // Add control mapping elements
  338. for (input::mapping* mapping: *mappings)
  339. {
  340. json mapping_element;
  341. switch (mapping->get_type())
  342. {
  343. case input::mapping_type::key:
  344. {
  345. const input::key_mapping* key_mapping = static_cast<const input::key_mapping*>(mapping);
  346. mapping_element["device"] = "keyboard";
  347. mapping_element["key"] = input::keyboard::get_scancode_name(key_mapping->scancode);
  348. break;
  349. }
  350. case input::mapping_type::mouse_wheel:
  351. {
  352. const input::mouse_wheel_mapping* wheel_mapping = static_cast<const input::mouse_wheel_mapping*>(mapping);
  353. mapping_element["device"] = "mouse";
  354. switch (wheel_mapping->axis)
  355. {
  356. case input::mouse_axis::negative_x:
  357. mapping_element["wheel"] = "x-";
  358. break;
  359. case input::mouse_axis::positive_x:
  360. mapping_element["wheel"] = "x+";
  361. break;
  362. case input::mouse_axis::negative_y:
  363. mapping_element["wheel"] = "y-";
  364. break;
  365. case input::mouse_axis::positive_y:
  366. mapping_element["wheel"] = "y+";
  367. break;
  368. default:
  369. break;
  370. }
  371. break;
  372. }
  373. case input::mapping_type::mouse_motion:
  374. {
  375. const input::mouse_motion_mapping* motion_mapping = static_cast<const input::mouse_motion_mapping*>(mapping);
  376. mapping_element["device"] = "mouse";
  377. switch (motion_mapping->axis)
  378. {
  379. case input::mouse_axis::negative_x:
  380. mapping_element["motion"] = "x-";
  381. break;
  382. case input::mouse_axis::positive_x:
  383. mapping_element["motion"] = "x+";
  384. break;
  385. case input::mouse_axis::negative_y:
  386. mapping_element["motion"] = "y-";
  387. break;
  388. case input::mouse_axis::positive_y:
  389. mapping_element["motion"] = "y+";
  390. break;
  391. default:
  392. break;
  393. }
  394. break;
  395. }
  396. case input::mapping_type::mouse_button:
  397. {
  398. const input::mouse_button_mapping* button_mapping = static_cast<const input::mouse_button_mapping*>(mapping);
  399. mapping_element["device"] = "mouse";
  400. mapping_element["button"] = button_mapping->button;
  401. break;
  402. }
  403. case input::mapping_type::gamepad_axis:
  404. {
  405. const input::gamepad_axis_mapping* axis_mapping = static_cast<const input::gamepad_axis_mapping*>(mapping);
  406. mapping_element["device"] = "gamepad";
  407. switch (axis_mapping->axis)
  408. {
  409. case input::gamepad_axis::left_x:
  410. if (axis_mapping->negative)
  411. mapping_element["axis"] = "leftx-";
  412. else
  413. mapping_element["axis"] = "leftx+";
  414. break;
  415. case input::gamepad_axis::left_y:
  416. if (axis_mapping->negative)
  417. mapping_element["axis"] = "lefty-";
  418. else
  419. mapping_element["axis"] = "lefty+";
  420. break;
  421. case input::gamepad_axis::right_x:
  422. if (axis_mapping->negative)
  423. mapping_element["axis"] = "rightx-";
  424. else
  425. mapping_element["axis"] = "rightx+";
  426. break;
  427. case input::gamepad_axis::right_y:
  428. if (axis_mapping->negative)
  429. mapping_element["axis"] = "righty-";
  430. else
  431. mapping_element["axis"] = "righty+";
  432. break;
  433. case input::gamepad_axis::left_trigger:
  434. mapping_element["axis"] = "lefttrigger+";
  435. break;
  436. case input::gamepad_axis::right_trigger:
  437. mapping_element["axis"] = "righttrigger+";
  438. break;
  439. default:
  440. break;
  441. }
  442. break;
  443. }
  444. case input::mapping_type::gamepad_button:
  445. {
  446. const input::gamepad_button_mapping* button_mapping = static_cast<const input::gamepad_button_mapping*>(mapping);
  447. mapping_element["device"] = "gamepad";
  448. switch (button_mapping->button)
  449. {
  450. case input::gamepad_button::a:
  451. mapping_element["button"] = "a";
  452. break;
  453. case input::gamepad_button::b:
  454. mapping_element["button"] = "b";
  455. break;
  456. case input::gamepad_button::x:
  457. mapping_element["button"] = "x";
  458. break;
  459. case input::gamepad_button::y:
  460. mapping_element["button"] = "y";
  461. break;
  462. case input::gamepad_button::back:
  463. mapping_element["button"] = "back";
  464. break;
  465. case input::gamepad_button::guide:
  466. mapping_element["button"] = "guide";
  467. break;
  468. case input::gamepad_button::start:
  469. mapping_element["button"] = "start";
  470. break;
  471. case input::gamepad_button::left_stick:
  472. mapping_element["button"] = "leftstick";
  473. break;
  474. case input::gamepad_button::right_stick:
  475. mapping_element["button"] = "rightstick";
  476. break;
  477. case input::gamepad_button::left_shoulder:
  478. mapping_element["button"] = "leftshoulder";
  479. break;
  480. case input::gamepad_button::right_shoulder:
  481. mapping_element["button"] = "rightshoulder";
  482. break;
  483. case input::gamepad_button::dpad_up:
  484. mapping_element["button"] = "dpup";
  485. break;
  486. case input::gamepad_button::dpad_down:
  487. mapping_element["button"] = "dpdown";
  488. break;
  489. case input::gamepad_button::dpad_left:
  490. mapping_element["button"] = "dpleft";
  491. break;
  492. case input::gamepad_button::dpad_right:
  493. mapping_element["button"] = "dpright";
  494. break;
  495. default:
  496. break;
  497. }
  498. break;
  499. }
  500. default:
  501. break;
  502. }
  503. control_element.push_back(mapping_element);
  504. }
  505. }
  506. std::ofstream control_profile_file(path);
  507. control_profile_file << control_profile;
  508. }
  509. catch (...)
  510. {
  511. debug::log::pop_task(EXIT_FAILURE);
  512. }
  513. debug::log::pop_task(EXIT_SUCCESS);
  514. */
  515. }
  516. void apply_gamepad_calibration(input::gamepad& gamepad, const json& calibration)
  517. {
  518. /*
  519. // Parse and apply activation thresholds
  520. if (calibration.contains("leftx_activation"))
  521. {
  522. float min = calibration["leftx_activation"][0].get<float>();
  523. float max = calibration["leftx_activation"][1].get<float>();
  524. gamepad.set_activation_threshold(input::gamepad_axis::left_x, min, max);
  525. }
  526. if (calibration.contains("lefty_activation"))
  527. {
  528. float min = calibration["lefty_activation"][0].get<float>();
  529. float max = calibration["lefty_activation"][1].get<float>();
  530. gamepad.set_activation_threshold(input::gamepad_axis::left_y, min, max);
  531. }
  532. if (calibration.contains("rightx_activation"))
  533. {
  534. float min = calibration["rightx_activation"][0].get<float>();
  535. float max = calibration["rightx_activation"][1].get<float>();
  536. gamepad.set_activation_threshold(input::gamepad_axis::right_x, min, max);
  537. }
  538. if (calibration.contains("righty_activation"))
  539. {
  540. float min = calibration["righty_activation"][0].get<float>();
  541. float max = calibration["righty_activation"][1].get<float>();
  542. gamepad.set_activation_threshold(input::gamepad_axis::right_y, min, max);
  543. }
  544. if (calibration.contains("lefttrigger_activation"))
  545. {
  546. float min = calibration["lefttrigger_activation"][0].get<float>();
  547. float max = calibration["lefttrigger_activation"][1].get<float>();
  548. gamepad.set_activation_threshold(input::gamepad_axis::left_trigger, min, max);
  549. }
  550. if (calibration.contains("righttrigger_activation"))
  551. {
  552. float min = calibration["righttrigger_activation"][0].get<float>();
  553. float max = calibration["righttrigger_activation"][1].get<float>();
  554. gamepad.set_activation_threshold(input::gamepad_axis::right_trigger, min, max);
  555. }
  556. // Parse and apply deadzone shapes
  557. if (calibration.contains("left_deadzone_cross"))
  558. gamepad.set_left_deadzone_cross(calibration["left_deadzone_cross"].get<bool>());
  559. if (calibration.contains("right_deadzone_cross"))
  560. gamepad.set_right_deadzone_cross(calibration["right_deadzone_cross"].get<bool>());
  561. if (calibration.contains("left_deadzone_roundness"))
  562. gamepad.set_left_deadzone_roundness(calibration["left_deadzone_roundness"].get<float>());
  563. if (calibration.contains("right_deadzone_roundness"))
  564. gamepad.set_right_deadzone_roundness(calibration["right_deadzone_roundness"].get<float>());
  565. auto parse_response_curve = [](const std::string& curve) -> input::gamepad_response_curve
  566. {
  567. if (curve == "square")
  568. return input::gamepad_response_curve::square;
  569. else if (curve == "cube")
  570. return input::gamepad_response_curve::cube;
  571. return input::gamepad_response_curve::linear;
  572. };
  573. // Parse and apply axis response curves
  574. if (calibration.contains("leftx_response_curve"))
  575. {
  576. auto curve = parse_response_curve(calibration["leftx_response_curve"].get<std::string>());
  577. gamepad.set_response_curve(input::gamepad_axis::left_x, curve);
  578. }
  579. if (calibration.contains("lefty_response_curve"))
  580. {
  581. auto curve = parse_response_curve(calibration["lefty_response_curve"].get<std::string>());
  582. gamepad.set_response_curve(input::gamepad_axis::left_y, curve);
  583. }
  584. if (calibration.contains("rightx_response_curve"))
  585. {
  586. auto curve = parse_response_curve(calibration["rightx_response_curve"].get<std::string>());
  587. gamepad.set_response_curve(input::gamepad_axis::right_x, curve);
  588. }
  589. if (calibration.contains("righty_response_curve"))
  590. {
  591. auto curve = parse_response_curve(calibration["righty_response_curve"].get<std::string>());
  592. gamepad.set_response_curve(input::gamepad_axis::right_y, curve);
  593. }
  594. if (calibration.contains("lefttrigger_response_curve"))
  595. {
  596. auto curve = parse_response_curve(calibration["lefttrigger_response_curve"].get<std::string>());
  597. gamepad.set_response_curve(input::gamepad_axis::left_trigger, curve);
  598. }
  599. if (calibration.contains("righttrigger_response_curve"))
  600. {
  601. auto curve = parse_response_curve(calibration["righttrigger_response_curve"].get<std::string>());
  602. gamepad.set_response_curve(input::gamepad_axis::right_trigger, curve);
  603. }
  604. */
  605. }
  606. } // namespace game