Merge pull request #11070 from AdmiralCurtiss/netplay-wiimotes
Netplay: Redesign Wiimote data exchange.
This commit is contained in:
commit
23806f8d60
|
@ -279,6 +279,8 @@ add_library(core
|
|||
HW/WiimoteCommon/WiimoteReport.h
|
||||
HW/WiimoteEmu/Camera.cpp
|
||||
HW/WiimoteEmu/Camera.h
|
||||
HW/WiimoteEmu/DesiredWiimoteState.cpp
|
||||
HW/WiimoteEmu/DesiredWiimoteState.h
|
||||
HW/WiimoteEmu/Dynamics.cpp
|
||||
HW/WiimoteEmu/Dynamics.h
|
||||
HW/WiimoteEmu/EmuSubroutines.cpp
|
||||
|
@ -286,6 +288,7 @@ add_library(core
|
|||
HW/WiimoteEmu/Encryption.h
|
||||
HW/WiimoteEmu/Extension/Classic.cpp
|
||||
HW/WiimoteEmu/Extension/Classic.h
|
||||
HW/WiimoteEmu/Extension/DesiredExtensionState.h
|
||||
HW/WiimoteEmu/Extension/DrawsomeTablet.cpp
|
||||
HW/WiimoteEmu/Extension/DrawsomeTablet.h
|
||||
HW/WiimoteEmu/Extension/Drums.cpp
|
||||
|
|
|
@ -498,9 +498,6 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
|
|||
if (core_parameter.bWii && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED))
|
||||
{
|
||||
Wiimote::LoadConfig();
|
||||
|
||||
if (NetPlay::IsNetPlayRunning())
|
||||
NetPlay::SetupWiimotes();
|
||||
}
|
||||
|
||||
FreeLook::LoadInputConfig();
|
||||
|
|
|
@ -94,8 +94,6 @@ ControllerEmu::ControlGroup* GetDrawsomeTabletGroup(int number,
|
|||
WiimoteEmu::DrawsomeTabletGroup group);
|
||||
ControllerEmu::ControlGroup* GetTaTaConGroup(int number, WiimoteEmu::TaTaConGroup group);
|
||||
ControllerEmu::ControlGroup* GetShinkansenGroup(int number, WiimoteEmu::ShinkansenGroup group);
|
||||
|
||||
bool NetPlay_GetButtonPress(int wiimote, bool pressed);
|
||||
} // namespace Wiimote
|
||||
|
||||
namespace WiimoteReal
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
|
||||
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
struct DesiredWiimoteState;
|
||||
}
|
||||
|
||||
namespace WiimoteCommon
|
||||
{
|
||||
// Source: HID_010_SPC_PFL/1.0 (official HID specification)
|
||||
|
@ -30,8 +35,12 @@ public:
|
|||
virtual void EventLinked() = 0;
|
||||
virtual void EventUnlinked() = 0;
|
||||
|
||||
virtual u8 GetWiimoteDeviceIndex() const = 0;
|
||||
virtual void SetWiimoteDeviceIndex(u8 index) = 0;
|
||||
|
||||
// Called every ~200hz after HID channels are established.
|
||||
virtual void Update() = 0;
|
||||
virtual void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) = 0;
|
||||
virtual void Update(const WiimoteEmu::DesiredWiimoteState& target_state) = 0;
|
||||
|
||||
void SetInterruptCallback(InterruptCallbackType callback) { m_callback = std::move(callback); }
|
||||
|
||||
|
@ -39,8 +48,10 @@ public:
|
|||
// Does not include HID-type header.
|
||||
virtual void InterruptDataOutput(const u8* data, u32 size) = 0;
|
||||
|
||||
// Used to connect a disconnected wii remote on button press.
|
||||
virtual bool IsButtonPressed() = 0;
|
||||
// Get a snapshot of the current state of the Wiimote's buttons.
|
||||
// Note that only the button bits of the return value are meaningful, the rest should be ignored.
|
||||
// This is used to query a disconnected Wiimote whether it wants to reconnect.
|
||||
virtual ButtonData GetCurrentlyPressedButtons() = 0;
|
||||
|
||||
protected:
|
||||
void InterruptDataInputCallback(const u8* data, u32 size)
|
||||
|
|
|
@ -52,36 +52,14 @@ int CameraLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
|
|||
return RawWrite(&m_reg_data, addr, count, data_in);
|
||||
}
|
||||
|
||||
void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_of_view)
|
||||
std::array<CameraPoint, CameraLogic::NUM_POINTS>
|
||||
CameraLogic::GetCameraPoints(const Common::Matrix44& transform, Common::Vec2 field_of_view)
|
||||
{
|
||||
// IR data is read from offset 0x37 on real hardware.
|
||||
auto& data = m_reg_data.camera_data;
|
||||
data.fill(0xff);
|
||||
|
||||
constexpr u8 OBJECT_TRACKING_ENABLE = 0x08;
|
||||
|
||||
// If Address 0x30 is not 0x08 the camera will return 0xFFs.
|
||||
// The Wii seems to write 0x01 here before changing modes/sensitivities.
|
||||
if (m_reg_data.enable_object_tracking != OBJECT_TRACKING_ENABLE)
|
||||
return;
|
||||
|
||||
// If the sensor bar is off the camera will see no LEDs and return 0xFFs.
|
||||
if (!IOS::g_gpio_out[IOS::GPIO::SENSOR_BAR])
|
||||
return;
|
||||
|
||||
using Common::Matrix33;
|
||||
using Common::Matrix44;
|
||||
using Common::Vec3;
|
||||
using Common::Vec4;
|
||||
|
||||
// FYI: A real wiimote normally only returns 1 point for each LED cluster (2 total).
|
||||
// Sending all 4 points can actually cause some stuttering issues.
|
||||
constexpr int NUM_POINTS = 2;
|
||||
|
||||
// Range from 0-15. Small values (2-4) seem to be very typical.
|
||||
// This is reduced based on distance from sensor bar.
|
||||
constexpr int MAX_POINT_SIZE = 15;
|
||||
|
||||
const std::array<Vec3, NUM_POINTS> leds{
|
||||
Vec3{-SENSOR_BAR_LED_SEPARATION / 2, 0, 0},
|
||||
Vec3{SENSOR_BAR_LED_SEPARATION / 2, 0, 0},
|
||||
|
@ -91,12 +69,6 @@ void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_o
|
|||
Matrix44::Perspective(field_of_view.y, field_of_view.x / field_of_view.y, 0.001f, 1000) *
|
||||
Matrix44::FromMatrix33(Matrix33::RotateX(float(MathUtil::TAU / 4))) * transform;
|
||||
|
||||
struct CameraPoint
|
||||
{
|
||||
IRBasic::IRObject position;
|
||||
u8 size = 0;
|
||||
};
|
||||
|
||||
std::array<CameraPoint, leds.size()> camera_points;
|
||||
|
||||
std::transform(leds.begin(), leds.end(), camera_points.begin(), [&](const Vec3& v) {
|
||||
|
@ -112,13 +84,32 @@ void CameraLogic::Update(const Common::Matrix44& transform, Common::Vec2 field_o
|
|||
const auto point_size = std::lround(MAX_POINT_SIZE / point.w / 2);
|
||||
|
||||
if (x >= 0 && y >= 0 && x < CAMERA_RES_X && y < CAMERA_RES_Y)
|
||||
return CameraPoint{{u16(x), u16(y)}, u8(point_size)};
|
||||
return CameraPoint({u16(x), u16(y)}, u8(point_size));
|
||||
}
|
||||
|
||||
// 0xFFFFs are interpreted as "not visible".
|
||||
return CameraPoint{{0xffff, 0xffff}, 0xff};
|
||||
return CameraPoint();
|
||||
});
|
||||
|
||||
return camera_points;
|
||||
}
|
||||
|
||||
void CameraLogic::Update(const std::array<CameraPoint, NUM_POINTS>& camera_points)
|
||||
{
|
||||
// IR data is read from offset 0x37 on real hardware.
|
||||
auto& data = m_reg_data.camera_data;
|
||||
data.fill(0xff);
|
||||
|
||||
constexpr u8 OBJECT_TRACKING_ENABLE = 0x08;
|
||||
|
||||
// If Address 0x30 is not 0x08 the camera will return 0xFFs.
|
||||
// The Wii seems to write 0x01 here before changing modes/sensitivities.
|
||||
if (m_reg_data.enable_object_tracking != OBJECT_TRACKING_ENABLE)
|
||||
return;
|
||||
|
||||
// If the sensor bar is off the camera will see no LEDs and return 0xFFs.
|
||||
if (!IOS::g_gpio_out[IOS::GPIO::SENSOR_BAR])
|
||||
return;
|
||||
|
||||
switch (m_reg_data.mode)
|
||||
{
|
||||
case IR_MODE_BASIC:
|
||||
|
|
|
@ -16,11 +16,26 @@ class Matrix44;
|
|||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
using IRObject = Common::TVec2<u16>;
|
||||
|
||||
struct CameraPoint
|
||||
{
|
||||
IRObject position;
|
||||
u8 size;
|
||||
|
||||
// 0xFFFFs are interpreted as "not visible".
|
||||
constexpr CameraPoint() : position({0xffff, 0xffff}), size(0xff) {}
|
||||
constexpr CameraPoint(IRObject position_, u8 size_) : position(position_), size(size_) {}
|
||||
constexpr bool operator==(const CameraPoint& other) const
|
||||
{
|
||||
return this->position == other.position && this->size == other.size;
|
||||
}
|
||||
constexpr bool operator!=(const CameraPoint& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
// Four bytes for two objects. Filled with 0xFF if empty
|
||||
struct IRBasic
|
||||
{
|
||||
using IRObject = Common::TVec2<u16>;
|
||||
|
||||
u8 x1;
|
||||
u8 y1;
|
||||
u8 x2hi : 2;
|
||||
|
@ -59,8 +74,8 @@ struct IRExtended
|
|||
u8 xhi : 2;
|
||||
u8 yhi : 2;
|
||||
|
||||
auto GetPosition() const { return IRBasic::IRObject(xhi << 8 | x, yhi << 8 | y); }
|
||||
void SetPosition(const IRBasic::IRObject& obj)
|
||||
auto GetPosition() const { return IRObject(xhi << 8 | x, yhi << 8 | y); }
|
||||
void SetPosition(const IRObject& obj)
|
||||
{
|
||||
x = obj.x;
|
||||
xhi = obj.x >> 8;
|
||||
|
@ -109,9 +124,19 @@ public:
|
|||
IR_MODE_FULL = 5,
|
||||
};
|
||||
|
||||
// FYI: A real wiimote normally only returns 1 point for each LED cluster (2 total).
|
||||
// Sending all 4 points can actually cause some stuttering issues.
|
||||
static constexpr int NUM_POINTS = 2;
|
||||
|
||||
// Range from 0-15. Small values (2-4) seem to be very typical.
|
||||
// This is reduced based on distance from sensor bar.
|
||||
static constexpr int MAX_POINT_SIZE = 15;
|
||||
|
||||
void Reset();
|
||||
void DoState(PointerWrap& p);
|
||||
void Update(const Common::Matrix44& transform, Common::Vec2 field_of_view);
|
||||
static std::array<CameraPoint, NUM_POINTS> GetCameraPoints(const Common::Matrix44& transform,
|
||||
Common::Vec2 field_of_view);
|
||||
void Update(const std::array<CameraPoint, NUM_POINTS>& camera_points);
|
||||
void SetEnabled(bool is_enabled);
|
||||
|
||||
static constexpr u8 I2C_ADDR = 0x58;
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Drums.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Guitar.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Shinkansen.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/TaTaCon.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Turntable.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/UDrawTablet.h"
|
||||
#include "Core/HW/WiimoteEmu/MotionPlus.h"
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state)
|
||||
{
|
||||
const u8 has_buttons = (state.buttons.hex & WiimoteCommon::ButtonData::BUTTON_MASK) != 0 ? 1 : 0;
|
||||
const u8 has_accel = state.acceleration != DesiredWiimoteState::DEFAULT_ACCELERATION ? 1 : 0;
|
||||
const u8 has_camera = state.camera_points != DesiredWiimoteState::DEFAULT_CAMERA ? 1 : 0;
|
||||
const u8 has_motion_plus = state.motion_plus.has_value() ? 1 : 0;
|
||||
|
||||
// Right now we support < 16 extensions so the info which extension is in use fits into 4 bits.
|
||||
// This allows 'empty' packets to be a single byte, which is very nice for reducing bandwidth.
|
||||
// If we ever support 16 or more we have to redesign this a bit; ideally use a variable-length
|
||||
// encoding so that typical extensions (None, Nunchuk, Classic Controller) still fit into the
|
||||
// initial 4 bits.
|
||||
static_assert(std::variant_size_v<DesiredExtensionState::ExtensionData> <= (1 << 4));
|
||||
const u8 extension = u8(state.extension.data.index());
|
||||
|
||||
SerializedWiimoteState s;
|
||||
s.length = 0;
|
||||
s.data[s.length++] = u8(has_buttons | (has_accel << 1) | (has_camera << 2) |
|
||||
(has_motion_plus << 3) | (extension << 4));
|
||||
|
||||
if (has_buttons)
|
||||
{
|
||||
const u8 buttons = u8((state.buttons.a) | (state.buttons.b << 1) | (state.buttons.plus << 2) |
|
||||
(state.buttons.minus << 3) | (state.buttons.one << 4) |
|
||||
(state.buttons.two << 5) | (state.buttons.home << 6));
|
||||
const u8 dpad = u8((state.buttons.up) | (state.buttons.down << 1) | (state.buttons.left << 2) |
|
||||
(state.buttons.right << 3));
|
||||
s.data[s.length++] = buttons;
|
||||
s.data[s.length++] = dpad;
|
||||
}
|
||||
|
||||
if (has_accel)
|
||||
{
|
||||
const u16 accel_x = state.acceleration.value.x; // 10 bits
|
||||
const u16 accel_y = state.acceleration.value.y; // 9 bits (ignore lowest bit)
|
||||
const u16 accel_z = state.acceleration.value.z; // 9 bits (ignore lowest bit)
|
||||
const u8 accel_x_high = u8(accel_x >> 2);
|
||||
const u8 accel_y_high = u8(accel_y >> 2);
|
||||
const u8 accel_z_high = u8(accel_z >> 2);
|
||||
const u8 accel_low = u8((accel_x & 0b11) | (Common::ExtractBit<1>(accel_y) << 2) |
|
||||
(Common::ExtractBit<1>(accel_z) << 3));
|
||||
|
||||
if (has_buttons)
|
||||
{
|
||||
// can use the high bits of the dpad field from buttons
|
||||
s.data[s.length - 1] |= u8(accel_low << 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
s.data[s.length++] = u8(accel_low << 4);
|
||||
}
|
||||
|
||||
s.data[s.length++] = accel_x_high;
|
||||
s.data[s.length++] = accel_y_high;
|
||||
s.data[s.length++] = accel_z_high;
|
||||
}
|
||||
|
||||
if (has_camera)
|
||||
{
|
||||
for (size_t i = 0; i < 2; ++i)
|
||||
{
|
||||
const u16 camera_x = state.camera_points[i].position.x; // 10 bits
|
||||
const u16 camera_y = state.camera_points[i].position.y; // 10 bits
|
||||
const u8 camera_size = state.camera_points[i].size; // 4 bits
|
||||
s.data[s.length++] = u8((camera_x & 0b11) | ((camera_y & 0b11) << 2) | (camera_size << 4));
|
||||
s.data[s.length++] = u8(camera_x >> 2);
|
||||
s.data[s.length++] = u8(camera_y >> 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_motion_plus)
|
||||
{
|
||||
const u16 pitch_slow = state.motion_plus->is_slow.x ? 1 : 0;
|
||||
const u16 roll_slow = state.motion_plus->is_slow.y ? 1 : 0;
|
||||
const u16 yaw_slow = state.motion_plus->is_slow.z ? 1 : 0;
|
||||
const u16 pitch_value = state.motion_plus->gyro.value.x; // 14 bits
|
||||
const u16 roll_value = state.motion_plus->gyro.value.y; // 14 bits
|
||||
const u16 yaw_value = state.motion_plus->gyro.value.z; // 14 bits
|
||||
s.data[s.length++] = u8(pitch_value);
|
||||
s.data[s.length++] = u8(((pitch_value >> 8) & 0x3f) | (pitch_slow << 7));
|
||||
s.data[s.length++] = u8(roll_value);
|
||||
s.data[s.length++] = u8(((roll_value >> 8) & 0x3f) | (roll_slow << 7));
|
||||
s.data[s.length++] = u8(yaw_value);
|
||||
s.data[s.length++] = u8(((yaw_value >> 8) & 0x3f) | (yaw_slow << 7));
|
||||
}
|
||||
|
||||
if (extension)
|
||||
{
|
||||
std::visit(
|
||||
[&s](const auto& arg) {
|
||||
using T = std::decay_t<decltype(arg)>;
|
||||
if constexpr (!std::is_same_v<std::monostate, T>)
|
||||
{
|
||||
static_assert(sizeof(arg) <= 6);
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
std::memcpy(&s.data[s.length], &arg, sizeof(arg));
|
||||
s.length += sizeof(arg);
|
||||
}
|
||||
},
|
||||
state.extension.data);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool DeserializeExtensionState(DesiredWiimoteState* state,
|
||||
const SerializedWiimoteState& serialized, size_t offset)
|
||||
{
|
||||
if (serialized.length < offset + sizeof(T))
|
||||
return false;
|
||||
auto& e = state->extension.data.emplace<T>();
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
std::memcpy(&e, &serialized.data[offset], sizeof(T));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimoteState& serialized)
|
||||
{
|
||||
// clear state
|
||||
state->buttons.hex = 0;
|
||||
state->acceleration = DesiredWiimoteState::DEFAULT_ACCELERATION;
|
||||
state->camera_points = DesiredWiimoteState::DEFAULT_CAMERA;
|
||||
state->motion_plus = std::nullopt;
|
||||
state->extension.data = std::monostate();
|
||||
|
||||
if (serialized.length < 1)
|
||||
{
|
||||
// can't be valid
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& d = serialized.data;
|
||||
const u8 has_buttons = d[0] & 1;
|
||||
const u8 has_accel = (d[0] >> 1) & 1;
|
||||
const u8 has_camera = (d[0] >> 2) & 1;
|
||||
const u8 has_motion_plus = (d[0] >> 3) & 1;
|
||||
const u8 extension = (d[0] >> 4);
|
||||
|
||||
if (extension >= ExtensionNumber::MAX)
|
||||
{
|
||||
// invalid extension
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t expected_size = [&]() {
|
||||
size_t s = 1;
|
||||
if (has_buttons && has_accel)
|
||||
s += 5;
|
||||
else if (has_buttons)
|
||||
s += 2;
|
||||
else if (has_accel)
|
||||
s += 4;
|
||||
if (has_camera)
|
||||
s += 6;
|
||||
if (has_motion_plus)
|
||||
s += 6;
|
||||
switch (extension)
|
||||
{
|
||||
case ExtensionNumber::NONE:
|
||||
break;
|
||||
case ExtensionNumber::NUNCHUK:
|
||||
s += sizeof(Nunchuk::DataFormat);
|
||||
break;
|
||||
case ExtensionNumber::CLASSIC:
|
||||
s += sizeof(Classic::DataFormat);
|
||||
break;
|
||||
case ExtensionNumber::GUITAR:
|
||||
s += sizeof(Guitar::DataFormat);
|
||||
break;
|
||||
case ExtensionNumber::DRUMS:
|
||||
s += sizeof(Drums::DesiredState);
|
||||
break;
|
||||
case ExtensionNumber::TURNTABLE:
|
||||
s += sizeof(Turntable::DataFormat);
|
||||
break;
|
||||
case ExtensionNumber::UDRAW_TABLET:
|
||||
s += sizeof(UDrawTablet::DataFormat);
|
||||
break;
|
||||
case ExtensionNumber::DRAWSOME_TABLET:
|
||||
s += sizeof(DrawsomeTablet::DataFormat);
|
||||
break;
|
||||
case ExtensionNumber::TATACON:
|
||||
s += sizeof(TaTaCon::DataFormat);
|
||||
break;
|
||||
case ExtensionNumber::SHINKANSEN:
|
||||
s += sizeof(Shinkansen::DesiredState);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return s;
|
||||
}();
|
||||
|
||||
if (serialized.length != expected_size)
|
||||
{
|
||||
// invalid length
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pos = 1;
|
||||
|
||||
if (has_buttons)
|
||||
{
|
||||
state->buttons.a = d[pos] & 1;
|
||||
state->buttons.b = (d[pos] >> 1) & 1;
|
||||
state->buttons.plus = (d[pos] >> 2) & 1;
|
||||
state->buttons.minus = (d[pos] >> 3) & 1;
|
||||
state->buttons.one = (d[pos] >> 4) & 1;
|
||||
state->buttons.two = (d[pos] >> 5) & 1;
|
||||
state->buttons.home = (d[pos] >> 6) & 1;
|
||||
state->buttons.up = d[pos + 1] & 1;
|
||||
state->buttons.down = (d[pos + 1] >> 1) & 1;
|
||||
state->buttons.left = (d[pos + 1] >> 2) & 1;
|
||||
state->buttons.right = (d[pos + 1] >> 3) & 1;
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
if (has_accel)
|
||||
{
|
||||
if (has_buttons)
|
||||
pos -= 1;
|
||||
const u8 accel_low = d[pos] >> 4;
|
||||
const u8 accel_x_high = d[pos + 1];
|
||||
const u8 accel_y_high = d[pos + 2];
|
||||
const u8 accel_z_high = d[pos + 3];
|
||||
state->acceleration.value.x = (accel_x_high << 2) | (accel_low & 0b11);
|
||||
state->acceleration.value.y =
|
||||
Common::ExpandValue<u16>((accel_y_high << 1) | Common::ExtractBit<2>(accel_low), 1);
|
||||
state->acceleration.value.z =
|
||||
Common::ExpandValue<u16>((accel_z_high << 1) | Common::ExtractBit<3>(accel_low), 1);
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
if (has_camera)
|
||||
{
|
||||
for (size_t i = 0; i < 2; ++i)
|
||||
{
|
||||
const u8 camera_misc = d[pos];
|
||||
const u8 camera_x_high = d[pos + 1];
|
||||
const u8 camera_y_high = d[pos + 2];
|
||||
const u16 camera_x = (camera_x_high << 2) | (camera_misc & 0b11);
|
||||
const u16 camera_y = (camera_y_high << 2) | ((camera_misc >> 2) & 0b11);
|
||||
const u8 camera_size = camera_misc >> 4;
|
||||
if (camera_y < CameraLogic::CAMERA_RES_Y)
|
||||
{
|
||||
state->camera_points[i] = CameraPoint({camera_x, camera_y}, camera_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// indicates an invalid camera point
|
||||
state->camera_points[i] = CameraPoint();
|
||||
}
|
||||
pos += 3;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_motion_plus)
|
||||
{
|
||||
const u16 pitch_value = d[pos] | ((d[pos + 1] & 0x3f) << 8);
|
||||
const u16 roll_value = d[pos + 2] | ((d[pos + 3] & 0x3f) << 8);
|
||||
const u16 yaw_value = d[pos + 4] | ((d[pos + 5] & 0x3f) << 8);
|
||||
const bool pitch_slow = (d[pos + 1] & 0x80) != 0;
|
||||
const bool roll_slow = (d[pos + 3] & 0x80) != 0;
|
||||
const bool yaw_slow = (d[pos + 5] & 0x80) != 0;
|
||||
state->motion_plus = MotionPlus::DataFormat::Data{
|
||||
MotionPlus::DataFormat::GyroRawValue{
|
||||
MotionPlus::DataFormat::GyroType(pitch_value, roll_value, yaw_value)},
|
||||
MotionPlus::DataFormat::SlowType(pitch_slow, roll_slow, yaw_slow)};
|
||||
pos += 6;
|
||||
}
|
||||
|
||||
switch (extension)
|
||||
{
|
||||
case ExtensionNumber::NONE:
|
||||
return true;
|
||||
case ExtensionNumber::NUNCHUK:
|
||||
return DeserializeExtensionState<Nunchuk::DataFormat>(state, serialized, pos);
|
||||
case ExtensionNumber::CLASSIC:
|
||||
return DeserializeExtensionState<Classic::DataFormat>(state, serialized, pos);
|
||||
case ExtensionNumber::GUITAR:
|
||||
return DeserializeExtensionState<Guitar::DataFormat>(state, serialized, pos);
|
||||
case ExtensionNumber::DRUMS:
|
||||
return DeserializeExtensionState<Drums::DesiredState>(state, serialized, pos);
|
||||
case ExtensionNumber::TURNTABLE:
|
||||
return DeserializeExtensionState<Turntable::DataFormat>(state, serialized, pos);
|
||||
case ExtensionNumber::UDRAW_TABLET:
|
||||
return DeserializeExtensionState<UDrawTablet::DataFormat>(state, serialized, pos);
|
||||
case ExtensionNumber::DRAWSOME_TABLET:
|
||||
return DeserializeExtensionState<DrawsomeTablet::DataFormat>(state, serialized, pos);
|
||||
case ExtensionNumber::TATACON:
|
||||
return DeserializeExtensionState<TaTaCon::DataFormat>(state, serialized, pos);
|
||||
case ExtensionNumber::SHINKANSEN:
|
||||
return DeserializeExtensionState<Shinkansen::DesiredState>(state, serialized, pos);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace WiimoteEmu
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include "Core/HW/WiimoteCommon/WiimoteReport.h"
|
||||
#include "Core/HW/WiimoteEmu/Camera.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/MotionPlus.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
struct DesiredWiimoteState
|
||||
{
|
||||
// 1g in Z direction, which is the default returned by an unmoving emulated Wiimote.
|
||||
static constexpr WiimoteCommon::AccelData DEFAULT_ACCELERATION = WiimoteCommon::AccelData(
|
||||
{Wiimote::ACCEL_ZERO_G << 2, Wiimote::ACCEL_ZERO_G << 2, Wiimote::ACCEL_ONE_G << 2});
|
||||
|
||||
// No light detected by the IR camera.
|
||||
static constexpr std::array<CameraPoint, 2> DEFAULT_CAMERA = {CameraPoint(), CameraPoint()};
|
||||
|
||||
WiimoteCommon::ButtonData buttons{}; // non-button state in this is ignored
|
||||
WiimoteCommon::AccelData acceleration = DEFAULT_ACCELERATION;
|
||||
std::array<CameraPoint, 2> camera_points = DEFAULT_CAMERA;
|
||||
std::optional<MotionPlus::DataFormat::Data> motion_plus = std::nullopt;
|
||||
DesiredExtensionState extension;
|
||||
};
|
||||
|
||||
// For Netplay.
|
||||
struct SerializedWiimoteState
|
||||
{
|
||||
u8 length;
|
||||
std::array<u8, 24> data; // 12 bytes Wiimote, 6 bytes MotionPlus, 6 bytes Extension
|
||||
};
|
||||
|
||||
SerializedWiimoteState SerializeDesiredState(const DesiredWiimoteState& state);
|
||||
bool DeserializeDesiredState(DesiredWiimoteState* state, const SerializedWiimoteState& serialized);
|
||||
} // namespace WiimoteEmu
|
|
@ -142,7 +142,8 @@ void Wiimote::SendAck(OutputReportID rpt_id, ErrorCode error_code)
|
|||
InterruptDataInputCallback(rpt.GetData(), rpt.GetSize());
|
||||
}
|
||||
|
||||
void Wiimote::HandleExtensionSwap()
|
||||
void Wiimote::HandleExtensionSwap(ExtensionNumber desired_extension_number,
|
||||
bool desired_motion_plus)
|
||||
{
|
||||
if (WIIMOTE_BALANCE_BOARD == m_index)
|
||||
{
|
||||
|
@ -151,15 +152,13 @@ void Wiimote::HandleExtensionSwap()
|
|||
return;
|
||||
}
|
||||
|
||||
ExtensionNumber desired_extension_number =
|
||||
static_cast<ExtensionNumber>(m_attachments->GetSelectedAttachment());
|
||||
|
||||
const bool desired_motion_plus = m_motion_plus_setting.GetValue();
|
||||
|
||||
// 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);
|
||||
|
||||
// M+ is attached and it's not wanted, so remove it.
|
||||
m_extension_port.AttachExtension(GetNoneExtension());
|
||||
m_is_motion_plus_attached = false;
|
||||
|
@ -184,6 +183,9 @@ void Wiimote::HandleExtensionSwap()
|
|||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG_FMT(WIIMOTE, "Attaching Motion Plus (Wiimote {} in slot {})", m_index,
|
||||
m_bt_device_index);
|
||||
|
||||
// No extension attached so attach M+.
|
||||
m_is_motion_plus_attached = true;
|
||||
m_extension_port.AttachExtension(&m_motion_plus);
|
||||
|
@ -198,12 +200,18 @@ void Wiimote::HandleExtensionSwap()
|
|||
// 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);
|
||||
|
||||
// First we must detach the current extension.
|
||||
// The next call will change to the new extension if needed.
|
||||
m_active_extension = ExtensionNumber::NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
INFO_LOG_FMT(WIIMOTE, "Switching to Extension {} (Wiimote {} in slot {})",
|
||||
desired_extension_number, m_index, m_bt_device_index);
|
||||
|
||||
m_active_extension = desired_extension_number;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "Common/BitUtils.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Control/Input.h"
|
||||
|
@ -105,7 +107,7 @@ Classic::Classic() : Extension1stParty("Classic", _trans("Classic Controller"))
|
|||
}
|
||||
}
|
||||
|
||||
void Classic::Update()
|
||||
void Classic::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DataFormat classic_data = {};
|
||||
|
||||
|
@ -149,7 +151,12 @@ void Classic::Update()
|
|||
|
||||
classic_data.SetButtons(buttons);
|
||||
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = classic_data;
|
||||
target_state->data = classic_data;
|
||||
}
|
||||
|
||||
void Classic::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
|
||||
}
|
||||
|
||||
void Classic::Reset()
|
||||
|
|
|
@ -178,7 +178,8 @@ public:
|
|||
|
||||
Classic();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
|
||||
ControllerEmu::ControlGroup* GetGroup(ClassicGroup group);
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
#include "Common/BitUtils.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Drums.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Guitar.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Nunchuk.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Shinkansen.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/TaTaCon.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Turntable.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/UDrawTablet.h"
|
||||
#include "Core/HW/WiimoteEmu/ExtensionPort.h"
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
struct DesiredExtensionState
|
||||
{
|
||||
using ExtensionData =
|
||||
std::variant<std::monostate, Nunchuk::DataFormat, Classic::DataFormat, Guitar::DataFormat,
|
||||
Drums::DesiredState, Turntable::DataFormat, UDrawTablet::DataFormat,
|
||||
DrawsomeTablet::DataFormat, TaTaCon::DataFormat, Shinkansen::DesiredState>;
|
||||
ExtensionData data = std::monostate();
|
||||
|
||||
static_assert(std::is_same_v<std::monostate,
|
||||
std::variant_alternative_t<ExtensionNumber::NONE, ExtensionData>>);
|
||||
static_assert(
|
||||
std::is_same_v<Nunchuk::DataFormat,
|
||||
std::variant_alternative_t<ExtensionNumber::NUNCHUK, ExtensionData>>);
|
||||
static_assert(
|
||||
std::is_same_v<Classic::DataFormat,
|
||||
std::variant_alternative_t<ExtensionNumber::CLASSIC, ExtensionData>>);
|
||||
static_assert(std::is_same_v<Guitar::DataFormat,
|
||||
std::variant_alternative_t<ExtensionNumber::GUITAR, ExtensionData>>);
|
||||
static_assert(std::is_same_v<Drums::DesiredState,
|
||||
std::variant_alternative_t<ExtensionNumber::DRUMS, ExtensionData>>);
|
||||
static_assert(
|
||||
std::is_same_v<Turntable::DataFormat,
|
||||
std::variant_alternative_t<ExtensionNumber::TURNTABLE, ExtensionData>>);
|
||||
static_assert(
|
||||
std::is_same_v<UDrawTablet::DataFormat,
|
||||
std::variant_alternative_t<ExtensionNumber::UDRAW_TABLET, ExtensionData>>);
|
||||
static_assert(
|
||||
std::is_same_v<DrawsomeTablet::DataFormat,
|
||||
std::variant_alternative_t<ExtensionNumber::DRAWSOME_TABLET, ExtensionData>>);
|
||||
static_assert(
|
||||
std::is_same_v<TaTaCon::DataFormat,
|
||||
std::variant_alternative_t<ExtensionNumber::TATACON, ExtensionData>>);
|
||||
static_assert(
|
||||
std::is_same_v<Shinkansen::DesiredState,
|
||||
std::variant_alternative_t<ExtensionNumber::SHINKANSEN, ExtensionData>>);
|
||||
static_assert(std::variant_size_v<DesiredExtensionState::ExtensionData> == ExtensionNumber::MAX);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void DefaultExtensionUpdate(EncryptedExtension::Register* reg,
|
||||
const DesiredExtensionState& target_state)
|
||||
{
|
||||
if (std::holds_alternative<T>(target_state.data))
|
||||
{
|
||||
Common::BitCastPtr<T>(®->controller_data) = std::get<T>(target_state.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
Common::BitCastPtr<T>(®->controller_data) = T{};
|
||||
}
|
||||
}
|
||||
} // namespace WiimoteEmu
|
|
@ -9,6 +9,8 @@
|
|||
#include "Common/BitUtils.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Control/Input.h"
|
||||
|
@ -31,9 +33,9 @@ DrawsomeTablet::DrawsomeTablet() : Extension3rdParty("Drawsome", _trans("Drawsom
|
|||
m_touch->AddInput(ControllerEmu::Translate, _trans("Pressure"));
|
||||
}
|
||||
|
||||
void DrawsomeTablet::Update()
|
||||
void DrawsomeTablet::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DataFormat tablet_data = {};
|
||||
DataFormat& tablet_data = target_state->data.emplace<DataFormat>();
|
||||
|
||||
// Stylus X/Y (calibrated values):
|
||||
constexpr u16 MIN_X = 0x0000;
|
||||
|
@ -77,8 +79,11 @@ void DrawsomeTablet::Update()
|
|||
|
||||
tablet_data.pressure1 = u8(pressure);
|
||||
tablet_data.pressure2 = u8(pressure >> 8);
|
||||
}
|
||||
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tablet_data;
|
||||
void DrawsomeTablet::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
|
||||
}
|
||||
|
||||
void DrawsomeTablet::Reset()
|
||||
|
|
|
@ -27,7 +27,8 @@ class DrawsomeTablet : public Extension3rdParty
|
|||
public:
|
||||
DrawsomeTablet();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
|
||||
ControllerEmu::ControlGroup* GetGroup(DrawsomeTabletGroup group);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "Common/BitUtils.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Control/Input.h"
|
||||
|
@ -77,8 +79,43 @@ Drums::Drums() : Extension1stParty("Drums", _trans("Drum Kit"))
|
|||
m_buttons->AddInput(ControllerEmu::DoNotTranslate, "+");
|
||||
}
|
||||
|
||||
void Drums::Update()
|
||||
void Drums::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DesiredState& state = target_state->data.emplace<DesiredState>();
|
||||
|
||||
{
|
||||
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
|
||||
|
||||
state.stick_x = MapFloat(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX);
|
||||
state.stick_y = MapFloat(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX);
|
||||
}
|
||||
|
||||
state.buttons = 0;
|
||||
m_buttons->GetState(&state.buttons, drum_button_bitmasks.data());
|
||||
|
||||
state.drum_pads = 0;
|
||||
m_pads->GetState(&state.drum_pads, drum_pad_bitmasks.data());
|
||||
|
||||
state.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100));
|
||||
}
|
||||
|
||||
void Drums::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DesiredState desired_state;
|
||||
if (std::holds_alternative<DesiredState>(target_state.data))
|
||||
{
|
||||
desired_state = std::get<DesiredState>(target_state.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set a sane default
|
||||
desired_state.stick_x = STICK_CENTER;
|
||||
desired_state.stick_y = STICK_CENTER;
|
||||
desired_state.buttons = 0;
|
||||
desired_state.drum_pads = 0;
|
||||
desired_state.softness = 7;
|
||||
}
|
||||
|
||||
DataFormat drum_data = {};
|
||||
|
||||
// The meaning of these bits are unknown but they are usually set.
|
||||
|
@ -94,20 +131,12 @@ void Drums::Update()
|
|||
drum_data.no_velocity_data_2 = 1;
|
||||
drum_data.softness = 7;
|
||||
|
||||
// Stick.
|
||||
{
|
||||
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
|
||||
|
||||
drum_data.stick_x = MapFloat(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX);
|
||||
drum_data.stick_y = MapFloat(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX);
|
||||
}
|
||||
|
||||
// Buttons.
|
||||
m_buttons->GetState(&drum_data.buttons, drum_button_bitmasks.data());
|
||||
drum_data.stick_x = desired_state.stick_x;
|
||||
drum_data.stick_y = desired_state.stick_y;
|
||||
drum_data.buttons = desired_state.buttons;
|
||||
|
||||
// Drum pads.
|
||||
u8 current_pad_input = 0;
|
||||
m_pads->GetState(¤t_pad_input, drum_pad_bitmasks.data());
|
||||
u8 current_pad_input = desired_state.drum_pads;
|
||||
m_new_pad_hits |= ~m_prev_pad_input & current_pad_input;
|
||||
m_prev_pad_input = current_pad_input;
|
||||
|
||||
|
@ -130,8 +159,7 @@ void Drums::Update()
|
|||
drum_data.no_velocity_data_1 = 0;
|
||||
drum_data.no_velocity_data_2 = 0;
|
||||
|
||||
// Set softness from user-configured hit strength setting.
|
||||
drum_data.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100));
|
||||
drum_data.softness = desired_state.softness;
|
||||
|
||||
// A drum-pad hit causes the relevent bit to be triggered for the next 10 frames.
|
||||
constexpr u8 HIT_FRAME_COUNT = 10;
|
||||
|
|
|
@ -28,6 +28,15 @@ enum class DrumsGroup
|
|||
class Drums : public Extension1stParty
|
||||
{
|
||||
public:
|
||||
struct DesiredState
|
||||
{
|
||||
u8 stick_x; // 6 bits
|
||||
u8 stick_y; // 6 bits
|
||||
u8 buttons; // 2 bits
|
||||
u8 drum_pads; // 6 bits
|
||||
u8 softness; // 3 bits
|
||||
};
|
||||
|
||||
struct DataFormat
|
||||
{
|
||||
u8 stick_x : 6;
|
||||
|
@ -77,7 +86,8 @@ public:
|
|||
|
||||
Drums();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
|
||||
ControllerEmu::ControlGroup* GetGroup(DrumsGroup group);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Inline.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
|
@ -43,7 +45,12 @@ bool None::ReadDeviceDetectPin() const
|
|||
return false;
|
||||
}
|
||||
|
||||
void None::Update()
|
||||
void None::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
target_state->data.emplace<std::monostate>();
|
||||
}
|
||||
|
||||
void None::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
// Nothing needed.
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
struct DesiredExtensionState;
|
||||
|
||||
class Extension : public ControllerEmu::EmulatedController, public I2CSlave
|
||||
{
|
||||
public:
|
||||
|
@ -32,7 +34,8 @@ public:
|
|||
|
||||
virtual void Reset() = 0;
|
||||
virtual void DoState(PointerWrap& p) = 0;
|
||||
virtual void Update() = 0;
|
||||
virtual void BuildDesiredExtensionState(DesiredExtensionState* target_state) = 0;
|
||||
virtual void Update(const DesiredExtensionState& target_state) = 0;
|
||||
|
||||
private:
|
||||
const char* const m_config_name;
|
||||
|
@ -46,7 +49,8 @@ public:
|
|||
|
||||
private:
|
||||
bool ReadDeviceDetectPin() const override;
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
|
@ -67,7 +71,6 @@ public:
|
|||
// TODO: TAS handles encryption poorly.
|
||||
EncryptionKey ext_key;
|
||||
|
||||
protected:
|
||||
static constexpr int CALIBRATION_CHECKSUM_BYTES = 2;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
@ -97,6 +100,7 @@ protected:
|
|||
|
||||
static_assert(0x100 == sizeof(Register));
|
||||
|
||||
protected:
|
||||
Register m_reg = {};
|
||||
|
||||
void Reset() override;
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "Common/BitUtils.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Control/Input.h"
|
||||
|
@ -93,7 +95,7 @@ Guitar::Guitar() : Extension1stParty(_trans("Guitar"))
|
|||
groups.emplace_back(m_slider_bar = new ControllerEmu::Slider(_trans("Slider Bar")));
|
||||
}
|
||||
|
||||
void Guitar::Update()
|
||||
void Guitar::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DataFormat guitar_data = {};
|
||||
|
||||
|
@ -135,7 +137,12 @@ void Guitar::Update()
|
|||
// flip button bits
|
||||
guitar_data.bt ^= 0xFFFF;
|
||||
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = guitar_data;
|
||||
target_state->data = guitar_data;
|
||||
}
|
||||
|
||||
void Guitar::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
|
||||
}
|
||||
|
||||
void Guitar::Reset()
|
||||
|
|
|
@ -50,7 +50,8 @@ public:
|
|||
|
||||
Guitar();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
|
||||
ControllerEmu::ControlGroup* GetGroup(GuitarGroup group);
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MathUtil.h"
|
||||
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Control/Input.h"
|
||||
|
@ -60,7 +62,7 @@ Nunchuk::Nunchuk() : Extension1stParty(_trans("Nunchuk"))
|
|||
"IMUAccelerometer", _trans("Accelerometer")));
|
||||
}
|
||||
|
||||
void Nunchuk::Update()
|
||||
void Nunchuk::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DataFormat nc_data = {};
|
||||
|
||||
|
@ -110,7 +112,12 @@ void Nunchuk::Update()
|
|||
const auto acc = ConvertAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
|
||||
nc_data.SetAccel(acc.value);
|
||||
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = nc_data;
|
||||
target_state->data = nc_data;
|
||||
}
|
||||
|
||||
void Nunchuk::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
|
||||
}
|
||||
|
||||
void Nunchuk::Reset()
|
||||
|
|
|
@ -149,7 +149,8 @@ public:
|
|||
|
||||
Nunchuk();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "Common/Assert.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/ControlGroup/Buttons.h"
|
||||
|
@ -50,9 +52,9 @@ Shinkansen::Shinkansen() : Extension3rdParty("Shinkansen", _trans("Shinkansen Co
|
|||
m_led->AddOutput(ControllerEmu::Translate, _trans("Doors Locked"));
|
||||
}
|
||||
|
||||
void Shinkansen::Update()
|
||||
void Shinkansen::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DataFormat ext_data = {};
|
||||
DesiredState& state = target_state->data.emplace<DesiredState>();
|
||||
|
||||
u16 digital = 0;
|
||||
const u16 lever_bitmasks[2] = {};
|
||||
|
@ -62,8 +64,8 @@ void Shinkansen::Update()
|
|||
// guesses).
|
||||
const u8 brake_values[] = {0, 53, 79, 105, 132, 159, 187, 217, 250};
|
||||
const u8 power_values[] = {255, 229, 208, 189, 170, 153, 135, 118, 101, 85, 68, 51, 35, 17};
|
||||
ext_data.brake = brake_values[size_t(analog[0] * (sizeof(brake_values) - 1))];
|
||||
ext_data.power = power_values[size_t(analog[1] * (sizeof(power_values) - 1))];
|
||||
state.brake = brake_values[size_t(analog[0] * (sizeof(brake_values) - 1))];
|
||||
state.power = power_values[size_t(analog[1] * (sizeof(power_values) - 1))];
|
||||
|
||||
// Note: This currently assumes a little-endian host.
|
||||
const u16 button_bitmasks[] = {
|
||||
|
@ -78,8 +80,27 @@ void Shinkansen::Update()
|
|||
0x0010, // Select
|
||||
0x0004, // Start
|
||||
};
|
||||
m_buttons->GetState(&ext_data.buttons, button_bitmasks);
|
||||
ext_data.buttons ^= 0xFFFF;
|
||||
m_buttons->GetState(&state.buttons, button_bitmasks);
|
||||
}
|
||||
|
||||
void Shinkansen::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DesiredState desired_state;
|
||||
if (std::holds_alternative<DesiredState>(target_state.data))
|
||||
{
|
||||
desired_state = std::get<DesiredState>(target_state.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
desired_state.brake = 0;
|
||||
desired_state.power = 255;
|
||||
desired_state.buttons = 0;
|
||||
}
|
||||
|
||||
DataFormat ext_data = {};
|
||||
ext_data.brake = desired_state.brake;
|
||||
ext_data.power = desired_state.power;
|
||||
ext_data.buttons = desired_state.buttons ^ 0xFFFF;
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = ext_data;
|
||||
|
||||
const auto lock = GetStateLock();
|
||||
|
|
|
@ -24,9 +24,17 @@ enum class ShinkansenGroup
|
|||
class Shinkansen : public Extension3rdParty
|
||||
{
|
||||
public:
|
||||
struct DesiredState
|
||||
{
|
||||
u8 brake;
|
||||
u8 power;
|
||||
u16 buttons;
|
||||
};
|
||||
|
||||
Shinkansen();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
ControllerEmu::ControlGroup* GetGroup(ShinkansenGroup group);
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "Common/BitUtils.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Control/Input.h"
|
||||
|
@ -48,7 +50,7 @@ TaTaCon::TaTaCon() : Extension3rdParty("TaTaCon", _trans("Taiko Drum"))
|
|||
m_rim->AddInput(ControllerEmu::Translate, name);
|
||||
}
|
||||
|
||||
void TaTaCon::Update()
|
||||
void TaTaCon::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DataFormat tatacon_data = {};
|
||||
|
||||
|
@ -58,7 +60,12 @@ void TaTaCon::Update()
|
|||
// Flip button bits.
|
||||
tatacon_data.state ^= 0xff;
|
||||
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tatacon_data;
|
||||
target_state->data = tatacon_data;
|
||||
}
|
||||
|
||||
void TaTaCon::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
|
||||
}
|
||||
|
||||
void TaTaCon::Reset()
|
||||
|
|
|
@ -31,7 +31,8 @@ public:
|
|||
|
||||
TaTaCon();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
|
||||
ControllerEmu::ControlGroup* GetGroup(TaTaConGroup group);
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "Common/BitUtils.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Control/Input.h"
|
||||
|
@ -78,7 +80,7 @@ Turntable::Turntable() : Extension1stParty("Turntable", _trans("DJ Turntable"))
|
|||
groups.emplace_back(m_crossfade = new ControllerEmu::Slider(_trans("Crossfade")));
|
||||
}
|
||||
|
||||
void Turntable::Update()
|
||||
void Turntable::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DataFormat tt_data = {};
|
||||
|
||||
|
@ -133,7 +135,12 @@ void Turntable::Update()
|
|||
tt_data.bt ^= (BUTTON_L_GREEN | BUTTON_L_RED | BUTTON_L_BLUE | BUTTON_R_GREEN | BUTTON_R_RED |
|
||||
BUTTON_R_BLUE | BUTTON_MINUS | BUTTON_PLUS | BUTTON_EUPHORIA);
|
||||
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tt_data;
|
||||
target_state->data = tt_data;
|
||||
}
|
||||
|
||||
void Turntable::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
|
||||
}
|
||||
|
||||
void Turntable::Reset()
|
||||
|
|
|
@ -56,7 +56,8 @@ public:
|
|||
|
||||
Turntable();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
|
||||
ControllerEmu::ControlGroup* GetGroup(TurntableGroup group);
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "Common/BitUtils.h"
|
||||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
|
||||
#include "InputCommon/ControllerEmu/Control/Input.h"
|
||||
|
@ -48,7 +50,7 @@ UDrawTablet::UDrawTablet() : Extension3rdParty("uDraw", _trans("uDraw GameTablet
|
|||
m_touch->AddInput(ControllerEmu::Translate, _trans("Pressure"));
|
||||
}
|
||||
|
||||
void UDrawTablet::Update()
|
||||
void UDrawTablet::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
DataFormat tablet_data = {};
|
||||
|
||||
|
@ -105,7 +107,12 @@ void UDrawTablet::Update()
|
|||
// Always 0xff
|
||||
tablet_data.unk = 0xff;
|
||||
|
||||
Common::BitCastPtr<DataFormat>(&m_reg.controller_data) = tablet_data;
|
||||
target_state->data = tablet_data;
|
||||
}
|
||||
|
||||
void UDrawTablet::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
DefaultExtensionUpdate<DataFormat>(&m_reg, target_state);
|
||||
}
|
||||
|
||||
void UDrawTablet::Reset()
|
||||
|
|
|
@ -27,7 +27,8 @@ class UDrawTablet : public Extension3rdParty
|
|||
public:
|
||||
UDrawTablet();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
|
||||
ControllerEmu::ControlGroup* GetGroup(UDrawTabletGroup group);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteEmu/Dynamics.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -387,7 +388,12 @@ bool MotionPlus::ReadDeviceDetectPin() const
|
|||
}
|
||||
}
|
||||
|
||||
void MotionPlus::Update()
|
||||
void MotionPlus::BuildDesiredExtensionState(DesiredExtensionState* target_state)
|
||||
{
|
||||
// MotionPlus is handled separately, nothing to do here.
|
||||
}
|
||||
|
||||
void MotionPlus::Update(const DesiredExtensionState& target_state)
|
||||
{
|
||||
if (m_progress_timer)
|
||||
--m_progress_timer;
|
||||
|
@ -522,9 +528,56 @@ void MotionPlus::Update()
|
|||
}
|
||||
}
|
||||
|
||||
MotionPlus::DataFormat::Data MotionPlus::GetGyroscopeData(const Common::Vec3& angular_velocity)
|
||||
{
|
||||
// Conversion from radians to the calibrated values in degrees.
|
||||
constexpr float VALUE_SCALE =
|
||||
(CALIBRATION_SCALE_OFFSET >> (CALIBRATION_BITS - BITS_OF_PRECISION)) / float(MathUtil::TAU) *
|
||||
360;
|
||||
|
||||
constexpr float SLOW_SCALE = VALUE_SCALE / CALIBRATION_SLOW_SCALE_DEGREES;
|
||||
constexpr float FAST_SCALE = VALUE_SCALE / CALIBRATION_FAST_SCALE_DEGREES;
|
||||
|
||||
static_assert(ZERO_VALUE == 1 << (BITS_OF_PRECISION - 1),
|
||||
"SLOW_MAX_RAD_PER_SEC assumes calibrated zero is at center of sensor values.");
|
||||
|
||||
constexpr u16 SENSOR_RANGE = 1 << (BITS_OF_PRECISION - 1);
|
||||
constexpr float SLOW_MAX_RAD_PER_SEC = SENSOR_RANGE / SLOW_SCALE;
|
||||
|
||||
// Slow (high precision) scaling can be used if it fits in the sensor range.
|
||||
const float yaw = angular_velocity.z;
|
||||
const bool yaw_slow = (std::abs(yaw) < SLOW_MAX_RAD_PER_SEC);
|
||||
const s32 yaw_value = yaw * (yaw_slow ? SLOW_SCALE : FAST_SCALE);
|
||||
|
||||
const float roll = angular_velocity.y;
|
||||
const bool roll_slow = (std::abs(roll) < SLOW_MAX_RAD_PER_SEC);
|
||||
const s32 roll_value = roll * (roll_slow ? SLOW_SCALE : FAST_SCALE);
|
||||
|
||||
const float pitch = angular_velocity.x;
|
||||
const bool pitch_slow = (std::abs(pitch) < SLOW_MAX_RAD_PER_SEC);
|
||||
const s32 pitch_value = pitch * (pitch_slow ? SLOW_SCALE : FAST_SCALE);
|
||||
|
||||
const u16 clamped_yaw_value = u16(std::clamp(yaw_value + ZERO_VALUE, 0, MAX_VALUE));
|
||||
const u16 clamped_roll_value = u16(std::clamp(roll_value + ZERO_VALUE, 0, MAX_VALUE));
|
||||
const u16 clamped_pitch_value = u16(std::clamp(pitch_value + ZERO_VALUE, 0, MAX_VALUE));
|
||||
|
||||
return MotionPlus::DataFormat::Data{
|
||||
MotionPlus::DataFormat::GyroRawValue{MotionPlus::DataFormat::GyroType(
|
||||
clamped_pitch_value, clamped_roll_value, clamped_yaw_value)},
|
||||
MotionPlus::DataFormat::SlowType(pitch_slow, roll_slow, yaw_slow)};
|
||||
}
|
||||
|
||||
MotionPlus::DataFormat::Data MotionPlus::GetDefaultGyroscopeData()
|
||||
{
|
||||
return MotionPlus::DataFormat::Data{
|
||||
MotionPlus::DataFormat::GyroRawValue{
|
||||
MotionPlus::DataFormat::GyroType(u16(ZERO_VALUE), u16(ZERO_VALUE), u16(ZERO_VALUE))},
|
||||
MotionPlus::DataFormat::SlowType(true, true, true)};
|
||||
}
|
||||
|
||||
// This is something that is triggered by a read of 0x00 on real hardware.
|
||||
// But we do it here for determinism reasons.
|
||||
void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
|
||||
void MotionPlus::PrepareInput(const MotionPlus::DataFormat::Data& gyroscope_data)
|
||||
{
|
||||
if (GetActivationStatus() != ActivationStatus::Active)
|
||||
return;
|
||||
|
@ -592,41 +645,16 @@ void MotionPlus::PrepareInput(const Common::Vec3& angular_velocity)
|
|||
// If the above logic determined this should be M+ data, update it here.
|
||||
if (mplus_data.is_mp_data)
|
||||
{
|
||||
constexpr int BITS_OF_PRECISION = 14;
|
||||
const bool pitch_slow = gyroscope_data.is_slow.x;
|
||||
const bool roll_slow = gyroscope_data.is_slow.y;
|
||||
const bool yaw_slow = gyroscope_data.is_slow.z;
|
||||
const u16 pitch_value = gyroscope_data.gyro.value.x;
|
||||
const u16 roll_value = gyroscope_data.gyro.value.y;
|
||||
const u16 yaw_value = gyroscope_data.gyro.value.z;
|
||||
|
||||
// Conversion from radians to the calibrated values in degrees.
|
||||
constexpr float VALUE_SCALE =
|
||||
(CALIBRATION_SCALE_OFFSET >> (CALIBRATION_BITS - BITS_OF_PRECISION)) /
|
||||
float(MathUtil::TAU) * 360;
|
||||
|
||||
constexpr float SLOW_SCALE = VALUE_SCALE / CALIBRATION_SLOW_SCALE_DEGREES;
|
||||
constexpr float FAST_SCALE = VALUE_SCALE / CALIBRATION_FAST_SCALE_DEGREES;
|
||||
|
||||
constexpr s32 ZERO_VALUE = CALIBRATION_ZERO >> (CALIBRATION_BITS - BITS_OF_PRECISION);
|
||||
constexpr s32 MAX_VALUE = (1 << BITS_OF_PRECISION) - 1;
|
||||
|
||||
static_assert(ZERO_VALUE == 1 << (BITS_OF_PRECISION - 1),
|
||||
"SLOW_MAX_RAD_PER_SEC assumes calibrated zero is at center of sensor values.");
|
||||
|
||||
constexpr u16 SENSOR_RANGE = 1 << (BITS_OF_PRECISION - 1);
|
||||
constexpr float SLOW_MAX_RAD_PER_SEC = SENSOR_RANGE / SLOW_SCALE;
|
||||
|
||||
// Slow (high precision) scaling can be used if it fits in the sensor range.
|
||||
const float yaw = angular_velocity.z;
|
||||
mplus_data.yaw_slow = (std::abs(yaw) < SLOW_MAX_RAD_PER_SEC);
|
||||
s32 yaw_value = yaw * (mplus_data.yaw_slow ? SLOW_SCALE : FAST_SCALE);
|
||||
|
||||
const float roll = angular_velocity.y;
|
||||
mplus_data.roll_slow = (std::abs(roll) < SLOW_MAX_RAD_PER_SEC);
|
||||
s32 roll_value = roll * (mplus_data.roll_slow ? SLOW_SCALE : FAST_SCALE);
|
||||
|
||||
const float pitch = angular_velocity.x;
|
||||
mplus_data.pitch_slow = (std::abs(pitch) < SLOW_MAX_RAD_PER_SEC);
|
||||
s32 pitch_value = pitch * (mplus_data.pitch_slow ? SLOW_SCALE : FAST_SCALE);
|
||||
|
||||
yaw_value = std::clamp(yaw_value + ZERO_VALUE, 0, MAX_VALUE);
|
||||
roll_value = std::clamp(roll_value + ZERO_VALUE, 0, MAX_VALUE);
|
||||
pitch_value = std::clamp(pitch_value + ZERO_VALUE, 0, MAX_VALUE);
|
||||
mplus_data.yaw_slow = u8(yaw_slow);
|
||||
mplus_data.roll_slow = u8(roll_slow);
|
||||
mplus_data.pitch_slow = u8(pitch_slow);
|
||||
|
||||
// Bits 0-7
|
||||
mplus_data.yaw1 = u8(yaw_value);
|
||||
|
|
|
@ -118,14 +118,18 @@ public:
|
|||
|
||||
MotionPlus();
|
||||
|
||||
void Update() override;
|
||||
void BuildDesiredExtensionState(DesiredExtensionState* target_state) override;
|
||||
void Update(const DesiredExtensionState& target_state) override;
|
||||
void Reset() override;
|
||||
void DoState(PointerWrap& p) override;
|
||||
|
||||
ExtensionPort& GetExtPort();
|
||||
|
||||
// Vec3 is interpreted as radians/s about the x,y,z axes following the "right-hand rule".
|
||||
void PrepareInput(const Common::Vec3& angular_velocity);
|
||||
static MotionPlus::DataFormat::Data GetGyroscopeData(const Common::Vec3& angular_velocity);
|
||||
static MotionPlus::DataFormat::Data GetDefaultGyroscopeData();
|
||||
|
||||
void PrepareInput(const MotionPlus::DataFormat::Data& gyroscope_data);
|
||||
|
||||
// Pointer to 6 bytes is expected.
|
||||
static void ApplyPassthroughModifications(PassthroughMode, u8* data);
|
||||
|
@ -218,6 +222,10 @@ private:
|
|||
static constexpr u16 CALIBRATION_FAST_SCALE_DEGREES = 0x4b0;
|
||||
static constexpr u16 CALIBRATION_SLOW_SCALE_DEGREES = 0x10e;
|
||||
|
||||
static constexpr int BITS_OF_PRECISION = 14;
|
||||
static constexpr s32 ZERO_VALUE = CALIBRATION_ZERO >> (CALIBRATION_BITS - BITS_OF_PRECISION);
|
||||
static constexpr s32 MAX_VALUE = (1 << BITS_OF_PRECISION) - 1;
|
||||
|
||||
void Activate();
|
||||
void Deactivate();
|
||||
void OnPassthroughModeWrite();
|
||||
|
|
|
@ -21,11 +21,12 @@
|
|||
#include "Core/Core.h"
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/Movie.h"
|
||||
#include "Core/NetPlayClient.h"
|
||||
|
||||
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
|
||||
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
|
||||
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Classic.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/DesiredExtensionState.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/DrawsomeTablet.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Drums.h"
|
||||
#include "Core/HW/WiimoteEmu/Extension/Guitar.h"
|
||||
|
@ -68,6 +69,8 @@ constexpr std::array<std::string_view, 7> named_buttons{
|
|||
|
||||
void Wiimote::Reset()
|
||||
{
|
||||
const bool want_determinism = Core::WantsDeterminism();
|
||||
|
||||
SetRumble(false);
|
||||
|
||||
// Wiimote starts in non-continuous CORE mode:
|
||||
|
@ -77,8 +80,12 @@ void Wiimote::Reset()
|
|||
m_speaker_mute = false;
|
||||
|
||||
// EEPROM
|
||||
|
||||
// TODO: This feels sketchy, this needs to properly handle the case where the load and the write
|
||||
// happen under different Wii Roots and/or determinism modes.
|
||||
|
||||
std::string eeprom_file = (File::GetUserPath(D_SESSION_WIIROOT_IDX) + "/" + GetName() + ".bin");
|
||||
if (m_eeprom_dirty)
|
||||
if (!want_determinism && m_eeprom_dirty)
|
||||
{
|
||||
// Write out existing EEPROM
|
||||
INFO_LOG_FMT(WIIMOTE, "Wrote EEPROM for {}", GetName());
|
||||
|
@ -91,7 +98,7 @@ void Wiimote::Reset()
|
|||
}
|
||||
m_eeprom = {};
|
||||
|
||||
if (File::Exists(eeprom_file))
|
||||
if (!want_determinism && File::Exists(eeprom_file))
|
||||
{
|
||||
// Read existing EEPROM
|
||||
std::ifstream file;
|
||||
|
@ -171,18 +178,26 @@ void Wiimote::Reset()
|
|||
m_extension_port.AttachExtension(GetNoneExtension());
|
||||
m_motion_plus.GetExtPort().AttachExtension(GetNoneExtension());
|
||||
|
||||
if (!want_determinism)
|
||||
{
|
||||
// Switch to desired M+ status and extension (if any).
|
||||
// M+ and EXT are reset on attachment.
|
||||
HandleExtensionSwap();
|
||||
HandleExtensionSwap(static_cast<ExtensionNumber>(m_attachments->GetSelectedAttachment()),
|
||||
m_motion_plus_setting.GetValue());
|
||||
}
|
||||
|
||||
// Reset sub-devices.
|
||||
m_speaker_logic.Reset();
|
||||
m_camera_logic.Reset();
|
||||
|
||||
m_status = {};
|
||||
|
||||
if (!want_determinism)
|
||||
{
|
||||
// This will suppress a status report on connect when an extension is already attached.
|
||||
// TODO: I am not 100% sure if this is proper.
|
||||
m_status.extension = m_extension_port.IsDeviceConnected();
|
||||
}
|
||||
|
||||
// Dynamics:
|
||||
m_swing_state = {};
|
||||
|
@ -193,7 +208,7 @@ void Wiimote::Reset()
|
|||
m_imu_cursor_state = {};
|
||||
}
|
||||
|
||||
Wiimote::Wiimote(const unsigned int index) : m_index(index)
|
||||
Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(index)
|
||||
{
|
||||
// Buttons
|
||||
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
|
||||
|
@ -427,20 +442,13 @@ bool Wiimote::ProcessExtensionPortEvent()
|
|||
return true;
|
||||
}
|
||||
|
||||
// Update buttons in status struct from user input.
|
||||
void Wiimote::UpdateButtonsStatus()
|
||||
void Wiimote::UpdateButtonsStatus(const DesiredWiimoteState& target_state)
|
||||
{
|
||||
m_status.buttons.hex = 0;
|
||||
|
||||
m_buttons->GetState(&m_status.buttons.hex, button_bitmasks);
|
||||
m_dpad->GetState(&m_status.buttons.hex, IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks);
|
||||
m_status.buttons.hex = target_state.buttons.hex & ButtonData::BUTTON_MASK;
|
||||
}
|
||||
|
||||
// This is called every ::Wiimote::UPDATE_FREQ (200hz)
|
||||
void Wiimote::Update()
|
||||
void Wiimote::BuildDesiredWiimoteState(DesiredWiimoteState* target_state)
|
||||
{
|
||||
const auto lock = GetStateLock();
|
||||
|
||||
// Hotkey / settings modifier
|
||||
// Data is later accessed in IsSideways and IsUpright
|
||||
m_hotkeys->UpdateState();
|
||||
|
@ -448,26 +456,70 @@ void Wiimote::Update()
|
|||
// Update our motion simulations.
|
||||
StepDynamics();
|
||||
|
||||
// Fetch pressed buttons from user input.
|
||||
target_state->buttons.hex = 0;
|
||||
m_buttons->GetState(&target_state->buttons.hex, button_bitmasks);
|
||||
m_dpad->GetState(&target_state->buttons.hex,
|
||||
IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks);
|
||||
|
||||
// Calculate accelerometer state.
|
||||
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
|
||||
target_state->acceleration =
|
||||
ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
|
||||
|
||||
// Calculate IR camera state.
|
||||
target_state->camera_points = CameraLogic::GetCameraPoints(
|
||||
GetTotalTransformation(),
|
||||
Common::Vec2(m_fov_x_setting.GetValue(), m_fov_y_setting.GetValue()) / 360 *
|
||||
float(MathUtil::TAU));
|
||||
|
||||
// Calculate MotionPlus state.
|
||||
if (m_motion_plus_setting.GetValue())
|
||||
target_state->motion_plus = MotionPlus::GetGyroscopeData(GetTotalAngularVelocity());
|
||||
else
|
||||
target_state->motion_plus = std::nullopt;
|
||||
|
||||
// Build Extension state.
|
||||
// This also allows the extension to perform any regular duties it may need.
|
||||
// (e.g. Nunchuk motion simulation step)
|
||||
static_cast<Extension*>(
|
||||
m_attachments->GetAttachmentList()[m_attachments->GetSelectedAttachment()].get())
|
||||
->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)
|
||||
{
|
||||
const auto lock = GetStateLock();
|
||||
BuildDesiredWiimoteState(target_state);
|
||||
}
|
||||
|
||||
void Wiimote::Update(const WiimoteEmu::DesiredWiimoteState& target_state)
|
||||
{
|
||||
// Update buttons in the status struct which is sent in 99% of input reports.
|
||||
// FYI: Movies only sync button updates in data reports.
|
||||
if (!Core::WantsDeterminism())
|
||||
{
|
||||
UpdateButtonsStatus();
|
||||
}
|
||||
UpdateButtonsStatus(target_state);
|
||||
|
||||
// If a new extension is requested in the GUI the change will happen here.
|
||||
HandleExtensionSwap();
|
||||
HandleExtensionSwap(static_cast<ExtensionNumber>(target_state.extension.data.index()),
|
||||
target_state.motion_plus.has_value());
|
||||
|
||||
// Allow extension to perform any regular duties it may need.
|
||||
// (e.g. Nunchuk motion simulation step)
|
||||
// Input is prepared here too.
|
||||
// TODO: Separate input preparation from Update.
|
||||
GetActiveExtension()->Update();
|
||||
// Prepare input data of the extension for reading.
|
||||
GetActiveExtension()->Update(target_state.extension);
|
||||
|
||||
if (m_is_motion_plus_attached)
|
||||
{
|
||||
// M+ has some internal state that must processed.
|
||||
m_motion_plus.Update();
|
||||
m_motion_plus.Update(target_state.extension);
|
||||
}
|
||||
|
||||
// Returns true if a report was sent.
|
||||
|
@ -485,10 +537,10 @@ void Wiimote::Update()
|
|||
return;
|
||||
}
|
||||
|
||||
SendDataReport();
|
||||
SendDataReport(target_state);
|
||||
}
|
||||
|
||||
void Wiimote::SendDataReport()
|
||||
void Wiimote::SendDataReport(const DesiredWiimoteState& target_state)
|
||||
{
|
||||
Movie::SetPolledDevice();
|
||||
|
||||
|
@ -508,7 +560,8 @@ void Wiimote::SendDataReport()
|
|||
DataReportBuilder rpt_builder(m_reporting_mode);
|
||||
|
||||
if (Movie::IsPlayingInput() &&
|
||||
Movie::PlayWiimote(m_index, rpt_builder, m_active_extension, GetExtensionEncryptionKey()))
|
||||
Movie::PlayWiimote(m_bt_device_index, rpt_builder, m_active_extension,
|
||||
GetExtensionEncryptionKey()))
|
||||
{
|
||||
// Update buttons in status struct from movie:
|
||||
rpt_builder.GetCoreData(&m_status.buttons);
|
||||
|
@ -518,22 +571,13 @@ void Wiimote::SendDataReport()
|
|||
// Core buttons:
|
||||
if (rpt_builder.HasCore())
|
||||
{
|
||||
if (Core::WantsDeterminism())
|
||||
{
|
||||
// When running non-deterministically we've already updated buttons in Update()
|
||||
UpdateButtonsStatus();
|
||||
}
|
||||
|
||||
rpt_builder.SetCoreData(m_status.buttons);
|
||||
}
|
||||
|
||||
// Acceleration:
|
||||
if (rpt_builder.HasAccel())
|
||||
{
|
||||
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
|
||||
AccelData accel =
|
||||
ConvertAccelData(GetTotalAcceleration(), ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
|
||||
rpt_builder.SetAccelData(accel);
|
||||
rpt_builder.SetAccelData(target_state.acceleration);
|
||||
}
|
||||
|
||||
// IR Camera:
|
||||
|
@ -541,9 +585,7 @@ void Wiimote::SendDataReport()
|
|||
{
|
||||
// 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(GetTotalTransformation(),
|
||||
Common::Vec2(m_fov_x_setting.GetValue(), m_fov_y_setting.GetValue()) /
|
||||
360 * float(MathUtil::TAU));
|
||||
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 =
|
||||
|
@ -571,7 +613,9 @@ void Wiimote::SendDataReport()
|
|||
if (m_is_motion_plus_attached)
|
||||
{
|
||||
// TODO: Make input preparation triggered by bus read.
|
||||
m_motion_plus.PrepareInput(GetTotalAngularVelocity());
|
||||
m_motion_plus.PrepareInput(target_state.motion_plus.has_value() ?
|
||||
target_state.motion_plus.value() :
|
||||
MotionPlus::GetDefaultGyroscopeData());
|
||||
}
|
||||
|
||||
u8* ext_data = rpt_builder.GetExtDataPtr();
|
||||
|
@ -585,19 +629,12 @@ void Wiimote::SendDataReport()
|
|||
}
|
||||
}
|
||||
|
||||
Movie::CallWiiInputManip(rpt_builder, m_index, m_active_extension, GetExtensionEncryptionKey());
|
||||
Movie::CallWiiInputManip(rpt_builder, m_bt_device_index, m_active_extension,
|
||||
GetExtensionEncryptionKey());
|
||||
}
|
||||
|
||||
if (NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
NetPlay_GetWiimoteData(m_index, rpt_builder.GetDataPtr(), rpt_builder.GetDataSize(),
|
||||
u8(m_reporting_mode));
|
||||
|
||||
// TODO: clean up how m_status.buttons is updated.
|
||||
rpt_builder.GetCoreData(&m_status.buttons);
|
||||
}
|
||||
|
||||
Movie::CheckWiimoteStatus(m_index, rpt_builder, m_active_extension, GetExtensionEncryptionKey());
|
||||
Movie::CheckWiimoteStatus(m_bt_device_index, rpt_builder, m_active_extension,
|
||||
GetExtensionEncryptionKey());
|
||||
|
||||
// Send the report:
|
||||
InterruptDataInputCallback(rpt_builder.GetDataPtr(), rpt_builder.GetDataSize());
|
||||
|
@ -609,14 +646,15 @@ void Wiimote::SendDataReport()
|
|||
m_reporting_mode = InputReportID::ReportInterleave1;
|
||||
}
|
||||
|
||||
bool Wiimote::IsButtonPressed()
|
||||
ButtonData Wiimote::GetCurrentlyPressedButtons()
|
||||
{
|
||||
u16 buttons = 0;
|
||||
const auto lock = GetStateLock();
|
||||
m_buttons->GetState(&buttons, button_bitmasks);
|
||||
m_dpad->GetState(&buttons, dpad_bitmasks);
|
||||
|
||||
return buttons != 0;
|
||||
ButtonData buttons{};
|
||||
m_buttons->GetState(&buttons.hex, button_bitmasks);
|
||||
m_dpad->GetState(&buttons.hex, IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks);
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
void Wiimote::LoadDefaults(const ControllerInterface& ciface)
|
||||
|
|
|
@ -37,6 +37,9 @@ class Tilt;
|
|||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
struct DesiredWiimoteState;
|
||||
struct DesiredExtensionState;
|
||||
|
||||
enum class WiimoteGroup
|
||||
{
|
||||
Buttons,
|
||||
|
@ -126,11 +129,15 @@ public:
|
|||
ControllerEmu::ControlGroup* GetTaTaConGroup(TaTaConGroup group) const;
|
||||
ControllerEmu::ControlGroup* GetShinkansenGroup(ShinkansenGroup group) const;
|
||||
|
||||
void Update() override;
|
||||
u8 GetWiimoteDeviceIndex() const override;
|
||||
void SetWiimoteDeviceIndex(u8 index) override;
|
||||
|
||||
void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) override;
|
||||
void Update(const WiimoteEmu::DesiredWiimoteState& target_state) override;
|
||||
void EventLinked() override;
|
||||
void EventUnlinked() override;
|
||||
void InterruptDataOutput(const u8* data, u32 size) override;
|
||||
bool IsButtonPressed() override;
|
||||
WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override;
|
||||
|
||||
void Reset();
|
||||
|
||||
|
@ -150,7 +157,8 @@ private:
|
|||
void RefreshConfig();
|
||||
|
||||
void StepDynamics();
|
||||
void UpdateButtonsStatus();
|
||||
void UpdateButtonsStatus(const DesiredWiimoteState& target_state);
|
||||
void BuildDesiredWiimoteState(DesiredWiimoteState* target_state);
|
||||
|
||||
// Returns simulated accelerometer data in m/s^2.
|
||||
Common::Vec3 GetAcceleration(
|
||||
|
@ -187,9 +195,9 @@ private:
|
|||
template <typename T, typename H>
|
||||
void InvokeHandler(H&& handler, const WiimoteCommon::OutputReportGeneric& rpt, u32 size);
|
||||
|
||||
void HandleExtensionSwap();
|
||||
void HandleExtensionSwap(ExtensionNumber desired_extension_number, bool desired_motion_plus);
|
||||
bool ProcessExtensionPortEvent();
|
||||
void SendDataReport();
|
||||
void SendDataReport(const DesiredWiimoteState& target_state);
|
||||
bool ProcessReadDataRequest();
|
||||
|
||||
void SetRumble(bool on);
|
||||
|
@ -202,8 +210,6 @@ private:
|
|||
Extension* GetActiveExtension() const;
|
||||
Extension* GetNoneExtension() const;
|
||||
|
||||
bool NetPlay_GetWiimoteData(int wiimote, u8* data, u8 size, u8 reporting_mode);
|
||||
|
||||
// TODO: Kill this nonsensical function used for TAS:
|
||||
EncryptionKey GetExtensionEncryptionKey() const;
|
||||
|
||||
|
@ -277,9 +283,15 @@ private:
|
|||
|
||||
ExtensionPort m_extension_port{&m_i2c_bus};
|
||||
|
||||
// Wiimote index, 0-3
|
||||
// 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;
|
||||
|
||||
|
|
|
@ -450,7 +450,22 @@ Report& Wiimote::ProcessReadQueue(bool repeat_last_data_report)
|
|||
return m_last_input_report;
|
||||
}
|
||||
|
||||
void Wiimote::Update()
|
||||
u8 Wiimote::GetWiimoteDeviceIndex() const
|
||||
{
|
||||
return m_bt_device_index;
|
||||
}
|
||||
|
||||
void Wiimote::SetWiimoteDeviceIndex(u8 index)
|
||||
{
|
||||
m_bt_device_index = index;
|
||||
}
|
||||
|
||||
void Wiimote::PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state)
|
||||
{
|
||||
// Nothing to do here on real Wiimotes.
|
||||
}
|
||||
|
||||
void Wiimote::Update(const WiimoteEmu::DesiredWiimoteState& target_state)
|
||||
{
|
||||
// Wii remotes send input at 200hz once a Wii enables "sniff mode" on the connection.
|
||||
// PC bluetooth stacks do not enable sniff mode causing remotes to send input at only 100hz.
|
||||
|
@ -475,7 +490,7 @@ void Wiimote::Update()
|
|||
u32(rpt.size() - REPORT_HID_HEADER_SIZE));
|
||||
}
|
||||
|
||||
bool Wiimote::IsButtonPressed()
|
||||
ButtonData Wiimote::GetCurrentlyPressedButtons()
|
||||
{
|
||||
Report& rpt = m_last_input_report;
|
||||
if (rpt.size() >= 4)
|
||||
|
@ -489,10 +504,10 @@ bool Wiimote::IsButtonPressed()
|
|||
ButtonData buttons = {};
|
||||
builder->GetCoreData(&buttons);
|
||||
|
||||
return buttons.hex != 0;
|
||||
return buttons;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return ButtonData{};
|
||||
}
|
||||
|
||||
void Wiimote::Prepare()
|
||||
|
|
|
@ -62,10 +62,15 @@ public:
|
|||
bool IsBalanceBoard();
|
||||
|
||||
void InterruptDataOutput(const u8* data, const u32 size) override;
|
||||
void Update() override;
|
||||
|
||||
u8 GetWiimoteDeviceIndex() const override;
|
||||
void SetWiimoteDeviceIndex(u8 index) override;
|
||||
|
||||
void PrepareInput(WiimoteEmu::DesiredWiimoteState* target_state) override;
|
||||
void Update(const WiimoteEmu::DesiredWiimoteState& target_state) override;
|
||||
void EventLinked() override;
|
||||
void EventUnlinked() override;
|
||||
bool IsButtonPressed() override;
|
||||
WiimoteCommon::ButtonData GetCurrentlyPressedButtons() override;
|
||||
|
||||
void EmuStop();
|
||||
|
||||
|
@ -98,6 +103,8 @@ protected:
|
|||
// This is not enabled on all platforms as connecting a Wiimote can be a pain on some platforms.
|
||||
bool m_really_disconnect = false;
|
||||
|
||||
u8 m_bt_device_index = 0;
|
||||
|
||||
private:
|
||||
void Read();
|
||||
bool Write();
|
||||
|
|
|
@ -19,8 +19,11 @@
|
|||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
|
||||
#include "Core/IOS/Device.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/NetPlayClient.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/SysConf.h"
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
|
@ -58,7 +61,9 @@ BluetoothEmuDevice::BluetoothEmuDevice(Kernel& ios, const std::string& device_na
|
|||
DEBUG_LOG_FMT(IOS_WIIMOTE, "Wii Remote {} BT ID {:x},{:x},{:x},{:x},{:x},{:x}", i, tmp_bd[0],
|
||||
tmp_bd[1], tmp_bd[2], tmp_bd[3], tmp_bd[4], tmp_bd[5]);
|
||||
|
||||
m_wiimotes.emplace_back(std::make_unique<WiimoteDevice>(this, i, tmp_bd));
|
||||
const unsigned int hid_source_number =
|
||||
NetPlay::IsNetPlayRunning() ? NetPlay::NetPlay_GetLocalWiimoteForSlot(i) : i;
|
||||
m_wiimotes[i] = std::make_unique<WiimoteDevice>(this, tmp_bd, hid_source_number);
|
||||
}
|
||||
|
||||
bt_dinf.num_registered = MAX_BBMOTES;
|
||||
|
@ -340,8 +345,45 @@ void BluetoothEmuDevice::Update()
|
|||
{
|
||||
g_controller_interface.SetCurrentInputChannel(ciface::InputChannel::Bluetooth);
|
||||
g_controller_interface.UpdateInput();
|
||||
for (auto& wiimote : m_wiimotes)
|
||||
wiimote->UpdateInput();
|
||||
|
||||
std::array<WiimoteEmu::DesiredWiimoteState, MAX_BBMOTES> wiimote_states;
|
||||
std::array<WiimoteDevice::NextUpdateInputCall, MAX_BBMOTES> next_call;
|
||||
|
||||
for (size_t i = 0; i < m_wiimotes.size(); ++i)
|
||||
next_call[i] = m_wiimotes[i]->PrepareInput(&wiimote_states[i]);
|
||||
|
||||
if (NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
std::array<WiimoteEmu::SerializedWiimoteState, MAX_BBMOTES> serialized;
|
||||
std::array<NetPlay::NetPlayClient::WiimoteDataBatchEntry, MAX_BBMOTES> batch;
|
||||
size_t batch_count = 0;
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
if (next_call[i] == WiimoteDevice::NextUpdateInputCall::None)
|
||||
continue;
|
||||
serialized[i] = WiimoteEmu::SerializeDesiredState(wiimote_states[i]);
|
||||
batch[batch_count].state = &serialized[i];
|
||||
batch[batch_count].wiimote = static_cast<int>(i);
|
||||
++batch_count;
|
||||
}
|
||||
|
||||
if (batch_count > 0)
|
||||
{
|
||||
NetPlay::NetPlay_GetWiimoteData(
|
||||
std::span<NetPlay::NetPlayClient::WiimoteDataBatchEntry>(batch.data(), batch_count));
|
||||
|
||||
for (size_t i = 0; i < batch_count; ++i)
|
||||
{
|
||||
const int wiimote = batch[i].wiimote;
|
||||
if (!WiimoteEmu::DeserializeDesiredState(&wiimote_states[wiimote], serialized[wiimote]))
|
||||
PanicAlertFmtT("Received invalid Wii Remote data from Netplay.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_wiimotes.size(); ++i)
|
||||
m_wiimotes[i]->UpdateInput(next_call[i], wiimote_states[i]);
|
||||
|
||||
m_last_ticks = now;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ public:
|
|||
void DoState(PointerWrap& p) override;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<WiimoteDevice>> m_wiimotes;
|
||||
std::array<std::unique_ptr<WiimoteDevice>, MAX_BBMOTES> m_wiimotes;
|
||||
|
||||
bdaddr_t m_controller_bd{{0x11, 0x02, 0x19, 0x79, 0x00, 0xff}};
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteCommon/WiimoteConstants.h"
|
||||
#include "Core/HW/WiimoteCommon/WiimoteHid.h"
|
||||
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTEmu.h"
|
||||
#include "Core/IOS/USB/Bluetooth/WiimoteHIDAttr.h"
|
||||
|
@ -54,24 +55,28 @@ private:
|
|||
|
||||
constexpr int CONNECTION_MESSAGE_TIME = 3000;
|
||||
|
||||
WiimoteDevice::WiimoteDevice(BluetoothEmuDevice* host, int number, bdaddr_t bd)
|
||||
WiimoteDevice::WiimoteDevice(BluetoothEmuDevice* host, bdaddr_t bd, unsigned int hid_source_number)
|
||||
: m_host(host), m_bd(bd),
|
||||
m_name(number == WIIMOTE_BALANCE_BOARD ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01")
|
||||
m_name(GetNumber() == WIIMOTE_BALANCE_BOARD ? "Nintendo RVL-WBC-01" : "Nintendo RVL-CNT-01")
|
||||
|
||||
{
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "Wiimote: #{} Constructed", number);
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "Wiimote: #{} Constructed", GetNumber());
|
||||
|
||||
m_link_key.fill(0xa0 + number);
|
||||
m_link_key.fill(0xa0 + GetNumber());
|
||||
m_class = {0x00, 0x04, 0x48};
|
||||
m_features = {0xBC, 0x02, 0x04, 0x38, 0x08, 0x00, 0x00, 0x00};
|
||||
m_lmp_version = 0x2;
|
||||
m_lmp_subversion = 0x229;
|
||||
|
||||
const auto hid_source = WiimoteCommon::GetHIDWiimoteSource(GetNumber());
|
||||
const auto hid_source = WiimoteCommon::GetHIDWiimoteSource(hid_source_number);
|
||||
|
||||
if (hid_source)
|
||||
{
|
||||
hid_source->SetWiimoteDeviceIndex(GetNumber());
|
||||
|
||||
// UGLY: This prevents an OSD message in SetSource -> Activate.
|
||||
if (hid_source)
|
||||
SetBasebandState(BasebandState::RequestConnection);
|
||||
}
|
||||
|
||||
SetSource(hid_source);
|
||||
}
|
||||
|
@ -337,25 +342,51 @@ void WiimoteDevice::Update()
|
|||
}
|
||||
}
|
||||
|
||||
void WiimoteDevice::UpdateInput()
|
||||
WiimoteDevice::NextUpdateInputCall
|
||||
WiimoteDevice::PrepareInput(WiimoteEmu::DesiredWiimoteState* wiimote_state)
|
||||
{
|
||||
if (m_connection_request_counter)
|
||||
--m_connection_request_counter;
|
||||
|
||||
if (!IsSourceValid())
|
||||
return;
|
||||
return NextUpdateInputCall::None;
|
||||
|
||||
// Allow button press to trigger activation after a second of no connection activity.
|
||||
if (!m_connection_request_counter && m_baseband_state == BasebandState::Inactive)
|
||||
if (m_baseband_state == BasebandState::Inactive)
|
||||
{
|
||||
if (Wiimote::NetPlay_GetButtonPress(GetNumber(), m_hid_source->IsButtonPressed()))
|
||||
Activate(true);
|
||||
// Allow button press to trigger activation after a second of no connection activity.
|
||||
if (!m_connection_request_counter)
|
||||
{
|
||||
wiimote_state->buttons = m_hid_source->GetCurrentlyPressedButtons();
|
||||
return NextUpdateInputCall::Activate;
|
||||
}
|
||||
return NextUpdateInputCall::None;
|
||||
}
|
||||
|
||||
// Verify interrupt channel is connected and configured.
|
||||
const auto* channel = FindChannelWithPSM(L2CAP_PSM_HID_INTR);
|
||||
if (channel && channel->IsComplete())
|
||||
m_hid_source->Update();
|
||||
{
|
||||
m_hid_source->PrepareInput(wiimote_state);
|
||||
return NextUpdateInputCall::Update;
|
||||
}
|
||||
return NextUpdateInputCall::None;
|
||||
}
|
||||
|
||||
void WiimoteDevice::UpdateInput(NextUpdateInputCall next_call,
|
||||
const WiimoteEmu::DesiredWiimoteState& wiimote_state)
|
||||
{
|
||||
switch (next_call)
|
||||
{
|
||||
case NextUpdateInputCall::Activate:
|
||||
if (wiimote_state.buttons.hex & WiimoteCommon::ButtonData::BUTTON_MASK)
|
||||
Activate(true);
|
||||
break;
|
||||
case NextUpdateInputCall::Update:
|
||||
m_hid_source->Update(wiimote_state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This function receives L2CAP commands from the CPU
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
|
||||
class PointerWrap;
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
struct DesiredWiimoteState;
|
||||
}
|
||||
|
||||
namespace IOS::HLE
|
||||
{
|
||||
class BluetoothEmuDevice;
|
||||
|
@ -24,7 +29,7 @@ public:
|
|||
using FeaturesType = std::array<u8, HCI_FEATURES_SIZE>;
|
||||
using LinkKeyType = std::array<u8, HCI_KEY_SIZE>;
|
||||
|
||||
WiimoteDevice(BluetoothEmuDevice* host, int number, bdaddr_t bd);
|
||||
WiimoteDevice(BluetoothEmuDevice* host, bdaddr_t bd, unsigned int hid_source_number);
|
||||
~WiimoteDevice();
|
||||
|
||||
WiimoteDevice(const WiimoteDevice&) = delete;
|
||||
|
@ -38,7 +43,15 @@ public:
|
|||
void Update();
|
||||
|
||||
// Called every ~200hz.
|
||||
void UpdateInput();
|
||||
enum class NextUpdateInputCall
|
||||
{
|
||||
None,
|
||||
Activate,
|
||||
Update
|
||||
};
|
||||
NextUpdateInputCall PrepareInput(WiimoteEmu::DesiredWiimoteState* wiimote_state);
|
||||
void UpdateInput(NextUpdateInputCall next_call,
|
||||
const WiimoteEmu::DesiredWiimoteState& wiimote_state);
|
||||
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
|
@ -54,6 +56,7 @@
|
|||
#include "Core/HW/Sram.h"
|
||||
#include "Core/HW/WiiSave.h"
|
||||
#include "Core/HW/WiiSaveStructs.h"
|
||||
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/IOS/FS/FileSystem.h"
|
||||
|
@ -695,20 +698,29 @@ void NetPlayClient::OnPadHostData(sf::Packet& packet)
|
|||
|
||||
void NetPlayClient::OnWiimoteData(sf::Packet& packet)
|
||||
{
|
||||
while (!packet.endOfPacket())
|
||||
{
|
||||
PadIndex map;
|
||||
WiimoteInput nw;
|
||||
u8 size;
|
||||
packet >> map;
|
||||
|
||||
packet >> map >> nw.report_id >> size;
|
||||
|
||||
nw.data.resize(size);
|
||||
for (auto& byte : nw.data)
|
||||
packet >> byte;
|
||||
WiimoteEmu::SerializedWiimoteState pad;
|
||||
packet >> pad.length;
|
||||
ASSERT(pad.length <= pad.data.size());
|
||||
if (pad.length <= pad.data.size())
|
||||
{
|
||||
for (size_t i = 0; i < pad.length; ++i)
|
||||
packet >> pad.data[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
pad.length = 0;
|
||||
}
|
||||
|
||||
// Trusting server for good map value (>=0 && <4)
|
||||
// add to Wiimote buffer
|
||||
m_wiimote_buffer.at(map).Push(nw);
|
||||
// add to pad buffer
|
||||
m_wiimote_buffer.at(map).Push(pad);
|
||||
m_wii_pad_event.Set();
|
||||
}
|
||||
}
|
||||
|
||||
void NetPlayClient::OnPadBuffer(sf::Packet& packet)
|
||||
|
@ -886,9 +898,6 @@ void NetPlayClient::OnStartGame(sf::Packet& packet)
|
|||
packet >> m_net_settings.save_data_region;
|
||||
packet >> m_net_settings.sync_codes;
|
||||
|
||||
for (int& extension : m_net_settings.wiimote_extension)
|
||||
packet >> extension;
|
||||
|
||||
packet >> m_net_settings.golf_mode;
|
||||
packet >> m_net_settings.use_fma;
|
||||
packet >> m_net_settings.hide_remote_gbas;
|
||||
|
@ -1619,15 +1628,14 @@ void NetPlayClient::AddPadStateToPacket(const int in_game_pad, const GCPadStatus
|
|||
}
|
||||
|
||||
// called from ---CPU--- thread
|
||||
void NetPlayClient::SendWiimoteState(const int in_game_pad, const WiimoteInput& nw)
|
||||
void NetPlayClient::AddWiimoteStateToPacket(int in_game_pad,
|
||||
const WiimoteEmu::SerializedWiimoteState& state,
|
||||
sf::Packet& packet)
|
||||
{
|
||||
sf::Packet packet;
|
||||
packet << MessageID::WiimoteData;
|
||||
packet << static_cast<PadIndex>(in_game_pad);
|
||||
packet << static_cast<u8>(nw.report_id);
|
||||
packet << static_cast<u8>(nw.data.size());
|
||||
packet.append(nw.data.data(), nw.data.size());
|
||||
SendAsync(std::move(packet));
|
||||
packet << state.length;
|
||||
for (size_t i = 0; i < state.length; ++i)
|
||||
packet << state.data[i];
|
||||
}
|
||||
|
||||
// called from ---GUI--- thread
|
||||
|
@ -2042,79 +2050,42 @@ u64 NetPlayClient::GetInitialRTCValue() const
|
|||
return m_initial_rtc;
|
||||
}
|
||||
|
||||
bool NetPlayClient::WaitForWiimoteBuffer(int _number)
|
||||
// called from ---CPU--- thread
|
||||
bool NetPlayClient::WiimoteUpdate(const std::span<WiimoteDataBatchEntry>& entries)
|
||||
{
|
||||
while (m_wiimote_buffer[_number].Size() == 0)
|
||||
for (const WiimoteDataBatchEntry& entry : entries)
|
||||
{
|
||||
const int local_wiimote = InGameWiimoteToLocalWiimote(entry.wiimote);
|
||||
DEBUG_LOG_FMT(NETPLAY,
|
||||
"Entering WiimoteUpdate() with wiimote {}, local_wiimote {}, state [{:02x}]",
|
||||
entry.wiimote, local_wiimote,
|
||||
fmt::join(std::span(entry.state->data.data(), entry.state->length), ", "));
|
||||
if (local_wiimote < 4)
|
||||
{
|
||||
sf::Packet packet;
|
||||
packet << MessageID::WiimoteData;
|
||||
if (AddLocalWiimoteToBuffer(local_wiimote, *entry.state, packet))
|
||||
SendAsync(std::move(packet));
|
||||
}
|
||||
|
||||
// Now, we either use the data pushed earlier, or wait for the
|
||||
// other clients to send it to us
|
||||
while (m_wiimote_buffer[entry.wiimote].Size() == 0)
|
||||
{
|
||||
if (!m_is_running.IsSet())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait for receiving thread to push some data
|
||||
m_wii_pad_event.Wait();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
m_wiimote_buffer[entry.wiimote].Pop(*entry.state);
|
||||
|
||||
// called from ---CPU--- thread
|
||||
bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const std::size_t size, u8 reporting_mode)
|
||||
{
|
||||
WiimoteInput nw;
|
||||
nw.report_id = reporting_mode;
|
||||
{
|
||||
std::lock_guard lkp(m_crit.players);
|
||||
|
||||
// Only send data, if this Wiimote is mapped to this player
|
||||
if (m_wiimote_map[_number] == m_local_player->pid)
|
||||
{
|
||||
nw.data.assign(data, data + size);
|
||||
|
||||
// TODO: add a seperate setting for wiimote buffer?
|
||||
while (m_wiimote_buffer[_number].Size() <= m_target_buffer_size * 200 / 120)
|
||||
{
|
||||
// add to buffer
|
||||
m_wiimote_buffer[_number].Push(nw);
|
||||
|
||||
SendWiimoteState(_number, nw);
|
||||
}
|
||||
DEBUG_LOG_FMT(NETPLAY, "Exiting WiimoteUpdate() with wiimote {}, state [{:02x}]", entry.wiimote,
|
||||
fmt::join(std::span(entry.state->data.data(), entry.state->length), ", "));
|
||||
}
|
||||
|
||||
} // unlock players
|
||||
|
||||
if (!WaitForWiimoteBuffer(_number))
|
||||
return false;
|
||||
|
||||
m_wiimote_buffer[_number].Pop(nw);
|
||||
|
||||
// If the reporting mode has changed, we just need to pop through the buffer,
|
||||
// until we reach a good input
|
||||
if (nw.report_id != reporting_mode)
|
||||
{
|
||||
u32 tries = 0;
|
||||
while (nw.report_id != reporting_mode)
|
||||
{
|
||||
if (!WaitForWiimoteBuffer(_number))
|
||||
return false;
|
||||
|
||||
m_wiimote_buffer[_number].Pop(nw);
|
||||
|
||||
++tries;
|
||||
if (tries > m_target_buffer_size * 200 / 120)
|
||||
break;
|
||||
}
|
||||
|
||||
// If it still mismatches, it surely desynced
|
||||
if (nw.report_id != reporting_mode)
|
||||
{
|
||||
PanicAlertFmtT("Netplay has desynced. There is no way to recover from this.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(nw.data.size() == size);
|
||||
std::copy(nw.data.begin(), nw.data.end(), data);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2171,6 +2142,28 @@ bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet)
|
|||
return data_added;
|
||||
}
|
||||
|
||||
bool NetPlayClient::AddLocalWiimoteToBuffer(const int local_wiimote,
|
||||
const WiimoteEmu::SerializedWiimoteState& state,
|
||||
sf::Packet& packet)
|
||||
{
|
||||
const int ingame_pad = LocalWiimoteToInGameWiimote(local_wiimote);
|
||||
bool data_added = false;
|
||||
|
||||
// adjust the buffer either up or down
|
||||
// inserting multiple padstates or dropping states
|
||||
while (m_wiimote_buffer[ingame_pad].Size() <= m_target_buffer_size)
|
||||
{
|
||||
// add to buffer
|
||||
m_wiimote_buffer[ingame_pad].Push(state);
|
||||
|
||||
// add to packet
|
||||
AddWiimoteStateToPacket(ingame_pad, state, packet);
|
||||
data_added = true;
|
||||
}
|
||||
|
||||
return data_added;
|
||||
}
|
||||
|
||||
void NetPlayClient::SendPadHostPoll(const PadIndex pad_num)
|
||||
{
|
||||
// Here we handle polling for the Host Input Authority and Golf modes. Pad data is "polled" from
|
||||
|
@ -2328,32 +2321,43 @@ bool NetPlayClient::IsFirstInGamePad(int ingame_pad) const
|
|||
[](auto mapping) { return mapping > 0; });
|
||||
}
|
||||
|
||||
int NetPlayClient::NumLocalPads() const
|
||||
static int CountLocalPads(const PadMappingArray& pad_map, const PlayerId& local_player_pid)
|
||||
{
|
||||
return static_cast<int>(std::count_if(m_pad_map.begin(), m_pad_map.end(), [this](auto mapping) {
|
||||
return mapping == m_local_player->pid;
|
||||
return static_cast<int>(
|
||||
std::count_if(pad_map.begin(), pad_map.end(), [&local_player_pid](const auto& mapping) {
|
||||
return mapping == local_player_pid;
|
||||
}));
|
||||
}
|
||||
|
||||
int NetPlayClient::InGamePadToLocalPad(int ingame_pad) const
|
||||
int NetPlayClient::NumLocalPads() const
|
||||
{
|
||||
return CountLocalPads(m_pad_map, m_local_player->pid);
|
||||
}
|
||||
|
||||
int NetPlayClient::NumLocalWiimotes() const
|
||||
{
|
||||
return CountLocalPads(m_wiimote_map, m_local_player->pid);
|
||||
}
|
||||
|
||||
static int InGameToLocal(int ingame_pad, const PadMappingArray& pad_map, PlayerId local_player_pid)
|
||||
{
|
||||
// not our pad
|
||||
if (m_pad_map[ingame_pad] != m_local_player->pid)
|
||||
if (pad_map[ingame_pad] != local_player_pid)
|
||||
return 4;
|
||||
|
||||
int local_pad = 0;
|
||||
int pad = 0;
|
||||
|
||||
for (; pad < ingame_pad; pad++)
|
||||
for (; pad < ingame_pad; ++pad)
|
||||
{
|
||||
if (m_pad_map[pad] == m_local_player->pid)
|
||||
if (pad_map[pad] == local_player_pid)
|
||||
local_pad++;
|
||||
}
|
||||
|
||||
return local_pad;
|
||||
}
|
||||
|
||||
int NetPlayClient::LocalPadToInGamePad(int local_pad) const
|
||||
static int LocalToInGame(int local_pad, const PadMappingArray& pad_map, PlayerId local_player_pid)
|
||||
{
|
||||
// Figure out which in-game pad maps to which local pad.
|
||||
// The logic we have here is that the local slots always
|
||||
|
@ -2362,7 +2366,7 @@ int NetPlayClient::LocalPadToInGamePad(int local_pad) const
|
|||
int ingame_pad = 0;
|
||||
for (; ingame_pad < 4; ingame_pad++)
|
||||
{
|
||||
if (m_pad_map[ingame_pad] == m_local_player->pid)
|
||||
if (pad_map[ingame_pad] == local_player_pid)
|
||||
local_pad_count++;
|
||||
|
||||
if (local_pad_count == local_pad)
|
||||
|
@ -2372,6 +2376,26 @@ int NetPlayClient::LocalPadToInGamePad(int local_pad) const
|
|||
return ingame_pad;
|
||||
}
|
||||
|
||||
int NetPlayClient::InGamePadToLocalPad(int ingame_pad) const
|
||||
{
|
||||
return InGameToLocal(ingame_pad, m_pad_map, m_local_player->pid);
|
||||
}
|
||||
|
||||
int NetPlayClient::LocalPadToInGamePad(int local_pad) const
|
||||
{
|
||||
return LocalToInGame(local_pad, m_pad_map, m_local_player->pid);
|
||||
}
|
||||
|
||||
int NetPlayClient::InGameWiimoteToLocalWiimote(int ingame_wiimote) const
|
||||
{
|
||||
return InGameToLocal(ingame_wiimote, m_wiimote_map, m_local_player->pid);
|
||||
}
|
||||
|
||||
int NetPlayClient::LocalWiimoteToInGameWiimote(int local_wiimote) const
|
||||
{
|
||||
return LocalToInGame(local_wiimote, m_wiimote_map, m_local_player->pid);
|
||||
}
|
||||
|
||||
bool NetPlayClient::PlayerHasControllerMapped(const PlayerId pid) const
|
||||
{
|
||||
const auto mapping_matches_player_id = [pid](const PlayerId& mapping) { return mapping == pid; };
|
||||
|
@ -2385,6 +2409,11 @@ bool NetPlayClient::IsLocalPlayer(const PlayerId pid) const
|
|||
return pid == m_local_player->pid;
|
||||
}
|
||||
|
||||
const PlayerId& NetPlayClient::GetLocalPlayerId() const
|
||||
{
|
||||
return m_local_player->pid;
|
||||
}
|
||||
|
||||
void NetPlayClient::SendGameStatus()
|
||||
{
|
||||
sf::Packet packet;
|
||||
|
@ -2580,23 +2609,6 @@ void SendPowerButtonEvent()
|
|||
netplay_client->SendPowerButtonEvent();
|
||||
}
|
||||
|
||||
void SetupWiimotes()
|
||||
{
|
||||
ASSERT(IsNetPlayRunning());
|
||||
const NetSettings& netplay_settings = netplay_client->GetNetSettings();
|
||||
const PadMappingArray& wiimote_map = netplay_client->GetWiimoteMapping();
|
||||
for (size_t i = 0; i < netplay_settings.wiimote_extension.size(); i++)
|
||||
{
|
||||
if (wiimote_map[i] > 0)
|
||||
{
|
||||
static_cast<ControllerEmu::Attachments*>(
|
||||
static_cast<WiimoteEmu::Wiimote*>(Wiimote::GetConfig()->GetController(int(i)))
|
||||
->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments))
|
||||
->SetSelectedAttachment(netplay_settings.wiimote_extension[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetGBASavePath(int pad_num)
|
||||
{
|
||||
std::lock_guard lk(crit_netplay_client);
|
||||
|
@ -2652,6 +2664,14 @@ PadDetails GetPadDetails(int pad_num)
|
|||
return res;
|
||||
}
|
||||
|
||||
int NumLocalWiimotes()
|
||||
{
|
||||
std::lock_guard lk(crit_netplay_client);
|
||||
if (netplay_client)
|
||||
return netplay_client->NumLocalWiimotes();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NetPlay_Enable(NetPlayClient* const np)
|
||||
{
|
||||
std::lock_guard lk(crit_netplay_client);
|
||||
|
@ -2679,37 +2699,51 @@ bool SerialInterface::CSIDevice_GCController::NetPlay_GetInput(int pad_num, GCPa
|
|||
return false;
|
||||
}
|
||||
|
||||
bool WiimoteEmu::Wiimote::NetPlay_GetWiimoteData(int wiimote, u8* data, u8 size, u8 reporting_mode)
|
||||
bool NetPlay::NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries)
|
||||
{
|
||||
std::lock_guard lk(NetPlay::crit_netplay_client);
|
||||
std::lock_guard lk(crit_netplay_client);
|
||||
|
||||
if (NetPlay::netplay_client)
|
||||
return NetPlay::netplay_client->WiimoteUpdate(wiimote, data, size, reporting_mode);
|
||||
if (netplay_client)
|
||||
return netplay_client->WiimoteUpdate(entries);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sync the info whether a button was pressed or not. Used for the reconnect on button press feature
|
||||
bool Wiimote::NetPlay_GetButtonPress(int wiimote, bool pressed)
|
||||
unsigned int NetPlay::NetPlay_GetLocalWiimoteForSlot(unsigned int slot)
|
||||
{
|
||||
std::lock_guard lk(NetPlay::crit_netplay_client);
|
||||
if (slot >= std::tuple_size_v<PadMappingArray>)
|
||||
return slot;
|
||||
|
||||
// Use the reporting mode 0 for the button pressed event, the real ones start at RT_REPORT_CORE
|
||||
static const u8 BUTTON_PRESS_REPORTING_MODE = 0;
|
||||
std::lock_guard lk(crit_netplay_client);
|
||||
|
||||
if (NetPlay::netplay_client)
|
||||
if (!netplay_client)
|
||||
return slot;
|
||||
|
||||
const auto& mapping = netplay_client->GetWiimoteMapping();
|
||||
const auto& local_player_id = netplay_client->GetLocalPlayerId();
|
||||
|
||||
std::array<unsigned int, std::tuple_size_v<std::decay_t<decltype(mapping)>>> slot_map;
|
||||
size_t player_count = 0;
|
||||
for (size_t i = 0; i < mapping.size(); ++i)
|
||||
{
|
||||
std::array<u8, 1> data = {u8(pressed)};
|
||||
if (NetPlay::netplay_client->WiimoteUpdate(wiimote, data.data(), data.size(),
|
||||
BUTTON_PRESS_REPORTING_MODE))
|
||||
if (mapping[i] == local_player_id)
|
||||
{
|
||||
return data[0];
|
||||
slot_map[i] = static_cast<unsigned int>(player_count);
|
||||
++player_count;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < mapping.size(); ++i)
|
||||
{
|
||||
if (mapping[i] != local_player_id)
|
||||
{
|
||||
slot_map[i] = static_cast<unsigned int>(player_count);
|
||||
++player_count;
|
||||
}
|
||||
PanicAlertFmtT("Netplay has desynced in NetPlay_GetButtonPress()");
|
||||
return false;
|
||||
}
|
||||
|
||||
return pressed;
|
||||
INFO_LOG_FMT(NETPLAY, "Wiimote slot map: [{}]", fmt::join(slot_map, ", "));
|
||||
|
||||
return slot_map[slot];
|
||||
}
|
||||
|
||||
// called from ---CPU--- thread
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
@ -35,6 +36,11 @@ namespace UICommon
|
|||
class GameFile;
|
||||
}
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
struct SerializedWiimoteState;
|
||||
}
|
||||
|
||||
namespace NetPlay
|
||||
{
|
||||
class NetPlayUI
|
||||
|
@ -129,7 +135,12 @@ public:
|
|||
std::string GetCurrentGolfer();
|
||||
|
||||
// Send and receive pads values
|
||||
bool WiimoteUpdate(int _number, u8* data, std::size_t size, u8 reporting_mode);
|
||||
struct WiimoteDataBatchEntry
|
||||
{
|
||||
int wiimote;
|
||||
WiimoteEmu::SerializedWiimoteState* state;
|
||||
};
|
||||
bool WiimoteUpdate(const std::span<WiimoteDataBatchEntry>& entries);
|
||||
bool GetNetPads(int pad_nb, bool from_vi, GCPadStatus* pad_status);
|
||||
|
||||
u64 GetInitialRTCValue() const;
|
||||
|
@ -140,13 +151,17 @@ public:
|
|||
|
||||
bool IsFirstInGamePad(int ingame_pad) const;
|
||||
int NumLocalPads() const;
|
||||
int NumLocalWiimotes() const;
|
||||
|
||||
int InGamePadToLocalPad(int ingame_pad) const;
|
||||
int LocalPadToInGamePad(int localPad) const;
|
||||
int LocalPadToInGamePad(int local_pad) const;
|
||||
int InGameWiimoteToLocalWiimote(int ingame_wiimote) const;
|
||||
int LocalWiimoteToInGameWiimote(int local_wiimote) const;
|
||||
|
||||
bool PlayerHasControllerMapped(PlayerId pid) const;
|
||||
bool LocalPlayerHasControllerMapped() const;
|
||||
bool IsLocalPlayer(PlayerId pid) const;
|
||||
const PlayerId& GetLocalPlayerId() const;
|
||||
|
||||
static void SendTimeBase();
|
||||
bool DoAllPlayersHaveGame();
|
||||
|
@ -182,7 +197,7 @@ protected:
|
|||
Common::SPSCQueue<AsyncQueueEntry, false> m_async_queue;
|
||||
|
||||
std::array<Common::SPSCQueue<GCPadStatus>, 4> m_pad_buffer;
|
||||
std::array<Common::SPSCQueue<WiimoteInput>, 4> m_wiimote_buffer;
|
||||
std::array<Common::SPSCQueue<WiimoteEmu::SerializedWiimoteState>, 4> m_wiimote_buffer;
|
||||
|
||||
std::array<GCPadStatus, 4> m_last_pad_status{};
|
||||
std::array<bool, 4> m_first_pad_status_received{};
|
||||
|
@ -242,9 +257,13 @@ private:
|
|||
bool PollLocalPad(int local_pad, sf::Packet& packet);
|
||||
void SendPadHostPoll(PadIndex pad_num);
|
||||
|
||||
bool AddLocalWiimoteToBuffer(int local_wiimote, const WiimoteEmu::SerializedWiimoteState& state,
|
||||
sf::Packet& packet);
|
||||
|
||||
void UpdateDevices();
|
||||
void AddPadStateToPacket(int in_game_pad, const GCPadStatus& np, sf::Packet& packet);
|
||||
void SendWiimoteState(int in_game_pad, const WiimoteInput& nw);
|
||||
void AddWiimoteStateToPacket(int in_game_pad, const WiimoteEmu::SerializedWiimoteState& np,
|
||||
sf::Packet& packet);
|
||||
void Send(const sf::Packet& packet, u8 channel_id = DEFAULT_CHANNEL);
|
||||
void Disconnect();
|
||||
bool Connect();
|
||||
|
@ -253,8 +272,6 @@ private:
|
|||
void DisplayPlayersPing();
|
||||
u32 GetPlayersMaxPing() const;
|
||||
|
||||
bool WaitForWiimoteBuffer(int _number);
|
||||
|
||||
void OnData(sf::Packet& packet);
|
||||
void OnPlayerJoin(sf::Packet& packet);
|
||||
void OnPlayerLeave(sf::Packet& packet);
|
||||
|
@ -335,4 +352,6 @@ private:
|
|||
|
||||
void NetPlay_Enable(NetPlayClient* const np);
|
||||
void NetPlay_Disable();
|
||||
bool NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries);
|
||||
unsigned int NetPlay_GetLocalWiimoteForSlot(unsigned int slot);
|
||||
} // namespace NetPlay
|
||||
|
|
|
@ -101,7 +101,6 @@ struct NetSettings
|
|||
bool strict_settings_sync = false;
|
||||
bool sync_codes = false;
|
||||
std::string save_data_region;
|
||||
std::array<int, 4> wiimote_extension{};
|
||||
bool golf_mode = false;
|
||||
bool use_fma = false;
|
||||
bool hide_remote_gbas = false;
|
||||
|
@ -228,11 +227,6 @@ enum : u8
|
|||
CHANNEL_COUNT
|
||||
};
|
||||
|
||||
struct WiimoteInput
|
||||
{
|
||||
u8 report_id = 0;
|
||||
std::vector<u8> data;
|
||||
};
|
||||
using PlayerId = u8;
|
||||
using FrameNum = u32;
|
||||
using PadIndex = s8;
|
||||
|
@ -260,7 +254,7 @@ std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map,
|
|||
bool IsNetPlayRunning();
|
||||
void SetSIPollBatching(bool state);
|
||||
void SendPowerButtonEvent();
|
||||
void SetupWiimotes();
|
||||
std::string GetGBASavePath(int pad_num);
|
||||
PadDetails GetPadDetails(int pad_num);
|
||||
int NumLocalWiimotes();
|
||||
} // namespace NetPlay
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include "Core/HW/Sram.h"
|
||||
#include "Core/HW/WiiSave.h"
|
||||
#include "Core/HW/WiiSaveStructs.h"
|
||||
#include "Core/HW/WiimoteEmu/DesiredWiimoteState.h"
|
||||
#include "Core/HW/WiimoteEmu/WiimoteEmu.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/IOS/ES/ES.h"
|
||||
|
@ -827,12 +828,13 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
|||
if (player.current_game != m_current_game)
|
||||
break;
|
||||
|
||||
sf::Packet spac;
|
||||
spac << MessageID::WiimoteData;
|
||||
|
||||
while (!packet.endOfPacket())
|
||||
{
|
||||
PadIndex map;
|
||||
u8 size;
|
||||
packet >> map >> size;
|
||||
std::vector<u8> data(size);
|
||||
for (u8& byte : data)
|
||||
packet >> byte;
|
||||
packet >> map;
|
||||
|
||||
// If the data is not from the correct player,
|
||||
// then disconnect them.
|
||||
|
@ -841,13 +843,18 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
|||
return 1;
|
||||
}
|
||||
|
||||
// relay to clients
|
||||
sf::Packet spac;
|
||||
spac << MessageID::WiimoteData;
|
||||
WiimoteEmu::SerializedWiimoteState pad;
|
||||
packet >> pad.length;
|
||||
if (pad.length > pad.data.size())
|
||||
return 1;
|
||||
for (size_t i = 0; i < pad.length; ++i)
|
||||
packet >> pad.data[i];
|
||||
|
||||
spac << map;
|
||||
spac << size;
|
||||
for (const u8& byte : data)
|
||||
spac << byte;
|
||||
spac << pad.length;
|
||||
for (size_t i = 0; i < pad.length; ++i)
|
||||
spac << pad.data[i];
|
||||
}
|
||||
|
||||
SendToClients(spac, player.pid);
|
||||
}
|
||||
|
@ -1518,16 +1525,6 @@ bool NetPlayServer::StartGame()
|
|||
spac << region;
|
||||
spac << m_settings.sync_codes;
|
||||
|
||||
for (size_t i = 0; i < m_settings.wiimote_extension.size(); i++)
|
||||
{
|
||||
const int extension =
|
||||
static_cast<ControllerEmu::Attachments*>(
|
||||
static_cast<WiimoteEmu::Wiimote*>(Wiimote::GetConfig()->GetController(int(i)))
|
||||
->GetWiimoteGroup(WiimoteEmu::WiimoteGroup::Attachments))
|
||||
->GetSelectedAttachment();
|
||||
spac << extension;
|
||||
}
|
||||
|
||||
spac << m_settings.golf_mode;
|
||||
spac << m_settings.use_fma;
|
||||
spac << m_settings.hide_remote_gbas;
|
||||
|
|
|
@ -315,8 +315,10 @@
|
|||
<ClInclude Include="Core\HW\WiimoteCommon\WiimoteReport.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\Camera.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\Dynamics.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\DesiredWiimoteState.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\Encryption.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\Extension\Classic.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\Extension\DesiredExtensionState.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\Extension\DrawsomeTablet.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\Extension\Drums.h" />
|
||||
<ClInclude Include="Core\HW\WiimoteEmu\Extension\Extension.h" />
|
||||
|
@ -931,6 +933,7 @@
|
|||
<ClCompile Include="Core\HW\Wiimote.cpp" />
|
||||
<ClCompile Include="Core\HW\WiimoteCommon\DataReport.cpp" />
|
||||
<ClCompile Include="Core\HW\WiimoteEmu\Camera.cpp" />
|
||||
<ClCompile Include="Core\HW\WiimoteEmu\DesiredWiimoteState.cpp" />
|
||||
<ClCompile Include="Core\HW\WiimoteEmu\Dynamics.cpp" />
|
||||
<ClCompile Include="Core\HW\WiimoteEmu\EmuSubroutines.cpp" />
|
||||
<ClCompile Include="Core\HW\WiimoteEmu\Encryption.cpp" />
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "Core/Core.h"
|
||||
#include "Core/HW/SI/SI.h"
|
||||
#include "Core/HW/SI/SI_Device.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h"
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
|
@ -60,11 +61,13 @@ static SerialInterface::SIDevices FromGCMenuIndex(const int menudevice)
|
|||
GamecubeControllersWidget::GamecubeControllersWidget(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
CreateLayout();
|
||||
LoadSettings();
|
||||
ConnectWidgets();
|
||||
|
||||
connect(&Settings::Instance(), &Settings::ConfigChanged, this,
|
||||
&GamecubeControllersWidget::LoadSettings);
|
||||
[this] { LoadSettings(Core::GetState()); });
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
|
||||
[this](Core::State state) { LoadSettings(state); });
|
||||
LoadSettings(Core::GetState());
|
||||
}
|
||||
|
||||
void GamecubeControllersWidget::CreateLayout()
|
||||
|
@ -160,8 +163,9 @@ void GamecubeControllersWidget::OnGCPadConfigure(size_t index)
|
|||
window->show();
|
||||
}
|
||||
|
||||
void GamecubeControllersWidget::LoadSettings()
|
||||
void GamecubeControllersWidget::LoadSettings(Core::State state)
|
||||
{
|
||||
const bool running = state != Core::State::Uninitialized;
|
||||
for (size_t i = 0; i < m_gc_groups.size(); i++)
|
||||
{
|
||||
const SerialInterface::SIDevices si_device =
|
||||
|
@ -170,6 +174,7 @@ void GamecubeControllersWidget::LoadSettings()
|
|||
if (gc_index)
|
||||
{
|
||||
SignalBlocking(m_gc_controller_boxes[i])->setCurrentIndex(*gc_index);
|
||||
m_gc_controller_boxes[i]->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true);
|
||||
OnGCTypeChanged(i);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,11 @@ class QGridLayout;
|
|||
class QGroupBox;
|
||||
class QPushButton;
|
||||
|
||||
namespace Core
|
||||
{
|
||||
enum class State;
|
||||
}
|
||||
|
||||
class GamecubeControllersWidget final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -20,7 +25,7 @@ public:
|
|||
explicit GamecubeControllersWidget(QWidget* parent);
|
||||
|
||||
private:
|
||||
void LoadSettings();
|
||||
void LoadSettings(Core::State state);
|
||||
void SaveSettings();
|
||||
|
||||
void OnGCTypeChanged(size_t index);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTReal.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
|
||||
#include "DolphinQt/Config/Mapping/MappingWindow.h"
|
||||
|
@ -286,6 +287,8 @@ void WiimoteControllersWidget::LoadSettings(Core::State state)
|
|||
const bool running_gc = running && !SConfig::GetInstance().bWii;
|
||||
const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc;
|
||||
const bool enable_emu_bt = !m_wiimote_passthrough->isChecked() && !running_gc;
|
||||
const bool is_netplay = NetPlay::IsNetPlayRunning();
|
||||
const bool running_netplay = running && is_netplay;
|
||||
|
||||
m_wiimote_sync->setEnabled(enable_passthrough);
|
||||
m_wiimote_reset->setEnabled(enable_passthrough);
|
||||
|
@ -293,17 +296,19 @@ 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;
|
||||
for (size_t i = 0; i < m_wiimote_groups.size(); i++)
|
||||
{
|
||||
m_wiimote_labels[i]->setEnabled(enable_emu_bt);
|
||||
m_wiimote_boxes[i]->setEnabled(enable_emu_bt);
|
||||
m_wiimote_boxes[i]->setEnabled(enable_emu_bt && !running_netplay);
|
||||
|
||||
const bool is_emu_wiimote = m_wiimote_boxes[i]->currentIndex() == 1;
|
||||
m_wiimote_buttons[i]->setEnabled(enable_emu_bt && is_emu_wiimote);
|
||||
m_wiimote_buttons[i]->setEnabled(enable_emu_bt && is_emu_wiimote &&
|
||||
static_cast<int>(i) < num_local_wiimotes);
|
||||
}
|
||||
|
||||
m_wiimote_real_balance_board->setEnabled(enable_emu_bt);
|
||||
m_wiimote_speaker_data->setEnabled(enable_emu_bt);
|
||||
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();
|
||||
|
||||
|
|
|
@ -360,8 +360,6 @@ void MainWindow::InitCoreCallbacks()
|
|||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
|
||||
if (state == Core::State::Uninitialized)
|
||||
OnStopComplete();
|
||||
if (state != Core::State::Uninitialized && NetPlay::IsNetPlayRunning() && m_controllers_window)
|
||||
m_controllers_window->reject();
|
||||
|
||||
if (state == Core::State::Running && m_fullscreen_requested)
|
||||
{
|
||||
|
|
|
@ -131,9 +131,6 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
|
|||
m_recording_play->setEnabled(m_game_selected && !running);
|
||||
m_recording_start->setEnabled((m_game_selected || running) && !Movie::IsPlayingInput());
|
||||
|
||||
// Options
|
||||
m_controllers_action->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true);
|
||||
|
||||
// JIT
|
||||
m_jit_interpreter_core->setEnabled(running);
|
||||
m_jit_block_linking->setEnabled(!running);
|
||||
|
|
|
@ -61,7 +61,6 @@ void ToolBar::OnEmulationStateChanged(Core::State state)
|
|||
m_stop_action->setEnabled(running);
|
||||
m_fullscreen_action->setEnabled(running);
|
||||
m_screenshot_action->setEnabled(running);
|
||||
m_controllers_action->setEnabled(NetPlay::IsNetPlayRunning() ? !running : true);
|
||||
|
||||
bool playing = running && state != Core::State::Paused;
|
||||
UpdatePausePlayButtonState(playing);
|
||||
|
@ -130,7 +129,6 @@ void ToolBar::MakeActions()
|
|||
m_config_action = addAction(tr("Config"), this, &ToolBar::SettingsPressed);
|
||||
m_graphics_action = addAction(tr("Graphics"), this, &ToolBar::GraphicsPressed);
|
||||
m_controllers_action = addAction(tr("Controllers"), this, &ToolBar::ControllersPressed);
|
||||
m_controllers_action->setEnabled(true);
|
||||
|
||||
// Ensure every button has about the same width
|
||||
std::vector<QWidget*> items;
|
||||
|
|
|
@ -1256,7 +1256,7 @@ void Device::IRState::ProcessData(const std::array<WiimoteEmu::IRBasic, 2>& data
|
|||
// A better implementation might extrapolate points when they fall out of camera view.
|
||||
// But just averaging visible points actually seems to work very well.
|
||||
|
||||
using IRObject = WiimoteEmu::IRBasic::IRObject;
|
||||
using IRObject = WiimoteEmu::IRObject;
|
||||
|
||||
MathUtil::RunningVariance<Common::Vec2> points;
|
||||
|
||||
|
|
Loading…
Reference in New Issue