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

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