From 4dad1c35f40060deea7e11a0a243547a345d4b5c Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Mon, 10 Feb 2025 19:46:10 -0600 Subject: [PATCH] Core/DolphinQt: Implement Emulated Balance Board. Co-authored-by: Jordan Woyak --- Source/Core/Core/CMakeLists.txt | 2 + Source/Core/Core/Core.cpp | 4 +- Source/Core/Core/HW/Wiimote.cpp | 100 ++-- Source/Core/Core/HW/Wiimote.h | 24 +- .../Core/HW/WiimoteCommon/WiimoteReport.h | 3 +- Source/Core/Core/HW/WiimoteEmu/Camera.cpp | 16 +- Source/Core/Core/HW/WiimoteEmu/Camera.h | 16 +- .../HW/WiimoteEmu/DesiredWiimoteState.cpp | 6 +- .../Core/HW/WiimoteEmu/DesiredWiimoteState.h | 5 +- .../Core/HW/WiimoteEmu/EmuSubroutines.cpp | 146 ++--- .../HW/WiimoteEmu/Extension/BalanceBoard.cpp | 199 +++++++ .../HW/WiimoteEmu/Extension/BalanceBoard.h | 64 +++ .../Extension/DesiredExtensionState.h | 4 +- .../Core/HW/WiimoteEmu/Extension/Extension.h | 16 +- .../Core/Core/HW/WiimoteEmu/ExtensionPort.h | 2 + Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp | 519 +++++++++++++----- Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h | 390 ++++++++----- Source/Core/Core/HotkeyManager.h | 1 - .../Core/IOS/USB/Bluetooth/WiimoteDevice.cpp | 21 +- .../Core/IOS/USB/Bluetooth/WiimoteDevice.h | 2 + Source/Core/Core/Movie.cpp | 52 +- Source/Core/Core/Movie.h | 23 +- Source/Core/Core/State.cpp | 8 +- Source/Core/DolphinLib.props | 2 + Source/Core/DolphinQt/CMakeLists.txt | 4 + .../Config/Mapping/BalanceBoardGeneral.cpp | 49 ++ .../Config/Mapping/BalanceBoardGeneral.h | 17 + .../Config/Mapping/MappingWindow.cpp | 8 + .../DolphinQt/Config/Mapping/MappingWindow.h | 1 + .../Config/WiimoteControllersWidget.cpp | 50 +- .../Config/WiimoteControllersWidget.h | 11 +- Source/Core/DolphinQt/DolphinQt.vcxproj | 4 + Source/Core/DolphinQt/MainWindow.cpp | 36 +- Source/Core/DolphinQt/MainWindow.h | 8 +- .../Core/DolphinQt/TAS/BalanceBoardWidget.cpp | 137 +++++ .../Core/DolphinQt/TAS/BalanceBoardWidget.h | 41 ++ Source/Core/DolphinQt/TAS/IRWidget.cpp | 12 +- Source/Core/DolphinQt/TAS/StickWidget.cpp | 10 +- Source/Core/DolphinQt/TAS/TASInputWindow.cpp | 79 ++- Source/Core/DolphinQt/TAS/TASInputWindow.h | 12 + .../Core/DolphinQt/TAS/WiiTASInputWindow.cpp | 408 ++++++++------ Source/Core/DolphinQt/TAS/WiiTASInputWindow.h | 52 +- 42 files changed, 1829 insertions(+), 735 deletions(-) create mode 100644 Source/Core/Core/HW/WiimoteEmu/Extension/BalanceBoard.cpp create mode 100644 Source/Core/Core/HW/WiimoteEmu/Extension/BalanceBoard.h create mode 100644 Source/Core/DolphinQt/Config/Mapping/BalanceBoardGeneral.cpp create mode 100644 Source/Core/DolphinQt/Config/Mapping/BalanceBoardGeneral.h create mode 100644 Source/Core/DolphinQt/TAS/BalanceBoardWidget.cpp create mode 100644 Source/Core/DolphinQt/TAS/BalanceBoardWidget.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index b4437747e8..19b4907bfd 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -304,6 +304,8 @@ add_library(core HW/WiimoteEmu/EmuSubroutines.cpp HW/WiimoteEmu/Encryption.cpp HW/WiimoteEmu/Encryption.h + HW/WiimoteEmu/Extension/BalanceBoard.cpp + HW/WiimoteEmu/Extension/BalanceBoard.h HW/WiimoteEmu/Extension/Classic.cpp HW/WiimoteEmu/Extension/Classic.h HW/WiimoteEmu/Extension/DesiredExtensionState.h diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 5f90f93bae..c4bfe30fae 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -691,7 +691,7 @@ void SetState(Core::System& system, State state, bool report_state_change, // NOTE: GetState() will return State::Paused immediately, even before anything has // stopped (including the CPU). system.GetCPU().SetStepping(true); // Break - Wiimote::Pause(); + WiimoteReal::Pause(); ResetRumble(); #ifdef USE_RETRO_ACHIEVEMENTS AchievementManager::GetInstance().DoIdle(); @@ -700,7 +700,7 @@ void SetState(Core::System& system, State state, bool report_state_change, case State::Running: { system.GetCPU().SetStepping(false); - Wiimote::Resume(); + WiimoteReal::Resume(); break; } default: diff --git a/Source/Core/Core/HW/Wiimote.cpp b/Source/Core/Core/HW/Wiimote.cpp index b8c138091e..0dae6bd460 100644 --- a/Source/Core/Core/HW/Wiimote.cpp +++ b/Source/Core/Core/HW/Wiimote.cpp @@ -18,16 +18,12 @@ #include "Core/IOS/USB/Bluetooth/BTEmu.h" #include "Core/IOS/USB/Bluetooth/WiimoteDevice.h" #include "Core/Movie.h" -#include "Core/NetPlayClient.h" #include "Core/System.h" #include "Core/WiiUtils.h" #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" #include "InputCommon/InputConfig.h" -// Limit the amount of wiimote connect requests, when a button is pressed in disconnected state -static std::array s_last_connect_request_counter; - namespace { static std::array, MAX_BBMOTES> s_wiimote_sources; @@ -60,6 +56,27 @@ void RefreshConfig() OnSourceChanged(i, Config::Get(Config::GetInfoForWiimoteSource(i))); } +void DoWiimoteSlotState(PointerWrap& p, int slot, ControllerEmu::EmulatedController* controller) +{ + const WiimoteSource source = GetSource(slot); + auto state_wiimote_source = u8(source); + p.Do(state_wiimote_source); + + if (WiimoteSource(state_wiimote_source) == WiimoteSource::Emulated) + { + // Sync complete state of emulated wiimotes. + static_cast(controller)->DoState(p); + } + + if (p.IsReadMode()) + { + // If using a real wiimote or the save-state source does not match the current source, + // then force a reconnection on load. + if (source == WiimoteSource::Real || source != WiimoteSource(state_wiimote_source)) + WiimoteCommon::UpdateSource(slot); + } +} + } // namespace namespace WiimoteCommon @@ -80,7 +97,9 @@ HIDWiimote* GetHIDWiimoteSource(unsigned int index) switch (GetSource(index)) { case WiimoteSource::Emulated: - hid_source = static_cast(::Wiimote::GetConfig()->GetController(index)); + hid_source = static_cast( + (index == WIIMOTE_BALANCE_BOARD) ? ::BalanceBoard::GetConfig()->GetController(0) : + ::Wiimote::GetConfig()->GetController(index)); break; case WiimoteSource::Real: @@ -96,10 +115,15 @@ HIDWiimote* GetHIDWiimoteSource(unsigned int index) } // namespace WiimoteCommon -namespace Wiimote +namespace { static InputConfig s_config(WIIMOTE_INI_NAME, _trans("Wii Remote"), "Wiimote", "Wiimote"); +static InputConfig s_bb_config(WIIMOTE_INI_NAME, _trans("Balance Board"), "BalanceBoard", + "BalanceBoard"); +} // namespace +namespace Wiimote +{ InputConfig* GetConfig() { return &s_config; @@ -162,8 +186,10 @@ ControllerEmu::ControlGroup* GetShinkansenGroup(int number, WiimoteEmu::Shinkans void Shutdown() { + s_bb_config.UnregisterHotplugCallback(); s_config.UnregisterHotplugCallback(); + s_bb_config.ClearControllers(); s_config.ClearControllers(); WiimoteReal::Stop(); @@ -179,13 +205,17 @@ void Initialize(InitializeMode init_mode) { if (s_config.ControllersNeedToBeCreated()) { - for (unsigned int i = WIIMOTE_CHAN_0; i < MAX_BBMOTES; ++i) + for (unsigned int i = WIIMOTE_CHAN_0; i < MAX_WIIMOTES; ++i) s_config.CreateController(i); + + s_bb_config.CreateController(WIIMOTE_BALANCE_BOARD); } s_config.RegisterHotplugCallback(); + s_bb_config.RegisterHotplugCallback(); LoadConfig(); + BalanceBoard::LoadConfig(); if (!s_config_callback_id) s_config_callback_id = Config::AddConfigChangedCallback(RefreshConfig); @@ -201,14 +231,15 @@ void Initialize(InitializeMode init_mode) void ResetAllWiimotes() { - for (int i = WIIMOTE_CHAN_0; i < MAX_BBMOTES; ++i) + for (int i = WIIMOTE_CHAN_0; i < MAX_WIIMOTES; ++i) static_cast(s_config.GetController(i))->Reset(); + + static_cast(s_bb_config.GetController(0))->Reset(); } void LoadConfig() { s_config.LoadConfig(); - s_last_connect_request_counter.fill(0); } void GenerateDynamicInputTextures() @@ -216,37 +247,30 @@ void GenerateDynamicInputTextures() s_config.GenerateControllerTextures(); } -void Resume() -{ - WiimoteReal::Resume(); -} - -void Pause() -{ - WiimoteReal::Pause(); -} - void DoState(PointerWrap& p) { - for (int i = 0; i < MAX_BBMOTES; ++i) - { - const WiimoteSource source = GetSource(i); - auto state_wiimote_source = u8(source); - p.Do(state_wiimote_source); + for (int slot = 0; slot < MAX_WIIMOTES; ++slot) + DoWiimoteSlotState(p, slot, s_config.GetController(slot)); - if (WiimoteSource(state_wiimote_source) == WiimoteSource::Emulated) - { - // Sync complete state of emulated wiimotes. - static_cast(s_config.GetController(i))->DoState(p); - } - - if (p.IsReadMode()) - { - // If using a real wiimote or the save-state source does not match the current source, - // then force a reconnection on load. - if (source == WiimoteSource::Real || source != WiimoteSource(state_wiimote_source)) - WiimoteCommon::UpdateSource(i); - } - } + DoWiimoteSlotState(p, WIIMOTE_BALANCE_BOARD, s_bb_config.GetController(0)); } } // namespace Wiimote + +namespace BalanceBoard +{ +InputConfig* GetConfig() +{ + return &s_bb_config; +} + +void LoadConfig() +{ + s_bb_config.LoadConfig(); +} + +ControllerEmu::ControlGroup* GetBalanceBoardGroup(int number, WiimoteEmu::BalanceBoardGroup group) +{ + return static_cast(s_bb_config.GetController(number)) + ->GetBalanceBoardGroup(group); +} +} // namespace BalanceBoard diff --git a/Source/Core/Core/HW/Wiimote.h b/Source/Core/Core/HW/Wiimote.h index f4b34f479b..78d126a927 100644 --- a/Source/Core/Core/HW/Wiimote.h +++ b/Source/Core/Core/HW/Wiimote.h @@ -3,10 +3,6 @@ #pragma once -#include -#include - -#include "Common/Common.h" #include "Common/CommonTypes.h" class InputConfig; @@ -29,9 +25,10 @@ enum class UDrawTabletGroup; enum class DrawsomeTabletGroup; enum class TaTaConGroup; enum class ShinkansenGroup; +enum class BalanceBoardGroup; } // namespace WiimoteEmu -enum +enum : u8 { WIIMOTE_CHAN_0 = 0, WIIMOTE_CHAN_1, @@ -74,15 +71,14 @@ enum class InitializeMode // The Real Wii Remote sends report every ~5ms (200 Hz). constexpr int UPDATE_FREQ = 200; -void Shutdown(); +// Note that these also handle the 5th-slot BalanceBoard, though it has a separate InputConfig. void Initialize(InitializeMode init_mode); +void Shutdown(); void ResetAllWiimotes(); -void LoadConfig(); void GenerateDynamicInputTextures(); -void Resume(); -void Pause(); - void DoState(PointerWrap& p); + +void LoadConfig(); InputConfig* GetConfig(); ControllerEmu::ControlGroup* GetWiimoteGroup(int number, WiimoteEmu::WiimoteGroup group); ControllerEmu::ControlGroup* GetNunchukGroup(int number, WiimoteEmu::NunchukGroup group); @@ -97,6 +93,13 @@ ControllerEmu::ControlGroup* GetTaTaConGroup(int number, WiimoteEmu::TaTaConGrou ControllerEmu::ControlGroup* GetShinkansenGroup(int number, WiimoteEmu::ShinkansenGroup group); } // namespace Wiimote +namespace BalanceBoard +{ +InputConfig* GetConfig(); +void LoadConfig(); +ControllerEmu::ControlGroup* GetBalanceBoardGroup(int number, WiimoteEmu::BalanceBoardGroup group); +} // namespace BalanceBoard + namespace WiimoteReal { void Initialize(::Wiimote::InitializeMode init_mode); @@ -105,5 +108,4 @@ void Shutdown(); void Resume(); void Pause(); void Refresh(); - } // namespace WiimoteReal diff --git a/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h b/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h index eb1d4cdcf2..0abedc47d7 100644 --- a/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h +++ b/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h @@ -215,7 +215,8 @@ struct InputReportStatus private: static constexpr auto BATTERY_MAX = std::numeric_limits::max(); - // Linear fit of battery level mid-point for charge bars in home menu. + // Linear fit of battery level mid-point for Wii Remote charge bars in home menu. + // Note that Balance Board battery values differ (probably from 2xAA vs 4xAA). static constexpr float BATTERY_LEVEL_M = 2.46f; static constexpr float BATTERY_LEVEL_B = -0.013f; }; diff --git a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp index 1dcdb77952..8331e2e11b 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp @@ -11,15 +11,15 @@ #include "Common/MathUtil.h" #include "Common/Matrix.h" -#include "Core/HW/WiimoteCommon/WiimoteReport.h" - namespace WiimoteEmu { +CameraLogic::CameraLogic(const WiimoteCommon::InputReportStatus* status) : m_wiimote_status{*status} +{ +} + void CameraLogic::Reset() { m_reg_data = {}; - - m_is_enabled = false; } void CameraLogic::DoState(PointerWrap& p) @@ -34,7 +34,7 @@ int CameraLogic::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) if (I2C_ADDR != slave_addr) return 0; - if (!m_is_enabled) + if (!IsEnabled()) return 0; return RawRead(&m_reg_data, addr, count, data_out); @@ -45,7 +45,7 @@ int CameraLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) if (I2C_ADDR != slave_addr) return 0; - if (!m_is_enabled) + if (!IsEnabled()) return 0; return RawWrite(&m_reg_data, addr, count, data_in); @@ -179,9 +179,9 @@ void CameraLogic::Update(const std::array& camera_point } } -void CameraLogic::SetEnabled(bool is_enabled) +bool CameraLogic::IsEnabled() const { - m_is_enabled = is_enabled; + return m_wiimote_status.ir; } } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/Camera.h b/Source/Core/Core/HW/WiimoteEmu/Camera.h index 23e21cc84e..fdd7c11534 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Camera.h +++ b/Source/Core/Core/HW/WiimoteEmu/Camera.h @@ -5,9 +5,10 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "Core/HW/WiimoteEmu/Dynamics.h" +#include "Common/Matrix.h" + +#include "Core/HW/WiimoteCommon/WiimoteReport.h" #include "Core/HW/WiimoteEmu/I2CBus.h" -#include "InputCommon/ControllerEmu/ControlGroup/Cursor.h" namespace Common { @@ -104,6 +105,8 @@ static_assert(sizeof(IRFull) == 9, "Wrong size"); class CameraLogic : public I2CSlave { public: + CameraLogic(const WiimoteCommon::InputReportStatus* status); + // OEM sensor bar distance between LED clusters in meters. static constexpr float SENSOR_BAR_LED_SEPARATION = 0.2f; @@ -136,7 +139,6 @@ public: static std::array GetCameraPoints(const Common::Matrix44& transform, Common::Vec2 field_of_view); void Update(const std::array& camera_points); - void SetEnabled(bool is_enabled); static constexpr u8 I2C_ADDR = 0x58; static constexpr int CAMERA_DATA_BYTES = 36; @@ -176,13 +178,15 @@ public: static const u8 REPORT_DATA_OFFSET = offsetof(Register, camera_data); private: + // When disabled the camera does not respond on the bus. + // Change is triggered by wiimote report 0x13. + bool IsEnabled() const; + int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override; int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override; Register m_reg_data{}; - // When disabled the camera does not respond on the bus. - // Change is triggered by wiimote report 0x13. - bool m_is_enabled = false; + const WiimoteCommon::InputReportStatus& m_wiimote_status; }; } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp index b9e68b2594..46c97080ba 100644 --- a/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.cpp @@ -3,6 +3,7 @@ #include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" +#include #include #include #include @@ -106,7 +107,6 @@ SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state) std::visit( [&s](const auto& arg) { using T = std::decay_t; - static_assert(sizeof(arg) <= 6); Common::BitCastPtr(&s.data[s.length]) = arg; s.length += sizeof(arg); }, @@ -170,6 +170,10 @@ bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimote return false; } + // Contriving data with MPlus + BBoard could create an oversided length. + if (expected_size > serialized.data.size()) + return false; + size_t pos = 1; if (has_buttons) diff --git a/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.h b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.h index 6ef25675f3..d9d3bcb968 100644 --- a/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.h +++ b/Source/Core/Core/HW/WiimoteEmu/DesiredWiimoteState.h @@ -35,7 +35,10 @@ struct DesiredWiimoteState struct SerializedWiimoteState { u8 length; - std::array data; // 18 bytes Wiimote, 6 bytes MotionPlus, 6 bytes Extension + + // 18 bytes Wiimote, 6 bytes MotionPlus, 6 bytes Extension + // Note that BalanceBoard Ext data is 8 bytes but it doesn't need Accel/IR/MotionPlus data. + std::array data; }; SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state); diff --git a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp index 46a4c417d8..02bd12b511 100644 --- a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp @@ -14,15 +14,19 @@ #include "Common/MsgHandler.h" #include "Common/Swap.h" -#include "Core/Core.h" #include "Core/HW/Wiimote.h" #include "Core/HW/WiimoteCommon/WiimoteHid.h" +#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h" + namespace WiimoteEmu { using namespace WiimoteCommon; -void Wiimote::HandleReportMode(const OutputReportMode& dr) +// Used only for error generation: +static constexpr u8 EEPROM_I2C_ADDR = 0x50; + +void WiimoteBase::HandleReportMode(const OutputReportMode& dr) { if (!DataReportBuilder::IsValidMode(dr.mode)) { @@ -44,7 +48,8 @@ void Wiimote::HandleReportMode(const OutputReportMode& dr) // Tests that we have enough bytes for the report before we run the handler. template -void Wiimote::InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size) +void WiimoteBase::InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, + u32 size) { if (size < sizeof(T)) { @@ -56,17 +61,17 @@ void Wiimote::InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneri (this->*handler)(Common::BitCastPtr(&rpt.data[0])); } -void Wiimote::EventLinked() +void WiimoteBase::EventLinked() { Reset(); } -void Wiimote::EventUnlinked() +void WiimoteBase::EventUnlinked() { Reset(); } -void Wiimote::InterruptDataOutput(const u8* data, u32 size) +void WiimoteBase::InterruptDataOutput(const u8* data, u32 size) { if (size == 0) { @@ -85,7 +90,7 @@ void Wiimote::InterruptDataOutput(const u8* data, u32 size) // WiiBrew: // In every single Output Report, bit 0 (0x01) of the first byte controls the Rumble feature. - InvokeHandler(&Wiimote::HandleReportRumble, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleReportRumble, rpt, rpt_size); switch (rpt.rpt_id) { @@ -93,34 +98,34 @@ void Wiimote::InterruptDataOutput(const u8* data, u32 size) // This is handled above. break; case OutputReportID::LED: - InvokeHandler(&Wiimote::HandleReportLeds, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleReportLeds, rpt, rpt_size); break; case OutputReportID::ReportMode: - InvokeHandler(&Wiimote::HandleReportMode, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleReportMode, rpt, rpt_size); break; case OutputReportID::IRLogicEnable: - InvokeHandler(&Wiimote::HandleIRLogicEnable, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleIRLogicEnable, rpt, rpt_size); break; case OutputReportID::SpeakerEnable: - InvokeHandler(&Wiimote::HandleSpeakerEnable, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleSpeakerEnable, rpt, rpt_size); break; case OutputReportID::RequestStatus: - InvokeHandler(&Wiimote::HandleRequestStatus, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleRequestStatus, rpt, rpt_size); break; case OutputReportID::WriteData: - InvokeHandler(&Wiimote::HandleWriteData, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleWriteData, rpt, rpt_size); break; case OutputReportID::ReadData: - InvokeHandler(&Wiimote::HandleReadData, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleReadData, rpt, rpt_size); break; case OutputReportID::SpeakerData: - InvokeHandler(&Wiimote::HandleSpeakerData, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleSpeakerData, rpt, rpt_size); break; case OutputReportID::SpeakerMute: - InvokeHandler(&Wiimote::HandleSpeakerMute, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleSpeakerMute, rpt, rpt_size); break; case OutputReportID::IRLogicEnable2: - InvokeHandler(&Wiimote::HandleIRLogicEnable2, rpt, rpt_size); + InvokeHandler(&WiimoteBase::HandleIRLogicEnable2, rpt, rpt_size); break; default: PanicAlertFmt("HidOutputReport: Unknown report ID {:#04x}", static_cast(rpt.rpt_id)); @@ -128,7 +133,7 @@ void Wiimote::InterruptDataOutput(const u8* data, u32 size) } } -void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code) +void WiimoteBase::SendAck(OutputReportID rpt_id, ErrorCode error_code) { TypedInputData rpt(InputReportID::Ack); auto& ack = rpt.payload; @@ -143,19 +148,11 @@ void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code) void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number, bool desired_motion_plus) { - if (WIIMOTE_BALANCE_BOARD == m_index) - { - // Prevent M+ or anything else silly from being attached to a balance board. - // In the future if we support an emulated balance board we can force the BB "extension" here. - return; - } - // FYI: AttachExtension also connects devices to the i2c bus if (m_is_motion_plus_attached && !desired_motion_plus) { - INFO_LOG_FMT(WIIMOTE, "Detaching Motion Plus (Wiimote {} in slot {})", m_index, - m_bt_device_index); + INFO_LOG_FMT(WIIMOTE, "Detaching Motion Plus ({} in slot {})", GetName(), m_bt_device_index); // M+ is attached and it's not wanted, so remove it. m_extension_port.AttachExtension(GetNoneExtension()); @@ -181,8 +178,7 @@ void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number, } else { - INFO_LOG_FMT(WIIMOTE, "Attaching Motion Plus (Wiimote {} in slot {})", m_index, - m_bt_device_index); + INFO_LOG_FMT(WIIMOTE, "Attaching Motion Plus ({} in slot {})", GetName(), m_bt_device_index); // No extension attached so attach M+. m_is_motion_plus_attached = true; @@ -198,8 +194,7 @@ void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number, // A different extension is wanted (either by user or by the M+ logic above) if (GetActiveExtensionNumber() != ExtensionNumber::NONE) { - INFO_LOG_FMT(WIIMOTE, "Detaching Extension (Wiimote {} in slot {})", m_index, - m_bt_device_index); + INFO_LOG_FMT(WIIMOTE, "Detaching Extension ({} in slot {})", GetName(), m_bt_device_index); // First we must detach the current extension. // The next call will change to the new extension if needed. @@ -207,8 +202,8 @@ void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number, } else { - INFO_LOG_FMT(WIIMOTE, "Switching to Extension {} (Wiimote {} in slot {})", - Common::ToUnderlying(desired_extension_number), m_index, m_bt_device_index); + INFO_LOG_FMT(WIIMOTE, "Switching to Extension {} ({} in slot {})", + Common::ToUnderlying(desired_extension_number), GetName(), m_bt_device_index); m_active_extension = desired_extension_number; } @@ -228,29 +223,19 @@ void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number, } } -void Wiimote::HandleRequestStatus(const OutputReportRequestStatus&) +void WiimoteBase::HandleRequestStatus(const OutputReportRequestStatus&) { // FYI: buttons are updated in Update() for determinism // Update status struct m_status.extension = m_extension_port.IsDeviceConnected(); - m_status.SetEstimatedCharge(m_battery_setting.GetValue() / ciface::BATTERY_INPUT_MAX_VALUE); - - if (Core::WantsDeterminism()) - { - // One less thing to break determinism: - m_status.SetEstimatedCharge(1.f); - } - - // Less than 0x20 triggers the low-battery flag: - m_status.battery_low = m_status.battery < 0x20; TypedInputData rpt(InputReportID::Status); rpt.payload = m_status; InterruptDataInputCallback(rpt.GetData(), rpt.GetSize()); } -void Wiimote::HandleWriteData(const OutputReportWriteData& wd) +void WiimoteBase::HandleWriteData(const OutputReportWriteData& wd) { if (m_read_request.size) { @@ -329,7 +314,7 @@ void Wiimote::HandleReportRumble(const WiimoteCommon::OutputReportRumble& rpt) // FYI: A real wiimote never seems to ACK a rumble report: } -void Wiimote::HandleReportLeds(const WiimoteCommon::OutputReportLeds& rpt) +void WiimoteBase::HandleReportLeds(const WiimoteCommon::OutputReportLeds& rpt) { m_status.leds = rpt.leds; @@ -337,7 +322,7 @@ void Wiimote::HandleReportLeds(const WiimoteCommon::OutputReportLeds& rpt) SendAck(OutputReportID::LED, ErrorCode::Success); } -void Wiimote::HandleIRLogicEnable2(const WiimoteCommon::OutputReportEnableFeature& rpt) +void WiimoteBase::HandleIRLogicEnable2(const WiimoteCommon::OutputReportEnableFeature& rpt) { // FYI: We ignore this and update camera data regardless. @@ -345,7 +330,7 @@ void Wiimote::HandleIRLogicEnable2(const WiimoteCommon::OutputReportEnableFeatur SendAck(OutputReportID::IRLogicEnable2, ErrorCode::Success); } -void Wiimote::HandleIRLogicEnable(const WiimoteCommon::OutputReportEnableFeature& rpt) +void WiimoteBase::HandleIRLogicEnable(const WiimoteCommon::OutputReportEnableFeature& rpt) { // Note: Wiibrew currently refers to this report (0x13) as "Enable IR Pixel Clock" // however my testing shows this affects the relevant status bit and whether or not @@ -353,8 +338,6 @@ void Wiimote::HandleIRLogicEnable(const WiimoteCommon::OutputReportEnableFeature m_status.ir = rpt.enable; - m_camera_logic.SetEnabled(m_status.ir); - if (rpt.ack) SendAck(OutputReportID::IRLogicEnable, ErrorCode::Success); } @@ -367,7 +350,7 @@ void Wiimote::HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature& SendAck(OutputReportID::SpeakerMute, ErrorCode::Success); } -void Wiimote::HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature& rpt) +void WiimoteBase::HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature& rpt) { m_status.speaker = rpt.enable; @@ -399,7 +382,7 @@ void Wiimote::HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData& rp // More investigation is needed. } -void Wiimote::HandleReadData(const OutputReportReadData& rd) +void WiimoteBase::HandleReadData(const OutputReportReadData& rd) { if (m_read_request.size) { @@ -430,7 +413,7 @@ void Wiimote::HandleReadData(const OutputReportReadData& rd) // FYI: No "ACK" is sent under normal situations. } -bool Wiimote::ProcessReadDataRequest() +auto WiimoteBase::ProcessReadDataRequest() -> UpdateProgress { // Limit the amt to 16 bytes // AyuanX: the MTU is 640B though... what a waste! @@ -439,7 +422,7 @@ bool Wiimote::ProcessReadDataRequest() if (0 == bytes_to_read) { // No active request: - return false; + return UpdateProgress::Continue; } TypedInputData rpt(InputReportID::ReadDataReply); @@ -528,32 +511,29 @@ bool Wiimote::ProcessReadDataRequest() InterruptDataInputCallback(rpt.GetData(), rpt.GetSize()); - return true; + return UpdateProgress::DoNotContinue; } -void Wiimote::DoState(PointerWrap& p) +void WiimoteBase::DoState(PointerWrap& p) { // No need to sync. Index will not change. // p.Do(m_index); - // No need to sync. This is not wiimote state. - // p.Do(m_sensor_bar_on_top); - p.Do(m_reporting_mode); p.Do(m_reporting_continuous); - p.Do(m_speaker_mute); - p.Do(m_status); p.Do(m_eeprom); p.Do(m_read_request); - // Sub-devices: - m_speaker_logic.DoState(p); - m_camera_logic.DoState(p); + p.DoMarker("WiimoteBase"); +} - if (p.IsReadMode()) - m_camera_logic.SetEnabled(m_status.ir); +void Wiimote::DoState(PointerWrap& p) +{ + WiimoteBase::DoState(p); + + p.Do(m_speaker_mute); p.Do(m_is_motion_plus_attached); p.Do(m_active_extension); @@ -569,6 +549,10 @@ void Wiimote::DoState(PointerWrap& p) if (m_active_extension != ExtensionNumber::NONE) GetActiveExtension()->DoState(p); + // Sub-devices: + m_speaker_logic.DoState(p); + m_camera_logic.DoState(p); + // Dynamics p.Do(m_swing_state); p.Do(m_tilt_state); @@ -581,9 +565,35 @@ void Wiimote::DoState(PointerWrap& p) p.DoMarker("Wiimote"); } -ExtensionNumber Wiimote::GetActiveExtensionNumber() const +void BalanceBoard::DoState(PointerWrap& p) { - return m_active_extension; + WiimoteBase::DoState(p); + m_ext.DoState(p); + p.DoMarker("BalanceBoard"); +} + +void BalanceBoard::HandleReportRumble(const WiimoteCommon::OutputReportRumble&) +{ + // Behaves like a Wii Remote (no ACK). +} + +void BalanceBoard::HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature& rpt) +{ + // ACKs like Wii Remote. + if (rpt.ack) + SendAck(OutputReportID::SpeakerMute, ErrorCode::Success); +} + +void BalanceBoard::HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&) +{ + // Behaves like a Wii Remote (no ACK). +} + +ControllerEmu::SubscribableSettingValue& Wiimote::GetAttachmentSetting() +{ + return static_cast( + GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments)) + ->GetAttachmentSetting(); } ControllerEmu::SubscribableSettingValue& Wiimote::GetMotionPlusSetting() diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/BalanceBoard.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/BalanceBoard.cpp new file mode 100644 index 0000000000..ce38e834cd --- /dev/null +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/BalanceBoard.cpp @@ -0,0 +1,199 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h" + +#include +#include + +#include "Common/Common.h" +#include "Common/CommonTypes.h" +#include "Common/Hash.h" +#include "Common/Swap.h" + +#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" +#include "Core/HW/WiimoteEmu/WiimoteEmu.h" + +#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h" + +namespace WiimoteEmu +{ +constexpr std::array balance_board_id{{0x00, 0x00, 0xa4, 0x20, 0x04, 0x02}}; + +BalanceBoardExt::BalanceBoardExt(BalanceBoard* owner) + : Extension1stParty("BalanceBoard", _trans("Balance Board")), m_owner(owner) +{ +} + +// Use the same calibration data for all sensors. +// Wii Fit internally converts to grams, but using grams for the actual values leads to +// overflowing values, and also underflowing values when a sensor gets negative if balance is +// extremely tilted. Actual balance boards tend to have a sensitivity of about 10 grams. + +// Real board values vary greatly but these nice values are very near those of a real board. +static constexpr u16 KG17_RANGE = 1700; +static constexpr u16 CALIBRATED_0_KG = 10000; +static constexpr u16 CALIBRATED_17_KG = CALIBRATED_0_KG + KG17_RANGE; +static constexpr u16 CALIBRATED_34_KG = CALIBRATED_17_KG + KG17_RANGE; + +// Chosen arbitrarily from the value for pokechu22's board. As long as the calibration and +// actual temperatures match, the value here doesn't matter. +static constexpr u8 REFERENCE_TEMPERATURE = 0x19; + +void BalanceBoardExt::BuildDesiredExtensionState(DesiredExtensionState* target_state) +{ + ControllerEmu::AnalogStick::StateData balance_state = m_owner->m_balance->GetState(); + + // TODO: make this all less ugly and work better.. + + const double half_body_weight = m_owner->m_weight_setting.GetValue() * 0.5; + + const double foot_l = m_owner->m_left_foot->controls[0]->GetState() * 2; + const double foot_r = m_owner->m_right_foot->controls[0]->GetState() * 2; + + const double down_l = std::abs(1 - foot_l); + const double down_r = std::abs(1 - foot_r); + + balance_state.x += (1 - down_l) - (1 - down_r); + + // What percent of weight user is putting on each foot. + const double amt_l = std::clamp(1 - balance_state.x, 0.0, down_l * 2); + const double amt_r = std::clamp(1 + balance_state.x, 0.0, down_r * 2); + + // How much the foot is on the board. + const double on_board_l = std::max(1 - foot_l, 0.0); + const double on_board_r = std::max(1 - foot_r, 0.0); + + // What percent of weight each foot is putting on the board. + const double weight_l = amt_l * on_board_l; + const double weight_r = amt_r * on_board_r; + + INFO_LOG_FMT(WIIMOTE, "f: {:.2f} {:.2f} b: {:.2f} {:.2f} w: {:.2f} {:.2f}", amt_l, amt_r, + on_board_l, on_board_r, weight_l, weight_r); + + // Distance between the feet on the bottom of the board. + constexpr auto SENSOR_DISTANCE = Common::DVec2{43, 24}; + + const auto balance_scale = Common::DVec2{m_owner->m_foot_separation_setting.GetValue(), + m_owner->m_foot_length_setting.GetValue()} / + SENSOR_DISTANCE; + + const double weight = (weight_l + weight_r); + balance_state.x = weight ? (weight_r - weight_l) / weight : 0.0; + + auto [weight_tr, weight_br, weight_tl, weight_bl] = + CenterOfBalanceToSensors(balance_state * balance_scale, weight * half_body_weight).data; + + if (auto& func = m_input_override_function) + { + weight_tr = func(BALANCE_GROUP, SENSOR_TR, weight_tr).value_or(weight_tr); + weight_br = func(BALANCE_GROUP, SENSOR_BR, weight_br).value_or(weight_br); + weight_tl = func(BALANCE_GROUP, SENSOR_TL, weight_tl).value_or(weight_tl); + weight_bl = func(BALANCE_GROUP, SENSOR_BL, weight_bl).value_or(weight_bl); + } + + DataFormat bb_data = {}; + + bb_data.sensor_tr = Common::swap16(ConvertToSensorWeight(weight_tr)); + bb_data.sensor_br = Common::swap16(ConvertToSensorWeight(weight_br)); + bb_data.sensor_tl = Common::swap16(ConvertToSensorWeight(weight_tl)); + bb_data.sensor_bl = Common::swap16(ConvertToSensorWeight(weight_bl)); + + target_state->data = bb_data; +} + +void BalanceBoardExt::Update(const DesiredExtensionState& target_state) +{ + DefaultExtensionUpdate(&m_reg, target_state); + + m_reg.controller_data[0x8] = REFERENCE_TEMPERATURE; + m_reg.controller_data[0x9] = 0x00; + + // FYI: Real EXT battery byte doesn't exactly match status report battery byte. + // e.g. Seen: EXT:0x9e and Status:0xc6 + // I'm guessing they just have separate ADCs. + m_reg.controller_data[0xa] = m_owner->m_status.battery; +} + +void BalanceBoardExt::Reset() +{ + EncryptedExtension::Reset(); + + m_reg.identifier = balance_board_id; + + struct CalibrationData + { + u8 always_0x01 = 0x01; + u8 battery = REFERENCE_BATTERY; + u8 always_0x00[2] = {}; + + using SensorValue = Common::BigEndianValue; + + // Each array is ordered: TR, BR, TL, BL + std::array kg0; + std::array kg17; + std::array kg34; + + // Note that 4 checksum bytes immediately follow. + }; + static_assert(sizeof(CalibrationData) == 0x1c, "Wrong size"); + + CalibrationData cal_data{}; + cal_data.kg0.fill(CalibrationData::SensorValue{CALIBRATED_0_KG}); + cal_data.kg17.fill(CalibrationData::SensorValue{CALIBRATED_17_KG}); + cal_data.kg34.fill(CalibrationData::SensorValue{CALIBRATED_34_KG}); + + Common::BitCastPtr(m_reg.calibration_long.data()) = cal_data; + m_reg.calibration2 = {REFERENCE_TEMPERATURE, 0x01}; + + ComputeCalibrationChecksum(); +} + +u16 BalanceBoardExt::ConvertToSensorWeight(double weight_in_kilos) +{ + return ControllerEmu::MapFloat((weight_in_kilos - 17.0) / 17.0, CALIBRATED_17_KG, CALIBRATED_0_KG, + CALIBRATED_34_KG); +} + +double BalanceBoardExt::ConvertToKilograms(u16 sensor_weight) +{ + const auto result = ControllerEmu::MapToFloat(sensor_weight, CALIBRATED_17_KG, + CALIBRATED_0_KG, CALIBRATED_34_KG); + return result * 17.0 + 17.0; +} + +void BalanceBoardExt::ComputeCalibrationChecksum() +{ + u32 crc = Common::StartCRC32(); + // Skip the first 4 bytes and the last 4 bytes (the CRC itself) + crc = Common::UpdateCRC32(crc, &m_reg.calibration_long[4], 0x18); + // Hash 2 of the bytes skipped earlier + crc = Common::UpdateCRC32(crc, m_reg.calibration_long.data(), 2); + crc = Common::UpdateCRC32(crc, m_reg.calibration2.data(), 2); + + const Common::BigEndianValue result{crc}; + Common::BitCastPtr>(&m_reg.calibration_long[0x1c]) = result; +} + +Common::DVec2 BalanceBoardExt::SensorsToCenterOfBalance(Common::DVec4 sensors) +{ + const double right = sensors.data[0] + sensors.data[1]; + const double left = sensors.data[2] + sensors.data[3]; + + const double total = right + left; + if (!total) + return Common::DVec2{}; + + const double top = sensors.data[0] + sensors.data[2]; + const double bottom = sensors.data[1] + sensors.data[3]; + return Common::DVec2(right - left, top - bottom) / total; +} + +Common::DVec4 BalanceBoardExt::CenterOfBalanceToSensors(Common::DVec2 balance, double total_weight) +{ + return Common::DVec4{(1 + balance.x) * (1 + balance.y), (1 + balance.x) * (1 - balance.y), + (1 - balance.x) * (1 + balance.y), (1 - balance.x) * (1 - balance.y)} * + (total_weight * 0.25); +} + +} // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/BalanceBoard.h b/Source/Core/Core/HW/WiimoteEmu/Extension/BalanceBoard.h new file mode 100644 index 0000000000..ac55fe138e --- /dev/null +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/BalanceBoard.h @@ -0,0 +1,64 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/Matrix.h" +#include "Core/HW/WiimoteEmu/Extension/Extension.h" + +namespace WiimoteEmu +{ +class BalanceBoard; + +// NOTE: Although the balance board is implemented as an extension (which matches how the actual +// balance board works), actual controls are not registered in the same way as other extensions; +// WiimoteEmu::BalanceBoard has all of the controls and some are also used by the extension. +class BalanceBoardExt : public Extension1stParty +{ +public: + struct DataFormat + { + // top-right, bottom-right, top-left, bottom-left + u16 sensor_tr; + u16 sensor_br; + u16 sensor_tl; + u16 sensor_bl; + // Note: temperature, padding, and battery bytes are not included. + // temperature change is not exposed, and arguably not important. + // battery level is pulled from the wii remote state. + }; + static_assert(sizeof(DataFormat) == 8, "Wrong size"); + + using DesiredState = DataFormat; + + static constexpr const char* BALANCE_GROUP = "Balance"; + + static constexpr const char* SENSOR_TR = "TR"; + static constexpr const char* SENSOR_BR = "BR"; + static constexpr const char* SENSOR_TL = "TL"; + static constexpr const char* SENSOR_BL = "BL"; + + BalanceBoardExt(BalanceBoard* owner); + + // WiiBrew: "always 0x69" + static constexpr u8 REFERENCE_BATTERY = 0x69; + + static constexpr float DEFAULT_WEIGHT = 60; + + void BuildDesiredExtensionState(DesiredExtensionState* target_state) override; + void Update(const DesiredExtensionState& target_state) override; + void Reset() override; + + // Vec4 ordered: top-right, bottom-right, top-left, bottom-left + static Common::DVec2 SensorsToCenterOfBalance(Common::DVec4 sensors); + static Common::DVec4 CenterOfBalanceToSensors(Common::DVec2 balance, double total_weight); + + static u16 ConvertToSensorWeight(double weight_in_kilos); + static double ConvertToKilograms(u16 sensor_weight); + +private: + void ComputeCalibrationChecksum(); + + const BalanceBoard* m_owner; +}; +} // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h b/Source/Core/Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h index f2d100bc48..6d4f3a597c 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h @@ -7,6 +7,7 @@ #include "Common/BitUtils.h" +#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h" #include "Core/HW/WiimoteEmu/Extension/Classic.h" #include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h" #include "Core/HW/WiimoteEmu/Extension/Drums.h" @@ -52,7 +53,8 @@ public: ExtNumTypePair, ExtNumTypePair, ExtNumTypePair, - ExtNumTypePair>::type; + ExtNumTypePair, + ExtNumTypePair>::type; ExtensionData data = std::monostate{}; }; diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h index e799853774..eb0f173e7f 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h @@ -60,6 +60,8 @@ private: class EncryptedExtension : public Extension { public: + void DoState(PointerWrap& p) override; + static constexpr u8 I2C_ADDR = 0x52; static constexpr int CONTROLLER_DATA_BYTES = 21; @@ -76,12 +78,19 @@ public: u8 unknown2[11]; // address 0x20 - std::array calibration; - u8 unknown3[0x10]; + union + { + std::array calibration; + std::array calibration_long; + }; // address 0x40 std::array encryption_key_data; - u8 unknown4[0xA0]; + u8 unknown3[0x10]; + + // Address 0x60 + std::array calibration2; + u8 unknown4[0x8e]; // address 0xF0 u8 encryption; @@ -99,7 +108,6 @@ protected: Register m_reg = {}; void Reset() override; - void DoState(PointerWrap& p) override; virtual void UpdateEncryptionKey() = 0; diff --git a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h index 86b1fd31f9..43b4aca6d1 100644 --- a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h +++ b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h @@ -23,6 +23,8 @@ enum ExtensionNumber : u8 TATACON, SHINKANSEN, + BALANCE_BOARD, + MAX }; diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index 265d39a4d7..9ba4e53cc7 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -25,6 +25,7 @@ #include "Core/HW/WiimoteCommon/WiimoteConstants.h" #include "Core/HW/WiimoteCommon/WiimoteHid.h" #include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" +#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h" #include "Core/HW/WiimoteEmu/Extension/Classic.h" #include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h" #include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h" @@ -36,6 +37,7 @@ #include "Core/HW/WiimoteEmu/Extension/Turntable.h" #include "Core/HW/WiimoteEmu/Extension/UDrawTablet.h" +#include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h" #include "InputCommon/ControllerEmu/ControlGroup/Attachments.h" #include "InputCommon/ControllerEmu/ControlGroup/Buttons.h" #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" @@ -62,18 +64,21 @@ static const u16 dpad_bitmasks[] = {Wiimote::PAD_UP, Wiimote::PAD_DOWN, Wiimote: static const u16 dpad_sideways_bitmasks[] = {Wiimote::PAD_RIGHT, Wiimote::PAD_LEFT, Wiimote::PAD_UP, Wiimote::PAD_DOWN}; -void Wiimote::Reset() +static const u16 bboard_bitmasks[] = {BalanceBoard::BUTTON_POWER}; + +static constexpr u16 IR_LOW_X = 0x7F; +static constexpr u16 IR_LOW_Y = 0x5D; +static constexpr u16 IR_HIGH_X = 0x380; +static constexpr u16 IR_HIGH_Y = 0x2A2; + +void WiimoteBase::Reset() { const bool want_determinism = Core::WantsDeterminism(); - SetRumble(false); - // Wiimote starts in non-continuous CORE mode: m_reporting_mode = InputReportID::ReportCore; m_reporting_continuous = false; - m_speaker_mute = false; - // EEPROM // TODO: This feels sketchy, this needs to properly handle the case where the load and the write @@ -103,69 +108,38 @@ void Wiimote::Reset() } else { - // Load some default data. - - // IR calibration: - std::array ir_calibration = { - // Point 1 - IR_LOW_X & 0xFF, - IR_LOW_Y & 0xFF, - // Mix - ((IR_LOW_Y & 0x300) >> 2) | ((IR_LOW_X & 0x300) >> 4) | ((IR_LOW_Y & 0x300) >> 6) | - ((IR_HIGH_X & 0x300) >> 8), - // Point 2 - IR_HIGH_X & 0xFF, - IR_LOW_Y & 0xFF, - // Point 3 - IR_HIGH_X & 0xFF, - IR_HIGH_Y & 0xFF, - // Mix - ((IR_HIGH_Y & 0x300) >> 2) | ((IR_HIGH_X & 0x300) >> 4) | ((IR_HIGH_Y & 0x300) >> 6) | - ((IR_LOW_X & 0x300) >> 8), - // Point 4 - IR_LOW_X & 0xFF, - IR_HIGH_Y & 0xFF, - // Checksum - 0x00, - }; - UpdateCalibrationDataChecksum(ir_calibration, 1); - m_eeprom.ir_calibration_1 = ir_calibration; - m_eeprom.ir_calibration_2 = ir_calibration; - - // Accel calibration: - // Last byte is a checksum. - std::array accel_calibration = { - ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_ZERO_G, 0, ACCEL_ONE_G, ACCEL_ONE_G, ACCEL_ONE_G, 0, 0, 0, - }; - UpdateCalibrationDataChecksum(accel_calibration, 1); - m_eeprom.accel_calibration_1 = accel_calibration; - m_eeprom.accel_calibration_2 = accel_calibration; - - // TODO: Is this needed? - // Data of unknown purpose: - constexpr std::array EEPROM_DATA_16D0 = { - 0x00, 0x00, 0x00, 0xFF, 0x11, 0xEE, 0x00, 0x00, 0x33, 0xCC, 0x44, 0xBB, - 0x00, 0x00, 0x66, 0x99, 0x77, 0x88, 0x00, 0x00, 0x2B, 0x01, 0xE8, 0x13}; - m_eeprom.unk_2 = EEPROM_DATA_16D0; - - std::string mii_file = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/mii.bin"; - if (File::Exists(mii_file)) - { - // Import from the existing mii.bin file, if present - std::ifstream file; - File::OpenFStream(file, mii_file, std::ios::binary | std::ios::in); - file.read(reinterpret_cast(m_eeprom.mii_data_1.data()), m_eeprom.mii_data_1.size()); - m_eeprom.mii_data_2 = m_eeprom.mii_data_1; - file.close(); - } + LoadDefaultEeprom(); } m_read_request = {}; // Initialize i2c bus: m_i2c_bus.Reset(); - m_i2c_bus.AddSlave(&m_speaker_logic); - m_i2c_bus.AddSlave(&m_camera_logic); + + m_status = {}; + + // A real wii remote does not normally send a status report on connection. + // But if an extension is already attached it does send one. + // Clearing this initially will simulate that on the first update cycle. + m_status.extension = 0; +} + +u8 WiimoteBase::GetWiimoteDeviceIndex() const +{ + return m_bt_device_index; +} + +void WiimoteBase::SetWiimoteDeviceIndex(u8 index) +{ + m_bt_device_index = index; +} + +void Wiimote::Reset() +{ + const bool want_determinism = Core::WantsDeterminism(); + + SetRumble(false); + m_speaker_mute = false; // Reset extension connections to NONE: m_is_motion_plus_attached = false; @@ -181,17 +155,16 @@ void Wiimote::Reset() m_motion_plus_setting.GetValue()); } + WiimoteBase::Reset(); + + // Initialize i2c bus: + m_i2c_bus.AddSlave(&m_speaker_logic); + m_i2c_bus.AddSlave(&m_camera_logic); + // Reset sub-devices. m_speaker_logic.Reset(); m_camera_logic.Reset(); - m_status = {}; - - // A real wii remote does not normally send a status report on connection. - // But if an extension is already attached it does send one. - // Clearing this initially will simulate that on the first update cycle. - m_status.extension = 0; - // Dynamics: m_swing_state = {}; m_tilt_state = {}; @@ -201,7 +174,86 @@ void Wiimote::Reset() m_imu_cursor_state = {}; } -Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(index) +void Wiimote::LoadDefaultEeprom() +{ + // Load some default data. + + // IR calibration: + std::array ir_calibration = { + // Point 1 + IR_LOW_X & 0xFF, + IR_LOW_Y & 0xFF, + // Mix + ((IR_LOW_Y & 0x300) >> 2) | ((IR_LOW_X & 0x300) >> 4) | ((IR_LOW_Y & 0x300) >> 6) | + ((IR_HIGH_X & 0x300) >> 8), + // Point 2 + IR_HIGH_X & 0xFF, + IR_LOW_Y & 0xFF, + // Point 3 + IR_HIGH_X & 0xFF, + IR_HIGH_Y & 0xFF, + // Mix + ((IR_HIGH_Y & 0x300) >> 2) | ((IR_HIGH_X & 0x300) >> 4) | ((IR_HIGH_Y & 0x300) >> 6) | + ((IR_LOW_X & 0x300) >> 8), + // Point 4 + IR_LOW_X & 0xFF, + IR_HIGH_Y & 0xFF, + // Checksum + 0x00, + }; + UpdateCalibrationDataChecksum(ir_calibration, 1); + m_eeprom.ir_calibration_1 = ir_calibration; + m_eeprom.ir_calibration_2 = ir_calibration; + + // Accel calibration: + // Last byte is a checksum. + std::array accel_calibration = { + ACCEL_ZERO_G, ACCEL_ZERO_G, ACCEL_ZERO_G, 0, ACCEL_ONE_G, ACCEL_ONE_G, ACCEL_ONE_G, 0, 0, 0, + }; + UpdateCalibrationDataChecksum(accel_calibration, 1); + m_eeprom.accel_calibration_1 = accel_calibration; + m_eeprom.accel_calibration_2 = accel_calibration; + + // TODO: Is this needed? + // Data of unknown purpose: + constexpr std::array EEPROM_DATA_16D0 = {0x00, 0x00, 0x00, 0xFF, 0x11, 0xEE, 0x00, 0x00, + 0x33, 0xCC, 0x44, 0xBB, 0x00, 0x00, 0x66, 0x99, + 0x77, 0x88, 0x00, 0x00, 0x2B, 0x01, 0xE8, 0x13}; + m_eeprom.unk_2 = EEPROM_DATA_16D0; + + std::string mii_file = File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/mii.bin"; + if (File::Exists(mii_file)) + { + // Import from the existing mii.bin file, if present + std::ifstream file; + File::OpenFStream(file, mii_file, std::ios::binary | std::ios::in); + file.read(reinterpret_cast(m_eeprom.mii_data_1.data()), m_eeprom.mii_data_1.size()); + m_eeprom.mii_data_2 = m_eeprom.mii_data_1; + file.close(); + } +} + +void BalanceBoard::LoadDefaultEeprom() +{ + // A real balance board starts with zero-filled EEPROM. + m_eeprom = {}; +} + +WiimoteBase::WiimoteBase(const u8 index) : m_bt_device_index(index) +{ +} + +InputConfig* Wiimote::GetConfig() const +{ + return ::Wiimote::GetConfig(); +} + +InputConfig* BalanceBoard::GetConfig() const +{ + return ::BalanceBoard::GetConfig(); +} + +Wiimote::Wiimote(const u8 index) : WiimoteBase{index}, m_config_index{index} { using Translatability = ControllerEmu::Translatability; @@ -275,7 +327,6 @@ Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(i m_attachments->AddAttachment(std::make_unique()); m_attachments->AddAttachment(std::make_unique()); m_attachments->AddAttachment(std::make_unique()); - m_attachments->AddSetting(&m_motion_plus_setting, {_trans("Attach MotionPlus")}, true); // Rumble @@ -318,14 +369,7 @@ Wiimote::~Wiimote() std::string Wiimote::GetName() const { - if (m_index == WIIMOTE_BALANCE_BOARD) - return "BalanceBoard"; - return fmt::format("Wiimote{}", 1 + m_index); -} - -InputConfig* Wiimote::GetConfig() const -{ - return ::Wiimote::GetConfig(); + return fmt::format("Wiimote{}", m_config_index + 1); } ControllerEmu::ControlGroup* Wiimote::GetWiimoteGroup(WiimoteGroup group) const @@ -424,13 +468,13 @@ ControllerEmu::ControlGroup* Wiimote::GetShinkansenGroup(ShinkansenGroup group) ->GetGroup(group); } -bool Wiimote::ProcessExtensionPortEvent() +auto WiimoteBase::ProcessExtensionPortEvent() -> UpdateProgress { // WiiBrew: Following a connection or disconnection event on the Extension Port, // data reporting is disabled and the Data Reporting Mode must be reset before new data can // arrive. if (m_extension_port.IsDeviceConnected() == m_status.extension) - return false; + return UpdateProgress::Continue; // FYI: This happens even during a read request which continues after the status report is sent. m_reporting_mode = InputReportID::ReportDisabled; @@ -439,7 +483,7 @@ bool Wiimote::ProcessExtensionPortEvent() HandleRequestStatus(OutputReportRequestStatus{}); - return true; + return UpdateProgress::DoNotContinue; } void Wiimote::UpdateButtonsStatus(const DesiredWiimoteState& target_state) @@ -447,6 +491,14 @@ void Wiimote::UpdateButtonsStatus(const DesiredWiimoteState& target_state) m_status.buttons.hex = target_state.buttons.hex & ButtonData::BUTTON_MASK; } +void Wiimote::UpdateBatteryStatus(double charge) +{ + m_status.SetEstimatedCharge(charge); + + // Less than 0x20 triggers the low-battery flag: + m_status.battery_low = m_status.battery < 0x20; +} + static std::array GetPassthroughCameraPoints(ControllerEmu::IRPassthrough* ir_passthrough) { @@ -528,61 +580,85 @@ void Wiimote::BuildDesiredWiimoteState(DesiredWiimoteState* target_state, ->BuildDesiredExtensionState(&target_state->extension); } -u8 Wiimote::GetWiimoteDeviceIndex() const -{ - return m_bt_device_index; -} - -void Wiimote::SetWiimoteDeviceIndex(u8 index) -{ - m_bt_device_index = index; -} - // This is called every ::Wiimote::UPDATE_FREQ (200hz) -void Wiimote::PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state, - SensorBarState sensor_bar_state) +void WiimoteBase::PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state, + SensorBarState sensor_bar_state) { const auto lock = GetStateLock(); BuildDesiredWiimoteState(target_state, sensor_bar_state); } -void Wiimote::Update(const WiimoteEmu::DesiredWiimoteState& target_state) +auto WiimoteBase::ProcessEvents() -> UpdateProgress +{ + if (ProcessExtensionPortEvent() == UpdateProgress::DoNotContinue) + { + // Extension port event occurred. + // Don't send any other reports. + return UpdateProgress::DoNotContinue; + } + + if (ProcessReadDataRequest() == UpdateProgress::DoNotContinue) + { + // Read requests suppress normal input reports + // Don't send any other reports + return UpdateProgress::DoNotContinue; + } + + return UpdateProgress::Continue; +} + +void WiimoteBase::Update(const WiimoteEmu::DesiredWiimoteState& target_state) { // Update buttons in the status struct which is sent in 99% of input reports. UpdateButtonsStatus(target_state); + if (Core::WantsDeterminism()) + { + // One less thing to break determinism: + UpdateBatteryStatus(1.0); + } + else + { + UpdateBatteryStatus(m_battery_setting.GetValue() / ciface::BATTERY_INPUT_MAX_VALUE); + } + + SubDeviceUpdate(target_state); + + if (ProcessEvents() == UpdateProgress::DoNotContinue) + return; + + SendDataReport(target_state); +} + +void Wiimote::SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state) +{ + m_camera_logic.Update(target_state.camera_points); + // If a new extension is requested in the GUI the change will happen here. HandleExtensionSwap(static_cast(target_state.extension.data.index()), target_state.motion_plus.has_value()); // Prepare input data of the extension for reading. + // Prepare extension input first as motion-plus may read from it. + // FYI: This should only happen when trigged by EXT bus read. + // but it's really not going to break anything being here. + // and refactoring is needed to do it properly. GetActiveExtension()->Update(target_state.extension); if (m_is_motion_plus_attached) { // M+ has some internal state that must processed. m_motion_plus.Update(target_state.extension); - } - // Returns true if a report was sent. - if (ProcessExtensionPortEvent()) - { - // Extension port event occurred. - // Don't send any other reports. - return; + // FYI: This should really only happen when trigged by EXT bus read. + // refactoring is needed to do that properly. + m_motion_plus.PrepareInput(target_state.motion_plus.has_value() ? + target_state.motion_plus.value() : + MotionPlus::GetDefaultGyroscopeData()); } - - if (ProcessReadDataRequest()) - { - // Read requests suppress normal input reports - // Don't send any other reports - return; - } - - SendDataReport(target_state); } -void Wiimote::SendDataReport(const DesiredWiimoteState& target_state) +void WiimoteBase::SendDataReport(const DesiredWiimoteState& target_state) { if (InputReportID::ReportDisabled == m_reporting_mode) { @@ -614,10 +690,6 @@ void Wiimote::SendDataReport(const DesiredWiimoteState& target_state) // IR Camera: if (rpt_builder.HasIR()) { - // Note: Camera logic currently contains no changing state so we can just update it here. - // If that changes this should be moved to Wiimote::Update(); - m_camera_logic.Update(target_state.camera_points); - // The real wiimote reads camera data from the i2c bus starting at offset 0x37: const u8 camera_data_offset = CameraLogic::REPORT_DATA_OFFSET + rpt_builder.GetIRDataFormatOffset(); @@ -636,19 +708,6 @@ void Wiimote::SendDataReport(const DesiredWiimoteState& target_state) // Extension port: if (rpt_builder.HasExt()) { - // Prepare extension input first as motion-plus may read from it. - // This currently happens in Wiimote::Update(); - // TODO: Separate extension input data preparation from Update. - // GetActiveExtension()->PrepareInput(); - - if (m_is_motion_plus_attached) - { - // TODO: Make input preparation triggered by bus read. - m_motion_plus.PrepareInput(target_state.motion_plus.has_value() ? - target_state.motion_plus.value() : - MotionPlus::GetDefaultGyroscopeData()); - } - u8* ext_data = rpt_builder.GetExtDataPtr(); const u8 ext_size = rpt_builder.GetExtDataSize(); @@ -727,10 +786,10 @@ void Wiimote::LoadDefaults(const ControllerInterface& ciface) m_buttons->SetControlExpression(4, "Q"); m_buttons->SetControlExpression(5, "E"); -#ifdef _WIN32 - m_buttons->SetControlExpression(6, "RETURN"); // Home -#else // Home +#ifdef _WIN32 + m_buttons->SetControlExpression(6, "RETURN"); +#else m_buttons->SetControlExpression(6, "Return"); #endif @@ -750,20 +809,20 @@ void Wiimote::LoadDefaults(const ControllerInterface& ciface) // DPad #ifdef _WIN32 - m_dpad->SetControlExpression(0, "UP"); // Up - m_dpad->SetControlExpression(1, "DOWN"); // Down - m_dpad->SetControlExpression(2, "LEFT"); // Left - m_dpad->SetControlExpression(3, "RIGHT"); // Right + m_dpad->SetControlExpression(0, "UP"); + m_dpad->SetControlExpression(1, "DOWN"); + m_dpad->SetControlExpression(2, "LEFT"); + m_dpad->SetControlExpression(3, "RIGHT"); #elif __APPLE__ - m_dpad->SetControlExpression(0, "`Up Arrow`"); // Up - m_dpad->SetControlExpression(1, "`Down Arrow`"); // Down - m_dpad->SetControlExpression(2, "`Left Arrow`"); // Left - m_dpad->SetControlExpression(3, "`Right Arrow`"); // Right + m_dpad->SetControlExpression(0, "`Up Arrow`"); + m_dpad->SetControlExpression(1, "`Down Arrow`"); + m_dpad->SetControlExpression(2, "`Left Arrow`"); + m_dpad->SetControlExpression(3, "`Right Arrow`"); #else - m_dpad->SetControlExpression(0, "Up"); // Up - m_dpad->SetControlExpression(1, "Down"); // Down - m_dpad->SetControlExpression(2, "Left"); // Left - m_dpad->SetControlExpression(3, "Right"); // Right + m_dpad->SetControlExpression(0, "Up"); + m_dpad->SetControlExpression(1, "Down"); + m_dpad->SetControlExpression(2, "Left"); + m_dpad->SetControlExpression(3, "Right"); #endif // Motion Source @@ -798,11 +857,26 @@ Extension* Wiimote::GetNoneExtension() const return static_cast(m_attachments->GetAttachmentList()[ExtensionNumber::NONE].get()); } -Extension* Wiimote::GetActiveExtension() const +Extension* Wiimote::GetActiveExtension() { return static_cast(m_attachments->GetAttachmentList()[m_active_extension].get()); } +Extension* BalanceBoard::GetActiveExtension() +{ + return &m_ext; +} + +ExtensionNumber Wiimote::GetActiveExtensionNumber() const +{ + return m_active_extension; +} + +ExtensionNumber BalanceBoard::GetActiveExtensionNumber() const +{ + return ExtensionNumber::BALANCE_BOARD; +} + bool Wiimote::IsSideways() const { const bool sideways_modifier_toggle = m_hotkeys->GetSettingsModifier()[0]; @@ -964,4 +1038,159 @@ Common::Matrix44 Wiimote::GetTotalTransformation() const Common::Quaternion::RotateX(m_imu_cursor_state.recentered_pitch))); } +std::string BalanceBoard::GetName() const +{ + return "BalanceBoard"; +} + +void BalanceBoard::LoadDefaults(const ControllerInterface& ciface) +{ + EmulatedController::LoadDefaults(ciface); + + // Power + m_buttons->SetControlExpression(0, "P"); + + // Balance: Up, Down, Left, Right + m_balance->SetControlExpression(0, "I"); + m_balance->SetControlExpression(1, "K"); + m_balance->SetControlExpression(2, "J"); + m_balance->SetControlExpression(3, "L"); + + // Feet + // TODO: + + // Because our defaults use keyboard input, set calibration shape to a square. + m_balance->SetCalibrationFromGate(ControllerEmu::SquareStickGate(1.0)); +} + +void BalanceBoard::SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state) +{ + m_ext.Update(target_state.extension); +} + +void BalanceBoard::Reset() +{ + WiimoteBase::Reset(); + + m_extension_port.AttachExtension(&m_ext); + m_ext.Reset(); +} + +BalanceBoard::BalanceBoard(const u8 index) : WiimoteBase(index) +{ + using Translatability = ControllerEmu::Translatability; + + // Buttons + groups.emplace_back(m_buttons = new ControllerEmu::Buttons(BUTTONS_GROUP_NAME)); + m_buttons->AddInput(Translatability::Translate, BUTTON_POWER_NAME); + + // Balance + groups.emplace_back( + m_balance = new ControllerEmu::AnalogStick( + _trans("Balance"), std::make_unique(1.0))); + + // Feet + groups.emplace_back(m_left_foot = new ControllerEmu::ControlGroup(_trans("Left Foot"))); + groups.emplace_back(m_right_foot = new ControllerEmu::ControlGroup(_trans("Right Foot"))); + for (auto& foot : {m_left_foot, m_right_foot}) + { + // foot->AddInput(Translatability::Translate, "Lift"); + foot->AddInput(Translatability::Translate, "Step Off"); + } + + // Options + groups.emplace_back(m_options = new ControllerEmu::ControlGroup(_trans("Options"))); + + m_options->AddSetting(&m_battery_setting, + {_trans("Battery"), + // i18n: The percent symbol. + _trans("%")}, + 95, 0, 100); + + m_options->AddSetting(&m_weight_setting, + {_trans("Weight"), + // i18n: The abbreviation for kilograms. + _trans("kg")}, + BalanceBoardExt::DEFAULT_WEIGHT, 0, 250); + + // Default is the on-center distance between the textured pads on the board. + constexpr int DEFAULT_FOOT_SEPARATION = 27; + m_options->AddSetting(&m_foot_separation_setting, + {_trans("Foot Separation"), + // i18n: The abbreviation for centimeters. + _trans("cm")}, + DEFAULT_FOOT_SEPARATION, 0, 50); + + // Default is the length of the textured foot pad on the board. + constexpr int DEFAULT_FOOT_LENGTH = 23; + m_options->AddSetting(&m_foot_length_setting, + {_trans("Foot Length"), + // i18n: The abbreviation for centimeters. + _trans("cm")}, + DEFAULT_FOOT_LENGTH, 0, 40); + + Reset(); +} + +ControllerEmu::ControlGroup* BalanceBoard::GetBalanceBoardGroup(BalanceBoardGroup group) const +{ + switch (group) + { + case BalanceBoardGroup::Buttons: + return m_buttons; + case BalanceBoardGroup::Options: + return m_options; + case BalanceBoardGroup::Balance: + return m_balance; + case BalanceBoardGroup::LeftFoot: + return m_left_foot; + case BalanceBoardGroup::RightFoot: + return m_right_foot; + default: + ASSERT(false); + return nullptr; + } +} + +ButtonData BalanceBoard::GetCurrentlyPressedButtons() +{ + const auto lock = GetStateLock(); + + ButtonData buttons{}; + m_buttons->GetState(&buttons.hex, bboard_bitmasks, m_input_override_function); + + return buttons; +} + +// Update buttons in status struct from user input. +void BalanceBoard::UpdateButtonsStatus(const DesiredWiimoteState& target_state) +{ + m_status.buttons.hex = target_state.buttons.hex & BalanceBoard::BUTTON_POWER; +} + +void BalanceBoard::UpdateBatteryStatus(double charge) +{ + // Balance Board battery values are higher than that of a Wii Remote. + // This is a linear fit for the charge bars in the home menu. + m_status.battery = u8(std::lround((charge + 3.53) / 8.52 * 0xff)); + + // TODO: Verify this behavior on real hardware. + m_status.battery_low = m_status.battery <= BalanceBoardExt::REFERENCE_BATTERY; +} + +void BalanceBoard::BuildDesiredWiimoteState(DesiredWiimoteState* target_state, + SensorBarState sensor_bar_state) +{ + m_buttons->GetState(&target_state->buttons.hex, bboard_bitmasks, m_input_override_function); + + // Real balance board observed with 0x2a-filled accel data. LSbs in button data were zero. + // TODO: This will result in SerializeDesiredState wasting 3 bytes on never-changing accel data. + target_state->acceleration.value.data.fill(0x2a << 2); + + // Default, 0xff-filled, camera data is accurate. + + // Balance board pseudo-extension is always attached. + m_ext.BuildDesiredExtensionState(&target_state->extension); +} + } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index be5fdfdc6e..39b37be7a0 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -15,7 +15,7 @@ #include "Core/HW/WiimoteEmu/Camera.h" #include "Core/HW/WiimoteEmu/Dynamics.h" -#include "Core/HW/WiimoteEmu/Encryption.h" +#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h" #include "Core/HW/WiimoteEmu/ExtensionPort.h" #include "Core/HW/WiimoteEmu/I2CBus.h" #include "Core/HW/WiimoteEmu/MotionPlus.h" @@ -25,6 +25,7 @@ class PointerWrap; namespace ControllerEmu { +class AnalogStick; class Attachments; class Buttons; class ControlGroup; @@ -38,6 +39,7 @@ class IRPassthrough; class ModifySettingsButton; class Output; class Tilt; +class Triggers; } // namespace ControllerEmu namespace WiimoteEmu @@ -63,6 +65,15 @@ enum class WiimoteGroup IRPassthrough, }; +enum class BalanceBoardGroup +{ + Buttons, + Balance, + LeftFoot, + RightFoot, + Options, +}; + enum class NunchukGroup; enum class ClassicGroup; enum class GuitarGroup; @@ -94,129 +105,41 @@ void UpdateCalibrationDataChecksum(T& data, int cksum_bytes) } } -class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon::HIDWiimote +class WiimoteBase : public ControllerEmu::EmulatedController, public WiimoteCommon::HIDWiimote { public: - static constexpr u16 IR_LOW_X = 0x7F; - static constexpr u16 IR_LOW_Y = 0x5D; - static constexpr u16 IR_HIGH_X = 0x380; - static constexpr u16 IR_HIGH_Y = 0x2A2; - - static constexpr u8 ACCEL_ZERO_G = 0x80; - static constexpr u8 ACCEL_ONE_G = 0x9A; - - static constexpr u16 PAD_LEFT = 0x01; - static constexpr u16 PAD_RIGHT = 0x02; - static constexpr u16 PAD_DOWN = 0x04; - static constexpr u16 PAD_UP = 0x08; - static constexpr u16 BUTTON_PLUS = 0x10; - - static constexpr u16 BUTTON_TWO = 0x0100; - static constexpr u16 BUTTON_ONE = 0x0200; - static constexpr u16 BUTTON_B = 0x0400; - static constexpr u16 BUTTON_A = 0x0800; - static constexpr u16 BUTTON_MINUS = 0x1000; - static constexpr u16 BUTTON_HOME = 0x8000; - - static constexpr const char* BUTTONS_GROUP = _trans("Buttons"); - static constexpr const char* DPAD_GROUP = _trans("D-Pad"); - static constexpr const char* ACCELEROMETER_GROUP = "IMUAccelerometer"; - static constexpr const char* GYROSCOPE_GROUP = "IMUGyroscope"; - static constexpr const char* IR_GROUP = "IR"; - static constexpr const char* IR_PASSTHROUGH_GROUP = "IRPassthrough"; - - static constexpr const char* A_BUTTON = "A"; - static constexpr const char* B_BUTTON = "B"; - static constexpr const char* ONE_BUTTON = "1"; - static constexpr const char* TWO_BUTTON = "2"; - static constexpr const char* MINUS_BUTTON = "-"; - static constexpr const char* PLUS_BUTTON = "+"; - static constexpr const char* HOME_BUTTON = "Home"; - - static constexpr const char* UPRIGHT_OPTION = "Upright Wiimote"; - static constexpr const char* SIDEWAYS_OPTION = "Sideways Wiimote"; - - explicit Wiimote(unsigned int index); - ~Wiimote(); - - std::string GetName() const override; - - InputConfig* GetConfig() const override; - - void LoadDefaults(const ControllerInterface& ciface) override; - - ControllerEmu::ControlGroup* GetWiimoteGroup(WiimoteGroup group) const; - ControllerEmu::ControlGroup* GetNunchukGroup(NunchukGroup group) const; - ControllerEmu::ControlGroup* GetClassicGroup(ClassicGroup group) const; - ControllerEmu::ControlGroup* GetGuitarGroup(GuitarGroup group) const; - ControllerEmu::ControlGroup* GetDrumsGroup(DrumsGroup group) const; - ControllerEmu::ControlGroup* GetTurntableGroup(TurntableGroup group) const; - ControllerEmu::ControlGroup* GetUDrawTabletGroup(UDrawTabletGroup group) const; - ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(DrawsomeTabletGroup group) const; - ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const; - ControllerEmu::ControlGroup* GetShinkansenGroup(ShinkansenGroup group) const; + explicit WiimoteBase(u8 index); u8 GetWiimoteDeviceIndex() const override; void SetWiimoteDeviceIndex(u8 index) override; - void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state, - SensorBarState sensor_bar_state) override; void Update(const WiimoteEmu::DesiredWiimoteState& target_state) override; void EventLinked() override; void EventUnlinked() override; void InterruptDataOutput(const u8* data, u32 size) override; - WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override; - void Reset(); + virtual void Reset(); + virtual void LoadDefaultEeprom() = 0; - void DoState(PointerWrap& p); + virtual void DoState(PointerWrap& p); + virtual Extension* GetActiveExtension() = 0; // Active extension number is exposed for TAS. - ExtensionNumber GetActiveExtensionNumber() const; - ControllerEmu::SubscribableSettingValue& GetMotionPlusSetting(); + virtual ExtensionNumber GetActiveExtensionNumber() const = 0; - static Common::Vec3 - OverrideVec3(const ControllerEmu::ControlGroup* control_group, Common::Vec3 vec, - const ControllerEmu::InputOverrideFunction& input_override_function); - -private: - // Used only for error generation: - static constexpr u8 EEPROM_I2C_ADDR = 0x50; - - // static constexpr int EEPROM_SIZE = 16 * 1024; - // This is the region exposed over bluetooth: +protected: + // This is the region exposed over bluetooth, total size is 0x4000. static constexpr int EEPROM_FREE_SIZE = 0x1700; - void RefreshConfig(); + virtual void SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state) = 0; + virtual void UpdateButtonsStatus(const DesiredWiimoteState& target_state) = 0; + virtual void UpdateBatteryStatus(double charge) = 0; + virtual void BuildDesiredWiimoteState(DesiredWiimoteState* target_state, + SensorBarState sensor_bar_state) = 0; + void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state, + SensorBarState sensor_bar_state) override; - void StepDynamics(); - void UpdateButtonsStatus(const DesiredWiimoteState& target_state); - void BuildDesiredWiimoteState(DesiredWiimoteState* target_state, SensorBarState sensor_bar_state); - - // Returns simulated accelerometer data in m/s^2. - Common::Vec3 GetAcceleration(Common::Vec3 extra_acceleration) const; - - // Returns simulated gyroscope data in radians/s. - Common::Vec3 GetAngularVelocity(Common::Vec3 extra_angular_velocity) const; - - // Returns the transformation of the world around the wiimote. - // Used for simulating camera data and for rotating acceleration data. - // Does not include orientation transformations. - Common::Matrix44 - GetTransformation(const Common::Matrix33& extra_rotation = Common::Matrix33::Identity()) const; - - // Returns the world rotation from the effects of sideways/upright settings. - Common::Quaternion GetOrientation() const; - - std::optional OverrideVec3(const ControllerEmu::ControlGroup* control_group, - std::optional optional_vec) const; - Common::Vec3 OverrideVec3(const ControllerEmu::ControlGroup* control_group, - Common::Vec3 vec) const; - Common::Vec3 GetTotalAcceleration() const; - Common::Vec3 GetTotalAngularVelocity() const; - Common::Matrix44 GetTotalTransformation() const; - - void HandleReportRumble(const WiimoteCommon::OutputReportRumble&); + virtual void HandleReportRumble(const WiimoteCommon::OutputReportRumble&) = 0; void HandleReportLeds(const WiimoteCommon::OutputReportLeds&); void HandleReportMode(const WiimoteCommon::OutputReportMode&); void HandleRequestStatus(const WiimoteCommon::OutputReportRequestStatus&); @@ -224,27 +147,25 @@ private: void HandleWriteData(const WiimoteCommon::OutputReportWriteData&); void HandleIRLogicEnable(const WiimoteCommon::OutputReportEnableFeature&); void HandleIRLogicEnable2(const WiimoteCommon::OutputReportEnableFeature&); - void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&); + virtual void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&) = 0; void HandleSpeakerEnable(const WiimoteCommon::OutputReportEnableFeature&); - void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&); + virtual void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&) = 0; template void InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size); - void HandleExtensionSwap(ExtensionNumber desired_extension_number, bool desired_motion_plus); - bool ProcessExtensionPortEvent(); - void SendDataReport(const DesiredWiimoteState& target_state); - bool ProcessReadDataRequest(); - - void SetRumble(bool on); - void SendAck(WiimoteCommon::OutputReportID rpt_id, WiimoteCommon::ErrorCode err); - bool IsSideways() const; - bool IsUpright() const; - - Extension* GetActiveExtension() const; - Extension* GetNoneExtension() const; +private: + enum class UpdateProgress + { + Continue, + DoNotContinue, + }; + UpdateProgress ProcessEvents(); + UpdateProgress ProcessExtensionPortEvent(); + UpdateProgress ProcessReadDataRequest(); + void SendDataReport(const DesiredWiimoteState& target_state); struct ReadRequest { @@ -286,6 +207,151 @@ private: static_assert(EEPROM_FREE_SIZE == sizeof(UsableEEPROMData)); +protected: + I2CBus m_i2c_bus; + + ExtensionPort m_extension_port{&m_i2c_bus}; + + // The Bluetooth 'slot' this device is connected to. + // This is usually the same as m_index, but can differ during Netplay. + u8 m_bt_device_index; + + ControllerEmu::SettingValue m_battery_setting; + +private: + WiimoteCommon::InputReportID m_reporting_mode = WiimoteCommon::InputReportID::ReportDisabled; + bool m_reporting_continuous = false; + +protected: + WiimoteCommon::InputReportStatus m_status; + +private: + bool m_eeprom_dirty = false; + ReadRequest m_read_request; + +protected: + UsableEEPROMData m_eeprom; +}; + +class Wiimote final : public WiimoteBase +{ +public: + static constexpr u8 ACCEL_ZERO_G = 0x80; + static constexpr u8 ACCEL_ONE_G = 0x9A; + + static constexpr u16 PAD_LEFT = 0x01; + static constexpr u16 PAD_RIGHT = 0x02; + static constexpr u16 PAD_DOWN = 0x04; + static constexpr u16 PAD_UP = 0x08; + static constexpr u16 BUTTON_PLUS = 0x10; + + static constexpr u16 BUTTON_TWO = 0x0100; + static constexpr u16 BUTTON_ONE = 0x0200; + static constexpr u16 BUTTON_B = 0x0400; + static constexpr u16 BUTTON_A = 0x0800; + static constexpr u16 BUTTON_MINUS = 0x1000; + static constexpr u16 BUTTON_HOME = 0x8000; + + static constexpr const char* BUTTONS_GROUP = _trans("Buttons"); + static constexpr const char* DPAD_GROUP = _trans("D-Pad"); + static constexpr const char* ACCELEROMETER_GROUP = "IMUAccelerometer"; + static constexpr const char* GYROSCOPE_GROUP = "IMUGyroscope"; + static constexpr const char* IR_GROUP = "IR"; + static constexpr const char* IR_PASSTHROUGH_GROUP = "IRPassthrough"; + + static constexpr const char* A_BUTTON = "A"; + static constexpr const char* B_BUTTON = "B"; + static constexpr const char* ONE_BUTTON = "1"; + static constexpr const char* TWO_BUTTON = "2"; + static constexpr const char* MINUS_BUTTON = "-"; + static constexpr const char* PLUS_BUTTON = "+"; + static constexpr const char* HOME_BUTTON = "Home"; + + static constexpr const char* UPRIGHT_OPTION = "Upright Wiimote"; + static constexpr const char* SIDEWAYS_OPTION = "Sideways Wiimote"; + + explicit Wiimote(u8 index); + ~Wiimote(); + + InputConfig* GetConfig() const override; + std::string GetName() const override; + void LoadDefaults(const ControllerInterface& ciface) override; + + ControllerEmu::ControlGroup* GetWiimoteGroup(WiimoteGroup group) const; + ControllerEmu::ControlGroup* GetNunchukGroup(NunchukGroup group) const; + ControllerEmu::ControlGroup* GetClassicGroup(ClassicGroup group) const; + ControllerEmu::ControlGroup* GetGuitarGroup(GuitarGroup group) const; + ControllerEmu::ControlGroup* GetDrumsGroup(DrumsGroup group) const; + ControllerEmu::ControlGroup* GetTurntableGroup(TurntableGroup group) const; + ControllerEmu::ControlGroup* GetUDrawTabletGroup(UDrawTabletGroup group) const; + ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(DrawsomeTabletGroup group) const; + ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const; + ControllerEmu::ControlGroup* GetShinkansenGroup(ShinkansenGroup group) const; + + void SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state) override; + WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override; + + void Reset() override; + void LoadDefaultEeprom() override; + + void DoState(PointerWrap& p) override; + + Extension* GetActiveExtension() override; + Extension* GetNoneExtension() const; + ExtensionNumber GetActiveExtensionNumber() const override; + ControllerEmu::SubscribableSettingValue& GetAttachmentSetting(); + ControllerEmu::SubscribableSettingValue& GetMotionPlusSetting(); + + static Common::Vec3 + OverrideVec3(const ControllerEmu::ControlGroup* control_group, Common::Vec3 vec, + const ControllerEmu::InputOverrideFunction& input_override_function); + +protected: + void HandleReportRumble(const WiimoteCommon::OutputReportRumble&) override; + void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&) override; + void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&) override; + + void UpdateButtonsStatus(const DesiredWiimoteState& target_state) override; + void UpdateBatteryStatus(double charge) override; + void BuildDesiredWiimoteState(DesiredWiimoteState* target_state, + SensorBarState sensor_bar_state) override; + + std::optional OverrideVec3(const ControllerEmu::ControlGroup* control_group, + std::optional optional_vec) const; + Common::Vec3 OverrideVec3(const ControllerEmu::ControlGroup* control_group, + Common::Vec3 vec) const; + Common::Vec3 GetTotalAcceleration() const; + Common::Vec3 GetTotalAngularVelocity() const; + +private: + void RefreshConfig(); + void StepDynamics(); + void HandleExtensionSwap(ExtensionNumber desired_extension_number, bool desired_motion_plus); + + // Returns simulated accelerometer data in m/s^2. + Common::Vec3 GetAcceleration(Common::Vec3 extra_acceleration) const; + + // Returns simulated gyroscope data in radians/s. + Common::Vec3 GetAngularVelocity(Common::Vec3 extra_angular_velocity) const; + + // Returns the transformation of the world around the wiimote. + // Used for simulating camera data and for rotating acceleration data. + // Does not include orientation transformations. + Common::Matrix44 + GetTransformation(const Common::Matrix33& extra_rotation = Common::Matrix33::Identity()) const; + + // Returns the world rotation from the effects of sideways/upright settings. + Common::Quaternion GetOrientation() const; + + Common::Matrix44 GetTotalTransformation() const; + + void SetRumble(bool on); + + bool IsSideways() const; + bool IsUpright() const; + + const u8 m_config_index; + // Control groups for user input: ControllerEmu::Buttons* m_buttons; ControllerEmu::Buttons* m_dpad; @@ -304,42 +370,18 @@ private: ControllerEmu::SettingValue m_sideways_setting; ControllerEmu::SettingValue m_upright_setting; - ControllerEmu::SettingValue m_battery_setting; ControllerEmu::SubscribableSettingValue m_motion_plus_setting; ControllerEmu::SettingValue m_fov_x_setting; ControllerEmu::SettingValue m_fov_y_setting; SpeakerLogic m_speaker_logic; - MotionPlus m_motion_plus; - CameraLogic m_camera_logic; - - I2CBus m_i2c_bus; - - ExtensionPort m_extension_port{&m_i2c_bus}; - - // Wiimote index, 0-3. - // Can also be 4 for Balance Board. - // This is used to look up the user button config. - const u8 m_index; - - // The Bluetooth 'slot' this device is connected to. - // This is usually the same as m_index, but can differ during Netplay. - u8 m_bt_device_index; - - WiimoteCommon::InputReportID m_reporting_mode; - bool m_reporting_continuous; + CameraLogic m_camera_logic{&m_status}; bool m_speaker_mute; - WiimoteCommon::InputReportStatus m_status; - - ExtensionNumber m_active_extension; - - bool m_is_motion_plus_attached; - - bool m_eeprom_dirty = false; - ReadRequest m_read_request; - UsableEEPROMData m_eeprom; + ExtensionNumber m_active_extension = NONE; + MotionPlus m_motion_plus; + bool m_is_motion_plus_attached = false; // Dynamics: MotionState m_swing_state; @@ -351,4 +393,56 @@ private: Config::ConfigChangedCallbackID m_config_changed_callback_id; }; + +class BalanceBoard final : public WiimoteBase +{ + friend class BalanceBoardExt; + +public: + static constexpr const char* BUTTONS_GROUP_NAME = _trans("Buttons"); + static constexpr u16 BUTTON_POWER = Wiimote::BUTTON_A; + static constexpr const char* BUTTON_POWER_NAME = "Power"; + + explicit BalanceBoard(u8 index); + + ControllerEmu::ControlGroup* GetBalanceBoardGroup(BalanceBoardGroup group) const; + + InputConfig* GetConfig() const override; + std::string GetName() const override; + void LoadDefaults(const ControllerInterface& ciface) override; + + void SubDeviceUpdate(const WiimoteEmu::DesiredWiimoteState& target_state) override; + WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override; + + void Reset() override; + void LoadDefaultEeprom() override; + + void DoState(PointerWrap& p) override; + + Extension* GetActiveExtension() override; + ExtensionNumber GetActiveExtensionNumber() const override; + +protected: + void UpdateButtonsStatus(const DesiredWiimoteState& target_state) override; + void UpdateBatteryStatus(double charge) override; + void BuildDesiredWiimoteState(DesiredWiimoteState* target_state, + SensorBarState sensor_bar_state) override; + + void HandleReportRumble(const WiimoteCommon::OutputReportRumble&) override; + void HandleSpeakerMute(const WiimoteCommon::OutputReportEnableFeature&) override; + void HandleSpeakerData(const WiimoteCommon::OutputReportSpeakerData&) override; + +private: + BalanceBoardExt m_ext{this}; + + ControllerEmu::Buttons* m_buttons; + ControllerEmu::AnalogStick* m_balance; + ControllerEmu::ControlGroup* m_left_foot; + ControllerEmu::ControlGroup* m_right_foot; + ControllerEmu::ControlGroup* m_options; + + ControllerEmu::SettingValue m_weight_setting; + ControllerEmu::SettingValue m_foot_separation_setting; + ControllerEmu::SettingValue m_foot_length_setting; +}; } // namespace WiimoteEmu diff --git a/Source/Core/Core/HotkeyManager.h b/Source/Core/Core/HotkeyManager.h index bb287b6561..c8899afcca 100644 --- a/Source/Core/Core/HotkeyManager.h +++ b/Source/Core/Core/HotkeyManager.h @@ -12,7 +12,6 @@ namespace ControllerEmu { -class ControllerEmu; class Buttons; } // namespace ControllerEmu diff --git a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp index 8b556da6d7..40719004b9 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.cpp @@ -4,9 +4,7 @@ #include "Core/IOS/USB/Bluetooth/WiimoteDevice.h" #include -#include #include -#include #include @@ -15,7 +13,6 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" -#include "Common/StringUtil.h" #include "Common/Swap.h" #include "Core/Core.h" #include "Core/HW/WII_IPC.h" @@ -62,7 +59,7 @@ WiimoteDevice::WiimoteDevice(BluetoothEmuDevice* host, bdaddr_t bd, unsigned int m_name(GetNumber() == WIIMOTE_BALANCE_BOARD ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01") { - INFO_LOG_FMT(IOS_WIIMOTE, "Wiimote: #{} Constructed", GetNumber()); + INFO_LOG_FMT(IOS_WIIMOTE, "{} Constructed", GetDisplayName()); m_link_key.fill(0xa0 + GetNumber()); m_class = {0x00, 0x04, 0x48}; @@ -224,8 +221,7 @@ void WiimoteDevice::Activate(bool connect) { SetBasebandState(BasebandState::RequestConnection); - Core::DisplayMessage(fmt::format("Wii Remote {} connected", GetNumber() + 1), - CONNECTION_MESSAGE_TIME); + Core::DisplayMessage(fmt::format("{} connected", GetDisplayName()), CONNECTION_MESSAGE_TIME); } else if (!connect && IsConnected()) { @@ -235,8 +231,7 @@ void WiimoteDevice::Activate(bool connect) // Not doing that doesn't seem to break anything. m_host->RemoteDisconnect(GetBD()); - Core::DisplayMessage(fmt::format("Wii Remote {} disconnected", GetNumber() + 1), - CONNECTION_MESSAGE_TIME); + Core::DisplayMessage(fmt::format("{} disconnected", GetDisplayName()), CONNECTION_MESSAGE_TIME); } } @@ -275,7 +270,7 @@ void WiimoteDevice::EventDisconnect(u8 reason) // FYI: It looks like reason is always 0x13 (User Ended Connection). Core::DisplayMessage( - fmt::format("Wii Remote {} disconnected by emulated software", GetNumber() + 1), + fmt::format("{} disconnected by emulated software (0x{:02x})", GetDisplayName(), reason), CONNECTION_MESSAGE_TIME); Reset(); @@ -1005,4 +1000,12 @@ void WiimoteDevice::InterruptDataInputCallback(u8 hid_type, const u8* data, u32 m_host->SendACLPacket(GetBD(), reinterpret_cast(&data_frame), data_frame_size); } + +std::string WiimoteDevice::GetDisplayName() +{ + if (GetNumber() != WIIMOTE_BALANCE_BOARD) + return fmt::format("Wii Remote {}", GetNumber() + 1); + else + return "Balance Board"; +} } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h index f74bb4717e..c07cda060d 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/WiimoteDevice.h @@ -170,5 +170,7 @@ private: void SDPSendServiceAttributeResponse(u16 cid, u16 transaction_id, u32 service_handle, u16 start_attr_id, u16 end_attr_id, u16 maximum_attribute_byte_count, u8* continuation_state); + + std::string GetDisplayName(); }; } // namespace IOS::HLE diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index aee5e4707a..a64de9aa00 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -54,6 +54,8 @@ #include "Core/HW/SI/SI_Device.h" #include "Core/HW/Wiimote.h" #include "Core/HW/WiimoteCommon/WiimoteReport.h" + +#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h" #include "Core/HW/WiimoteEmu/Extension/Classic.h" #include "Core/HW/WiimoteEmu/Extension/Nunchuk.h" #include "Core/HW/WiimoteEmu/ExtensionPort.h" @@ -119,10 +121,8 @@ std::string MovieManager::GetInputDisplay() if (!IsMovieActive()) { m_controllers = {}; - m_wiimotes = {}; - const auto& si = m_system.GetSerialInterface(); - for (int i = 0; i < 4; ++i) + for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) { if (si.GetDeviceType(i) == SerialInterface::SIDEVICE_GC_GBA_EMULATED) m_controllers[i] = ControllerType::GBA; @@ -130,6 +130,10 @@ std::string MovieManager::GetInputDisplay() m_controllers[i] = ControllerType::GC; else m_controllers[i] = ControllerType::None; + } + m_wiimotes = {}; + for (int i = 0; i < MAX_BBMOTES; ++i) + { m_wiimotes[i] = Config::Get(Config::GetInfoForWiimoteSource(i)) != WiimoteSource::None; } } @@ -137,15 +141,15 @@ std::string MovieManager::GetInputDisplay() std::string input_display; { std::lock_guard guard(m_input_display_lock); - for (int i = 0; i < 4; ++i) + for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) { if (IsUsingPad(i)) input_display += m_input_display[i] + '\n'; } - for (int i = 0; i < 4; ++i) + for (int i = 0; i < MAX_BBMOTES; ++i) { if (IsUsingWiimote(i)) - input_display += m_input_display[i + 4] + '\n'; + input_display += m_input_display[i + SerialInterface::MAX_SI_CHANNELS] + '\n'; } } return input_display; @@ -464,7 +468,7 @@ void MovieManager::ChangeWiiPads(bool instantly) { WiimoteEnabledArray wiimotes{}; - for (int i = 0; i < MAX_WIIMOTES; ++i) + for (int i = 0; i < MAX_BBMOTES; ++i) { wiimotes[i] = Config::Get(Config::GetInfoForWiimoteSource(i)) != WiimoteSource::None; } @@ -474,7 +478,7 @@ void MovieManager::ChangeWiiPads(bool instantly) return; const auto bt = WiiUtils::GetBluetoothEmuDevice(); - for (int i = 0; i < MAX_WIIMOTES; ++i) + for (int i = 0; i < MAX_BBMOTES; ++i) { const bool is_using_wiimote = IsUsingWiimote(i); @@ -664,7 +668,8 @@ static std::string GenerateInputDisplayString(ControllerState padState, int cont // NOTE: CPU Thread static std::string GenerateWiiInputDisplayString(int index, const DesiredWiimoteState& state) { - std::string display_str = fmt::format("R{}:", index + 1); + std::string display_str = + (index == WIIMOTE_BALANCE_BOARD ? "BB:" : fmt::format("R{}:", index + 1)); const auto& buttons = state.buttons; if (buttons.hex & WiimoteCommon::ButtonData::BUTTON_MASK) @@ -764,6 +769,14 @@ static std::string GenerateWiiInputDisplayString(int index, const DesiredWiimote [&](const DrawsomeTablet::DesiredState&) { display_str += " Drawsome"; }, [&](const TaTaCon::DesiredState&) { display_str += " TaTaCon"; }, [&](const Shinkansen::DesiredState&) { display_str += " Shinkansen"; }, + [&](const BalanceBoardExt::DataFormat& bb) { + const double tr = BalanceBoardExt::ConvertToKilograms(Common::swap16(bb.sensor_tr)); + const double br = BalanceBoardExt::ConvertToKilograms(Common::swap16(bb.sensor_br)); + const double tl = BalanceBoardExt::ConvertToKilograms(Common::swap16(bb.sensor_tl)); + const double bl = BalanceBoardExt::ConvertToKilograms(Common::swap16(bb.sensor_bl)); + display_str += + fmt::format(" TR:{:5.2f}kg BR:{:5.2f}kg TL:{:5.2f}kg BL:{:5.2f}kg", tr, br, tl, bl); + }, [](const auto& arg) { static_assert(std::is_same_v>, "unimplemented extension"); @@ -840,7 +853,7 @@ void MovieManager::CheckWiimoteStatus(int wiimote, const DesiredWiimoteState& de std::string display_str = GenerateWiiInputDisplayString(wiimote, desired_state); std::lock_guard guard(m_input_display_lock); - m_input_display[wiimote + 4] = std::move(display_str); + m_input_display[wiimote + SerialInterface::MAX_SI_CHANNELS] = std::move(display_str); } if (IsRecordingInput()) @@ -864,7 +877,7 @@ void MovieManager::RecordWiimote(int wiimote, const SerializedWiimoteState& seri // NOTE: EmuThread / Host Thread void MovieManager::ReadHeader() { - for (int i = 0; i < 4; ++i) + for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) { if (m_temp_header.GBAControllers & (1 << i)) m_controllers[i] = ControllerType::GBA; @@ -872,8 +885,13 @@ void MovieManager::ReadHeader() m_controllers[i] = ControllerType::GC; else m_controllers[i] = ControllerType::None; - m_wiimotes[i] = (m_temp_header.controllers & (1 << (i + 4))) != 0; } + for (int i = 0; i < MAX_WIIMOTES; i++) + { + m_wiimotes[i] = + (m_temp_header.controllers & (1 << (i + SerialInterface::MAX_SI_CHANNELS))) != 0; + } + m_wiimotes[WIIMOTE_BALANCE_BOARD] = m_temp_header.bBalanceBoard; m_recording_start_time = m_temp_header.recordingStartTime; if (m_rerecords < m_temp_header.numRerecords) m_rerecords = m_temp_header.numRerecords; @@ -1356,15 +1374,19 @@ void MovieManager::SaveRecording(const std::string& filename) header.bWii = m_system.IsWii(); header.controllers = 0; header.GBAControllers = 0; - for (int i = 0; i < 4; ++i) + for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) { if (IsUsingGBA(i)) header.GBAControllers |= 1 << i; if (IsUsingPad(i)) header.controllers |= 1 << i; - if (IsUsingWiimote(i) && m_system.IsWii()) - header.controllers |= 1 << (i + 4); } + for (int i = 0; i < MAX_WIIMOTES; i++) + { + if (IsUsingWiimote(i) && m_system.IsWii()) + header.controllers |= 1 << (i + SerialInterface::MAX_SI_CHANNELS); + } + header.bBalanceBoard = IsUsingWiimote(WIIMOTE_BALANCE_BOARD); header.bFromSaveState = m_recording_from_save_state; header.frameCount = m_total_frames; diff --git a/Source/Core/Core/Movie.h b/Source/Core/Core/Movie.h index e842ec9d7b..5817c388f0 100644 --- a/Source/Core/Core/Movie.h +++ b/Source/Core/Core/Movie.h @@ -12,6 +12,9 @@ #include #include "Common/CommonTypes.h" + +#include "Core/HW/SI/SI.h" +#include "Core/HW/Wiimote.h" #include "Core/HW/WiimoteEmu/DesiredWiimoteState.h" struct BootParameters; @@ -51,8 +54,8 @@ enum class ControllerType GC, GBA, }; -using ControllerTypeArray = std::array; -using WiimoteEnabledArray = std::array; +using ControllerTypeArray = std::array; +using WiimoteEnabledArray = std::array; // GameCube Controller State #pragma pack(push, 1) @@ -131,10 +134,11 @@ struct DTMHeader u8 reserved3; bool bFollowBranch; bool bUseFMA; - u8 GBAControllers; // GBA Controllers plugged in (the bits are ports 1-4) - bool bWidescreen; // true indicates SYSCONF aspect ratio is 16:9, false for 4:3 - u8 countryCode; // SYSCONF country code - std::array reserved; // Padding for any new config options + u8 GBAControllers; // GBA Controllers plugged in (the bits are ports 1-4) + bool bWidescreen; // true indicates SYSCONF aspect ratio is 16:9, false for 4:3 + u8 countryCode; // SYSCONF country code + bool bBalanceBoard; + std::array reserved; // Padding for any new config options std::array discChange; // Name of iso file to switch to, for two disc games. std::array revision; // Git hash u32 DSPiromHash; @@ -235,8 +239,8 @@ private: u32 m_rerecords = 0; PlayMode m_play_mode = PlayMode::None; - std::array m_controllers{}; - std::array m_wiimotes{}; + ControllerTypeArray m_controllers{}; + WiimoteEnabledArray m_wiimotes{}; ControllerState m_pad_state{}; DTMHeader m_temp_header{}; std::vector m_temp_input; @@ -271,7 +275,8 @@ private: // m_input_display is used by both CPU and GPU (is mutable). std::mutex m_input_display_lock; - std::array m_input_display; + std::array(SerialInterface::MAX_SI_CHANNELS) + MAX_BBMOTES> + m_input_display; Core::System& m_system; }; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 44465b3f42..83ecf61efe 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -4,7 +4,6 @@ #include "Core/State.h" #include -#include #include #include #include @@ -12,7 +11,6 @@ #include #include #include -#include #include #include @@ -31,12 +29,10 @@ #include "Common/MsgHandler.h" #include "Common/Thread.h" #include "Common/TimeUtil.h" -#include "Common/Timer.h" #include "Common/Version.h" #include "Common/WorkQueueThread.h" #include "Core/AchievementManager.h" -#include "Core/Config/AchievementSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/CoreTiming.h" @@ -46,7 +42,7 @@ #include "Core/HW/Wiimote.h" #include "Core/Host.h" #include "Core/Movie.h" -#include "Core/NetPlayClient.h" +#include "Core/NetPlayProto.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" @@ -99,7 +95,7 @@ static size_t s_state_writes_in_queue; static std::condition_variable s_state_write_queue_is_empty; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 172; // Last changed in PR 13385 +constexpr u32 STATE_VERSION = 173; // Last changed in PR 13337 // Increase this if the StateExtendedHeader definition changes constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217 diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 388367afd1..21ab1b3fe1 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -337,6 +337,7 @@ + @@ -1000,6 +1001,7 @@ + diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 38d481cdf9..a97ceff755 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -113,6 +113,8 @@ add_executable(dolphin-emu Config/LogConfigWidget.h Config/LogWidget.cpp Config/LogWidget.h + Config/Mapping/BalanceBoardGeneral.cpp + Config/Mapping/BalanceBoardGeneral.h Config/Mapping/FreeLookGeneral.cpp Config/Mapping/FreeLookGeneral.h Config/Mapping/FreeLookRotation.cpp @@ -364,6 +366,8 @@ add_executable(dolphin-emu SkylanderPortal/SkylanderModifyDialog.h SkylanderPortal/SkylanderPortalWindow.cpp SkylanderPortal/SkylanderPortalWindow.h + TAS/BalanceBoardWidget.cpp + TAS/BalanceBoardWidget.h TAS/GCTASInputWindow.cpp TAS/GCTASInputWindow.h TAS/GBATASInputWindow.cpp diff --git a/Source/Core/DolphinQt/Config/Mapping/BalanceBoardGeneral.cpp b/Source/Core/DolphinQt/Config/Mapping/BalanceBoardGeneral.cpp new file mode 100644 index 0000000000..77b75f8265 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/BalanceBoardGeneral.cpp @@ -0,0 +1,49 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Config/Mapping/BalanceBoardGeneral.h" + +#include +#include +#include +#include +#include +#include + +#include "Core/HW/Wiimote.h" +#include "Core/HW/WiimoteEmu/WiimoteEmu.h" + +#include "DolphinQt/Config/Mapping/MappingWindow.h" + +#include "InputCommon/InputConfig.h" + +BalanceBoardGeneral::BalanceBoardGeneral(MappingWindow* window) : MappingWidget(window) +{ + auto* layout = new QHBoxLayout; + + auto& get_group = BalanceBoard::GetBalanceBoardGroup; + using BBG = WiimoteEmu::BalanceBoardGroup; + + layout->addWidget(CreateGroupBox(tr("Buttons"), get_group(GetPort(), BBG::Buttons))); + layout->addWidget(CreateGroupBox(tr("Balance"), get_group(GetPort(), BBG::Balance))); + layout->addWidget(CreateGroupBox(tr("Options"), get_group(GetPort(), BBG::Options))); + layout->addWidget(CreateGroupBox(tr("Left Foot"), get_group(GetPort(), BBG::LeftFoot))); + layout->addWidget(CreateGroupBox(tr("Right Foot"), get_group(GetPort(), BBG::RightFoot))); + + setLayout(layout); +} + +void BalanceBoardGeneral::LoadSettings() +{ + BalanceBoard::LoadConfig(); +} + +void BalanceBoardGeneral::SaveSettings() +{ + BalanceBoard::GetConfig()->SaveConfig(); +} + +InputConfig* BalanceBoardGeneral::GetConfig() +{ + return BalanceBoard::GetConfig(); +} diff --git a/Source/Core/DolphinQt/Config/Mapping/BalanceBoardGeneral.h b/Source/Core/DolphinQt/Config/Mapping/BalanceBoardGeneral.h new file mode 100644 index 0000000000..77d9302975 --- /dev/null +++ b/Source/Core/DolphinQt/Config/Mapping/BalanceBoardGeneral.h @@ -0,0 +1,17 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "DolphinQt/Config/Mapping/MappingWidget.h" + +class BalanceBoardGeneral final : public MappingWidget +{ + Q_OBJECT +public: + explicit BalanceBoardGeneral(MappingWindow* window); + + InputConfig* GetConfig() override; + void LoadSettings() override; + void SaveSettings() override; +}; diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp index aab247dfa0..340e9cb62f 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.cpp @@ -24,6 +24,7 @@ #include "Common/IniFile.h" #include "Common/StringUtil.h" +#include "DolphinQt/Config/Mapping/BalanceBoardGeneral.h" #include "DolphinQt/Config/Mapping/FreeLookGeneral.h" #include "DolphinQt/Config/Mapping/FreeLookRotation.h" #include "DolphinQt/Config/Mapping/GBAPadEmu.h" @@ -467,6 +468,13 @@ void MappingWindow::SetMappingType(MappingWindow::Type type) ShowExtensionMotionTabs(false); break; } + case Type::MAPPING_BALANCE_BOARD_EMU: + { + widget = new BalanceBoardGeneral(this); + setWindowTitle(tr("Balance Board")); + AddWidget(tr("General and Options"), widget); + break; + } case Type::MAPPING_HOTKEYS: { widget = new HotkeyGeneral(this); diff --git a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h index f07aac7fd4..c470aee9ad 100644 --- a/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h +++ b/Source/Core/DolphinQt/Config/Mapping/MappingWindow.h @@ -41,6 +41,7 @@ public: MAPPING_GC_MICROPHONE, // Wii MAPPING_WIIMOTE_EMU, + MAPPING_BALANCE_BOARD_EMU, // Hotkeys MAPPING_HOTKEYS, // Freelook diff --git a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp index 9159402be2..e9c96b2d4a 100644 --- a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp @@ -14,9 +14,6 @@ #include #include -#include -#include - #include "Common/Config/Config.h" #include "Core/Config/MainSettings.h" @@ -26,7 +23,6 @@ #include "Core/HW/Wiimote.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/IOS/IOS.h" -#include "Core/IOS/USB/Bluetooth/BTReal.h" #include "Core/NetPlayProto.h" #include "Core/System.h" #include "Core/WiiUtils.h" @@ -38,8 +34,6 @@ #include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/Settings.h" -#include "UICommon/UICommon.h" - WiimoteControllersWidget::WiimoteControllersWidget(QWidget* parent) : QWidget(parent) { CreateLayout(); @@ -111,7 +105,6 @@ void WiimoteControllersWidget::CreateLayout() m_wiimote_pt_labels[1] = new QLabel(tr("Reset all saved Wii Remote pairings")); m_wiimote_emu = new QRadioButton(tr("Emulate the Wii's Bluetooth adapter")); m_wiimote_continuous_scanning = new QCheckBox(tr("Continuous Scanning")); - m_wiimote_real_balance_board = new QCheckBox(tr("Real Balance Board")); m_wiimote_speaker_data = new QCheckBox(tr("Enable Speaker Data")); m_wiimote_ciface = new QCheckBox(tr("Connect Wii Remotes for Emulated Controllers")); @@ -136,12 +129,22 @@ void WiimoteControllersWidget::CreateLayout() for (size_t i = 0; i < m_wiimote_groups.size(); i++) { - auto* wm_label = m_wiimote_labels[i] = new QLabel(tr("Wii Remote %1").arg(i + 1)); + auto text = (i == WIIMOTE_BALANCE_BOARD ? tr("Balance Board") : tr("Wii Remote %1").arg(i + 1)); + auto* wm_label = m_wiimote_labels[i] = new QLabel(text); auto* wm_box = m_wiimote_boxes[i] = new QComboBox(); auto* wm_button = m_wiimote_buttons[i] = new NonDefaultQPushButton(tr("Configure")); - for (const auto& item : {tr("None"), tr("Emulated Wii Remote"), tr("Real Wii Remote")}) - wm_box->addItem(item); + wm_box->addItem(tr("None")); + if (i == WIIMOTE_BALANCE_BOARD) + { + wm_box->addItem(tr("Emulated Balance Board")); + wm_box->addItem(tr("Real Balance Board")); + } + else + { + wm_box->addItem(tr("Emulated Wii Remote")); + wm_box->addItem(tr("Real Wii Remote")); + } int wm_row = m_wiimote_layout->rowCount(); m_wiimote_layout->addWidget(wm_label, wm_row, 1); @@ -149,7 +152,6 @@ void WiimoteControllersWidget::CreateLayout() m_wiimote_layout->addWidget(wm_button, wm_row, 3); } - m_wiimote_layout->addWidget(m_wiimote_real_balance_board, m_wiimote_layout->rowCount(), 1, 1, -1); m_wiimote_layout->addWidget(m_wiimote_speaker_data, m_wiimote_layout->rowCount(), 1, 1, -1); m_wiimote_layout->addWidget(m_wiimote_ciface, m_wiimote_layout->rowCount(), 0, 1, -1); @@ -185,8 +187,6 @@ void WiimoteControllersWidget::ConnectWidgets() LoadSettings(Core::GetState(Core::System::GetInstance())); }); - connect(m_wiimote_real_balance_board, &QCheckBox::toggled, this, - &WiimoteControllersWidget::SaveSettings); connect(m_wiimote_speaker_data, &QCheckBox::toggled, this, &WiimoteControllersWidget::SaveSettings); connect(m_wiimote_sync, &QPushButton::clicked, this, @@ -248,16 +248,15 @@ void WiimoteControllersWidget::OnWiimoteRefreshPressed() void WiimoteControllersWidget::OnWiimoteConfigure(size_t index) { MappingWindow::Type type; - switch (m_wiimote_boxes[index]->currentIndex()) + if (index == WIIMOTE_BALANCE_BOARD) + { + type = MappingWindow::Type::MAPPING_BALANCE_BOARD_EMU; + // Balance Board is a single entry its own InputConfig object. + index = 0; + } + else { - case 0: // None - case 2: // Real Wii Remote - return; - case 1: // Emulated Wii Remote type = MappingWindow::Type::MAPPING_WIIMOTE_EMU; - break; - default: - return; } MappingWindow* window = new MappingWindow(this, type, static_cast(index)); @@ -274,8 +273,6 @@ void WiimoteControllersWidget::LoadSettings(Core::State state) SignalBlocking(m_wiimote_boxes[i]) ->setCurrentIndex(int(Config::Get(Config::GetInfoForWiimoteSource(int(i))))); } - SignalBlocking(m_wiimote_real_balance_board) - ->setChecked(Config::Get(Config::WIIMOTE_BB_SOURCE) == WiimoteSource::Real); SignalBlocking(m_wiimote_speaker_data) ->setChecked(Config::Get(Config::MAIN_WIIMOTE_ENABLE_SPEAKER)); SignalBlocking(m_wiimote_ciface) @@ -308,7 +305,7 @@ void WiimoteControllersWidget::LoadSettings(Core::State state) for (auto* pt_label : m_wiimote_pt_labels) pt_label->setEnabled(enable_passthrough); - const int num_local_wiimotes = is_netplay ? NetPlay::NumLocalWiimotes() : 4; + const int num_local_wiimotes = is_netplay ? NetPlay::NumLocalWiimotes() : MAX_BBMOTES; for (size_t i = 0; i < m_wiimote_groups.size(); i++) { m_wiimote_labels[i]->setEnabled(enable_emu_bt); @@ -319,7 +316,6 @@ void WiimoteControllersWidget::LoadSettings(Core::State state) static_cast(i) < num_local_wiimotes); } - m_wiimote_real_balance_board->setEnabled(enable_emu_bt && !running_netplay); m_wiimote_speaker_data->setEnabled(enable_emu_bt && !running_netplay); const bool ciface_wiimotes = m_wiimote_ciface->isChecked(); @@ -342,10 +338,6 @@ void WiimoteControllersWidget::SaveSettings() Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED, m_wiimote_passthrough->isChecked()); - const WiimoteSource bb_source = - m_wiimote_real_balance_board->isChecked() ? WiimoteSource::Real : WiimoteSource::None; - Config::SetBaseOrCurrent(Config::WIIMOTE_BB_SOURCE, bb_source); - for (size_t i = 0; i < m_wiimote_groups.size(); i++) { const int index = m_wiimote_boxes[i]->currentIndex(); diff --git a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h index d81e6f0653..5379ce2b1c 100644 --- a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h +++ b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h @@ -7,6 +7,8 @@ #include +#include "Core/HW/Wiimote.h" + class QCheckBox; class QComboBox; class QHBoxLayout; @@ -42,10 +44,10 @@ private: QGroupBox* m_wiimote_box; QGridLayout* m_wiimote_layout; - std::array m_wiimote_labels; - std::array m_wiimote_boxes; - std::array m_wiimote_buttons; - std::array m_wiimote_groups; + std::array m_wiimote_labels; + std::array m_wiimote_boxes; + std::array m_wiimote_buttons; + std::array m_wiimote_groups; std::array m_wiimote_pt_labels; QRadioButton* m_wiimote_emu; @@ -53,7 +55,6 @@ private: QPushButton* m_wiimote_sync; QPushButton* m_wiimote_reset; QCheckBox* m_wiimote_continuous_scanning; - QCheckBox* m_wiimote_real_balance_board; QCheckBox* m_wiimote_speaker_data; QCheckBox* m_wiimote_ciface; QPushButton* m_wiimote_refresh; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 41349a4cee..39012eafd1 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -90,6 +90,7 @@ + @@ -219,6 +220,7 @@ + @@ -312,6 +314,7 @@ + @@ -424,6 +427,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 93ad16a59e..079484978a 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -42,7 +42,6 @@ #include "Core/Boot/Boot.h" #include "Core/BootManager.h" #include "Core/CommonTitles.h" -#include "Core/Config/AchievementSettings.h" #include "Core/Config/MainSettings.h" #include "Core/Config/NetplaySettings.h" #include "Core/Config/UISettings.h" @@ -56,10 +55,8 @@ #include "Core/HW/ProcessorInterface.h" #include "Core/HW/SI/SI_Device.h" #include "Core/HW/Wiimote.h" -#include "Core/HW/WiimoteEmu/WiimoteEmu.h" #include "Core/HotkeyManager.h" #include "Core/IOS/USB/Bluetooth/BTEmu.h" -#include "Core/IOS/USB/Bluetooth/WiimoteDevice.h" #include "Core/Movie.h" #include "Core/NetPlayClient.h" #include "Core/NetPlayProto.h" @@ -108,7 +105,6 @@ #include "DolphinQt/QtUtils/FileOpenEventFilter.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" #include "DolphinQt/QtUtils/ParallelProgressDialog.h" -#include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/QtUtils/RunOnObject.h" #include "DolphinQt/QtUtils/SetWindowDecorations.h" #include "DolphinQt/QtUtils/WindowActivationEventFilter.h" @@ -126,18 +122,14 @@ #include "DolphinQt/WiiUpdate.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" -#include "InputCommon/GCAdapter.h" #include "UICommon/DiscordPresence.h" #include "UICommon/GameFile.h" #include "UICommon/ResourcePack/Manager.h" -#include "UICommon/ResourcePack/Manifest.h" #include "UICommon/ResourcePack/ResourcePack.h" - #include "UICommon/UICommon.h" #include "VideoCommon/NetPlayChatUI.h" -#include "VideoCommon/VideoConfig.h" #ifdef HAVE_XRANDR #include "UICommon/X11Utils.h" @@ -350,12 +342,12 @@ MainWindow::~MainWindow() delete m_render_widget; delete m_netplay_dialog; - for (int i = 0; i < 4; i++) - { - delete m_gc_tas_input_windows[i]; - delete m_gba_tas_input_windows[i]; - delete m_wii_tas_input_windows[i]; - } + for (auto& window : m_gc_tas_input_windows) + delete window; + for (auto& window : m_gba_tas_input_windows) + delete window; + for (auto& window : m_wii_tas_input_windows) + delete window; ShutdownControllers(); @@ -458,13 +450,17 @@ void MainWindow::CreateComponents() m_render_widget = new RenderWidget; m_stack = new QStackedWidget(this); - for (int i = 0; i < 4; i++) + for (int i = 0; i != num_gc_controllers; ++i) { m_gc_tas_input_windows[i] = new GCTASInputWindow(nullptr, i); m_gba_tas_input_windows[i] = new GBATASInputWindow(nullptr, i); - m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i); } + for (int i = 0; i != MAX_WIIMOTES; ++i) + m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i); + + m_wii_tas_input_windows[WIIMOTE_BALANCE_BOARD] = new BalanceBoardTASInputWindow(nullptr); + m_jit_widget = new JITWidget(m_system, this); m_log_widget = new LogWidget(this); m_log_config_widget = new LogConfigWidget(this); @@ -1873,7 +1869,7 @@ void MainWindow::OnStartRecording() Movie::ControllerTypeArray controllers{}; Movie::WiimoteEnabledArray wiimotes{}; - for (int i = 0; i < 4; i++) + for (int i = 0; i < num_gc_controllers; i++) { const SerialInterface::SIDevices si_device = Config::Get(Config::GetInfoForSIDevice(i)); if (si_device == SerialInterface::SIDEVICE_GC_GBA_EMULATED) @@ -1882,9 +1878,11 @@ void MainWindow::OnStartRecording() controllers[i] = Movie::ControllerType::GC; else controllers[i] = Movie::ControllerType::None; - wiimotes[i] = Config::Get(Config::GetInfoForWiimoteSource(i)) != WiimoteSource::None; } + for (int i = 0; i != MAX_BBMOTES; ++i) + wiimotes[i] = Config::Get(Config::GetInfoForWiimoteSource(i)) != WiimoteSource::None; + if (movie.BeginRecordingInput(controllers, wiimotes)) { emit RecordingStatusChanged(true); @@ -1949,7 +1947,7 @@ void MainWindow::ShowTASInput() } } - for (int i = 0; i < num_wii_controllers; i++) + for (int i = 0; i != MAX_BBMOTES; ++i) { if (Config::Get(Config::GetInfoForWiimoteSource(i)) == WiimoteSource::Emulated && (!Core::IsRunning(m_system) || m_system.IsWii())) diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index f9f0f1c95d..5a47f44a3c 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -12,6 +12,8 @@ #include #include "Core/Boot/Boot.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/Wiimote.h" class QMenu; class QStackedWidget; @@ -51,7 +53,6 @@ class SkylanderPortalWindow; class ThreadWidget; class ToolBar; class WatchWidget; -class WiiTASInputWindow; struct WindowSystemInfo; namespace Core @@ -252,11 +253,10 @@ private: NetPlayDialog* m_netplay_dialog; DiscordHandler* m_netplay_discord; NetPlaySetupDialog* m_netplay_setup_dialog; - static constexpr int num_gc_controllers = 4; + static constexpr int num_gc_controllers = SerialInterface::MAX_SI_CHANNELS; std::array m_gc_tas_input_windows{}; std::array m_gba_tas_input_windows{}; - static constexpr int num_wii_controllers = 4; - std::array m_wii_tas_input_windows{}; + std::array m_wii_tas_input_windows{}; #ifdef USE_RETRO_ACHIEVEMENTS AchievementsWindow* m_achievements_window = nullptr; diff --git a/Source/Core/DolphinQt/TAS/BalanceBoardWidget.cpp b/Source/Core/DolphinQt/TAS/BalanceBoardWidget.cpp new file mode 100644 index 0000000000..275042fa8b --- /dev/null +++ b/Source/Core/DolphinQt/TAS/BalanceBoardWidget.cpp @@ -0,0 +1,137 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/TAS/BalanceBoardWidget.h" + +#include +#include + +#include +#include +#include + +#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h" + +BalanceBoardWidget::BalanceBoardWidget(QWidget* parent, QDoubleSpinBox* total_weight_spinbox, + const std::array& sensors) + : QWidget(parent) +{ + setMouseTracking(false); + setToolTip(tr("Left click to set the balance value.\n" + "Right click to return to perfect balance.")); + + using BW = BalanceBoardWidget; + + auto const connect_sensor = [&](QDoubleSpinBox* spinbox, double* this_value) { + connect(this, &BW::UpdateSensorWidgets, this, [this, spinbox, this_value]() { + QSignalBlocker blocker{this}; + spinbox->setValue(*this_value); + }); + connect(spinbox, &QDoubleSpinBox::valueChanged, this, + [this, spinbox, this_value](double new_value) { + if (signalsBlocked()) + return; + *this_value = spinbox->value(); + update(); + UpdateTotalWidget(); + }); + }; + + connect_sensor(sensors[0], &m_sensor_tr); + connect_sensor(sensors[1], &m_sensor_br); + connect_sensor(sensors[2], &m_sensor_tl); + connect_sensor(sensors[3], &m_sensor_bl); + + connect(this, &BW::UpdateTotalWidget, this, [this, box = total_weight_spinbox]() { + QSignalBlocker blocker{this}; + box->setValue(GetTotalWeight()); + }); + connect(total_weight_spinbox, &QDoubleSpinBox::valueChanged, this, [this](double new_total) { + if (signalsBlocked()) + return; + SetTotal(new_total); + }); + + SetTotal(WiimoteEmu::BalanceBoardExt::DEFAULT_WEIGHT); + UpdateTotalWidget(); +} + +double BalanceBoardWidget::GetTotalWeight() +{ + return m_sensor_tr + m_sensor_br + m_sensor_tl + m_sensor_bl; +} + +void BalanceBoardWidget::SetTotal(double total) +{ + const auto cob = GetCenterOfBalance(); + const auto quarter_weight = total * 0.25; + m_sensor_tr = quarter_weight; + m_sensor_br = quarter_weight; + m_sensor_tl = quarter_weight; + m_sensor_bl = quarter_weight; + SetCenterOfBalance(cob); +} + +void BalanceBoardWidget::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + + painter.setBrush(Qt::white); + painter.drawRect(0, 0, width() - 1, height() - 1); + + painter.drawLine(0, height() / 2, width(), height() / 2); + painter.drawLine(width() / 2, 0, width() / 2, height()); + + auto cob = GetCenterOfBalance(); + + // Convert to widget space. + cob.x += 1; + cob.y = 1 - cob.y; + cob *= Common::DVec2(width(), height()) * 0.5; + + painter.drawLine(width() / 2, height() / 2, cob.x, cob.y); + + const int wh_avg = (width() + height()) / 2; + const int radius = wh_avg / 30; + + painter.setBrush(Qt::blue); + painter.drawEllipse(QPointF{cob.x, cob.y}, radius, radius); +} + +void BalanceBoardWidget::mousePressEvent(QMouseEvent* event) +{ + if (event->button() != Qt::RightButton) + return; + + SetCenterOfBalance({0, 0}); +} + +void BalanceBoardWidget::mouseMoveEvent(QMouseEvent* event) +{ + if (event->buttons() != Qt::LeftButton) + return; + + // Convert from widget space to value space. + const double com_x = std::clamp((event->pos().x() * 2.0) / width() - 1, -1.0, 1.0); + const double com_y = std::clamp(1 - (event->pos().y() * 2.0) / height(), -1.0, 1.0); + + SetCenterOfBalance({com_x, com_y}); +} + +void BalanceBoardWidget::SetCenterOfBalance(Common::DVec2 cob) +{ + std::tie(m_sensor_tr, m_sensor_br, m_sensor_tl, m_sensor_bl) = std::tuple_cat( + WiimoteEmu::BalanceBoardExt::CenterOfBalanceToSensors(cob, GetTotalWeight()).data); + + update(); + UpdateSensorWidgets(); +} + +Common::DVec2 BalanceBoardWidget::GetCenterOfBalance() const +{ + return WiimoteEmu::BalanceBoardExt::SensorsToCenterOfBalance( + {m_sensor_tr, m_sensor_br, m_sensor_tl, m_sensor_bl}); +} diff --git a/Source/Core/DolphinQt/TAS/BalanceBoardWidget.h b/Source/Core/DolphinQt/TAS/BalanceBoardWidget.h new file mode 100644 index 0000000000..c0224ea34e --- /dev/null +++ b/Source/Core/DolphinQt/TAS/BalanceBoardWidget.h @@ -0,0 +1,41 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include + +#include "Common/Matrix.h" + +class QDoubleSpinBox; + +class BalanceBoardWidget : public QWidget +{ + Q_OBJECT +signals: + void UpdateSensorWidgets(); + void UpdateTotalWidget(); + +public: + explicit BalanceBoardWidget(QWidget* parent, QDoubleSpinBox* total, + const std::array& sensors); + + void SetTotal(double total_weight); + +protected: + void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + +private: + double GetTotalWeight(); + void SetCenterOfBalance(Common::DVec2); + Common::DVec2 GetCenterOfBalance() const; + + double m_sensor_tr = 0; + double m_sensor_br = 0; + double m_sensor_tl = 0; + double m_sensor_bl = 0; +}; diff --git a/Source/Core/DolphinQt/TAS/IRWidget.cpp b/Source/Core/DolphinQt/TAS/IRWidget.cpp index e9f4033672..3ab4ab89c4 100644 --- a/Source/Core/DolphinQt/TAS/IRWidget.cpp +++ b/Source/Core/DolphinQt/TAS/IRWidget.cpp @@ -54,14 +54,14 @@ void IRWidget::paintEvent(QPaintEvent* event) painter.drawLine(PADDING + w / 2, PADDING, PADDING + w / 2, PADDING + h); // convert from value space to widget space - u16 x = PADDING + ((m_x * w) / IR_MAX_X); - u16 y = PADDING + (h - (m_y * h) / IR_MAX_Y); + const int x = PADDING + ((m_x * w) / IR_MAX_X); + const int y = PADDING + (h - (m_y * h) / IR_MAX_Y); painter.drawLine(PADDING + w / 2, PADDING + h / 2, x, y); painter.setBrush(Qt::blue); - int wh_avg = (w + h) / 2; - int radius = wh_avg / 30; + const int wh_avg = (w + h) / 2; + const int radius = wh_avg / 30; painter.drawEllipse(x - radius, y - radius, radius * 2, radius * 2); } @@ -90,8 +90,8 @@ void IRWidget::handleMouseEvent(QMouseEvent* event) else { // convert from widget space to value space - int new_x = (event->pos().x() * IR_MAX_X) / width(); - int new_y = IR_MAX_Y - (event->pos().y() * IR_MAX_Y) / height(); + const int new_x = (event->pos().x() * IR_MAX_X) / width(); + const int new_y = IR_MAX_Y - (event->pos().y() * IR_MAX_Y) / height(); m_x = std::max(0, std::min(static_cast(IR_MAX_X), new_x)); m_y = std::max(0, std::min(static_cast(IR_MAX_Y), new_y)); diff --git a/Source/Core/DolphinQt/TAS/StickWidget.cpp b/Source/Core/DolphinQt/TAS/StickWidget.cpp index fd43496930..35e7c94b6a 100644 --- a/Source/Core/DolphinQt/TAS/StickWidget.cpp +++ b/Source/Core/DolphinQt/TAS/StickWidget.cpp @@ -57,13 +57,13 @@ void StickWidget::paintEvent(QPaintEvent* event) painter.drawLine(PADDING + diameter / 2, PADDING, PADDING + diameter / 2, PADDING + diameter); // convert from value space to widget space - u16 x = PADDING + ((m_x * diameter) / m_max_x); - u16 y = PADDING + (diameter - (m_y * diameter) / m_max_y); + const int x = PADDING + ((m_x * diameter) / m_max_x); + const int y = PADDING + (diameter - (m_y * diameter) / m_max_y); painter.drawLine(PADDING + diameter / 2, PADDING + diameter / 2, x, y); painter.setBrush(Qt::blue); - int neutral_radius = diameter / 30; + const int neutral_radius = diameter / 30; painter.drawEllipse(x - neutral_radius, y - neutral_radius, neutral_radius * 2, neutral_radius * 2); } @@ -93,8 +93,8 @@ void StickWidget::handleMouseEvent(QMouseEvent* event) else { // convert from widget space to value space - int new_x = (event->pos().x() * m_max_x) / width(); - int new_y = m_max_y - (event->pos().y() * m_max_y) / height(); + const int new_x = (event->pos().x() * m_max_x) / width(); + const int new_y = m_max_y - (event->pos().y() * m_max_y) / height(); m_x = std::max(0, std::min(static_cast(m_max_x), new_x)); m_y = std::max(0, std::min(static_cast(m_max_y), new_y)); diff --git a/Source/Core/DolphinQt/TAS/TASInputWindow.cpp b/Source/Core/DolphinQt/TAS/TASInputWindow.cpp index 8075c6af58..7cf78b72aa 100644 --- a/Source/Core/DolphinQt/TAS/TASInputWindow.cpp +++ b/Source/Core/DolphinQt/TAS/TASInputWindow.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include "DolphinQt/Host.h" #include "DolphinQt/QtUtils/AspectRatioWidget.h" +#include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/Resources.h" #include "DolphinQt/TAS/StickWidget.h" #include "DolphinQt/TAS/TASCheckBox.h" @@ -199,7 +201,7 @@ TASSpinBox* TASInputWindow::CreateSliderValuePair( } // The shortcut_widget argument needs to specify the container widget that will be hidden/shown. -// This is done to avoid ambigous shortcuts +// This is done to avoid ambiguous shortcuts TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_, int max, QKeySequence shortcut_key_sequence, Qt::Orientation orientation, @@ -234,6 +236,60 @@ TASSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int defaul return value; } +QDoubleSpinBox* TASInputWindow::CreateWeightSliderValuePair(std::string_view group_name, + std::string_view control_name, + InputOverrider* overrider, + QBoxLayout* layout, int min, int max, + QKeySequence shortcut_key_sequence, + QWidget* shortcut_widget) +{ + QDoubleSpinBox* value = + CreateWeightSliderValuePair(layout, min, max, shortcut_key_sequence, shortcut_widget); + + InputOverrider::OverrideFunction func = [this, value](ControlState controller_state) { + return GetSpinBox(value, controller_state); + }; + + overrider->AddFunction(group_name, control_name, std::move(func)); + + return value; +} + +// The shortcut_widget argument needs to specify the container widget that will be hidden/shown. +// This is done to avoid ambiguous shortcuts +QDoubleSpinBox* TASInputWindow::CreateWeightSliderValuePair(QBoxLayout* layout, int min, int max, + QKeySequence shortcut_key_sequence, + QWidget* shortcut_widget) +{ + auto* value = new QDoubleSpinBox(); + value->setRange(min, max); + value->setDecimals(2); + value->setSuffix(QStringLiteral("kg")); + auto* slider = new QSlider(Qt::Orientation::Horizontal); + slider->setRange(min * 100, max * 100); + slider->setFocusPolicy(Qt::ClickFocus); + slider->setSingleStep(100); + slider->setPageStep(1000); + slider->setTickPosition(QSlider::TickPosition::TicksBelow); + + connect(slider, &QSlider::valueChanged, value, [value](int i) { value->setValue(i / 100.0); }); + connect(value, &QDoubleSpinBox::valueChanged, slider, [slider](double d) { + QSignalBlocker blocker{slider}; + slider->setValue((int)(d * 100)); + }); + + auto* shortcut = new QShortcut(shortcut_key_sequence, shortcut_widget); + connect(shortcut, &QShortcut::activated, [value] { + value->setFocus(); + value->selectAll(); + }); + + layout->addWidget(slider); + layout->addWidget(value); + + return value; +} + std::optional TASInputWindow::GetButton(TASCheckBox* checkbox, ControlState controller_state) { @@ -267,6 +323,27 @@ std::optional TASInputWindow::GetSpinBox(TASSpinBox* spin, int zer return (spin->GetValue() - zero) / scale; } +std::optional TASInputWindow::GetSpinBox(QDoubleSpinBox* spin, + ControlState controller_state) +{ + if (m_use_controller->isChecked()) + { + if (!m_spinbox_most_recent_values_double.count(spin) || + m_spinbox_most_recent_values_double[spin] != controller_state) + { + QueueOnObjectBlocking(spin, [spin, controller_state] { spin->setValue(controller_state); }); + } + + m_spinbox_most_recent_values_double[spin] = controller_state; + } + else + { + m_spinbox_most_recent_values_double.clear(); + } + + return spin->value(); +} + void TASInputWindow::changeEvent(QEvent* const event) { if (event->type() == QEvent::ActivationChange) diff --git a/Source/Core/DolphinQt/TAS/TASInputWindow.h b/Source/Core/DolphinQt/TAS/TASInputWindow.h index 924e53ac87..d5908f9c82 100644 --- a/Source/Core/DolphinQt/TAS/TASInputWindow.h +++ b/Source/Core/DolphinQt/TAS/TASInputWindow.h @@ -18,6 +18,7 @@ class QBoxLayout; class QCheckBox; class QDialog; +class QDoubleSpinBox; class QEvent; class QGroupBox; class QSpinBox; @@ -68,6 +69,14 @@ protected: TASSpinBox* CreateSliderValuePair(QBoxLayout* layout, int default_, int max, QKeySequence shortcut_key_sequence, Qt::Orientation orientation, QWidget* shortcut_widget); + QDoubleSpinBox* CreateWeightSliderValuePair(std::string_view group_name, + std::string_view control_name, + InputOverrider* overrider, QBoxLayout* layout, + int min, int max, QKeySequence shortcut_key_sequence, + QWidget* shortcut_widget); + QDoubleSpinBox* CreateWeightSliderValuePair(QBoxLayout* layout, int min, int max, + QKeySequence shortcut_key_sequence, + QWidget* shortcut_widget); void changeEvent(QEvent* event) override; @@ -82,4 +91,7 @@ private: ControlState controller_state); std::optional GetSpinBox(TASSpinBox* spin, int zero, ControlState controller_state, ControlState scale); + std::optional GetSpinBox(QDoubleSpinBox* spin, ControlState controller_state); + + std::map m_spinbox_most_recent_values_double; }; diff --git a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp index f3faeb6583..f495e92825 100644 --- a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp +++ b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp @@ -19,22 +19,22 @@ #include "Core/Core.h" #include "Core/HW/Wiimote.h" +#include "Core/HW/WiimoteEmu/Extension/BalanceBoard.h" #include "Core/HW/WiimoteEmu/Extension/Classic.h" #include "Core/HW/WiimoteEmu/Extension/Extension.h" #include "Core/HW/WiimoteEmu/Extension/Nunchuk.h" #include "Core/HW/WiimoteEmu/MotionPlus.h" #include "Core/HW/WiimoteEmu/WiimoteEmu.h" -#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/System.h" #include "DolphinQt/QtUtils/AspectRatioWidget.h" #include "DolphinQt/QtUtils/QueueOnObject.h" #include "DolphinQt/QtUtils/SetWindowDecorations.h" +#include "DolphinQt/TAS/BalanceBoardWidget.h" #include "DolphinQt/TAS/IRWidget.h" #include "DolphinQt/TAS/TASCheckBox.h" #include "DolphinQt/TAS/TASSpinBox.h" -#include "InputCommon/ControllerEmu/ControlGroup/Attachments.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" #include "InputCommon/ControllerEmu/StickGate.h" #include "InputCommon/InputConfig.h" @@ -55,26 +55,26 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( const int ir_y_center = static_cast(std::round(IRWidget::IR_MAX_Y / 2.)); auto* x_layout = new QHBoxLayout; - m_ir_x_value = CreateSliderValuePair( + auto* const ir_x_value = CreateSliderValuePair( WiimoteEmu::Wiimote::IR_GROUP, ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE, &m_wiimote_overrider, x_layout, ir_x_center, ir_x_center, IRWidget::IR_MIN_X, IRWidget::IR_MAX_X, ir_x_shortcut_key_sequence, Qt::Horizontal, m_ir_box); auto* y_layout = new QVBoxLayout; - m_ir_y_value = CreateSliderValuePair( + auto* const ir_y_value = CreateSliderValuePair( WiimoteEmu::Wiimote::IR_GROUP, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE, &m_wiimote_overrider, y_layout, ir_y_center, ir_y_center, IRWidget::IR_MIN_Y, IRWidget::IR_MAX_Y, ir_y_shortcut_key_sequence, Qt::Vertical, m_ir_box); - m_ir_y_value->setMaximumWidth(60); + ir_y_value->setMaximumWidth(60); auto* visual = new IRWidget(this); visual->SetX(ir_x_center); visual->SetY(ir_y_center); - connect(m_ir_x_value, &QSpinBox::valueChanged, visual, &IRWidget::SetX); - connect(m_ir_y_value, &QSpinBox::valueChanged, visual, &IRWidget::SetY); - connect(visual, &IRWidget::ChangedX, m_ir_x_value, &QSpinBox::setValue); - connect(visual, &IRWidget::ChangedY, m_ir_y_value, &QSpinBox::setValue); + connect(ir_x_value, &QSpinBox::valueChanged, visual, &IRWidget::SetX); + connect(ir_y_value, &QSpinBox::valueChanged, visual, &IRWidget::SetY); + connect(visual, &IRWidget::ChangedX, ir_x_value, &QSpinBox::setValue); + connect(visual, &IRWidget::ChangedY, ir_y_value, &QSpinBox::setValue); auto* visual_ar = new AspectRatioWidget(visual, IRWidget::IR_MAX_X, IRWidget::IR_MAX_Y); @@ -223,48 +223,49 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( triggers_layout->addLayout(r_trigger_layout); m_triggers_box->setLayout(triggers_layout); - m_a_button = CreateButton(QStringLiteral("&A"), WiimoteEmu::Wiimote::BUTTONS_GROUP, - WiimoteEmu::Wiimote::A_BUTTON, &m_wiimote_overrider); - m_b_button = CreateButton(QStringLiteral("&B"), WiimoteEmu::Wiimote::BUTTONS_GROUP, - WiimoteEmu::Wiimote::B_BUTTON, &m_wiimote_overrider); - m_1_button = CreateButton(QStringLiteral("&1"), WiimoteEmu::Wiimote::BUTTONS_GROUP, - WiimoteEmu::Wiimote::ONE_BUTTON, &m_wiimote_overrider); - m_2_button = CreateButton(QStringLiteral("&2"), WiimoteEmu::Wiimote::BUTTONS_GROUP, - WiimoteEmu::Wiimote::TWO_BUTTON, &m_wiimote_overrider); - m_plus_button = CreateButton(QStringLiteral("&+"), WiimoteEmu::Wiimote::BUTTONS_GROUP, - WiimoteEmu::Wiimote::PLUS_BUTTON, &m_wiimote_overrider); - m_minus_button = CreateButton(QStringLiteral("&-"), WiimoteEmu::Wiimote::BUTTONS_GROUP, - WiimoteEmu::Wiimote::MINUS_BUTTON, &m_wiimote_overrider); - m_home_button = CreateButton(QStringLiteral("&HOME"), WiimoteEmu::Wiimote::BUTTONS_GROUP, - WiimoteEmu::Wiimote::HOME_BUTTON, &m_wiimote_overrider); + auto* const a_button = CreateButton(QStringLiteral("&A"), WiimoteEmu::Wiimote::BUTTONS_GROUP, + WiimoteEmu::Wiimote::A_BUTTON, &m_wiimote_overrider); + auto* const b_button = CreateButton(QStringLiteral("&B"), WiimoteEmu::Wiimote::BUTTONS_GROUP, + WiimoteEmu::Wiimote::B_BUTTON, &m_wiimote_overrider); + auto* const button_1 = CreateButton(QStringLiteral("&1"), WiimoteEmu::Wiimote::BUTTONS_GROUP, + WiimoteEmu::Wiimote::ONE_BUTTON, &m_wiimote_overrider); + auto* const button_2 = CreateButton(QStringLiteral("&2"), WiimoteEmu::Wiimote::BUTTONS_GROUP, + WiimoteEmu::Wiimote::TWO_BUTTON, &m_wiimote_overrider); + auto* const plus_button = CreateButton(QStringLiteral("&+"), WiimoteEmu::Wiimote::BUTTONS_GROUP, + WiimoteEmu::Wiimote::PLUS_BUTTON, &m_wiimote_overrider); + auto* const minus_button = CreateButton(QStringLiteral("&-"), WiimoteEmu::Wiimote::BUTTONS_GROUP, + WiimoteEmu::Wiimote::MINUS_BUTTON, &m_wiimote_overrider); + auto* const home_button = + CreateButton(QStringLiteral("&HOME"), WiimoteEmu::Wiimote::BUTTONS_GROUP, + WiimoteEmu::Wiimote::HOME_BUTTON, &m_wiimote_overrider); - m_left_button = CreateButton(QStringLiteral("&Left"), WiimoteEmu::Wiimote::DPAD_GROUP, - DIRECTION_LEFT, &m_wiimote_overrider); - m_up_button = CreateButton(QStringLiteral("&Up"), WiimoteEmu::Wiimote::DPAD_GROUP, DIRECTION_UP, - &m_wiimote_overrider); - m_down_button = CreateButton(QStringLiteral("&Down"), WiimoteEmu::Wiimote::DPAD_GROUP, - DIRECTION_DOWN, &m_wiimote_overrider); - m_right_button = CreateButton(QStringLiteral("&Right"), WiimoteEmu::Wiimote::DPAD_GROUP, - DIRECTION_RIGHT, &m_wiimote_overrider); + auto* const left_button = CreateButton(QStringLiteral("&Left"), WiimoteEmu::Wiimote::DPAD_GROUP, + DIRECTION_LEFT, &m_wiimote_overrider); + auto* const up_button = CreateButton(QStringLiteral("&Up"), WiimoteEmu::Wiimote::DPAD_GROUP, + DIRECTION_UP, &m_wiimote_overrider); + auto* const down_button = CreateButton(QStringLiteral("&Down"), WiimoteEmu::Wiimote::DPAD_GROUP, + DIRECTION_DOWN, &m_wiimote_overrider); + auto* const right_button = CreateButton(QStringLiteral("&Right"), WiimoteEmu::Wiimote::DPAD_GROUP, + DIRECTION_RIGHT, &m_wiimote_overrider); - m_c_button = CreateButton(QStringLiteral("&C"), WiimoteEmu::Nunchuk::BUTTONS_GROUP, - WiimoteEmu::Nunchuk::C_BUTTON, &m_nunchuk_overrider); - m_z_button = CreateButton(QStringLiteral("&Z"), WiimoteEmu::Nunchuk::BUTTONS_GROUP, - WiimoteEmu::Nunchuk::Z_BUTTON, &m_nunchuk_overrider); + auto* const c_button = CreateButton(QStringLiteral("&C"), WiimoteEmu::Nunchuk::BUTTONS_GROUP, + WiimoteEmu::Nunchuk::C_BUTTON, &m_nunchuk_overrider); + auto* const z_button = CreateButton(QStringLiteral("&Z"), WiimoteEmu::Nunchuk::BUTTONS_GROUP, + WiimoteEmu::Nunchuk::Z_BUTTON, &m_nunchuk_overrider); auto* buttons_layout = new QGridLayout; - buttons_layout->addWidget(m_a_button, 0, 0); - buttons_layout->addWidget(m_b_button, 0, 1); - buttons_layout->addWidget(m_1_button, 0, 2); - buttons_layout->addWidget(m_2_button, 0, 3); - buttons_layout->addWidget(m_plus_button, 0, 4); - buttons_layout->addWidget(m_minus_button, 0, 5); + buttons_layout->addWidget(a_button, 0, 0); + buttons_layout->addWidget(b_button, 0, 1); + buttons_layout->addWidget(button_1, 0, 2); + buttons_layout->addWidget(button_2, 0, 3); + buttons_layout->addWidget(plus_button, 0, 4); + buttons_layout->addWidget(minus_button, 0, 5); - buttons_layout->addWidget(m_home_button, 1, 0); - buttons_layout->addWidget(m_left_button, 1, 1); - buttons_layout->addWidget(m_up_button, 1, 2); - buttons_layout->addWidget(m_down_button, 1, 3); - buttons_layout->addWidget(m_right_button, 1, 4); + buttons_layout->addWidget(home_button, 1, 0); + buttons_layout->addWidget(left_button, 1, 1); + buttons_layout->addWidget(up_button, 1, 2); + buttons_layout->addWidget(down_button, 1, 3); + buttons_layout->addWidget(right_button, 1, 4); buttons_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding), 0, 7); @@ -272,63 +273,70 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( m_remote_buttons_box->setLayout(buttons_layout); auto* nunchuk_buttons_layout = new QHBoxLayout; - nunchuk_buttons_layout->addWidget(m_c_button); - nunchuk_buttons_layout->addWidget(m_z_button); + nunchuk_buttons_layout->addWidget(c_button); + nunchuk_buttons_layout->addWidget(z_button); nunchuk_buttons_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding)); m_nunchuk_buttons_box = new QGroupBox(tr("Nunchuk Buttons")); m_nunchuk_buttons_box->setLayout(nunchuk_buttons_layout); - m_classic_a_button = CreateButton(QStringLiteral("&A"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::A_BUTTON, &m_classic_overrider); - m_classic_b_button = CreateButton(QStringLiteral("&B"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::B_BUTTON, &m_classic_overrider); - m_classic_x_button = CreateButton(QStringLiteral("&X"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::X_BUTTON, &m_classic_overrider); - m_classic_y_button = CreateButton(QStringLiteral("&Y"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::Y_BUTTON, &m_classic_overrider); - m_classic_zl_button = CreateButton(QStringLiteral("&ZL"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::ZL_BUTTON, &m_classic_overrider); - m_classic_zr_button = CreateButton(QStringLiteral("ZR"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::ZR_BUTTON, &m_classic_overrider); - m_classic_plus_button = CreateButton(QStringLiteral("&+"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::PLUS_BUTTON, &m_classic_overrider); - m_classic_minus_button = CreateButton(QStringLiteral("&-"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::MINUS_BUTTON, &m_classic_overrider); - m_classic_home_button = CreateButton(QStringLiteral("&HOME"), WiimoteEmu::Classic::BUTTONS_GROUP, - WiimoteEmu::Classic::HOME_BUTTON, &m_classic_overrider); + auto& wcbg = WiimoteEmu::Classic::BUTTONS_GROUP; + using WiimoteEmu::Classic; + auto* const classic_a_button = + CreateButton(QStringLiteral("&A"), wcbg, Classic::A_BUTTON, &m_classic_overrider); + auto* const classic_b_button = + CreateButton(QStringLiteral("&B"), wcbg, Classic::B_BUTTON, &m_classic_overrider); + auto* const classic_x_button = + CreateButton(QStringLiteral("&X"), wcbg, Classic::X_BUTTON, &m_classic_overrider); + auto* const classic_y_button = + CreateButton(QStringLiteral("&Y"), wcbg, Classic::Y_BUTTON, &m_classic_overrider); + auto* const classic_zl_button = + CreateButton(QStringLiteral("&ZL"), wcbg, Classic::ZL_BUTTON, &m_classic_overrider); + auto* const classic_zr_button = + CreateButton(QStringLiteral("ZR"), wcbg, Classic::ZR_BUTTON, &m_classic_overrider); + auto* const classic_plus_button = + CreateButton(QStringLiteral("&+"), wcbg, Classic::PLUS_BUTTON, &m_classic_overrider); + auto* const classic_minus_button = + CreateButton(QStringLiteral("&-"), wcbg, Classic::MINUS_BUTTON, &m_classic_overrider); + auto* const classic_home_button = + CreateButton(QStringLiteral("&HOME"), wcbg, Classic::HOME_BUTTON, &m_classic_overrider); - m_classic_l_button = CreateButton(QStringLiteral("&L"), WiimoteEmu::Classic::TRIGGERS_GROUP, - WiimoteEmu::Classic::L_DIGITAL, &m_classic_overrider); - m_classic_r_button = CreateButton(QStringLiteral("&R"), WiimoteEmu::Classic::TRIGGERS_GROUP, - WiimoteEmu::Classic::R_DIGITAL, &m_classic_overrider); + auto* const classic_l_button = + CreateButton(QStringLiteral("&L"), WiimoteEmu::Classic::TRIGGERS_GROUP, + WiimoteEmu::Classic::L_DIGITAL, &m_classic_overrider); + auto* const classic_r_button = + CreateButton(QStringLiteral("&R"), WiimoteEmu::Classic::TRIGGERS_GROUP, + WiimoteEmu::Classic::R_DIGITAL, &m_classic_overrider); - m_classic_left_button = CreateButton(QStringLiteral("L&eft"), WiimoteEmu::Classic::DPAD_GROUP, - DIRECTION_LEFT, &m_classic_overrider); - m_classic_up_button = CreateButton(QStringLiteral("&Up"), WiimoteEmu::Classic::DPAD_GROUP, - DIRECTION_UP, &m_classic_overrider); - m_classic_down_button = CreateButton(QStringLiteral("&Down"), WiimoteEmu::Classic::DPAD_GROUP, - DIRECTION_DOWN, &m_classic_overrider); - m_classic_right_button = CreateButton(QStringLiteral("R&ight"), WiimoteEmu::Classic::DPAD_GROUP, - DIRECTION_RIGHT, &m_classic_overrider); + auto* const classic_left_button = + CreateButton(QStringLiteral("L&eft"), WiimoteEmu::Classic::DPAD_GROUP, DIRECTION_LEFT, + &m_classic_overrider); + auto* const classic_up_button = CreateButton( + QStringLiteral("&Up"), WiimoteEmu::Classic::DPAD_GROUP, DIRECTION_UP, &m_classic_overrider); + auto* const classic_down_button = + CreateButton(QStringLiteral("&Down"), WiimoteEmu::Classic::DPAD_GROUP, DIRECTION_DOWN, + &m_classic_overrider); + auto* const classic_right_button = + CreateButton(QStringLiteral("R&ight"), WiimoteEmu::Classic::DPAD_GROUP, DIRECTION_RIGHT, + &m_classic_overrider); auto* classic_buttons_layout = new QGridLayout; - classic_buttons_layout->addWidget(m_classic_a_button, 0, 0); - classic_buttons_layout->addWidget(m_classic_b_button, 0, 1); - classic_buttons_layout->addWidget(m_classic_x_button, 0, 2); - classic_buttons_layout->addWidget(m_classic_y_button, 0, 3); - classic_buttons_layout->addWidget(m_classic_l_button, 0, 4); - classic_buttons_layout->addWidget(m_classic_r_button, 0, 5); - classic_buttons_layout->addWidget(m_classic_zl_button, 0, 6); - classic_buttons_layout->addWidget(m_classic_zr_button, 0, 7); + classic_buttons_layout->addWidget(classic_a_button, 0, 0); + classic_buttons_layout->addWidget(classic_b_button, 0, 1); + classic_buttons_layout->addWidget(classic_x_button, 0, 2); + classic_buttons_layout->addWidget(classic_y_button, 0, 3); + classic_buttons_layout->addWidget(classic_l_button, 0, 4); + classic_buttons_layout->addWidget(classic_r_button, 0, 5); + classic_buttons_layout->addWidget(classic_zl_button, 0, 6); + classic_buttons_layout->addWidget(classic_zr_button, 0, 7); - classic_buttons_layout->addWidget(m_classic_plus_button, 1, 0); - classic_buttons_layout->addWidget(m_classic_minus_button, 1, 1); - classic_buttons_layout->addWidget(m_classic_home_button, 1, 2); - classic_buttons_layout->addWidget(m_classic_left_button, 1, 3); - classic_buttons_layout->addWidget(m_classic_up_button, 1, 4); - classic_buttons_layout->addWidget(m_classic_down_button, 1, 5); - classic_buttons_layout->addWidget(m_classic_right_button, 1, 6); + classic_buttons_layout->addWidget(classic_plus_button, 1, 0); + classic_buttons_layout->addWidget(classic_minus_button, 1, 1); + classic_buttons_layout->addWidget(classic_home_button, 1, 2); + classic_buttons_layout->addWidget(classic_left_button, 1, 3); + classic_buttons_layout->addWidget(classic_up_button, 1, 4); + classic_buttons_layout->addWidget(classic_down_button, 1, 5); + classic_buttons_layout->addWidget(classic_right_button, 1, 6); classic_buttons_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding), 0, 8); @@ -349,21 +357,96 @@ WiiTASInputWindow::WiiTASInputWindow(QWidget* parent, int num) : TASInputWindow( setLayout(layout); } +BalanceBoardTASInputWindow::BalanceBoardTASInputWindow(QWidget* parent) : TASInputWindow{parent} +{ + setWindowTitle(tr("Wii TAS Input %1 - Balance Board").arg(WIIMOTE_BALANCE_BOARD + 1)); + + const QKeySequence balance_tl_keyseq = QKeySequence(Qt::ALT | Qt::Key_L); + const QKeySequence balance_tr_keyseq = QKeySequence(Qt::ALT | Qt::Key_R); + const QKeySequence balance_bl_keyseq = QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_L); + const QKeySequence balance_br_keyseq = QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_R); + const QKeySequence balance_weight_keyseq = QKeySequence(Qt::ALT | Qt::Key_W); + + auto* const balance_board_box = + new QGroupBox(QStringLiteral("%1 (%2/%3/%4)") + .arg(tr("Balance"), balance_tl_keyseq.toString(QKeySequence::NativeText), + balance_tr_keyseq.toString(QKeySequence::NativeText), + balance_weight_keyseq.toString(QKeySequence::NativeText))); + SetQWidgetWindowDecorations(balance_board_box); + + // How heavy do the TASers want to pretend to be? + constexpr int max_total_kg = 136; + // FYI, a lifted board shows about -4kg (the weight of the board itself). + // But I don't think TASers necessarily need to simulate that. + // They'd probably prefer the bottom of the slider be 0kg. + constexpr int min_total_kg = 0; + constexpr int max_sensor_value = max_total_kg; + constexpr int min_sensor_value = min_total_kg / 4; + + using BBExt = WiimoteEmu::BalanceBoardExt; + auto& balance_group = BBExt::BALANCE_GROUP; + auto* bal_top_layout = new QHBoxLayout; + auto* const balance_value_tl = CreateWeightSliderValuePair( + balance_group, BBExt::SENSOR_TL, &m_balance_board_overrider, bal_top_layout, min_sensor_value, + max_sensor_value, balance_tl_keyseq, balance_board_box); + auto* const balance_value_tr = CreateWeightSliderValuePair( + balance_group, BBExt::SENSOR_TR, &m_balance_board_overrider, bal_top_layout, min_sensor_value, + max_sensor_value, balance_tr_keyseq, balance_board_box); + + auto* bal_bottom_layout = new QHBoxLayout; + auto* const balance_value_bl = CreateWeightSliderValuePair( + balance_group, BBExt::SENSOR_BL, &m_balance_board_overrider, bal_bottom_layout, + min_sensor_value, max_sensor_value, balance_bl_keyseq, balance_board_box); + auto* const balance_value_br = CreateWeightSliderValuePair( + balance_group, BBExt::SENSOR_BR, &m_balance_board_overrider, bal_bottom_layout, + min_sensor_value, max_sensor_value, balance_br_keyseq, balance_board_box); + + auto* bal_weight_layout = new QHBoxLayout; + auto* const total_weight_value = CreateWeightSliderValuePair( + bal_weight_layout, min_total_kg, max_total_kg, balance_weight_keyseq, balance_board_box); + + auto* bal_visual = new BalanceBoardWidget( + this, total_weight_value, + {balance_value_tr, balance_value_br, balance_value_tl, balance_value_bl}); + + auto* bal_ar = new AspectRatioWidget(bal_visual, 20, 12); + bal_ar->setMinimumHeight(120); + auto* bal_visual_layout = new QHBoxLayout; + bal_visual_layout->addWidget(bal_ar); + + auto* const bboard_power_button = + CreateButton(QStringLiteral("&Power Button"), WiimoteEmu::BalanceBoard::BUTTONS_GROUP_NAME, + WiimoteEmu::BalanceBoard::BUTTON_POWER_NAME, &m_wiimote_overrider); + + auto* bal_layout = new QVBoxLayout; + bal_layout->addLayout(bal_top_layout); + bal_layout->addLayout(bal_visual_layout); + bal_layout->addLayout(bal_bottom_layout); + bal_layout->addLayout(bal_weight_layout); + bal_layout->addWidget(bboard_power_button); + + balance_board_box->setLayout(bal_layout); + + auto* top_layout = new QHBoxLayout; + top_layout->addWidget(balance_board_box); + + auto* layout = new QVBoxLayout; + layout->addLayout(top_layout); + layout->addWidget(m_settings_box); + + setLayout(layout); + + adjustSize(); +} + WiimoteEmu::Wiimote* WiiTASInputWindow::GetWiimote() { return static_cast(Wiimote::GetConfig()->GetController(m_num)); } -ControllerEmu::Attachments* WiiTASInputWindow::GetAttachments() -{ - return static_cast( - GetWiimote()->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments)); -} - WiimoteEmu::Extension* WiiTASInputWindow::GetExtension() { - return static_cast( - GetAttachments()->GetAttachmentList()[m_active_extension].get()); + return GetWiimote()->GetActiveExtension(); } void WiiTASInputWindow::UpdateExtension(const int extension) @@ -425,69 +508,59 @@ void WiiTASInputWindow::LoadExtensionAndMotionPlus() QueueOnObject(this, [this, attached] { UpdateMotionPlus(attached); }); }); m_attachment_callback_id = - GetAttachments()->GetAttachmentSetting().AddCallback([this](const int extension_index) { + wiimote->GetAttachmentSetting().AddCallback([this](const int extension_index) { QueueOnObject(this, [this, extension_index] { UpdateExtension(extension_index); }); }); } void WiiTASInputWindow::UpdateControlVisibility() { - if (m_active_extension == WiimoteEmu::ExtensionNumber::NUNCHUK) - { - setWindowTitle(tr("Wii TAS Input %1 - Wii Remote + Nunchuk").arg(m_num + 1)); - SetQWidgetWindowDecorations(m_ir_box); - m_ir_box->show(); - SetQWidgetWindowDecorations(m_nunchuk_stick_box); - m_nunchuk_stick_box->show(); - m_classic_right_stick_box->hide(); - m_classic_left_stick_box->hide(); - SetQWidgetWindowDecorations(m_remote_accelerometer_box); - m_remote_accelerometer_box->show(); - m_remote_gyroscope_box->setVisible(m_is_motion_plus_attached); - SetQWidgetWindowDecorations(m_nunchuk_accelerometer_box); - m_nunchuk_accelerometer_box->show(); - m_triggers_box->hide(); - SetQWidgetWindowDecorations(m_nunchuk_buttons_box); - m_nunchuk_buttons_box->show(); - SetQWidgetWindowDecorations(m_remote_buttons_box); - m_remote_buttons_box->show(); - m_classic_buttons_box->hide(); - } - else if (m_active_extension == WiimoteEmu::ExtensionNumber::CLASSIC) + m_ir_box->hide(); + m_remote_accelerometer_box->hide(); + m_remote_gyroscope_box->hide(); + m_nunchuk_buttons_box->hide(); + m_remote_buttons_box->hide(); + m_nunchuk_accelerometer_box->hide(); + m_nunchuk_stick_box->hide(); + m_classic_right_stick_box->hide(); + m_classic_left_stick_box->hide(); + m_classic_buttons_box->hide(); + m_triggers_box->hide(); + + const auto show_box = [](QGroupBox* box) { + SetQWidgetWindowDecorations(box); + box->show(); + }; + + if (m_active_extension == WiimoteEmu::ExtensionNumber::CLASSIC) { setWindowTitle(tr("Wii TAS Input %1 - Classic Controller").arg(m_num + 1)); - m_ir_box->hide(); - m_nunchuk_stick_box->hide(); - SetQWidgetWindowDecorations(m_classic_right_stick_box); - m_classic_right_stick_box->show(); - SetQWidgetWindowDecorations(m_classic_left_stick_box); - m_classic_left_stick_box->show(); - m_remote_accelerometer_box->hide(); - m_remote_gyroscope_box->hide(); - m_nunchuk_accelerometer_box->hide(); - SetQWidgetWindowDecorations(m_triggers_box); - m_triggers_box->show(); - m_remote_buttons_box->hide(); - m_nunchuk_buttons_box->hide(); - SetQWidgetWindowDecorations(m_classic_buttons_box); - m_classic_buttons_box->show(); + + show_box(m_classic_right_stick_box); + show_box(m_classic_left_stick_box); + show_box(m_triggers_box); + show_box(m_classic_buttons_box); } else { - setWindowTitle(tr("Wii TAS Input %1 - Wii Remote").arg(m_num + 1)); - m_ir_box->show(); - m_nunchuk_stick_box->hide(); - m_classic_right_stick_box->hide(); - m_classic_left_stick_box->hide(); - SetQWidgetWindowDecorations(m_remote_accelerometer_box); - m_remote_accelerometer_box->show(); - m_remote_gyroscope_box->setVisible(m_is_motion_plus_attached); - m_nunchuk_accelerometer_box->hide(); - m_triggers_box->hide(); - SetQWidgetWindowDecorations(m_remote_buttons_box); - m_remote_buttons_box->show(); - m_nunchuk_buttons_box->hide(); - m_classic_buttons_box->hide(); + show_box(m_ir_box); + show_box(m_remote_accelerometer_box); + show_box(m_remote_buttons_box); + if (m_is_motion_plus_attached) + show_box(m_remote_gyroscope_box); + + if (m_active_extension == WiimoteEmu::ExtensionNumber::NUNCHUK) + { + setWindowTitle(tr("Wii TAS Input %1 - Wii Remote + Nunchuk").arg(m_num + 1)); + + show_box(m_nunchuk_stick_box); + show_box(m_nunchuk_accelerometer_box); + show_box(m_nunchuk_buttons_box); + } + else + { + setWindowTitle(tr("Wii TAS Input %1 - Wii Remote").arg(m_num + 1)); + } } // Without these calls, switching between attachments can result in the Stick/IRWidgets being @@ -501,10 +574,7 @@ void WiiTASInputWindow::hideEvent(QHideEvent* const event) WiimoteEmu::Wiimote* const wiimote = GetWiimote(); wiimote->ClearInputOverrideFunction(); - wiimote->GetMotionPlusSetting().RemoveCallback(m_motion_plus_callback_id); - GetExtension()->ClearInputOverrideFunction(); - GetAttachments()->GetAttachmentSetting().RemoveCallback(m_attachment_callback_id); TASInputWindow::hideEvent(event); } @@ -516,6 +586,32 @@ void WiiTASInputWindow::showEvent(QShowEvent* const event) TASInputWindow::showEvent(event); } +WiimoteEmu::BalanceBoard* BalanceBoardTASInputWindow::GetBalanceBoard() const +{ + return static_cast(BalanceBoard::GetConfig()->GetController(0)); +} + +void BalanceBoardTASInputWindow::hideEvent(QHideEvent* const event) +{ + auto* const board = GetBalanceBoard(); + + board->ClearInputOverrideFunction(); + board->GetActiveExtension()->ClearInputOverrideFunction(); + + TASInputWindow::hideEvent(event); +} + +void BalanceBoardTASInputWindow::showEvent(QShowEvent* const event) +{ + auto* const board = GetBalanceBoard(); + + board->SetInputOverrideFunction(m_wiimote_overrider.GetInputOverrideFunction()); + board->GetActiveExtension()->SetInputOverrideFunction( + m_balance_board_overrider.GetInputOverrideFunction()); + + TASInputWindow::showEvent(event); +} + void WiiTASInputWindow::UpdateInputOverrideFunction() { WiimoteEmu::Wiimote* const wiimote = GetWiimote(); diff --git a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h index a7a413e864..40d1a08377 100644 --- a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h +++ b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.h @@ -18,13 +18,9 @@ namespace WiimoteEmu { class Extension; class Wiimote; +class BalanceBoard; } // namespace WiimoteEmu -namespace ControllerEmu -{ -class Attachments; -} - class WiiTASInputWindow : public TASInputWindow { Q_OBJECT @@ -39,7 +35,6 @@ public: private: WiimoteEmu::Wiimote* GetWiimote(); - ControllerEmu::Attachments* GetAttachments(); WiimoteEmu::Extension* GetExtension(); void LoadExtensionAndMotionPlus(); @@ -56,36 +51,6 @@ private: InputOverrider m_nunchuk_overrider; InputOverrider m_classic_overrider; - TASCheckBox* m_a_button; - TASCheckBox* m_b_button; - TASCheckBox* m_1_button; - TASCheckBox* m_2_button; - TASCheckBox* m_plus_button; - TASCheckBox* m_minus_button; - TASCheckBox* m_home_button; - TASCheckBox* m_left_button; - TASCheckBox* m_up_button; - TASCheckBox* m_down_button; - TASCheckBox* m_right_button; - TASCheckBox* m_c_button; - TASCheckBox* m_z_button; - TASCheckBox* m_classic_a_button; - TASCheckBox* m_classic_b_button; - TASCheckBox* m_classic_x_button; - TASCheckBox* m_classic_y_button; - TASCheckBox* m_classic_plus_button; - TASCheckBox* m_classic_minus_button; - TASCheckBox* m_classic_l_button; - TASCheckBox* m_classic_r_button; - TASCheckBox* m_classic_zl_button; - TASCheckBox* m_classic_zr_button; - TASCheckBox* m_classic_home_button; - TASCheckBox* m_classic_left_button; - TASCheckBox* m_classic_up_button; - TASCheckBox* m_classic_down_button; - TASCheckBox* m_classic_right_button; - TASSpinBox* m_ir_x_value; - TASSpinBox* m_ir_y_value; QGroupBox* m_remote_accelerometer_box; QGroupBox* m_remote_gyroscope_box; QGroupBox* m_nunchuk_accelerometer_box; @@ -98,3 +63,18 @@ private: QGroupBox* m_classic_buttons_box; QGroupBox* m_triggers_box; }; + +class BalanceBoardTASInputWindow : public TASInputWindow +{ +public: + explicit BalanceBoardTASInputWindow(QWidget* parent); + +private: + void hideEvent(QHideEvent* event) override; + void showEvent(QShowEvent* event) override; + + WiimoteEmu::BalanceBoard* GetBalanceBoard() const; + + InputOverrider m_wiimote_overrider; + InputOverrider m_balance_board_overrider; +};