// Copyright 2010 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/HW/GCPadEmu.h" #include #include "Common/Common.h" #include "Common/CommonTypes.h" #include "InputCommon/ControllerEmu/Control/Input.h" #include "InputCommon/ControllerEmu/Control/Output.h" #include "InputCommon/ControllerEmu/ControlGroup/AnalogStick.h" #include "InputCommon/ControllerEmu/ControlGroup/Buttons.h" #include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h" #include "InputCommon/ControllerEmu/ControlGroup/MixedTriggers.h" #include "InputCommon/ControllerEmu/StickGate.h" #include "InputCommon/GCPadStatus.h" static const u16 button_bitmasks[] = { PAD_BUTTON_A, PAD_BUTTON_B, PAD_BUTTON_X, PAD_BUTTON_Y, PAD_TRIGGER_Z, PAD_BUTTON_START, 0 // MIC HAX }; static const u16 trigger_bitmasks[] = { PAD_TRIGGER_L, PAD_TRIGGER_R, }; static const u16 dpad_bitmasks[] = {PAD_BUTTON_UP, PAD_BUTTON_DOWN, PAD_BUTTON_LEFT, PAD_BUTTON_RIGHT}; static const char* const named_buttons[] = {"A", "B", "X", "Y", "Z", "Start"}; static const char* const named_triggers[] = { // i18n: The left trigger button (labeled L on real controllers) _trans("L"), // i18n: The right trigger button (labeled R on real controllers) _trans("R"), // i18n: The left trigger button (labeled L on real controllers) used as an analog input _trans("L-Analog"), // i18n: The right trigger button (labeled R on real controllers) used as an analog input _trans("R-Analog")}; GCPad::GCPad(const unsigned int index) : m_index(index) { // buttons groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons"))); for (const char* named_button : named_buttons) { const bool is_start = named_button == std::string("Start"); const ControllerEmu::Translatability translate = is_start ? ControllerEmu::Translate : ControllerEmu::DoNotTranslate; // i18n: The START/PAUSE button on GameCube controllers std::string ui_name = is_start ? _trans("START") : named_button; m_buttons->AddInput(translate, named_button, std::move(ui_name)); } // sticks groups.emplace_back(m_main_stick = new ControllerEmu::OctagonAnalogStick( "Main Stick", _trans("Control Stick"), MAIN_STICK_GATE_RADIUS)); groups.emplace_back(m_c_stick = new ControllerEmu::OctagonAnalogStick( "C-Stick", _trans("C Stick"), C_STICK_GATE_RADIUS)); // triggers groups.emplace_back(m_triggers = new ControllerEmu::MixedTriggers(_trans("Triggers"))); for (const char* named_trigger : named_triggers) { m_triggers->AddInput(ControllerEmu::Translate, named_trigger); } // rumble groups.emplace_back(m_rumble = new ControllerEmu::ControlGroup(_trans("Rumble"))); m_rumble->AddOutput(ControllerEmu::Translate, _trans("Motor")); // Microphone groups.emplace_back(m_mic = new ControllerEmu::Buttons(_trans("Microphone"))); m_mic->AddInput(ControllerEmu::Translate, _trans("Button")); // dpad groups.emplace_back(m_dpad = new ControllerEmu::Buttons(_trans("D-Pad"))); for (const char* named_direction : named_directions) { m_dpad->AddInput(ControllerEmu::Translate, named_direction); } // options groups.emplace_back(m_options = new ControllerEmu::ControlGroup(_trans("Options"))); m_options->AddSetting( &m_always_connected_setting, // i18n: Treat a controller as always being connected regardless of what // devices the user actually has plugged in {_trans("Always Connected"), nullptr, _trans("If checked, the emulated controller is always connected.\n" "If unchecked, the connection state of the emulated controller is linked\n" "to the connection state of the real default device (if there is one).")}, false); } std::string GCPad::GetName() const { return std::string("GCPad") + char('1' + m_index); } ControllerEmu::ControlGroup* GCPad::GetGroup(PadGroup group) { switch (group) { case PadGroup::Buttons: return m_buttons; case PadGroup::MainStick: return m_main_stick; case PadGroup::CStick: return m_c_stick; case PadGroup::DPad: return m_dpad; case PadGroup::Triggers: return m_triggers; case PadGroup::Rumble: return m_rumble; case PadGroup::Mic: return m_mic; case PadGroup::Options: return m_options; default: return nullptr; } } GCPadStatus GCPad::GetInput() const { const auto lock = GetStateLock(); GCPadStatus pad = {}; if (!(m_always_connected_setting.GetValue() || IsDefaultDeviceConnected())) { pad.isConnected = false; return pad; } // buttons m_buttons->GetState(&pad.button, button_bitmasks); // set analog A/B analog to full or w/e, prolly not needed if (pad.button & PAD_BUTTON_A) pad.analogA = 0xFF; if (pad.button & PAD_BUTTON_B) pad.analogB = 0xFF; // dpad m_dpad->GetState(&pad.button, dpad_bitmasks); // sticks const auto main_stick_state = m_main_stick->GetState(); pad.stickX = MapFloat(main_stick_state.x, GCPadStatus::MAIN_STICK_CENTER_X); pad.stickY = MapFloat(main_stick_state.y, GCPadStatus::MAIN_STICK_CENTER_Y); const auto c_stick_state = m_c_stick->GetState(); pad.substickX = MapFloat(c_stick_state.x, GCPadStatus::C_STICK_CENTER_X); pad.substickY = MapFloat(c_stick_state.y, GCPadStatus::C_STICK_CENTER_Y); // triggers std::array triggers; m_triggers->GetState(&pad.button, trigger_bitmasks, triggers.data()); pad.triggerLeft = MapFloat(triggers[0], 0); pad.triggerRight = MapFloat(triggers[1], 0); return pad; } void GCPad::SetOutput(const ControlState strength) { const auto lock = GetStateLock(); m_rumble->controls[0]->control_ref->State(strength); } void GCPad::LoadDefaults(const ControllerInterface& ciface) { EmulatedController::LoadDefaults(ciface); // Buttons m_buttons->SetControlExpression(0, "`X`"); // A m_buttons->SetControlExpression(1, "`Z`"); // B m_buttons->SetControlExpression(2, "`C`"); // X m_buttons->SetControlExpression(3, "`S`"); // Y m_buttons->SetControlExpression(4, "`D`"); // Z #ifdef _WIN32 m_buttons->SetControlExpression(5, "`RETURN`"); // Start #else // OS X/Linux // Start m_buttons->SetControlExpression(5, "`Return`"); #endif // stick modifiers to 50 % m_main_stick->controls[4]->control_ref->range = 0.5f; m_c_stick->controls[4]->control_ref->range = 0.5f; // D-Pad m_dpad->SetControlExpression(0, "`T`"); // Up m_dpad->SetControlExpression(1, "`G`"); // Down m_dpad->SetControlExpression(2, "`F`"); // Left m_dpad->SetControlExpression(3, "`H`"); // Right // C Stick m_c_stick->SetControlExpression(0, "`I`"); // Up m_c_stick->SetControlExpression(1, "`K`"); // Down m_c_stick->SetControlExpression(2, "`J`"); // Left m_c_stick->SetControlExpression(3, "`L`"); // Right // Modifier m_c_stick->SetControlExpression(4, "`Ctrl`"); // Control Stick #ifdef _WIN32 m_main_stick->SetControlExpression(0, "`UP`"); // Up m_main_stick->SetControlExpression(1, "`DOWN`"); // Down m_main_stick->SetControlExpression(2, "`LEFT`"); // Left m_main_stick->SetControlExpression(3, "`RIGHT`"); // Right #elif __APPLE__ m_main_stick->SetControlExpression(0, "`Up Arrow`"); // Up m_main_stick->SetControlExpression(1, "`Down Arrow`"); // Down m_main_stick->SetControlExpression(2, "`Left Arrow`"); // Left m_main_stick->SetControlExpression(3, "`Right Arrow`"); // Right #else m_main_stick->SetControlExpression(0, "`Up`"); // Up m_main_stick->SetControlExpression(1, "`Down`"); // Down m_main_stick->SetControlExpression(2, "`Left`"); // Left m_main_stick->SetControlExpression(3, "`Right`"); // Right #endif // Modifier m_main_stick->SetControlExpression(4, "`Shift`"); // Because our defaults use keyboard input, set calibration shapes to squares. m_c_stick->SetCalibrationFromGate(ControllerEmu::SquareStickGate(1.0)); m_main_stick->SetCalibrationFromGate(ControllerEmu::SquareStickGate(1.0)); // Triggers m_triggers->SetControlExpression(0, "`Q`"); // L m_triggers->SetControlExpression(1, "`W`"); // R } bool GCPad::GetMicButton() const { const auto lock = GetStateLock(); return m_mic->controls.back()->GetState(); }