negcon_rumble.cpp (24241B)
1 // SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> and contributors. 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "negcon_rumble.h" 5 #include "IconsFontAwesome5.h" 6 #include "common/assert.h" 7 #include "common/log.h" 8 #include "host.h" 9 #include "settings.h" 10 #include "system.h" 11 12 #include "util/imgui_manager.h" 13 #include "util/input_manager.h" 14 #include "util/state_wrapper.h" 15 16 #include "common/bitutils.h" 17 #include "common/log.h" 18 #include "common/string_util.h" 19 20 #include "IconsFontAwesome5.h" 21 #include "IconsPromptFont.h" 22 23 #include <cmath> 24 25 Log_SetChannel(NeGconRumble); 26 27 // Mapping of Button to index of corresponding bit in m_button_state 28 static constexpr std::array<u8, static_cast<size_t>(NeGconRumble::Button::Count)> s_button_indices = {3, 4, 5, 6, 29 7, 11, 12, 13}; 30 NeGconRumble::NeGconRumble(u32 index) : Controller(index) 31 { 32 m_status_byte = 0x5A; 33 m_axis_state.fill(0x00); 34 m_axis_state[static_cast<u8>(Axis::Steering)] = 0x80; 35 m_rumble_config.fill(0xFF); 36 } 37 38 NeGconRumble::~NeGconRumble() = default; 39 40 ControllerType NeGconRumble::GetType() const 41 { 42 return ControllerType::NeGconRumble; 43 } 44 bool NeGconRumble::InAnalogMode() const 45 { 46 return m_analog_mode; 47 } 48 49 void NeGconRumble::Reset() 50 { 51 m_command = Command::Idle; 52 m_command_step = 0; 53 m_rx_buffer.fill(0x00); 54 m_tx_buffer.fill(0x00); 55 m_analog_mode = false; 56 m_configuration_mode = false; 57 58 for (u32 i = 0; i < NUM_MOTORS; i++) 59 { 60 if (m_motor_state[i] != 0) 61 SetMotorState(i, 0); 62 } 63 64 m_dualshock_enabled = false; 65 ResetRumbleConfig(); 66 67 m_status_byte = 0x5A; 68 69 if (m_force_analog_on_reset) 70 { 71 if (!CanStartInAnalogMode(ControllerType::AnalogController)) 72 { 73 Host::AddIconOSDMessage( 74 fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD, 75 TRANSLATE_STR("OSDMessage", 76 "Analog mode forcing is disabled by game settings. Controller will start in digital mode."), 77 10.0f); 78 } 79 else 80 { 81 SetAnalogMode(true, false); 82 } 83 } 84 } 85 86 bool NeGconRumble::DoState(StateWrapper& sw, bool apply_input_state) 87 { 88 if (!Controller::DoState(sw, apply_input_state)) 89 return false; 90 91 const bool old_analog_mode = m_analog_mode; 92 93 sw.Do(&m_analog_mode); 94 sw.Do(&m_dualshock_enabled); 95 sw.Do(&m_configuration_mode); 96 sw.DoEx(&m_status_byte, 55, static_cast<u8>(0x5A)); 97 98 u16 button_state = m_button_state; 99 sw.DoEx(&button_state, 44, static_cast<u16>(0xFFFF)); 100 if (apply_input_state) 101 m_button_state = button_state; 102 else 103 m_analog_mode = old_analog_mode; 104 105 sw.Do(&m_command); 106 107 sw.DoEx(&m_rumble_config, 45, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); 108 sw.DoEx(&m_rumble_config_large_motor_index, 45, -1); 109 sw.DoEx(&m_rumble_config_small_motor_index, 45, -1); 110 sw.DoEx(&m_analog_toggle_queued, 45, false); 111 112 MotorState motor_state = m_motor_state; 113 sw.Do(&motor_state); 114 115 if (sw.IsReading()) 116 { 117 for (u8 i = 0; i < NUM_MOTORS; i++) 118 SetMotorState(i, motor_state[i]); 119 120 if (old_analog_mode != m_analog_mode) 121 { 122 Host::AddIconOSDMessage(fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD, 123 fmt::format(m_analog_mode ? 124 TRANSLATE_FS("AnalogController", "Controller {} switched to analog mode.") : 125 TRANSLATE_FS("AnalogController", "Controller {} switched to digital mode."), 126 m_index + 1u), 127 5.0f); 128 } 129 } 130 return true; 131 } 132 133 float NeGconRumble::GetBindState(u32 index) const 134 { 135 if (index >= static_cast<u32>(Button::Count)) 136 { 137 const u32 sub_index = index - static_cast<u32>(Button::Count); 138 if (sub_index >= static_cast<u32>(m_half_axis_state.size())) 139 return 0.0f; 140 141 return static_cast<float>(m_half_axis_state[sub_index]) * (1.0f / 255.0f); 142 } 143 else if (index < static_cast<u32>(Button::Analog)) 144 { 145 return static_cast<float>(((m_button_state >> index) & 1u) ^ 1u); 146 } 147 else 148 { 149 return 0.0f; 150 } 151 } 152 153 void NeGconRumble::SetBindState(u32 index, float value) 154 { 155 if (index == static_cast<s32>(Button::Analog)) 156 { 157 // analog toggle 158 if (value >= 0.5f) 159 { 160 if (m_command == Command::Idle) 161 ProcessAnalogModeToggle(); 162 else 163 m_analog_toggle_queued = true; 164 } 165 166 return; 167 } 168 // Steering Axis: -1..1 -> 0..255 169 else if (index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringLeft)) || 170 index == (static_cast<u32>(Button::Count) + static_cast<u32>(HalfAxis::SteeringRight))) 171 { 172 value *= m_steering_sensitivity; 173 if (value < m_steering_deadzone) 174 value = 0.0f; 175 176 m_half_axis_state[index - static_cast<u32>(Button::Count)] = 177 static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f)); 178 179 // Merge left/right. Seems to be inverted. 180 m_axis_state[static_cast<u32>(Axis::Steering)] = 181 ((m_half_axis_state[1] != 0) ? (127u + ((m_half_axis_state[1] + 1u) / 2u)) : 182 (127u - (m_half_axis_state[0] / 2u))); 183 } 184 else if (index >= static_cast<u32>(Button::Count)) 185 { 186 // less one because of the two steering axes 187 const u32 sub_index = index - (static_cast<u32>(Button::Count) + 1); 188 if (sub_index >= m_axis_state.size()) 189 return; 190 191 m_axis_state[sub_index] = static_cast<u8>(std::clamp(value * 255.0f, 0.0f, 255.0f)); 192 } 193 else if (index < static_cast<u32>(Button::Count)) 194 { 195 const u16 bit = u16(1) << s_button_indices[static_cast<u8>(index)]; 196 197 if (value >= 0.5f) 198 { 199 if (m_button_state & bit) 200 System::SetRunaheadReplayFlag(); 201 202 m_button_state &= ~bit; 203 } 204 else 205 { 206 if (!(m_button_state & bit)) 207 System::SetRunaheadReplayFlag(); 208 209 m_button_state |= bit; 210 } 211 } 212 } 213 214 u32 NeGconRumble::GetButtonStateBits() const 215 { 216 return m_button_state ^ 0xFFFF; 217 } 218 219 std::optional<u32> NeGconRumble::GetAnalogInputBytes() const 220 { 221 return m_axis_state[static_cast<size_t>(Axis::L)] << 24 | m_axis_state[static_cast<size_t>(Axis::II)] << 16 | 222 m_axis_state[static_cast<size_t>(Axis::I)] << 8 | m_axis_state[static_cast<size_t>(Axis::Steering)]; 223 } 224 225 void NeGconRumble::ResetTransferState() 226 { 227 if (m_analog_toggle_queued) 228 { 229 ProcessAnalogModeToggle(); 230 m_analog_toggle_queued = false; 231 } 232 233 m_command = Command::Idle; 234 m_command_step = 0; 235 } 236 237 void NeGconRumble::SetAnalogMode(bool enabled, bool show_message) 238 { 239 if (m_analog_mode == enabled) 240 return; 241 242 INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital"); 243 if (show_message) 244 { 245 Host::AddIconOSDMessage( 246 fmt::format("analog_mode_toggle_{}", m_index), ICON_FA_GAMEPAD, 247 enabled ? fmt::format(TRANSLATE_FS("Controller", "Controller {} switched to analog mode."), m_index + 1u) : 248 fmt::format(TRANSLATE_FS("Controller", "Controller {} switched to digital mode."), m_index + 1u)); 249 } 250 251 m_analog_mode = enabled; 252 } 253 254 void NeGconRumble::ProcessAnalogModeToggle() 255 { 256 if (m_analog_locked) 257 { 258 Host::AddIconOSDMessage( 259 fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD, 260 fmt::format(m_analog_mode ? 261 TRANSLATE_FS("AnalogController", "Controller {} is locked to analog mode by the game.") : 262 TRANSLATE_FS("AnalogController", "Controller {} is locked to digital mode by the game."), 263 m_index + 1u), 264 5.0f); 265 } 266 else 267 { 268 SetAnalogMode(!m_analog_mode, true); 269 ResetRumbleConfig(); 270 271 if (m_dualshock_enabled) 272 m_status_byte = 0x00; 273 } 274 } 275 276 void NeGconRumble::SetMotorState(u32 motor, u8 value) 277 { 278 DebugAssert(motor < NUM_MOTORS); 279 if (m_motor_state[motor] != value) 280 { 281 m_motor_state[motor] = value; 282 UpdateHostVibration(); 283 } 284 } 285 286 void NeGconRumble::UpdateHostVibration() 287 { 288 std::array<float, NUM_MOTORS> hvalues; 289 for (u32 motor = 0; motor < NUM_MOTORS; motor++) 290 { 291 // Curve from https://github.com/KrossX/Pokopom/blob/master/Pokopom/Input_XInput.cpp#L210 292 const u8 state = m_motor_state[motor]; 293 const double x = static_cast<double>(std::min<u32>(state + static_cast<u32>(m_rumble_bias), 255)); 294 const double strength = 0.006474549734772402 * std::pow(x, 3.0) - 1.258165252213538 * std::pow(x, 2.0) + 295 156.82454281087692 * x + 3.637978807091713e-11; 296 297 hvalues[motor] = (state != 0) ? static_cast<float>(strength / 65535.0) : 0.0f; 298 } 299 300 InputManager::SetPadVibrationIntensity(m_index, hvalues[0], hvalues[1]); 301 } 302 303 u8 NeGconRumble::GetExtraButtonMaskLSB() const 304 { 305 return 0xFF; 306 } 307 308 void NeGconRumble::ResetRumbleConfig() 309 { 310 m_rumble_config.fill(0xFF); 311 312 m_rumble_config_large_motor_index = -1; 313 m_rumble_config_small_motor_index = -1; 314 315 SetMotorState(LargeMotor, 0); 316 SetMotorState(SmallMotor, 0); 317 } 318 319 void NeGconRumble::SetMotorStateForConfigIndex(int index, u8 value) 320 { 321 if (m_rumble_config_small_motor_index == index) 322 SetMotorState(SmallMotor, ((value & 0x01) != 0) ? 255 : 0); 323 else if (m_rumble_config_large_motor_index == index) 324 SetMotorState(LargeMotor, value); 325 } 326 327 u8 NeGconRumble::GetResponseNumHalfwords() const 328 { 329 if (m_configuration_mode || m_analog_mode) 330 return 0x3; 331 332 return (0x1); 333 } 334 335 u8 NeGconRumble::GetModeID() const 336 { 337 if (m_configuration_mode) 338 return 0xF; 339 340 if (m_analog_mode) 341 return 0x2; 342 343 return 0x4; 344 } 345 346 u8 NeGconRumble::GetIDByte() const 347 { 348 return Truncate8((GetModeID() << 4) | GetResponseNumHalfwords()); 349 } 350 351 bool NeGconRumble::Transfer(const u8 data_in, u8* data_out) 352 { 353 bool ack; 354 m_rx_buffer[m_command_step] = data_in; 355 356 switch (m_command) 357 { 358 case Command::Idle: 359 { 360 *data_out = 0xFF; 361 362 if (data_in == 0x01) 363 { 364 DEBUG_LOG("ACK controller access"); 365 m_command = Command::Ready; 366 return true; 367 } 368 369 DEV_LOG("Unknown data_in = 0x{:02X}", data_in); 370 return false; 371 } 372 break; 373 374 case Command::Ready: 375 { 376 if (data_in == 0x42) 377 { 378 Assert(m_command_step == 0); 379 m_response_length = (GetResponseNumHalfwords() + 1) * 2; 380 m_command = Command::ReadPad; 381 m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 382 } 383 else if (data_in == 0x43) 384 { 385 Assert(m_command_step == 0); 386 m_response_length = (GetResponseNumHalfwords() + 1) * 2; 387 m_command = Command::ConfigModeSetMode; 388 m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 389 } 390 else if (m_configuration_mode && data_in == 0x44) 391 { 392 Assert(m_command_step == 0); 393 m_response_length = (GetResponseNumHalfwords() + 1) * 2; 394 m_command = Command::SetAnalogMode; 395 m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 396 397 ResetRumbleConfig(); 398 } 399 else if (m_configuration_mode && data_in == 0x45) 400 { 401 Assert(m_command_step == 0); 402 m_response_length = (GetResponseNumHalfwords() + 1) * 2; 403 m_command = Command::GetAnalogMode; 404 m_tx_buffer = {GetIDByte(), m_status_byte, 0x01, 0x02, BoolToUInt8(m_analog_mode), 0x02, 0x01, 0x00}; 405 } 406 else if (m_configuration_mode && data_in == 0x46) 407 { 408 Assert(m_command_step == 0); 409 m_response_length = (GetResponseNumHalfwords() + 1) * 2; 410 m_command = Command::Command46; 411 m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 412 } 413 else if (m_configuration_mode && data_in == 0x47) 414 { 415 Assert(m_command_step == 0); 416 m_response_length = (GetResponseNumHalfwords() + 1) * 2; 417 m_command = Command::Command47; 418 m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00}; 419 } 420 else if (m_configuration_mode && data_in == 0x4C) 421 { 422 Assert(m_command_step == 0); 423 m_response_length = (GetResponseNumHalfwords() + 1) * 2; 424 m_command = Command::Command4C; 425 m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 426 } 427 else if (m_configuration_mode && data_in == 0x4D) 428 { 429 Assert(m_command_step == 0); 430 m_response_length = (GetResponseNumHalfwords() + 1) * 2; 431 m_command = Command::GetSetRumble; 432 m_tx_buffer = {GetIDByte(), m_status_byte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 433 434 m_rumble_config_large_motor_index = -1; 435 m_rumble_config_small_motor_index = -1; 436 } 437 else 438 { 439 if (m_configuration_mode) 440 ERROR_LOG("Unimplemented config mode command 0x{:02X}", data_in); 441 442 *data_out = 0xFF; 443 return false; 444 } 445 } 446 break; 447 448 case Command::ReadPad: 449 { 450 const int rumble_index = m_command_step - 2; 451 452 switch (m_command_step) 453 { 454 case 2: 455 { 456 m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB(); 457 458 if (m_dualshock_enabled) 459 SetMotorStateForConfigIndex(rumble_index, data_in); 460 } 461 break; 462 463 case 3: 464 { 465 m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8); 466 467 if (m_dualshock_enabled) 468 { 469 SetMotorStateForConfigIndex(rumble_index, data_in); 470 } 471 else 472 { 473 bool legacy_rumble_on = (m_rx_buffer[2] & 0xC0) == 0x40 && (m_rx_buffer[3] & 0x01) != 0; 474 SetMotorState(SmallMotor, legacy_rumble_on ? 255 : 0); 475 } 476 } 477 break; 478 479 case 4: 480 { 481 if (m_configuration_mode || m_analog_mode) 482 m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::Steering)]; 483 484 if (m_dualshock_enabled) 485 SetMotorStateForConfigIndex(rumble_index, data_in); 486 } 487 break; 488 489 case 5: 490 { 491 if (m_configuration_mode || m_analog_mode) 492 m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::I)]; 493 494 if (m_dualshock_enabled) 495 SetMotorStateForConfigIndex(rumble_index, data_in); 496 } 497 break; 498 499 case 6: 500 { 501 if (m_configuration_mode || m_analog_mode) 502 m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::II)]; 503 504 if (m_dualshock_enabled) 505 SetMotorStateForConfigIndex(rumble_index, data_in); 506 } 507 break; 508 509 case 7: 510 { 511 if (m_configuration_mode || m_analog_mode) 512 m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::L)]; 513 514 if (m_dualshock_enabled) 515 SetMotorStateForConfigIndex(rumble_index, data_in); 516 } 517 break; 518 519 default: 520 { 521 } 522 break; 523 } 524 } 525 break; 526 527 case Command::ConfigModeSetMode: 528 { 529 if (!m_configuration_mode) 530 { 531 switch (m_command_step) 532 { 533 case 2: 534 { 535 m_tx_buffer[m_command_step] = Truncate8(m_button_state) & GetExtraButtonMaskLSB(); 536 } 537 break; 538 539 case 3: 540 { 541 m_tx_buffer[m_command_step] = Truncate8(m_button_state >> 8); 542 } 543 break; 544 545 case 4: 546 { 547 if (m_configuration_mode || m_analog_mode) 548 m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::Steering)]; 549 } 550 break; 551 552 case 5: 553 { 554 if (m_configuration_mode || m_analog_mode) 555 m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::I)]; 556 } 557 break; 558 559 case 6: 560 { 561 if (m_configuration_mode || m_analog_mode) 562 m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::II)]; 563 } 564 break; 565 566 case 7: 567 { 568 if (m_configuration_mode || m_analog_mode) 569 m_tx_buffer[m_command_step] = m_axis_state[static_cast<u8>(Axis::L)]; 570 } 571 break; 572 573 default: 574 { 575 } 576 break; 577 } 578 } 579 580 if (m_command_step == (static_cast<s32>(m_response_length) - 1)) 581 { 582 m_configuration_mode = (m_rx_buffer[2] == 1); 583 584 if (m_configuration_mode) 585 { 586 m_dualshock_enabled = true; 587 m_status_byte = 0x5A; 588 } 589 590 DEV_LOG("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave"); 591 } 592 } 593 break; 594 595 case Command::SetAnalogMode: 596 { 597 if (m_command_step == 2) 598 { 599 DEV_LOG("analog mode val 0x{:02x}", data_in); 600 601 if (data_in == 0x00 || data_in == 0x01) 602 SetAnalogMode((data_in == 0x01), true); 603 } 604 else if (m_command_step == 3) 605 { 606 DEV_LOG("analog mode lock 0x{:02x}", data_in); 607 608 if (data_in == 0x02 || data_in == 0x03) 609 m_analog_locked = (data_in == 0x03); 610 } 611 } 612 break; 613 614 case Command::GetAnalogMode: 615 { 616 // Intentionally empty, analog mode byte is set in reply buffer when command is first received 617 } 618 break; 619 620 case Command::Command46: 621 { 622 if (m_command_step == 2) 623 { 624 if (data_in == 0x00) 625 { 626 m_tx_buffer[4] = 0x01; 627 m_tx_buffer[5] = 0x02; 628 m_tx_buffer[6] = 0x00; 629 m_tx_buffer[7] = 0x0A; 630 } 631 else if (data_in == 0x01) 632 { 633 m_tx_buffer[4] = 0x01; 634 m_tx_buffer[5] = 0x01; 635 m_tx_buffer[6] = 0x01; 636 m_tx_buffer[7] = 0x14; 637 } 638 } 639 } 640 break; 641 642 case Command::Command47: 643 { 644 if (m_command_step == 2 && data_in != 0x00) 645 { 646 m_tx_buffer[4] = 0x00; 647 m_tx_buffer[5] = 0x00; 648 m_tx_buffer[6] = 0x00; 649 m_tx_buffer[7] = 0x00; 650 } 651 } 652 break; 653 654 case Command::Command4C: 655 { 656 if (m_command_step == 2) 657 { 658 if (data_in == 0x00) 659 m_tx_buffer[5] = 0x04; 660 else if (data_in == 0x01) 661 m_tx_buffer[5] = 0x02; 662 } 663 } 664 break; 665 666 case Command::GetSetRumble: 667 { 668 int rumble_index = m_command_step - 2; 669 if (rumble_index >= 0) 670 { 671 m_tx_buffer[m_command_step] = m_rumble_config[rumble_index]; 672 m_rumble_config[rumble_index] = data_in; 673 674 if (data_in == 0x00) 675 m_rumble_config_small_motor_index = rumble_index; 676 else if (data_in == 0x01) 677 m_rumble_config_large_motor_index = rumble_index; 678 } 679 680 if (m_command_step == 7) 681 { 682 if (m_rumble_config_large_motor_index == -1) 683 SetMotorState(LargeMotor, 0); 684 685 if (m_rumble_config_small_motor_index == -1) 686 SetMotorState(SmallMotor, 0); 687 } 688 } 689 break; 690 691 DefaultCaseIsUnreachable(); 692 } 693 694 *data_out = m_tx_buffer[m_command_step]; 695 696 m_command_step = (m_command_step + 1) % m_response_length; 697 ack = (m_command_step == 0) ? false : true; 698 699 if (m_command_step == 0) 700 { 701 m_command = Command::Idle; 702 703 DEBUG_LOG("Rx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_rx_buffer[0], m_rx_buffer[1], 704 m_rx_buffer[2], m_rx_buffer[3], m_rx_buffer[4], m_rx_buffer[5], m_rx_buffer[6], m_rx_buffer[7]); 705 DEBUG_LOG("Tx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_tx_buffer[0], m_tx_buffer[1], 706 m_tx_buffer[2], m_tx_buffer[3], m_tx_buffer[4], m_tx_buffer[5], m_tx_buffer[6], m_tx_buffer[7]); 707 708 m_rx_buffer.fill(0x00); 709 m_tx_buffer.fill(0x00); 710 } 711 712 return ack; 713 } 714 715 std::unique_ptr<NeGconRumble> NeGconRumble::Create(u32 index) 716 { 717 return std::make_unique<NeGconRumble>(index); 718 } 719 720 static const Controller::ControllerBindingInfo s_binding_info[] = { 721 #define BUTTON(name, display_name, icon_name, button, genb) \ 722 { \ 723 name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb \ 724 } 725 #define AXIS(name, display_name, icon_name, halfaxis, genb) \ 726 { \ 727 name, display_name, icon_name, static_cast<u32>(NeGconRumble::Button::Count) + static_cast<u32>(halfaxis), \ 728 InputBindingInfo::Type::HalfAxis, genb \ 729 } 730 731 // clang-format off 732 BUTTON("Up", TRANSLATE_NOOP("NeGconRumble", "D-Pad Up"), ICON_PF_DPAD_UP, NeGconRumble::Button::Up, GenericInputBinding::DPadUp), 733 BUTTON("Right", TRANSLATE_NOOP("NeGconRumble", "D-Pad Right"), ICON_PF_DPAD_RIGHT, NeGconRumble::Button::Right, GenericInputBinding::DPadRight), 734 BUTTON("Down", TRANSLATE_NOOP("NeGconRumble", "D-Pad Down"), ICON_PF_DPAD_DOWN, NeGconRumble::Button::Down, GenericInputBinding::DPadDown), 735 BUTTON("Left", TRANSLATE_NOOP("NeGconRumble", "D-Pad Left"), ICON_PF_DPAD_LEFT, NeGconRumble::Button::Left, GenericInputBinding::DPadLeft), 736 BUTTON("Start", TRANSLATE_NOOP("NeGconRumble", "Start"),ICON_PF_START, NeGconRumble::Button::Start, GenericInputBinding::Start), 737 BUTTON("A", TRANSLATE_NOOP("NeGconRumble", "A Button"), ICON_PF_BUTTON_A, NeGconRumble::Button::A, GenericInputBinding::Circle), 738 BUTTON("B", TRANSLATE_NOOP("NeGconRumble", "B Button"), ICON_PF_BUTTON_B, NeGconRumble::Button::B, GenericInputBinding::Triangle), 739 AXIS("I", TRANSLATE_NOOP("NeGconRumble", "I Button"), ICON_PF_RIGHT_TRIGGER_R2, NeGconRumble::HalfAxis::I, GenericInputBinding::R2), 740 AXIS("II", TRANSLATE_NOOP("NeGconRumble", "II Button"), ICON_PF_LEFT_TRIGGER_L2, NeGconRumble::HalfAxis::II, GenericInputBinding::L2), 741 AXIS("L", TRANSLATE_NOOP("NeGconRumble", "Left Trigger"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::L, GenericInputBinding::L1), 742 BUTTON("R", TRANSLATE_NOOP("NeGconRumble", "Right Trigger"), ICON_PF_RIGHT_SHOULDER_R1, NeGconRumble::Button::R, GenericInputBinding::R1), 743 AXIS("SteeringLeft", TRANSLATE_NOOP("NeGconRumble", "Steering (Twist) Left"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::SteeringLeft, GenericInputBinding::LeftStickLeft), 744 AXIS("SteeringRight", TRANSLATE_NOOP("NeGconRumble", "Steering (Twist) Right"), ICON_PF_LEFT_ANALOG_LEFT, NeGconRumble::HalfAxis::SteeringRight, GenericInputBinding::LeftStickRight), 745 BUTTON("Analog", TRANSLATE_NOOP("NeGconRumble", "Analog Toggle"), ICON_PF_ANALOG_LEFT_RIGHT, NeGconRumble::Button::Analog, GenericInputBinding::System), 746 // clang-format on 747 748 #undef AXIS 749 #undef BUTTON 750 }; 751 752 static const SettingInfo s_settings[] = { 753 {SettingInfo::Type::Float, "SteeringDeadzone", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Deadzone"), 754 TRANSLATE_NOOP("NeGconRumble", "Sets deadzone size for steering axis."), "0.00f", "0.00f", "0.99f", "0.01f", 755 "%.0f%%", nullptr, 100.0f}, 756 {SettingInfo::Type::Float, "SteeringSensitivity", TRANSLATE_NOOP("NeGconRumble", "Steering Axis Sensitivity"), 757 TRANSLATE_NOOP("NeGconRumble", "Sets the steering axis scaling factor."), "1.00f", "0.01f", "2.00f", "0.01f", 758 "%.0f%%", nullptr, 100.0f}, 759 }; 760 761 const Controller::ControllerInfo NeGconRumble::INFO = {ControllerType::NeGconRumble, 762 "NeGconRumble", 763 TRANSLATE_NOOP("ControllerType", "NeGcon with Rumble"), 764 ICON_PF_GAMEPAD, 765 s_binding_info, 766 s_settings, 767 Controller::VibrationCapabilities::LargeSmallMotors}; 768 769 void NeGconRumble::LoadSettings(SettingsInterface& si, const char* section, bool initial) 770 { 771 Controller::LoadSettings(si, section, initial); 772 m_steering_deadzone = si.GetFloatValue(section, "SteeringDeadzone", 0.10f); 773 m_steering_sensitivity = si.GetFloatValue(section, "SteeringSensitivity", 1.00f); 774 m_rumble_bias = static_cast<u8>(std::min<u32>(si.GetIntValue(section, "VibrationBias", 8), 255)); 775 }