Controllers (#3378)

* lay some groundwork

* use custom window (which is currently identical to the LUS window)

* start making it shippy

* start moving stuff out of gamecontroleditor

* clean up shouldrumble

* include the other way

* wii u

* latest lus main

* notch snap angle buttons

* buttons on all the sliders

* just use a hidden id

* handle debug for port 2 and rename tabs so everything fits

* button line buttons look better

* padding fixed

* clang format

* bump to latest LUS main

* big buttons

* just default the analog stick options to open for now

* fix wii u build

* bonus: make it all scale-aware

* clang format

* fix horizontal scrolling

* fix all +/- buttons

* keyboard set defaults

* axis threshold helper text

* bonus: test rumble button

* clang format

* fix otrexporter submodule

* bump to latest lus main
This commit is contained in:
briaguya 2023-11-20 05:02:15 -08:00 committed by GitHub
parent 34556e40d6
commit 76e90c0928
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 2037 additions and 85 deletions

@ -1 +1 @@
Subproject commit f717dd265aff2eff359de26915d8ad4e498ffdaf Subproject commit 1b41afa9eaedb19a9073f5dd1eca2cda4dea59c8

View File

@ -14,6 +14,8 @@
#include <Utils/StringHelper.h> #include <Utils/StringHelper.h>
#include <libultraship/libultraship.h> #include <libultraship/libultraship.h>
#include "macros.h"
#include "../../UIWidgets.hpp" #include "../../UIWidgets.hpp"
namespace GameControlEditor { namespace GameControlEditor {
@ -214,16 +216,6 @@ namespace GameControlEditor {
ImGui::EndTable(); ImGui::EndTable();
} }
// CurrentPort is indexed started at 1 here due to the Generic tab, instead of 0 like in InputEditorWindow
// Therefore CurrentPort - 1 must always be used inside this function instead of CurrentPort
void DrawCustomButtons() {
auto inputEditorWindow = std::reinterpret_pointer_cast<LUS::InputEditorWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Input Editor"));
inputEditorWindow->DrawControllerSelect(CurrentPort - 1);
inputEditorWindow->DrawButton("Modifier 1", BTN_MODIFIER1, CurrentPort - 1, &BtnReading);
inputEditorWindow->DrawButton("Modifier 2", BTN_MODIFIER2, CurrentPort - 1, &BtnReading);
}
void DrawCameraControlPanel(GameControlEditorWindow* window) { void DrawCameraControlPanel(GameControlEditorWindow* window) {
if (!ImGui::CollapsingHeader("Camera Controls")) { if (!ImGui::CollapsingHeader("Camera Controls")) {
return; return;
@ -329,70 +321,14 @@ namespace GameControlEditor {
window->EndGroupPanelPublic(0); window->EndGroupPanelPublic(0);
} }
void DrawLEDControlPanel(GameControlEditorWindow* window) {
window->BeginGroupPanelPublic("LED Colors", ImGui::GetContentRegionAvail());
static const char* ledSources[] = { "Original Tunic Colors", "Cosmetics Tunic Colors", "Health Colors",
"Original Navi Targeting Colors", "Cosmetics Navi Targeting Colors", "Custom" };
UIWidgets::PaddedText("Source");
UIWidgets::EnhancementCombobox("gLedColorSource", ledSources, LED_SOURCE_TUNIC_ORIGINAL);
DrawHelpIcon("Health\n- Red when health critical (13-20% depending on max health)\n- Yellow when health < 40%. Green otherwise.\n\n" \
"Tunics: colors will mirror currently equipped tunic, whether original or the current values in Cosmetics Editor.\n\n" \
"Custom: single, solid color");
if (CVarGetInteger("gLedColorSource", 1) == LED_SOURCE_CUSTOM) {
UIWidgets::Spacer(3);
auto port1Color = CVarGetColor24("gLedPort1Color", { 255, 255, 255 });
ImVec4 colorVec = { port1Color.r / 255.0f, port1Color.g / 255.0f, port1Color.b / 255.0f, 1.0f };
if (ImGui::ColorEdit3("", (float*)&colorVec, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) {
Color_RGB8 color;
color.r = colorVec.x * 255.0;
color.g = colorVec.y * 255.0;
color.b = colorVec.z * 255.0;
CVarSetColor24("gLedPort1Color", color);
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
ImGui::SameLine();
ImGui::Text("Custom Color");
}
UIWidgets::PaddedEnhancementSliderFloat("Brightness: %d%%", "##LED_Brightness", "gLedBrightness",
0.0f, 1.0f, "", 1.0f, true, true);
DrawHelpIcon("Sets the brightness of controller LEDs. 0% brightness = LEDs off.");
UIWidgets::PaddedEnhancementCheckbox("Critical Health Override", "gLedCriticalOverride", true, true,
CVarGetInteger("gLedColorSource", LED_SOURCE_TUNIC_ORIGINAL) == LED_SOURCE_HEALTH, "Override redundant for health source.",
UIWidgets::CheckboxGraphics::Cross, true);
DrawHelpIcon("Shows red color when health is critical, otherwise displays according to color source.");
window->EndGroupPanelPublic(0);
}
void GameControlEditorWindow::DrawElement() { void GameControlEditorWindow::DrawElement() {
ImGui::SetNextWindowSize(ImVec2(465, 430), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(465, 430), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Game Controls Configuration", &mIsVisible)) { if (ImGui::Begin("Game Controls Configuration", &mIsVisible)) {
ImGui::BeginTabBar("##CustomControllers"); DrawOcarinaControlPanel(this);
if (ImGui::BeginTabItem("Generic")) { DrawCameraControlPanel(this);
CurrentPort = 0; DrawDpadControlPanel(this);
ImGui::EndTabItem(); DrawMiscControlPanel(this);
}
for (int i = 1; i <= 4; i++) {
if (ImGui::BeginTabItem(StringHelper::Sprintf("Port %d", i).c_str())) {
CurrentPort = i;
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
if (CurrentPort == 0) {
DrawOcarinaControlPanel(this);
DrawCameraControlPanel(this);
DrawDpadControlPanel(this);
DrawMiscControlPanel(this);
} else {
DrawCustomButtons();
if (CurrentPort == 1 && LUS::Context::GetInstance()->GetControlDeck()->GetDeviceFromPortIndex(0)->CanSetLed()) {
DrawLEDControlPanel(this);
}
}
} }
ImGui::End(); ImGui::End();
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,86 @@
#pragma once
#include "stdint.h"
#include <libultraship/libultraship.h>
#include <ImGui/imgui.h>
#include <unordered_map>
#include <string>
#include <vector>
#include <set>
class SohInputEditorWindow : public LUS::GuiWindow {
public:
using GuiWindow::GuiWindow;
~SohInputEditorWindow();
void DrawButton(const char* label, int32_t n64Btn, int32_t currentPort, int32_t* btnReading);
void DrawInputChip(const char* buttonName, ImVec4 color);
void DrawAnalogPreview(const char* label, ImVec2 stick, float deadzone = 0, bool gyro = false);
void DrawControllerSchema();
bool TestingRumble();
protected:
void InitElement() override;
void DrawElement() override;
void UpdateElement() override;
private:
void DrawStickDirectionLine(const char* axisDirectionName, uint8_t port, uint8_t stick, LUS::Direction direction,
ImVec4 color);
void DrawButtonLine(const char* buttonName, uint8_t port, uint16_t bitmask, ImVec4 color);
void DrawButtonLineEditMappingButton(uint8_t port, uint16_t bitmask, std::string id);
void DrawButtonLineAddMappingButton(uint8_t port, uint16_t bitmask);
void DrawStickDirectionLineEditMappingButton(uint8_t port, uint8_t stick, LUS::Direction direction, std::string id);
void DrawStickDirectionLineAddMappingButton(uint8_t port, uint8_t stick, LUS::Direction direction);
void DrawStickSection(uint8_t port, uint8_t stick, int32_t id, ImVec4 color);
void DrawRumbleSection(uint8_t port);
void DrawRemoveRumbleMappingButton(uint8_t port, std::string id);
void DrawAddRumbleMappingButton(uint8_t port);
void DrawLEDSection(uint8_t port);
void DrawRemoveLEDMappingButton(uint8_t port, std::string id);
void DrawAddLEDMappingButton(uint8_t port);
void DrawGyroSection(uint8_t port);
void DrawRemoveGyroMappingButton(uint8_t port, std::string id);
void DrawAddGyroMappingButton(uint8_t port);
int32_t mGameInputBlockTimer;
int32_t mMappingInputBlockTimer;
int32_t mRumbleTimer;
std::shared_ptr<LUS::ControllerRumbleMapping> mRumbleMappingToTest;
// mBitmaskToMappingIds[port][bitmask] = { id0, id1, ... }
std::unordered_map<uint8_t, std::unordered_map<uint16_t, std::vector<std::string>>> mBitmaskToMappingIds;
// mStickDirectionToMappingIds[port][stick][direction] = { id0, id1, ... }
std::unordered_map<uint8_t,
std::unordered_map<uint8_t, std::unordered_map<LUS::Direction, std::vector<std::string>>>>
mStickDirectionToMappingIds;
void UpdateBitmaskToMappingIds(uint8_t port);
void UpdateStickDirectionToMappingIds(uint8_t port);
void GetButtonColorsForLUSDeviceIndex(LUS::LUSDeviceIndex lusIndex, ImVec4& buttonColor,
ImVec4& buttonHoveredColor);
void DrawLinkTab();
void DrawIvanTab();
void DrawDebugPortTab(uint8_t portIndex, std::string customName = "");
void DrawDevicesTab();
std::set<uint16_t> mButtonsBitmasks;
std::set<uint16_t> mDpadBitmasks;
std::set<uint16_t> mModifierButtonsBitmasks;
void DrawButtonDeviceIcons(uint8_t portIndex, std::set<uint16_t> bitmasks);
void DrawAnalogStickDeviceIcons(uint8_t portIndex, LUS::Stick stick);
void DrawRumbleDeviceIcons(uint8_t portIndex);
void DrawGyroDeviceIcons(uint8_t portIndex);
void DrawLEDDeviceIcons(uint8_t portIndex);
bool mInputEditorPopupOpen;
void DrawSetDefaultsButton(uint8_t portIndex);
void DrawClearAllButton(uint8_t portIndex);
void DrawHelpIcon(const std::string& helptext);
};

View File

@ -30,6 +30,7 @@
#include <AudioPlayer.h> #include <AudioPlayer.h>
#include "Enhancements/speechsynthesizer/SpeechSynthesizer.h" #include "Enhancements/speechsynthesizer/SpeechSynthesizer.h"
#include "Enhancements/controls/GameControlEditor.h" #include "Enhancements/controls/GameControlEditor.h"
#include "Enhancements/controls/SohInputEditorWindow.h"
#include "Enhancements/cosmetics/CosmeticsEditor.h" #include "Enhancements/cosmetics/CosmeticsEditor.h"
#include "Enhancements/audio/AudioCollection.h" #include "Enhancements/audio/AudioCollection.h"
#include "Enhancements/audio/AudioEditor.h" #include "Enhancements/audio/AudioEditor.h"
@ -279,8 +280,25 @@ OTRGlobals::OTRGlobals() {
OOT_PAL_GC_DBG1, OOT_PAL_GC_DBG1,
OOT_PAL_GC_DBG2 OOT_PAL_GC_DBG2
}; };
context = LUS::Context::CreateUninitializedInstance("Ship of Harkinian", appShortName, "shipofharkinian.json");
context->InitLogging();
context->InitConfiguration();
context->InitConsoleVariables();
// tell LUS to reserve 3 SoH specific threads (Game, Audio, Save) // tell LUS to reserve 3 SoH specific threads (Game, Audio, Save)
context = LUS::Context::CreateInstance("Ship of Harkinian", appShortName, "shipofharkinian.json", OTRFiles, {}, 3); context->InitResourceManager(OTRFiles, {}, 3);
context->InitControlDeck({BTN_MODIFIER1, BTN_MODIFIER2});
context->GetControlDeck()->SetSinglePlayerMappingMode(true);
context->InitCrashHandler();
context->InitConsole();
auto sohInputEditorWindow = std::make_shared<SohInputEditorWindow>("gControllerConfigurationEnabled", "Input Editor");
context->InitWindow(sohInputEditorWindow);
context->InitAudio();
context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SOH_Animation, "Animation", std::make_shared<LUS::AnimationFactory>()); context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SOH_Animation, "Animation", std::make_shared<LUS::AnimationFactory>());
context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SOH_PlayerAnimation, "PlayerAnimation", std::make_shared<LUS::PlayerAnimationFactory>()); context->GetResourceManager()->GetResourceLoader()->RegisterResourceFactory(LUS::ResourceType::SOH_PlayerAnimation, "PlayerAnimation", std::make_shared<LUS::PlayerAnimationFactory>());
@ -2065,15 +2083,22 @@ Color_RGB8 GetColorForControllerLED() {
} }
extern "C" void OTRControllerCallback(uint8_t rumble) { extern "C" void OTRControllerCallback(uint8_t rumble) {
auto physicalDevice = LUS::Context::GetInstance()->GetControlDeck()->GetDeviceFromPortIndex(0); // We call this every tick, SDL accounts for this use and prevents driver spam
// https://github.com/libsdl-org/SDL/blob/f17058b562c8a1090c0c996b42982721ace90903/src/joystick/SDL_joystick.c#L1114-L1144
LUS::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0)->GetLED()->SetLEDColor(GetColorForControllerLED());
if (physicalDevice->CanSetLed()) { static std::shared_ptr<SohInputEditorWindow> controllerConfigWindow = nullptr;
// We call this every tick, SDL accounts for this use and prevents driver spam if (controllerConfigWindow == nullptr) {
// https://github.com/libsdl-org/SDL/blob/f17058b562c8a1090c0c996b42982721ace90903/src/joystick/SDL_joystick.c#L1114-L1144 controllerConfigWindow = std::dynamic_pointer_cast<SohInputEditorWindow>(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetGuiWindow("Input Editor"));
physicalDevice->SetLedColor(0, GetColorForControllerLED()); } else if (controllerConfigWindow->TestingRumble()) {
return;
} }
physicalDevice->SetRumble(0, rumble); if (rumble) {
LUS::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0)->GetRumble()->StartRumble();
} else {
LUS::Context::GetInstance()->GetControlDeck()->GetControllerByPort(0)->GetRumble()->StopRumble();
}
} }
extern "C" float OTRGetAspectRatio() { extern "C" float OTRGetAspectRatio() {
@ -2112,12 +2137,12 @@ extern "C" void AudioPlayer_Play(const uint8_t* buf, uint32_t len) {
} }
extern "C" int Controller_ShouldRumble(size_t slot) { extern "C" int Controller_ShouldRumble(size_t slot) {
auto controlDeck = LUS::Context::GetInstance()->GetControlDeck(); for (auto [id, mapping] : LUS::Context::GetInstance()
->GetControlDeck()
if (slot < controlDeck->GetNumConnectedPorts()) { ->GetControllerByPort(static_cast<uint8_t>(slot))
auto physicalDevice = controlDeck->GetDeviceFromPortIndex(slot); ->GetRumble()
->GetAllRumbleMappings()) {
if (physicalDevice->GetProfile(slot)->UseRumble && physicalDevice->CanRumble()) { if (mapping->PhysicalDeviceIsConnected()) {
return 1; return 1;
} }
} }

View File

@ -12,6 +12,9 @@
#define GAME_PLATFORM_N64 0 #define GAME_PLATFORM_N64 0
#define GAME_PLATFORM_GC 1 #define GAME_PLATFORM_GC 1
#define BTN_MODIFIER1 0x00040
#define BTN_MODIFIER2 0x00080
#ifdef __cplusplus #ifdef __cplusplus
#include <Context.h> #include <Context.h>
#include "Enhancements/savestates.h" #include "Enhancements/savestates.h"