mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-05-24 06:50:43 -04:00
0c1372f986
This reverts commit 23a91a37be.
1127 lines
47 KiB
C++
1127 lines
47 KiB
C++
#include "fmt/format.h"
|
|
#include "imgui.h"
|
|
|
|
#include "ImGuiEngine.hpp"
|
|
#include "ImGuiConsole.hpp"
|
|
#include "ImGuiMenuGame.hpp"
|
|
#include "ImGuiConfig.hpp"
|
|
|
|
#include "JSystem/JUtility/JUTGamePad.h"
|
|
#include "dusk/audio/DuskAudioSystem.h"
|
|
#include "dusk/audio/DuskDsp.hpp"
|
|
#include "dusk/main.h"
|
|
#include "dusk/hotkeys.h"
|
|
#include "dusk/settings.h"
|
|
#include "dusk/livesplit.h"
|
|
#include "m_Do/m_Do_controller_pad.h"
|
|
#include "m_Do/m_Do_graphic.h"
|
|
|
|
#include <aurora/gfx.h>
|
|
#include <SDL3/SDL_gamepad.h>
|
|
|
|
#include "m_Do/m_Do_main.h"
|
|
|
|
namespace {
|
|
constexpr int kInternalResolutionScaleMax = 12;
|
|
} // namespace
|
|
|
|
namespace aurora::gx {
|
|
extern bool enableLodBias;
|
|
}
|
|
|
|
namespace dusk {
|
|
void ImGuiMenuGame::ToggleFullscreen() {
|
|
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
|
|
VISetWindowFullscreen(getSettings().video.enableFullscreen);
|
|
config::Save();
|
|
}
|
|
|
|
ImGuiMenuGame::ImGuiMenuGame() {}
|
|
|
|
void ImGuiMenuGame::draw() {
|
|
if (ImGui::BeginMenu("Settings")) {
|
|
drawAudioMenu();
|
|
drawCheatsMenu();
|
|
drawGameplayMenu();
|
|
drawGraphicsMenu();
|
|
drawInputMenu();
|
|
drawInterfaceMenu();
|
|
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::MenuItem("Reset", hotkeys::DO_RESET)) {
|
|
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
|
}
|
|
|
|
if (!IsMobile && ImGui::MenuItem("Exit")) {
|
|
dusk::IsRunning = false;
|
|
}
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
void ImGuiMenuGame::drawGraphicsMenu() {
|
|
if (ImGui::BeginMenu("Graphics")) {
|
|
ImGui::SeparatorText("Display");
|
|
|
|
if (!IsMobile) {
|
|
if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) {
|
|
ToggleFullscreen();
|
|
}
|
|
|
|
if (ImGui::Button("Restore Default Window Size")) {
|
|
getSettings().video.enableFullscreen.setValue(false);
|
|
VISetWindowFullscreen(false);
|
|
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
|
|
VICenterWindow();
|
|
}
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
bool vsync = getSettings().video.enableVsync;
|
|
if (ImGui::Checkbox("Enable VSync", &vsync)) {
|
|
getSettings().video.enableVsync.setValue(vsync);
|
|
aurora_enable_vsync(vsync);
|
|
config::Save();
|
|
}
|
|
|
|
bool lockAspect = getSettings().video.lockAspectRatio;
|
|
if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) {
|
|
getSettings().video.lockAspectRatio.setValue(lockAspect);
|
|
|
|
if (lockAspect) {
|
|
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
|
|
} else {
|
|
AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH);
|
|
}
|
|
|
|
config::Save();
|
|
}
|
|
|
|
ImGui::SeparatorText("Resolution");
|
|
|
|
u32 internalResolutionWidth = 0;
|
|
u32 internalResolutionHeight = 0;
|
|
AuroraGetRenderSize(&internalResolutionWidth, &internalResolutionHeight);
|
|
ImGui::TextDisabled("Current internal resolution: %ux%u", internalResolutionWidth,
|
|
internalResolutionHeight);
|
|
|
|
int scale = std::clamp(getSettings().game.internalResolutionScale.getValue(), 0,
|
|
kInternalResolutionScaleMax);
|
|
if (ImGui::SliderInt("Internal Resolution", &scale, 0, kInternalResolutionScaleMax,
|
|
scale == 0 ? "Auto" : "%dx"))
|
|
{
|
|
getSettings().game.internalResolutionScale.setValue(scale);
|
|
VISetFrameBufferScale(static_cast<float>(scale));
|
|
config::Save();
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Auto renders at the native window resolution.\n"
|
|
"Higher values scale the game's internal framebuffer.");
|
|
}
|
|
|
|
config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d");
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Improves the shadow resolution, making them higher quality.");
|
|
}
|
|
|
|
ImGui::SeparatorText("Post-Processing");
|
|
|
|
constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"};
|
|
int bloomMode = static_cast<int>(getSettings().game.bloomMode.getValue());
|
|
if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) {
|
|
for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) {
|
|
const bool selected = bloomMode == i;
|
|
if (ImGui::Selectable(bloomModeNames[i], selected)) {
|
|
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(i));
|
|
config::Save();
|
|
}
|
|
if (selected) {
|
|
ImGui::SetItemDefaultFocus();
|
|
}
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
bool bloomOff = bloomMode == static_cast<int>(BloomMode::Off);
|
|
if (bloomOff) ImGui::BeginDisabled();
|
|
float mult = getSettings().game.bloomMultiplier.getValue();
|
|
if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) {
|
|
getSettings().game.bloomMultiplier.setValue(mult);
|
|
config::Save();
|
|
}
|
|
if (bloomOff) ImGui::EndDisabled();
|
|
|
|
ImGui::SeparatorText("Rendering");
|
|
|
|
config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation);
|
|
const bool frameInterpolationHovered = ImGui::IsItemHovered();
|
|
ImGui::SameLine();
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f));
|
|
ImGui::TextUnformatted("[EXPERIMENTAL]");
|
|
ImGui::PopStyleColor();
|
|
if (frameInterpolationHovered || ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur.");
|
|
}
|
|
|
|
ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias);
|
|
|
|
config::ImGuiCheckbox("Enable Depth of Field", getSettings().game.enableDepthOfField);
|
|
|
|
config::ImGuiCheckbox("Enable Mini-Map Shadows", getSettings().game.enableMapBackground);
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
void ImGuiMenuGame::drawGameplayMenu() {
|
|
if (ImGui::BeginMenu("Gameplay")) {
|
|
ImGui::SeparatorText("General");
|
|
|
|
config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Disables the main HUD of the game.\n"
|
|
"Useful for recording or a more immersive experience!");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n"
|
|
"the first released version.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Enables rotating Link in the collection menu with the C-Stick");
|
|
}
|
|
|
|
ImGui::SeparatorText("Difficulty");
|
|
|
|
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
|
config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d");
|
|
|
|
config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Any hit will instantly kill you.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Hearts will never drop from enemies,\n"
|
|
"pots and various other places.");
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::SeparatorText("Quality of Life");
|
|
|
|
config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Skip the delay when writing to the Memory Card.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Hold B for Instant Text", getSettings().game.instantText);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Make text scroll immediately by holding B.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Prevents Link from playing a struggle animation\n"
|
|
"when grabbing ledges or climbing on vines.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Link won't recoil when his sword hits walls.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Skip the warning screen shown when starting the game.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously.");
|
|
}
|
|
|
|
ImGui::SeparatorText("Speedrunning");
|
|
if (config::ImGuiCheckbox("Speedrun Mode", getSettings().game.speedrunMode)) {
|
|
resetForSpeedrunMode();
|
|
}
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Enables Speedrunning options, while restricting certain gameplay modifiers.");
|
|
}
|
|
|
|
ImGui::BeginDisabled(!getSettings().game.speedrunMode);
|
|
bool prevLiveSplit = getSettings().game.liveSplitEnabled;
|
|
config::ImGuiCheckbox("LiveSplit Connection", getSettings().game.liveSplitEnabled);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Connect to LiveSplit server on localhost:16834.");
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
if ((bool)getSettings().game.liveSplitEnabled != prevLiveSplit) {
|
|
if (getSettings().game.liveSplitEnabled) {
|
|
dusk::speedrun::connectLiveSplit();
|
|
} else {
|
|
dusk::speedrun::disconnectLiveSplit();
|
|
DuskToast("LiveSplit disconnected", 3.f);
|
|
}
|
|
}
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
void ImGuiMenuGame::drawCheatsMenu() {
|
|
if (ImGui::BeginMenu("Cheats")) {
|
|
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
|
|
|
ImGui::SeparatorText("Resources");
|
|
config::ImGuiCheckbox("Infinite Hearts", getSettings().game.infiniteHearts);
|
|
config::ImGuiCheckbox("Infinite Arrows", getSettings().game.infiniteArrows);
|
|
config::ImGuiCheckbox("Infinite Bombs", getSettings().game.infiniteBombs);
|
|
config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil);
|
|
config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen);
|
|
config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees);
|
|
config::ImGuiCheckbox("No Item Timer", getSettings().game.enableIndefiniteItemDrops);
|
|
ImGui::SetItemTooltip("Item drops such as Rupees, Hearts, etc. will never disappear after they drop.");
|
|
|
|
ImGui::SeparatorText("Abilities");
|
|
config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump);
|
|
config::ImGuiCheckbox("Super Clawshot", getSettings().game.superClawshot);
|
|
config::ImGuiCheckbox("Always Greatspin", getSettings().game.alwaysGreatspin);
|
|
config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots);
|
|
|
|
config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Allows you to transform even if NPCs are looking.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Speeds up Spinner movement when holding R.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Makes the magic armor work without rupees.");
|
|
}
|
|
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
void ImGuiMenuGame::drawAudioMenu() {
|
|
if (ImGui::BeginMenu("Audio")) {
|
|
|
|
ImGui::SeparatorText("Volume");
|
|
|
|
ImGui::Text("Master Volume");
|
|
if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) {
|
|
dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f);
|
|
}
|
|
|
|
/*
|
|
// TODO: Implement additional settings
|
|
ImGui::Text("Main Music Volume");
|
|
ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100);
|
|
|
|
ImGui::Text("Sub Music Volume");
|
|
ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100);
|
|
|
|
ImGui::Text("Sound Effects Volume");
|
|
ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100);
|
|
|
|
ImGui::Text("Fanfare Volume");
|
|
ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100);
|
|
|
|
Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface();
|
|
if (audioMgr != nullptr) {
|
|
}
|
|
*/
|
|
|
|
ImGui::SeparatorText("Effects");
|
|
|
|
if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) {
|
|
dusk::audio::SetEnableReverb(getSettings().audio.enableReverb);
|
|
}
|
|
|
|
|
|
ImGui::SeparatorText("Tweaks");
|
|
|
|
config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Disable the beeping sound when having low health.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing.");
|
|
}
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
void ImGuiMenuGame::drawInputMenu() {
|
|
if (ImGui::BeginMenu("Input")) {
|
|
ImGui::SeparatorText("Controller");
|
|
|
|
if (ImGui::Button("Configure Controller")){
|
|
m_showControllerConfig = !m_showControllerConfig;
|
|
}
|
|
|
|
ImGui::SeparatorText("Camera");
|
|
|
|
config::ImGuiCheckbox("Free Camera", getSettings().game.freeCamera);
|
|
|
|
if (getSettings().game.freeCamera) {
|
|
config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis);
|
|
config::ImGuiCheckbox("Invert Camera Y Axis", getSettings().game.invertCameraYAxis);
|
|
config::ImGuiSliderFloat("Free Camera Sensitivity", getSettings().game.freeCameraSensitivity, 0.5f, 2.0f, "%.1f");
|
|
} else {
|
|
config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis);
|
|
}
|
|
|
|
ImGui::SeparatorText("Gyro");
|
|
|
|
config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Enables the gyroscope on supported controllers\n"
|
|
"while in look mode (C-Up) and while aiming the\n"
|
|
"Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s),\n"
|
|
"Ball and Chain, and Dominion Rod.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n"
|
|
"tilt the Rollgoal table in Hena's Cabin.");
|
|
}
|
|
|
|
if (getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal) {
|
|
config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroSensitivityY, 0.25f, 4.0f, "%.2f");
|
|
config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroSensitivityX, 0.25f, 4.0f, "%.2f");
|
|
|
|
if (getSettings().game.enableGyroRollgoal) {
|
|
config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroSensitivityRollgoal, 0.25f, 4.0f, "%.2f");
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Additional multiplier for scaling how strongly\n"
|
|
"the gyroscope affects the Rollgoal table.");
|
|
}
|
|
}
|
|
|
|
config::ImGuiSliderFloat("Gyro Deadband", getSettings().game.gyroDeadband, 0.0f, 0.5f, "%.3f");
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Angular rates below this magnitude are treated as zero,\n"
|
|
"reducing drift and jitter when the controller is still.");
|
|
}
|
|
|
|
config::ImGuiSliderFloat("Gyro Smoothing", getSettings().game.gyroSmoothing, 0.0f, 1.0f, "%.2f");
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Low values track raw gyro input more closely,\n"
|
|
"while higher values smooth out input over time.");
|
|
}
|
|
|
|
config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroInvertPitch);
|
|
config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroInvertYaw);
|
|
}
|
|
|
|
ImGui::SeparatorText("Tools");
|
|
|
|
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
|
config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind);
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x.");
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
void ImGuiMenuGame::drawInterfaceMenu() {
|
|
if (ImGui::BeginMenu("Interface")) {
|
|
config::ImGuiCheckbox("Achievement Notifications", getSettings().game.enableAchievementNotifications);
|
|
config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI);
|
|
config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation);
|
|
#if DUSK_ENABLE_SENTRY_NATIVE
|
|
config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting);
|
|
#endif
|
|
if (!IsMobile) {
|
|
config::ImGuiCheckbox("Pause on Focus Lost", getSettings().game.pauseOnFocusLost);
|
|
}
|
|
|
|
ImGui::EndMenu();
|
|
}
|
|
}
|
|
|
|
static void drawVirtualStick(const char* id, const ImVec2& stick) {
|
|
float scale = ImGuiScale();
|
|
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10));
|
|
|
|
ImGui::BeginChild(id, ImVec2(80 * scale, 80 * scale), 0, ImGuiWindowFlags_NoBackground);
|
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
|
ImVec2 p = ImGui::GetCursorScreenPos();
|
|
|
|
float radius = 30.0f * scale;
|
|
ImVec2 pos = ImVec2(p.x + radius, p.y + radius);
|
|
|
|
constexpr ImU32 stickGray = IM_COL32(150, 150, 150, 255);
|
|
constexpr ImU32 white = IM_COL32(255, 255, 255, 255);
|
|
constexpr ImU32 red = IM_COL32(230, 0, 0, 255);
|
|
|
|
dl->AddCircleFilled(pos, radius, stickGray, 8);
|
|
dl->AddCircleFilled(ImVec2(pos.x + stick.x * (radius), pos.y + -stick.y * (radius)), 3 * scale, red);
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
struct SpecificButtonName {
|
|
SDL_GamepadType Type;
|
|
const char* Name;
|
|
};
|
|
|
|
struct ButtonNames {
|
|
SDL_GamepadButton Button;
|
|
std::vector<SpecificButtonName> Names;
|
|
};
|
|
|
|
// clang-format off
|
|
static const std::vector<ButtonNames> GamepadButtonNames = {
|
|
{ SDL_GAMEPAD_BUTTON_LEFT_STICK, {
|
|
{SDL_GAMEPAD_TYPE_PS3, "L3"},
|
|
{SDL_GAMEPAD_TYPE_PS4, "L3"},
|
|
{SDL_GAMEPAD_TYPE_PS5, "L3"},
|
|
{SDL_GAMEPAD_TYPE_XBOX360, "Left Stick"},
|
|
{SDL_GAMEPAD_TYPE_XBOXONE, "Left Stick"},
|
|
{SDL_GAMEPAD_TYPE_GAMECUBE, "Control Stick"},
|
|
}},
|
|
{ SDL_GAMEPAD_BUTTON_RIGHT_STICK, {
|
|
{SDL_GAMEPAD_TYPE_PS3, "R3"},
|
|
{SDL_GAMEPAD_TYPE_PS4, "R3"},
|
|
{SDL_GAMEPAD_TYPE_PS5, "R3"},
|
|
{SDL_GAMEPAD_TYPE_XBOX360, "Right Stick"},
|
|
{SDL_GAMEPAD_TYPE_XBOXONE, "Right Stick"},
|
|
{SDL_GAMEPAD_TYPE_GAMECUBE, "C Stick"},
|
|
}},
|
|
{ SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, {
|
|
{SDL_GAMEPAD_TYPE_PS3, "L1"},
|
|
{SDL_GAMEPAD_TYPE_PS4, "L1"},
|
|
{SDL_GAMEPAD_TYPE_PS5, "L1"},
|
|
{SDL_GAMEPAD_TYPE_XBOX360, "LB"},
|
|
{SDL_GAMEPAD_TYPE_XBOXONE, "LB"},
|
|
}},
|
|
{ SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, {
|
|
{SDL_GAMEPAD_TYPE_PS3, "R1"},
|
|
{SDL_GAMEPAD_TYPE_PS4, "R1"},
|
|
{SDL_GAMEPAD_TYPE_PS5, "R1"},
|
|
{SDL_GAMEPAD_TYPE_XBOX360, "RB"},
|
|
{SDL_GAMEPAD_TYPE_XBOXONE, "RB"},
|
|
{SDL_GAMEPAD_TYPE_GAMECUBE, "Z"},
|
|
}},
|
|
{ SDL_GAMEPAD_BUTTON_BACK, {
|
|
{SDL_GAMEPAD_TYPE_PS3, "Select"},
|
|
{SDL_GAMEPAD_TYPE_PS4, "Share"},
|
|
{SDL_GAMEPAD_TYPE_PS5, "Create"},
|
|
{SDL_GAMEPAD_TYPE_XBOX360, "Back"},
|
|
{SDL_GAMEPAD_TYPE_XBOXONE, "View"},
|
|
}},
|
|
{ SDL_GAMEPAD_BUTTON_START, {
|
|
{SDL_GAMEPAD_TYPE_PS3, "Start"},
|
|
{SDL_GAMEPAD_TYPE_PS4, "Options"},
|
|
{SDL_GAMEPAD_TYPE_PS5, "Options"},
|
|
{SDL_GAMEPAD_TYPE_XBOX360, "Start"},
|
|
{SDL_GAMEPAD_TYPE_XBOXONE, "Menu"},
|
|
{SDL_GAMEPAD_TYPE_GAMECUBE, "Start/Pause"},
|
|
}},
|
|
};
|
|
// clang-format on
|
|
|
|
static const char* GetNameForGamepadButton(SDL_Gamepad* gamepad, u32 buttonUntyped) {
|
|
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
|
|
return "Not bound";
|
|
}
|
|
|
|
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
|
|
auto label = SDL_GetGamepadButtonLabel(gamepad, button);
|
|
|
|
switch (label) {
|
|
case SDL_GAMEPAD_BUTTON_LABEL_A:
|
|
return "A";
|
|
case SDL_GAMEPAD_BUTTON_LABEL_B:
|
|
return "B";
|
|
case SDL_GAMEPAD_BUTTON_LABEL_X:
|
|
return "X";
|
|
case SDL_GAMEPAD_BUTTON_LABEL_Y:
|
|
return "Y";
|
|
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
|
|
return "Cross";
|
|
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
|
|
return "Circle";
|
|
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
|
|
return "Triangle";
|
|
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
|
|
return "Square";
|
|
default:; // Fall through
|
|
}
|
|
|
|
auto padType = SDL_GetGamepadType(gamepad);
|
|
for (const auto& buttonNames : GamepadButtonNames) {
|
|
if (buttonNames.Button != button) {
|
|
continue;
|
|
}
|
|
|
|
for (const auto& name : buttonNames.Names) {
|
|
if (name.Type == padType) {
|
|
return name.Name;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (button) {
|
|
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
|
return "D-pad left";
|
|
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
|
return "D-pad right";
|
|
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
|
return "D-pad up";
|
|
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
|
return "D-pad down";
|
|
default:
|
|
return PADGetNativeButtonName(buttonUntyped);
|
|
}
|
|
}
|
|
|
|
void ImGuiMenuGame::windowControllerConfig() {
|
|
if (!m_showControllerConfig) {
|
|
return;
|
|
}
|
|
|
|
// if pending for a button mapping, check to set new input
|
|
if (m_controllerConfig.m_pendingButtonMapping != nullptr) {
|
|
s32 nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort);
|
|
if (nativeButton != -1) {
|
|
m_controllerConfig.m_pendingButtonMapping->nativeButton = nativeButton;
|
|
m_controllerConfig.m_pendingButtonMapping = nullptr;
|
|
m_controllerConfig.m_pendingPort = -1;
|
|
PADBlockInput(false);
|
|
PADSerializeMappings();
|
|
}
|
|
}
|
|
|
|
// if pending for an axis mapping, check to set new input
|
|
if (m_controllerConfig.m_pendingAxisMapping != nullptr) {
|
|
auto nativeAxis = PADGetNativeAxisPulled(m_controllerConfig.m_pendingPort);
|
|
if (nativeAxis.nativeAxis != -1) {
|
|
m_controllerConfig.m_pendingAxisMapping->nativeAxis = nativeAxis;
|
|
m_controllerConfig.m_pendingAxisMapping->nativeButton = -1;
|
|
m_controllerConfig.m_pendingAxisMapping = nullptr;
|
|
m_controllerConfig.m_pendingPort = -1;
|
|
PADBlockInput(false);
|
|
PADSerializeMappings();
|
|
} else {
|
|
auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort);
|
|
if (nativeButton != -1) {
|
|
m_controllerConfig.m_pendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
|
|
m_controllerConfig.m_pendingAxisMapping->nativeButton = nativeButton;
|
|
m_controllerConfig.m_pendingAxisMapping = nullptr;
|
|
m_controllerConfig.m_pendingPort = -1;
|
|
PADBlockInput(false);
|
|
PADSerializeMappings();
|
|
}
|
|
}
|
|
}
|
|
|
|
float scale = ImGuiScale();
|
|
ImGuiWindowFlags windowFlags =
|
|
ImGuiWindowFlags_NoResize |
|
|
ImGuiWindowFlags_AlwaysAutoResize;
|
|
|
|
// ImGui::SetNextWindowBgAlpha(0.65f);
|
|
|
|
if (!ImGui::Begin("Controller Config", &m_showControllerConfig, windowFlags)) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// port tabs
|
|
ImGui::BeginTabBar("##ControllerTabs");
|
|
for (int i = PAD_1; i <= PAD_4; i++) {
|
|
if (ImGui::BeginTabItem(fmt::format("Port {}", i + 1).c_str())) {
|
|
m_controllerConfig.m_selectedPort = i;
|
|
ImGui::EndTabItem();
|
|
}
|
|
}
|
|
ImGui::EndTabBar();
|
|
|
|
// if tab is changed while waiting for input, cancel pending
|
|
if ((m_controllerConfig.m_pendingButtonMapping != nullptr ||
|
|
m_controllerConfig.m_pendingAxisMapping != nullptr) &&
|
|
m_controllerConfig.m_pendingPort != m_controllerConfig.m_selectedPort)
|
|
{
|
|
m_controllerConfig.m_pendingButtonMapping = nullptr;
|
|
m_controllerConfig.m_pendingAxisMapping = nullptr;
|
|
m_controllerConfig.m_pendingPort = -1;
|
|
PADBlockInput(false);
|
|
}
|
|
|
|
// get a list of all available controller's names
|
|
std::vector<std::string> controllerList;
|
|
controllerList.push_back("None");
|
|
for (int i = 0; i < PADCount(); i++) {
|
|
// attach index to name for unique name
|
|
controllerList.push_back(fmt::format("{}-{}", PADGetNameForControllerIndex(i), i));
|
|
}
|
|
|
|
// get current controller name
|
|
const char* tmpName = PADGetName(m_controllerConfig.m_selectedPort);
|
|
std::string currentName = "None";
|
|
if (tmpName != nullptr) {
|
|
currentName = fmt::format("{}-{}", tmpName, PADGetIndexForPort(m_controllerConfig.m_selectedPort));
|
|
}
|
|
|
|
// controller selection combo box
|
|
bool changedController = false;
|
|
int changedControllerIndex = 0;
|
|
ImGui::SetNextItemWidth(400.0f * scale);
|
|
if (ImGui::BeginCombo("##ControllerDeviceList", currentName.c_str())) {
|
|
for (int i = 0; const auto& name : controllerList) {
|
|
if (ImGui::Selectable(name.c_str(), currentName == name)) {
|
|
changedControllerIndex = i;
|
|
changedController = true;
|
|
}
|
|
i++;
|
|
}
|
|
ImGui::EndCombo();
|
|
}
|
|
|
|
// handle controller change
|
|
if (changedController) {
|
|
if (changedControllerIndex > 0) {
|
|
PADSetPortForIndex(changedControllerIndex - 1, m_controllerConfig.m_selectedPort);
|
|
}
|
|
else if (changedControllerIndex == 0) {
|
|
// if "None" selected
|
|
PADClearPort(m_controllerConfig.m_selectedPort);
|
|
}
|
|
PADSerializeMappings();
|
|
}
|
|
|
|
// restore defaults button
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Default")) {
|
|
PADRestoreDefaultMapping(m_controllerConfig.m_selectedPort);
|
|
PADSerializeMappings();
|
|
}
|
|
|
|
// buttons panel
|
|
const float uiButtonSize = 40 * scale;
|
|
ImVec2 btnSize(110.0f * scale, 30.0f * scale);
|
|
|
|
ImGuiBeginGroupPanel("Buttons", ImVec2(150 * scale, 20 * scale));
|
|
|
|
SDL_Gamepad* gamepad = PADGetSDLGamepadForIndex(PADGetIndexForPort(m_controllerConfig.m_selectedPort));
|
|
u32 buttonCount;
|
|
PADButtonMapping* btnMappingList = PADGetButtonMappings(m_controllerConfig.m_selectedPort, &buttonCount);
|
|
if (btnMappingList != nullptr) {
|
|
for (int i = 0; i < buttonCount; i++) {
|
|
const char* btnName = PADGetButtonName(btnMappingList[i].padButton);
|
|
ImVec2 len = ImGui::CalcTextSize(btnName);
|
|
ImVec2 pos = ImGui::GetCursorPos();
|
|
|
|
ImGui::SetCursorPosY(pos.y + len.y / 4);
|
|
ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize));
|
|
ImGui::Text("%s", btnName);
|
|
ImGui::SameLine();
|
|
|
|
ImGui::SetCursorPosY(pos.y);
|
|
|
|
std::string dispName;
|
|
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingButtonMapping == &btnMappingList[i]) {
|
|
dispName = fmt::format("Press a Key...##{}", btnName);
|
|
} else {
|
|
const char* nativeName = GetNameForGamepadButton(gamepad, btnMappingList[i].nativeButton);
|
|
if (nativeName == nullptr) {
|
|
nativeName = "[unbound]";
|
|
}
|
|
dispName = fmt::format("{0}##-{1}", nativeName, i);
|
|
}
|
|
bool pressed = ImGui::Button(dispName.c_str(),
|
|
btnSize);
|
|
|
|
if (pressed) {
|
|
m_controllerConfig.m_isReading = true;
|
|
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
|
|
m_controllerConfig.m_pendingButtonMapping = &btnMappingList[i];
|
|
PADBlockInput(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGuiEndGroupPanel();
|
|
ImGui::SameLine();
|
|
|
|
uint32_t axisCount;
|
|
PADAxisMapping* axisMappingList = PADGetAxisMappings(m_controllerConfig.m_selectedPort, &axisCount);
|
|
|
|
ImGuiBeginGroupPanel("Analog Triggers", ImVec2(150 * scale, 20 * scale));
|
|
|
|
PADAxis triggers[] = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R};
|
|
if (axisMappingList != nullptr) {
|
|
for (PADAxis trigger : triggers) {
|
|
const char* axisName = PADGetAxisName(axisMappingList[trigger].padAxis);
|
|
ImVec2 len = ImGui::CalcTextSize(axisName);
|
|
ImVec2 pos = ImGui::GetCursorPos();
|
|
|
|
ImGui::SetCursorPosY(pos.y + len.y / 4);
|
|
ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize));
|
|
ImGui::Text("%s", axisName);
|
|
ImGui::SameLine();
|
|
|
|
ImGui::SetCursorPosY(pos.y);
|
|
|
|
std::string dispName;
|
|
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[trigger]) {
|
|
dispName = fmt::format("Press a Key...##{}", axisName);
|
|
} else {
|
|
dispName = fmt::format("{0}##-{1}", PADGetNativeAxisName(axisMappingList[trigger].nativeAxis), trigger);
|
|
}
|
|
bool pressed = ImGui::Button(dispName.c_str(),
|
|
btnSize);
|
|
|
|
if (pressed) {
|
|
m_controllerConfig.m_isReading = true;
|
|
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
|
|
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[trigger];
|
|
PADBlockInput(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
int port = m_controllerConfig.m_selectedPort;
|
|
PADDeadZones* deadZones = PADGetDeadZones(port);
|
|
|
|
if (deadZones != nullptr) {
|
|
ImGui::Text("L Threshold");
|
|
ImGui::SameLine();
|
|
{
|
|
float tmp = static_cast<float>(deadZones->leftTriggerActivationZone * 100.f) / 32767.f;
|
|
if (ImGui::DragFloat("##LThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
|
|
deadZones->leftTriggerActivationZone = static_cast<u16>((tmp / 100.f) * 32767);
|
|
PADSerializeMappings();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deadZones != nullptr) {
|
|
ImGui::Text("R Threshold");
|
|
ImGui::SameLine();
|
|
{
|
|
float tmp = static_cast<float>(deadZones->rightTriggerActivationZone * 100.f) / 32767.f;
|
|
if (ImGui::DragFloat("##RThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
|
|
deadZones->rightTriggerActivationZone = static_cast<u16>((tmp / 100.f) * 32767);
|
|
PADSerializeMappings();
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGuiEndGroupPanel();
|
|
ImGui::SameLine();
|
|
|
|
// main stick panel
|
|
ImGuiBeginGroupPanel("Control Stick", ImVec2(150 * scale, 20 * scale));
|
|
|
|
drawVirtualStick("##mainStick", ImVec2{ mDoCPd_c::getStickX(port), mDoCPd_c::getStickY(port) });
|
|
|
|
if (axisMappingList != nullptr) {
|
|
const PADAxis lStickAxes[] = {PAD_AXIS_LEFT_Y_POS, PAD_AXIS_LEFT_Y_NEG, PAD_AXIS_LEFT_X_NEG, PAD_AXIS_LEFT_X_POS};
|
|
for (auto axis : lStickAxes) {
|
|
const char* label = PADGetAxisDirectionLabel(axis);
|
|
ImVec2 len = ImGui::CalcTextSize(label);
|
|
ImVec2 pos = ImGui::GetCursorPos();
|
|
|
|
ImGui::SetCursorPosY(pos.y + len.y / 4);
|
|
ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize));
|
|
ImGui::Text("%s", label);
|
|
ImGui::SameLine();
|
|
|
|
ImGui::SetCursorPosY(pos.y);
|
|
|
|
std::string dispName;
|
|
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) {
|
|
dispName = fmt::format("Press a Key...##{}", label);
|
|
} else {
|
|
if (axisMappingList[axis].nativeAxis.nativeAxis != -1) {
|
|
const char* signStr;
|
|
if (axis == PAD_AXIS_TRIGGER_L || axis == PAD_AXIS_TRIGGER_R) {
|
|
signStr = "";
|
|
} else if (axisMappingList[axis].nativeAxis.sign == AXIS_SIGN_POSITIVE) {
|
|
signStr = "+";
|
|
} else {
|
|
signStr = "-";
|
|
}
|
|
dispName = fmt::format("{0}{1}##-{2}", PADGetNativeAxisName(axisMappingList[axis].nativeAxis), signStr, axis);
|
|
} else {
|
|
assert(axisMappingList[axis].nativeButton != -1);
|
|
dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis);
|
|
}
|
|
}
|
|
bool pressed = ImGui::Button(dispName.c_str(), btnSize);
|
|
|
|
if (pressed) {
|
|
m_controllerConfig.m_isReading = true;
|
|
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
|
|
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis];
|
|
PADBlockInput(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deadZones != nullptr) {
|
|
ImGui::Text("Dead Zone");
|
|
{
|
|
float tmp = static_cast<float>(deadZones->stickDeadZone * 100.f) / 32767.f;
|
|
if (ImGui::DragFloat("##mainDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
|
|
deadZones->stickDeadZone = static_cast<u16>((tmp / 100.f) * 32767);
|
|
PADSerializeMappings();
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGuiEndGroupPanel();
|
|
ImGui::SameLine();
|
|
|
|
// sub stick panel
|
|
ImGuiBeginGroupPanel("C Stick", ImVec2(150 * scale, 20 * scale));
|
|
|
|
drawVirtualStick("##subStick", ImVec2{ mDoCPd_c::getSubStickX(port), mDoCPd_c::getSubStickY(port) });
|
|
|
|
if (axisMappingList != nullptr) {
|
|
const PADAxis rStickAxes[] = {PAD_AXIS_RIGHT_Y_POS, PAD_AXIS_RIGHT_Y_NEG, PAD_AXIS_RIGHT_X_NEG, PAD_AXIS_RIGHT_X_POS};
|
|
for (auto axis : rStickAxes) {
|
|
const char* label = PADGetAxisDirectionLabel(axisMappingList[axis].padAxis);
|
|
ImVec2 len = ImGui::CalcTextSize(label);
|
|
ImVec2 pos = ImGui::GetCursorPos();
|
|
|
|
ImGui::SetCursorPosY(pos.y + len.y / 4);
|
|
ImGui::SetCursorPosX(pos.x + abs(len.x - uiButtonSize));
|
|
ImGui::Text("%s", label);
|
|
ImGui::SameLine();
|
|
|
|
ImGui::SetCursorPosY(pos.y);
|
|
|
|
std::string dispName;
|
|
if (m_controllerConfig.m_isReading && m_controllerConfig.m_pendingAxisMapping == &axisMappingList[axis]) {
|
|
dispName = fmt::format("Press a Key...##sub{}", label);
|
|
} else {
|
|
if (axisMappingList[axis].nativeAxis.nativeAxis != -1) {
|
|
const char* signStr;
|
|
if (axis == PAD_AXIS_TRIGGER_L || axis == PAD_AXIS_TRIGGER_R) {
|
|
signStr = "";
|
|
} else if (axisMappingList[axis].nativeAxis.sign == AXIS_SIGN_POSITIVE) {
|
|
signStr = "+";
|
|
} else {
|
|
signStr = "-";
|
|
}
|
|
dispName = fmt::format("{0}{1}##-{2}", PADGetNativeAxisName(axisMappingList[axis].nativeAxis), signStr, axis);
|
|
} else {
|
|
assert(axisMappingList[axis].nativeButton != -1);
|
|
dispName = fmt::format("{0}##-{1}", PADGetNativeButtonName(axisMappingList[axis].nativeButton), axis);
|
|
}
|
|
}
|
|
bool pressed = ImGui::Button(fmt::format("{0}##sub{1}", dispName, label).c_str(), btnSize);
|
|
|
|
if (pressed) {
|
|
m_controllerConfig.m_isReading = true;
|
|
m_controllerConfig.m_pendingPort = m_controllerConfig.m_selectedPort;
|
|
m_controllerConfig.m_pendingAxisMapping = &axisMappingList[axis];
|
|
PADBlockInput(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deadZones != nullptr) {
|
|
ImGui::Text("Dead Zone");
|
|
{
|
|
float tmp = static_cast<float>(deadZones->substickDeadZone * 100.f) / 32767.f;
|
|
if (ImGui::DragFloat("##subDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) {
|
|
deadZones->substickDeadZone = static_cast<u16>((tmp / 100.f) * 32767);
|
|
PADSerializeMappings();
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGuiEndGroupPanel();
|
|
ImGui::SameLine();
|
|
|
|
// Options panel
|
|
ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, -1));
|
|
|
|
if (deadZones != nullptr) {
|
|
if (ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones)) {
|
|
PADSerializeMappings();
|
|
}
|
|
if (ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers)) {
|
|
PADSerializeMappings();
|
|
}
|
|
}
|
|
|
|
if (PADSupportsRumbleIntensity(m_controllerConfig.m_selectedPort)) {
|
|
ImGuiBeginGroupPanel("Rumble Intensity", ImVec2(150 * scale, -1));
|
|
u16 low;
|
|
u16 high;
|
|
(void)PADGetRumbleIntensity(m_controllerConfig.m_selectedPort, &low, &high);
|
|
float fLow = (static_cast<float>(low) / 32767.f) * 100.f;
|
|
bool changed = ImGui::SliderFloat("Low", &fLow, 0.f, 100.f, "%.0f%%");
|
|
float fHigh = (static_cast<float>(high) / 32767.f) * 100.f;
|
|
changed |= ImGui::SliderFloat("High", &fHigh, 0.f, 100.f, "%.0f%%");
|
|
if (changed) {
|
|
PADSetRumbleIntensity(m_controllerConfig.m_selectedPort,
|
|
static_cast<u16>((fLow / 100) * 32767),
|
|
static_cast<u16>((fHigh / 100) * 32767));
|
|
PADSerializeMappings();
|
|
}
|
|
if (ImGui::Button(fmt::format("{0}...##rumbleTest", m_controllerConfig.m_isRumbling ? "Stop": "Test").c_str(), {-1, 0})) {
|
|
PADControlMotor(m_controllerConfig.m_selectedPort, !m_controllerConfig.m_isRumbling ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP_HARD);
|
|
m_controllerConfig.m_isRumbling ^= 1;
|
|
}
|
|
ImGuiEndGroupPanel();
|
|
}
|
|
ImGuiEndGroupPanel();
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
static std::string GetFormattedTime(OSTime ticks) {
|
|
OSCalendarTime time;
|
|
OSTicksToCalendarTime(ticks, &time);
|
|
|
|
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", time.hour, time.min, time.sec, time.msec);
|
|
}
|
|
|
|
void ImGuiMenuGame::resetForSpeedrunMode() {
|
|
// reset settings that should be off for speedrun mode
|
|
mDoMain::developmentMode = -1;
|
|
|
|
getSettings().game.damageMultiplier.setValue(1);
|
|
getSettings().game.instantDeath.setValue(false);
|
|
getSettings().game.noHeartDrops.setValue(false);
|
|
|
|
getSettings().game.infiniteHearts.setValue(false);
|
|
getSettings().game.infiniteArrows.setValue(false);
|
|
getSettings().game.infiniteBombs.setValue(false);
|
|
getSettings().game.infiniteOil.setValue(false);
|
|
getSettings().game.infiniteOxygen.setValue(false);
|
|
getSettings().game.infiniteRupees.setValue(false);
|
|
getSettings().game.enableIndefiniteItemDrops.setValue(false);
|
|
|
|
getSettings().game.moonJump.setValue(false);
|
|
getSettings().game.superClawshot.setValue(false);
|
|
getSettings().game.alwaysGreatspin.setValue(false);
|
|
getSettings().game.enableFastIronBoots.setValue(false);
|
|
getSettings().game.canTransformAnywhere.setValue(false);
|
|
getSettings().game.fastSpinner.setValue(false);
|
|
getSettings().game.freeMagicArmor.setValue(false);
|
|
|
|
getSettings().game.enableTurboKeybind.setValue(false);
|
|
}
|
|
|
|
SpeedrunInfo m_speedrunInfo;
|
|
|
|
void ImGuiMenuGame::drawSpeedrunTimerOverlay() {
|
|
if (!getSettings().game.speedrunMode) {
|
|
return;
|
|
}
|
|
|
|
// L+R+A+Start to reset timer
|
|
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigStart(PAD_1)) {
|
|
m_speedrunInfo.reset();
|
|
}
|
|
|
|
// L+R+A+Z to manually stop timer
|
|
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1)) {
|
|
if (m_speedrunInfo.m_isRunStarted) {
|
|
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
|
m_speedrunInfo.m_isRunStarted = false;
|
|
}
|
|
}
|
|
|
|
ImGui::SetNextWindowBgAlpha(0.65f);
|
|
ImGuiWindowFlags flags =
|
|
ImGuiWindowFlags_NoResize
|
|
| ImGuiWindowFlags_NoDocking
|
|
| ImGuiWindowFlags_NoTitleBar
|
|
| ImGuiWindowFlags_NoScrollbar;
|
|
|
|
if (ImGui::Begin("##SpeedrunTimerWindow", nullptr, flags)) {
|
|
OSTime elapsedTime = 0;
|
|
if (m_speedrunInfo.m_isRunStarted) {
|
|
elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
|
} else if (m_speedrunInfo.m_endTimestamp != 0) {
|
|
elapsedTime = m_speedrunInfo.m_endTimestamp;
|
|
}
|
|
|
|
ImGui::Text("RTA");
|
|
ImGui::SameLine(60.0f);
|
|
ImGuiStringViewText(GetFormattedTime(elapsedTime));
|
|
|
|
if (!m_speedrunInfo.m_isPauseIGT) {
|
|
m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime;
|
|
}
|
|
|
|
ImGui::Text("IGT");
|
|
ImGui::SameLine(60.0f);
|
|
ImGuiStringViewText(GetFormattedTime(m_speedrunInfo.m_igtTimer));
|
|
}
|
|
ImGui::End();
|
|
}
|
|
}
|