mirror of
https://github.com/stenzek/duckstation.git
synced 2025-02-19 22:22:15 +00:00
DDGoController: Rewrite to support axis input
Some checks failed
GameDB Lint / gamedb-lint (push) Has been cancelled
Automated Builds / 💻 Windows (push) Has been cancelled
Automated Builds / 🐧 Linux AppImage (push) Has been cancelled
Automated Builds / 📦 Linux Flatpak (push) Has been cancelled
Automated Builds / 🍎 MacOS (push) Has been cancelled
Automated Builds / 📤 Create Release (push) Has been cancelled
Some checks failed
GameDB Lint / gamedb-lint (push) Has been cancelled
Automated Builds / 💻 Windows (push) Has been cancelled
Automated Builds / 🐧 Linux AppImage (push) Has been cancelled
Automated Builds / 📦 Linux Flatpak (push) Has been cancelled
Automated Builds / 🍎 MacOS (push) Has been cancelled
Automated Builds / 📤 Create Release (push) Has been cancelled
And add virtual buttons for each of the possible states.
This commit is contained in:
parent
a25d5dcd2f
commit
7bc18c8538
@ -36447,6 +36447,7 @@ SCPS-45166:
|
||||
name: "Densha De Go! (aka Densha De Go! / Densya De Go! - Let's Go by Train!)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
publisher: "Taito"
|
||||
developer: "Taito"
|
||||
@ -36465,6 +36466,7 @@ SCPS-45167:
|
||||
name: "Densha De Go! (aka Densha De Go! / Densya De Go! - Let's Go by Train!)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
publisher: "Taito"
|
||||
developer: "Taito"
|
||||
@ -36483,6 +36485,7 @@ SLPS-01150:
|
||||
name: "Densha de Go! (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
publisher: "Taito"
|
||||
developer: "Taito"
|
||||
@ -36499,12 +36502,16 @@ SLPS-01150:
|
||||
linkCable: false
|
||||
SLPM-80166:
|
||||
name: "Densha de Go! (Japan) (Demo)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
genre: "** DEMO **"
|
||||
SLPM-86263:
|
||||
name: "Densha de Go! (Japan) (PlayStation the Best)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
publisher: "Taito"
|
||||
developer: "Taito"
|
||||
@ -36524,6 +36531,7 @@ SLPM-86142:
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
publisher: "Taito"
|
||||
developer: "Taito"
|
||||
@ -36540,10 +36548,18 @@ SLPM-86142:
|
||||
linkCable: false
|
||||
SLPM-80346:
|
||||
name: "Densha de Go! 2 (Japan) (Demo 1)"
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
genre: "** DEMO **"
|
||||
SLPM-80406:
|
||||
name: "Densha de Go! 2 (Japan) (Demo 2)"
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
genre: "** DEMO **"
|
||||
SLPM-86615:
|
||||
@ -36551,6 +36567,7 @@ SLPM-86615:
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
publisher: "Taito"
|
||||
developer: "Taito"
|
||||
@ -36570,6 +36587,7 @@ SLPM-86141:
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
publisher: "Taito"
|
||||
developer: "Taito"
|
||||
@ -36589,6 +36607,7 @@ SLPM-86424:
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
publisher: "Taito"
|
||||
developer: "Taito"
|
||||
@ -36605,6 +36624,10 @@ SLPM-86424:
|
||||
linkCable: false
|
||||
SLPM-80521:
|
||||
name: "Densha de Go! Nagoya Tetsudou-hen (Japan) (Demo)"
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
metadata:
|
||||
genre: "** DEMO **"
|
||||
SLPM-86378:
|
||||
@ -36612,6 +36635,7 @@ SLPM-86378:
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
codes:
|
||||
- SLPM-86378
|
||||
- SLPM-86705
|
||||
@ -36631,6 +36655,10 @@ SLPM-86378:
|
||||
linkCable: false
|
||||
SLPM-80511:
|
||||
name: "Densha de Go! Professional Shiyou (Japan) (Taikenban)"
|
||||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
- DDGoController
|
||||
SCPS-18003:
|
||||
name: "Depth (Japan)"
|
||||
controllers:
|
||||
|
@ -109,6 +109,10 @@ ALWAYS_INLINE static constexpr u64 BoolToUInt64(bool value)
|
||||
{
|
||||
return static_cast<u64>(value);
|
||||
}
|
||||
ALWAYS_INLINE static constexpr float BoolToFloat(bool value)
|
||||
{
|
||||
return static_cast<float>(value);
|
||||
}
|
||||
|
||||
// Integer to boolean
|
||||
template<typename TValue>
|
||||
|
@ -32,6 +32,8 @@ add_library(core
|
||||
cpu_pgxp.h
|
||||
cpu_types.cpp
|
||||
cpu_types.h
|
||||
ddgo_controller.cpp
|
||||
ddgo_controller.h
|
||||
digital_controller.cpp
|
||||
digital_controller.h
|
||||
dma.cpp
|
||||
|
@ -1,9 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "controller.h"
|
||||
#include "analog_controller.h"
|
||||
#include "analog_joystick.h"
|
||||
#include "ddgo_controller.h"
|
||||
#include "digital_controller.h"
|
||||
#include "game_database.h"
|
||||
#include "guncon.h"
|
||||
@ -35,7 +36,7 @@ static constexpr std::array<const Controller::ControllerInfo*, static_cast<size_
|
||||
&NeGconRumble::INFO,
|
||||
&Justifier::INFO,
|
||||
&DigitalController::INFO_POPN,
|
||||
&DigitalController::INFO_DDGO,
|
||||
&DDGoController::INFO,
|
||||
&JogCon::INFO,
|
||||
}};
|
||||
|
||||
@ -120,7 +121,6 @@ std::unique_ptr<Controller> Controller::Create(ControllerType type, u32 index)
|
||||
{
|
||||
case ControllerType::DigitalController:
|
||||
case ControllerType::PopnController:
|
||||
case ControllerType::DDGoController:
|
||||
return DigitalController::Create(index, type);
|
||||
|
||||
case ControllerType::AnalogController:
|
||||
@ -144,6 +144,9 @@ std::unique_ptr<Controller> Controller::Create(ControllerType type, u32 index)
|
||||
case ControllerType::NeGconRumble:
|
||||
return NeGconRumble::Create(index);
|
||||
|
||||
case ControllerType::DDGoController:
|
||||
return DDGoController::Create(index);
|
||||
|
||||
case ControllerType::JogCon:
|
||||
return JogCon::Create(index);
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="cpu_types.cpp" />
|
||||
<ClCompile Include="ddgo_controller.cpp" />
|
||||
<ClCompile Include="digital_controller.cpp" />
|
||||
<ClCompile Include="fullscreen_ui.cpp" />
|
||||
<ClCompile Include="game_database.cpp" />
|
||||
@ -108,6 +109,7 @@
|
||||
<ClInclude Include="cpu_recompiler_x64.h">
|
||||
<ExcludedFromBuild Condition="'$(Platform)'!='x64'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ddgo_controller.h" />
|
||||
<ClInclude Include="digital_controller.h" />
|
||||
<ClInclude Include="fullscreen_ui.h" />
|
||||
<ClInclude Include="game_database.h" />
|
||||
|
@ -67,6 +67,7 @@
|
||||
<ClCompile Include="pio.cpp" />
|
||||
<ClCompile Include="gpu_thread.cpp" />
|
||||
<ClCompile Include="gpu_presenter.cpp" />
|
||||
<ClCompile Include="ddgo_controller.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
@ -144,6 +145,7 @@
|
||||
<ClInclude Include="gpu_thread.h" />
|
||||
<ClInclude Include="gpu_thread_commands.h" />
|
||||
<ClInclude Include="gpu_presenter.h" />
|
||||
<ClInclude Include="ddgo_controller.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="gpu_sw_rasterizer.inl" />
|
||||
|
363
src/core/ddgo_controller.cpp
Normal file
363
src/core/ddgo_controller.cpp
Normal file
@ -0,0 +1,363 @@
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#include "ddgo_controller.h"
|
||||
#include "host.h"
|
||||
#include "system.h"
|
||||
|
||||
#include "util/state_wrapper.h"
|
||||
|
||||
#include "IconsPromptFont.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "common/settings_interface.h"
|
||||
|
||||
DDGoController::DDGoController(u32 index) : Controller(index)
|
||||
{
|
||||
}
|
||||
|
||||
DDGoController::~DDGoController() = default;
|
||||
|
||||
ControllerType DDGoController::GetType() const
|
||||
{
|
||||
return ControllerType::DDGoController;
|
||||
}
|
||||
|
||||
void DDGoController::Reset()
|
||||
{
|
||||
m_transfer_state = TransferState::Idle;
|
||||
m_power_transition_frames_remaining = 0;
|
||||
UpdatePowerBits();
|
||||
m_brake_transition_frames_remaining = 0;
|
||||
UpdateBrakeBits();
|
||||
}
|
||||
|
||||
bool DDGoController::DoState(StateWrapper& sw, bool apply_input_state)
|
||||
{
|
||||
if (!Controller::DoState(sw, apply_input_state))
|
||||
return false;
|
||||
|
||||
u16 button_state = m_button_state;
|
||||
u8 power_level = m_power_level;
|
||||
u8 power_transition_frames_remaining = m_power_transition_frames_remaining;
|
||||
u8 brake_level = m_brake_level;
|
||||
u8 brake_transition_frames_remaining = m_brake_transition_frames_remaining;
|
||||
sw.Do(&button_state);
|
||||
sw.Do(&power_level);
|
||||
sw.Do(&power_transition_frames_remaining);
|
||||
sw.Do(&brake_level);
|
||||
sw.Do(&brake_transition_frames_remaining);
|
||||
|
||||
if (apply_input_state)
|
||||
{
|
||||
m_button_state = button_state;
|
||||
m_power_level = power_level;
|
||||
m_power_transition_frames_remaining = power_transition_frames_remaining;
|
||||
m_brake_level = brake_level;
|
||||
m_brake_transition_frames_remaining = brake_transition_frames_remaining;
|
||||
UpdatePowerBits();
|
||||
UpdateBrakeBits();
|
||||
}
|
||||
|
||||
sw.Do(&m_transfer_state);
|
||||
return true;
|
||||
}
|
||||
|
||||
float DDGoController::GetBindState(u32 index) const
|
||||
{
|
||||
if (index >= static_cast<u32>(Bind::VirtualButtonStart))
|
||||
{
|
||||
if (index < static_cast<u32>(Bind::VirtualBrakeReleased))
|
||||
return BoolToFloat(m_power_level == (index - static_cast<u32>(Bind::VirtualPowerOff)));
|
||||
else
|
||||
return BoolToFloat(m_brake_level == (index - static_cast<u32>(Bind::VirtualBrakeReleased)));
|
||||
}
|
||||
|
||||
// don't show the buttons set by the level
|
||||
static constexpr u16 REPORT_MASK = (1u << static_cast<u16>(Bind::Start)) | (1u << static_cast<u16>(Bind::Select)) |
|
||||
(1u << static_cast<u16>(Bind::A)) | (1u << static_cast<u16>(Bind::B)) |
|
||||
(1u << static_cast<u16>(Bind::C));
|
||||
return static_cast<float>((((m_button_state ^ 0xFFFFu) & REPORT_MASK) >> index) & 1u);
|
||||
}
|
||||
|
||||
void DDGoController::SetBindState(u32 index, float value)
|
||||
{
|
||||
if (index == static_cast<u32>(Bind::Power))
|
||||
{
|
||||
value = (value < m_analog_deadzone) ? 0.0f : (value * m_analog_sensitivity);
|
||||
SetPowerLevel(std::min(static_cast<u32>(value * MAX_POWER_LEVEL), MAX_POWER_LEVEL));
|
||||
return;
|
||||
}
|
||||
else if (index == static_cast<u32>(Bind::Brake))
|
||||
{
|
||||
value = (value < m_analog_deadzone) ? 0.0f : (value * m_analog_sensitivity);
|
||||
SetBrakeLevel(std::min(static_cast<u32>(value * MAX_BRAKE_LEVEL), MAX_BRAKE_LEVEL));
|
||||
return;
|
||||
}
|
||||
else if (index >= static_cast<u32>(Bind::VirtualButtonStart))
|
||||
{
|
||||
// vbutton, only handle press
|
||||
if (value < 0.5f)
|
||||
return;
|
||||
|
||||
if (index < static_cast<u32>(Bind::VirtualBrakeReleased))
|
||||
SetPowerLevel(index - static_cast<u32>(Bind::VirtualPowerOff));
|
||||
else
|
||||
SetBrakeLevel(index - static_cast<u32>(Bind::VirtualBrakeReleased));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const bool pressed = (value >= 0.5f);
|
||||
const u16 bit = u16(1) << static_cast<u8>(index);
|
||||
if (pressed)
|
||||
{
|
||||
if (m_button_state & bit)
|
||||
System::SetRunaheadReplayFlag();
|
||||
|
||||
m_button_state &= ~bit;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(m_button_state & bit))
|
||||
System::SetRunaheadReplayFlag();
|
||||
|
||||
m_button_state |= bit;
|
||||
}
|
||||
}
|
||||
|
||||
u32 DDGoController::GetButtonStateBits() const
|
||||
{
|
||||
return m_button_state ^ 0xFFFF;
|
||||
}
|
||||
|
||||
void DDGoController::ResetTransferState()
|
||||
{
|
||||
m_transfer_state = TransferState::Idle;
|
||||
}
|
||||
|
||||
bool DDGoController::Transfer(const u8 data_in, u8* data_out)
|
||||
{
|
||||
static constexpr u16 ID = 0x5A41;
|
||||
|
||||
switch (m_transfer_state)
|
||||
{
|
||||
case TransferState::Idle:
|
||||
{
|
||||
*data_out = 0xFF;
|
||||
|
||||
if (data_in == 0x01)
|
||||
{
|
||||
m_transfer_state = TransferState::Ready;
|
||||
|
||||
// handle transition time
|
||||
if (m_power_transition_frames_remaining > 0)
|
||||
{
|
||||
m_power_transition_frames_remaining--;
|
||||
UpdatePowerBits();
|
||||
}
|
||||
|
||||
if (m_brake_transition_frames_remaining > 0)
|
||||
{
|
||||
m_brake_transition_frames_remaining--;
|
||||
UpdateBrakeBits();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
case TransferState::Ready:
|
||||
{
|
||||
if (data_in == 0x42)
|
||||
{
|
||||
*data_out = Truncate8(ID);
|
||||
m_transfer_state = TransferState::IDMSB;
|
||||
return true;
|
||||
}
|
||||
|
||||
*data_out = 0xFF;
|
||||
return false;
|
||||
}
|
||||
|
||||
case TransferState::IDMSB:
|
||||
{
|
||||
*data_out = Truncate8(ID >> 8);
|
||||
m_transfer_state = TransferState::ButtonsLSB;
|
||||
return true;
|
||||
}
|
||||
|
||||
case TransferState::ButtonsLSB:
|
||||
{
|
||||
*data_out = Truncate8(m_button_state & BUTTON_MASK);
|
||||
m_transfer_state = TransferState::ButtonsMSB;
|
||||
return true;
|
||||
}
|
||||
|
||||
case TransferState::ButtonsMSB:
|
||||
*data_out = Truncate8((m_button_state & BUTTON_MASK) >> 8);
|
||||
m_transfer_state = TransferState::Idle;
|
||||
return false;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
}
|
||||
}
|
||||
|
||||
void DDGoController::SetPowerLevel(u32 level)
|
||||
{
|
||||
DebugAssert(level <= MAX_POWER_LEVEL);
|
||||
if (m_power_level == level)
|
||||
return;
|
||||
|
||||
m_power_level = Truncate8(level);
|
||||
m_power_transition_frames_remaining = m_power_transition_frames;
|
||||
UpdatePowerBits();
|
||||
System::SetRunaheadReplayFlag();
|
||||
}
|
||||
|
||||
void DDGoController::UpdatePowerBits()
|
||||
{
|
||||
#define POWER_BITS(b0, b1, b2) \
|
||||
static_cast<u16>(~(((b0) ? (1u << static_cast<u32>(Bind::PowerBit0)) : 0u) | \
|
||||
((b1) ? (1u << static_cast<u32>(Bind::PowerBit1)) : 0u) | \
|
||||
((b2) ? (1u << static_cast<u32>(Bind::PowerBit2)) : 0u)) & \
|
||||
POWER_MASK)
|
||||
|
||||
static constexpr std::array<u16, MAX_POWER_LEVEL + 2> POWER_TABLE = {{
|
||||
POWER_BITS(0, 1, 1), // N
|
||||
POWER_BITS(1, 0, 1), // P1
|
||||
POWER_BITS(0, 0, 1), // P2
|
||||
POWER_BITS(1, 1, 0), // P3
|
||||
POWER_BITS(0, 1, 0), // P4
|
||||
POWER_BITS(1, 0, 0), // P5
|
||||
POWER_BITS(0, 0, 0), // Transition
|
||||
}};
|
||||
|
||||
#undef POWER_BITS
|
||||
|
||||
const u32 idx = (m_power_transition_frames_remaining > 0) ? (MAX_POWER_LEVEL + 1) : m_power_level;
|
||||
m_button_state = (m_button_state & ~POWER_MASK) | POWER_TABLE[idx];
|
||||
}
|
||||
|
||||
void DDGoController::SetBrakeLevel(u32 level)
|
||||
{
|
||||
DebugAssert(level <= MAX_BRAKE_LEVEL);
|
||||
if (m_brake_level == level)
|
||||
return;
|
||||
|
||||
m_brake_level = Truncate8(level);
|
||||
m_brake_transition_frames_remaining = m_brake_transition_frames;
|
||||
UpdateBrakeBits();
|
||||
System::SetRunaheadReplayFlag();
|
||||
}
|
||||
|
||||
void DDGoController::UpdateBrakeBits()
|
||||
{
|
||||
#define BRAKE_BITS(b0, b1, b2, b3) \
|
||||
static_cast<u16>(~(((b0) ? (1u << static_cast<u32>(Bind::BrakeBit0)) : 0u) | \
|
||||
((b1) ? (1u << static_cast<u32>(Bind::BrakeBit1)) : 0u) | \
|
||||
((b2) ? (1u << static_cast<u32>(Bind::BrakeBit2)) : 0u) | \
|
||||
((b3) ? (1u << static_cast<u32>(Bind::BrakeBit3)) : 0u)) & \
|
||||
BRAKE_MASK)
|
||||
|
||||
static constexpr std::array<u16, MAX_BRAKE_LEVEL + 2> BRAKE_TABLE = {{
|
||||
BRAKE_BITS(0, 1, 1, 1), // Released
|
||||
BRAKE_BITS(1, 0, 1, 1), // B1
|
||||
BRAKE_BITS(0, 0, 1, 1), // B2
|
||||
BRAKE_BITS(1, 1, 0, 1), // B3
|
||||
BRAKE_BITS(0, 1, 0, 1), // B4
|
||||
BRAKE_BITS(1, 0, 0, 1), // B5
|
||||
BRAKE_BITS(0, 0, 0, 1), // B6
|
||||
BRAKE_BITS(1, 1, 1, 0), // B7
|
||||
BRAKE_BITS(0, 1, 1, 0), // B8
|
||||
BRAKE_BITS(0, 0, 0, 0), // Emergency
|
||||
BRAKE_BITS(1, 1, 1, 1), // Transition
|
||||
}};
|
||||
|
||||
#undef BRAKE_BITS
|
||||
|
||||
const u32 idx = (m_brake_transition_frames_remaining > 0) ? (MAX_BRAKE_LEVEL + 1) : m_brake_level;
|
||||
m_button_state = (m_button_state & ~BRAKE_MASK) | BRAKE_TABLE[idx];
|
||||
}
|
||||
|
||||
void DDGoController::LoadSettings(const SettingsInterface& si, const char* section, bool initial)
|
||||
{
|
||||
Controller::LoadSettings(si, section, initial);
|
||||
m_analog_deadzone = std::clamp(si.GetFloatValue(section, "AnalogDeadzone", DEFAULT_STICK_DEADZONE), 0.0f, 1.0f);
|
||||
m_analog_sensitivity =
|
||||
std::clamp(si.GetFloatValue(section, "AnalogSensitivity", DEFAULT_STICK_SENSITIVITY), 0.01f, 3.0f);
|
||||
m_power_transition_frames = static_cast<u8>(si.GetIntValue(section, "PowerTransitionFrames", 0));
|
||||
m_brake_transition_frames = static_cast<u8>(si.GetIntValue(section, "BrakeTransitionFrames", 0));
|
||||
}
|
||||
|
||||
std::unique_ptr<DDGoController> DDGoController::Create(u32 index)
|
||||
{
|
||||
return std::make_unique<DDGoController>(index);
|
||||
}
|
||||
|
||||
static const Controller::ControllerBindingInfo s_binding_info[] = {
|
||||
// clang-format off
|
||||
#define BUTTON(name, display_name, icon_name, button, genb) \
|
||||
{name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb}
|
||||
#define AXIS(name, display_name, icon_name, axis, genb) \
|
||||
{name, display_name, icon_name, static_cast<u32>(axis), InputBindingInfo::Type::HalfAxis, genb}
|
||||
|
||||
BUTTON("Select", TRANSLATE_NOOP("DDGoController", "Select"), ICON_PF_SELECT_SHARE, DDGoController::Bind::Select, GenericInputBinding::Select),
|
||||
BUTTON("Start", TRANSLATE_NOOP("DDGoController", "Start"), ICON_PF_START, DDGoController::Bind::Start, GenericInputBinding::Start),
|
||||
BUTTON("A", TRANSLATE_NOOP("DDGoController", "A"), ICON_PF_BUTTON_A, DDGoController::Bind::A, GenericInputBinding::Square),
|
||||
BUTTON("B", TRANSLATE_NOOP("DDGoController", "B"), ICON_PF_BUTTON_B, DDGoController::Bind::B, GenericInputBinding::Cross),
|
||||
BUTTON("C", TRANSLATE_NOOP("DDGoController", "C"), ICON_PF_BUTTON_C, DDGoController::Bind::C, GenericInputBinding::Circle),
|
||||
BUTTON("VirtualPowerOff", TRANSLATE_NOOP("DDGoController", "Power Off"), ICON_PF_KEY_N, DDGoController::Bind::VirtualPowerOff, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualPower1", TRANSLATE_NOOP("DDGoController", "Power 1"), ICON_PF_1, DDGoController::Bind::VirtualPower1, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualPower2", TRANSLATE_NOOP("DDGoController", "Power 2"), ICON_PF_2, DDGoController::Bind::VirtualPower2, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualPower3", TRANSLATE_NOOP("DDGoController", "Power 3"), ICON_PF_3, DDGoController::Bind::VirtualPower3, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualPower4", TRANSLATE_NOOP("DDGoController", "Power 4"), ICON_PF_4, DDGoController::Bind::VirtualPower4, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualPower5", TRANSLATE_NOOP("DDGoController", "Power 5"), ICON_PF_5, DDGoController::Bind::VirtualPower5, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrakeReleased", TRANSLATE_NOOP("DDGoController", "Brake Released"), ICON_PF_KEY_R, DDGoController::Bind::VirtualBrakeReleased, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrake1", TRANSLATE_NOOP("DDGoController", "Brake 1"), ICON_PF_1, DDGoController::Bind::VirtualBrake1, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrake2", TRANSLATE_NOOP("DDGoController", "Brake 2"), ICON_PF_2, DDGoController::Bind::VirtualBrake2, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrake3", TRANSLATE_NOOP("DDGoController", "Brake 3"), ICON_PF_3, DDGoController::Bind::VirtualBrake3, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrake4", TRANSLATE_NOOP("DDGoController", "Brake 4"), ICON_PF_4, DDGoController::Bind::VirtualBrake4, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrake5", TRANSLATE_NOOP("DDGoController", "Brake 5"), ICON_PF_5, DDGoController::Bind::VirtualBrake5, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrake6", TRANSLATE_NOOP("DDGoController", "Brake 6"), ICON_PF_6, DDGoController::Bind::VirtualBrake6, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrake7", TRANSLATE_NOOP("DDGoController", "Brake 7"), ICON_PF_7, DDGoController::Bind::VirtualBrake7, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrake8", TRANSLATE_NOOP("DDGoController", "Brake 8"), ICON_PF_8, DDGoController::Bind::VirtualBrake8, GenericInputBinding::Unknown),
|
||||
BUTTON("VirtualBrakeEmergency", TRANSLATE_NOOP("DDGoController", "Brake Emergency"), ICON_PF_KEY_E, DDGoController::Bind::VirtualBrakeEmergency, GenericInputBinding::Unknown),
|
||||
|
||||
AXIS("Power", TRANSLATE_NOOP("DDGoController", "Power"), ICON_PF_KEY_P, DDGoController::Bind::Power, GenericInputBinding::L2),
|
||||
AXIS("Brake", TRANSLATE_NOOP("DDGoController", "Brake"), ICON_PF_KEY_B, DDGoController::Bind::Brake, GenericInputBinding::R2),
|
||||
// clang-format on
|
||||
|
||||
#undef AXIS
|
||||
#undef BUTTON
|
||||
};
|
||||
|
||||
static const SettingInfo s_settings[] = {
|
||||
{SettingInfo::Type::Float, "AnalogDeadzone", TRANSLATE_NOOP("DDGoController", "Analog Deadzone"),
|
||||
TRANSLATE_NOOP("AnalogController",
|
||||
"Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored."),
|
||||
"0.00f", "0.00f", "1.00f", "0.01f", "%.0f%%", nullptr, 100.0f},
|
||||
{SettingInfo::Type::Float, "AnalogSensitivity", TRANSLATE_NOOP("DDGoController", "Analog Sensitivity"),
|
||||
TRANSLATE_NOOP(
|
||||
"AnalogController",
|
||||
"Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent "
|
||||
"controllers, e.g. DualShock 4, Xbox One Controller."),
|
||||
"1.33f", "0.01f", "2.00f", "0.01f", "%.0f%%", nullptr, 100.0f},
|
||||
{SettingInfo::Type::Integer, "PowerTransitionFrames", TRANSLATE_NOOP("DDGoController", "Power Transition Frames"),
|
||||
TRANSLATE_NOOP("DDGoController", "Sets the number of frames that the controller will report the "
|
||||
"transitioning/inbetween state when changing power level."),
|
||||
"10", "0", "255", "1", "%d", nullptr, 1.0f},
|
||||
{SettingInfo::Type::Integer, "BrakeTransitionFrames", TRANSLATE_NOOP("DDGoController", "Brake Transition Frames"),
|
||||
TRANSLATE_NOOP("DDGoController", "Sets the number of frames that the controller will report the "
|
||||
"transitioning/inbetween state when changing brake level."),
|
||||
"10", "0", "255", "1", "%d", nullptr, 1.0f}};
|
||||
|
||||
const Controller::ControllerInfo DDGoController::INFO = {ControllerType::DDGoController,
|
||||
"DDGoController",
|
||||
TRANSLATE_NOOP("ControllerType", "Densha de Go! Controller"),
|
||||
ICON_PF_GAMEPAD_ALT,
|
||||
s_binding_info,
|
||||
s_settings};
|
124
src/core/ddgo_controller.h
Normal file
124
src/core/ddgo_controller.h
Normal file
@ -0,0 +1,124 @@
|
||||
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class DDGoController final : public Controller
|
||||
{
|
||||
public:
|
||||
enum class Axis : u8
|
||||
{
|
||||
Count
|
||||
};
|
||||
|
||||
enum class Bind : u8
|
||||
{
|
||||
Select = 0,
|
||||
Start = 3,
|
||||
A = 15,
|
||||
B = 14,
|
||||
C = 13,
|
||||
PowerBit0 = 12,
|
||||
PowerBit1 = 7,
|
||||
PowerBit2 = 5,
|
||||
BrakeBit0 = 10,
|
||||
BrakeBit1 = 8,
|
||||
BrakeBit2 = 11,
|
||||
BrakeBit3 = 9,
|
||||
|
||||
// We have to sneak the power/brake axes in here because otherwise we go over the limit for the
|
||||
// input overlay, which is 32 bindings.
|
||||
Power = 1,
|
||||
Brake = 2,
|
||||
|
||||
VirtualButtonStart = 16,
|
||||
VirtualPowerOff = VirtualButtonStart,
|
||||
VirtualPower1,
|
||||
VirtualPower2,
|
||||
VirtualPower3,
|
||||
VirtualPower4,
|
||||
VirtualPower5,
|
||||
VirtualBrakeReleased,
|
||||
VirtualBrake1,
|
||||
VirtualBrake2,
|
||||
VirtualBrake3,
|
||||
VirtualBrake4,
|
||||
VirtualBrake5,
|
||||
VirtualBrake6,
|
||||
VirtualBrake7,
|
||||
VirtualBrake8,
|
||||
VirtualBrakeEmergency,
|
||||
Count,
|
||||
};
|
||||
|
||||
static const Controller::ControllerInfo INFO;
|
||||
|
||||
DDGoController(u32 index);
|
||||
~DDGoController() override;
|
||||
|
||||
static std::unique_ptr<DDGoController> Create(u32 index);
|
||||
|
||||
ControllerType GetType() const override;
|
||||
|
||||
void Reset() override;
|
||||
bool DoState(StateWrapper& sw, bool apply_input_state) override;
|
||||
|
||||
float GetBindState(u32 index) const override;
|
||||
void SetBindState(u32 index, float value) override;
|
||||
u32 GetButtonStateBits() const override;
|
||||
|
||||
void ResetTransferState() override;
|
||||
bool Transfer(const u8 data_in, u8* data_out) override;
|
||||
|
||||
void LoadSettings(const SettingsInterface& si, const char* section, bool initial) override;
|
||||
|
||||
private:
|
||||
enum class TransferState : u8
|
||||
{
|
||||
Idle,
|
||||
Ready,
|
||||
IDMSB,
|
||||
ButtonsLSB,
|
||||
ButtonsMSB
|
||||
};
|
||||
|
||||
static constexpr u16 BUTTON_MASK = static_cast<u16>(~((1u << 4) | (1u << 6)));
|
||||
|
||||
static constexpr u16 POWER_MASK =
|
||||
static_cast<u16>((1u << static_cast<u32>(Bind::PowerBit0)) | (1u << static_cast<u32>(Bind::PowerBit1)) |
|
||||
(1u << static_cast<u32>(Bind::PowerBit2)));
|
||||
|
||||
static constexpr u16 BRAKE_MASK =
|
||||
static_cast<u16>((1u << static_cast<u32>(Bind::BrakeBit0)) | (1u << static_cast<u32>(Bind::BrakeBit1)) |
|
||||
(1u << static_cast<u32>(Bind::BrakeBit2)) | (1u << static_cast<u32>(Bind::BrakeBit3)));
|
||||
|
||||
static constexpr u32 MAX_POWER_LEVEL = 5;
|
||||
static constexpr u32 MAX_BRAKE_LEVEL = 9;
|
||||
|
||||
void SetPowerLevel(u32 level);
|
||||
void UpdatePowerBits();
|
||||
|
||||
void SetBrakeLevel(u32 level);
|
||||
void UpdateBrakeBits();
|
||||
|
||||
// buttons are active low
|
||||
u16 m_button_state = UINT16_C(0xFFFF);
|
||||
|
||||
TransferState m_transfer_state = TransferState::Idle;
|
||||
|
||||
u8 m_power_level = 0;
|
||||
u8 m_brake_level = MAX_BRAKE_LEVEL;
|
||||
|
||||
u8 m_power_transition_frames_remaining = 0;
|
||||
u8 m_brake_transition_frames_remaining = 0;
|
||||
|
||||
u8 m_power_transition_frames = 10;
|
||||
u8 m_brake_transition_frames = 10;
|
||||
|
||||
float m_analog_deadzone = 0.0f;
|
||||
float m_analog_sensitivity = 1.33f;
|
||||
};
|
@ -149,15 +149,9 @@ std::unique_ptr<DigitalController> DigitalController::Create(u32 index, Controll
|
||||
static_cast<u16>(~((1u << static_cast<u8>(Button::Right)) | (1u << static_cast<u8>(Button::Down)) |
|
||||
(1u << static_cast<u8>(Button::Left))));
|
||||
|
||||
// densha de go! controller - up/down grounded
|
||||
static constexpr u16 DDGO_BUTTON_MASK =
|
||||
static_cast<u16>(~((1u << static_cast<u8>(Button::Up)) | (1u << static_cast<u8>(Button::Down))));
|
||||
|
||||
u16 mask = 0xFFFFu;
|
||||
if (type == ControllerType::PopnController)
|
||||
mask = POPN_BUTTON_MASK;
|
||||
else if (type == ControllerType::DDGoController)
|
||||
mask = DDGO_BUTTON_MASK;
|
||||
|
||||
return std::make_unique<DigitalController>(index, mask);
|
||||
}
|
||||
@ -215,33 +209,3 @@ static const Controller::ControllerBindingInfo s_popn_binding_info[] = {
|
||||
const Controller::ControllerInfo DigitalController::INFO_POPN = {
|
||||
ControllerType::PopnController, "PopnController", TRANSLATE_NOOP("ControllerType", "Pop'n Controller"),
|
||||
ICON_PF_POPN_CONTROLLER, s_popn_binding_info, {}};
|
||||
|
||||
static const Controller::ControllerBindingInfo s_ddgo_binding_info[] = {
|
||||
#define BUTTON(name, display_name, icon_name, button, genb) \
|
||||
{name, display_name, icon_name, static_cast<u32>(button), InputBindingInfo::Type::Button, genb}
|
||||
|
||||
// clang-format off
|
||||
BUTTON("Select", TRANSLATE_NOOP("DDGoController", "Select"), ICON_PF_SELECT_SHARE, DigitalController::Button::Select, GenericInputBinding::Select),
|
||||
BUTTON("Start", TRANSLATE_NOOP("DDGoController", "Start"), ICON_PF_START, DigitalController::Button::Start, GenericInputBinding::Start),
|
||||
BUTTON("A", TRANSLATE_NOOP("DDGoController", "A"), ICON_PF_BUTTON_SQUARE, DigitalController::Button::Square, GenericInputBinding::Square),
|
||||
BUTTON("B", TRANSLATE_NOOP("DDGoController", "B"), ICON_PF_BUTTON_CROSS, DigitalController::Button::Cross, GenericInputBinding::Cross),
|
||||
BUTTON("C", TRANSLATE_NOOP("DDGoController", "C"), ICON_PF_BUTTON_CIRCLE, DigitalController::Button::Circle, GenericInputBinding::Circle),
|
||||
BUTTON("Power1", TRANSLATE_NOOP("DDGoController", "Power 1"), ICON_PF_BUTTON_TRIANGLE, DigitalController::Button::Triangle, GenericInputBinding::Triangle),
|
||||
BUTTON("Power2", TRANSLATE_NOOP("DDGoController", "Power 2"), ICON_PF_DPAD_LEFT, DigitalController::Button::Left, GenericInputBinding::DPadLeft),
|
||||
BUTTON("Power3", TRANSLATE_NOOP("DDGoController", "Power 3"), ICON_PF_DPAD_RIGHT, DigitalController::Button::Right, GenericInputBinding::DPadRight),
|
||||
BUTTON("Brake1", TRANSLATE_NOOP("DDGoController", "Brake 1"), ICON_PF_LEFT_SHOULDER_L1, DigitalController::Button::L1, GenericInputBinding::L1),
|
||||
BUTTON("Brake2", TRANSLATE_NOOP("DDGoController", "Brake 3"), ICON_PF_RIGHT_SHOULDER_R1, DigitalController::Button::R1, GenericInputBinding::R1),
|
||||
BUTTON("Brake3", TRANSLATE_NOOP("DDGoController", "Brake 2"), ICON_PF_LEFT_TRIGGER_L2, DigitalController::Button::L2, GenericInputBinding::L2),
|
||||
BUTTON("Brake4", TRANSLATE_NOOP("DDGoController", "Brake 4"), ICON_PF_RIGHT_TRIGGER_R2, DigitalController::Button::R2, GenericInputBinding::R2),
|
||||
// clang-format on
|
||||
|
||||
#undef BUTTON
|
||||
};
|
||||
|
||||
const Controller::ControllerInfo DigitalController::INFO_DDGO = {
|
||||
ControllerType::DDGoController,
|
||||
"DDGoController",
|
||||
TRANSLATE_NOOP("ControllerType", "Densha de Go! Controller"),
|
||||
ICON_PF_GAMEPAD_ALT,
|
||||
s_ddgo_binding_info,
|
||||
{}};
|
||||
|
@ -3,6 +3,6 @@
|
||||
|
||||
static constexpr ImWchar FA_ICON_RANGE[] = { 0xe06f,0xe070,0xe086,0xe086,0xf002,0xf002,0xf005,0xf005,0xf007,0xf007,0xf00c,0xf00e,0xf011,0xf013,0xf017,0xf017,0xf019,0xf019,0xf01c,0xf01c,0xf021,0xf021,0xf023,0xf023,0xf025,0xf026,0xf028,0xf028,0xf02e,0xf02e,0xf030,0xf030,0xf03a,0xf03a,0xf03d,0xf03e,0xf04a,0xf04c,0xf050,0xf050,0xf056,0xf056,0xf05e,0xf05e,0xf062,0xf063,0xf065,0xf067,0xf071,0xf071,0xf075,0xf075,0xf077,0xf078,0xf07b,0xf07c,0xf083,0xf085,0xf091,0xf091,0xf0ac,0xf0ae,0xf0b2,0xf0b2,0xf0c3,0xf0c3,0xf0c5,0xf0c5,0xf0c7,0xf0c9,0xf0cb,0xf0cb,0xf0d0,0xf0d0,0xf0dc,0xf0dc,0xf0e0,0xf0e0,0xf0e2,0xf0e2,0xf0e7,0xf0e8,0xf0eb,0xf0eb,0xf0f1,0xf0f1,0xf0f3,0xf0f3,0xf0fe,0xf0fe,0xf110,0xf110,0xf11b,0xf11c,0xf140,0xf140,0xf144,0xf144,0xf146,0xf146,0xf14a,0xf14a,0xf15b,0xf15d,0xf191,0xf192,0xf1ab,0xf1ab,0xf1c0,0xf1c0,0xf1c5,0xf1c5,0xf1de,0xf1de,0xf1e6,0xf1e6,0xf1eb,0xf1eb,0xf1f8,0xf1f8,0xf1fb,0xf1fc,0xf201,0xf201,0xf240,0xf240,0xf242,0xf242,0xf245,0xf245,0xf26c,0xf26c,0xf279,0xf279,0xf2c1,0xf2c1,0xf2d0,0xf2d0,0xf2db,0xf2db,0xf2f1,0xf2f2,0xf302,0xf302,0xf31e,0xf31e,0xf35d,0xf35d,0xf360,0xf360,0xf362,0xf362,0xf3fd,0xf3fd,0xf410,0xf410,0xf422,0xf422,0xf424,0xf424,0xf462,0xf462,0xf466,0xf466,0xf4ce,0xf4ce,0xf500,0xf500,0xf517,0xf517,0xf51f,0xf51f,0xf538,0xf538,0xf53f,0xf53f,0xf545,0xf545,0xf547,0xf548,0xf54c,0xf54c,0xf55b,0xf55b,0xf55d,0xf55d,0xf565,0xf565,0xf56e,0xf570,0xf575,0xf575,0xf5a2,0xf5a2,0xf5aa,0xf5aa,0xf5ae,0xf5ae,0xf5c7,0xf5c7,0xf5cb,0xf5cb,0xf5e7,0xf5e7,0xf5ee,0xf5ee,0xf61f,0xf61f,0xf65d,0xf65e,0xf6a9,0xf6a9,0xf6cf,0xf6cf,0xf70c,0xf70c,0xf70e,0xf70e,0xf78c,0xf78c,0xf794,0xf794,0xf7a0,0xf7a0,0xf7a4,0xf7a5,0xf7c2,0xf7c2,0xf807,0xf807,0xf815,0xf815,0xf818,0xf818,0xf84c,0xf84c,0xf853,0xf853,0xf87d,0xf87d,0xf8cc,0xf8cc,0x0,0x0 };
|
||||
|
||||
static constexpr ImWchar PF_ICON_RANGE[] = { 0x2196,0x2199,0x219e,0x21a3,0x21b0,0x21b3,0x21ba,0x21c3,0x21c7,0x21ca,0x21d0,0x21d4,0x21e0,0x21e3,0x21e6,0x21e8,0x21ed,0x21ee,0x21f7,0x21f8,0x21fa,0x21fb,0x221a,0x221b,0x227a,0x227f,0x2284,0x2284,0x22bf,0x22c8,0x2349,0x2349,0x235e,0x235e,0x2360,0x2361,0x2364,0x2366,0x23b2,0x23b4,0x23cc,0x23cc,0x23ce,0x23ce,0x23f4,0x23f7,0x2427,0x243a,0x243c,0x243e,0x2446,0x2446,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2717,0x2717,0x2753,0x2753,0x278a,0x278e,0x27fc,0x27fc,0xe000,0xe001,0xff21,0xff3a,0x1f52b,0x1f52b,0x0,0x0 };
|
||||
static constexpr ImWchar PF_ICON_RANGE[] = { 0x2196,0x2199,0x219e,0x21a3,0x21b0,0x21b3,0x21ba,0x21c3,0x21c7,0x21ca,0x21d0,0x21d4,0x21e0,0x21e3,0x21e6,0x21e8,0x21eb,0x21eb,0x21ed,0x21ee,0x21f7,0x21f8,0x21fa,0x21fb,0x221a,0x221b,0x227a,0x227f,0x2284,0x2284,0x22bf,0x22c8,0x2349,0x2349,0x235e,0x235e,0x2360,0x2361,0x2364,0x2366,0x23b2,0x23b4,0x23cc,0x23cc,0x23ce,0x23ce,0x23f4,0x23f7,0x2427,0x243a,0x243c,0x243e,0x2446,0x2446,0x2460,0x246b,0x248f,0x248f,0x24f5,0x24fd,0x24ff,0x24ff,0x2717,0x2717,0x2753,0x2753,0x278a,0x278e,0x27fc,0x27fc,0xe000,0xe001,0xff21,0xff3a,0x1f52b,0x1f52b,0x0,0x0 };
|
||||
|
||||
static constexpr ImWchar EMOJI_ICON_RANGE[] = { 0x2139,0x2139,0x23e9,0x23ea,0x23f8,0x23f8,0x26a0,0x26a0,0x1f4be,0x1f4be,0x1f4c2,0x1f4c2,0x1f4f7,0x1f4f8,0x1f504,0x1f504,0x1f507,0x1f507,0x1f509,0x1f50a,0x1f50d,0x1f50d,0x1f513,0x1f513,0x0,0x0 };
|
||||
|
Loading…
x
Reference in New Issue
Block a user