Controller Configuration UI and JSON Config (#760)

* Initial controller hud ui

* Reverted fbdemo changes

* Moved config to json and implemented controller config

* fix build on linux, gitignore new config file

* fix build

* Fix compilation and file directory paths

* Call save on cvar save

* Fixed cvar loading and added deck slots to the config

* Changed control deck port 0 to use a physical device by default

* Added gyro and rumble & fixed loading errors

* Save config on toggle menubar

* fix linux build

* Fixed drift calculation

* Controller config now saves when pressing F1

* Removed ExitGame hook from ImGuiImpl

* Moved mappings to a map

* Added GetKeyName

* untranslate scancodes

* Fixed hud layout on keyboard device

* Fixed keyboard read on hud

* Fixed crash when reloading controllers

* Removed ConfigFile and changed file extension

* Changed Dummy to Disconnected and fixed filters

* Removed function leftover

* Changed ControllerHud to InputEditor

Co-authored-by: briaguya <briaguya@alice>
Co-authored-by: David Chavez <david@dcvz.io>
This commit is contained in:
KiritoDev 2022-07-13 22:12:11 -05:00 committed by GitHub
parent cb6876792e
commit 219804cbe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1841 additions and 896 deletions

1
.gitignore vendored
View File

@ -405,3 +405,4 @@ tags
oot.otr
*.sav
shipofharkinian.ini
shipofharkinian.json

View File

@ -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 \
)

View File

@ -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<GlobalCtx2> 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<std::string>& ConfigFile::operator[](const std::string& Section) {
return Val[Section];
}
mINI::INIMap<std::string> 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);
}
}

View File

@ -1,42 +0,0 @@
#ifndef CONFIG_FILE_H
#define CONFIG_FILE_H
#pragma once
#include <string>
#include <memory>
#include "Lib/mINI/src/mini/ini.h"
#include "UltraController.h"
#include "LUSMacros.h"
namespace Ship {
class GlobalCtx2;
class ConfigFile {
public:
ConfigFile(std::shared_ptr<GlobalCtx2> Context, const std::string& Path);
~ConfigFile();
bool Save();
// Expose the ini values.
mINI::INIMap<std::string>& operator[](const std::string& Section);
mINI::INIMap<std::string> 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<GlobalCtx2> GetContext() { return Context.lock(); }
protected:
bool CreateDefaultConfig();
private:
mINI::INIStructure Val;
std::weak_ptr<GlobalCtx2> Context;
std::string Path;
mINI::INIFile File;
};
}
#endif

View File

@ -0,0 +1,155 @@
#include "ControlDeck.h"
#include "Window.h"
#include "Controller.h"
#include "DisconnectedController.h"
#include "KeyboardController.h"
#include "SDLController.h"
#include <Utils/StringHelper.h>
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<SDLController>(i);
sdl->Open();
physicalDevices.push_back(sdl);
}
}
physicalDevices.push_back(std::make_shared<KeyboardController>());
physicalDevices.push_back(std::make_shared<DisconnectedController>());
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<int>(physicalDevices.size()) - 1);
}
LoadControllerSettings();
}
void Ship::ControlDeck::SetPhysicalDevice(int slot, int deviceSlot) {
const std::shared_ptr<Controller> 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<Mercury> 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<Controller> 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<ControllerThresholds>(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<Mercury> Config = GlobalCtx2::GetInstance()->GetConfig();
for (size_t i = 0; i < virtualDevices.size(); i++) {
std::shared_ptr<Controller> 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();
}

View File

@ -0,0 +1,20 @@
#pragma once
#include "Controller.h"
#include <vector>
#include <string>
namespace Ship {
class ControlDeck {
public:
std::vector<int> virtualDevices;
std::vector<std::shared_ptr<Controller>> physicalDevices = {};
void Init(uint8_t* controllerBits);
void ScanPhysicalDevices();
void WriteToPad(OSContPad* pad) const;
void LoadControllerSettings();
void SaveControllerSettings();
void SetPhysicalDevice(int slot, int deviceSlot);
};
}

View File

@ -1,8 +1,6 @@
#include "Controller.h"
#include "GlobalCtx2.h"
#include "stox.h"
#include <memory>
#include <string>
#include <algorithm>
#if __APPLE__
#include <SDL_events.h>
#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<ConfigFile> 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<ConfigFile> 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<int32_t, int32_t>& Mappings = profiles[slot].Mappings;
std::erase_if(Mappings, [n64Button](const std::pair<int32_t, int32_t>& bin) { return bin.second == n64Button; });
Mappings[dwScancode] = n64Button;
}
}

View File

@ -1,52 +1,82 @@
#pragma once
#include <memory>
#include <map>
#include <memory>
#include <string>
#include <optional>
#include "stdint.h"
#include "UltraController.h"
#include "ControllerAttachment.h"
#include <vector>
#include <unordered_map>
#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<ControllerThresholds, int32_t> Thresholds;
std::unordered_map<int32_t, int32_t> GyroThresholds;
std::map<int32_t, int32_t> 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<DeviceProfile> 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<ControllerAttachment> GetAttachment() { return Attachment; }
int32_t GetControllerNumber() { return dwControllerNumber; }
virtual bool HasPadConf() const = 0;
virtual std::optional<std::string> 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<int32_t, int32_t> ButtonMapping;
int8_t wStickX;
int8_t wStickY;
float wGyroX;
float wGyroY;
float wCamX;
float wCamY;
float wCamX;
float wCamY;
protected:
std::vector<int32_t> 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<ControllerAttachment> Attachment;
int32_t dwControllerNumber;
};
struct ControllerEntry {
uint8_t* controllerBits;
Controller* entryIO;
};
}

View File

@ -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<Controller> GetControllerPerSlot(int slot) {
const std::vector<int> vDevices = Window::ControllerApi->virtualDevices;
return Window::ControllerApi->physicalDevices[vDevices[slot]];
}
void InputEditor::DrawButton(const char* label, int n64Btn) {
const std::shared_ptr<Controller> 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<int> vDevices = Window::ControllerApi->virtualDevices;
const std::vector<std::shared_ptr<Controller>> devices = Window::ControllerApi->physicalDevices;
std::shared_ptr<Controller> 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<int>(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();
}
}

View File

@ -5,6 +5,7 @@
#include <memory>
#include <utility>
#include <PR/ultra64/gbi.h>
#include "imgui_internal.h"
std::map<std::string, std::unique_ptr<CVar>, std::less<>> cvars;
@ -70,7 +71,7 @@ extern "C" void CVar_SetString(const char* name, const char* value) {
cvar = std::make_unique<CVar>();
}
cvar->type = CVAR_TYPE_STRING;
cvar->value.valueStr = value;
cvar->value.valueStr = ImStrdup(value);
}
extern "C" void CVar_RegisterS32(const char* name, s32 defaultValue) {

View File

@ -0,0 +1,31 @@
#pragma once
#include <vector>
#include <optional>
#include "Controller.h"
class DisconnectedController final : public Ship::Controller {
public:
DisconnectedController() {
GUID = "Disconnected";
}
std::map<std::vector<std::string>, 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<std::string> 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"; }
};

View File

@ -7,7 +7,6 @@
#include <PR/ultra64/pi.h>
#include <PR/ultra64/message.h>
#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<ConfigFile> 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<ModInternal::GfxInit>([] {
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();
});
}

View File

@ -15,7 +15,6 @@
namespace Ship {
std::weak_ptr<GlobalCtx2> GlobalCtx2::Context;
ModManager* INSTANCE;
std::shared_ptr<GlobalCtx2> 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<ConfigFile>(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<ResourceMgr>(GlobalCtx2::GetInstance(), MainPath, PatchesPath);
Win = std::make_shared<Window>(GlobalCtx2::GetInstance());
Config = std::make_shared<Mercury>(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<ResourceMgr>(GetInstance(), MainPath, PatchesPath);
Win = std::make_shared<Window>(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();
}
}
}

View File

@ -6,8 +6,9 @@
#ifdef __cplusplus
#include <filesystem>
#include <memory>
#include <fstream>
#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<Window> GetWindow() { return Win; }
std::shared_ptr<ResourceMgr> GetResourceManager() { return ResMan; }
std::shared_ptr<spdlog::logger> GetLogger() { return Logger; }
std::shared_ptr<ConfigFile> GetConfig() { return Config; }
std::shared_ptr<Mercury> 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 <GlobalCtx2> Context;
std::shared_ptr<spdlog::logger> Logger;
std::shared_ptr<Window> Win;
std::shared_ptr<ConfigFile> Config; // Config needs to be after the Window because we call the Window during it's destructor.
std::shared_ptr<Mercury> Config; // Config needs to be after the Window because we call the Window during it's destructor.
std::shared_ptr<ResourceMgr> ResMan;
std::string Name;
std::string MainPath;

View File

@ -5,6 +5,7 @@
#include <functional>
#include "UltraController.h"
#include "Controller.h"
#define DEFINE_HOOK(name, type) struct name { typedef std::function<type> 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());
}

View File

@ -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<ImRect> 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<ModInternal::ControllerRead>([](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<std::string>& items, int defaultValue) {
if (ImGui::BeginCombo(name.c_str(), items[static_cast<int>(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<ImTextureID>(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();
}
}

View File

@ -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<std::string>& 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);
}

View File

@ -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<Controller> GetControllerPerSlot(int slot) {
const std::vector<int> vDevices = Window::ControllerApi->virtualDevices;
return Window::ControllerApi->physicalDevices[vDevices[slot]];
}
void InputEditor::DrawButton(const char* label, int n64Btn) {
const std::shared_ptr<Controller> 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<int> vDevices = Window::ControllerApi->virtualDevices;
const std::vector<std::shared_ptr<Controller>> devices = Window::ControllerApi->physicalDevices;
std::shared_ptr<Controller> 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<int>(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();
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
#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();
};
}

View File

@ -1,56 +1,105 @@
#include "KeyboardController.h"
#if __APPLE__
#include <SDL_keyboard.h>
#else
#include <SDL2/SDL_keyboard.h>
#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<int32_t, int32_t>& Mappings = profiles[slot].Mappings;
const auto find = std::find_if(Mappings.begin(), Mappings.end(), [n64Button](const std::pair<int32_t, int32_t>& 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";
}
}

View File

@ -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<std::string> 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;
};
}

View File

@ -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<ModInternal::ExitGame>();
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

View File

@ -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<ModInternal::ExitGame>();
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

View File

@ -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

View File

@ -0,0 +1,134 @@
#include "Mercury.h"
#include <fstream>
#include <sstream>
#include <string>
#include <filesystem>
#include <unordered_map>
#include <any>
namespace fs = std::filesystem;
using json = nlohmann::json;
std::unordered_map<std::string, std::any> ramMap;
Mercury::Mercury(std::string path) : path_(std::move(path)) {
this->reload();
}
std::vector<std::string> split(const std::string& s, const char delimiter) {
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string>().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);
}

View File

@ -0,0 +1,47 @@
#pragma once
#include <any>
#include <vector>
#include <string>
#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<T> 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<T> array);
void reload();
void save() const;
bool isNewInstance = false;
};
template< typename T >
std::vector<T> Mercury::getArray(const std::string& key) {
if (nlohmann::json tmp = this->nested(key); tmp.is_array())
return tmp.get<std::vector<T>>();
return std::vector<T>();
};
template <typename T>
void Mercury::setArray(const std::string& key, std::vector<T> array) {
this->vjson[formatNestedKey(key)] = nlohmann::json(array);
}

View File

@ -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 <Utils/StringHelper.h>
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<SDLController*>(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<ConfigFile> 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<ConfigFile> 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<ConfigFile> 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<ConfigFile> 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<SDL_GameControllerButton>(i))) {
return i;
}
}
for (int32_t i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++) {
const auto Axis = static_cast<SDL_GameControllerAxis>(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<SDL_GameControllerButton>(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<SDL_GameControllerAxis>(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<ConfigFile> 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<int32_t, int32_t>& Mappings = profiles[slot].Mappings;
const auto find = std::find_if(Mappings.begin(), Mappings.end(), [n64Button](const std::pair<int32_t, int32_t>& 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<ConfigFile> 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<std::string> 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;
}
}

View File

@ -6,46 +6,35 @@
#include <SDL2/SDL.h>
#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<std::string> 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<int32_t, int16_t> 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();
};
}

View File

@ -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];

View File

@ -38,8 +38,7 @@ extern "C" {
uint8_t __enableGameInput = 1;
int32_t osContInit(OSMesgQueue* mq, uint8_t* controllerBits, OSContStatus* status) {
std::shared_ptr<Ship::ConfigFile> 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<Ship::KeyboardController>(i));
Ship::Window::Controllers[i].push_back(std::make_shared<Ship::SDLController>(i));
} else if (ControllerType == "keyboard") {
Ship::Window::Controllers[i].push_back(std::make_shared<Ship::KeyboardController>(i));
} else if (ControllerType == "usb") {
Ship::Window::Controllers[i].push_back(std::make_shared<Ship::SDLController>(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<ModInternal::ControllerRead>(pad);
@ -129,15 +89,10 @@ extern "C" {
if (hashStr != nullptr) {
auto res = std::static_pointer_cast<Ship::Array>(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::Matrix>(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<size_t, std::vector<std::shared_ptr<Controller>>> Window::Controllers;
int32_t Window::lastScancode;
Window::Window(std::shared_ptr<GlobalCtx2> Context) : Context(Context), APlayer(nullptr) {
@ -248,26 +203,49 @@ namespace Ship {
SPDLOG_INFO("destruct window");
}
void Window::CreateDefaults() {
const std::shared_ptr<Mercury> 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<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
std::shared_ptr<Mercury> 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<ModInternal::ExitGame>([]() {
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<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
std::shared_ptr<Mercury> 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<KeyboardController*>(Ship::Window::Controllers[i][j].get());
if (pad != nullptr) {
if (pad->ReleaseButton(dwScancode)) {
bIsProcessed = true;
}
}
const auto pad = dynamic_cast<KeyboardController*>(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<KeyboardController*>(Ship::Window::Controllers[i][j].get());
if (pad != nullptr) {
if (pad->PressButton(dwScancode)) {
bIsProcessed = true;
}
}
const auto pad = dynamic_cast<KeyboardController*>(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<KeyboardController*>(Ship::Window::Controllers[i][j].get());
if (pad != nullptr) {
pad->ReleaseAllButtons();
}
}
const auto pad = dynamic_cast<KeyboardController*>(ControllerApi->physicalDevices[ControllerApi->physicalDevices.size() - 2].get());
if (pad != nullptr) {
pad->ReleaseAllButtons();
}
}
void Window::OnFullscreenChanged(bool bIsFullscreen) {
std::shared_ptr<ConfigFile> pConf = GlobalCtx2::GetInstance()->GetConfig();
ConfigFile& Conf = *pConf.get();
std::shared_ptr<Mercury> 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);
}

View File

@ -5,17 +5,22 @@
#include "UltraController.h"
#include "Controller.h"
#include "GlobalCtx2.h"
#include "ControlDeck.h"
#include <string>
#include "Lib/Fast3D/gfx_window_manager_api.h"
namespace Ship {
class AudioPlayer;
class Window {
public:
static std::map<size_t, std::vector<std::shared_ptr<Controller>>> Controllers;
static int32_t lastScancode;
inline static ControlDeck* ControllerApi = new ControlDeck;
Window(std::shared_ptr<GlobalCtx2> 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<GlobalCtx2> GetContext() { return Context.lock(); }
std::shared_ptr<AudioPlayer> 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<GlobalCtx2> Context;
std::shared_ptr<AudioPlayer> APlayer;
GfxWindowManagerAPI* WmApi;
GfxRenderingAPI* RenderingApi;
GfxWindowManagerAPI* WmApi;
bool bIsFullscreen;
uint32_t dwWidth;
uint32_t dwHeight;
};
}

View File

@ -38,38 +38,38 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Testing|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Testing|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@ -256,13 +256,16 @@
<ItemGroup>
<ClCompile Include="Audio.cpp" />
<ClCompile Include="Blob.cpp" />
<ClCompile Include="ControlDeck.cpp" />
<ClCompile Include="Cvar.cpp" />
<ClCompile Include="Environment.cpp" />
<ClCompile Include="Factories\AudioFactory.cpp" />
<ClCompile Include="InputEditor.cpp" />
<ClCompile Include="GameOverlay.cpp" />
<ClCompile Include="GameSettings.cpp" />
<ClCompile Include="Lib\ImGui\backends\imgui_impl_dx11.cpp" />
<ClCompile Include="Lib\ImGui\backends\imgui_impl_win32.cpp" />
<ClCompile Include="Lib\Mercury\Mercury.cpp" />
<ClCompile Include="luslog.cpp" />
<ClCompile Include="mixer.c" />
<ClCompile Include="ModManager.cpp" />
@ -279,7 +282,6 @@
<ClCompile Include="Factories\TextureFactory.cpp" />
<ClCompile Include="Factories\VtxFactory.cpp" />
<ClCompile Include="Array.cpp" />
<ClCompile Include="ConfigFile.cpp" />
<ClCompile Include="Controller.cpp" />
<ClCompile Include="Hooks.cpp" />
<ClCompile Include="ImGuiImpl.cpp" />
@ -342,13 +344,18 @@
<ClCompile Include="SDLController.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Lib\Mercury\Mercury.h" />
<ClInclude Include="Lib\nlohmann\json.hpp" />
<ClInclude Include="abi.h" />
<ClInclude Include="Audio.h" />
<ClInclude Include="AudioPlayer.h" />
<ClInclude Include="Blob.h" />
<ClInclude Include="ControlDeck.h" />
<ClInclude Include="Cvar.h" />
<ClInclude Include="DisconnectedController.h" />
<ClInclude Include="Environment.h" />
<ClInclude Include="Factories\AudioFactory.h" />
<ClInclude Include="InputEditor.h" />
<ClInclude Include="GameOverlay.h" />
<ClInclude Include="GameSettings.h" />
<ClInclude Include="GameVersions.h" />
@ -404,7 +411,6 @@
<ClInclude Include="Vertex.h" />
<ClInclude Include="stox.h" />
<ClInclude Include="Lib\mINI\src\mini\ini.h" />
<ClInclude Include="ConfigFile.h" />
<ClInclude Include="Controller.h" />
<ClInclude Include="KeyboardController.h" />
<ClInclude Include="Factories\CollisionHeaderFactory.h" />

View File

@ -31,9 +31,6 @@
<Filter Include="Source Files\Globals">
<UniqueIdentifier>{c0f07350-c627-444e-9f66-23e19407ad9a}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Config">
<UniqueIdentifier>{9cf4833f-e90c-4a9d-8747-d47cde657beb}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Resources\Files">
<UniqueIdentifier>{2aa34c3b-6148-480f-a4fc-19c4e0f8c822}</UniqueIdentifier>
</Filter>
@ -94,6 +91,15 @@
<Filter Include="Source Files\Lib\dr_libs">
<UniqueIdentifier>{db6e02cc-fc4c-4138-8219-1d281ad93ec2}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Lib\nlohmann">
<UniqueIdentifier>{2be7c90f-ba21-455d-8a11-6f99452be15c}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Lib\Mercury">
<UniqueIdentifier>{7e415dd2-403b-4d4d-b4f2-3e311f91db19}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Controller\InputEditor">
<UniqueIdentifier>{010dc29b-d1f6-4793-a4e7-4156aa4fcdd6}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Factories\MaterialFactory.cpp">
@ -165,9 +171,6 @@
<ClCompile Include="MemoryPack.cpp">
<Filter>Source Files\Controller\Attachment</Filter>
</ClCompile>
<ClCompile Include="ConfigFile.cpp">
<Filter>Source Files\Config</Filter>
</ClCompile>
<ClCompile Include="CollisionHeader.cpp">
<Filter>Source Files\Resources\Files</Filter>
</ClCompile>
@ -354,6 +357,15 @@
<ClCompile Include="Factories\AudioFactory.cpp">
<Filter>Source Files\Resources\Factories</Filter>
</ClCompile>
<ClCompile Include="InputEditor.cpp">
<Filter>Source Files\Controller\InputEditor</Filter>
</ClCompile>
<ClCompile Include="ControlDeck.cpp">
<Filter>Source Files\Controller</Filter>
</ClCompile>
<ClCompile Include="Lib\Mercury\Mercury.cpp">
<Filter>Source Files\Lib\Mercury</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Lib\tinyxml2\tinyxml2.h">
@ -386,9 +398,6 @@
<ClInclude Include="MemoryPack.h">
<Filter>Source Files\Controller\Attachment</Filter>
</ClInclude>
<ClInclude Include="ConfigFile.h">
<Filter>Source Files\Config</Filter>
</ClInclude>
<ClInclude Include="ResourceMgr.h">
<Filter>Source Files\Resources</Filter>
</ClInclude>
@ -659,5 +668,20 @@
<ClInclude Include="Lib\dr_libs\wav.h">
<Filter>Source Files\Lib\dr_libs</Filter>
</ClInclude>
<ClInclude Include="InputEditor.h">
<Filter>Source Files\Controller\InputEditor</Filter>
</ClInclude>
<ClInclude Include="ControlDeck.h">
<Filter>Source Files\Controller</Filter>
</ClInclude>
<ClInclude Include="DisconnectedController.h">
<Filter>Source Files\Controller</Filter>
</ClInclude>
<ClInclude Include="Lib\nlohmann\json.hpp">
<Filter>Source Files\Lib\nlohmann</Filter>
</ClInclude>
<ClInclude Include="Lib\Mercury\Mercury.h">
<Filter>Source Files\Lib\Mercury</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -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);

View File

@ -143,7 +143,7 @@
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>false</SDLCheck>
<PreprocessorDefinitions>INCLUDE_GAME_PRINTF;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;ENABLE_DX11;%(PreprocessorDefinitions)GLEW_STATIC </PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;ENABLE_DX11;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -1038,7 +1038,6 @@
<ClInclude Include="soh\Enhancements\debugger\debugSaveEditor.h" />
<ClInclude Include="soh\Enhancements\debugger\ImGuiHelpers.h" />
<ClInclude Include="soh\gameconsole.h" />
<ClInclude Include="soh\Lib\nlohmann\json.hpp" />
<ClInclude Include="soh\OTRAudio.h" />
<ClInclude Include="soh\OTRGlobals.h" />
<ClInclude Include="soh\SaveManager.h" />

View File

@ -82,12 +82,6 @@
<Filter Include="Source Files\soh\Enhancements\debugger">
<UniqueIdentifier>{04fc1c52-49ff-48e2-ae23-2c00867374f8}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\soh\Lib">
<UniqueIdentifier>{dbcf07c4-80b1-4c88-ac54-2bbdd8f53ee4}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\soh\Lib\nlohmann">
<UniqueIdentifier>{9c880c8e-492b-48f6-b230-1fd269ea74b1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\boot\assert.c">
@ -3947,9 +3941,6 @@
<ClInclude Include="soh\OTRAudio.h">
<Filter>Source Files\soh</Filter>
</ClInclude>
<ClInclude Include="soh\Lib\nlohmann\json.hpp">
<Filter>Source Files\soh\Lib\nlohmann</Filter>
</ClInclude>
<ClInclude Include="soh\SaveManager.h">
<Filter>Source Files\soh</Filter>
</ClInclude>

View File

@ -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);

View File

@ -16,6 +16,7 @@
#include <Utils/StringHelper.h>
#include <Utils/File.h>
#include "Window.h"
#include "Lib/ImGui/imgui_internal.h"
#undef PATH_HACK
#undef Path
@ -315,7 +316,7 @@ static bool SaveStateHandler(const std::vector<std::string>& 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<std::string>& args) {
static bool LoadStateHandler(const std::vector<std::string>& 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<std::string>& 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<std::string>& 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 <typename Numeric> 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<Mercury> 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<std::string>().c_str());
break;
case nlohmann::detail::value_t::boolean:
CVar_SetS32(item.key().c_str(), value.get<bool>());
break;
case nlohmann::detail::value_t::number_unsigned:
case nlohmann::detail::value_t::number_integer:
CVar_SetS32(item.key().c_str(), value.get<int>());
break;
case nlohmann::detail::value_t::number_float:
CVar_SetFloat(item.key().c_str(), value.get<float>());
break;
default: ;
}
if (item.key() == "gOpenMenuBar") {
int bp = 0;
}
}
DebugConsole_LoadLegacyCVars();
}
void DebugConsole_SaveCVars()
{
std::string output;
std::shared_ptr<Mercury> 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();
}

View File

@ -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 <cstdio>
#include <cstdlib>

View File

@ -1,5 +1,5 @@
#include "randomizer.h"
#include "soh/Lib/nlohmann/json.hpp"
#include "Lib/nlohmann/json.hpp"
#include <fstream>
#include <variables.h>
#include <macros.h>

View File

@ -1,5 +1,4 @@
#ifndef RANDOMIZER_H
#define RANDOMIZER_H
#pragma once
#include <unordered_map>
#include <string>
@ -54,5 +53,3 @@ void Rando_Init(void);
}
#endif
#endif

View File

@ -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<Mercury> 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<Ship::ConfigFile> pConf = OTRGlobals::Instance->context->GetConfig();
Ship::ConfigFile& Conf = *pConf.get();
const std::shared_ptr<Mercury> pConf = OTRGlobals::Instance->context->GetConfig();
return GetSaveFile(Conf);
return GetSaveFile(pConf);
}
void OTRGlobals::CheckSaveFile(size_t sramSize) {
std::shared_ptr<Ship::ConfigFile> pConf = context->GetConfig();
Ship::ConfigFile& Conf = *pConf.get();
void OTRGlobals::CheckSaveFile(size_t sramSize) const {
const std::shared_ptr<Mercury> 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<Ship::ConfigFile> 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<Ship::ConfigFile> 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<unsigned long> 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;
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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];

View File

@ -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;
}