diff --git a/.gitignore b/.gitignore index 5a201405d..4b6eb3968 100644 --- a/.gitignore +++ b/.gitignore @@ -405,3 +405,4 @@ tags oot.otr *.sav shipofharkinian.ini +shipofharkinian.json \ No newline at end of file diff --git a/libultraship/Makefile b/libultraship/Makefile index 819acce9f..18dfca271 100644 --- a/libultraship/Makefile +++ b/libultraship/Makefile @@ -82,6 +82,7 @@ CXX_FILES := \ $(shell find libultraship/Lib/Fast3D -name "*.cpp") \ $(shell find libultraship -maxdepth 1 -name "*.cpp") \ $(shell find libultraship/Lib/ImGui -maxdepth 1 -name "*.cpp") \ + $(shell find libultraship/Lib/Mercury -maxdepth 1 -name "*.cpp") \ libultraship/Lib/ImGui/backends/imgui_impl_opengl3.cpp \ libultraship/Lib/ImGui/backends/imgui_impl_sdl.cpp \ libultraship/Lib/StrHash64.cpp \ @@ -114,6 +115,7 @@ INC_DIRS := $(addprefix -I, \ libultraship/Lib/spdlog \ libultraship/Lib/spdlog/include \ libultraship/Lib/ImGui \ + libultraship/Lib/Mercury \ libultraship \ ../StormLib/src \ ) diff --git a/libultraship/libultraship/ConfigFile.cpp b/libultraship/libultraship/ConfigFile.cpp deleted file mode 100644 index e8a52d881..000000000 --- a/libultraship/libultraship/ConfigFile.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "ConfigFile.h" -#include "spdlog/spdlog.h" -#include "GlobalCtx2.h" -#include "Window.h" -#include "GameSettings.h" - -namespace Ship { - ConfigFile::ConfigFile(std::shared_ptr Context, const std::string& Path) : Context(Context), Path(Path), File(Path.c_str()) { - if (Path.empty()) { - SPDLOG_ERROR("ConfigFile received an empty file name"); - exit(EXIT_FAILURE); - } - - if (!File.read(Val)) { - if (!CreateDefaultConfig()) { - SPDLOG_ERROR("Failed to create default configs"); - exit(EXIT_FAILURE); - } - } - } - - ConfigFile::~ConfigFile() { - if (!Save()) { - SPDLOG_ERROR("Failed to save configs!!!"); - } - - SPDLOG_INFO("destruct configfile"); - } - - mINI::INIMap& ConfigFile::operator[](const std::string& Section) { - return Val[Section]; - } - - mINI::INIMap ConfigFile::get(const std::string& Section) { - return Val.get(Section); - } - - bool ConfigFile::has(const std::string& Section) { - return Val.has(Section); - } - - bool ConfigFile::remove(const std::string& Section) { - return Val.remove(Section); - } - - void ConfigFile::clear() { - Val.clear(); - } - - std::size_t ConfigFile::size() const { - return Val.size(); - } - - bool ConfigFile::Save() { - return File.write(Val); - } - - bool ConfigFile::CreateDefaultConfig() { - (*this)["ARCHIVE"]["Main Archive"] = ""; - (*this)["ARCHIVE"]["Patches Directory"] = ""; - - (*this)["SAVE"]["Save Filename"] = ""; - - (*this)["CONTROLLERS"]["CONTROLLER 1"] = "Auto"; - (*this)["CONTROLLERS"]["CONTROLLER 2"] = "Unplugged"; - (*this)["CONTROLLERS"]["CONTROLLER 3"] = "Unplugged"; - (*this)["CONTROLLERS"]["CONTROLLER 4"] = "Unplugged"; - - (*this)["KEYBOARD SHORTCUTS"]["KEY_FULLSCREEN"] = std::to_string(0x044); - (*this)["KEYBOARD SHORTCUTS"]["KEY_CONSOLE"] = std::to_string(0x029); - - (*this)["WINDOW"]["WINDOW WIDTH"] = std::to_string(640); - (*this)["WINDOW"]["WINDOW HEIGHT"] = std::to_string(480); - (*this)["WINDOW"]["FULLSCREEN WIDTH"] = std::to_string(1920); - (*this)["WINDOW"]["FULLSCREEN HEIGHT"] = std::to_string(1080); - (*this)["WINDOW"]["FULLSCREEN"] = std::to_string(false); - (*this)["WINDOW"]["GFX BACKEND"] = ""; - - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_CRIGHT)] = std::to_string(0x14D); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_CLEFT)] = std::to_string(0x14B); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_CDOWN)] = std::to_string(0x150); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_CUP)] = std::to_string(0x148); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_R)] = std::to_string(0x013); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_L)] = std::to_string(0x012); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_DRIGHT)] = std::to_string(0x023); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_DLEFT)] = std::to_string(0x021); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_DDOWN)] = std::to_string(0x022); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_DUP)] = std::to_string(0x014); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_START)] = std::to_string(0x039); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_Z)] = std::to_string(0x02C); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_B)] = std::to_string(0x02E); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_A)] = std::to_string(0x02D); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_STICKRIGHT)] = std::to_string(0x020); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_STICKLEFT)] = std::to_string(0x01E); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_STICKDOWN)] = std::to_string(0x01F); - (*this)["KEYBOARD CONTROLLER BINDING 1"][STR(BTN_STICKUP)] = std::to_string(0x011); - - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_CRIGHT)] = std::to_string(0x14D); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_CLEFT)] = std::to_string(0x14B); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_CDOWN)] = std::to_string(0x150); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_CUP)] = std::to_string(0x148); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_R)] = std::to_string(0x013); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_L)] = std::to_string(0x012); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_DRIGHT)] = std::to_string(0x023); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_DLEFT)] = std::to_string(0x021); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_DDOWN)] = std::to_string(0x022); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_DUP)] = std::to_string(0x014); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_START)] = std::to_string(0x039); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_Z)] = std::to_string(0x02C); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_B)] = std::to_string(0x02E); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_A)] = std::to_string(0x02D); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_STICKRIGHT)] = std::to_string(0x020); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_STICKLEFT)] = std::to_string(0x01E); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_STICKDOWN)] = std::to_string(0x01F); - (*this)["KEYBOARD CONTROLLER BINDING 2"][STR(BTN_STICKUP)] = std::to_string(0x011); - - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_CRIGHT)] = std::to_string(0x14D); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_CLEFT)] = std::to_string(0x14B); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_CDOWN)] = std::to_string(0x150); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_CUP)] = std::to_string(0x148); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_R)] = std::to_string(0x013); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_L)] = std::to_string(0x012); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_DRIGHT)] = std::to_string(0x023); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_DLEFT)] = std::to_string(0x021); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_DDOWN)] = std::to_string(0x022); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_DUP)] = std::to_string(0x014); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_START)] = std::to_string(0x039); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_Z)] = std::to_string(0x02C); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_B)] = std::to_string(0x02E); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_A)] = std::to_string(0x02D); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_STICKRIGHT)] = std::to_string(0x020); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_STICKLEFT)] = std::to_string(0x01E); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_STICKDOWN)] = std::to_string(0x01F); - (*this)["KEYBOARD CONTROLLER BINDING 3"][STR(BTN_STICKUP)] = std::to_string(0x011); - - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_CRIGHT)] = std::to_string(0x14D); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_CLEFT)] = std::to_string(0x14B); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_CDOWN)] = std::to_string(0x150); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_CUP)] = std::to_string(0x148); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_R)] = std::to_string(0x013); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_L)] = std::to_string(0x012); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_DRIGHT)] = std::to_string(0x023); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_DLEFT)] = std::to_string(0x021); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_DDOWN)] = std::to_string(0x022); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_DUP)] = std::to_string(0x014); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_START)] = std::to_string(0x039); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_Z)] = std::to_string(0x02C); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_B)] = std::to_string(0x02E); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_A)] = std::to_string(0x02D); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_STICKRIGHT)] = std::to_string(0x020); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_STICKLEFT)] = std::to_string(0x01E); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_STICKDOWN)] = std::to_string(0x01F); - (*this)["KEYBOARD CONTROLLER BINDING 4"][STR(BTN_STICKUP)] = std::to_string(0x011); - - (*this)["ENHANCEMENT SETTINGS"]["TEXT_SPEED"] = "1"; - - (*this)["SDL CONTROLLER 1"]["GUID"] = ""; - (*this)["SDL CONTROLLER 2"]["GUID"] = ""; - (*this)["SDL CONTROLLER 3"]["GUID"] = ""; - (*this)["SDL CONTROLLER 4"]["GUID"] = ""; - - return File.generate(Val); - } -} diff --git a/libultraship/libultraship/ConfigFile.h b/libultraship/libultraship/ConfigFile.h deleted file mode 100644 index b94e22f88..000000000 --- a/libultraship/libultraship/ConfigFile.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CONFIG_FILE_H -#define CONFIG_FILE_H - -#pragma once - -#include -#include -#include "Lib/mINI/src/mini/ini.h" -#include "UltraController.h" -#include "LUSMacros.h" - -namespace Ship { - class GlobalCtx2; - - class ConfigFile { - public: - ConfigFile(std::shared_ptr Context, const std::string& Path); - ~ConfigFile(); - - bool Save(); - - // Expose the ini values. - mINI::INIMap& operator[](const std::string& Section); - mINI::INIMap get(const std::string& Section); - bool has(const std::string& Section); - bool remove(const std::string& Section); - void clear(); - std::size_t size() const; - std::shared_ptr GetContext() { return Context.lock(); } - - protected: - bool CreateDefaultConfig(); - - private: - mINI::INIStructure Val; - std::weak_ptr Context; - std::string Path; - mINI::INIFile File; - }; -} - -#endif diff --git a/libultraship/libultraship/ControlDeck.cpp b/libultraship/libultraship/ControlDeck.cpp new file mode 100644 index 000000000..2fbf3d225 --- /dev/null +++ b/libultraship/libultraship/ControlDeck.cpp @@ -0,0 +1,155 @@ +#include "ControlDeck.h" + +#include "Window.h" +#include "Controller.h" +#include "DisconnectedController.h" +#include "KeyboardController.h" +#include "SDLController.h" +#include + +uint8_t* controllerBits; + +void Ship::ControlDeck::Init(uint8_t* bits) { + ScanPhysicalDevices(); + controllerBits = bits; +} + +void Ship::ControlDeck::ScanPhysicalDevices() { + + virtualDevices.clear(); + physicalDevices.clear(); + + for (int i = 0; i < SDL_NumJoysticks(); i++) { + if (SDL_IsGameController(i)) { + auto sdl = std::make_shared(i); + sdl->Open(); + physicalDevices.push_back(sdl); + } + } + + physicalDevices.push_back(std::make_shared()); + physicalDevices.push_back(std::make_shared()); + + for (const auto& device : physicalDevices) { + for (int i = 0; i < MAXCONTROLLERS; i++) { + device->CreateDefaultBinding(i); + } + } + + for (int i = 0; i < MAXCONTROLLERS; i++) { + virtualDevices.push_back(i == 0 ? 0 : static_cast(physicalDevices.size()) - 1); + } + + LoadControllerSettings(); +} + +void Ship::ControlDeck::SetPhysicalDevice(int slot, int deviceSlot) { + const std::shared_ptr backend = physicalDevices[deviceSlot]; + virtualDevices[slot] = deviceSlot; + *controllerBits |= (backend->Connected()) << slot; +} + +void Ship::ControlDeck::WriteToPad(OSContPad* pad) const { + for (size_t i = 0; i < virtualDevices.size(); i++) { + physicalDevices[virtualDevices[i]]->Read(&pad[i], i); + } +} + +#define NESTED(key, ...) StringHelper::Sprintf("Controllers.%s.Slot_%d." key, device->GetGuid().c_str(), slot, __VA_ARGS__) + +void Ship::ControlDeck::LoadControllerSettings() { + std::shared_ptr Config = GlobalCtx2::GetInstance()->GetConfig(); + + for (auto const& val : Config->rjson["Controllers"]["Deck"].items()) { + int slot = std::stoi(val.key().substr(5)); + + for (size_t dev = 0; dev < physicalDevices.size(); dev++) { + std::string guid = physicalDevices[dev]->GetGuid(); + if(guid != val.value()) continue; + + virtualDevices[slot] = dev; + } + } + + for (size_t i = 0; i < virtualDevices.size(); i++) { + std::shared_ptr backend = physicalDevices[virtualDevices[i]]; + Config->setString(StringHelper::Sprintf("Controllers.Deck.Slot_%d", (int)i), backend->GetGuid()); + } + + for (const auto& device : physicalDevices) { + + std::string guid = device->GetGuid(); + + for (int slot = 0; slot < MAXCONTROLLERS; slot++) { + + if (!(Config->rjson["Controllers"].contains(guid) && Config->rjson["Controllers"][guid].contains(StringHelper::Sprintf("Slot_%d", slot)))) continue; + + auto& profile = device->profiles[slot]; + auto rawProfile = Config->rjson["Controllers"][guid][StringHelper::Sprintf("Slot_%d", slot)]; + + profile.Mappings.clear(); + profile.Thresholds.clear(); + profile.GyroThresholds.clear(); + profile.UseRumble = Config->getBool(NESTED("Rumble.Enabled", "")); + profile.RumbleStrength = Config->getBool(NESTED("Rumble.Strength", "")); + profile.UseGyro = Config->getBool(NESTED("Gyro.Enabled", "")); + + for (auto const& val : rawProfile["Gyro"]["Thresholds"].items()) { + profile.GyroThresholds[std::stoi(val.key())] = val.value(); + } + + for (auto const& val : rawProfile["Thresholds"].items()) { + profile.Thresholds[static_cast(std::stoi(val.key()))] = val.value(); + } + + for (auto const& val : rawProfile["Mappings"].items()) { + device->SetButtonMapping(slot, std::stoi(val.key().substr(4)), val.value()); + } + } + } +} + +void Ship::ControlDeck::SaveControllerSettings() { + std::shared_ptr Config = GlobalCtx2::GetInstance()->GetConfig(); + + for (size_t i = 0; i < virtualDevices.size(); i++) { + std::shared_ptr backend = physicalDevices[virtualDevices[i]]; + Config->setString(StringHelper::Sprintf("Controllers.Deck.Slot_%d", (int)i), backend->GetGuid()); + } + + for (const auto& device : physicalDevices) { + + int slot = 0; + std::string guid = device->GetGuid(); + + for (const auto& profile : device->profiles) { + + if (!device->Connected()) continue; + + auto rawProfile = Config->rjson["Controllers"][guid][StringHelper::Sprintf("Slot_%d", slot)]; + Config->setBool(NESTED("Rumble.Enabled", ""), profile.UseRumble); + Config->setFloat(NESTED("Rumble.Strength", ""), profile.RumbleStrength); + Config->setBool(NESTED("Gyro.Enabled", ""), profile.UseGyro); + + for (auto const& val : rawProfile["Mappings"].items()) { + Config->setInt(NESTED("Mappings.%s", val.key().c_str()), -1); + } + + for (auto const& [key, val] : profile.GyroThresholds) { + Config->setInt(NESTED("Gyro.Thresholds.%d", key), val); + } + + for (auto const& [key, val] : profile.Thresholds) { + Config->setInt(NESTED("Thresholds.%d", key), val); + } + + for (auto const& [key, val] : profile.Mappings) { + Config->setInt(NESTED("Mappings.BTN_%d", val), key); + } + + slot++; + } + } + + Config->save(); +} \ No newline at end of file diff --git a/libultraship/libultraship/ControlDeck.h b/libultraship/libultraship/ControlDeck.h new file mode 100644 index 000000000..fbbaca7ab --- /dev/null +++ b/libultraship/libultraship/ControlDeck.h @@ -0,0 +1,20 @@ +#pragma once + +#include "Controller.h" +#include +#include + +namespace Ship { + + class ControlDeck { + public: + std::vector virtualDevices; + std::vector> physicalDevices = {}; + void Init(uint8_t* controllerBits); + void ScanPhysicalDevices(); + void WriteToPad(OSContPad* pad) const; + void LoadControllerSettings(); + void SaveControllerSettings(); + void SetPhysicalDevice(int slot, int deviceSlot); + }; +} diff --git a/libultraship/libultraship/Controller.cpp b/libultraship/libultraship/Controller.cpp index 7cad7f178..8c0994a85 100644 --- a/libultraship/libultraship/Controller.cpp +++ b/libultraship/libultraship/Controller.cpp @@ -1,8 +1,6 @@ #include "Controller.h" -#include "GlobalCtx2.h" -#include "stox.h" #include -#include +#include #if __APPLE__ #include #else @@ -10,29 +8,29 @@ #endif namespace Ship { - Controller::Controller(int32_t dwControllerNumber) : dwControllerNumber(dwControllerNumber) { - dwPressedButtons = 0; - wStickX = 0; - wStickY = 0; - wGyroX = 0; - wGyroY = 0; + + Controller::Controller() : isRumbling(false), wStickX(0), wStickY(0), wGyroX(0), wGyroY(0), dwPressedButtons(0){ Attachment = nullptr; + profiles.resize(MAXCONTROLLERS); + for(int slot = 0; slot < MAXCONTROLLERS; slot++) { + dwPressedButtons.push_back(0); + } } - void Controller::Read(OSContPad* pad) { - ReadFromSource(); + void Controller::Read(OSContPad* pad, int32_t slot) { + ReadFromSource(slot); SDL_PumpEvents(); // Button Inputs - pad->button |= dwPressedButtons & 0xFFFF; + pad->button |= dwPressedButtons[slot] & 0xFFFF; // Stick Inputs if (pad->stick_x == 0) { - if (dwPressedButtons & BTN_STICKLEFT) { + if (dwPressedButtons[slot] & BTN_STICKLEFT) { pad->stick_x = -128; } - else if (dwPressedButtons & BTN_STICKRIGHT) { + else if (dwPressedButtons[slot] & BTN_STICKRIGHT) { pad->stick_x = 127; } else { @@ -41,10 +39,10 @@ namespace Ship { } if (pad->stick_y == 0) { - if (dwPressedButtons & BTN_STICKDOWN) { + if (dwPressedButtons[slot] & BTN_STICKDOWN) { pad->stick_y = -128; } - else if (dwPressedButtons & BTN_STICKUP) { + else if (dwPressedButtons[slot] & BTN_STICKUP) { pad->stick_y = 127; } else { @@ -52,60 +50,38 @@ namespace Ship { } } + // Stick Inputs + if (pad->cam_x == 0) { + if (dwPressedButtons[slot] & BTN_VSTICKLEFT) { + pad->cam_x = -128 * 10.0f; + } + else if (dwPressedButtons[slot] & BTN_VSTICKRIGHT) { + pad->cam_x = 127 * 10.0f; + } + else { + pad->cam_x = wCamX; + } + } + if (pad->cam_y == 0) { + if (dwPressedButtons[slot] & BTN_VSTICKDOWN) { + pad->cam_y = -128 * 10.0f; + } + else if (dwPressedButtons[slot] & BTN_VSTICKUP) { + pad->cam_y = 127 * 10.0f; + } + else { + pad->cam_y = wCamY; + } + } + // Gyro pad->gyro_x = wGyroX; pad->gyro_y = wGyroY; - - // Right Stick - pad->cam_x = wCamX; - pad->cam_y = wCamY; } - void Controller::SetButtonMapping(const std::string& szButtonName, int32_t dwScancode) { - // Update the config value. - std::string ConfSection = GetBindingConfSection(); - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); - Conf[ConfSection][szButtonName] = dwScancode; - - // Reload the button mapping from Config - LoadBinding(); - } - - void Controller::LoadBinding() { - std::string ConfSection = GetBindingConfSection(); - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); - - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CRIGHT)])] = BTN_CRIGHT; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CLEFT)])] = BTN_CLEFT; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CDOWN)])] = BTN_CDOWN; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CUP)])] = BTN_CUP; - //ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CRIGHT + "_2")])] = BTN_CRIGHT; - //ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CLEFT + "_2")])] = BTN_CLEFT; - //ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CDOWN + "_2")])] = BTN_CDOWN; - //ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_CUP + "_2")])] = BTN_CUP; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_R)])] = BTN_R; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_L)])] = BTN_L; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_DRIGHT)])] = BTN_DRIGHT; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_DLEFT)])] = BTN_DLEFT; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_DDOWN)])] = BTN_DDOWN; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_DUP)])] = BTN_DUP; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_START)])] = BTN_START; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_Z)])] = BTN_Z; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_B)])] = BTN_B; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_A)])] = BTN_A; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_STICKRIGHT)])] = BTN_STICKRIGHT; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_STICKLEFT)])] = BTN_STICKLEFT; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_STICKDOWN)])] = BTN_STICKDOWN; - ButtonMapping[Ship::stoi(Conf[ConfSection][STR(BTN_STICKUP)])] = BTN_STICKUP; - } - - std::string Controller::GetConfSection() { - return GetControllerType() + " CONTROLLER " + std::to_string(GetControllerNumber() + 1); - } - - std::string Controller::GetBindingConfSection() { - return GetControllerType() + " CONTROLLER BINDING " + std::to_string(GetControllerNumber() + 1); + void Controller::SetButtonMapping(int slot, int32_t n64Button, int32_t dwScancode) { + std::map& Mappings = profiles[slot].Mappings; + std::erase_if(Mappings, [n64Button](const std::pair& bin) { return bin.second == n64Button; }); + Mappings[dwScancode] = n64Button; } } diff --git a/libultraship/libultraship/Controller.h b/libultraship/libultraship/Controller.h index d03d70e8a..ffbeeaabf 100644 --- a/libultraship/libultraship/Controller.h +++ b/libultraship/libultraship/Controller.h @@ -1,52 +1,82 @@ #pragma once -#include #include +#include #include -#include #include "stdint.h" #include "UltraController.h" #include "ControllerAttachment.h" +#include +#include #define EXTENDED_SCANCODE_BIT (1 << 8) #define AXIS_SCANCODE_BIT (1 << 9) namespace Ship { + + enum ControllerThresholds { + LEFT_STICK = 1, + RIGHT_STICK = 2, + LEFT_TRIGGER = 3, + RIGHT_TRIGGER = 4, + DRIFT_X = 5, + DRIFT_Y = 6, + SENSITIVITY = 7, + GYRO_SENSITIVITY = 8 + }; + + struct DeviceProfile { + bool UseRumble = false; + bool UseGyro = false; + float RumbleStrength = 1.0f; + std::unordered_map Thresholds; + std::unordered_map GyroThresholds; + std::map Mappings; + }; + class Controller { public: - Controller(int32_t dwControllerNumber); - - void Read(OSContPad* pad); - virtual void ReadFromSource() = 0; - virtual void WriteToSource(ControllerCallback* controller) = 0; + virtual ~Controller() = default; + Controller(); + void Read(OSContPad* pad, int32_t slot); + virtual void ReadFromSource(int32_t slot) = 0; + virtual void WriteToSource(int32_t slot, ControllerCallback* controller) = 0; virtual bool Connected() const = 0; virtual bool CanRumble() const = 0; + virtual bool CanGyro() const = 0; + virtual void CreateDefaultBinding(int32_t slot) = 0; bool isRumbling; + std::vector profiles; - void SetButtonMapping(const std::string& szButtonName, int32_t dwScancode); + virtual void ClearRawPress() = 0; + virtual int32_t ReadRawPress() = 0; + void SetButtonMapping(int slot, int32_t n64Button, int32_t dwScancode); std::shared_ptr GetAttachment() { return Attachment; } - int32_t GetControllerNumber() { return dwControllerNumber; } - virtual bool HasPadConf() const = 0; - virtual std::optional GetPadConfSection() = 0; + std::string GetGuid() { return GUID; } + virtual const char* GetButtonName(int slot, int n64Button) = 0; + virtual const char* GetControllerName() = 0; - protected: - int32_t dwPressedButtons; - std::map ButtonMapping; int8_t wStickX; int8_t wStickY; float wGyroX; float wGyroY; - float wCamX; - float wCamY; + float wCamX; + float wCamY; + + protected: + std::vector dwPressedButtons; + std::string GUID; - virtual std::string GetControllerType() = 0; - virtual std::string GetConfSection() = 0; - virtual std::string GetBindingConfSection() = 0; void LoadBinding(); private: std::shared_ptr Attachment; - int32_t dwControllerNumber; }; + + struct ControllerEntry { + uint8_t* controllerBits; + Controller* entryIO; + }; + } diff --git a/libultraship/libultraship/ControllerHud.cpp b/libultraship/libultraship/ControllerHud.cpp new file mode 100644 index 000000000..e40990b0d --- /dev/null +++ b/libultraship/libultraship/ControllerHud.cpp @@ -0,0 +1,277 @@ +#include "InputEditor.h" +#include "Controller.h" +#include "Window.h" +#include "Lib/ImGui/imgui.h" +#include "ImGuiImpl.h" +#include "Utils/StringHelper.h" +#include "Lib/ImGui/imgui_internal.h" + +namespace Ship { + + extern "C" uint8_t __enableGameInput; + #define SEPARATION() ImGui::Dummy(ImVec2(0, 5)) + + void InputEditor::Init() { + BtnReading = -1; + } + + std::shared_ptr GetControllerPerSlot(int slot) { + const std::vector vDevices = Window::ControllerApi->virtualDevices; + return Window::ControllerApi->physicalDevices[vDevices[slot]]; + } + + void InputEditor::DrawButton(const char* label, int n64Btn) { + const std::shared_ptr backend = GetControllerPerSlot(CurrentPort); + + float size = 40; + bool readingMode = BtnReading == n64Btn; + bool disabled = BtnReading != -1 && !readingMode || !backend->Connected(); + ImVec2 len = ImGui::CalcTextSize(label); + ImVec2 pos = ImGui::GetCursorPos(); + ImGui::SetCursorPosY(pos.y + len.y / 4); + ImGui::SetCursorPosX(pos.x + abs(len.x - size)); + ImGui::Text("%s", label); + ImGui::SameLine(); + ImGui::SetCursorPosY(pos.y); + + if(disabled) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + + if(readingMode) { + const int32_t btn = backend->ReadRawPress(); + + if(btn != -1) { + backend->SetButtonMapping(CurrentPort, n64Btn, btn); + BtnReading = -1; + } + } + + const char* BtnName = backend->GetButtonName(CurrentPort, n64Btn); + + if (ImGui::Button(StringHelper::Sprintf("%s##HBTNID_%d", readingMode ? "Press a Key..." : BtnName, n64Btn).c_str())) { + BtnReading = n64Btn; + backend->ClearRawPress(); + } + + if(disabled) { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + } + } + + void InputEditor::DrawVirtualStick(const char* label, ImVec2 stick) { + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 5, ImGui::GetCursorPos().y)); + ImGui::BeginChild(label, ImVec2(68, 75), false); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 p = ImGui::GetCursorScreenPos(); + + float sz = 45.0f; + float rad = sz * 0.5f; + ImVec2 pos = ImVec2(p.x + sz * 0.5f + 12, p.y + sz * 0.5f + 11); + + float stickX = (stick.x / 83.0f) * (rad * 0.5f); + float stickY = -(stick.y / 83.0f) * (rad * 0.5f); + + ImVec4 rect = ImVec4(p.x + 2, p.y + 2, 65, 65); + draw_list->AddRect(ImVec2(rect.x, rect.y), ImVec2(rect.x + rect.z, rect.y + rect.w), ImColor(100, 100, 100, 255), 0.0f, 0, 1.5f); + draw_list->AddCircleFilled(pos, rad, ImColor(130, 130, 130, 255), 8); + draw_list->AddCircleFilled(ImVec2(pos.x + stickX, pos.y + stickY), 5, ImColor(15, 15, 15, 255), 7); + ImGui::EndChild(); + } + + void InputEditor::DrawControllerSchema() { + + const std::vector vDevices = Window::ControllerApi->virtualDevices; + const std::vector> devices = Window::ControllerApi->physicalDevices; + + std::shared_ptr Backend = devices[vDevices[CurrentPort]]; + DeviceProfile& profile =Backend->profiles[CurrentPort]; + float sensitivity = profile.Thresholds[SENSITIVITY]; + bool IsKeyboard = Backend->GetGuid() == "Keyboard" || !Backend->Connected(); + const char* ControllerName = Backend->GetControllerName(); + + if (ControllerName != nullptr && ImGui::BeginCombo("##ControllerEntries", ControllerName)) { + for (uint8_t i = 0; i < devices.size(); i++) { + if (ImGui::Selectable(devices[i]->GetControllerName(), i == vDevices[CurrentPort])) { + Window::ControllerApi->SetPhysicalDevice(CurrentPort, i); + } + } + ImGui::EndCombo(); + } + + ImGui::SameLine(); + + if(ImGui::Button("Refresh")) { + Window::ControllerApi->ScanPhysicalDevices(); + } + + SohImGui::BeginGroupPanel("Buttons", ImVec2(150, 20)); + DrawButton("A", BTN_A); + DrawButton("B", BTN_B); + DrawButton("L", BTN_L); + DrawButton("R", BTN_R); + DrawButton("Z", BTN_Z); + DrawButton("START", BTN_START); + SEPARATION(); + SohImGui::EndGroupPanel(IsKeyboard ? 7.0f : 48.0f); + ImGui::SameLine(); + SohImGui::BeginGroupPanel("Digital Pad", ImVec2(150, 20)); + DrawButton("Up", BTN_DUP); + DrawButton("Down", BTN_DDOWN); + DrawButton("Left", BTN_DLEFT); + DrawButton("Right", BTN_DRIGHT); + SEPARATION(); + SohImGui::EndGroupPanel(IsKeyboard ? 53.0f : 94.0f); + ImGui::SameLine(); + SohImGui::BeginGroupPanel("Analog Stick", ImVec2(150, 20)); + DrawButton("Up", BTN_STICKUP); + DrawButton("Down", BTN_STICKDOWN); + DrawButton("Left", BTN_STICKLEFT); + DrawButton("Right", BTN_STICKRIGHT); + + if (!IsKeyboard) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); + DrawVirtualStick("##MainVirtualStick", ImVec2(Backend->wStickX, Backend->wStickY)); + ImGui::SameLine(); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + + ImGui::BeginChild("##MSInput", ImVec2(90, 50), false); + ImGui::Text("Deadzone"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##MDZone", &profile.Thresholds[LEFT_STICK]); + ImGui::PopItemWidth(); + ImGui::EndChild(); + } else { + ImGui::Dummy(ImVec2(0, 6)); + } + SohImGui::EndGroupPanel(IsKeyboard ? 52.0f : 24.0f); + ImGui::SameLine(); + + if (!IsKeyboard) { + ImGui::SameLine(); + SohImGui::BeginGroupPanel("Camera Stick", ImVec2(150, 20)); + DrawButton("Up", BTN_VSTICKUP); + DrawButton("Down", BTN_VSTICKDOWN); + DrawButton("Left", BTN_VSTICKLEFT); + DrawButton("Right", BTN_VSTICKRIGHT); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); + DrawVirtualStick("##CameraVirtualStick", ImVec2(Backend->wCamX / sensitivity, Backend->wCamY / sensitivity)); + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + ImGui::BeginChild("##CSInput", ImVec2(90, 85), false); + ImGui::Text("Deadzone"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##MDZone", &profile.Thresholds[RIGHT_STICK]); + ImGui::PopItemWidth(); + ImGui::Text("Sensitivity"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##MSensitivity", &profile.Thresholds[SENSITIVITY]); + ImGui::PopItemWidth(); + ImGui::EndChild(); + SohImGui::EndGroupPanel(14.0f); + } + + if(Backend->CanGyro()) { + ImGui::SameLine(); + + SohImGui::BeginGroupPanel("Gyro Options", ImVec2(175, 20)); + float cursorX = ImGui::GetCursorPosX() + 5; + ImGui::SetCursorPosX(cursorX); + ImGui::Checkbox("Enable Gyro", &profile.UseGyro); + ImGui::SetCursorPosX(cursorX); + ImGui::Text("Gyro Sensitivity: %d%%", profile.Thresholds[GYRO_SENSITIVITY]); + ImGui::PushItemWidth(135.0f); + ImGui::SetCursorPosX(cursorX); + ImGui::SliderInt("##GSensitivity", &profile.Thresholds[GYRO_SENSITIVITY], 0, 100, ""); + ImGui::PopItemWidth(); + ImGui::Dummy(ImVec2(0, 1)); + ImGui::SetCursorPosX(cursorX); + if (ImGui::Button("Recalibrate Gyro##RGyro")) { + profile.Thresholds[DRIFT_X] = 0; + profile.Thresholds[DRIFT_Y] = 0; + } + ImGui::SetCursorPosX(cursorX); + DrawVirtualStick("##GyroPreview", ImVec2(Backend->wGyroX, Backend->wGyroY)); + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + ImGui::BeginChild("##GyInput", ImVec2(90, 85), false); + ImGui::Text("Drift X"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##GDriftX", &profile.Thresholds[DRIFT_X]); + ImGui::PopItemWidth(); + ImGui::Text("Drift Y"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##GDriftY", &profile.Thresholds[DRIFT_Y]); + ImGui::PopItemWidth(); + ImGui::EndChild(); + SohImGui::EndGroupPanel(14.0f); + } + + ImGui::SameLine(); + + const ImVec2 cursor = ImGui::GetCursorPos(); + + SohImGui::BeginGroupPanel("C-Buttons", ImVec2(158, 20)); + DrawButton("Up", BTN_CUP); + DrawButton("Down", BTN_CDOWN); + DrawButton("Left", BTN_CLEFT); + DrawButton("Right", BTN_CRIGHT); + ImGui::Dummy(ImVec2(0, 5)); + SohImGui::EndGroupPanel(); + + ImGui::SetCursorPosX(cursor.x); + ImGui::SetCursorPosY(cursor.y + 120); + SohImGui::BeginGroupPanel("Options", ImVec2(158, 20)); + float cursorX = ImGui::GetCursorPosX() + 5; + ImGui::SetCursorPosX(cursorX); + ImGui::Checkbox("Rumble Enabled", &profile.UseRumble); + if (Backend->CanRumble()) { + ImGui::SetCursorPosX(cursorX); + ImGui::Text("Rumble Force: %d%%", static_cast(100 * profile.RumbleStrength)); + ImGui::SetCursorPosX(cursorX); + ImGui::PushItemWidth(135.0f); + ImGui::SliderFloat("##RStrength", &profile.RumbleStrength, 0, 1.0f, ""); + ImGui::PopItemWidth(); + } + ImGui::Dummy(ImVec2(0, 5)); + SohImGui::EndGroupPanel(IsKeyboard ? 0.0f : 2.0f); + } + + void InputEditor::DrawHud() { + + __enableGameInput = true; + + if (!this->Opened) { + BtnReading = -1; + return; + } + + ImGui::SetNextWindowSizeConstraints(ImVec2(641, 250), ImVec2(1200, 290)); + //OTRTODO: Disable this stupid workaround ( ReadRawPress() only works when the window is on the main viewport ) + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + ImGui::Begin("Controller Configuration", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize); + + ImGui::BeginTabBar("##Controllers"); + + for (int i = 0; i < 4; i++) { + if (ImGui::BeginTabItem(StringHelper::Sprintf("Port %d", i + 1).c_str())) { + CurrentPort = i; + ImGui::EndTabItem(); + } + } + + ImGui::EndTabBar(); + + // Draw current cfg + + DrawControllerSchema(); + + ImGui::End(); + } +} diff --git a/libultraship/libultraship/Cvar.cpp b/libultraship/libultraship/Cvar.cpp index 55fc70793..231308fe7 100644 --- a/libultraship/libultraship/Cvar.cpp +++ b/libultraship/libultraship/Cvar.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "imgui_internal.h" std::map, std::less<>> cvars; @@ -70,7 +71,7 @@ extern "C" void CVar_SetString(const char* name, const char* value) { cvar = std::make_unique(); } cvar->type = CVAR_TYPE_STRING; - cvar->value.valueStr = value; + cvar->value.valueStr = ImStrdup(value); } extern "C" void CVar_RegisterS32(const char* name, s32 defaultValue) { diff --git a/libultraship/libultraship/DisconnectedController.h b/libultraship/libultraship/DisconnectedController.h new file mode 100644 index 000000000..9cbc2b99c --- /dev/null +++ b/libultraship/libultraship/DisconnectedController.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +#include "Controller.h" + +class DisconnectedController final : public Ship::Controller { +public: + DisconnectedController() { + GUID = "Disconnected"; + } + + std::map, int32_t> ReadButtonPress(); + void ReadFromSource(int32_t slot) override {} + const char* GetControllerName() override { return "Disconnected"; } + const char* GetButtonName(int slot, int n64Button) override { return "None"; } + void WriteToSource(int32_t slot, ControllerCallback* controller) override { } + bool Connected() const override { return false; } + bool CanRumble() const override { return false; } + bool CanGyro() const override { return false; } + + void ClearRawPress() override {} + int32_t ReadRawPress() override { return -1; } + bool HasPadConf() const { return true; } + std::optional GetPadConfSection() { return "Unk"; } + void CreateDefaultBinding(int32_t slot) override {} +protected: + std::string GetControllerType() { return "Unk"; } + std::string GetConfSection() { return "Unk"; } + std::string GetBindingConfSection() { return "Unk"; } +}; diff --git a/libultraship/libultraship/GameSettings.cpp b/libultraship/libultraship/GameSettings.cpp index 0cfbbd21a..e3f95ad76 100644 --- a/libultraship/libultraship/GameSettings.cpp +++ b/libultraship/libultraship/GameSettings.cpp @@ -7,7 +7,6 @@ #include #include -#include "ConfigFile.h" #include "Cvar.h" #include "GlobalCtx2.h" #include "ImGuiImpl.h" @@ -33,18 +32,6 @@ namespace Game { Audio_SetGameVolume(SEQ_SFX, CVar_GetFloat("gFanfareVolume", 1)); } - void LoadPadSettings() { - const std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf; - - for (const auto& [i, controllers] : Ship::Window::Controllers) { - for (const auto& controller : controllers) { - if (auto padConfSection = controller->GetPadConfSection()) { - } - } - } - } - void LoadSettings() { DebugConsole_LoadCVars(); } @@ -58,6 +45,7 @@ namespace Game { ModInternal::RegisterHook([] { gfx_get_current_rendering_api()->set_texture_filter((FilteringMode) CVar_GetS32("gTextureFilter", FILTER_THREE_POINT)); SohImGui::console->opened = CVar_GetS32("gConsoleEnabled", 0); + SohImGui::controller->Opened = CVar_GetS32("gControllerConfigurationEnabled", 0); UpdateAudio(); }); } diff --git a/libultraship/libultraship/GlobalCtx2.cpp b/libultraship/libultraship/GlobalCtx2.cpp index 54e64e0ce..329644192 100644 --- a/libultraship/libultraship/GlobalCtx2.cpp +++ b/libultraship/libultraship/GlobalCtx2.cpp @@ -15,7 +15,6 @@ namespace Ship { std::weak_ptr GlobalCtx2::Context; ModManager* INSTANCE; - std::shared_ptr GlobalCtx2::GetInstance() { return Context.lock(); } @@ -49,7 +48,7 @@ namespace Ship { return GlobalCtx2::GetAppDirectoryPath() + "/" + path; } - GlobalCtx2::GlobalCtx2(const std::string& Name) : Name(Name), MainPath(""), PatchesPath("") { + GlobalCtx2::GlobalCtx2(std::string Name) : Name(std::move(Name)) { } @@ -60,22 +59,19 @@ namespace Ship { void GlobalCtx2::InitWindow() { InitLogging(); - Config = std::make_shared(GlobalCtx2::GetInstance(), GetPathRelativeToAppDirectory("shipofharkinian.ini")); - MainPath = (*Config)["ARCHIVE"]["Main Archive"]; - if (MainPath.empty()) { - MainPath = GetPathRelativeToAppDirectory("oot.otr"); - } - PatchesPath = (*Config)["ARCHIVE"]["Patches Directory"]; - if (PatchesPath.empty()) { - PatchesPath = GetAppDirectoryPath() + "/"; - } - ResMan = std::make_shared(GlobalCtx2::GetInstance(), MainPath, PatchesPath); - Win = std::make_shared(GlobalCtx2::GetInstance()); + Config = std::make_shared(GetPathRelativeToAppDirectory("shipofharkinian.json")); + Config->reload(); + + MainPath = Config->getString("Game.Main Archive", GetPathRelativeToAppDirectory("oot.otr")); + PatchesPath = Config->getString("Game.Patches Archive", GetAppDirectoryPath() + "/mods"); + + ResMan = std::make_shared(GetInstance(), MainPath, PatchesPath); + Win = std::make_shared(GetInstance()); if (!ResMan->DidLoadSuccessfully()) { #ifdef _WIN32 - MessageBox(NULL, L"Main OTR file not found!", L"Uh oh", MB_OK); + MessageBox(nullptr, L"Main OTR file not found!", L"Uh oh", MB_OK); #else SPDLOG_ERROR("Main OTR file not found!"); #endif @@ -109,7 +105,7 @@ namespace Ship { } } - void GlobalCtx2::WriteSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size) { + void GlobalCtx2::WriteSaveFile(const std::filesystem::path& savePath, const uintptr_t addr, void* dramAddr, const size_t size) { std::ofstream saveFile = std::ofstream(savePath, std::fstream::in | std::fstream::out | std::fstream::binary); saveFile.seekp(addr); saveFile.write((char*)dramAddr, size); @@ -129,4 +125,4 @@ namespace Ship { saveFile.close(); } -} \ No newline at end of file +} diff --git a/libultraship/libultraship/GlobalCtx2.h b/libultraship/libultraship/GlobalCtx2.h index 5736e2b66..21402070d 100644 --- a/libultraship/libultraship/GlobalCtx2.h +++ b/libultraship/libultraship/GlobalCtx2.h @@ -6,8 +6,9 @@ #ifdef __cplusplus #include #include +#include #include "spdlog/spdlog.h" -#include "ConfigFile.h" +#include "Lib/Mercury/Mercury.h" namespace Ship { class ResourceMgr; @@ -22,15 +23,15 @@ namespace Ship { std::shared_ptr GetWindow() { return Win; } std::shared_ptr GetResourceManager() { return ResMan; } std::shared_ptr GetLogger() { return Logger; } - std::shared_ptr GetConfig() { return Config; } + std::shared_ptr GetConfig() { return Config; } static std::string GetAppDirectoryPath(); static std::string GetPathRelativeToAppDirectory(const char* path); - void WriteSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size); + void WriteSaveFile(const std::filesystem::path& savePath, uintptr_t addr, void* dramAddr, size_t size); void ReadSaveFile(std::filesystem::path savePath, uintptr_t addr, void* dramAddr, size_t size); - GlobalCtx2(const std::string& Name); + GlobalCtx2(std::string Name); ~GlobalCtx2(); protected: @@ -41,7 +42,7 @@ namespace Ship { static std::weak_ptr Context; std::shared_ptr Logger; std::shared_ptr Win; - std::shared_ptr Config; // Config needs to be after the Window because we call the Window during it's destructor. + std::shared_ptr Config; // Config needs to be after the Window because we call the Window during it's destructor. std::shared_ptr ResMan; std::string Name; std::string MainPath; diff --git a/libultraship/libultraship/Hooks.h b/libultraship/libultraship/Hooks.h index b5dec8159..411cffe42 100644 --- a/libultraship/libultraship/Hooks.h +++ b/libultraship/libultraship/Hooks.h @@ -5,6 +5,7 @@ #include #include "UltraController.h" +#include "Controller.h" #define DEFINE_HOOK(name, type) struct name { typedef std::function fn; } @@ -28,12 +29,11 @@ namespace ModInternal { } DEFINE_HOOK(ControllerRead, void(OSContPad* cont_pad)); - + DEFINE_HOOK(ControllerRawInput, void(Ship::Controller* backend, uint32_t raw)); DEFINE_HOOK(AudioInit, void()); - DEFINE_HOOK(LoadTexture, void(const char* path, uint8_t** texture)); - DEFINE_HOOK(GfxInit, void()); + DEFINE_HOOK(ExitGame, void()); } diff --git a/libultraship/libultraship/ImGuiImpl.cpp b/libultraship/libultraship/ImGuiImpl.cpp index 69d05a74d..dd172118d 100644 --- a/libultraship/libultraship/ImGuiImpl.cpp +++ b/libultraship/libultraship/ImGuiImpl.cpp @@ -12,6 +12,7 @@ #include "GameSettings.h" #include "Console.h" #include "Hooks.h" +#define IMGUI_DEFINE_MATH_OPERATORS #include "Lib/ImGui/imgui_internal.h" #include "GlobalCtx2.h" #include "ResourceMgr.h" @@ -63,6 +64,8 @@ namespace SohImGui { ImGuiIO* io; Console* console = new Console; GameOverlay* overlay = new GameOverlay; + InputEditor* controller = new InputEditor; + static ImVector s_GroupPanelLabelStack; bool p_open = false; bool needs_save = false; @@ -326,6 +329,7 @@ namespace SohImGui { } console->Init(); overlay->Init(); + controller->Init(); ImGuiWMInit(); ImGuiBackendInit(); @@ -346,23 +350,17 @@ namespace SohImGui { LoadTexture("C-Down", "assets/ship_of_harkinian/buttons/CDown.png"); }); - for (const auto& [i, controllers] : Ship::Window::Controllers) - { - CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftX", i).c_str(), 0); - CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftY", i).c_str(), 0); - needs_save = true; - } - ModInternal::RegisterHook([](OSContPad* cont_pad) { pads = cont_pad; }); + Game::InitSettings(); CVar_SetS32("gRandoGenerating", 0); CVar_SetS32("gNewSeedGenerated", 0); CVar_SetS32("gNewFileDropped", 0); - CVar_SetString("gDroppedFile", ""); - Game::SaveSettings(); + CVar_SetString("gDroppedFile", "None"); + // Game::SaveSettings(); } void Update(EventImpl event) { @@ -511,7 +509,7 @@ namespace SohImGui { } void EnhancementCombo(const std::string& name, const char* cvarName, const std::vector& items, int defaultValue) { - + if (ImGui::BeginCombo(name.c_str(), items[static_cast(CVar_GetS32(cvarName, defaultValue))].c_str())) { for (int settingIndex = 0; settingIndex < (int) items.size(); settingIndex++) { if (ImGui::Selectable(items[settingIndex].c_str())) { @@ -691,36 +689,32 @@ namespace SohImGui { needs_save = true; GlobalCtx2::GetInstance()->GetWindow()->dwMenubar = menu_bar; ShowCursor(menu_bar, Dialogues::dMenubar); - + GlobalCtx2::GetInstance()->GetWindow()->GetControlDeck()->SaveControllerSettings(); if (CVar_GetS32("gControlNav", 0)) { if (CVar_GetS32("gOpenMenuBar", 0)) { io->ConfigFlags |=ImGuiConfigFlags_NavEnableGamepad | ImGuiConfigFlags_NavEnableKeyboard; + } else { + io->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; } - else - { - io->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; - } - } - else - { - io->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; + } else { + io->ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad; } } #if __APPLE__ if ((ImGui::IsKeyDown(ImGuiKey_LeftSuper) || - ImGui::IsKeyDown(ImGuiKey_RightSuper)) && + ImGui::IsKeyDown(ImGuiKey_RightSuper)) && ImGui::IsKeyPressed(ImGuiKey_R, false)) { console->Commands["reset"].handler(emptyArgs); } #else if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || - ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && + ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R, false)) { console->Commands["reset"].handler(emptyArgs); } #endif - + if (ImGui::BeginMenuBar()) { if (DefaultAssets.contains("Game_Icon")) { ImGui::SetCursorPos(ImVec2(5, 2.5f)); @@ -740,7 +734,7 @@ namespace SohImGui { console->Commands["reset"].handler(emptyArgs); } ImGui::EndMenu(); - } + } if (ImGui::BeginMenu("Audio")) { EnhancementSliderFloat("Master Volume: %d %%", "##Master_Vol", "gGameMasterVolume", 0.0f, 1.0f, "", 1.0f, true); @@ -758,6 +752,9 @@ namespace SohImGui { EnhancementCheckbox("Use Controller Navigation", "gControlNav"); Tooltip("Allows controller navigation of the menu bar\nD-pad to move between items, A to select, and X to grab focus on the menu bar"); + EnhancementCheckbox("Controller Configuration", "gControllerConfigurationEnabled"); + controller->Opened = CVar_GetS32("gControllerConfigurationEnabled", 0); + ImGui::Separator(); // TODO mutual exclusions -- There should be some system to prevent conclifting enhancements from being selected @@ -771,41 +768,9 @@ namespace SohImGui { EnhancementCheckbox("Show Inputs", "gInputEnabled"); Tooltip("Shows currently pressed inputs on the bottom right of the screen"); - EnhancementCheckbox("Rumble Enabled", "gRumbleEnabled"); EnhancementSliderFloat("Input Scale: %.1f", "##Input", "gInputScale", 1.0f, 3.0f, "", 1.0f, false); - Tooltip("Sets the on screen size of the displayed inputs from the Show Inputs setting"); - - ImGui::Separator(); - - for (const auto& [i, controllers] : Ship::Window::Controllers) - { - bool hasPad = std::find_if(controllers.begin(), controllers.end(), [](const auto& c) { - return c->HasPadConf() && c->Connected(); - }) != controllers.end(); - - if (!hasPad) continue; - - auto menuLabel = "Controller " + std::to_string(i + 1); - if (ImGui::BeginMenu(menuLabel.c_str())) - { - EnhancementSliderFloat("Gyro Sensitivity: %d %%", "##GYROSCOPE", StringHelper::Sprintf("gCont%i_GyroSensitivity", i).c_str(), 0.0f, 1.0f, "", 1.0f, true); - - if (ImGui::Button("Recalibrate Gyro")) - { - CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftX", i).c_str(), 0); - CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftY", i).c_str(), 0); - needs_save = true; - } - - ImGui::Separator(); - - EnhancementSliderFloat("Rumble Strength: %d %%", "##RUMBLE", StringHelper::Sprintf("gCont%i_RumbleStrength", i).c_str(), 0.0f, 1.0f, "", 1.0f, true); - - ImGui::EndMenu(); - } - ImGui::Separator(); - } + Tooltip("Sets the on screen size of the displayed inputs from the Show Inputs setting"); ImGui::EndMenu(); } @@ -933,7 +898,7 @@ namespace SohImGui { Tooltip("Disables random drops, except from the Goron Pot, Dampe, and bosses"); EnhancementCheckbox("No Heart Drops", "gNoHeartDrops"); Tooltip("Disables heart drops, but not heart placements, like from a Deku Scrub running off\nThis simulates Hero Mode from other games in the series"); - + if (ImGui::BeginMenu("Potion Values")) { EnhancementCheckbox("Change Red Potion Effect", "gRedPotionEffect"); @@ -942,7 +907,7 @@ namespace SohImGui { Tooltip("Changes the amount of health restored by Red Potions"); EnhancementCheckbox("Red Potion Percent Restore", "gRedPercentRestore"); Tooltip("Toggles from Red Potions restoring a fixed amount of health to a percent of the player's current max health"); - + EnhancementCheckbox("Change Green Potion Effect", "gGreenPotionEffect"); Tooltip("Enable the following changes to the amount of mana restored by Green Potions"); EnhancementSliderInt("Green Potion Mana: %d", "##GREENPOTIONMANA", "gGreenPotionMana", 1, 100, ""); @@ -956,7 +921,7 @@ namespace SohImGui { Tooltip("Changes the amount of health restored by Blue Potions"); EnhancementCheckbox("Blue Potion Health Percent Restore", "gBlueHealthPercentRestore"); Tooltip("Toggles from Blue Potions restoring a fixed amount of health to a percent of the player's current max health"); - + EnhancementSliderInt("Blue Potion Mana: %d", "##BLUEPOTIONMANA", "gBluePotionMana", 1, 100, ""); Tooltip("Changes the amount of mana restored by Blue Potions, base max mana is 48, max upgraded mana is 96"); EnhancementCheckbox("Blue Potion Mana Percent Restore", "gBlueManaPercentRestore"); @@ -1019,7 +984,7 @@ namespace SohImGui { ImGui::EndMenu(); } - + EnhancementCheckbox("Visual Stone of Agony", "gVisualAgony"); Tooltip("Displays an icon and plays a sound when Stone of Agony\nshould be activated, for those without rumble"); EnhancementCheckbox("Assignable Tunics and Boots", "gAssignableTunicsAndBoots"); @@ -1268,7 +1233,7 @@ namespace SohImGui { ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); ImGui::SetNextWindowSize(ImVec2 (0,0)); ImGuiWindowFlags HiddenWndFlags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNavInputs | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoDecoration; ImGui::Begin(category.first.c_str(), nullptr, HiddenWndFlags); ImGui::End(); @@ -1292,6 +1257,7 @@ namespace SohImGui { } console->Draw(); + controller->DrawHud(); for (auto& windowIter : customWindows) { CustomWindow& window = windowIter.second; @@ -1457,4 +1423,124 @@ namespace SohImGui { #endif return reinterpret_cast(id); } + + void BeginGroupPanel(const char* name, const ImVec2& size) + { + ImGui::BeginGroup(); + + // auto cursorPos = ImGui::GetCursorScreenPos(); + auto itemSpacing = ImGui::GetStyle().ItemSpacing; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + auto frameHeight = ImGui::GetFrameHeight(); + ImGui::BeginGroup(); + + ImVec2 effectiveSize = size; + if (size.x < 0.0f) + effectiveSize.x = ImGui::GetContentRegionAvail().x; + else + effectiveSize.x = size.x; + ImGui::Dummy(ImVec2(effectiveSize.x, 0.0f)); + + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::SameLine(0.0f, 0.0f); + ImGui::BeginGroup(); + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::SameLine(0.0f, 0.0f); + ImGui::TextUnformatted(name); + auto labelMin = ImGui::GetItemRectMin(); + auto labelMax = ImGui::GetItemRectMax(); + ImGui::SameLine(0.0f, 0.0f); + ImGui::Dummy(ImVec2(0.0, frameHeight + itemSpacing.y)); + ImGui::BeginGroup(); + + //ImGui::GetWindowDrawList()->AddRect(labelMin, labelMax, IM_COL32(255, 0, 255, 255)); + + ImGui::PopStyleVar(2); + +#if IMGUI_VERSION_NUM >= 17301 + ImGui::GetCurrentWindow()->ContentRegionRect.Max.x -= frameHeight * 0.5f; + ImGui::GetCurrentWindow()->WorkRect.Max.x -= frameHeight * 0.5f; + ImGui::GetCurrentWindow()->InnerRect.Max.x -= frameHeight * 0.5f; +#else + ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x -= frameHeight * 0.5f; +#endif + ImGui::GetCurrentWindow()->Size.x -= frameHeight; + + auto itemWidth = ImGui::CalcItemWidth(); + ImGui::PushItemWidth(ImMax(0.0f, itemWidth - frameHeight)); + s_GroupPanelLabelStack.push_back(ImRect(labelMin, labelMax)); + } + + void EndGroupPanel(float minHeight) { + ImGui::PopItemWidth(); + + auto itemSpacing = ImGui::GetStyle().ItemSpacing; + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + + auto frameHeight = ImGui::GetFrameHeight(); + + ImGui::EndGroup(); + + //ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(0, 255, 0, 64), 4.0f); + + ImGui::EndGroup(); + + ImGui::SameLine(0.0f, 0.0f); + ImGui::Dummy(ImVec2(frameHeight * 0.5f, 0.0f)); + ImGui::Dummy(ImVec2(0.0, std::max(frameHeight - frameHeight * 0.5f - itemSpacing.y, minHeight))); + + ImGui::EndGroup(); + + auto itemMin = ImGui::GetItemRectMin(); + auto itemMax = ImGui::GetItemRectMax(); + //ImGui::GetWindowDrawList()->AddRectFilled(itemMin, itemMax, IM_COL32(255, 0, 0, 64), 4.0f); + + auto labelRect = s_GroupPanelLabelStack.back(); + s_GroupPanelLabelStack.pop_back(); + + ImVec2 halfFrame = ImVec2(frameHeight * 0.25f, frameHeight) * 0.5f; + ImRect frameRect = ImRect(itemMin + halfFrame, itemMax - ImVec2(halfFrame.x, 0.0f)); + labelRect.Min.x -= itemSpacing.x; + labelRect.Max.x += itemSpacing.x; + for (int i = 0; i < 4; ++i) + { + switch (i) + { + // left half-plane + case 0: ImGui::PushClipRect(ImVec2(-FLT_MAX, -FLT_MAX), ImVec2(labelRect.Min.x, FLT_MAX), true); break; + // right half-plane + case 1: ImGui::PushClipRect(ImVec2(labelRect.Max.x, -FLT_MAX), ImVec2(FLT_MAX, FLT_MAX), true); break; + // top + case 2: ImGui::PushClipRect(ImVec2(labelRect.Min.x, -FLT_MAX), ImVec2(labelRect.Max.x, labelRect.Min.y), true); break; + // bottom + case 3: ImGui::PushClipRect(ImVec2(labelRect.Min.x, labelRect.Max.y), ImVec2(labelRect.Max.x, FLT_MAX), true); break; + } + + ImGui::GetWindowDrawList()->AddRect( + frameRect.Min, frameRect.Max, + ImColor(ImGui::GetStyleColorVec4(ImGuiCol_Border)), + halfFrame.x); + + ImGui::PopClipRect(); + } + + ImGui::PopStyleVar(2); + +#if IMGUI_VERSION_NUM >= 17301 + ImGui::GetCurrentWindow()->ContentRegionRect.Max.x += frameHeight * 0.5f; + ImGui::GetCurrentWindow()->WorkRect.Max.x += frameHeight * 0.5f; + ImGui::GetCurrentWindow()->InnerRect.Max.x += frameHeight * 0.5f; +#else + ImGui::GetCurrentWindow()->ContentsRegionRect.Max.x += frameHeight * 0.5f; +#endif + ImGui::GetCurrentWindow()->Size.x += frameHeight; + + ImGui::Dummy(ImVec2(0.0f, 0.0f)); + + ImGui::EndGroup(); + } } diff --git a/libultraship/libultraship/ImGuiImpl.h b/libultraship/libultraship/ImGuiImpl.h index 34bc368a6..1b2ba06c1 100644 --- a/libultraship/libultraship/ImGuiImpl.h +++ b/libultraship/libultraship/ImGuiImpl.h @@ -3,6 +3,7 @@ #include "GameOverlay.h" #include "Lib/ImGui/imgui.h" #include "Console.h" +#include "InputEditor.h" struct GameAsset { uint32_t textureId; @@ -59,12 +60,13 @@ namespace SohImGui { } CustomWindow; extern Console* console; + extern Ship::InputEditor* controller; extern Ship::GameOverlay* overlay; extern bool needs_save; void Init(WindowImpl window_impl); void Update(EventImpl event); void Tooltip(const char* text); - + void EnhancementRadioButton(const char* text, const char* cvarName, int id); void EnhancementCheckbox(const char* text, const char* cvarName); void EnhancementButton(const char* text, const char* cvarName); @@ -75,7 +77,7 @@ namespace SohImGui { void EnhancementCombo(const std::string& name, const char* cvarName, const std::vector& items, int defaultValue = 0); void DrawMainMenuAndCalculateGameSize(void); - + void DrawFramebufferAndGameInput(void); void Render(void); void CancelFrame(void); @@ -90,4 +92,6 @@ namespace SohImGui { void ResetColor(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha); ImTextureID GetTextureByID(int id); ImTextureID GetTextureByName(const std::string& name); + void BeginGroupPanel(const char* name, const ImVec2 & size = ImVec2(0.0f, 0.0f)); + void EndGroupPanel(float minHeight = 0.0f); } diff --git a/libultraship/libultraship/InputEditor.cpp b/libultraship/libultraship/InputEditor.cpp new file mode 100644 index 000000000..e40990b0d --- /dev/null +++ b/libultraship/libultraship/InputEditor.cpp @@ -0,0 +1,277 @@ +#include "InputEditor.h" +#include "Controller.h" +#include "Window.h" +#include "Lib/ImGui/imgui.h" +#include "ImGuiImpl.h" +#include "Utils/StringHelper.h" +#include "Lib/ImGui/imgui_internal.h" + +namespace Ship { + + extern "C" uint8_t __enableGameInput; + #define SEPARATION() ImGui::Dummy(ImVec2(0, 5)) + + void InputEditor::Init() { + BtnReading = -1; + } + + std::shared_ptr GetControllerPerSlot(int slot) { + const std::vector vDevices = Window::ControllerApi->virtualDevices; + return Window::ControllerApi->physicalDevices[vDevices[slot]]; + } + + void InputEditor::DrawButton(const char* label, int n64Btn) { + const std::shared_ptr backend = GetControllerPerSlot(CurrentPort); + + float size = 40; + bool readingMode = BtnReading == n64Btn; + bool disabled = BtnReading != -1 && !readingMode || !backend->Connected(); + ImVec2 len = ImGui::CalcTextSize(label); + ImVec2 pos = ImGui::GetCursorPos(); + ImGui::SetCursorPosY(pos.y + len.y / 4); + ImGui::SetCursorPosX(pos.x + abs(len.x - size)); + ImGui::Text("%s", label); + ImGui::SameLine(); + ImGui::SetCursorPosY(pos.y); + + if(disabled) { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + + if(readingMode) { + const int32_t btn = backend->ReadRawPress(); + + if(btn != -1) { + backend->SetButtonMapping(CurrentPort, n64Btn, btn); + BtnReading = -1; + } + } + + const char* BtnName = backend->GetButtonName(CurrentPort, n64Btn); + + if (ImGui::Button(StringHelper::Sprintf("%s##HBTNID_%d", readingMode ? "Press a Key..." : BtnName, n64Btn).c_str())) { + BtnReading = n64Btn; + backend->ClearRawPress(); + } + + if(disabled) { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + } + } + + void InputEditor::DrawVirtualStick(const char* label, ImVec2 stick) { + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 5, ImGui::GetCursorPos().y)); + ImGui::BeginChild(label, ImVec2(68, 75), false); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 p = ImGui::GetCursorScreenPos(); + + float sz = 45.0f; + float rad = sz * 0.5f; + ImVec2 pos = ImVec2(p.x + sz * 0.5f + 12, p.y + sz * 0.5f + 11); + + float stickX = (stick.x / 83.0f) * (rad * 0.5f); + float stickY = -(stick.y / 83.0f) * (rad * 0.5f); + + ImVec4 rect = ImVec4(p.x + 2, p.y + 2, 65, 65); + draw_list->AddRect(ImVec2(rect.x, rect.y), ImVec2(rect.x + rect.z, rect.y + rect.w), ImColor(100, 100, 100, 255), 0.0f, 0, 1.5f); + draw_list->AddCircleFilled(pos, rad, ImColor(130, 130, 130, 255), 8); + draw_list->AddCircleFilled(ImVec2(pos.x + stickX, pos.y + stickY), 5, ImColor(15, 15, 15, 255), 7); + ImGui::EndChild(); + } + + void InputEditor::DrawControllerSchema() { + + const std::vector vDevices = Window::ControllerApi->virtualDevices; + const std::vector> devices = Window::ControllerApi->physicalDevices; + + std::shared_ptr Backend = devices[vDevices[CurrentPort]]; + DeviceProfile& profile =Backend->profiles[CurrentPort]; + float sensitivity = profile.Thresholds[SENSITIVITY]; + bool IsKeyboard = Backend->GetGuid() == "Keyboard" || !Backend->Connected(); + const char* ControllerName = Backend->GetControllerName(); + + if (ControllerName != nullptr && ImGui::BeginCombo("##ControllerEntries", ControllerName)) { + for (uint8_t i = 0; i < devices.size(); i++) { + if (ImGui::Selectable(devices[i]->GetControllerName(), i == vDevices[CurrentPort])) { + Window::ControllerApi->SetPhysicalDevice(CurrentPort, i); + } + } + ImGui::EndCombo(); + } + + ImGui::SameLine(); + + if(ImGui::Button("Refresh")) { + Window::ControllerApi->ScanPhysicalDevices(); + } + + SohImGui::BeginGroupPanel("Buttons", ImVec2(150, 20)); + DrawButton("A", BTN_A); + DrawButton("B", BTN_B); + DrawButton("L", BTN_L); + DrawButton("R", BTN_R); + DrawButton("Z", BTN_Z); + DrawButton("START", BTN_START); + SEPARATION(); + SohImGui::EndGroupPanel(IsKeyboard ? 7.0f : 48.0f); + ImGui::SameLine(); + SohImGui::BeginGroupPanel("Digital Pad", ImVec2(150, 20)); + DrawButton("Up", BTN_DUP); + DrawButton("Down", BTN_DDOWN); + DrawButton("Left", BTN_DLEFT); + DrawButton("Right", BTN_DRIGHT); + SEPARATION(); + SohImGui::EndGroupPanel(IsKeyboard ? 53.0f : 94.0f); + ImGui::SameLine(); + SohImGui::BeginGroupPanel("Analog Stick", ImVec2(150, 20)); + DrawButton("Up", BTN_STICKUP); + DrawButton("Down", BTN_STICKDOWN); + DrawButton("Left", BTN_STICKLEFT); + DrawButton("Right", BTN_STICKRIGHT); + + if (!IsKeyboard) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); + DrawVirtualStick("##MainVirtualStick", ImVec2(Backend->wStickX, Backend->wStickY)); + ImGui::SameLine(); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + + ImGui::BeginChild("##MSInput", ImVec2(90, 50), false); + ImGui::Text("Deadzone"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##MDZone", &profile.Thresholds[LEFT_STICK]); + ImGui::PopItemWidth(); + ImGui::EndChild(); + } else { + ImGui::Dummy(ImVec2(0, 6)); + } + SohImGui::EndGroupPanel(IsKeyboard ? 52.0f : 24.0f); + ImGui::SameLine(); + + if (!IsKeyboard) { + ImGui::SameLine(); + SohImGui::BeginGroupPanel("Camera Stick", ImVec2(150, 20)); + DrawButton("Up", BTN_VSTICKUP); + DrawButton("Down", BTN_VSTICKDOWN); + DrawButton("Left", BTN_VSTICKLEFT); + DrawButton("Right", BTN_VSTICKRIGHT); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8); + DrawVirtualStick("##CameraVirtualStick", ImVec2(Backend->wCamX / sensitivity, Backend->wCamY / sensitivity)); + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + ImGui::BeginChild("##CSInput", ImVec2(90, 85), false); + ImGui::Text("Deadzone"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##MDZone", &profile.Thresholds[RIGHT_STICK]); + ImGui::PopItemWidth(); + ImGui::Text("Sensitivity"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##MSensitivity", &profile.Thresholds[SENSITIVITY]); + ImGui::PopItemWidth(); + ImGui::EndChild(); + SohImGui::EndGroupPanel(14.0f); + } + + if(Backend->CanGyro()) { + ImGui::SameLine(); + + SohImGui::BeginGroupPanel("Gyro Options", ImVec2(175, 20)); + float cursorX = ImGui::GetCursorPosX() + 5; + ImGui::SetCursorPosX(cursorX); + ImGui::Checkbox("Enable Gyro", &profile.UseGyro); + ImGui::SetCursorPosX(cursorX); + ImGui::Text("Gyro Sensitivity: %d%%", profile.Thresholds[GYRO_SENSITIVITY]); + ImGui::PushItemWidth(135.0f); + ImGui::SetCursorPosX(cursorX); + ImGui::SliderInt("##GSensitivity", &profile.Thresholds[GYRO_SENSITIVITY], 0, 100, ""); + ImGui::PopItemWidth(); + ImGui::Dummy(ImVec2(0, 1)); + ImGui::SetCursorPosX(cursorX); + if (ImGui::Button("Recalibrate Gyro##RGyro")) { + profile.Thresholds[DRIFT_X] = 0; + profile.Thresholds[DRIFT_Y] = 0; + } + ImGui::SetCursorPosX(cursorX); + DrawVirtualStick("##GyroPreview", ImVec2(Backend->wGyroX, Backend->wGyroY)); + + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5); + ImGui::BeginChild("##GyInput", ImVec2(90, 85), false); + ImGui::Text("Drift X"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##GDriftX", &profile.Thresholds[DRIFT_X]); + ImGui::PopItemWidth(); + ImGui::Text("Drift Y"); + ImGui::PushItemWidth(80); + ImGui::InputInt("##GDriftY", &profile.Thresholds[DRIFT_Y]); + ImGui::PopItemWidth(); + ImGui::EndChild(); + SohImGui::EndGroupPanel(14.0f); + } + + ImGui::SameLine(); + + const ImVec2 cursor = ImGui::GetCursorPos(); + + SohImGui::BeginGroupPanel("C-Buttons", ImVec2(158, 20)); + DrawButton("Up", BTN_CUP); + DrawButton("Down", BTN_CDOWN); + DrawButton("Left", BTN_CLEFT); + DrawButton("Right", BTN_CRIGHT); + ImGui::Dummy(ImVec2(0, 5)); + SohImGui::EndGroupPanel(); + + ImGui::SetCursorPosX(cursor.x); + ImGui::SetCursorPosY(cursor.y + 120); + SohImGui::BeginGroupPanel("Options", ImVec2(158, 20)); + float cursorX = ImGui::GetCursorPosX() + 5; + ImGui::SetCursorPosX(cursorX); + ImGui::Checkbox("Rumble Enabled", &profile.UseRumble); + if (Backend->CanRumble()) { + ImGui::SetCursorPosX(cursorX); + ImGui::Text("Rumble Force: %d%%", static_cast(100 * profile.RumbleStrength)); + ImGui::SetCursorPosX(cursorX); + ImGui::PushItemWidth(135.0f); + ImGui::SliderFloat("##RStrength", &profile.RumbleStrength, 0, 1.0f, ""); + ImGui::PopItemWidth(); + } + ImGui::Dummy(ImVec2(0, 5)); + SohImGui::EndGroupPanel(IsKeyboard ? 0.0f : 2.0f); + } + + void InputEditor::DrawHud() { + + __enableGameInput = true; + + if (!this->Opened) { + BtnReading = -1; + return; + } + + ImGui::SetNextWindowSizeConstraints(ImVec2(641, 250), ImVec2(1200, 290)); + //OTRTODO: Disable this stupid workaround ( ReadRawPress() only works when the window is on the main viewport ) + ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); + ImGui::Begin("Controller Configuration", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize); + + ImGui::BeginTabBar("##Controllers"); + + for (int i = 0; i < 4; i++) { + if (ImGui::BeginTabItem(StringHelper::Sprintf("Port %d", i + 1).c_str())) { + CurrentPort = i; + ImGui::EndTabItem(); + } + } + + ImGui::EndTabBar(); + + // Draw current cfg + + DrawControllerSchema(); + + ImGui::End(); + } +} diff --git a/libultraship/libultraship/InputEditor.h b/libultraship/libultraship/InputEditor.h new file mode 100644 index 000000000..39bddd51d --- /dev/null +++ b/libultraship/libultraship/InputEditor.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "Lib/ImGui/imgui.h" + +namespace Ship { + + class InputEditor { + int CurrentPort = 0; + int BtnReading = -1; + public: + bool Opened = false; + void Init(); + void DrawButton(const char* label, int n64Btn); + void DrawVirtualStick(const char* label, ImVec2 stick); + void DrawControllerSchema(); + void DrawHud(); + }; +} diff --git a/libultraship/libultraship/KeyboardController.cpp b/libultraship/libultraship/KeyboardController.cpp index 13014c6a7..0ba08f415 100644 --- a/libultraship/libultraship/KeyboardController.cpp +++ b/libultraship/libultraship/KeyboardController.cpp @@ -1,56 +1,105 @@ #include "KeyboardController.h" + +#if __APPLE__ +#include +#else +#include +#endif + +#include "Hooks.h" #include "GlobalCtx2.h" +#include "Window.h" namespace Ship { - KeyboardController::KeyboardController(int32_t dwControllerNumber) : Controller(dwControllerNumber) { - LoadBinding(); - } - KeyboardController::~KeyboardController() { - + KeyboardController::KeyboardController() : Controller(), lastScancode(-1) { + GUID = "Keyboard"; } bool KeyboardController::PressButton(int32_t dwScancode) { - if (ButtonMapping.contains(dwScancode)) { - dwPressedButtons |= ButtonMapping[dwScancode]; - return true; + + lastKey = dwScancode; + + for (int slot = 0; slot < MAXCONTROLLERS; slot++) { + + if (profiles[slot].Mappings.contains(dwScancode)) { + dwPressedButtons[slot] |= profiles[slot].Mappings[dwScancode]; + return true; + } } return false; } bool KeyboardController::ReleaseButton(int32_t dwScancode) { - if (ButtonMapping.contains(dwScancode)) { - dwPressedButtons &= ~ButtonMapping[dwScancode]; - return true; + for (int slot = 0; slot < MAXCONTROLLERS; slot++) { + if (profiles[slot].Mappings.contains(dwScancode)) { + dwPressedButtons[slot] &= ~profiles[slot].Mappings[dwScancode]; + return true; + } } return false; } void KeyboardController::ReleaseAllButtons() { - dwPressedButtons = 0; + for(int slot = 0; slot < MAXCONTROLLERS; slot++) { + dwPressedButtons[slot] = 0; + } } - void KeyboardController::ReadFromSource() { + void KeyboardController::ReadFromSource(int32_t slot) { wStickX = 0; wStickY = 0; + wCamX = 0; + wCamY = 0; } - void KeyboardController::WriteToSource(ControllerCallback* controller) + int32_t KeyboardController::ReadRawPress() { + return lastKey; + } + + + void KeyboardController::WriteToSource(int32_t slot, ControllerCallback* controller) { } - std::string KeyboardController::GetControllerType() { - return "KEYBOARD"; + const char* KeyboardController::GetButtonName(int slot, int n64Button) { + std::map& Mappings = profiles[slot].Mappings; + const auto find = std::find_if(Mappings.begin(), Mappings.end(), [n64Button](const std::pair& pair) { + return pair.second == n64Button; + }); + + if (find == Mappings.end()) return "Unknown"; + const char* name = GlobalCtx2::GetInstance()->GetWindow()->GetKeyName(find->first); + return strlen(name) == 0 ? "Unknown" : name; } - std::string KeyboardController::GetConfSection() { - return GetControllerType() + " CONTROLLER " + std::to_string(GetControllerNumber() + 1); + + void KeyboardController::CreateDefaultBinding(int32_t slot) { + DeviceProfile& profile = profiles[slot]; + profile.Mappings[0x14D] = BTN_CRIGHT; + profile.Mappings[0x14B] = BTN_CLEFT; + profile.Mappings[0x150] = BTN_CDOWN; + profile.Mappings[0x148] = BTN_CUP; + profile.Mappings[0x13] = BTN_R; + profile.Mappings[0x12] = BTN_L; + profile.Mappings[0x023] = BTN_DRIGHT; + profile.Mappings[0x021] = BTN_DLEFT; + profile.Mappings[0x022] = BTN_DDOWN; + profile.Mappings[0x014] = BTN_DUP; + profile.Mappings[0x039] = BTN_START; + profile.Mappings[0x02C] = BTN_Z; + profile.Mappings[0x02E] = BTN_B; + profile.Mappings[0x02D] = BTN_A; + profile.Mappings[0x020] = BTN_STICKRIGHT; + profile.Mappings[0x01E] = BTN_STICKLEFT; + profile.Mappings[0x01F] = BTN_STICKDOWN; + profile.Mappings[0x011] = BTN_STICKUP; } - std::string KeyboardController::GetBindingConfSection() { - return GetControllerType() + " CONTROLLER BINDING " + std::to_string(GetControllerNumber() + 1); + const char* KeyboardController::GetControllerName() { + return "Keyboard"; } } diff --git a/libultraship/libultraship/KeyboardController.h b/libultraship/libultraship/KeyboardController.h index f6c109ca7..cc0aca04e 100644 --- a/libultraship/libultraship/KeyboardController.h +++ b/libultraship/libultraship/KeyboardController.h @@ -5,24 +5,34 @@ namespace Ship { class KeyboardController : public Controller { public: - KeyboardController(int32_t dwControllerNumber); - ~KeyboardController(); - - void ReadFromSource(); - void WriteToSource(ControllerCallback* controller); - bool Connected() const { return true; } - bool CanRumble() const { return false; } + KeyboardController(); + void ReadFromSource(int32_t slot) override; + void WriteToSource(int32_t slot, ControllerCallback* controller) override; + bool Connected() const override { return true; } + bool CanRumble() const override { return false; } + bool CanGyro() const override { return false; } + const char* GetControllerName() override; + const char* GetButtonName(int slot, int n64Button) override; bool PressButton(int32_t dwScancode); bool ReleaseButton(int32_t dwScancode); + + void ClearRawPress() override { + lastKey = -1; + } + + int32_t ReadRawPress() override; void ReleaseAllButtons(); - bool HasPadConf() const { return false; } - std::optional GetPadConfSection() { return {}; } + void SetLastScancode(int32_t key) { + lastScancode = key; + } + + int32_t GetLastScancode() { return lastScancode; } + void CreateDefaultBinding(int32_t slot) override; protected: - std::string GetControllerType(); - std::string GetConfSection(); - std::string GetBindingConfSection(); + int32_t lastScancode; + int32_t lastKey = -1; }; } diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.cpp b/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.cpp index 06ff5f60a..620fb5271 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.cpp +++ b/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.cpp @@ -28,6 +28,7 @@ #include "gfx_pc.h" #include "../../ImGuiImpl.h" #include "../../Cvar.h" +#include "../../Hooks.h" #define DECLARE_GFX_DXGI_FUNCTIONS #include "gfx_dxgi.h" @@ -240,6 +241,7 @@ static LRESULT CALLBACK gfx_dxgi_wnd_proc(HWND h_wnd, UINT message, WPARAM w_par dxgi.current_height = (uint32_t)(l_param >> 16); break; case WM_DESTROY: + ModInternal::ExecuteHooks(); exit(0); case WM_PAINT: if (dxgi.in_paint) { @@ -718,6 +720,12 @@ void ThrowIfFailed(HRESULT res, HWND h_wnd, const char *message) { } } +const char* gfx_dxgi_get_key_name(int scancode) { + TCHAR* Text = new TCHAR[64]; + GetKeyNameText(scancode << 16, Text, 64); + return (char*) Text; +} + extern "C" struct GfxWindowManagerAPI gfx_dxgi_api = { gfx_dxgi_init, gfx_dxgi_set_keyboard_callbacks, @@ -734,6 +742,7 @@ extern "C" struct GfxWindowManagerAPI gfx_dxgi_api = { gfx_dxgi_set_target_fps, gfx_dxgi_set_maximum_frame_latency, gfx_dxgi_get_detected_hz, + gfx_dxgi_get_key_name }; #endif diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_sdl2.cpp b/libultraship/libultraship/Lib/Fast3D/gfx_sdl2.cpp index 44ad9ec37..cb0d7a7f0 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_sdl2.cpp +++ b/libultraship/libultraship/Lib/Fast3D/gfx_sdl2.cpp @@ -23,6 +23,7 @@ #include "../../ImGuiImpl.h" #include "../../Cvar.h" +#include "../../Hooks.h" #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" @@ -108,7 +109,7 @@ static void set_fullscreen(bool on, bool call_callback) { SDL_GetDesktopDisplayMode(0, &mode); window_width = mode.w; window_height = mode.h; - //SDL_ShowCursor(false); + SDL_ShowCursor(false); } else { window_width = DESIRED_SCREEN_WIDTH; window_height = DESIRED_SCREEN_HEIGHT; @@ -229,6 +230,15 @@ static int translate_scancode(int scancode) { } } +static int untranslate_scancode(int translatedScancode) { + for (int i = 0; i < 512; i++) { + if (inverted_scancode_table[i] == translatedScancode) { + return i; + } + } + return 0; +} + static void gfx_sdl_onkeydown(int scancode) { int key = translate_scancode(scancode); if (on_key_down_callback != NULL) { @@ -270,6 +280,7 @@ static void gfx_sdl_handle_events(void) { Game::SaveSettings(); break; case SDL_QUIT: + ModInternal::ExecuteHooks(); SDL_Quit(); // bandaid fix for linux window closing issue exit(0); } @@ -339,6 +350,10 @@ static float gfx_sdl_get_detected_hz(void) { return 0; } +static const char* gfx_sdl_get_key_name(int scancode) { + return SDL_GetScancodeName((SDL_Scancode) untranslate_scancode(scancode)); +} + struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_init, gfx_sdl_set_keyboard_callbacks, @@ -354,7 +369,8 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_get_time, gfx_sdl_set_target_fps, gfx_sdl_set_maximum_frame_latency, - gfx_sdl_get_detected_hz + gfx_sdl_get_detected_hz, + gfx_sdl_get_key_name }; #endif diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_window_manager_api.h b/libultraship/libultraship/Lib/Fast3D/gfx_window_manager_api.h index 50618f661..99344940e 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_window_manager_api.h +++ b/libultraship/libultraship/Lib/Fast3D/gfx_window_manager_api.h @@ -20,6 +20,7 @@ struct GfxWindowManagerAPI { void (*set_target_fps)(int fps); void (*set_maximum_frame_latency)(int latency); float (*get_detected_hz)(void); + const char* (*get_key_name)(int scancode); }; #endif diff --git a/libultraship/libultraship/Lib/Mercury/Mercury.cpp b/libultraship/libultraship/Lib/Mercury/Mercury.cpp new file mode 100644 index 000000000..d6bf734ea --- /dev/null +++ b/libultraship/libultraship/Lib/Mercury/Mercury.cpp @@ -0,0 +1,134 @@ +#include "Mercury.h" + +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; +using json = nlohmann::json; + +std::unordered_map ramMap; + +Mercury::Mercury(std::string path) : path_(std::move(path)) { + this->reload(); +} + +std::vector split(const std::string& s, const char delimiter) { + std::vector result; + std::stringstream ss(s); + std::string item; + while (getline(ss, item, delimiter)) { + result.push_back(item); + } + return result; +} + +std::string Mercury::formatNestedKey(const std::string& key) { + const std::vector dots = split(key, '.'); + std::string tmp; + if (dots.size() > 1) + for (const auto& dot : dots) { + tmp += "/" + dot; + } + else + tmp = "/" + dots[0]; + + return tmp; +} + +json Mercury::nested(const std::string& key) { + std::vector dots = split(key, '.'); + if (!this->vjson.is_object()) + return this->vjson; + json gjson = this->vjson.unflatten(); + + if (dots.size() > 1) { + for (auto& key : dots) { + if (key == "*" || gjson.contains(key)) + gjson = gjson[key]; + } + return gjson; + } + + return gjson[key]; +} + +std::string Mercury::getString(const std::string& key, const std::string& def) { + json n = this->nested(key); + if (n.is_string() && !n.get().empty()) + return n; + return def; +} + +float Mercury::getFloat(const std::string& key, float def) { + json n = this->nested(key); + if (n.is_number_float()) + return n; + return def; +} + +bool Mercury::getBool(const std::string& key, bool def) { + json n = this->nested(key); + if (n.is_boolean()) + return n; + return def; +} + +int Mercury::getInt(const std::string& key, int def) { + json n = this->nested(key); + if (n.is_number_integer()) + return n; + return def; +} + +bool Mercury::contains(const std::string& key) { + return !this->nested(key).is_null(); +} + +void Mercury::setString(const std::string& key, const std::string& value) { + this->vjson[formatNestedKey(key)] = value; +} + +void Mercury::setFloat(const std::string& key, float value) { + this->vjson[formatNestedKey(key)] = value; +} + +void Mercury::setBool(const std::string& key, bool value) { + this->vjson[formatNestedKey(key)] = value; +} + +void Mercury::setInt(const std::string& key, int value) { + this->vjson[formatNestedKey(key)] = value; +} + +void Mercury::setUInt(const std::string& key, uint32_t value) { + this->vjson[formatNestedKey(key)] = value; +} + +void Mercury::erase(const std::string& key) { + this->vjson.erase(formatNestedKey(key)); +} + +void Mercury::reload() { + if (this->path_ == "None" || !fs::exists(this->path_) || !fs::is_regular_file(this->path_)) { + this->isNewInstance = true; + this->vjson = json::object(); + return; + } + std::ifstream ifs(this->path_); + try { + this->rjson = json::parse(ifs); + this->vjson = this->rjson.flatten(); + } + catch (...) { + this->vjson = json::object(); + } +} + +void Mercury::save() const { + std::ofstream file(this->path_); + file << this->vjson.unflatten().dump(4); +} diff --git a/libultraship/libultraship/Lib/Mercury/Mercury.h b/libultraship/libultraship/Lib/Mercury/Mercury.h new file mode 100644 index 000000000..5c2646d36 --- /dev/null +++ b/libultraship/libultraship/Lib/Mercury/Mercury.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include +#include "../nlohmann/json.hpp" + +class Mercury { +protected: + std::string path_; +public: + explicit Mercury(std::string path); + + nlohmann::json vjson; + nlohmann::json rjson; + nlohmann::json nested(const std::string& key); + static std::string formatNestedKey(const std::string& key); + std::string getString(const std::string& key, const std::string& def = ""); + float getFloat(const std::string& key, float defValue = 0.0f); + bool getBool(const std::string& key, bool defValue = false); + int getInt(const std::string& key, int defValue = 0); + bool contains(const std::string& key); + template< typename T > std::vector getArray(const std::string& key); + void setString(const std::string& key, const std::string& value); + void setFloat(const std::string& key, float value); + void setBool(const std::string& key, bool value); + void setInt(const std::string& key, int value); + void setUInt(const std::string& key, uint32_t value); + void erase(const std::string& key); + void set(const std::string& key, std::any value); + template< typename T > void setArray(const std::string& key, std::vector array); + + void reload(); + void save() const; + bool isNewInstance = false; +}; + +template< typename T > +std::vector Mercury::getArray(const std::string& key) { + if (nlohmann::json tmp = this->nested(key); tmp.is_array()) + return tmp.get>(); + return std::vector(); +}; + +template +void Mercury::setArray(const std::string& key, std::vector array) { + this->vjson[formatNestedKey(key)] = nlohmann::json(array); +} diff --git a/soh/soh/Lib/nlohmann/LICENSE.MIT b/libultraship/libultraship/Lib/nlohmann/LICENSE.MIT similarity index 100% rename from soh/soh/Lib/nlohmann/LICENSE.MIT rename to libultraship/libultraship/Lib/nlohmann/LICENSE.MIT diff --git a/soh/soh/Lib/nlohmann/json.hpp b/libultraship/libultraship/Lib/nlohmann/json.hpp similarity index 100% rename from soh/soh/Lib/nlohmann/json.hpp rename to libultraship/libultraship/Lib/nlohmann/json.hpp diff --git a/libultraship/libultraship/SDLController.cpp b/libultraship/libultraship/SDLController.cpp index d0550e730..fc813dd66 100644 --- a/libultraship/libultraship/SDLController.cpp +++ b/libultraship/libultraship/SDLController.cpp @@ -1,103 +1,36 @@ #include "SDLController.h" - -#include "GameSettings.h" #include "GlobalCtx2.h" #include "spdlog/spdlog.h" -#include "stox.h" #include "Window.h" -#include "Cvar.h" #include extern "C" uint8_t __osMaxControllers; namespace Ship { - SDLController::SDLController(int32_t dwControllerNumber) : Controller(dwControllerNumber), Cont(nullptr), guid(INVALID_SDL_CONTROLLER_GUID) { - - } - - SDLController::~SDLController() { - Close(); - } - - bool SDLController::IsGuidInUse(const std::string& guid) { - // Check if the GUID is loaded in any other controller; - for (size_t i = 0; i < __osMaxControllers; i++) { - for (size_t j = 0; j < Window::Controllers[i].size(); j++) { - SDLController* OtherCont = dynamic_cast(Window::Controllers[i][j].get()); - - if (OtherCont != nullptr && OtherCont->GetGuid().compare(guid) == 0) { - return true; - } - } - } - - return false; - } bool SDLController::Open() { - std::string ConfSection = GetConfSection(); - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); - for (int i = 0; i < SDL_NumJoysticks(); i++) { - if (SDL_IsGameController(i)) { - // Get the GUID from SDL - char GuidBuf[33]; - SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i), GuidBuf, sizeof(GuidBuf)); - auto NewGuid = std::string(GuidBuf); + const auto NewCont = SDL_GameControllerOpen(physicalSlot); - // Invalid GUID read. Go to next. - if (NewGuid.compare(INVALID_SDL_CONTROLLER_GUID) == 0) { - SPDLOG_ERROR("SDL Controller returned invalid guid"); - continue; - } - - // The GUID is in use, we want to use a different physical controller. Go to next. - if (IsGuidInUse(NewGuid)) { - continue; - } - - // If the GUID is blank from the config, OR if the config GUID matches, load the controller. - if (Conf[ConfSection]["GUID"].compare("") == 0 || Conf[ConfSection]["GUID"].compare(INVALID_SDL_CONTROLLER_GUID) == 0 || Conf[ConfSection]["GUID"].compare(NewGuid) == 0) { - auto NewCont = SDL_GameControllerOpen(i); - - // We failed to load the controller. Go to next. - if (NewCont == nullptr) { - SPDLOG_ERROR("SDL Controller failed to open: ({})", SDL_GetError()); - continue; - } - - if (SDL_GameControllerHasSensor(NewCont, SDL_SENSOR_GYRO)) - { - SDL_GameControllerSetSensorEnabled(NewCont, SDL_SENSOR_GYRO, SDL_TRUE); - } - - guid = NewGuid; - Cont = NewCont; - - std::string BindingConfSection = GetBindingConfSection(); - std::string PadConfSection = *GetPadConfSection(); - std::shared_ptr config = GlobalCtx2::GetInstance()->GetConfig(); - - if (!config->has(BindingConfSection)) { - CreateDefaultBinding(); - } - - if (!config->has(PadConfSection)) { - CreateDefaultPadConf(); - } - - LoadBinding(); - LoadAxisThresholds(); - // Update per-controller settings in ImGui menu after opening controller. - Game::LoadPadSettings(); - - break; - } - } + // We failed to load the controller. Go to next. + if (NewCont == nullptr) { + SPDLOG_ERROR("SDL Controller failed to open: ({})", SDL_GetError()); + return false; } - return Cont != nullptr; + if (SDL_GameControllerHasSensor(NewCont, SDL_SENSOR_GYRO)) { + SDL_GameControllerSetSensorEnabled(NewCont, SDL_SENSOR_GYRO, SDL_TRUE); + supportsGyro = true; + } + + char GuidBuf[33]; + SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(physicalSlot), GuidBuf, sizeof(GuidBuf)); + GUID = std::string(GuidBuf); + Cont = NewCont; + wCamX = 0; + wCamY = 0; + + return true; } bool SDLController::Close() { @@ -108,31 +41,14 @@ namespace Ship { SDL_GameControllerClose(Cont); } Cont = nullptr; - guid = ""; - ButtonMapping.clear(); - ThresholdMapping.clear(); - dwPressedButtons = 0; wStickX = 0; wStickY = 0; return true; } - void SDLController::LoadAxisThresholds() { - std::string ConfSection = GetBindingConfSection(); - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); - ThresholdMapping[SDL_CONTROLLER_AXIS_LEFTX] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_LEFTX) + "_threshold"]); - ThresholdMapping[SDL_CONTROLLER_AXIS_LEFTY] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_LEFTY) + "_threshold"]); - ThresholdMapping[SDL_CONTROLLER_AXIS_RIGHTX] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_RIGHTX) + "_threshold"]); - ThresholdMapping[SDL_CONTROLLER_AXIS_RIGHTY] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_RIGHTY) + "_threshold"]); - ThresholdMapping[SDL_CONTROLLER_AXIS_TRIGGERLEFT] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_TRIGGERLEFT) + "_threshold"]); - ThresholdMapping[SDL_CONTROLLER_AXIS_TRIGGERRIGHT] = Ship::stoi(Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + "_threshold"]); - } - - - void SDLController::NormalizeStickAxis(int16_t wAxisValueX, int16_t wAxisValueY, int16_t wAxisThreshold, bool isRightStick) { + void SDLController::NormalizeStickAxis(int16_t wAxisValueX, int16_t wAxisValueY, int16_t wAxisThreshold, bool isRightStick, float sensitivity) { //scale {-32768 ... +32767} to {-84 ... +84} auto ax = wAxisValueX * 85.0 / 32767.0; auto ay = wAxisValueY * 85.0 / 32767.0; @@ -166,18 +82,56 @@ namespace Ship { if (!isRightStick) { wStickX = +ax; wStickY = -ay; - } - else { - //SOHTODO KIRITO: Camera Sensitivity - wCamX = +ax * 15.0f; - wCamY = -ay * 15.0f; + } else { + wCamX = +ax * sensitivity; + wCamY = -ay * sensitivity; } } - void SDLController::ReadFromSource() { - std::string ConfSection = GetBindingConfSection(); - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); + + int32_t SDLController::ReadRawPress() { + SDL_GameControllerUpdate(); + + for (int32_t i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) { + if (SDL_GameControllerGetButton(Cont, static_cast(i))) { + return i; + } + } + + for (int32_t i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) { + const auto Axis = static_cast(i); + const auto AxisValue = SDL_GameControllerGetAxis(Cont, Axis) / 32767; + + if(AxisValue < 0) { + return -(Axis + AXIS_SCANCODE_BIT); + } + + if (AxisValue > 0) { + return (Axis + AXIS_SCANCODE_BIT); + } + } + + return -1; + } + + ControllerThresholds SDLAxisToThreshold( uint32_t axis ){ + switch(axis){ + case SDL_CONTROLLER_AXIS_LEFTX: + case SDL_CONTROLLER_AXIS_LEFTY: + return LEFT_STICK; + case SDL_CONTROLLER_AXIS_RIGHTX: + case SDL_CONTROLLER_AXIS_RIGHTY: + return RIGHT_STICK; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + return LEFT_TRIGGER; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + return RIGHT_TRIGGER; + default: return DRIFT_X; + } + } + + void SDLController::ReadFromSource(int32_t slot) { + DeviceProfile& profile = profiles[slot]; SDL_GameControllerUpdate(); @@ -194,21 +148,14 @@ namespace Ship { } } - auto cameraX = SDL_GameControllerGetAxis(Cont, SDL_CONTROLLER_AXIS_RIGHTX); - auto cameraY = SDL_GameControllerGetAxis(Cont, SDL_CONTROLLER_AXIS_RIGHTY); - NormalizeStickAxis(cameraX, cameraY, ThresholdMapping[SDL_CONTROLLER_AXIS_LEFTX], true); - - if (SDL_GameControllerHasSensor(Cont, SDL_SENSOR_GYRO)) - { - size_t contNumber = GetControllerNumber(); + if (supportsGyro && profile.UseGyro) { float gyroData[3]; SDL_GameControllerGetSensorData(Cont, SDL_SENSOR_GYRO, gyroData, 3); - const char* contName = SDL_GameControllerName(Cont); - float gyro_drift_x = CVar_GetFloat(StringHelper::Sprintf("gCont%i_GyroDriftX", contNumber).c_str(), 0.0f); - float gyro_drift_y = CVar_GetFloat(StringHelper::Sprintf("gCont%i_GyroDriftY", contNumber).c_str(), 0.0f); - const float gyro_sensitivity = CVar_GetFloat(StringHelper::Sprintf("gCont%i_GyroSensitivity", contNumber).c_str(), 1.0f); + float gyro_drift_x = profile.GyroThresholds[DRIFT_X] / 100.0f; + float gyro_drift_y = profile.GyroThresholds[DRIFT_Y] / 100.0f; + const float gyro_sensitivity = profile.GyroThresholds[SENSITIVITY] / 100.0f; if (gyro_drift_x == 0) { gyro_drift_x = gyroData[0]; @@ -218,8 +165,8 @@ namespace Ship { gyro_drift_y = gyroData[1]; } - CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftX", contNumber).c_str(), gyro_drift_x); - CVar_SetFloat(StringHelper::Sprintf("gCont%i_GyroDriftY", contNumber).c_str(), gyro_drift_y); + profile.GyroThresholds[DRIFT_X] = (int) gyro_drift_x * 100; + profile.GyroThresholds[DRIFT_Y] = (int) gyro_drift_y * 100; wGyroX = gyroData[0] - gyro_drift_x; wGyroY = gyroData[1] - gyro_drift_y; @@ -229,28 +176,32 @@ namespace Ship { } for (int32_t i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++) { - if (ButtonMapping.contains(i)) { - if (SDL_GameControllerGetButton(Cont, (SDL_GameControllerButton)i)) { - dwPressedButtons |= ButtonMapping[i]; + if (profile.Mappings.contains(i)) { + if (SDL_GameControllerGetButton(Cont, static_cast(i))) { + dwPressedButtons[slot] |= profile.Mappings[i]; } else { - dwPressedButtons &= ~ButtonMapping[i]; + dwPressedButtons[slot] &= ~profile.Mappings[i]; } } } - SDL_GameControllerAxis StickAxisX = SDL_CONTROLLER_AXIS_INVALID; - SDL_GameControllerAxis StickAxisY = SDL_CONTROLLER_AXIS_INVALID; - int32_t StickDeadzone = 0; + SDL_GameControllerAxis LStickAxisX = SDL_CONTROLLER_AXIS_INVALID; + SDL_GameControllerAxis LStickAxisY = SDL_CONTROLLER_AXIS_INVALID; + int32_t LStickDeadzone = 0; + + SDL_GameControllerAxis RStickAxisX = SDL_CONTROLLER_AXIS_INVALID; + SDL_GameControllerAxis RStickAxisY = SDL_CONTROLLER_AXIS_INVALID; + int32_t RStickDeadzone = 0; for (int32_t i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) { - auto Axis = (SDL_GameControllerAxis)i; - auto PosScancode = i + AXIS_SCANCODE_BIT; - auto NegScancode = -PosScancode; - auto AxisThreshold = ThresholdMapping[i]; - auto PosButton = ButtonMapping[PosScancode]; - auto NegButton = ButtonMapping[NegScancode]; - auto AxisValue = SDL_GameControllerGetAxis(Cont, Axis); + const auto Axis = static_cast(i); + const auto PosScancode = i + AXIS_SCANCODE_BIT; + const auto NegScancode = -PosScancode; + const auto AxisThreshold = profile.Thresholds[SDLAxisToThreshold(i)]; + const auto PosButton = profile.Mappings[PosScancode]; + const auto NegButton = profile.Mappings[NegScancode]; + const auto AxisValue = SDL_GameControllerGetAxis(Cont, Axis); #ifdef TARGET_WEB // Firefox has a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1606562 @@ -263,94 +214,176 @@ namespace Ship { } #endif - // If the axis is NOT mapped to the control stick. if (!( PosButton == BTN_STICKLEFT || PosButton == BTN_STICKRIGHT || PosButton == BTN_STICKUP || PosButton == BTN_STICKDOWN || NegButton == BTN_STICKLEFT || NegButton == BTN_STICKRIGHT || NegButton == BTN_STICKUP || NegButton == BTN_STICKDOWN)) { - if (AxisValue > AxisThreshold) { - dwPressedButtons |= PosButton; - dwPressedButtons &= ~NegButton; + + if (AxisValue > 0x1E00) { + dwPressedButtons[slot] |= PosButton; + dwPressedButtons[slot] &= ~NegButton; } - else if (AxisValue < -AxisThreshold) { - dwPressedButtons &= ~PosButton; - dwPressedButtons |= NegButton; + else if (AxisValue < -0x1E00) { + dwPressedButtons[slot] &= ~PosButton; + dwPressedButtons[slot] |= NegButton; } else { - dwPressedButtons &= ~PosButton; - dwPressedButtons &= ~NegButton; + dwPressedButtons[slot] &= ~PosButton; + dwPressedButtons[slot] &= ~NegButton; } } else { if (PosButton == BTN_STICKLEFT || PosButton == BTN_STICKRIGHT) { - if (StickAxisX != SDL_CONTROLLER_AXIS_INVALID && StickAxisX != Axis) { - SPDLOG_TRACE("Invalid PosStickX configured. Neg was {} and Pos is {}", StickAxisX, Axis); + if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisX != Axis) { + SPDLOG_TRACE("Invalid PosStickX configured. Neg was {} and Pos is {}", LStickAxisX, Axis); } - if (StickDeadzone != 0 && StickDeadzone != AxisThreshold) { - SPDLOG_TRACE("Invalid Deadzone configured. Up/Down was {} and Left/Right is {}", StickDeadzone, AxisThreshold); + if (LStickDeadzone != 0 && LStickDeadzone != AxisThreshold) { + SPDLOG_TRACE("Invalid Deadzone configured. Up/Down was {} and Left/Right is {}", LStickDeadzone, AxisThreshold); } - StickDeadzone = AxisThreshold; - StickAxisX = Axis; + LStickDeadzone = AxisThreshold; + LStickAxisX = Axis; } if (PosButton == BTN_STICKUP || PosButton == BTN_STICKDOWN) { - if (StickAxisY != SDL_CONTROLLER_AXIS_INVALID && StickAxisY != Axis) { - SPDLOG_TRACE("Invalid PosStickY configured. Neg was {} and Pos is {}", StickAxisY, Axis); + if (LStickAxisY != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != Axis) { + SPDLOG_TRACE("Invalid PosStickY configured. Neg was {} and Pos is {}", LStickAxisY, Axis); } - if (StickDeadzone != 0 && StickDeadzone != AxisThreshold) { - SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", StickDeadzone, AxisThreshold); + if (LStickDeadzone != 0 && LStickDeadzone != AxisThreshold) { + SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisThreshold); } - StickDeadzone = AxisThreshold; - StickAxisY = Axis; + LStickDeadzone = AxisThreshold; + LStickAxisY = Axis; } if (NegButton == BTN_STICKLEFT || NegButton == BTN_STICKRIGHT) { - if (StickAxisX != SDL_CONTROLLER_AXIS_INVALID && StickAxisX != Axis) { - SPDLOG_TRACE("Invalid NegStickX configured. Pos was {} and Neg is {}", StickAxisX, Axis); + if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisX != Axis) { + SPDLOG_TRACE("Invalid NegStickX configured. Pos was {} and Neg is {}", LStickAxisX, Axis); } - if (StickDeadzone != 0 && StickDeadzone != AxisThreshold) { - SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", StickDeadzone, AxisThreshold); + if (LStickDeadzone != 0 && LStickDeadzone != AxisThreshold) { + SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisThreshold); } - StickDeadzone = AxisThreshold; - StickAxisX = Axis; + LStickDeadzone = AxisThreshold; + LStickAxisX = Axis; } if (NegButton == BTN_STICKUP || NegButton == BTN_STICKDOWN) { - if (StickAxisY != SDL_CONTROLLER_AXIS_INVALID && StickAxisY != Axis) { - SPDLOG_TRACE("Invalid NegStickY configured. Pos was {} and Neg is {}", StickAxisY, Axis); + if (LStickAxisY != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != Axis) { + SPDLOG_TRACE("Invalid NegStickY configured. Pos was {} and Neg is {}", LStickAxisY, Axis); } - if (StickDeadzone != 0 && StickDeadzone != AxisThreshold) { - SPDLOG_TRACE("Invalid Deadzone misconfigured. Left/Right was {} and Up/Down is {}", StickDeadzone, AxisThreshold); + if (LStickDeadzone != 0 && LStickDeadzone != AxisThreshold) { + SPDLOG_TRACE("Invalid Deadzone misconfigured. Left/Right was {} and Up/Down is {}", LStickDeadzone, AxisThreshold); } - StickDeadzone = AxisThreshold; - StickAxisY = Axis; + LStickDeadzone = AxisThreshold; + LStickAxisY = Axis; } } - if (StickAxisX != SDL_CONTROLLER_AXIS_INVALID && StickAxisY != SDL_CONTROLLER_AXIS_INVALID) { - auto AxisValueX = SDL_GameControllerGetAxis(Cont, StickAxisX); - auto AxisValueY = SDL_GameControllerGetAxis(Cont, StickAxisY); - NormalizeStickAxis(AxisValueX, AxisValueY, StickDeadzone, false); + if (LStickAxisX != SDL_CONTROLLER_AXIS_INVALID && LStickAxisY != SDL_CONTROLLER_AXIS_INVALID) { + const auto AxisValueX = SDL_GameControllerGetAxis(Cont, LStickAxisX); + const auto AxisValueY = SDL_GameControllerGetAxis(Cont, LStickAxisY); + NormalizeStickAxis(AxisValueX, AxisValueY, LStickDeadzone, false, profile.Thresholds[SENSITIVITY]); + } + + // Right Stick + // If the axis is NOT mapped to the control stick. + if (!( + PosButton == BTN_VSTICKLEFT || PosButton == BTN_VSTICKRIGHT || + PosButton == BTN_VSTICKUP || PosButton == BTN_VSTICKDOWN || + NegButton == BTN_VSTICKLEFT || NegButton == BTN_VSTICKRIGHT || + NegButton == BTN_VSTICKUP || NegButton == BTN_VSTICKDOWN)) { + + if (AxisValue > 0x1E00) { + dwPressedButtons[slot] |= PosButton; + dwPressedButtons[slot] &= ~NegButton; + } + else if (AxisValue < -0x1E00) { + dwPressedButtons[slot] &= ~PosButton; + dwPressedButtons[slot] |= NegButton; + } + else { + dwPressedButtons[slot] &= ~PosButton; + dwPressedButtons[slot] &= ~NegButton; + } + + } else { + if (PosButton == BTN_VSTICKLEFT || PosButton == BTN_VSTICKRIGHT) { + if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisX != Axis) { + SPDLOG_TRACE("Invalid PosStickX configured. Neg was {} and Pos is {}", RStickAxisX, Axis); + } + + if (RStickDeadzone != 0 && RStickDeadzone != AxisThreshold) { + SPDLOG_TRACE("Invalid Deadzone configured. Up/Down was {} and Left/Right is {}", RStickDeadzone, AxisThreshold); + } + + RStickDeadzone = AxisThreshold; + RStickAxisX = Axis; + } + + if (PosButton == BTN_VSTICKUP || PosButton == BTN_VSTICKDOWN) { + if (RStickAxisY != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != Axis) { + SPDLOG_TRACE("Invalid PosStickY configured. Neg was {} and Pos is {}", RStickAxisY, Axis); + } + + if (RStickDeadzone != 0 && RStickDeadzone != AxisThreshold) { + SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisThreshold); + } + + RStickDeadzone = AxisThreshold; + RStickAxisY = Axis; + } + + if (NegButton == BTN_VSTICKLEFT || NegButton == BTN_VSTICKRIGHT) { + if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisX != Axis) { + SPDLOG_TRACE("Invalid NegStickX configured. Pos was {} and Neg is {}", RStickAxisX, Axis); + } + + if (RStickDeadzone != 0 && RStickDeadzone != AxisThreshold) { + SPDLOG_TRACE("Invalid Deadzone configured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisThreshold); + } + + RStickDeadzone = AxisThreshold; + RStickAxisX = Axis; + } + + if (NegButton == BTN_VSTICKUP || NegButton == BTN_VSTICKDOWN) { + if (RStickAxisY != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != Axis) { + SPDLOG_TRACE("Invalid NegStickY configured. Pos was {} and Neg is {}", RStickAxisY, Axis); + } + + if (RStickDeadzone != 0 && RStickDeadzone != AxisThreshold) { + SPDLOG_TRACE("Invalid Deadzone misconfigured. Left/Right was {} and Up/Down is {}", RStickDeadzone, AxisThreshold); + } + + RStickDeadzone = AxisThreshold; + RStickAxisY = Axis; + } + } + + if (RStickAxisX != SDL_CONTROLLER_AXIS_INVALID && RStickAxisY != SDL_CONTROLLER_AXIS_INVALID) { + const auto AxisValueX = SDL_GameControllerGetAxis(Cont, RStickAxisX); + const auto AxisValueY = SDL_GameControllerGetAxis(Cont, RStickAxisY); + NormalizeStickAxis(AxisValueX, AxisValueY, RStickDeadzone, true, profile.Thresholds[SENSITIVITY]); } } } - void SDLController::WriteToSource(ControllerCallback* controller) + void SDLController::WriteToSource(int32_t slot, ControllerCallback* controller) { - if (CanRumble()) { + if (CanRumble() && profiles[slot].UseRumble) { if (controller->rumble > 0) { - float rumble_strength = CVar_GetFloat(StringHelper::Sprintf("gCont%i_RumbleStrength", GetControllerNumber()).c_str(), 1.0f); + float rumble_strength = profiles[slot].RumbleStrength; SDL_GameControllerRumble(Cont, 0xFFFF * rumble_strength, 0xFFFF * rumble_strength, 0); - } else { + } + else { SDL_GameControllerRumble(Cont, 0, 0, 0); } } @@ -373,74 +406,71 @@ namespace Ship { } } - void SDLController::CreateDefaultBinding() { - std::string ConfSection = GetBindingConfSection(); - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); + const char* AxisNames[] = { + "Left Stick X", + "Left Stick Y", + "Right Stick X", + "Right Stick Y", + "Left Trigger", + "Right Trigger", + "Start Button" + }; - ConfigFile& Conf = *pConf.get(); + char buffer[50]; + const char* SDLController::GetButtonName(int slot, int n64Button) { + std::map& Mappings = profiles[slot].Mappings; + const auto find = std::find_if(Mappings.begin(), Mappings.end(), [n64Button](const std::pair& pair) { + return pair.second == n64Button; + }); - Conf[ConfSection][STR(BTN_CRIGHT)] = std::to_string(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); - Conf[ConfSection][STR(BTN_CLEFT)] = std::to_string(SDL_CONTROLLER_BUTTON_Y); - Conf[ConfSection][STR(BTN_CDOWN)] = std::to_string(SDL_CONTROLLER_BUTTON_X); - Conf[ConfSection][STR(BTN_CUP)] = std::to_string(SDL_CONTROLLER_BUTTON_RIGHTSTICK); - //Conf[ConfSection][STR(BTN_CRIGHT + "_2")] = std::to_string(SDL_CONTROLLER_BUTTON_X); - //Conf[ConfSection][STR(BTN_CLEFT + "_2")] = std::to_string(SDL_CONTROLLER_BUTTON_Y); - //Conf[ConfSection][STR(BTN_CDOWN + "_2")] = std::to_string(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); - //Conf[ConfSection][STR(BTN_CUP + "_2")] = std::to_string(SDL_CONTROLLER_BUTTON_RIGHTSTICK); - Conf[ConfSection][STR(BTN_R)] = std::to_string((SDL_CONTROLLER_AXIS_TRIGGERRIGHT + AXIS_SCANCODE_BIT)); - Conf[ConfSection][STR(BTN_L)] = std::to_string(SDL_CONTROLLER_BUTTON_LEFTSHOULDER); - Conf[ConfSection][STR(BTN_DRIGHT)] = std::to_string(SDL_CONTROLLER_BUTTON_DPAD_RIGHT); - Conf[ConfSection][STR(BTN_DLEFT)] = std::to_string(SDL_CONTROLLER_BUTTON_DPAD_LEFT); - Conf[ConfSection][STR(BTN_DDOWN)] = std::to_string(SDL_CONTROLLER_BUTTON_DPAD_DOWN); - Conf[ConfSection][STR(BTN_DUP)] = std::to_string(SDL_CONTROLLER_BUTTON_DPAD_UP); - Conf[ConfSection][STR(BTN_START)] = std::to_string(SDL_CONTROLLER_BUTTON_START); - Conf[ConfSection][STR(BTN_Z)] = std::to_string((SDL_CONTROLLER_AXIS_TRIGGERLEFT + AXIS_SCANCODE_BIT)); - Conf[ConfSection][STR(BTN_B)] = std::to_string(SDL_CONTROLLER_BUTTON_B); - Conf[ConfSection][STR(BTN_A)] = std::to_string(SDL_CONTROLLER_BUTTON_A); - Conf[ConfSection][STR(BTN_STICKRIGHT)] = std::to_string((SDL_CONTROLLER_AXIS_LEFTX + AXIS_SCANCODE_BIT)); - Conf[ConfSection][STR(BTN_STICKLEFT)] = std::to_string(-(SDL_CONTROLLER_AXIS_LEFTX + AXIS_SCANCODE_BIT)); - Conf[ConfSection][STR(BTN_STICKDOWN)] = std::to_string((SDL_CONTROLLER_AXIS_LEFTY + AXIS_SCANCODE_BIT)); - Conf[ConfSection][STR(BTN_STICKUP)] = std::to_string(-(SDL_CONTROLLER_AXIS_LEFTY + AXIS_SCANCODE_BIT)); + if (find == Mappings.end()) return "Unknown"; - Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_LEFTX) + "_threshold"] = std::to_string(16.0); - Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_LEFTY) + "_threshold"] = std::to_string(16.0); - Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_RIGHTX) + "_threshold"] = std::to_string(0x4000); - Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_RIGHTY) + "_threshold"] = std::to_string(0x4000); - Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_TRIGGERLEFT) + "_threshold"] = std::to_string(0x1E00); - Conf[ConfSection][STR(SDL_CONTROLLER_AXIS_TRIGGERRIGHT) + "_threshold"] = std::to_string(0x1E00); + int btn = abs(find->first); - Conf.Save(); - } + if(btn >= AXIS_SCANCODE_BIT) { + btn -= AXIS_SCANCODE_BIT; - void SDLController::CreateDefaultPadConf() { - std::string ConfSection = *GetPadConfSection(); - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); - - Conf.Save(); - } - - void SDLController::SetButtonMapping(const std::string& szButtonName, int32_t dwScancode) { - if (guid.compare(INVALID_SDL_CONTROLLER_GUID)) { - return; + snprintf(buffer, sizeof(buffer), "%s%s", AxisNames[btn], find->first > 0 ? "+" : "-"); + return buffer; } - Controller::SetButtonMapping(szButtonName, dwScancode); + snprintf(buffer, sizeof(buffer), "Button %d", btn); + return buffer; } - std::string SDLController::GetControllerType() { - return "SDL"; - } - - std::string SDLController::GetConfSection() { - return GetControllerType() + " CONTROLLER " + std::to_string(GetControllerNumber() + 1); + const char* SDLController::GetControllerName() { + return SDL_GameControllerNameForIndex(physicalSlot); } - std::string SDLController::GetBindingConfSection() { - return GetControllerType() + " CONTROLLER BINDING " + guid; - } + void SDLController::CreateDefaultBinding(int32_t slot) { + DeviceProfile& profile = profiles[slot]; + profile.Mappings.clear(); - std::optional SDLController::GetPadConfSection() { - return GetControllerType() + " CONTROLLER PAD " + guid; + profile.UseRumble = true; + profile.RumbleStrength = 1.0f; + profile.UseGyro = false; + profile.Mappings[ SDL_CONTROLLER_AXIS_RIGHTX | AXIS_SCANCODE_BIT] = BTN_CRIGHT; + profile.Mappings[-(SDL_CONTROLLER_AXIS_RIGHTX | AXIS_SCANCODE_BIT)] = BTN_CLEFT; + profile.Mappings[ SDL_CONTROLLER_AXIS_RIGHTY | AXIS_SCANCODE_BIT] = BTN_CDOWN; + profile.Mappings[-(SDL_CONTROLLER_AXIS_RIGHTY | AXIS_SCANCODE_BIT)] = BTN_CUP; + profile.Mappings[SDL_CONTROLLER_AXIS_TRIGGERRIGHT + AXIS_SCANCODE_BIT] = BTN_R; + profile.Mappings[SDL_CONTROLLER_BUTTON_LEFTSHOULDER] = BTN_L; + profile.Mappings[SDL_CONTROLLER_BUTTON_DPAD_RIGHT] = BTN_DRIGHT; + profile.Mappings[SDL_CONTROLLER_BUTTON_DPAD_LEFT] = BTN_DLEFT; + profile.Mappings[SDL_CONTROLLER_BUTTON_DPAD_DOWN] = BTN_DDOWN; + profile.Mappings[SDL_CONTROLLER_BUTTON_DPAD_UP] = BTN_DUP; + profile.Mappings[SDL_CONTROLLER_BUTTON_START] = BTN_START; + profile.Mappings[SDL_CONTROLLER_AXIS_TRIGGERLEFT + AXIS_SCANCODE_BIT] = BTN_Z; + profile.Mappings[SDL_CONTROLLER_BUTTON_B] = BTN_B; + profile.Mappings[SDL_CONTROLLER_BUTTON_A] = BTN_A; + profile.Mappings[(SDL_CONTROLLER_AXIS_LEFTX + AXIS_SCANCODE_BIT)] = BTN_STICKRIGHT; + profile.Mappings[-(SDL_CONTROLLER_AXIS_LEFTX + AXIS_SCANCODE_BIT)] = BTN_STICKLEFT; + profile.Mappings[SDL_CONTROLLER_AXIS_LEFTY + AXIS_SCANCODE_BIT] = BTN_STICKDOWN; + profile.Mappings[-(SDL_CONTROLLER_AXIS_LEFTY + AXIS_SCANCODE_BIT)] = BTN_STICKUP; + profile.Thresholds[LEFT_STICK] = 16.0; + profile.Thresholds[RIGHT_STICK] = 16.0; + profile.Thresholds[LEFT_TRIGGER] = 0x1E00; + profile.Thresholds[RIGHT_TRIGGER] = 0x1E00; + profile.Thresholds[SENSITIVITY] = 16.0; } } diff --git a/libultraship/libultraship/SDLController.h b/libultraship/libultraship/SDLController.h index d1d671bb9..ae239a2f1 100644 --- a/libultraship/libultraship/SDLController.h +++ b/libultraship/libultraship/SDLController.h @@ -6,46 +6,35 @@ #include #endif -#define INVALID_SDL_CONTROLLER_GUID (std::string("00000000000000000000000000000000")) - namespace Ship { class SDLController : public Controller { public: - SDLController(int32_t dwControllerNumber); - ~SDLController(); - - void ReadFromSource(); - void WriteToSource(ControllerCallback* controller); - bool Connected() const { return Cont != nullptr; } - bool CanRumble() const { + SDLController(int slot) : Controller(), Cont(nullptr), physicalSlot(slot) { } + void ReadFromSource(int32_t slot) override; + const char* GetControllerName() override; + const char* GetButtonName(int slot, int n64Button) override; + void WriteToSource(int32_t slot, ControllerCallback* controller) override; + bool Connected() const override { return Cont != nullptr; } + bool CanGyro() const override { return supportsGyro; } + bool CanRumble() const override { #if SDL_COMPILEDVERSION >= SDL_VERSIONNUM(2,0,18) return SDL_GameControllerHasRumble(Cont); #endif return true; } - std::string GetGuid() { return guid; }; - - bool HasPadConf() const { return true; } - std::optional GetPadConfSection(); + bool Open(); + void ClearRawPress() override {} + int32_t ReadRawPress() override; protected: - std::string GetControllerType(); - void SetButtonMapping(const std::string& szButtonName, int32_t dwScancode); - std::string GetConfSection(); - std::string GetBindingConfSection(); - void CreateDefaultBinding(); - void CreateDefaultPadConf(); - static bool IsGuidInUse(const std::string& guid); + void CreateDefaultBinding(int32_t slot) override; private: SDL_GameController* Cont; - std::string guid; - std::map ThresholdMapping; - - void LoadAxisThresholds(); - void NormalizeStickAxis(int16_t wAxisValueX, int16_t wAxisValueY, int16_t wAxisThreshold, bool isRightStick); - bool Open(); + int physicalSlot; + bool supportsGyro; + void NormalizeStickAxis(int16_t wAxisValueX, int16_t wAxisValueY, int16_t wAxisThreshold, bool isRightStick, float sensitivity); bool Close(); }; } diff --git a/libultraship/libultraship/UltraController.h b/libultraship/libultraship/UltraController.h index e08212b97..41ea7f082 100644 --- a/libultraship/libultraship/UltraController.h +++ b/libultraship/libultraship/UltraController.h @@ -101,6 +101,10 @@ #define BTN_STICKRIGHT 0x20000 #define BTN_STICKDOWN 0x40000 #define BTN_STICKUP 0x80000 +#define BTN_VSTICKUP 0x100000 +#define BTN_VSTICKDOWN 0x200000 +#define BTN_VSTICKLEFT 0x400000 +#define BTN_VSTICKRIGHT 0x800000 typedef struct { /* 0x00 */ int32_t ram[15]; diff --git a/libultraship/libultraship/Window.cpp b/libultraship/libultraship/Window.cpp index 91878b890..b8f8957c4 100644 --- a/libultraship/libultraship/Window.cpp +++ b/libultraship/libultraship/Window.cpp @@ -38,8 +38,7 @@ extern "C" { uint8_t __enableGameInput = 1; int32_t osContInit(OSMesgQueue* mq, uint8_t* controllerBits, OSContStatus* status) { - std::shared_ptr pConf = Ship::GlobalCtx2::GetInstance()->GetConfig(); - Ship::ConfigFile& Conf = *pConf.get(); + *controllerBits = 0; if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) { SPDLOG_ERROR("Failed to initialize SDL game controllers ({})", SDL_GetError()); @@ -54,43 +53,7 @@ extern "C" { SPDLOG_ERROR("Failed add SDL game controller mappings from \"{}\" ({})", controllerDb, SDL_GetError()); } - // TODO: This for loop is debug. Burn it with fire. - for (int i = 0; i < SDL_NumJoysticks(); i++) { - if (SDL_IsGameController(i)) { - // Get the GUID from SDL - char buf[33]; - SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(i), buf, sizeof(buf)); - auto guid = std::string(buf); - auto name = std::string(SDL_GameControllerNameForIndex(i)); - - SPDLOG_INFO("Found Controller \"{}\" with ID \"{}\"", name, guid); - } - } - - for (int32_t i = 0; i < __osMaxControllers; i++) { - std::string ControllerType = Conf["CONTROLLERS"]["CONTROLLER " + std::to_string(i+1)]; - mINI::INIStringUtil::toLower(ControllerType); - - if (ControllerType == "auto") { - Ship::Window::Controllers[i].push_back(std::make_shared(i)); - Ship::Window::Controllers[i].push_back(std::make_shared(i)); - } else if (ControllerType == "keyboard") { - Ship::Window::Controllers[i].push_back(std::make_shared(i)); - } else if (ControllerType == "usb") { - Ship::Window::Controllers[i].push_back(std::make_shared(i)); - } else if (ControllerType == "unplugged") { - // Do nothing for unplugged controllers - } else { - SPDLOG_ERROR("Invalid Controller Type: {}", ControllerType); - } - } - - *controllerBits = 0; - for (size_t i = 0; i < __osMaxControllers; i++) { - if (Ship::Window::Controllers[i].size() > 0) { - *controllerBits |= 1 << i; - } - } + Ship::Window::ControllerApi->Init(controllerBits); return 0; } @@ -103,17 +66,14 @@ extern "C" { pad->button = 0; pad->stick_x = 0; pad->stick_y = 0; + pad->cam_x = 0; + pad->cam_y = 0; pad->err_no = 0; pad->gyro_x = 0; pad->gyro_y = 0; - if (__enableGameInput) - { - for (size_t i = 0; i < __osMaxControllers; i++) { - for (size_t j = 0; j < Ship::Window::Controllers[i].size(); j++) { - Ship::Window::Controllers[i][j]->Read(&pad[i]); - } - } + if (__enableGameInput) { + Ship::Window::ControllerApi->WriteToPad(pad); } ModInternal::ExecuteHooks(pad); @@ -129,15 +89,10 @@ extern "C" { if (hashStr != nullptr) { auto res = std::static_pointer_cast(Ship::GlobalCtx2::GetInstance()->GetResourceManager()->LoadResource(hashStr->c_str())); + return (Vtx*)res->vertices.data(); + } - //if (res != nullptr) - return (Vtx*)res->vertices.data(); - //else - //return (Vtx*)Ship::GlobalCtx2::GetInstance()->GetResourceManager()->LoadFile(hashStr)->buffer.get(); - } - else { - return nullptr; - } + return nullptr; } int32_t* ResourceMgr_LoadMtxByCRC(uint64_t crc) { @@ -146,9 +101,9 @@ extern "C" { if (hashStr != nullptr) { auto res = std::static_pointer_cast(Ship::GlobalCtx2::GetInstance()->GetResourceManager()->LoadResource(hashStr->c_str())); return (int32_t*)res->mtx.data(); - } else { - return nullptr; } + + return nullptr; } Gfx* ResourceMgr_LoadGfxByCRC(uint64_t crc) { @@ -233,7 +188,7 @@ extern GfxWindowManagerAPI gfx_sdl; void SetWindowManager(GfxWindowManagerAPI** WmApi, GfxRenderingAPI** RenderingApi, const std::string& gfx_backend); namespace Ship { - std::map>> Window::Controllers; + int32_t Window::lastScancode; Window::Window(std::shared_ptr Context) : Context(Context), APlayer(nullptr) { @@ -248,26 +203,49 @@ namespace Ship { SPDLOG_INFO("destruct window"); } + void Window::CreateDefaults() { + const std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); + if (pConf->isNewInstance) { + pConf->setInt("Window.Width", 640); + pConf->setInt("Window.Height", 480); + pConf->setBool("Window.Options", false); + pConf->setString("Window.GfxBackend", ""); + + pConf->setBool("Window.Fullscreen.Enabled", false); + pConf->setInt("Window.Fullscreen.Width", 640); + pConf->setInt("Window.Fullscreen.Height", 480); + + pConf->setString("Game.SaveName", ""); + pConf->setString("Game.Main Archive", ""); + pConf->setString("Game.Patches Archive", ""); + + pConf->setInt("Shortcuts.Fullscreen", 0x044); + pConf->setInt("Shortcuts.Console", 0x029); + pConf->save(); + } + } + void Window::Init() { - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); + std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); + + CreateDefaults(); SetAudioPlayer(); - bIsFullscreen = Ship::stob(Conf["WINDOW"]["FULLSCREEN"]); - if (bIsFullscreen) { - dwWidth = Ship::stoi(Conf["WINDOW"]["FULLSCREEN WIDTH"], 1920); - dwHeight = Ship::stoi(Conf["WINDOW"]["FULLSCREEN HEIGHT"], 1080); - } else { - dwWidth = Ship::stoi(Conf["WINDOW"]["WINDOW WIDTH"], 640); - dwHeight = Ship::stoi(Conf["WINDOW"]["WINDOW HEIGHT"], 480); - } - dwMenubar = Ship::stoi(Conf["WINDOW"]["menubar"], 0); - const std::string& gfx_backend = Conf["WINDOW"]["GFX BACKEND"]; + bIsFullscreen = pConf->getBool("Window.Fullscreen.Enabled", false); + + dwWidth = pConf->getInt("Window.Fullscreen.Width", bIsFullscreen ? 1920 : 640); + dwHeight = pConf->getInt("Window.Fullscreen.Height", bIsFullscreen ? 1080 : 480); + dwMenubar = pConf->getBool("Window.Options", false); + const std::string& gfx_backend = pConf->getString("Window.GfxBackend"); SetWindowManager(&WmApi, &RenderingApi, gfx_backend); gfx_init(WmApi, RenderingApi, GetContext()->GetName().c_str(), bIsFullscreen, dwWidth, dwHeight); - WmApi->set_fullscreen_changed_callback(Window::OnFullscreenChanged); - WmApi->set_keyboard_callbacks(Window::KeyDown, Window::KeyUp, Window::AllKeysUp); + WmApi->set_fullscreen_changed_callback(OnFullscreenChanged); + WmApi->set_keyboard_callbacks(KeyDown, KeyUp, AllKeysUp); + + ModInternal::RegisterHook([]() { + ControllerApi->SaveControllerSettings(); + }); } void Window::StartFrame() { @@ -318,30 +296,26 @@ namespace Ship { void Window::MainLoop(void (*MainFunction)(void)) { WmApi->main_loop(MainFunction); } + bool Window::KeyUp(int32_t dwScancode) { - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); + std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - if (dwScancode == Ship::stoi(Conf["KEYBOARD SHORTCUTS"]["KEY_FULLSCREEN"])) { + if (dwScancode == pConf->getInt("Shortcuts.Fullscreen", 0x044)) { GlobalCtx2::GetInstance()->GetWindow()->ToggleFullscreen(); } - - // OTRTODO: Rig with Kirito's console? //if (dwScancode == Ship::stoi(Conf["KEYBOARD SHORTCUTS"]["KEY_CONSOLE"])) { // ToggleConsole(); //} + + lastScancode = -1; bool bIsProcessed = false; - for (size_t i = 0; i < __osMaxControllers; i++) { - for (size_t j = 0; j < Controllers[i].size(); j++) { - KeyboardController* pad = dynamic_cast(Ship::Window::Controllers[i][j].get()); - if (pad != nullptr) { - if (pad->ReleaseButton(dwScancode)) { - bIsProcessed = true; - } - } + const auto pad = dynamic_cast(ControllerApi->physicalDevices[ControllerApi->physicalDevices.size() - 2].get()); + if (pad != nullptr) { + if (pad->ReleaseButton(dwScancode)) { + bIsProcessed = true; } } @@ -350,14 +324,11 @@ namespace Ship { bool Window::KeyDown(int32_t dwScancode) { bool bIsProcessed = false; - for (size_t i = 0; i < __osMaxControllers; i++) { - for (size_t j = 0; j < Controllers[i].size(); j++) { - KeyboardController* pad = dynamic_cast(Ship::Window::Controllers[i][j].get()); - if (pad != nullptr) { - if (pad->PressButton(dwScancode)) { - bIsProcessed = true; - } - } + + const auto pad = dynamic_cast(ControllerApi->physicalDevices[ControllerApi->physicalDevices.size() - 2].get()); + if (pad != nullptr) { + if (pad->PressButton(dwScancode)) { + bIsProcessed = true; } } @@ -368,21 +339,17 @@ namespace Ship { void Window::AllKeysUp(void) { - for (size_t i = 0; i < __osMaxControllers; i++) { - for (size_t j = 0; j < Controllers[i].size(); j++) { - KeyboardController* pad = dynamic_cast(Ship::Window::Controllers[i][j].get()); - if (pad != nullptr) { - pad->ReleaseAllButtons(); - } - } + const auto pad = dynamic_cast(ControllerApi->physicalDevices[ControllerApi->physicalDevices.size() - 2].get()); + if (pad != nullptr) { + pad->ReleaseAllButtons(); } } void Window::OnFullscreenChanged(bool bIsFullscreen) { - std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); - ConfigFile& Conf = *pConf.get(); + std::shared_ptr pConf = GlobalCtx2::GetInstance()->GetConfig(); + GlobalCtx2::GetInstance()->GetWindow()->bIsFullscreen = bIsFullscreen; - Conf["WINDOW"]["FULLSCREEN"] = std::to_string(bIsFullscreen); + pConf->setBool("Window.Fullscreen.Enabled", bIsFullscreen); GlobalCtx2::GetInstance()->GetWindow()->ShowCursor(!bIsFullscreen); } diff --git a/libultraship/libultraship/Window.h b/libultraship/libultraship/Window.h index a3087bf0c..1fa43abeb 100644 --- a/libultraship/libultraship/Window.h +++ b/libultraship/libultraship/Window.h @@ -5,17 +5,22 @@ #include "UltraController.h" #include "Controller.h" #include "GlobalCtx2.h" +#include "ControlDeck.h" +#include + +#include "Lib/Fast3D/gfx_window_manager_api.h" namespace Ship { class AudioPlayer; class Window { public: - static std::map>> Controllers; static int32_t lastScancode; + inline static ControlDeck* ControllerApi = new ControlDeck; Window(std::shared_ptr Context); ~Window(); + void CreateDefaults(); void MainLoop(void (*MainFunction)(void)); void Init(); void StartFrame(); @@ -31,9 +36,11 @@ namespace Ship { bool IsFullscreen() { return bIsFullscreen; } uint32_t GetCurrentWidth(); uint32_t GetCurrentHeight(); + ControlDeck* GetControlDeck() { return ControllerApi; }; uint32_t dwMenubar; std::shared_ptr GetContext() { return Context.lock(); } std::shared_ptr GetAudioPlayer() { return APlayer; } + const char* GetKeyName(int scancode) { return WmApi->get_key_name(scancode); } protected: private: @@ -46,11 +53,10 @@ namespace Ship { std::weak_ptr Context; std::shared_ptr APlayer; - GfxWindowManagerAPI* WmApi; GfxRenderingAPI* RenderingApi; + GfxWindowManagerAPI* WmApi; bool bIsFullscreen; uint32_t dwWidth; uint32_t dwHeight; }; } - diff --git a/libultraship/libultraship/libultraship.vcxproj b/libultraship/libultraship/libultraship.vcxproj index d37915482..964f51f51 100644 --- a/libultraship/libultraship/libultraship.vcxproj +++ b/libultraship/libultraship/libultraship.vcxproj @@ -38,38 +38,38 @@ StaticLibrary true - v142 + v143 Unicode StaticLibrary true - v142 + v143 Unicode StaticLibrary false - v142 + v143 true Unicode StaticLibrary true - v142 + v143 Unicode StaticLibrary true - v142 + v143 Unicode StaticLibrary false - v142 + v143 true Unicode @@ -256,13 +256,16 @@ + + + @@ -279,7 +282,6 @@ - @@ -342,13 +344,18 @@ + + + + + @@ -404,7 +411,6 @@ - diff --git a/libultraship/libultraship/libultraship.vcxproj.filters b/libultraship/libultraship/libultraship.vcxproj.filters index d1682c86f..ec1c6f870 100644 --- a/libultraship/libultraship/libultraship.vcxproj.filters +++ b/libultraship/libultraship/libultraship.vcxproj.filters @@ -31,9 +31,6 @@ {c0f07350-c627-444e-9f66-23e19407ad9a} - - {9cf4833f-e90c-4a9d-8747-d47cde657beb} - {2aa34c3b-6148-480f-a4fc-19c4e0f8c822} @@ -94,6 +91,15 @@ {db6e02cc-fc4c-4138-8219-1d281ad93ec2} + + {2be7c90f-ba21-455d-8a11-6f99452be15c} + + + {7e415dd2-403b-4d4d-b4f2-3e311f91db19} + + + {010dc29b-d1f6-4793-a4e7-4156aa4fcdd6} + @@ -165,9 +171,6 @@ Source Files\Controller\Attachment - - Source Files\Config - Source Files\Resources\Files @@ -354,6 +357,15 @@ Source Files\Resources\Factories + + Source Files\Controller\InputEditor + + + Source Files\Controller + + + Source Files\Lib\Mercury + @@ -386,9 +398,6 @@ Source Files\Controller\Attachment - - Source Files\Config - Source Files\Resources @@ -659,5 +668,20 @@ Source Files\Lib\dr_libs + + Source Files\Controller\InputEditor + + + Source Files\Controller + + + Source Files\Controller + + + Source Files\Lib\nlohmann + + + Source Files\Lib\Mercury + \ No newline at end of file diff --git a/soh/include/functions.h b/soh/include/functions.h index 73f366bb5..72fd6e98c 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -559,6 +559,7 @@ void ActorOverlayTable_Cleanup(void); u16 DynaSSNodeList_GetNextNodeIdx(DynaSSNodeList*); void func_80038A28(CollisionPoly* poly, f32 tx, f32 ty, f32 tz, MtxF* dest); f32 CollisionPoly_GetPointDistanceFromPlane(CollisionPoly* poly, Vec3f* point); +CollisionHeader* BgCheck_GetCollisionHeader(CollisionContext* colCtx, s32 bgId); void CollisionPoly_GetVerticesByBgId(CollisionPoly* poly, s32 bgId, CollisionContext* colCtx, Vec3f* dest); s32 BgCheck_CheckStaticCeiling(StaticLookup* lookup, u16 xpFlags, CollisionContext* colCtx, f32* outY, Vec3f* pos, f32 checkHeight, CollisionPoly** outPoly); diff --git a/soh/soh.vcxproj b/soh/soh.vcxproj index 5f7ea818f..c305b03b1 100644 --- a/soh/soh.vcxproj +++ b/soh/soh.vcxproj @@ -143,7 +143,7 @@ TurnOffAllWarnings false - INCLUDE_GAME_PRINTF;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;ENABLE_DX11;%(PreprocessorDefinitions)GLEW_STATIC + _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;ENABLE_DX11;%(PreprocessorDefinitions) true stdcpp20 MultiThreadedDebug @@ -1038,7 +1038,6 @@ - diff --git a/soh/soh.vcxproj.filters b/soh/soh.vcxproj.filters index bbe6578f2..457e23ed1 100644 --- a/soh/soh.vcxproj.filters +++ b/soh/soh.vcxproj.filters @@ -82,12 +82,6 @@ {04fc1c52-49ff-48e2-ae23-2c00867374f8} - - {dbcf07c4-80b1-4c88-ac54-2bbdd8f53ee4} - - - {9c880c8e-492b-48f6-b230-1fd269ea74b1} - @@ -3947,9 +3941,6 @@ Source Files\soh - - Source Files\soh\Lib\nlohmann - Source Files\soh diff --git a/soh/soh/Enhancements/bootcommands.c b/soh/soh/Enhancements/bootcommands.c index 0b789b445..4f20155d4 100644 --- a/soh/soh/Enhancements/bootcommands.c +++ b/soh/soh/Enhancements/bootcommands.c @@ -20,7 +20,7 @@ extern BootCommandFunc BootCommands_Command_LoadFileSelect(char** argv, s32 argc static BootCommand sCommands[] = { { "--skiplogo", BootCommands_Command_SkipLogo }, { "--loadfileselect", BootCommands_Command_LoadFileSelect } }; -void BootCommands_Init() +void BootCommands_Init() { CVar_RegisterS32("gDisableLOD", 0); CVar_RegisterS32("gDebugEnabled", 0); @@ -30,7 +30,6 @@ void BootCommands_Init() CVar_RegisterS32("gHoverFishing", 0); CVar_RegisterS32("gN64WeirdFrames", 0); CVar_RegisterS32("gBombchusOOB", 0); - CVar_RegisterS32("gRumbleEnabled", 0); CVar_RegisterS32("gUniformLR", 0); CVar_RegisterS32("gTwoHandedIdle", 0); CVar_RegisterS32("gDekuNutUpgradeFix", 0); diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index b2981d361..4bb4efd19 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -16,6 +16,7 @@ #include #include +#include "Window.h" #include "Lib/ImGui/imgui_internal.h" #undef PATH_HACK #undef Path @@ -315,7 +316,7 @@ static bool SaveStateHandler(const std::vector& args) { unsigned int slot = OTRGlobals::Instance->gSaveStateMgr->GetCurrentSlot(); const SaveStateReturn rtn = OTRGlobals::Instance->gSaveStateMgr->AddRequest({ slot, RequestType::SAVE }); - switch (rtn) { + switch (rtn) { case SaveStateReturn::SUCCESS: INFO("[SOH] Saved state to slot %u", slot); return CMD_SUCCESS; @@ -329,7 +330,7 @@ static bool SaveStateHandler(const std::vector& args) { static bool LoadStateHandler(const std::vector& args) { unsigned int slot = OTRGlobals::Instance->gSaveStateMgr->GetCurrentSlot(); const SaveStateReturn rtn = OTRGlobals::Instance->gSaveStateMgr->AddRequest({ slot, RequestType::LOAD }); - + switch (rtn) { case SaveStateReturn::SUCCESS: INFO("[SOH] Loaded state from slot %u", slot); @@ -342,7 +343,7 @@ static bool LoadStateHandler(const std::vector& args) { return CMD_FAILED; case SaveStateReturn::FAIL_WRONG_GAMESTATE: ERROR("[SOH] Can not load a state outside of \"GamePlay\""); - return CMD_FAILED; + return CMD_FAILED; } } @@ -360,7 +361,7 @@ static bool StateSlotSelectHandler(const std::vector& args) { ERROR("[SOH] SaveState slot value must be a number."); return CMD_FAILED; } - + if (slot < 0) { ERROR("[SOH] Invalid slot passed. Slot must be between 0 and 2"); return CMD_FAILED; @@ -498,8 +499,7 @@ template bool is_number(const std::string& s) { return ((std::istringstream(s) >> n >> std::ws).eof()); } -void DebugConsole_LoadCVars() -{ +void DebugConsole_LoadLegacyCVars() { auto cvarsConfig = Ship::GlobalCtx2::GetPathRelativeToAppDirectory("cvars.cfg"); if (File::Exists(cvarsConfig)) { const auto lines = File::ReadAllLines(cvarsConfig); @@ -520,23 +520,58 @@ void DebugConsole_LoadCVars() CVar_SetS32(cfg[0].c_str(), std::stoi(cfg[1])); } } + + fs::remove("cvars.cfg"); } } +void DebugConsole_LoadCVars() { + + std::shared_ptr pConf = Ship::GlobalCtx2::GetInstance()->GetConfig(); + pConf->reload(); + + for (const auto& item : pConf->rjson["CVars"].items()) { + auto value = item.value(); + switch (value.type()) { + case nlohmann::detail::value_t::array: + break; + case nlohmann::detail::value_t::string: + CVar_SetString(item.key().c_str(), value.get().c_str()); + break; + case nlohmann::detail::value_t::boolean: + CVar_SetS32(item.key().c_str(), value.get()); + break; + case nlohmann::detail::value_t::number_unsigned: + case nlohmann::detail::value_t::number_integer: + CVar_SetS32(item.key().c_str(), value.get()); + break; + case nlohmann::detail::value_t::number_float: + CVar_SetFloat(item.key().c_str(), value.get()); + break; + default: ; + } + if (item.key() == "gOpenMenuBar") { + int bp = 0; + } + } + + DebugConsole_LoadLegacyCVars(); +} + void DebugConsole_SaveCVars() { - std::string output; + std::shared_ptr pConf = Ship::GlobalCtx2::GetInstance()->GetConfig(); for (const auto &cvar : cvars) { - if (cvar.second->type == CVAR_TYPE_STRING) - output += StringHelper::Sprintf("%s = \"%s\"\n", cvar.first.c_str(), cvar.second->value.valueStr); + const std::string key = StringHelper::Sprintf("CVars.%s", cvar.first.c_str()); + + if (cvar.second->type == CVAR_TYPE_STRING && cvar.second->value.valueStr != nullptr) + pConf->setString(key, std::string(cvar.second->value.valueStr)); else if (cvar.second->type == CVAR_TYPE_S32) - output += StringHelper::Sprintf("%s = %i\n", cvar.first.c_str(), cvar.second->value.valueS32); + pConf->setInt(key, cvar.second->value.valueS32); else if (cvar.second->type == CVAR_TYPE_FLOAT) - output += StringHelper::Sprintf("%s = %f\n", cvar.first.c_str(), cvar.second->value.valueFloat); + pConf->setFloat(key, cvar.second->value.valueFloat); } - - auto cvarsConfig = Ship::GlobalCtx2::GetPathRelativeToAppDirectory("cvars.cfg"); - File::WriteAllText(cvarsConfig, output); + pConf->save(); } diff --git a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp index 3ed486a44..24662f1d7 100644 --- a/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp +++ b/soh/soh/Enhancements/randomizer/3drando/spoiler_log.cpp @@ -11,7 +11,7 @@ #include "utils.hpp" #include "shops.hpp" #include "hints.hpp" -#include "soh/Lib/nlohmann/json.hpp" +#include "Lib/nlohmann/json.hpp" #include #include diff --git a/soh/soh/Enhancements/randomizer/randomizer.cpp b/soh/soh/Enhancements/randomizer/randomizer.cpp index 43e278ab5..09704e867 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer.cpp @@ -1,5 +1,5 @@ #include "randomizer.h" -#include "soh/Lib/nlohmann/json.hpp" +#include "Lib/nlohmann/json.hpp" #include #include #include diff --git a/soh/soh/Enhancements/randomizer/randomizer.h b/soh/soh/Enhancements/randomizer/randomizer.h index 5fe2e0ae4..7f4d16031 100644 --- a/soh/soh/Enhancements/randomizer/randomizer.h +++ b/soh/soh/Enhancements/randomizer/randomizer.h @@ -1,5 +1,4 @@ -#ifndef RANDOMIZER_H -#define RANDOMIZER_H +#pragma once #include #include @@ -54,5 +53,3 @@ void Rando_Init(void); } #endif - -#endif diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 3a862d58a..514edc563 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1153,34 +1153,27 @@ extern "C" s32* ResourceMgr_LoadCSByName(const char* path) return (s32*)res->commands.data(); } -std::filesystem::path GetSaveFile(Ship::ConfigFile& Conf) { - std::string fileName = Conf.get("SAVE").get("Save Filename"); - - if (fileName.empty()) { - Conf["SAVE"]["Save Filename"] = Ship::GlobalCtx2::GetPathRelativeToAppDirectory("oot_save.sav"); - Conf.Save(); - } +std::filesystem::path GetSaveFile(std::shared_ptr Conf) { + const std::string fileName = Conf->getString("Game.SaveName", Ship::GlobalCtx2::GetPathRelativeToAppDirectory("oot_save.sav")); std::filesystem::path saveFile = std::filesystem::absolute(fileName); - if (!std::filesystem::exists(saveFile.parent_path())) { - std::filesystem::create_directories(saveFile.parent_path()); + if (!exists(saveFile.parent_path())) { + create_directories(saveFile.parent_path()); } return saveFile; } std::filesystem::path GetSaveFile() { - std::shared_ptr pConf = OTRGlobals::Instance->context->GetConfig(); - Ship::ConfigFile& Conf = *pConf.get(); + const std::shared_ptr pConf = OTRGlobals::Instance->context->GetConfig(); - return GetSaveFile(Conf); + return GetSaveFile(pConf); } -void OTRGlobals::CheckSaveFile(size_t sramSize) { - std::shared_ptr pConf = context->GetConfig(); - Ship::ConfigFile& Conf = *pConf.get(); +void OTRGlobals::CheckSaveFile(size_t sramSize) const { + const std::shared_ptr pConf = Instance->context->GetConfig(); - std::filesystem::path savePath = GetSaveFile(Conf); + std::filesystem::path savePath = GetSaveFile(pConf); std::fstream saveFile(savePath, std::fstream::in | std::fstream::out | std::fstream::binary); if (saveFile.fail()) { saveFile.open(savePath, std::fstream::in | std::fstream::out | std::fstream::binary | std::fstream::app); @@ -1199,25 +1192,6 @@ extern "C" void Ctx_WriteSaveFile(uintptr_t addr, void* dramAddr, size_t size) { OTRGlobals::Instance->context->WriteSaveFile(GetSaveFile(), addr, dramAddr, size); } -/* Remember to free after use of value */ -extern "C" char* Config_getValue(char* category, char* key) { - std::shared_ptr pConf = OTRGlobals::Instance->context->GetConfig(); - Ship::ConfigFile& Conf = *pConf.get(); - - std::string data = Conf.get(std::string(category)).get(std::string(key)); - char* retval = (char*)malloc(data.length()+1); - strcpy(retval, data.c_str()); - - return retval; -} - -extern "C" bool Config_setValue(char* category, char* key, char* value) { - std::shared_ptr pConf = OTRGlobals::Instance->context->GetConfig(); - Ship::ConfigFile& Conf = *pConf.get(); - Conf[std::string(category)][std::string(key)] = std::string(value); - return Conf.Save(); -} - std::wstring StringToU16(const std::string& s) { std::vector result; size_t i = 0; @@ -1319,11 +1293,10 @@ extern "C" uint32_t OTRGetCurrentHeight() { } extern "C" void OTRControllerCallback(ControllerCallback* controller) { - auto controllers = OTRGlobals::Instance->context->GetWindow()->Controllers; - for (size_t i = 0; i < controllers.size(); i++) { - for (int j = 0; j < controllers[i].size(); j++) { - OTRGlobals::Instance->context->GetWindow()->Controllers[i][j]->WriteToSource(controller); - } + const auto controllers = Ship::Window::ControllerApi->virtualDevices; + + for (int i = 0; i < controllers.size(); ++i) { + Ship::Window::ControllerApi->physicalDevices[controllers[i]]->WriteToSource(i, controller); } } @@ -1377,11 +1350,11 @@ extern "C" void AudioPlayer_Play(const uint8_t* buf, uint32_t len) { } extern "C" int Controller_ShouldRumble(size_t i) { - for (const auto& controller : Ship::Window::Controllers.at(i)) - { - float rumble_strength = CVar_GetFloat(StringHelper::Sprintf("gCont%i_RumbleStrength", i).c_str(), 1.0f); - if (controller->CanRumble() && rumble_strength > 0.001f) { + const auto controllers = Ship::Window::ControllerApi->virtualDevices; + + for (const auto virtual_entry : controllers) { + if (Ship::Window::ControllerApi->physicalDevices[virtual_entry]->CanRumble()) { return 1; } } diff --git a/soh/soh/OTRGlobals.h b/soh/soh/OTRGlobals.h index 7c56e54c1..7c42da370 100644 --- a/soh/soh/OTRGlobals.h +++ b/soh/soh/OTRGlobals.h @@ -23,7 +23,7 @@ public: ~OTRGlobals(); private: - void CheckSaveFile(size_t sramSize); + void CheckSaveFile(size_t sramSize) const; }; #endif @@ -61,8 +61,6 @@ SoundFontSample* ResourceMgr_LoadAudioSample(const char* path); CollisionHeader* ResourceMgr_LoadColByName(const char* path); void Ctx_ReadSaveFile(uintptr_t addr, void* dramAddr, size_t size); void Ctx_WriteSaveFile(uintptr_t addr, void* dramAddr, size_t size); -char* Config_getValue(char* category, char* key); -bool Config_setValue(char* category, char* key, char* value); uint64_t GetPerfCounter(); struct SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path); diff --git a/soh/src/code/padmgr.c b/soh/src/code/padmgr.c index d89207e6f..9bd9d53e7 100644 --- a/soh/src/code/padmgr.c +++ b/soh/src/code/padmgr.c @@ -269,7 +269,7 @@ void PadMgr_ProcessInputs(PadMgr* padMgr) { input->press.stick_y += (s8)(input->cur.stick_y - input->prev.stick_y); } - controllerCallback.rumble = CVar_GetS32("gRumbleEnabled", 0) && (padMgr->rumbleEnable[0] > 0); + controllerCallback.rumble = (padMgr->rumbleEnable[0] > 0); if (HealthMeter_IsCritical()) { controllerCallback.ledColor = 0; diff --git a/soh/src/code/z_camera.c b/soh/src/code/z_camera.c index 704062b1b..287edeb60 100644 --- a/soh/src/code/z_camera.c +++ b/soh/src/code/z_camera.c @@ -1481,7 +1481,7 @@ s32 Camera_Free(Camera* camera) { camBgChk.pos = camera->eye; - float maxRadius = 160.0f; + float maxRadius = 150.0f; if (Camera_BGCheckInfo(camera, &at, &camBgChk)) { VecSph collSphere; OLib_Vec3fDiffToVecSphGeo(&collSphere, &at, &camBgChk.pos); diff --git a/soh/src/code/z_fbdemo_wipe1.c b/soh/src/code/z_fbdemo_wipe1.c index 1dfb0a501..d4502f8e7 100644 --- a/soh/src/code/z_fbdemo_wipe1.c +++ b/soh/src/code/z_fbdemo_wipe1.c @@ -89,6 +89,7 @@ void TransitionWipe_Draw(void* thisx, Gfx** gfxP) { TransitionWipe* this = (TransitionWipe*)thisx; s32 pad[4]; Gfx* tex; + Gfx* wipeDl = sWipeDList; modelView = this->modelView[this->frame]; diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index d07222768..576f550c7 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -419,7 +419,7 @@ void FileChoose_UpdateMainMenu(GameState* thisx) { (!fileSelectSpoilerFileLoaded && SpoilerFileExists(CVar_GetString("gSpoilerLog", "")))) { if (CVar_GetS32("gNewFileDropped", 0) != 0) { - CVar_SetString("gSpoilerLog", CVar_GetString("gDroppedFile", "")); + CVar_SetString("gSpoilerLog", CVar_GetString("gDroppedFile", "None")); } bool silent = true; if ((CVar_GetS32("gNewFileDropped", 0) != 0) || @@ -1894,7 +1894,7 @@ void FileChoose_Main(GameState* thisx) { }; FileChooseContext* this = (FileChooseContext*)thisx; Input* input = &this->state.input[0]; - + if (CVar_GetS32("gTimeFlowFileSelect", 0) != 0) { gSaveContext.skyboxTime += 0x10; }