mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-28 23:15:32 -04:00
Merge branch 'main' of https://github.com/TwilitRealm/dusk into randomizer
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include "Z2AudioLib/Z2SoundInfo.h"
|
||||
#if TARGET_PC
|
||||
#include "dusk/audio/DuskDsp.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include <cmath>
|
||||
#endif
|
||||
#include "Z2AudioLib/Z2Calc.h"
|
||||
@@ -705,6 +706,11 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) {
|
||||
f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) {
|
||||
Vec local_54 = param_0;
|
||||
local_54.y = 0.0f;
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableMirrorMode) {
|
||||
local_54.x = -local_54.x;
|
||||
}
|
||||
#endif
|
||||
|
||||
f32 dVar6 = VECMag(&local_54);
|
||||
if (dVar6 < 0.1f) {
|
||||
|
||||
@@ -517,6 +517,12 @@ void daE_OctBg_c::core_fish_attack() {
|
||||
field_0xbaf = cM_rndFX(80.0f) + 100.0f;
|
||||
}
|
||||
}
|
||||
#if AVOID_UB
|
||||
else {
|
||||
in_f31 = cM_rndF(400.0f) + 80.0f;
|
||||
field_0xbaf = cM_rndFX(80.0f) + 100.0f;
|
||||
}
|
||||
#endif
|
||||
} else if (current.pos.abs(cStack_5c) < 400.0f) {
|
||||
in_f31 = cM_rndF(50.0f) + 20.0f;
|
||||
field_0xbaf = cM_rndFX(20.0f) + 40.0f;
|
||||
|
||||
@@ -1040,6 +1040,11 @@ void dCamera_c::debugDrawInit() {
|
||||
bool dCamera_c::Run() {
|
||||
#if TARGET_PC
|
||||
ResetView();
|
||||
if (executeDebugFlyCam()) {
|
||||
mFrameCounter++;
|
||||
mTicks++;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
@@ -7474,7 +7479,104 @@ bool dCamera_c::test2Camera(s32 param_0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr f32 FLYCAM_SPEED = 0.5f;
|
||||
static constexpr f32 FLYCAM_FAST_SPEED = 4.0f;
|
||||
static constexpr f32 FLYCAM_ROTATION_SPEED = 0.002f;
|
||||
static constexpr f32 FLYCAM_TRIGGER_DEADZONE = 20.0f;
|
||||
|
||||
#if TARGET_PC
|
||||
bool dCamera_c::executeDebugFlyCam() {
|
||||
if (!dusk::getSettings().game.debugFlyCam) {
|
||||
if (mDebugFlyCam.initialized) {
|
||||
deactivateDebugFlyCam();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
dEvt_control_c* event = dComIfGp_getEvent();
|
||||
if (event == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mDebugFlyCam.initialized && (event->mEventStatus != 0 || dComIfGp_isPauseFlag())) {
|
||||
dusk::getSettings().game.debugFlyCam.setValue(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mDebugFlyCam.initialized) {
|
||||
mDebugFlyCam.savedCenter = mCenter;
|
||||
mDebugFlyCam.savedEye = mEye;
|
||||
mDebugFlyCam.savedFovy = mFovy;
|
||||
mDebugFlyCam.savedBank = mBank;
|
||||
|
||||
f32 dx = mCenter.x - mEye.x;
|
||||
f32 dy = mCenter.y - mEye.y;
|
||||
f32 dz = mCenter.z - mEye.z;
|
||||
mDebugFlyCam.yaw = atan2f(dz, dx);
|
||||
f32 horizontal = sqrtf(dx * dx + dz * dz);
|
||||
mDebugFlyCam.pitch = atan2f(dy, horizontal);
|
||||
|
||||
mDebugFlyCam.initialized = true;
|
||||
}
|
||||
|
||||
event->mEventStatus = 1;
|
||||
dComIfGp_getEventManager().setCameraPlay(1);
|
||||
|
||||
interface_of_controller_pad& pad = mDoCPd_c::getCpadInfo(0);
|
||||
f32 stickY = pad.mMainStickPosY * 72.0f;
|
||||
f32 stickX = pad.mMainStickPosX * 72.0f;
|
||||
f32 cStickY = pad.mCStickPosY * 59.0f;
|
||||
f32 cStickX = pad.mCStickPosX * 59.0f;
|
||||
f32 trigL = pad.mTriggerLeft * 150.0f;
|
||||
f32 trigR = pad.mTriggerRight * 150.0f;
|
||||
|
||||
f32 verticalDisp = 0.0f;
|
||||
if (trigR >= FLYCAM_TRIGGER_DEADZONE) {
|
||||
verticalDisp += trigR;
|
||||
}
|
||||
if (trigL >= FLYCAM_TRIGGER_DEADZONE) {
|
||||
verticalDisp -= trigL;
|
||||
}
|
||||
|
||||
f32 moveDy = stickY * sinf(mDebugFlyCam.pitch) + verticalDisp;
|
||||
f32 moveDx = stickY * cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) - stickX * sinf(mDebugFlyCam.yaw);
|
||||
f32 moveDz = stickY * sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) + stickX * cosf(mDebugFlyCam.yaw);
|
||||
|
||||
f32 speed = mDoCPd_c::getHoldZ(PAD_1) ? FLYCAM_FAST_SPEED : FLYCAM_SPEED;
|
||||
|
||||
mEye.x += speed * moveDx;
|
||||
mEye.y += speed * moveDy;
|
||||
mEye.z += speed * moveDz;
|
||||
|
||||
static constexpr f32 FLYCAM_TARGET_DIST = 100.0f;
|
||||
mCenter.x = mEye.x + cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
|
||||
mCenter.z = mEye.z + sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
|
||||
mCenter.y = mEye.y + sinf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST;
|
||||
|
||||
Reset(mCenter, mEye);
|
||||
|
||||
f32 yawInput = dusk::getSettings().game.invertCameraXAxis ? cStickX : -cStickX;
|
||||
mDebugFlyCam.yaw += yawInput * FLYCAM_ROTATION_SPEED;
|
||||
mDebugFlyCam.yaw = fmodf(mDebugFlyCam.yaw + 2.0f * (f32)M_PI, 2.0f * (f32)M_PI);
|
||||
|
||||
f32 maxPitch = (f32)M_PI / 2.0f - 0.1f;
|
||||
f32 minPitch = -(f32)M_PI / 2.0f + 0.1f;
|
||||
mDebugFlyCam.pitch = std::clamp(mDebugFlyCam.pitch + cStickY * FLYCAM_ROTATION_SPEED, minPitch, maxPitch);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void dCamera_c::deactivateDebugFlyCam() {
|
||||
Reset(mDebugFlyCam.savedCenter, mDebugFlyCam.savedEye, mDebugFlyCam.savedFovy, mDebugFlyCam.savedBank.Val());
|
||||
|
||||
dEvt_control_c* event = dComIfGp_getEvent();
|
||||
if (event != nullptr) {
|
||||
event->mEventStatus = 0;
|
||||
}
|
||||
dComIfGp_getEventManager().setCameraPlay(0);
|
||||
mDebugFlyCam.initialized = false;
|
||||
}
|
||||
|
||||
bool dCamera_c::freeCamera() {
|
||||
if (dusk::getSettings().game.freeCamera && mGear == 1) {
|
||||
mGear = 0;
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
#include "ImGuiAchievements.hpp"
|
||||
#include "ImGuiConfig.hpp"
|
||||
#include "dusk/achievements.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "fmt/format.h"
|
||||
#include "imgui.h"
|
||||
|
||||
namespace dusk {
|
||||
|
||||
void ImGuiAchievements::notify(std::string name) {
|
||||
if (m_notifyTimer <= 0.f) {
|
||||
m_notifyName = std::move(name);
|
||||
m_notifyTimer = NOTIFY_DURATION;
|
||||
} else {
|
||||
m_notifyQueue.push(std::move(name));
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiAchievements::showNotification() {
|
||||
if (!getSettings().game.enableAchievementNotifications.getValue()) {
|
||||
return;
|
||||
}
|
||||
if (m_notifyTimer <= 0.f) {
|
||||
if (m_notifyQueue.empty()) {
|
||||
return;
|
||||
}
|
||||
m_notifyName = std::move(m_notifyQueue.front());
|
||||
m_notifyQueue.pop();
|
||||
m_notifyTimer = NOTIFY_DURATION;
|
||||
}
|
||||
|
||||
m_notifyTimer -= ImGui::GetIO().DeltaTime;
|
||||
|
||||
const float alpha = std::min({
|
||||
m_notifyTimer / NOTIFY_FADE_TIME,
|
||||
(NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME,
|
||||
1.0f
|
||||
});
|
||||
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
const float padding = 12.0f;
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding),
|
||||
ImGuiCond_Always, ImVec2(1.0f, 0.0f)
|
||||
);
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(alpha * 0.92f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f));
|
||||
|
||||
constexpr ImGuiWindowFlags flags =
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs;
|
||||
|
||||
if (ImGui::Begin("##achievement_notify", nullptr, flags)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha));
|
||||
ImGui::TextUnformatted("Achievement Unlocked!");
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted(m_notifyName.c_str());
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
|
||||
void ImGuiAchievements::draw(bool& open) {
|
||||
showNotification();
|
||||
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(800, 200), ImVec2(1280, 900));
|
||||
ImGui::SetNextWindowSize(ImVec2(800, 480), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (!ImGui::Begin(
|
||||
"Achievements", &open,
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)
|
||||
)
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto achievements = AchievementSystem::get().getAchievements();
|
||||
|
||||
int unlocked = 0;
|
||||
for (const auto& a : achievements) {
|
||||
if (a.unlocked) {
|
||||
++unlocked;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("%d / %d achievements unlocked", unlocked, (int)achievements.size());
|
||||
ImGui::SameLine();
|
||||
config::ImGuiCheckbox("Notifications", getSettings().game.enableAchievementNotifications);
|
||||
ImGui::Separator();
|
||||
|
||||
static const struct {
|
||||
AchievementCategory cat;
|
||||
const char* label;
|
||||
ImVec4 color;
|
||||
} ACHIEVEMENT_CATEGORIES[] = {
|
||||
{AchievementCategory::Story, "Story", ImVec4(1.0f, 0.82f, 0.1f, 1.0f)},
|
||||
{AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)},
|
||||
{AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)},
|
||||
{AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)},
|
||||
{AchievementCategory::Misc, "Misc", ImVec4(0.65f, 0.65f, 0.65f, 1.0f)},
|
||||
{AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)},
|
||||
};
|
||||
|
||||
const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
|
||||
|
||||
if (ImGui::BeginTabBar("##achievement_tabs", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||
for (const auto& catInfo : ACHIEVEMENT_CATEGORIES) {
|
||||
int catTotal = 0, catUnlocked = 0;
|
||||
for (const auto& a : achievements) {
|
||||
if (a.category == catInfo.cat) {
|
||||
++catTotal;
|
||||
if (a.unlocked) {
|
||||
++catUnlocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (catTotal == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string tabLabel = fmt::format("{} ({}/{})###{}", catInfo.label, catUnlocked, catTotal, catInfo.label);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color);
|
||||
const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (tabOpen) {
|
||||
ImGui::BeginChild(
|
||||
"##cat_list",
|
||||
ImVec2(0, -footerHeight),
|
||||
ImGuiChildFlags_None,
|
||||
ImGuiWindowFlags_NoBackground
|
||||
);
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
for (const auto& a : achievements) {
|
||||
if (a.category != catInfo.cat) {
|
||||
continue;
|
||||
}
|
||||
ImGui::PushID(a.key);
|
||||
ImGui::BeginGroup();
|
||||
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_Text,
|
||||
a.unlocked ?
|
||||
ImVec4(1.0f, 0.65f, 0.15f, 1.0f) :
|
||||
ImGui::GetStyleColorVec4(ImGuiCol_Text)
|
||||
);
|
||||
|
||||
ImGui::TextUnformatted(a.name);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
const char* statusLabel = a.unlocked ? "[Unlocked]" : "[Locked]";
|
||||
ImGui::SameLine(
|
||||
ImGui::GetContentRegionMax().x -
|
||||
ImGui::CalcTextSize(statusLabel).x
|
||||
);
|
||||
|
||||
if (a.unlocked) {
|
||||
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", statusLabel);
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), "%s", statusLabel);
|
||||
}
|
||||
|
||||
ImGui::TextDisabled("%s", a.description);
|
||||
|
||||
if (a.isCounter) {
|
||||
const float fraction = a.goal > 0 ? (float)(a.progress) / (float)(a.goal) : 1.0f;
|
||||
const std::string overlay = fmt::format("{} / {}", a.progress, a.goal);
|
||||
ImGui::PushStyleColor(
|
||||
ImGuiCol_PlotHistogram,
|
||||
a.unlocked ?
|
||||
ImVec4(0.4f, 0.7f, 0.1f, 1.0f) :
|
||||
ImVec4(0.2f, 0.45f, 0.8f, 1.0f)
|
||||
);
|
||||
ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), overlay.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::EndGroup();
|
||||
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
ImGui::OpenPopup("##ctx");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("##ctx")) {
|
||||
ImGui::TextDisabled("%s", a.name);
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Clear Achievement")) {
|
||||
AchievementSystem::get().clearOne(a.key);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Clear All Achievements")) {
|
||||
ImGui::OpenPopup("##confirm_clear");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("##confirm_clear")) {
|
||||
ImGui::Text("Reset all achievement progress?");
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button("Yes, reset all")) {
|
||||
AchievementSystem::get().clearAll();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
class ImGuiAchievements {
|
||||
public:
|
||||
void draw(bool& open);
|
||||
void notify(std::string name);
|
||||
|
||||
private:
|
||||
void showNotification();
|
||||
|
||||
std::string m_notifyName;
|
||||
float m_notifyTimer = 0.f;
|
||||
std::queue<std::string> m_notifyQueue;
|
||||
static constexpr float NOTIFY_DURATION = 4.0f;
|
||||
static constexpr float NOTIFY_FADE_TIME = 0.5f;
|
||||
};
|
||||
|
||||
} // namespace dusk
|
||||
@@ -1,9 +1,12 @@
|
||||
#include "f_op/f_op_camera_mng.h"
|
||||
#include "SSystem/SComponent/c_xyz.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiConfig.hpp"
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "ImGuiMenuTools.hpp"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
namespace dusk {
|
||||
void ImGuiMenuTools::ShowCameraOverlay() {
|
||||
@@ -46,70 +49,25 @@ namespace dusk {
|
||||
|
||||
ImGui::InputFloat("Camera FOV", &dCam->mFovy);
|
||||
|
||||
ImGui::SeparatorText("Free-look Data");
|
||||
ImGui::SeparatorText("Options");
|
||||
|
||||
static float eyeYawDeg = 0.0f;
|
||||
static float moveSpeed = 5000.0f;
|
||||
static float rotSpeed = 5.0f;
|
||||
static cXyz freeLookPos = cXyz::Zero;
|
||||
static bool freeLookActive = false;
|
||||
|
||||
bool changed = false;
|
||||
|
||||
if (ImGui::IsKeyDown(ImGuiKey_LeftArrow)) {
|
||||
eyeYawDeg += rotSpeed;
|
||||
if (eyeYawDeg >= 360.0f)
|
||||
eyeYawDeg -= 360.0f;
|
||||
|
||||
changed = true;
|
||||
bool eventRunning = (dComIfGp_event_runCheck() || dComIfGp_isPauseFlag()) && !getSettings().game.debugFlyCam;
|
||||
if (eventRunning) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
else if (ImGui::IsKeyDown(ImGuiKey_RightArrow)) {
|
||||
eyeYawDeg -= rotSpeed;
|
||||
if (eyeYawDeg < 0.0f)
|
||||
eyeYawDeg += 360.0f;
|
||||
|
||||
changed = true;
|
||||
config::ImGuiCheckbox("Fly Mode", getSettings().game.debugFlyCam);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
||||
if (eventRunning) {
|
||||
ImGui::SetTooltip("Cannot enable while paused or during an active event.");
|
||||
} else {
|
||||
ImGui::SetTooltip("Detach camera and fly freely.\n"
|
||||
"Left stick: move, C-stick: look\n"
|
||||
"L/R triggers: up/down, Z: fast");
|
||||
}
|
||||
}
|
||||
cSAngle yawAngle = cSAngle(eyeYawDeg);
|
||||
cXyz frontDir = cXyz(yawAngle.Sin(), 0.0f, yawAngle.Cos());
|
||||
|
||||
if (ImGui::IsKeyDown(ImGuiKey_UpArrow)) {
|
||||
freeLookPos -= frontDir * moveSpeed * ImGui::GetIO().DeltaTime;
|
||||
changed = true;
|
||||
if (eventRunning) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
else if (ImGui::IsKeyDown(ImGuiKey_DownArrow)) {
|
||||
freeLookPos += frontDir * moveSpeed * ImGui::GetIO().DeltaTime;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) {
|
||||
freeLookPos += cXyz::BaseY * moveSpeed * ImGui::GetIO().DeltaTime;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {
|
||||
freeLookPos -= cXyz::BaseY * moveSpeed * ImGui::GetIO().DeltaTime;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!freeLookActive && changed) {
|
||||
freeLookPos += dCam->Center();
|
||||
freeLookActive = true;
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyDown(ImGuiKey_R)) {
|
||||
freeLookPos = cXyz::Zero;
|
||||
freeLookActive = false;
|
||||
}
|
||||
|
||||
if (freeLookActive) {
|
||||
dCam->Reset(freeLookPos, freeLookPos + (frontDir * 100.0f));
|
||||
}
|
||||
|
||||
ImGui::InputFloat("Free-look Yaw", &eyeYawDeg);
|
||||
ImGui::InputFloat3("Free-look Position", &freeLookPos.x);
|
||||
ImGui::InputFloat("Free-look Move Speed", &moveSpeed);
|
||||
ImGui::InputFloat("Free-look Rotation Speed", &rotSpeed);
|
||||
|
||||
ShowCornerContextMenu(m_cameraOverlayCorner, 0);
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "dusk/ui/preset.hpp"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "JSystem/JUtility/JUTGamePad.h"
|
||||
#include "SDL3/SDL_events.h"
|
||||
#include "SDL3/SDL_mouse.h"
|
||||
#include "aurora/lib/window.hpp"
|
||||
#include "dusk/achievements.h"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/config.hpp"
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
#include "f_pc/f_pc_name.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
@@ -35,14 +37,6 @@ using namespace std::string_literals;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace {
|
||||
ImVec2 TouchEventToScreenPos(const SDL_TouchFingerEvent& touch) {
|
||||
const AuroraWindowSize size = aurora::window::get_window_size();
|
||||
return ImVec2{
|
||||
touch.x * static_cast<float>(size.width),
|
||||
touch.y * static_cast<float>(size.height),
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) {
|
||||
while (window != nullptr) {
|
||||
const bool canScrollX = window->ScrollMax.x > 0.0f;
|
||||
@@ -241,48 +235,7 @@ namespace dusk {
|
||||
ImGuiConsole::ImGuiConsole() {}
|
||||
|
||||
void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) {
|
||||
if (!IsGameLaunched) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
if (!m_touchTapActive) {
|
||||
m_touchTapActive = true;
|
||||
m_touchTapMoved = false;
|
||||
m_touchTapFingerId = event.tfinger.fingerID;
|
||||
m_touchTapStartPos = TouchEventToScreenPos(event.tfinger);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) {
|
||||
const auto currentPos = TouchEventToScreenPos(event.tfinger);
|
||||
const auto delta = currentPos - m_touchTapStartPos;
|
||||
if (ImLengthSqr(delta) > 144.0f) {
|
||||
m_touchTapMoved = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) {
|
||||
const bool shouldToggle =
|
||||
!m_touchTapMoved && (m_isHidden || !ImGui::GetIO().WantCaptureMouse);
|
||||
m_touchTapActive = false;
|
||||
m_touchTapMoved = false;
|
||||
if (shouldToggle) {
|
||||
m_isHidden = !m_isHidden;
|
||||
getSettings().backend.duskMenuOpen.setValue(!m_isHidden);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_FINGER_CANCELED:
|
||||
m_touchTapActive = false;
|
||||
m_touchTapMoved = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
(void)event;
|
||||
}
|
||||
|
||||
void ImGuiConsole::UpdateSettings() {
|
||||
@@ -310,7 +263,8 @@ namespace dusk {
|
||||
}
|
||||
}
|
||||
|
||||
if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
|
||||
if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) &&
|
||||
(ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
|
||||
ImGui::IsKeyPressed(ImGuiKey_R))
|
||||
{
|
||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||
@@ -324,15 +278,10 @@ namespace dusk {
|
||||
// m_preLaunchWindow.draw();
|
||||
// }
|
||||
|
||||
m_isHidden = !getSettings().backend.duskMenuOpen;
|
||||
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
|
||||
m_isHidden = !m_isHidden;
|
||||
}
|
||||
bool showMenu = !m_isHidden;
|
||||
if (getSettings().backend.duskMenuOpen != showMenu) {
|
||||
getSettings().backend.duskMenuOpen.setValue(showMenu);
|
||||
Save();
|
||||
}
|
||||
|
||||
// The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg,
|
||||
// so make the window bg fully transparent temporarily
|
||||
@@ -356,14 +305,9 @@ namespace dusk {
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (!getSettings().backend.wasPresetChosen) {
|
||||
m_firstRunPreset.draw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
|
||||
AddToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ?
|
||||
"Tap to toggle menu"s :
|
||||
"3-finger tap to toggle menu"s :
|
||||
"Press F1 to toggle menu"s,
|
||||
4.f);
|
||||
m_isLaunchInitialized = true;
|
||||
@@ -399,10 +343,9 @@ namespace dusk {
|
||||
m_menuTools.ShowSaveEditor();
|
||||
m_menuTools.ShowStateShare();
|
||||
}
|
||||
m_menuTools.ShowStateShare();
|
||||
m_menuRandomizer.windowRandoStats();
|
||||
m_menuRandomizer.windowRandoGeneration();
|
||||
m_menuTools.ShowAchievements();
|
||||
m_menuTools.showAchievementNotification();
|
||||
DuskDebugPad(); // temporary, remove later
|
||||
|
||||
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
#include <string_view>
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <SDL3/SDL_touch.h>
|
||||
|
||||
#include "ImGuiFirstRunPreset.hpp"
|
||||
#include "ImGuiMenuGame.hpp"
|
||||
#include "ImGuiMenuTools.hpp"
|
||||
#include "ImGuiMenuRandomizer.hpp"
|
||||
@@ -43,15 +41,10 @@ private:
|
||||
|
||||
bool m_isHidden = true;
|
||||
bool m_isLaunchInitialized = false;
|
||||
bool m_touchTapActive = false;
|
||||
bool m_touchTapMoved = false;
|
||||
SDL_FingerID m_touchTapFingerId = 0;
|
||||
ImVec2 m_touchTapStartPos = {};
|
||||
ImGuiWindow* m_dragScrollWindow = nullptr;
|
||||
ImVec2 m_dragScrollLastMousePos = {};
|
||||
std::deque<Toast> m_toasts;
|
||||
|
||||
ImGuiFirstRunPreset m_firstRunPreset;
|
||||
ImGuiMenuGame m_menuGame;
|
||||
ImGuiMenuRandomizer m_menuRandomizer;
|
||||
ImGuiPreLaunchWindow m_preLaunchWindow;
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
#include "ImGuiFirstRunPreset.hpp"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "ImGuiEngine.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/config.hpp"
|
||||
#include <dusk/dusk.h>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
static void ApplyPresetClassic() {
|
||||
auto& s = getSettings();
|
||||
s.video.lockAspectRatio.setValue(true);
|
||||
s.game.bloomMode.setValue(BloomMode::Classic);
|
||||
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
|
||||
}
|
||||
|
||||
static void ApplyPresetHD() {
|
||||
auto& s = getSettings();
|
||||
s.game.bloomMode.setValue(BloomMode::Classic);
|
||||
s.game.hideTvSettingsScreen.setValue(true);
|
||||
s.game.skipWarningScreen.setValue(true);
|
||||
s.game.noReturnRupees.setValue(true);
|
||||
s.game.disableRupeeCutscenes.setValue(true);
|
||||
s.game.noSwordRecoil.setValue(true);
|
||||
s.game.fastClimbing.setValue(true);
|
||||
s.game.noMissClimbing.setValue(true);
|
||||
s.game.fastTears.setValue(true);
|
||||
s.game.biggerWallets.setValue(true);
|
||||
s.game.invertCameraXAxis.setValue(true);
|
||||
s.game.freeCamera.setValue(true);
|
||||
s.game.no2ndFishForCat.setValue(true);
|
||||
}
|
||||
|
||||
static void ApplyPresetDusk() {
|
||||
ApplyPresetHD();
|
||||
|
||||
auto& s = getSettings();
|
||||
s.game.enableAchievementNotifications.setValue(true);
|
||||
s.game.enableQuickTransform.setValue(true);
|
||||
s.game.instantSaves.setValue(true);
|
||||
s.game.midnasLamentNonStop.setValue(true);
|
||||
s.game.enableFrameInterpolation.setValue(true);
|
||||
s.game.sunsSong.setValue(true);
|
||||
s.game.bloomMode.setValue(BloomMode::Dusk);
|
||||
s.game.autoSave.setValue(true);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
||||
void ImGuiFirstRunPreset::draw() {
|
||||
const char* modalTitle = "Welcome to Dusk!";
|
||||
|
||||
if (m_done) return;
|
||||
|
||||
if (!m_opened) {
|
||||
ImGui::OpenPopup(modalTitle);
|
||||
m_opened = true;
|
||||
}
|
||||
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowSize(ImVec2(800.0f * ImGuiScale(), 0.0f), ImGuiCond_Always);
|
||||
|
||||
if (!ImGui::BeginPopupModal(modalTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
|
||||
// force the user to actually pick one, and not just hit escape to skip the dialog
|
||||
m_opened = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::TextWrapped("Choose a preset to get started. You can change any setting later from the Enhancements menu.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
int chosen = -1;
|
||||
|
||||
if (ImGui::BeginTable("##presets", 5, ImGuiTableFlags_None)) {
|
||||
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale());
|
||||
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale());
|
||||
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::PushFont(ImGuiEngine::fontLarge);
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
if (ImGui::Button("Classic##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) {
|
||||
chosen = 0;
|
||||
}
|
||||
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
if (ImGui::Button("HD##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) {
|
||||
chosen = 1;
|
||||
}
|
||||
|
||||
ImGui::TableSetColumnIndex(4);
|
||||
if (ImGui::Button("Dusk##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale())))
|
||||
{
|
||||
chosen = 2;
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("All enhancements disabled to match the GameCube version. Good for speedrunning or simple nostalgia!");
|
||||
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("Some enhancements enabled to match the HD version. A good starting point for most players!");
|
||||
|
||||
ImGui::TableSetColumnIndex(4);
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("More enhancements enabled than the HD preset. Veteran players will appreciate the additional tweaks!");
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
if (chosen >= 0) {
|
||||
if (chosen == 0) ApplyPresetClassic();
|
||||
if (chosen == 1) ApplyPresetHD();
|
||||
if (chosen == 2) ApplyPresetDusk();
|
||||
|
||||
getSettings().backend.wasPresetChosen.setValue(true);
|
||||
config::Save();
|
||||
|
||||
m_done = true;
|
||||
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace dusk {
|
||||
|
||||
class ImGuiFirstRunPreset {
|
||||
public:
|
||||
void draw();
|
||||
|
||||
private:
|
||||
bool m_opened = false;
|
||||
bool m_done = false;
|
||||
};
|
||||
|
||||
} // namespace dusk
|
||||
@@ -601,6 +601,7 @@ namespace dusk {
|
||||
getSettings().game.freeMagicArmor.setValue(false);
|
||||
|
||||
getSettings().game.enableTurboKeybind.setValue(false);
|
||||
getSettings().game.debugFlyCam.setValue(false);
|
||||
}
|
||||
|
||||
SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
@@ -66,7 +66,6 @@ namespace dusk {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
ImGui::MenuItem("Achievements", nullptr, &m_showAchievements);
|
||||
|
||||
#if DUSK_CAN_OPEN_DATA_FOLDER
|
||||
ImGui::Separator();
|
||||
@@ -269,11 +268,65 @@ namespace dusk {
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
void ImGuiMenuTools::ShowAchievements() {
|
||||
m_achievementsWindow.draw(m_showAchievements);
|
||||
void ImGuiMenuTools::notifyAchievement(std::string name) {
|
||||
if (m_notifyTimer <= 0.f) {
|
||||
m_notifyName = std::move(name);
|
||||
m_notifyTimer = NOTIFY_DURATION;
|
||||
} else {
|
||||
m_notifyQueue.push(std::move(name));
|
||||
}
|
||||
}
|
||||
|
||||
void ImGuiMenuTools::notifyAchievement(std::string name) {
|
||||
m_achievementsWindow.notify(std::move(name));
|
||||
void ImGuiMenuTools::showAchievementNotification() {
|
||||
if (!getSettings().game.enableAchievementNotifications.getValue()) {
|
||||
return;
|
||||
}
|
||||
if (m_notifyTimer <= 0.f) {
|
||||
if (m_notifyQueue.empty()) {
|
||||
return;
|
||||
}
|
||||
m_notifyName = std::move(m_notifyQueue.front());
|
||||
m_notifyQueue.pop();
|
||||
m_notifyTimer = NOTIFY_DURATION;
|
||||
}
|
||||
|
||||
m_notifyTimer -= ImGui::GetIO().DeltaTime;
|
||||
|
||||
const float alpha = std::min({
|
||||
m_notifyTimer / NOTIFY_FADE_TIME,
|
||||
(NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME,
|
||||
1.0f
|
||||
});
|
||||
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
const float padding = 12.0f;
|
||||
ImGui::SetNextWindowPos(
|
||||
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding),
|
||||
ImGuiCond_Always, ImVec2(1.0f, 0.0f)
|
||||
);
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(alpha * 0.92f);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f));
|
||||
|
||||
constexpr ImGuiWindowFlags flags =
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs;
|
||||
|
||||
if (ImGui::Begin("##achievement_notify", nullptr, flags)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha));
|
||||
ImGui::TextUnformatted("Achievement Unlocked!");
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted(m_notifyName.c_str());
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
#define DUSK_IMGUI_MENUTOOLS_HPP
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiAchievements.hpp"
|
||||
#include "ImGuiSaveEditor.hpp"
|
||||
#include "ImGuiStateShare.hpp"
|
||||
|
||||
@@ -27,8 +27,8 @@ namespace dusk {
|
||||
void ShowAudioDebug();
|
||||
void ShowSaveEditor();
|
||||
void ShowStateShare();
|
||||
void ShowAchievements();
|
||||
void notifyAchievement(std::string name);
|
||||
void showAchievementNotification();
|
||||
|
||||
private:
|
||||
bool m_showDebugOverlay = false;
|
||||
@@ -69,8 +69,11 @@ namespace dusk {
|
||||
bool m_showStateShare = false;
|
||||
ImGuiStateShare m_stateShare;
|
||||
|
||||
bool m_showAchievements = false;
|
||||
ImGuiAchievements m_achievementsWindow;
|
||||
std::string m_notifyName;
|
||||
float m_notifyTimer = 0.f;
|
||||
std::queue<std::string> m_notifyQueue;
|
||||
static constexpr float NOTIFY_DURATION = 4.0f;
|
||||
static constexpr float NOTIFY_FADE_TIME = 0.5f;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ UserSettings g_userSettings = {
|
||||
.fanfareVolume {"audio.fanfareVolume", 100},
|
||||
.enableReverb {"audio.enableReverb", true},
|
||||
.enableHrtf {"audio.enableHrtf", false},
|
||||
.menuSounds {"audio.menuSounds", true},
|
||||
},
|
||||
|
||||
.game = {
|
||||
@@ -25,8 +26,8 @@ UserSettings g_userSettings = {
|
||||
|
||||
// Quality of Life
|
||||
.enableQuickTransform {"game.enableQuickTransform", false},
|
||||
.hideTvSettingsScreen {"game.hideTvSettingsScreen", false},
|
||||
.skipWarningScreen {"game.skipWarningScreen", false},
|
||||
.hideTvSettingsScreen {"game.hideTvSettingsScreen", true},
|
||||
.skipWarningScreen {"game.skipWarningScreen", true},
|
||||
.biggerWallets {"game.biggerWallets", false},
|
||||
.noReturnRupees {"game.noReturnRupees", false},
|
||||
.disableRupeeCutscenes {"game.disableRupeeCutscenes", false},
|
||||
@@ -47,11 +48,11 @@ UserSettings g_userSettings = {
|
||||
.enableMirrorMode {"game.enableMirrorMode", false},
|
||||
.disableMainHUD {"game.disableMainHUD", false},
|
||||
.pauseOnFocusLost {"game.pauseOnFocusLost", false},
|
||||
.enableLinkDollRotation = {"game.enableLinkDollRotation", false },
|
||||
.enableAchievementNotifications {"game.enableAchievementNotifications", false},
|
||||
.enableLinkDollRotation = {"game.enableLinkDollRotation", false},
|
||||
.enableAchievementNotifications {"game.enableAchievementNotifications", true},
|
||||
|
||||
// Graphics
|
||||
.bloomMode {"game.bloomMode", BloomMode::Classic},
|
||||
.bloomMode {"game.bloomMode", BloomMode::Dusk},
|
||||
.bloomMultiplier {"game.bloomMultiplier", 1.0f},
|
||||
.disableWaterRefraction {"game.disableWaterRefraction", false},
|
||||
.enableFrameInterpolation {"game.enableFrameInterpolation", false},
|
||||
@@ -78,6 +79,7 @@ UserSettings g_userSettings = {
|
||||
.invertCameraXAxis {"game.invertCameraXAxis", false},
|
||||
.invertCameraYAxis {"game.invertCameraYAxis", false},
|
||||
.freeCameraSensitivity {"game.freeCameraSensitivity", 1.0f},
|
||||
.debugFlyCam {"game.debugFlyCam", false},
|
||||
|
||||
// Cheats
|
||||
.infiniteHearts {"game.infiniteHearts", false},
|
||||
@@ -113,7 +115,6 @@ UserSettings g_userSettings = {
|
||||
.showPipelineCompilation {"backend.showPipelineCompilation", false},
|
||||
.wasPresetChosen {"backend.wasPresetChosen", false},
|
||||
.enableCrashReporting {"backend.enableCrashReporting", true},
|
||||
.duskMenuOpen {"backend.duskMenuOpen", false},
|
||||
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)}
|
||||
}
|
||||
};
|
||||
@@ -136,6 +137,7 @@ void registerSettings() {
|
||||
Register(g_userSettings.audio.fanfareVolume);
|
||||
Register(g_userSettings.audio.enableReverb);
|
||||
Register(g_userSettings.audio.enableHrtf);
|
||||
Register(g_userSettings.audio.menuSounds);
|
||||
|
||||
// Game
|
||||
Register(g_userSettings.game.language);
|
||||
@@ -203,6 +205,7 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.gyroInvertPitch);
|
||||
Register(g_userSettings.game.gyroInvertYaw);
|
||||
Register(g_userSettings.game.freeCamera);
|
||||
Register(g_userSettings.game.debugFlyCam);
|
||||
|
||||
Register(g_userSettings.backend.isoPath);
|
||||
Register(g_userSettings.backend.graphicsBackend);
|
||||
@@ -210,7 +213,6 @@ void registerSettings() {
|
||||
Register(g_userSettings.backend.showPipelineCompilation);
|
||||
Register(g_userSettings.backend.wasPresetChosen);
|
||||
Register(g_userSettings.backend.enableCrashReporting);
|
||||
Register(g_userSettings.backend.duskMenuOpen);
|
||||
Register(g_userSettings.backend.cardFileType);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
#include "achievements.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "dusk/achievements.h"
|
||||
#include "fmt/format.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
#include "nav_types.hpp"
|
||||
#include "pane.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
struct CategoryInfo {
|
||||
AchievementCategory cat;
|
||||
const char* label;
|
||||
};
|
||||
|
||||
constexpr CategoryInfo kCategories[] = {
|
||||
{AchievementCategory::Story, "Story"},
|
||||
{AchievementCategory::Collection, "Collection"},
|
||||
{AchievementCategory::Challenge, "Challenge"},
|
||||
{AchievementCategory::Minigame, "Minigame"},
|
||||
{AchievementCategory::Misc, "Misc"},
|
||||
{AchievementCategory::Glitched, "Glitched"},
|
||||
};
|
||||
|
||||
Rml::String build_achievement_info_rml(const Achievement& a) {
|
||||
Rml::String s = fmt::format(
|
||||
R"(<div class="achievement-header">)"
|
||||
R"(<span class="achievement-name{}">{}</span>)"
|
||||
R"(<span class="achievement-badge{}">{}</span>)"
|
||||
R"(</div>)"
|
||||
R"(<p class="achievement-desc">{}</p>)",
|
||||
a.unlocked ? " unlocked" : "",
|
||||
a.name,
|
||||
a.unlocked ? " unlocked" : " locked",
|
||||
a.unlocked ? "Unlocked" : "Locked",
|
||||
a.description
|
||||
);
|
||||
|
||||
if (a.isCounter) {
|
||||
float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f;
|
||||
s += fmt::format(
|
||||
R"(<progressbar value="{:.3f}" class="{}"/>)"
|
||||
R"(<span class="achievement-progress">{} / {}</span>)",
|
||||
fraction,
|
||||
a.unlocked ? "progress-done" : "progress-ongoing",
|
||||
a.progress,
|
||||
a.goal
|
||||
);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
class AchievementRow : public FluentComponent<AchievementRow> {
|
||||
public:
|
||||
AchievementRow(Rml::Element* parent, const Achievement& a)
|
||||
: FluentComponent(createRowRoot(parent))
|
||||
{
|
||||
auto& btn = add_child<Button>(Button::Props{"×"});
|
||||
mClearButton = &btn;
|
||||
btn.root()->SetClass("achievement-clear", true);
|
||||
|
||||
btn.on_nav_command([this, key = std::string(a.key)](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
if (mConfirming) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
|
||||
AchievementSystem::get().clearOne(key.c_str());
|
||||
resetConfirm();
|
||||
} else {
|
||||
mConfirming = true;
|
||||
mClearButton->set_text("Clear?");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (cmd == NavCommand::Cancel && mConfirming) {
|
||||
resetConfirm();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
Component::listen(btn.root(), Rml::EventId::Blur, [this](Rml::Event&) {
|
||||
resetConfirm();
|
||||
});
|
||||
|
||||
auto* infoDiv = append(mRoot, "div");
|
||||
infoDiv->SetClass("achievement-info", true);
|
||||
infoDiv->SetInnerRML(build_achievement_info_rml(a));
|
||||
}
|
||||
|
||||
bool focus() override { return mClearButton->focus(); }
|
||||
|
||||
private:
|
||||
static Rml::Element* createRowRoot(Rml::Element* parent) {
|
||||
auto* doc = parent->GetOwnerDocument();
|
||||
auto elem = doc->CreateElement("div");
|
||||
elem->SetClass("achievement-row", true);
|
||||
return parent->AppendChild(std::move(elem));
|
||||
}
|
||||
|
||||
void resetConfirm() {
|
||||
mConfirming = false;
|
||||
mClearButton->set_text("×");
|
||||
}
|
||||
|
||||
Button* mClearButton = nullptr;
|
||||
bool mConfirming = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
AchievementsWindow::AchievementsWindow() {
|
||||
const auto all = AchievementSystem::get().getAchievements();
|
||||
|
||||
for (const auto& catInfo : kCategories) {
|
||||
int catTotal = 0;
|
||||
for (const auto& a : all) {
|
||||
if (a.category == catInfo.cat) {
|
||||
++catTotal;
|
||||
}
|
||||
}
|
||||
if (catTotal == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
add_tab(catInfo.label, [this, cat = catInfo.cat](Rml::Element* content) {
|
||||
const auto achievements = AchievementSystem::get().getAchievements();
|
||||
|
||||
int total = 0, unlocked = 0;
|
||||
for (const auto& a : achievements) {
|
||||
if (a.category == cat) {
|
||||
++total;
|
||||
if (a.unlocked) {
|
||||
++unlocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto& pane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
|
||||
pane.add_section(fmt::format("{} / {} unlocked", unlocked, total));
|
||||
|
||||
for (const auto& a : achievements) {
|
||||
if (a.category != cat) {
|
||||
continue;
|
||||
}
|
||||
pane.add_child<AchievementRow>(a);
|
||||
}
|
||||
|
||||
pane.add_section("Actions");
|
||||
|
||||
auto& clearAllBtn = pane.add_button("Clear All Achievements");
|
||||
auto* clearAllPtr = &clearAllBtn;
|
||||
auto confirmingAll = std::make_shared<bool>(false);
|
||||
|
||||
clearAllBtn.on_nav_command([clearAllPtr, confirmingAll](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
if (*confirmingAll) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
|
||||
AchievementSystem::get().clearAll();
|
||||
*confirmingAll = false;
|
||||
clearAllPtr->set_text("Clear All Achievements");
|
||||
} else {
|
||||
*confirmingAll = true;
|
||||
clearAllPtr->set_text("Are you sure?");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (cmd == NavCommand::Cancel && *confirmingAll) {
|
||||
*confirmingAll = false;
|
||||
clearAllPtr->set_text("Clear All Achievements");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
clearAllBtn.listen(Rml::EventId::Blur, [clearAllPtr, confirmingAll](Rml::Event&) {
|
||||
*confirmingAll = false;
|
||||
clearAllPtr->set_text("Clear All Achievements");
|
||||
});
|
||||
|
||||
pane.finalize();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AchievementsWindow::update() {
|
||||
const auto current = AchievementSystem::get().getAchievements();
|
||||
bool dirty = current.size() != mSnapshot.size();
|
||||
if (!dirty) {
|
||||
for (size_t i = 0; i < current.size(); ++i) {
|
||||
if (current[i].progress != mSnapshot[i].progress ||
|
||||
current[i].unlocked != mSnapshot[i].unlocked)
|
||||
{
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dirty) {
|
||||
mSnapshot = current;
|
||||
refresh_active_tab();
|
||||
}
|
||||
Window::update();
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "dusk/achievements.h"
|
||||
#include "window.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class AchievementsWindow : public Window {
|
||||
public:
|
||||
AchievementsWindow();
|
||||
void update() override;
|
||||
|
||||
private:
|
||||
std::vector<Achievement> mSnapshot;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,9 +1,16 @@
|
||||
#include "bool_button.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
BoolButton::BoolButton(Rml::Element* parent, Props props)
|
||||
: BaseControlledSelectButton(parent, {std::move(props.key)}),
|
||||
: BaseControlledSelectButton(parent,
|
||||
{
|
||||
.key = std::move(props.key),
|
||||
.icon = std::move(props.icon),
|
||||
}),
|
||||
mGetValue(std::move(props.getValue)), mSetValue(std::move(props.setValue)),
|
||||
mIsDisabled(std::move(props.isDisabled)), mIsModified(std::move(props.isModified)) {}
|
||||
|
||||
@@ -27,7 +34,9 @@ Rml::String BoolButton::format_value() {
|
||||
|
||||
bool BoolButton::handle_nav_command(NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) {
|
||||
mSetValue(!mGetValue());
|
||||
const bool newValue = !mGetValue();
|
||||
mSetValue(newValue);
|
||||
mDoAud_seStartMenu(newValue ? Z2SE_SY_CURSOR_OK : Z2SE_SY_CURSOR_CANCEL);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -7,6 +7,7 @@ class BoolButton : public BaseControlledSelectButton {
|
||||
public:
|
||||
struct Props {
|
||||
Rml::String key;
|
||||
Rml::String icon;
|
||||
std::function<bool()> getValue;
|
||||
std::function<void(bool)> setValue;
|
||||
std::function<bool()> isDisabled;
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include "ui.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace dusk::ui {
|
||||
@@ -34,6 +37,7 @@ Button& Button::on_pressed(ButtonCallback callback) {
|
||||
// TODO: convert this to a FluentComponent method?
|
||||
on_nav_command([callback = std::move(callback)](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
|
||||
callback();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,15 +66,6 @@ public:
|
||||
return static_cast<Derived&>(*this);
|
||||
}
|
||||
|
||||
Derived& on_focus(ScopedEventListener::Callback callback) {
|
||||
return listen(
|
||||
Rml::EventId::Focus, [this, callback = std::move(callback)](Rml::Event& event) {
|
||||
if (!disabled()) {
|
||||
callback(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Derived& on_nav_command(std::function<bool(Rml::Event&, NavCommand)> callback) {
|
||||
listen(Rml::EventId::Click, [this, callback](Rml::Event& event) {
|
||||
if (!disabled() && callback(event, NavCommand::Confirm)) {
|
||||
|
||||
@@ -246,18 +246,16 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
|
||||
mRightPane = &rightPane;
|
||||
mActivePort = port;
|
||||
|
||||
auto showPage = [this, &rightPane, port](Page page) {
|
||||
mPage = page;
|
||||
render_page(rightPane, port, page);
|
||||
};
|
||||
auto addPageButton = [&leftPane, showPage](Page page, Rml::String key, auto getValue) {
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = std::move(key),
|
||||
.getValue = std::move(getValue),
|
||||
})
|
||||
.on_focus([showPage, page](Rml::Event&) { showPage(page); })
|
||||
.on_pressed([showPage, page] { showPage(page); });
|
||||
auto addPageButton = [this, &leftPane, &rightPane, port](
|
||||
Page page, Rml::String key, auto getValue) {
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = std::move(key),
|
||||
.getValue = std::move(getValue),
|
||||
}),
|
||||
rightPane, [this, port, page](Pane& pane) {
|
||||
mPage = page;
|
||||
render_page(pane, port, page);
|
||||
});
|
||||
};
|
||||
|
||||
addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); });
|
||||
@@ -266,47 +264,43 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
|
||||
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); });
|
||||
|
||||
leftPane.add_section("Options");
|
||||
leftPane
|
||||
.add_child<BoolButton>(BoolButton::Props{
|
||||
.key = "Enable Dead Zones",
|
||||
.getValue =
|
||||
[port] {
|
||||
PADDeadZones* deadZones = PADGetDeadZones(port);
|
||||
return deadZones != nullptr && deadZones->useDeadzones;
|
||||
},
|
||||
.setValue =
|
||||
[port](bool value) {
|
||||
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
|
||||
deadZones->useDeadzones = value;
|
||||
PADSerializeMappings();
|
||||
}
|
||||
},
|
||||
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text("Apply configured dead zones to the sticks and analog triggers.");
|
||||
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
|
||||
.key = "Enable Dead Zones",
|
||||
.getValue =
|
||||
[port] {
|
||||
PADDeadZones* deadZones = PADGetDeadZones(port);
|
||||
return deadZones != nullptr && deadZones->useDeadzones;
|
||||
},
|
||||
.setValue =
|
||||
[port](bool value) {
|
||||
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
|
||||
deadZones->useDeadzones = value;
|
||||
PADSerializeMappings();
|
||||
}
|
||||
},
|
||||
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.add_text("Apply configured dead zones to the sticks and analog triggers.");
|
||||
});
|
||||
leftPane
|
||||
.add_child<BoolButton>(BoolButton::Props{
|
||||
.key = "Emulate Triggers",
|
||||
.getValue =
|
||||
[port] {
|
||||
PADDeadZones* deadZones = PADGetDeadZones(port);
|
||||
return deadZones != nullptr && deadZones->emulateTriggers;
|
||||
},
|
||||
.setValue =
|
||||
[port](bool value) {
|
||||
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
|
||||
deadZones->emulateTriggers = value;
|
||||
PADSerializeMappings();
|
||||
}
|
||||
},
|
||||
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text("Treat analog trigger movement as digital L and R button input.");
|
||||
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
|
||||
.key = "Emulate Triggers",
|
||||
.getValue =
|
||||
[port] {
|
||||
PADDeadZones* deadZones = PADGetDeadZones(port);
|
||||
return deadZones != nullptr && deadZones->emulateTriggers;
|
||||
},
|
||||
.setValue =
|
||||
[port](bool value) {
|
||||
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
|
||||
deadZones->emulateTriggers = value;
|
||||
PADSerializeMappings();
|
||||
}
|
||||
},
|
||||
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.add_text("Treat analog trigger movement as digital L and R button input.");
|
||||
});
|
||||
|
||||
render_page(rightPane, port, mPage);
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include "aurora/rmlui.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
@@ -17,7 +20,7 @@ Rml::ElementDocument* load_document(const Rml::String& source) {
|
||||
} // namespace
|
||||
|
||||
Document::Document(const Rml::String& source) : mDocument(load_document(source)) {
|
||||
// Block events while hidden (except for Menu command)
|
||||
// Block events while hidden (except for Menu command); play nav sounds when visible
|
||||
listen(
|
||||
Rml::EventId::Keydown,
|
||||
[this](Rml::Event& event) {
|
||||
@@ -38,8 +41,18 @@ Document::Document(const Rml::String& source) : mDocument(load_document(source))
|
||||
|
||||
listen(Rml::EventId::Keydown, [this](Rml::Event& event) {
|
||||
const auto cmd = map_nav_event(event);
|
||||
if (cmd != NavCommand::None && handle_nav_command(event, cmd)) {
|
||||
if (cmd == NavCommand::None) {
|
||||
return;
|
||||
}
|
||||
auto* prevFocused = mDocument->GetFocusLeafNode();
|
||||
if (handle_nav_command(event, cmd)) {
|
||||
event.StopPropagation();
|
||||
return;
|
||||
}
|
||||
if ((cmd == NavCommand::Up || cmd == NavCommand::Down) &&
|
||||
mDocument->GetFocusLeafNode() != prevFocused)
|
||||
{
|
||||
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -100,6 +113,7 @@ bool Document::visible() const {
|
||||
|
||||
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Menu) {
|
||||
mDoAud_seStartMenu(visible() ? Z2SE_SY_MENU_OUT : Z2SE_SY_MENU_IN);
|
||||
toggle();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ public:
|
||||
show();
|
||||
}
|
||||
}
|
||||
void push(std::unique_ptr<Document> document) {
|
||||
push_document(std::move(document));
|
||||
hide(false);
|
||||
}
|
||||
void pop() {
|
||||
hide(true);
|
||||
show_top_document();
|
||||
|
||||
+295
-307
@@ -1295,73 +1295,71 @@ EditorWindow::EditorWindow() {
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Player");
|
||||
leftPane
|
||||
.add_child<StringButton>(StringButton::Props{
|
||||
.key = "Player Name",
|
||||
.getValue = get_player_name,
|
||||
.setValue = set_player_name,
|
||||
.maxLength = 16,
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<StringButton>(StringButton::Props{
|
||||
.key = "Horse Name",
|
||||
.getValue = get_horse_name,
|
||||
.setValue = set_horse_name,
|
||||
.maxLength = 16,
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(leftPane.add_child<StringButton>(StringButton::Props{
|
||||
.key = "Player Name",
|
||||
.getValue = get_player_name,
|
||||
.setValue = set_player_name,
|
||||
.maxLength = 16,
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(leftPane.add_child<StringButton>(StringButton::Props{
|
||||
.key = "Horse Name",
|
||||
.getValue = get_horse_name,
|
||||
.setValue = set_horse_name,
|
||||
.maxLength = 16,
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Max Health",
|
||||
.getValue = [] { return get_player_status()->getMaxLife(); },
|
||||
.setValue = [](int value) { return get_player_status()->setMaxLife(value); },
|
||||
.max = UINT16_MAX, // TODO: actual max
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Health",
|
||||
.getValue = [] { return get_player_status()->getLife(); },
|
||||
.setValue = [](int value) { return get_player_status()->setLife(value); },
|
||||
.max = UINT16_MAX, // TODO: actual max
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Rupees",
|
||||
.getValue = [] { return get_player_status()->getRupee(); },
|
||||
.setValue = [](int value) { return get_player_status()->setRupee(value); },
|
||||
.max = get_player_status()->getRupeeMax(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Max Oil",
|
||||
.getValue = [] { return get_player_status()->getMaxOil(); },
|
||||
.setValue = [](int value) { return get_player_status()->setMaxOil(value); },
|
||||
.max = UINT16_MAX, // TODO: actual max
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Oil",
|
||||
.getValue = [] { return get_player_status()->getOil(); },
|
||||
.setValue = [](int value) { return get_player_status()->setOil(value); },
|
||||
.max = UINT16_MAX, // TODO: actual max
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
|
||||
leftPane.add_section("Equipment");
|
||||
const auto genSelectItemComboBox = [&leftPane, &rightPane](
|
||||
const Rml::String& label, u8& selectItemData) {
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = label,
|
||||
.getValue = [&selectItemData] { return item_label_for_slot(selectItemData); },
|
||||
})
|
||||
.on_focus([&rightPane, &selectItemData](Rml::Event&) {
|
||||
populate_select_item_picker(rightPane, selectItemData);
|
||||
}),
|
||||
rightPane, [&selectItemData](Pane& pane) {
|
||||
populate_select_item_picker(pane, selectItemData);
|
||||
});
|
||||
};
|
||||
genSelectItemComboBox("Equip X", get_player_status()->mSelectItem[0]);
|
||||
@@ -1369,80 +1367,80 @@ EditorWindow::EditorWindow() {
|
||||
genSelectItemComboBox("Combo Equip X", get_player_status()->mMixItem[0]);
|
||||
genSelectItemComboBox("Combo Equip Y", get_player_status()->mMixItem[1]);
|
||||
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Clothes",
|
||||
.getValue = [] { return get_item_name(get_player_status()->mSelectEquip[0]); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_select_clothes_picker(rightPane); });
|
||||
leftPane
|
||||
.add_select_button({
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_select_clothes_picker(pane); });
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Sword",
|
||||
.getValue = [] { return get_item_name(get_player_status()->mSelectEquip[1]); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
populate_select_equip_picker(
|
||||
rightPane, get_player_status()->mSelectEquip[1], swordEntries);
|
||||
pane, get_player_status()->mSelectEquip[1], swordEntries);
|
||||
});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Shield",
|
||||
.getValue = [] { return get_item_name(get_player_status()->mSelectEquip[2]); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
populate_select_equip_picker(
|
||||
rightPane, get_player_status()->mSelectEquip[2], shieldEntries);
|
||||
pane, get_player_status()->mSelectEquip[2], shieldEntries);
|
||||
});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Scent",
|
||||
.getValue = [] { return get_item_name(get_player_status()->mSelectEquip[3]); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
populate_select_equip_picker(
|
||||
rightPane, get_player_status()->mSelectEquip[3], smellEntries);
|
||||
pane, get_player_status()->mSelectEquip[3], smellEntries);
|
||||
});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Wallet Size",
|
||||
.getValue = [] { return walletSizeNames[get_player_status()->getWalletSize()]; },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_wallet_picker(rightPane); });
|
||||
leftPane
|
||||
.add_select_button({
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_wallet_picker(pane); });
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Form",
|
||||
.getValue = [] { return formNames[get_player_status()->getTransformStatus()]; },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_form_picker(rightPane); });
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_form_picker(pane); });
|
||||
|
||||
leftPane.add_section("World");
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Day",
|
||||
.getValue = [] { return get_player_status_b()->getDate(); },
|
||||
.setValue =
|
||||
[](int value) { get_player_status_b()->setDate(static_cast<u16>(value)); },
|
||||
.max = UINT16_MAX,
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Hour",
|
||||
.getValue = [] { return dKy_getdaytime_hour(); },
|
||||
.setValue = [](int value) { set_clock_time(value, dKy_getdaytime_minute()); },
|
||||
.max = 23,
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Minute",
|
||||
.getValue = [] { return dKy_getdaytime_minute(); },
|
||||
.setValue = [](int value) { set_clock_time(dKy_getdaytime_hour(), value); },
|
||||
.max = 59,
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Transform Level",
|
||||
.getValue =
|
||||
[] {
|
||||
@@ -1455,10 +1453,10 @@ EditorWindow::EditorWindow() {
|
||||
static_cast<u8>((1u << value) - 1u);
|
||||
},
|
||||
.max = 3,
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Twilight Clear Level",
|
||||
.getValue =
|
||||
[] {
|
||||
@@ -1471,8 +1469,8 @@ EditorWindow::EditorWindow() {
|
||||
static_cast<u8>((1u << value) - 1u);
|
||||
},
|
||||
.max = 3,
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
});
|
||||
|
||||
add_tab("Location", [this](Rml::Element* content) {
|
||||
@@ -1481,33 +1479,36 @@ EditorWindow::EditorWindow() {
|
||||
|
||||
leftPane.add_section("Save Location");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Stage",
|
||||
.getValue =
|
||||
[] {
|
||||
return stage_label_for_file(fixed_string(get_player_return_place()->mName));
|
||||
},
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
populate_stage_picker(
|
||||
rightPane, [] { return fixed_string(get_player_return_place()->mName); },
|
||||
[](const char* stageFile) {
|
||||
set_fixed_string(get_player_return_place()->mName, Rml::String(stageFile));
|
||||
});
|
||||
})
|
||||
.register_control(leftPane.add_select_button({
|
||||
.key = "Stage",
|
||||
.getValue =
|
||||
[] {
|
||||
return stage_label_for_file(
|
||||
fixed_string(get_player_return_place()->mName));
|
||||
},
|
||||
}),
|
||||
rightPane,
|
||||
[](Pane& pane) {
|
||||
populate_stage_picker(
|
||||
pane, [] { return fixed_string(get_player_return_place()->mName); },
|
||||
[](const char* stageFile) {
|
||||
set_fixed_string(
|
||||
get_player_return_place()->mName, Rml::String(stageFile));
|
||||
});
|
||||
})
|
||||
.set_disabled(true);
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Room",
|
||||
.getValue = [] { return get_player_return_place()->mRoomNo; },
|
||||
.setValue =
|
||||
[](int value) { get_player_return_place()->mRoomNo = static_cast<s8>(value); },
|
||||
.min = std::numeric_limits<s8>::min(),
|
||||
.max = std::numeric_limits<s8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Spawn ID",
|
||||
.getValue = [] { return get_player_return_place()->mPlayerStatus; },
|
||||
.setValue =
|
||||
@@ -1515,74 +1516,76 @@ EditorWindow::EditorWindow() {
|
||||
get_player_return_place()->mPlayerStatus = static_cast<u8>(value);
|
||||
},
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
|
||||
leftPane.add_section("Horse Location");
|
||||
leftPane
|
||||
.add_child<StringButton>(StringButton::Props{
|
||||
.key = "Horse Position",
|
||||
.getValue =
|
||||
[] {
|
||||
const auto* horsePlace = get_horse_place();
|
||||
return fmt::format("{}, {}, {}", static_cast<float>(horsePlace->mPos.x),
|
||||
static_cast<float>(horsePlace->mPos.y),
|
||||
static_cast<float>(horsePlace->mPos.z));
|
||||
},
|
||||
.setValue =
|
||||
[](Rml::String value) {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
if (parse_vec3(value, x, y, z)) {
|
||||
auto* horsePlace = get_horse_place();
|
||||
horsePlace->mPos.x = x;
|
||||
horsePlace->mPos.y = y;
|
||||
horsePlace->mPos.z = z;
|
||||
}
|
||||
},
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(leftPane.add_child<StringButton>(StringButton::Props{
|
||||
.key = "Horse Position",
|
||||
.getValue =
|
||||
[] {
|
||||
const auto* horsePlace = get_horse_place();
|
||||
return fmt::format("{}, {}, {}",
|
||||
static_cast<float>(horsePlace->mPos.x),
|
||||
static_cast<float>(horsePlace->mPos.y),
|
||||
static_cast<float>(horsePlace->mPos.z));
|
||||
},
|
||||
.setValue =
|
||||
[](Rml::String value) {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
if (parse_vec3(value, x, y, z)) {
|
||||
auto* horsePlace = get_horse_place();
|
||||
horsePlace->mPos.x = x;
|
||||
horsePlace->mPos.y = y;
|
||||
horsePlace->mPos.z = z;
|
||||
}
|
||||
},
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Horse Angle",
|
||||
.getValue = [] { return get_horse_place()->mAngleY; },
|
||||
.setValue = [](int value) { get_horse_place()->mAngleY = static_cast<s16>(value); },
|
||||
.min = std::numeric_limits<s16>::min(),
|
||||
.max = std::numeric_limits<s16>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Horse Stage",
|
||||
.getValue =
|
||||
[] { return stage_label_for_file(fixed_string(get_horse_place()->mName)); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
populate_stage_picker(
|
||||
rightPane, [] { return fixed_string(get_horse_place()->mName); },
|
||||
[](const char* stageFile) {
|
||||
set_fixed_string(get_horse_place()->mName, Rml::String(stageFile));
|
||||
});
|
||||
})
|
||||
.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Horse Stage",
|
||||
.getValue =
|
||||
[] { return stage_label_for_file(fixed_string(get_horse_place()->mName)); },
|
||||
}),
|
||||
rightPane,
|
||||
[](Pane& pane) {
|
||||
populate_stage_picker(
|
||||
pane, [] { return fixed_string(get_horse_place()->mName); },
|
||||
[](const char* stageFile) {
|
||||
set_fixed_string(get_horse_place()->mName, Rml::String(stageFile));
|
||||
});
|
||||
})
|
||||
.set_disabled(true);
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Horse Room",
|
||||
.getValue = [] { return get_horse_place()->mRoomNo; },
|
||||
.setValue = [](int value) { get_horse_place()->mRoomNo = static_cast<s8>(value); },
|
||||
.min = std::numeric_limits<s8>::min(),
|
||||
.max = std::numeric_limits<s8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Horse Spawn ID",
|
||||
.getValue = [] { return get_horse_place()->mSpawnId; },
|
||||
.setValue = [](int value) { get_horse_place()->mSpawnId = static_cast<u8>(value); },
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
});
|
||||
|
||||
add_tab("Inventory", [this](Rml::Element* content) {
|
||||
@@ -1590,44 +1593,41 @@ EditorWindow::EditorWindow() {
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Item Wheel");
|
||||
leftPane.add_button("Default All")
|
||||
.on_pressed([&rightPane] {
|
||||
for (int slot = 0; slot < 24; ++slot) {
|
||||
dComIfGs_setItem(slot, get_slot_default(slot));
|
||||
}
|
||||
rightPane.clear();
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane.add_button("Clear All")
|
||||
.on_pressed([&rightPane] {
|
||||
for (int slot = 0; slot < 24; ++slot) {
|
||||
dComIfGs_setItem(slot, dItemNo_NONE_e);
|
||||
}
|
||||
rightPane.clear();
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane.register_control(leftPane.add_button("Default All").on_pressed([&rightPane] {
|
||||
for (int slot = 0; slot < 24; ++slot) {
|
||||
dComIfGs_setItem(slot, get_slot_default(slot));
|
||||
}
|
||||
rightPane.clear();
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(leftPane.add_button("Clear All").on_pressed([&rightPane] {
|
||||
for (int slot = 0; slot < 24; ++slot) {
|
||||
dComIfGs_setItem(slot, dItemNo_NONE_e);
|
||||
}
|
||||
rightPane.clear();
|
||||
}),
|
||||
rightPane, {});
|
||||
for (int slot = 0; slot < 24; ++slot) {
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = fmt::format("Slot {0:02d}", slot),
|
||||
.getValue = [slot] { return get_item_name(get_player_item()->mItems[slot]); },
|
||||
})
|
||||
.on_focus([&rightPane, slot](
|
||||
Rml::Event&) { populate_item_slot_picker(rightPane, slot); });
|
||||
}),
|
||||
rightPane, [slot](Pane& pane) { populate_item_slot_picker(pane, slot); });
|
||||
}
|
||||
|
||||
leftPane.add_section("Amounts");
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Arrows Amount",
|
||||
.getValue = [] { return get_player_item_record()->mArrowNum; },
|
||||
.setValue =
|
||||
[](int value) { get_player_item_record()->mArrowNum = static_cast<u8>(value); },
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Slingshot Amount",
|
||||
.getValue = [] { return get_player_item_record()->mPachinkoNum; },
|
||||
.setValue =
|
||||
@@ -1635,11 +1635,11 @@ EditorWindow::EditorWindow() {
|
||||
get_player_item_record()->mPachinkoNum = static_cast<u8>(value);
|
||||
},
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
for (int bag = 0; bag < 3; ++bag) {
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = fmt::format("Bomb Bag {} Amount", bag + 1),
|
||||
.getValue = [bag] { return get_player_item_record()->mBombNum[bag]; },
|
||||
.setValue =
|
||||
@@ -1647,12 +1647,12 @@ EditorWindow::EditorWindow() {
|
||||
get_player_item_record()->mBombNum[bag] = static_cast<u8>(value);
|
||||
},
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
}
|
||||
for (int bottle = 0; bottle < 4; ++bottle) {
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = fmt::format("Bottle {} Amount", bottle + 1),
|
||||
.getValue = [bottle] { return get_player_item_record()->mBottleNum[bottle]; },
|
||||
.setValue =
|
||||
@@ -1660,175 +1660,165 @@ EditorWindow::EditorWindow() {
|
||||
get_player_item_record()->mBottleNum[bottle] = static_cast<u8>(value);
|
||||
},
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
}
|
||||
|
||||
leftPane.add_section("Capacities");
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Arrows Max",
|
||||
.getValue = [] { return get_player_item_max()->mItemMax[0]; },
|
||||
.setValue =
|
||||
[](int value) { get_player_item_max()->mItemMax[0] = static_cast<u8>(value); },
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Normal Bombs Max",
|
||||
.getValue = [] { return get_player_item_max()->mItemMax[1]; },
|
||||
.setValue =
|
||||
[](int value) { get_player_item_max()->mItemMax[1] = static_cast<u8>(value); },
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Water Bombs Max",
|
||||
.getValue = [] { return get_player_item_max()->mItemMax[2]; },
|
||||
.setValue =
|
||||
[](int value) { get_player_item_max()->mItemMax[2] = static_cast<u8>(value); },
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Bomblings Max",
|
||||
.getValue = [] { return get_player_item_max()->mItemMax[3]; },
|
||||
.setValue =
|
||||
[](int value) { get_player_item_max()->mItemMax[3] = static_cast<u8>(value); },
|
||||
.max = std::numeric_limits<u8>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
|
||||
leftPane.add_section("Flags");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Obtained Items",
|
||||
.getValue = [] { return "Edit"; },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_item_flag_picker(rightPane); });
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = "Obtained Items",
|
||||
.getValue = [] { return "Edit"; },
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_item_flag_picker(pane); });
|
||||
});
|
||||
add_tab("Collection", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Equipment");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Swords",
|
||||
.getValue =
|
||||
[] {
|
||||
return count_label(
|
||||
count_item_first_bits(swordEntries), swordEntries.size());
|
||||
},
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
populate_toggle_group(rightPane, item_toggle_entries(swordEntries));
|
||||
});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
}),
|
||||
rightPane,
|
||||
[](Pane& pane) { populate_toggle_group(pane, item_toggle_entries(swordEntries)); });
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Shields",
|
||||
.getValue =
|
||||
[] {
|
||||
return count_label(
|
||||
count_item_first_bits(shieldEntries), shieldEntries.size());
|
||||
},
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
populate_toggle_group(rightPane, item_toggle_entries(shieldEntries));
|
||||
});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Clothing",
|
||||
.getValue = [] { return count_label(count_clothing(), 4); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_collect_clothes_picker(rightPane); });
|
||||
}),
|
||||
rightPane,
|
||||
[](Pane& pane) { populate_toggle_group(pane, item_toggle_entries(shieldEntries)); });
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = "Clothing",
|
||||
.getValue = [] { return count_label(count_clothing(), 4); },
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_collect_clothes_picker(pane); });
|
||||
|
||||
leftPane.add_section("Key Items");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Fused Shadows",
|
||||
.getValue =
|
||||
[] {
|
||||
return count_label(
|
||||
count_collect_crystals(fusedShadowEntries), fusedShadowEntries.size());
|
||||
},
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
populate_toggle_group(
|
||||
rightPane, collect_crystal_toggle_entries(fusedShadowEntries));
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
populate_toggle_group(pane, collect_crystal_toggle_entries(fusedShadowEntries));
|
||||
});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Mirror Shards",
|
||||
.getValue =
|
||||
[] {
|
||||
return count_label(
|
||||
count_collect_mirrors(mirrorShardEntries), mirrorShardEntries.size());
|
||||
},
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
populate_toggle_group(rightPane, collect_mirror_toggle_entries(mirrorShardEntries));
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
populate_toggle_group(pane, collect_mirror_toggle_entries(mirrorShardEntries));
|
||||
});
|
||||
|
||||
leftPane.add_section("Health & Souls");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Poe Souls",
|
||||
.getValue = [] { return fmt::format("{} / 60", dComIfGs_getPohSpiritNum()); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_poe_souls_picker(rightPane); });
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Max Life",
|
||||
.getValue = [] { return max_life_label(); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_max_life_picker(rightPane); });
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_poe_souls_picker(pane); });
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = "Max Life",
|
||||
.getValue = [] { return max_life_label(); },
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_max_life_picker(pane); });
|
||||
|
||||
leftPane.add_section("Golden Bugs");
|
||||
for (const auto& bug : bugSpeciesEntries) {
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = bug.name,
|
||||
.getValue = [bug] { return bug_species_label(bug); },
|
||||
})
|
||||
.on_focus([&rightPane, bug](
|
||||
Rml::Event&) { populate_bug_species_picker(rightPane, bug); });
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = bug.name,
|
||||
.getValue = [bug] { return bug_species_label(bug); },
|
||||
}),
|
||||
rightPane, [bug](Pane& pane) { populate_bug_species_picker(pane, bug); });
|
||||
}
|
||||
|
||||
leftPane.add_section("Skills");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Hidden Skills",
|
||||
.getValue =
|
||||
[] {
|
||||
return count_label(
|
||||
count_event_bits(hiddenSkillEntries), hiddenSkillEntries.size());
|
||||
},
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
populate_toggle_group(rightPane, event_toggle_entries(hiddenSkillEntries));
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
populate_toggle_group(pane, event_toggle_entries(hiddenSkillEntries));
|
||||
});
|
||||
|
||||
leftPane.add_section("Logs");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Postman Letters",
|
||||
.getValue = [] { return count_label(count_letters(), letterSenders.size()); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_letters_picker(rightPane); });
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_letters_picker(pane); });
|
||||
|
||||
leftPane.add_section("Fishing Log");
|
||||
for (const auto& fish : fishSpeciesEntries) {
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = fish.name,
|
||||
.getValue = [fish] { return fish_species_label(fish); },
|
||||
})
|
||||
.on_focus([&rightPane, fish](
|
||||
Rml::Event&) { populate_fish_species_picker(rightPane, fish); });
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = fish.name,
|
||||
.getValue = [fish] { return fish_species_label(fish); },
|
||||
}),
|
||||
rightPane, [fish](Pane& pane) { populate_fish_species_picker(pane, fish); });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1841,8 +1831,8 @@ EditorWindow::EditorWindow() {
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Records");
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "STAR Game Time (ms)",
|
||||
.getValue =
|
||||
[] {
|
||||
@@ -1854,10 +1844,10 @@ EditorWindow::EditorWindow() {
|
||||
get_minigame()->setHookGameTime(static_cast<u32>(std::max(0, value)));
|
||||
},
|
||||
.max = std::numeric_limits<int>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Snowboard Race Time (ms)",
|
||||
.getValue =
|
||||
[] {
|
||||
@@ -1869,10 +1859,10 @@ EditorWindow::EditorWindow() {
|
||||
get_minigame()->setRaceGameTime(static_cast<u32>(std::max(0, value)));
|
||||
},
|
||||
.max = std::numeric_limits<int>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Fruit-Pop-Flight Score",
|
||||
.getValue =
|
||||
[] {
|
||||
@@ -1884,8 +1874,8 @@ EditorWindow::EditorWindow() {
|
||||
get_minigame()->setBalloonScore(static_cast<u32>(std::max(0, value)));
|
||||
},
|
||||
.max = std::numeric_limits<int>::max(),
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
}),
|
||||
rightPane, {});
|
||||
});
|
||||
|
||||
add_tab("Config", [this](Rml::Element* content) {
|
||||
@@ -1893,25 +1883,23 @@ EditorWindow::EditorWindow() {
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Options");
|
||||
leftPane
|
||||
.add_child<BoolButton>(BoolButton::Props{
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<BoolButton>(BoolButton::Props{
|
||||
.key = "Enable Vibration",
|
||||
.getValue = [] { return get_player_config()->getVibration() != 0; },
|
||||
.setValue = [](bool value) { get_player_config()->setVibration(value); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { rightPane.clear(); });
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Target Type",
|
||||
.getValue = [] { return target_type_label(); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_target_type_picker(rightPane); });
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Sound",
|
||||
.getValue = [] { return sound_mode_label(); },
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) { populate_sound_mode_picker(rightPane); });
|
||||
}),
|
||||
rightPane, {});
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = "Target Type",
|
||||
.getValue = [] { return target_type_label(); },
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_target_type_picker(pane); });
|
||||
leftPane.register_control(leftPane.add_select_button({
|
||||
.key = "Sound",
|
||||
.getValue = [] { return sound_mode_label(); },
|
||||
}),
|
||||
rightPane, [](Pane& pane) { populate_sound_mode_picker(pane); });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+166
-16
@@ -5,6 +5,7 @@
|
||||
#include <RmlUi/Core.h>
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <SDL3/SDL_touch.h>
|
||||
#include <aurora/rmlui.hpp>
|
||||
#include <dolphin/pad.h>
|
||||
|
||||
@@ -22,6 +23,10 @@ constexpr double kGamepadMenuChordGraceDuration = 0.12;
|
||||
constexpr Sint16 kGamepadAxisPressThreshold = 16384;
|
||||
constexpr Sint16 kGamepadAxisReleaseThreshold = 12000;
|
||||
constexpr int kGamepadAxisDirectionCount = SDL_GAMEPAD_AXIS_COUNT * 2;
|
||||
constexpr int kMenuTapFingerCount = 3;
|
||||
constexpr float kMenuTapMoveThreshold = 12.0f;
|
||||
constexpr double kMenuTapMaxDownSpan = 0.18;
|
||||
constexpr double kMenuTapMaxDuration = 0.55;
|
||||
|
||||
struct GamepadRepeatState {
|
||||
Rml::Input::KeyIdentifier key = Rml::Input::KI_UNKNOWN;
|
||||
@@ -32,11 +37,26 @@ struct GamepadRepeatState {
|
||||
bool pending = false;
|
||||
};
|
||||
|
||||
struct TouchTapFinger {
|
||||
SDL_FingerID id = 0;
|
||||
Rml::Vector2f startPosition;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct TouchTapState {
|
||||
std::array<TouchTapFinger, kMenuTapFingerCount> fingers;
|
||||
int activeCount = 0;
|
||||
double firstDownAt = 0.0;
|
||||
bool candidate = false;
|
||||
bool failed = false;
|
||||
};
|
||||
|
||||
bool sPadInputBlocked = false;
|
||||
std::array<GamepadRepeatState, SDL_GAMEPAD_BUTTON_COUNT> sGamepadButtonRepeats;
|
||||
std::array<GamepadRepeatState, kGamepadAxisDirectionCount> sGamepadAxisRepeats;
|
||||
std::array<u32, PAD_MAX_CONTROLLERS> sPadHoldMasks;
|
||||
std::array<bool, PAD_MAX_CONTROLLERS> sMenuChordConsumed;
|
||||
TouchTapState sTouchMenuTap;
|
||||
|
||||
double now_seconds() noexcept {
|
||||
return static_cast<double>(SDL_GetTicksNS()) / 1000000000.0;
|
||||
@@ -55,15 +75,6 @@ bool has_menu_chord_part_held(u32 port) noexcept {
|
||||
return (held & (PAD_TRIGGER_R | PAD_BUTTON_START)) != 0;
|
||||
}
|
||||
|
||||
bool should_block_pad_for_menu_chord() noexcept {
|
||||
for (u32 port = 0; port < sPadHoldMasks.size(); ++port) {
|
||||
if (sMenuChordConsumed[port] && has_menu_chord_part_held(port)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* controller_change_type(Uint32 eventType) noexcept {
|
||||
switch (eventType) {
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
@@ -389,6 +400,133 @@ void clear_gamepad_repeats() noexcept {
|
||||
sMenuChordConsumed.fill(false);
|
||||
}
|
||||
|
||||
void reset_touch_menu_tap() noexcept {
|
||||
sTouchMenuTap = {};
|
||||
}
|
||||
|
||||
Rml::Vector2f touch_position(const SDL_TouchFingerEvent& event, Rml::Context& context) noexcept {
|
||||
const auto dimensions = context.GetDimensions();
|
||||
return {
|
||||
event.x * static_cast<float>(dimensions.x),
|
||||
event.y * static_cast<float>(dimensions.y),
|
||||
};
|
||||
}
|
||||
|
||||
TouchTapFinger* find_touch_finger(SDL_FingerID id) noexcept {
|
||||
for (auto& finger : sTouchMenuTap.fingers) {
|
||||
if (finger.active && finger.id == id) {
|
||||
return &finger;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TouchTapFinger* find_free_touch_finger() noexcept {
|
||||
for (auto& finger : sTouchMenuTap.fingers) {
|
||||
if (!finger.active) {
|
||||
return &finger;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool touch_moved_too_far(
|
||||
const TouchTapFinger& finger, Rml::Vector2f position, Rml::Context& context) noexcept {
|
||||
const Rml::Vector2f delta = position - finger.startPosition;
|
||||
const float threshold =
|
||||
kMenuTapMoveThreshold * std::max(context.GetDensityIndependentPixelRatio(), 1.0f);
|
||||
return delta.SquaredMagnitude() > threshold * threshold;
|
||||
}
|
||||
|
||||
void dispatch_menu_key(Rml::Context& context) noexcept {
|
||||
context.ProcessMouseLeave();
|
||||
context.ProcessKeyDown(Rml::Input::KI_F1, 0);
|
||||
context.ProcessKeyUp(Rml::Input::KI_F1, 0);
|
||||
}
|
||||
|
||||
bool handle_touch_menu_tap(Rml::Context& context, const SDL_Event& event) noexcept {
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_FINGER_DOWN: {
|
||||
const double now = now_seconds();
|
||||
if (sTouchMenuTap.activeCount == 0) {
|
||||
reset_touch_menu_tap();
|
||||
sTouchMenuTap.firstDownAt = now;
|
||||
}
|
||||
|
||||
if (sTouchMenuTap.candidate || sTouchMenuTap.activeCount >= kMenuTapFingerCount ||
|
||||
find_touch_finger(event.tfinger.fingerID) != nullptr)
|
||||
{
|
||||
sTouchMenuTap.failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* finger = find_free_touch_finger();
|
||||
if (finger == nullptr) {
|
||||
sTouchMenuTap.failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
*finger = TouchTapFinger{
|
||||
.id = event.tfinger.fingerID,
|
||||
.startPosition = touch_position(event.tfinger, context),
|
||||
.active = true,
|
||||
};
|
||||
sTouchMenuTap.activeCount++;
|
||||
|
||||
if (now - sTouchMenuTap.firstDownAt > kMenuTapMaxDownSpan) {
|
||||
sTouchMenuTap.failed = true;
|
||||
}
|
||||
if (sTouchMenuTap.activeCount == kMenuTapFingerCount) {
|
||||
sTouchMenuTap.candidate = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case SDL_EVENT_FINGER_MOTION: {
|
||||
auto* finger = find_touch_finger(event.tfinger.fingerID);
|
||||
if (finger == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (touch_moved_too_far(*finger, touch_position(event.tfinger, context), context)) {
|
||||
sTouchMenuTap.failed = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case SDL_EVENT_FINGER_UP: {
|
||||
auto* finger = find_touch_finger(event.tfinger.fingerID);
|
||||
if (finger == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const double now = now_seconds();
|
||||
if (!sTouchMenuTap.candidate ||
|
||||
touch_moved_too_far(*finger, touch_position(event.tfinger, context), context))
|
||||
{
|
||||
sTouchMenuTap.failed = true;
|
||||
}
|
||||
|
||||
*finger = {};
|
||||
sTouchMenuTap.activeCount--;
|
||||
if (sTouchMenuTap.activeCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool shouldDispatch = sTouchMenuTap.candidate && !sTouchMenuTap.failed &&
|
||||
now - sTouchMenuTap.firstDownAt <= kMenuTapMaxDuration;
|
||||
reset_touch_menu_tap();
|
||||
if (shouldDispatch) {
|
||||
dispatch_menu_key(context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case SDL_EVENT_FINGER_CANCELED:
|
||||
reset_touch_menu_tap();
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void begin_gamepad_key(GamepadRepeatState& repeat, Rml::Input::KeyIdentifier key) noexcept {
|
||||
if (repeat.held) {
|
||||
return;
|
||||
@@ -488,7 +626,8 @@ void process_axis_direction(
|
||||
}
|
||||
|
||||
set_pad_button_held(port, heldPadButton, true);
|
||||
const bool chorded = heldPadButton == PAD_TRIGGER_R && is_menu_chord(port);
|
||||
const bool chorded = heldPadButton == PAD_TRIGGER_R && is_menu_chord(port) &&
|
||||
(port >= sMenuChordConsumed.size() || !sMenuChordConsumed[port]);
|
||||
if (chorded) {
|
||||
consume_menu_chord(port, context);
|
||||
}
|
||||
@@ -510,7 +649,7 @@ void process_axis_direction(
|
||||
} // namespace
|
||||
|
||||
void sync_input_block() noexcept {
|
||||
const bool shouldBlock = any_document_visible() || should_block_pad_for_menu_chord();
|
||||
const bool shouldBlock = any_document_visible();
|
||||
if (sPadInputBlocked == shouldBlock) {
|
||||
return;
|
||||
}
|
||||
@@ -530,6 +669,7 @@ void release_input_block() noexcept {
|
||||
|
||||
void reset_input_state() noexcept {
|
||||
clear_gamepad_repeats();
|
||||
reset_touch_menu_tap();
|
||||
}
|
||||
|
||||
void handle_event(const SDL_Event& event) noexcept {
|
||||
@@ -541,17 +681,27 @@ void handle_event(const SDL_Event& event) noexcept {
|
||||
}
|
||||
}
|
||||
dispatch_controller_change_event(event);
|
||||
if (event.type != SDL_EVENT_GAMEPAD_BUTTON_DOWN && event.type != SDL_EVENT_GAMEPAD_BUTTON_UP &&
|
||||
event.type != SDL_EVENT_GAMEPAD_AXIS_MOTION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_FINGER_DOWN || event.type == SDL_EVENT_FINGER_MOTION ||
|
||||
event.type == SDL_EVENT_FINGER_UP || event.type == SDL_EVENT_FINGER_CANCELED)
|
||||
{
|
||||
if (handle_touch_menu_tap(*context, event)) {
|
||||
sync_input_block();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type != SDL_EVENT_GAMEPAD_BUTTON_DOWN && event.type != SDL_EVENT_GAMEPAD_BUTTON_UP &&
|
||||
event.type != SDL_EVENT_GAMEPAD_AXIS_MOTION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
|
||||
process_axis_direction(*context, event.gaxis, AXIS_SIGN_POSITIVE);
|
||||
process_axis_direction(*context, event.gaxis, AXIS_SIGN_NEGATIVE);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "number_button.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
#include <charconv>
|
||||
#include <fmt/format.h>
|
||||
|
||||
@@ -51,11 +54,13 @@ void NumberButton::set_value(Rml::String value) {
|
||||
}
|
||||
|
||||
bool NumberButton::handle_nav_command(NavCommand cmd) {
|
||||
if (cmd == NavCommand::Left) {
|
||||
mSetValue(std::clamp(mGetValue() - mStep, mMin, mMax));
|
||||
return true;
|
||||
} else if (cmd == NavCommand::Right) {
|
||||
mSetValue(std::clamp(mGetValue() + mStep, mMin, mMax));
|
||||
if (cmd == NavCommand::Left || cmd == NavCommand::Right) {
|
||||
const int newValue = std::clamp(
|
||||
mGetValue() + (cmd == NavCommand::Right ? mStep : -mStep), mMin, mMax);
|
||||
if (newValue != mGetValue()) {
|
||||
mSetValue(newValue);
|
||||
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return BaseStringButton::handle_nav_command(cmd);
|
||||
|
||||
+10
-5
@@ -1,5 +1,8 @@
|
||||
#include "overlay.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
#include <dolphin/gx/GXAurora.h>
|
||||
#include <dolphin/vi.h>
|
||||
#include <fmt/format.h>
|
||||
@@ -84,7 +87,7 @@ Rml::Element* create_stepped_carousel_arrow(
|
||||
auto button = doc->CreateElement("button");
|
||||
button->SetClass("stepped-carousel-arrow", true);
|
||||
button->SetClass(className, true);
|
||||
button->SetInnerRML(escape(label));
|
||||
button->SetInnerRML(label);
|
||||
return parent->AppendChild(std::move(button));
|
||||
}
|
||||
|
||||
@@ -92,10 +95,10 @@ Rml::Element* create_stepped_carousel_arrow(
|
||||
|
||||
SteppedCarousel::SteppedCarousel(Rml::Element* parent, Props props)
|
||||
: Component(create_stepped_carousel_root(parent)), mProps(std::move(props)) {
|
||||
Rml::Element* prevElem = create_stepped_carousel_arrow(mRoot, "prev", "<");
|
||||
Rml::Element* prevElem = create_stepped_carousel_arrow(mRoot, "prev", "");
|
||||
mValueElem = append(mRoot, "div");
|
||||
mValueElem->SetClass("stepped-carousel-value", true);
|
||||
Rml::Element* nextElem = create_stepped_carousel_arrow(mRoot, "next", ">");
|
||||
Rml::Element* nextElem = create_stepped_carousel_arrow(mRoot, "next", "");
|
||||
|
||||
listen(prevElem, Rml::EventId::Click,
|
||||
[this](Rml::Event&) { handle_nav_command(NavCommand::Left); });
|
||||
@@ -146,6 +149,7 @@ void SteppedCarousel::apply(int value) {
|
||||
if (nextValue == currentValue) {
|
||||
return;
|
||||
}
|
||||
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
|
||||
if (mProps.onChange) {
|
||||
mProps.onChange(nextValue);
|
||||
}
|
||||
@@ -160,10 +164,10 @@ Rml::String format_graphics_setting_value(GraphicsOption option, int value) {
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
AuroraGetRenderSize(&width, &height);
|
||||
return fmt::format("{}x ({}x{})", value, width, height);
|
||||
return fmt::format("{}× ({}×{})", value, width, height);
|
||||
}
|
||||
case GraphicsOption::ShadowResolution:
|
||||
return fmt::format("{}x", value);
|
||||
return fmt::format("{}×", value);
|
||||
case GraphicsOption::BloomMode:
|
||||
switch (static_cast<BloomMode>(value)) {
|
||||
case BloomMode::Off:
|
||||
@@ -229,6 +233,7 @@ Overlay::Overlay(OverlayProps props)
|
||||
}
|
||||
|
||||
void Overlay::show() {
|
||||
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
|
||||
Document::show();
|
||||
mRoot->SetAttribute("open", "");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "pane.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
#include "ui.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
@@ -56,6 +58,7 @@ Pane::Pane(Rml::Element* parent, Type type) : FluentComponent(createRoot(parent)
|
||||
int i = focusedChild + direction;
|
||||
while (i >= 0 && i < mChildren.size()) {
|
||||
if (mChildren[i]->focus()) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
|
||||
event.StopPropagation();
|
||||
break;
|
||||
}
|
||||
@@ -72,6 +75,12 @@ Pane::Pane(Rml::Element* parent, Type type) : FluentComponent(createRoot(parent)
|
||||
childIndex = i;
|
||||
}
|
||||
}
|
||||
// If item already selected, deselect
|
||||
if (childIndex >= 0 && childIndex < mChildren.size() &&
|
||||
mChildren[childIndex]->selected())
|
||||
{
|
||||
childIndex = -1;
|
||||
}
|
||||
set_selected_item(childIndex);
|
||||
// If the selection was handled locally, don't allow it to bubble up to window
|
||||
if (event.GetParameter("handled", false)) {
|
||||
@@ -95,6 +104,40 @@ void Pane::set_selected_item(int index) {
|
||||
}
|
||||
}
|
||||
|
||||
Component& Pane::register_control(
|
||||
Component& component, Pane& nextPane, std::function<void(Pane&)> callback) {
|
||||
component.listen(component.root(), Rml::EventId::Mouseover,
|
||||
[this, &component, &nextPane, callback](Rml::Event&) {
|
||||
if (component.disabled()) {
|
||||
return;
|
||||
}
|
||||
bool anySelected = false;
|
||||
for (const auto& child : mChildren) {
|
||||
if (child->selected()) {
|
||||
anySelected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!anySelected) {
|
||||
nextPane.clear();
|
||||
if (callback) {
|
||||
callback(nextPane);
|
||||
}
|
||||
}
|
||||
});
|
||||
component.listen(component.root(), Rml::EventId::Focus,
|
||||
[&component, &nextPane, callback = std::move(callback)](Rml::Event&) {
|
||||
if (component.disabled()) {
|
||||
return;
|
||||
}
|
||||
nextPane.clear();
|
||||
if (callback) {
|
||||
callback(nextPane);
|
||||
}
|
||||
});
|
||||
return component;
|
||||
}
|
||||
|
||||
bool Pane::focus() {
|
||||
// Focus the first selected child
|
||||
for (const auto& child : mChildren) {
|
||||
|
||||
@@ -19,6 +19,8 @@ public:
|
||||
void update() override;
|
||||
|
||||
void set_selected_item(int index);
|
||||
Component& register_control(
|
||||
Component& component, Pane& nextPane, std::function<void(Pane&)> callback);
|
||||
|
||||
Rml::Element* add_section(const Rml::String& text);
|
||||
ControlledButton& add_button(ControlledButton::Props props) {
|
||||
|
||||
+23
-7
@@ -2,9 +2,16 @@
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
#include "achievements.hpp"
|
||||
#include "aurora/rmlui.hpp"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "editor.hpp"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
#include "f_pc/f_pc_name.h"
|
||||
#include "imgui.h"
|
||||
#include "settings.hpp"
|
||||
#include "ui.hpp"
|
||||
@@ -35,14 +42,18 @@ Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("pop
|
||||
.onClose = [this] { hide(false); },
|
||||
.autoSelect = false,
|
||||
});
|
||||
mTabBar->add_tab("Settings", [] { push_document(std::make_unique<SettingsWindow>()); });
|
||||
mTabBar->add_tab("Settings", [this] { push(std::make_unique<SettingsWindow>()); });
|
||||
// mTabBar->add_tab("Warp", [] {
|
||||
// // TODO
|
||||
// });
|
||||
mTabBar->add_tab("Editor", [] { push_document(std::make_unique<EditorWindow>()); });
|
||||
mTabBar->add_tab("Editor", [this] { push(std::make_unique<EditorWindow>()); });
|
||||
mTabBar->add_tab("Achievements", [this] { push(std::make_unique<AchievementsWindow>()); });
|
||||
mTabBar->add_tab("Reset", [this] {
|
||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||
mTabBar->set_active_tab(-1);
|
||||
if (fpcM_SearchByName(fpcNm_LOGO_SCENE_e)) {
|
||||
return;
|
||||
}
|
||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||
hide(false);
|
||||
});
|
||||
mTabBar->add_tab("Quit", [] { IsRunning = false; });
|
||||
@@ -55,18 +66,19 @@ Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("pop
|
||||
Document::hide(mPendingClose);
|
||||
}
|
||||
});
|
||||
|
||||
// We start hidden, but want focus for an open nav event
|
||||
mDocument->Focus();
|
||||
}
|
||||
|
||||
void Popup::show() {
|
||||
Document::show();
|
||||
mRoot->SetAttribute("open", "");
|
||||
mTabBar->set_active_tab(-1);
|
||||
if (!mTabBar->focus_tab(mFocusedTabIndex)) {
|
||||
mTabBar->focus();
|
||||
}
|
||||
}
|
||||
|
||||
void Popup::hide(bool close) {
|
||||
mFocusedTabIndex = mTabBar->focused_tab_index();
|
||||
mRoot->RemoveAttribute("open");
|
||||
if (close) {
|
||||
mPendingClose = true;
|
||||
@@ -121,7 +133,11 @@ bool Popup::visible() const {
|
||||
}
|
||||
|
||||
bool Popup::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Cancel) {
|
||||
if (!getSettings().backend.wasPresetChosen) {
|
||||
return true;
|
||||
}
|
||||
if (cmd == NavCommand::Cancel && visible()) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_MENU_OUT);
|
||||
hide(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
std::unique_ptr<Button> mCloseButton;
|
||||
Insets mTabBarPadding;
|
||||
float mTopMargin = 0.f;
|
||||
int mFocusedTabIndex = -1;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
+28
-20
@@ -28,10 +28,13 @@ const Rml::String kDocumentSource = R"RML(
|
||||
</hero>
|
||||
<div id="menu-list" />
|
||||
</menu>
|
||||
<disk-status class="intro-item delay-4">
|
||||
<span id="status" class="status" />
|
||||
<span id="detail" class="detail" />
|
||||
</disk-status>
|
||||
<disc-info class="intro-item delay-4">
|
||||
<div id="disc-status">
|
||||
<icon />
|
||||
<span id="disc-status-label" />
|
||||
</div>
|
||||
<span id="disc-version" class="detail" />
|
||||
</disc-info>
|
||||
<version-info class="intro-item delay-5">
|
||||
<div class="version">Version <span id="version-text"></span></div>
|
||||
<div class="update"><span>Update available!</span> Download</div>
|
||||
@@ -113,7 +116,7 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
|
||||
if (auto* menuList = mDocument->GetElementById("menu-list")) {
|
||||
const bool hasValidPath = is_selected_path_valid();
|
||||
mMenuButtons.push_back(
|
||||
std::make_unique<Button>(menuList, hasValidPath ? "Start Game" : "Select Disk Image"));
|
||||
std::make_unique<Button>(menuList, hasValidPath ? "Start Game" : "Select Disc Image"));
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
if (!is_selected_path_valid()) {
|
||||
open_iso_picker();
|
||||
@@ -125,8 +128,7 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Options"));
|
||||
mMenuButtons.back()->on_pressed(
|
||||
[] { push_document(std::make_unique<PrelaunchOptions>()); });
|
||||
mMenuButtons.back()->on_pressed([this] { push(std::make_unique<PrelaunchOptions>()); });
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-2");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit To Desktop"));
|
||||
@@ -134,8 +136,8 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-3");
|
||||
}
|
||||
|
||||
mDiscStatus = mDocument->GetElementById("status");
|
||||
mDiscDetail = mDocument->GetElementById("detail");
|
||||
mDiscStatus = mDocument->GetElementById("disc-status");
|
||||
mDiscDetail = mDocument->GetElementById("disc-version");
|
||||
mVersion = mDocument->GetElementById("version-text");
|
||||
|
||||
listen(mDocument, Rml::EventId::Transitionend, [this](Rml::Event& event) {
|
||||
@@ -186,27 +188,34 @@ void Prelaunch::update() {
|
||||
}
|
||||
|
||||
if (!mMenuButtons.empty()) {
|
||||
mMenuButtons[0]->set_text(hasValidPath ? "Start Game" : "Select Disk Image");
|
||||
mMenuButtons[0]->set_text(hasValidPath ? "Start Game" : "Select Disc Image");
|
||||
}
|
||||
if (mDiscStatus != nullptr) {
|
||||
|
||||
const auto discStatusLabel = mDiscStatus->GetElementById("disc-status-label");
|
||||
|
||||
if (mDiscStatus != nullptr && discStatusLabel != nullptr) {
|
||||
if (hasValidPath) {
|
||||
mDiscStatus->RemoveAttribute("bad");
|
||||
mDiscStatus->SetInnerRML("Disc Ready");
|
||||
mDiscStatus->SetAttribute("status", "good");
|
||||
discStatusLabel->SetInnerRML("Disc ready.");
|
||||
} else {
|
||||
mDiscStatus->SetAttribute("bad", "");
|
||||
mDiscStatus->SetInnerRML("Disk Not Found");
|
||||
mDiscStatus->SetAttribute("status", "bad");
|
||||
discStatusLabel->SetInnerRML("Disc not found.");
|
||||
}
|
||||
}
|
||||
if (mDiscDetail != nullptr) {
|
||||
if (hasValidPath) {
|
||||
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
|
||||
mDiscDetail->SetInnerRML(state.isPal ? "GameCube • PAL" : "GameCube • USA");
|
||||
mDiscDetail->SetInnerRML(state.isPal ? "GameCube • EUR" : "GameCube • USA");
|
||||
} else {
|
||||
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
|
||||
}
|
||||
}
|
||||
if (mVersion != nullptr) {
|
||||
mVersion->SetInnerRML(escape(DUSK_WC_DESCRIBE));
|
||||
std::string_view versionStr(DUSK_WC_DESCRIBE);
|
||||
if (versionStr[0] == 'v') {
|
||||
versionStr = versionStr.substr(1);
|
||||
}
|
||||
mVersion->SetInnerRML(escape(versionStr));
|
||||
}
|
||||
|
||||
Document::update();
|
||||
@@ -240,9 +249,8 @@ bool Prelaunch::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto buttonCount = static_cast<int>(mMenuButtons.size());
|
||||
int i = (focusedButton + direction) % buttonCount;
|
||||
if (i < 0) i += buttonCount;
|
||||
const auto n = static_cast<int>(mMenuButtons.size());
|
||||
int i = ((focusedButton + direction) % n + n) % n;
|
||||
while (i >= 0 && i < mMenuButtons.size()) {
|
||||
if (mMenuButtons[i]->focus()) {
|
||||
event.StopPropagation();
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
#include "preset.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "button.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <dolphin/gx/GXAurora.h>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
void applyPresetClassic() {
|
||||
auto& s = getSettings();
|
||||
s.video.lockAspectRatio.setValue(true);
|
||||
s.game.bloomMode.setValue(BloomMode::Classic);
|
||||
s.game.enableAchievementNotifications.setValue(false);
|
||||
s.game.internalResolutionScale.setValue(1);
|
||||
s.game.shadowResolutionMultiplier.setValue(1);
|
||||
s.game.hideTvSettingsScreen.setValue(false);
|
||||
s.game.skipWarningScreen.setValue(false);
|
||||
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
|
||||
}
|
||||
|
||||
void applyPresetDusk() {
|
||||
auto& s = getSettings();
|
||||
s.game.hideTvSettingsScreen.setValue(true);
|
||||
s.game.skipWarningScreen.setValue(true);
|
||||
s.game.noReturnRupees.setValue(true);
|
||||
s.game.disableRupeeCutscenes.setValue(true);
|
||||
s.game.noSwordRecoil.setValue(true);
|
||||
s.game.fastClimbing.setValue(true);
|
||||
s.game.noMissClimbing.setValue(true);
|
||||
s.game.fastTears.setValue(true);
|
||||
s.game.biggerWallets.setValue(true);
|
||||
s.game.invertCameraXAxis.setValue(true);
|
||||
s.game.no2ndFishForCat.setValue(true);
|
||||
s.game.enableAchievementNotifications.setValue(true);
|
||||
s.game.enableQuickTransform.setValue(true);
|
||||
s.game.instantSaves.setValue(true);
|
||||
s.game.midnasLamentNonStop.setValue(true);
|
||||
s.game.enableFrameInterpolation.setValue(true);
|
||||
s.game.sunsSong.setValue(true);
|
||||
s.game.bloomMode.setValue(BloomMode::Dusk);
|
||||
s.game.internalResolutionScale.setValue(0);
|
||||
s.game.shadowResolutionMultiplier.setValue(4);
|
||||
s.game.enableGyroAim.setValue(true);
|
||||
}
|
||||
|
||||
Rml::Element* createElement(Rml::Element* parent, const Rml::String& tag) {
|
||||
auto* doc = parent->GetOwnerDocument();
|
||||
auto elem = doc->CreateElement(tag);
|
||||
return parent->AppendChild(std::move(elem));
|
||||
}
|
||||
|
||||
const Rml::String kDocumentSource = R"RML(
|
||||
<rml>
|
||||
<head>
|
||||
<link type="text/rcss" href="res/rml/window.rcss" />
|
||||
</head>
|
||||
<body>
|
||||
<window id="window" class="small preset">
|
||||
<div id="preset-dialog" class="preset-dialog"></div>
|
||||
</window>
|
||||
</body>
|
||||
</rml>
|
||||
)RML";
|
||||
|
||||
} // namespace
|
||||
|
||||
PresetWindow::PresetWindow()
|
||||
: Document(kDocumentSource), mRoot(mDocument->GetElementById("window")) {
|
||||
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
|
||||
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
|
||||
Document::visible()) {
|
||||
Document::hide(mPendingClose);
|
||||
}
|
||||
});
|
||||
|
||||
auto* dialog = mDocument->GetElementById("preset-dialog");
|
||||
|
||||
auto* title = createElement(dialog, "div");
|
||||
title->SetClass("preset-title", true);
|
||||
title->SetInnerRML("Welcome to Dusk!");
|
||||
|
||||
auto* intro = createElement(dialog, "div");
|
||||
intro->SetClass("preset-intro", true);
|
||||
intro->SetInnerRML(
|
||||
"Choose a preset to get started.<br/>"
|
||||
"You can change any setting later from the Settings menu.");
|
||||
|
||||
auto* grid = createElement(dialog, "div");
|
||||
grid->SetClass("preset-grid", true);
|
||||
|
||||
struct PresetInfo {
|
||||
const char* name;
|
||||
const char* desc;
|
||||
void (*apply)();
|
||||
};
|
||||
|
||||
static constexpr PresetInfo kPresets[] = {
|
||||
{"Classic",
|
||||
"Enhancements disabled to match the GameCube version. "
|
||||
"Good for speedrunning or simple nostalgia!",
|
||||
applyPresetClassic},
|
||||
{"Dusk",
|
||||
"Graphics & quality of life tweaks, including some from the Wii U version. "
|
||||
"Our recommended way to play!",
|
||||
applyPresetDusk},
|
||||
};
|
||||
|
||||
for (const auto& preset : kPresets) {
|
||||
auto* col = createElement(grid, "div");
|
||||
col->SetClass("preset-col", true);
|
||||
|
||||
auto btn = std::make_unique<Button>(col, Rml::String(preset.name));
|
||||
btn->root()->SetClass("preset-btn", true);
|
||||
btn->on_nav_command([this, apply = preset.apply](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
apply();
|
||||
getSettings().backend.wasPresetChosen.setValue(true);
|
||||
config::Save();
|
||||
hide(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
mButtons.push_back(std::move(btn));
|
||||
|
||||
auto* desc = createElement(col, "div");
|
||||
desc->SetClass("preset-desc", true);
|
||||
desc->SetInnerRML(preset.desc);
|
||||
}
|
||||
}
|
||||
|
||||
void PresetWindow::show() {
|
||||
Document::show();
|
||||
mRoot->SetAttribute("open", "");
|
||||
}
|
||||
|
||||
void PresetWindow::hide(bool close) {
|
||||
mRoot->RemoveAttribute("open");
|
||||
mPendingClose = close;
|
||||
}
|
||||
|
||||
bool PresetWindow::visible() const {
|
||||
return mRoot->HasAttribute("open");
|
||||
}
|
||||
|
||||
bool PresetWindow::focus() {
|
||||
if (!mButtons.empty()) {
|
||||
return mButtons.back()->focus();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PresetWindow::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Cancel || cmd == NavCommand::Menu) {
|
||||
return true;
|
||||
}
|
||||
int direction = 0;
|
||||
if (cmd == NavCommand::Left) {
|
||||
direction = -1;
|
||||
} else if (cmd == NavCommand::Right) {
|
||||
direction = 1;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
auto* target = event.GetTargetElement();
|
||||
for (int i = 0; i < static_cast<int>(mButtons.size()); ++i) {
|
||||
if (mButtons[i]->contains(target)) {
|
||||
const int next = i + direction;
|
||||
if (next >= 0 && next < static_cast<int>(mButtons.size())) {
|
||||
if (mButtons[next]->focus()) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
#include "component.hpp"
|
||||
#include "document.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class PresetWindow : public Document {
|
||||
public:
|
||||
PresetWindow();
|
||||
|
||||
void show() override;
|
||||
void hide(bool close) override;
|
||||
bool visible() const override;
|
||||
bool focus() override;
|
||||
|
||||
protected:
|
||||
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
|
||||
|
||||
private:
|
||||
Rml::Element* mRoot = nullptr;
|
||||
std::vector<std::unique_ptr<Component>> mButtons;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -19,6 +19,7 @@ Rml::Element* createRoot(Rml::Element* parent) {
|
||||
SelectButton::SelectButton(Rml::Element* parent, Props props)
|
||||
: FluentComponent(createRoot(parent)) {
|
||||
mKeyElem = append(mRoot, "key");
|
||||
mIconElem = append(mRoot, "icon");
|
||||
mValueElem = append(mRoot, "value");
|
||||
update_props(std::move(props));
|
||||
on_nav_command([this](Rml::Event&, NavCommand cmd) { return handle_nav_command(cmd); });
|
||||
@@ -32,7 +33,7 @@ void SelectButton::set_modified(bool value) {
|
||||
if (mProps.modified != value) {
|
||||
mValueElem->SetClass("modified", value);
|
||||
if (value) {
|
||||
mValueElem->SetInnerRML(fmt::format("• {}", escape(mProps.value)));
|
||||
mValueElem->SetInnerRML(fmt::format("• {}", escape(mProps.value)));
|
||||
} else {
|
||||
mValueElem->SetInnerRML(escape(mProps.value));
|
||||
}
|
||||
@@ -43,7 +44,7 @@ void SelectButton::set_modified(bool value) {
|
||||
void SelectButton::set_value_label(const Rml::String& value) {
|
||||
if (mProps.value != value) {
|
||||
if (mProps.modified) {
|
||||
mValueElem->SetInnerRML(fmt::format("• {}", escape(value)));
|
||||
mValueElem->SetInnerRML(fmt::format("• {}", escape(value)));
|
||||
} else {
|
||||
mValueElem->SetInnerRML(escape(value));
|
||||
}
|
||||
@@ -67,13 +68,23 @@ void SelectButton::update_props(Props props) {
|
||||
if (mProps.key != props.key) {
|
||||
mKeyElem->SetInnerRML(escape(props.key));
|
||||
}
|
||||
if (mProps.icon != props.icon) {
|
||||
Rml::StringList iconClasses;
|
||||
Rml::StringUtilities::ExpandString(iconClasses, mIconElem->GetClassNames(), ' ', true);
|
||||
for (const auto& className : iconClasses) {
|
||||
mIconElem->SetClass(className, false);
|
||||
}
|
||||
if (!props.icon.empty()) {
|
||||
mIconElem->SetClass(props.icon, true);
|
||||
}
|
||||
}
|
||||
set_value_label(props.value);
|
||||
set_modified(props.modified);
|
||||
mProps = std::move(props);
|
||||
}
|
||||
|
||||
bool SelectButton::handle_nav_command(NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
if (cmd == NavCommand::Confirm && mProps.submit) {
|
||||
mRoot->DispatchEvent(Rml::EventId::Submit, {});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ public:
|
||||
struct Props {
|
||||
Rml::String key;
|
||||
Rml::String value;
|
||||
Rml::String icon;
|
||||
bool modified = false;
|
||||
bool submit = true;
|
||||
};
|
||||
|
||||
SelectButton(Rml::Element* parent, Props props);
|
||||
@@ -31,6 +33,7 @@ protected:
|
||||
|
||||
Props mProps;
|
||||
Rml::Element* mKeyElem = nullptr;
|
||||
Rml::Element* mIconElem = nullptr;
|
||||
Rml::Element* mValueElem = nullptr;
|
||||
};
|
||||
|
||||
@@ -52,10 +55,15 @@ public:
|
||||
std::function<Rml::String()> getValue;
|
||||
std::function<bool()> isDisabled;
|
||||
std::function<bool()> isModified;
|
||||
bool submit = true;
|
||||
};
|
||||
|
||||
ControlledSelectButton(Rml::Element* parent, Props props)
|
||||
: BaseControlledSelectButton(parent, {std::move(props.key)}),
|
||||
: BaseControlledSelectButton(parent,
|
||||
{
|
||||
.key = std::move(props.key),
|
||||
.submit = props.submit,
|
||||
}),
|
||||
mGetValue(std::move(props.getValue)), mIsDisabled(std::move(props.isDisabled)),
|
||||
mIsModified(std::move(props.isModified)) {}
|
||||
|
||||
|
||||
+408
-446
@@ -56,11 +56,9 @@ const Rml::String kBloomHelpText =
|
||||
"a higher-quality bloom pass.";
|
||||
const Rml::String kBloomBrightnessHelpText =
|
||||
"Configure bloom intensity. Higher values make bright areas glow more strongly.";
|
||||
|
||||
int bloom_multiplier_percent() {
|
||||
return std::clamp(
|
||||
static_cast<int>(getSettings().game.bloomMultiplier.getValue() * 100.0f + 0.5f), 0, 100);
|
||||
}
|
||||
const Rml::String kUnlockFramerateHelpText =
|
||||
"Uses inter-frame interpolation to enable higher frame rates.<br/><br/>May introduce minor "
|
||||
"visual artifacts or animation glitches.";
|
||||
|
||||
int float_setting_percent(ConfigVar<float>& var) {
|
||||
return static_cast<int>(var.getValue() * 100.0f + 0.5f);
|
||||
@@ -72,6 +70,7 @@ bool gyro_enabled() {
|
||||
|
||||
struct ConfigBoolProps {
|
||||
Rml::String key;
|
||||
Rml::String icon;
|
||||
Rml::String helpText;
|
||||
std::function<void(bool)> onChange;
|
||||
std::function<bool()> isDisabled;
|
||||
@@ -79,276 +78,192 @@ struct ConfigBoolProps {
|
||||
|
||||
SelectButton& config_bool_select(
|
||||
Pane& leftPane, Pane& rightPane, ConfigVar<bool>& var, ConfigBoolProps props) {
|
||||
return leftPane
|
||||
.add_child<BoolButton>(BoolButton::Props{.key = std::move(props.key),
|
||||
.getValue = [&var] { return var.getValue(); },
|
||||
.setValue =
|
||||
[&var, callback = std::move(props.onChange)](bool value) {
|
||||
if (value == var.getValue()) {
|
||||
return;
|
||||
}
|
||||
var.setValue(value);
|
||||
config::Save();
|
||||
if (callback) {
|
||||
callback(value);
|
||||
}
|
||||
},
|
||||
.isDisabled = std::move(props.isDisabled),
|
||||
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); }})
|
||||
.on_focus([&rightPane, helpText = std::move(props.helpText)](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_rml(helpText);
|
||||
auto& button = leftPane.add_child<BoolButton>(BoolButton::Props{
|
||||
.key = std::move(props.key),
|
||||
.icon = std::move(props.icon),
|
||||
.getValue = [&var] { return var.getValue(); },
|
||||
.setValue =
|
||||
[&var, callback = std::move(props.onChange)](bool value) {
|
||||
if (value == var.getValue()) {
|
||||
return;
|
||||
}
|
||||
var.setValue(value);
|
||||
config::Save();
|
||||
if (callback) {
|
||||
callback(value);
|
||||
}
|
||||
},
|
||||
.isDisabled = std::move(props.isDisabled),
|
||||
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
|
||||
});
|
||||
leftPane.register_control(
|
||||
button, rightPane, [helpText = std::move(props.helpText)](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_rml(helpText);
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<float>& var,
|
||||
Rml::String key, Rml::String helpText, int min, int max, int step = 5,
|
||||
std::function<bool()> isDisabled = {}) {
|
||||
return leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = std::move(key),
|
||||
.getValue = [&var] { return float_setting_percent(var); },
|
||||
.setValue =
|
||||
[&var, min, max](int value) {
|
||||
var.setValue(std::clamp(value, min, max) / 100.0f);
|
||||
config::Save();
|
||||
},
|
||||
.isDisabled = std::move(isDisabled),
|
||||
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
|
||||
.min = min,
|
||||
.max = max,
|
||||
.step = step,
|
||||
.suffix = "%",
|
||||
})
|
||||
.on_focus([&rightPane, helpText = std::move(helpText)](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text(helpText);
|
||||
auto& button = leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = std::move(key),
|
||||
.getValue = [&var] { return float_setting_percent(var); },
|
||||
.setValue =
|
||||
[&var, min, max](int value) {
|
||||
var.setValue(std::clamp(value, min, max) / 100.0f);
|
||||
config::Save();
|
||||
},
|
||||
.isDisabled = std::move(isDisabled),
|
||||
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
|
||||
.min = min,
|
||||
.max = max,
|
||||
.step = step,
|
||||
.suffix = "%",
|
||||
});
|
||||
leftPane.register_control(button, rightPane, [helpText = std::move(helpText)](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text(helpText);
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void overlay_control(
|
||||
Window& window, Pane& leftPane, Pane& rightPane, ConfigVar<T>& var, const OverlayProps& props) {
|
||||
leftPane.register_control(
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = props.title,
|
||||
.getValue =
|
||||
[&var, option = props.option] {
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
return format_graphics_setting_value(
|
||||
option, float_setting_percent(var));
|
||||
} else {
|
||||
return format_graphics_setting_value(
|
||||
option, static_cast<int>(var.getValue()));
|
||||
}
|
||||
},
|
||||
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
|
||||
.submit = false,
|
||||
})
|
||||
.on_nav_command([&window, props](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
|
||||
cmd == NavCommand::Right) {
|
||||
window.push(std::make_unique<Overlay>(props));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
rightPane, [helpText = props.helpText](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text(helpText);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SettingsWindow::SettingsWindow() {
|
||||
add_tab("Audio", [this](Rml::Element* content) {
|
||||
add_tab("Graphics", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
// TODO: Individual sliders for Main Music, Sub Music, Sound Effects, and Fanfare.
|
||||
leftPane.add_section("Volume");
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Master Volume",
|
||||
.getValue = [] { return getSettings().audio.masterVolume.getValue(); },
|
||||
.setValue =
|
||||
[](int value) {
|
||||
getSettings().audio.masterVolume.setValue(value);
|
||||
config::Save();
|
||||
audio::SetMasterVolume(value / 100.f);
|
||||
},
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().audio.masterVolume.getValue() !=
|
||||
getSettings().audio.masterVolume.getDefaultValue();
|
||||
},
|
||||
.max = 100,
|
||||
.suffix = "%",
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text("Adjusts the volume of all sounds in the game.");
|
||||
});
|
||||
leftPane.add_section("Display");
|
||||
|
||||
leftPane.add_section("Effects");
|
||||
config_bool_select(leftPane, rightPane, getSettings().audio.enableReverb,
|
||||
leftPane.register_control(
|
||||
leftPane.add_button("Toggle Fullscreen").on_pressed([] {
|
||||
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
|
||||
VISetWindowFullscreen(getSettings().video.enableFullscreen);
|
||||
config::Save();
|
||||
}),
|
||||
rightPane, [](Pane& pane) { pane.clear(); }
|
||||
);
|
||||
leftPane.register_control(
|
||||
leftPane.add_button("Restore Default Window Size").on_pressed([] {
|
||||
getSettings().video.enableFullscreen.setValue(false);
|
||||
VISetWindowFullscreen(false);
|
||||
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
|
||||
VICenterWindow();
|
||||
}),
|
||||
rightPane, [](Pane& pane) { pane.clear(); }
|
||||
);
|
||||
config_bool_select(leftPane, rightPane, getSettings().video.enableVsync,
|
||||
{
|
||||
.key = "Enable Reverb",
|
||||
.helpText = "Enables the reverb effect in game audio.",
|
||||
.onChange = [](bool value) { audio::SetEnableReverb(value); },
|
||||
.key = "Enable VSync",
|
||||
.helpText = "Synchronizes the frame rate to your monitor's refresh rate.",
|
||||
.onChange = [](bool value) { aurora_enable_vsync(value); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().audio.enableHrtf,
|
||||
config_bool_select(leftPane, rightPane, getSettings().video.lockAspectRatio,
|
||||
{
|
||||
.key = "Enable Spatial Sound",
|
||||
.helpText = "Emulate surround sound via HRTF. Recommended only for use with headphones!",
|
||||
.onChange = [](bool value) { audio::EnableHrtf = value; },
|
||||
});
|
||||
|
||||
leftPane.add_section("Tweaks");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.noLowHpSound,
|
||||
{
|
||||
.key = "No Low HP Sound",
|
||||
.helpText = "Disable the beeping sound when having low health.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.midnasLamentNonStop,
|
||||
{
|
||||
.key = "Non-Stop Midna's Lament",
|
||||
.helpText = "Prevents enemy music while Midna's Lament is playing.",
|
||||
});
|
||||
});
|
||||
|
||||
add_tab("Cheats", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
auto addCheat = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, value,
|
||||
{
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
};
|
||||
|
||||
leftPane.add_section("Resources");
|
||||
addCheat("Infinite Hearts", getSettings().game.infiniteHearts,
|
||||
"Keeps your health full.");
|
||||
addCheat("Infinite Arrows", getSettings().game.infiniteArrows,
|
||||
"Keeps your arrow count full.");
|
||||
addCheat("Infinite Bombs", getSettings().game.infiniteBombs,
|
||||
"Keeps all bomb bags full.");
|
||||
addCheat("Infinite Oil", getSettings().game.infiniteOil,
|
||||
"Keeps your lantern oil full.");
|
||||
addCheat("Infinite Oxygen", getSettings().game.infiniteOxygen,
|
||||
"Keeps your underwater oxygen meter full.");
|
||||
addCheat("Infinite Rupees", getSettings().game.infiniteRupees,
|
||||
"Keeps your rupee count full.");
|
||||
addCheat("No Item Timer", getSettings().game.enableIndefiniteItemDrops,
|
||||
"Item drops such as rupees and hearts will never disappear after they drop.");
|
||||
|
||||
leftPane.add_section("Abilities");
|
||||
addCheat("Moon Jump (R+A)", getSettings().game.moonJump,
|
||||
"Hold R and A to rise into the air.");
|
||||
addCheat("Super Clawshot", getSettings().game.superClawshot,
|
||||
"Extends clawshot behavior beyond the normal game rules.");
|
||||
addCheat("Always Greatspin", getSettings().game.alwaysGreatspin,
|
||||
"Allows the Great Spin attack without requiring full health.");
|
||||
addCheat("Fast Iron Boots", getSettings().game.enableFastIronBoots,
|
||||
"Speeds up movement while wearing the Iron Boots.");
|
||||
addCheat("Can Transform Anywhere", getSettings().game.canTransformAnywhere,
|
||||
"Allows transforming even if NPCs are looking.");
|
||||
addCheat("Fast Spinner", getSettings().game.fastSpinner,
|
||||
"Speeds up Spinner movement while holding R.");
|
||||
addCheat("Free Magic Armor", getSettings().game.freeMagicArmor,
|
||||
"Lets the magic armor work without consuming rupees.");
|
||||
});
|
||||
|
||||
add_tab("Gameplay", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
auto addOption = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, value,
|
||||
{
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
});
|
||||
};
|
||||
auto addSpeedrunDisabledOption = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, value,
|
||||
{
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
};
|
||||
|
||||
leftPane.add_section("General");
|
||||
addOption("Mirror Mode", getSettings().game.enableMirrorMode,
|
||||
"Mirrors the world horizontally, matching the Wii version of the game.");
|
||||
addOption("Disable Main HUD", getSettings().game.disableMainHUD,
|
||||
"Disables the main HUD of the game.<br/>Useful for recording or a more immersive "
|
||||
"experience.");
|
||||
addOption("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches,
|
||||
"Restores patched glitches from Wii USA 1.0, the first released version.");
|
||||
addOption("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation,
|
||||
"Enables rotating Link in the collection menu with the C-Stick.");
|
||||
|
||||
leftPane.add_section("Difficulty");
|
||||
leftPane
|
||||
.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Damage Multiplier",
|
||||
.getValue = [] { return getSettings().game.damageMultiplier.getValue(); },
|
||||
.setValue =
|
||||
[](int value) {
|
||||
getSettings().game.damageMultiplier.setValue(value);
|
||||
config::Save();
|
||||
},
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.damageMultiplier.getValue() !=
|
||||
getSettings().game.damageMultiplier.getDefaultValue();
|
||||
},
|
||||
.min = 1,
|
||||
.max = 8,
|
||||
.prefix = "x",
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text("Multiplies incoming damage.");
|
||||
});
|
||||
addSpeedrunDisabledOption("Instant Death", getSettings().game.instantDeath,
|
||||
"Any hit will instantly kill you.");
|
||||
addSpeedrunDisabledOption("No Heart Drops", getSettings().game.noHeartDrops,
|
||||
"Hearts will never drop from enemies, pots, and various other places.");
|
||||
|
||||
leftPane.add_section("Quality of Life");
|
||||
addOption("Bigger Wallets", getSettings().game.biggerWallets,
|
||||
"Wallet sizes are like in the HD version. (500, 1000, 2000)");
|
||||
addOption("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes,
|
||||
"Rupees will not play cutscenes after you have collected them the first time.");
|
||||
addOption("Faster Climbing", getSettings().game.fastClimbing,
|
||||
"Quicker climbing on ladders and vines like the HD version.");
|
||||
addOption("Faster Tears of Light", getSettings().game.fastTears,
|
||||
"Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
|
||||
addOption("Autosave", getSettings().game.autoSave,
|
||||
"Autosaves the game when going to a new area, opening a dungeon door, or getting "
|
||||
"a new item.<br/><br/>This feature is currently experimental, use at your own risk.");
|
||||
addOption("Instant Saves", getSettings().game.instantSaves,
|
||||
"Skips the delay when writing to the Memory Card.");
|
||||
addOption("Hold B for Instant Text", getSettings().game.instantText,
|
||||
"Makes text scroll immediately by holding B.");
|
||||
addOption("No Climbing Miss Animation", getSettings().game.noMissClimbing,
|
||||
"Prevents Link from playing a struggle animation when grabbing ledges or "
|
||||
"climbing on vines.");
|
||||
addOption("No Rupee Returns", getSettings().game.noReturnRupees,
|
||||
"Always collect Rupees even if your Wallet is too full.");
|
||||
addOption("No Sword Recoil", getSettings().game.noSwordRecoil,
|
||||
"Link will not recoil when his sword hits walls.");
|
||||
addOption("No 2nd Fish for Cat", getSettings().game.no2ndFishForCat,
|
||||
"Skip needing to catch a second fish for Sera's cat.");
|
||||
addOption("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen,
|
||||
"Skips the TV calibration screen shown when loading a save.");
|
||||
addOption("Skip Warning Screen", getSettings().game.skipWarningScreen,
|
||||
"Skips the warning screen shown when starting the game.");
|
||||
addOption("Sun's Song (R+X)", getSettings().game.sunsSong,
|
||||
"Allows Wolf Link to howl and change the time of day.");
|
||||
addOption("Quick Transform (R+Y)", getSettings().game.enableQuickTransform,
|
||||
"Transform instantly by pressing R and Y simultaneously.");
|
||||
|
||||
leftPane.add_section("Speedrunning");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.speedrunMode,
|
||||
{
|
||||
.key = "Speedrun Mode",
|
||||
.helpText =
|
||||
"Enables speedrunning options while restricting certain gameplay modifiers.",
|
||||
.onChange = [](bool) { reset_for_speedrun_mode(); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.liveSplitEnabled,
|
||||
{
|
||||
.key = "LiveSplit Connection",
|
||||
.helpText = "Connect to LiveSplit server on localhost:16834.",
|
||||
.key = "Lock 4:3 Aspect Ratio",
|
||||
.helpText = "Lock the game's aspect ratio to the original.",
|
||||
.onChange =
|
||||
[](bool enabled) {
|
||||
if (enabled) {
|
||||
speedrun::connectLiveSplit();
|
||||
} else {
|
||||
speedrun::disconnectLiveSplit();
|
||||
}
|
||||
[](bool value) {
|
||||
AuroraSetViewportPolicy(
|
||||
value ? AURORA_VIEWPORT_FIT : AURORA_VIEWPORT_STRETCH);
|
||||
},
|
||||
.isDisabled = [] { return !getSettings().game.speedrunMode; },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.pauseOnFocusLost,
|
||||
{
|
||||
.key = "Pause on Focus Lost",
|
||||
.isDisabled = [] { return IsMobile; },
|
||||
});
|
||||
|
||||
leftPane.add_section("Resolution");
|
||||
overlay_control(*this, leftPane, rightPane, getSettings().game.internalResolutionScale,
|
||||
OverlayProps{
|
||||
.option = GraphicsOption::InternalResolution,
|
||||
.title = "Internal Resolution",
|
||||
.helpText = kInternalResolutionHelpText,
|
||||
.valueMin = 0,
|
||||
.valueMax = 12,
|
||||
.defaultValue = 0,
|
||||
});
|
||||
overlay_control(*this, leftPane, rightPane, getSettings().game.shadowResolutionMultiplier,
|
||||
OverlayProps{
|
||||
.option = GraphicsOption::ShadowResolution,
|
||||
.title = "Shadow Resolution",
|
||||
.helpText = kShadowResolutionHelpText,
|
||||
.valueMin = 1,
|
||||
.valueMax = 8,
|
||||
.defaultValue = 1,
|
||||
});
|
||||
|
||||
leftPane.add_section("Post-Processing");
|
||||
overlay_control(*this, leftPane, rightPane, getSettings().game.bloomMode,
|
||||
OverlayProps{
|
||||
.option = GraphicsOption::BloomMode,
|
||||
.title = "Bloom",
|
||||
.helpText = kBloomHelpText,
|
||||
.valueMin = static_cast<int>(BloomMode::Off),
|
||||
.valueMax = static_cast<int>(BloomMode::Dusk),
|
||||
.defaultValue = static_cast<int>(BloomMode::Classic),
|
||||
});
|
||||
overlay_control(*this, leftPane, rightPane, getSettings().game.bloomMultiplier,
|
||||
OverlayProps{
|
||||
.option = GraphicsOption::BloomMultiplier,
|
||||
.title = "Bloom Brightness",
|
||||
.helpText = kBloomBrightnessHelpText,
|
||||
.valueMin = 0,
|
||||
.valueMax = 100,
|
||||
.defaultValue = 100,
|
||||
});
|
||||
|
||||
leftPane.add_section("Rendering");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableFrameInterpolation,
|
||||
{
|
||||
.key = "Unlock Framerate",
|
||||
.helpText = kUnlockFramerateHelpText,
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableDepthOfField,
|
||||
{
|
||||
.key = "Enable Depth of Field",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableMapBackground,
|
||||
{
|
||||
.key = "Enable Mini-Map Shadows",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -367,11 +282,12 @@ SettingsWindow::SettingsWindow() {
|
||||
};
|
||||
|
||||
leftPane.add_section("Controller");
|
||||
leftPane.add_button("Configure Controller")
|
||||
.on_pressed([] { push_document(std::make_unique<ControllerConfigWindow>()); })
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text("Open controller binding configuration.");
|
||||
leftPane.register_control(leftPane.add_button("Configure Controller").on_pressed([this] {
|
||||
push(std::make_unique<ControllerConfigWindow>());
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text("Open controller binding configuration.");
|
||||
});
|
||||
|
||||
leftPane.add_section("Camera");
|
||||
@@ -420,199 +336,232 @@ SettingsWindow::SettingsWindow() {
|
||||
[] { return getSettings().game.speedrunMode; });
|
||||
});
|
||||
|
||||
add_tab("Graphics", [this](Rml::Element* content) {
|
||||
add_tab("Audio", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Display");
|
||||
|
||||
leftPane.add_button("Toggle Fullscreen").on_pressed([] {
|
||||
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
|
||||
VISetWindowFullscreen(getSettings().video.enableFullscreen);
|
||||
config::Save();
|
||||
});
|
||||
leftPane.add_button("Restore Default Window Size").on_pressed([] {
|
||||
getSettings().video.enableFullscreen.setValue(false);
|
||||
VISetWindowFullscreen(false);
|
||||
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
|
||||
VICenterWindow();
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().video.enableVsync,
|
||||
{
|
||||
.key = "Enable VSync",
|
||||
.helpText = "Synchronizes the frame rate to your monitor's refresh rate.",
|
||||
.onChange = [](bool value) { aurora_enable_vsync(value); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().video.lockAspectRatio,
|
||||
{
|
||||
.key = "Lock 4:3 Aspect Ratio",
|
||||
.helpText = "Lock the game's aspect ratio to the original.",
|
||||
.onChange =
|
||||
[](bool value) {
|
||||
AuroraSetViewportPolicy(
|
||||
value ? AURORA_VIEWPORT_FIT : AURORA_VIEWPORT_STRETCH);
|
||||
},
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.pauseOnFocusLost,
|
||||
{
|
||||
.key = "Pause on Focus Lost",
|
||||
.isDisabled = [] { return IsMobile; },
|
||||
});
|
||||
|
||||
leftPane.add_section("Resolution");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Internal Resolution",
|
||||
.getValue =
|
||||
[] {
|
||||
return format_graphics_setting_value(GraphicsOption::InternalResolution,
|
||||
getSettings().game.internalResolutionScale.getValue());
|
||||
// TODO: Individual sliders for Main Music, Sub Music, Sound Effects, and Fanfare.
|
||||
leftPane.add_section("Volume");
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Master Volume",
|
||||
.getValue = [] { return getSettings().audio.masterVolume.getValue(); },
|
||||
.setValue =
|
||||
[](int value) {
|
||||
getSettings().audio.masterVolume.setValue(value);
|
||||
config::Save();
|
||||
audio::SetMasterVolume(value / 100.f);
|
||||
},
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.internalResolutionScale.getValue() !=
|
||||
getSettings().game.internalResolutionScale.getDefaultValue();
|
||||
return getSettings().audio.masterVolume.getValue() !=
|
||||
getSettings().audio.masterVolume.getDefaultValue();
|
||||
},
|
||||
})
|
||||
.on_nav_command([](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
|
||||
cmd == NavCommand::Right) {
|
||||
push_document(std::make_unique<Overlay>(OverlayProps{
|
||||
.option = GraphicsOption::InternalResolution,
|
||||
.title = "Internal Resolution",
|
||||
.helpText = kInternalResolutionHelpText,
|
||||
.valueMin = 0,
|
||||
.valueMax = 12,
|
||||
.defaultValue = 0,
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text(kInternalResolutionHelpText);
|
||||
});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Shadow Resolution",
|
||||
.getValue =
|
||||
[] {
|
||||
return format_graphics_setting_value(GraphicsOption::ShadowResolution,
|
||||
getSettings().game.shadowResolutionMultiplier.getValue());
|
||||
},
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.shadowResolutionMultiplier.getValue() !=
|
||||
getSettings().game.shadowResolutionMultiplier.getDefaultValue();
|
||||
},
|
||||
})
|
||||
.on_nav_command([](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
|
||||
cmd == NavCommand::Right) {
|
||||
push_document(std::make_unique<Overlay>(OverlayProps{
|
||||
.option = GraphicsOption::ShadowResolution,
|
||||
.title = "Shadow Resolution",
|
||||
.helpText = kShadowResolutionHelpText,
|
||||
.valueMin = 1,
|
||||
.valueMax = 8,
|
||||
.defaultValue = 1,
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text(kShadowResolutionHelpText);
|
||||
.max = 100,
|
||||
.suffix = "%",
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text("Adjusts the volume of all sounds in the game.");
|
||||
});
|
||||
|
||||
leftPane.add_section("Post-Processing");
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Bloom",
|
||||
.getValue =
|
||||
[] {
|
||||
return format_graphics_setting_value(GraphicsOption::BloomMode,
|
||||
static_cast<int>(getSettings().game.bloomMode.getValue()));
|
||||
},
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.bloomMode.getValue() !=
|
||||
getSettings().game.bloomMode.getDefaultValue();
|
||||
},
|
||||
})
|
||||
.on_nav_command([](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
|
||||
cmd == NavCommand::Right) {
|
||||
push_document(std::make_unique<Overlay>(OverlayProps{
|
||||
.option = GraphicsOption::BloomMode,
|
||||
.title = "Bloom",
|
||||
.helpText = kBloomHelpText,
|
||||
.valueMin = static_cast<int>(BloomMode::Off),
|
||||
.valueMax = static_cast<int>(BloomMode::Dusk),
|
||||
.defaultValue = static_cast<int>(BloomMode::Classic),
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text(kBloomHelpText);
|
||||
});
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Bloom Brightness",
|
||||
.getValue =
|
||||
[] {
|
||||
return format_graphics_setting_value(
|
||||
GraphicsOption::BloomMultiplier, bloom_multiplier_percent());
|
||||
},
|
||||
.isDisabled =
|
||||
[] { return getSettings().game.bloomMode.getValue() == BloomMode::Off; },
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.bloomMultiplier.getValue() !=
|
||||
getSettings().game.bloomMultiplier.getDefaultValue();
|
||||
},
|
||||
})
|
||||
.on_nav_command([](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
|
||||
cmd == NavCommand::Right) {
|
||||
push_document(std::make_unique<Overlay>(OverlayProps{
|
||||
.option = GraphicsOption::BloomMultiplier,
|
||||
.title = "Bloom Brightness",
|
||||
.helpText = kBloomBrightnessHelpText,
|
||||
.valueMin = 0,
|
||||
.valueMax = 100,
|
||||
.defaultValue = 100,
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.on_focus([&rightPane](Rml::Event&) {
|
||||
rightPane.clear();
|
||||
rightPane.add_text(kBloomBrightnessHelpText);
|
||||
});
|
||||
|
||||
leftPane.add_section("Rendering");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableFrameInterpolation,
|
||||
leftPane.add_section("Effects");
|
||||
config_bool_select(leftPane, rightPane, getSettings().audio.enableReverb,
|
||||
{
|
||||
.key = "Unlock Framerate",
|
||||
.key = "Enable Reverb",
|
||||
.helpText = "Enables the reverb effect in game audio.",
|
||||
.onChange = [](bool value) { audio::SetEnableReverb(value); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().audio.enableHrtf,
|
||||
{
|
||||
.key = "Enable Spatial Sound",
|
||||
.helpText =
|
||||
"Uses inter-frame interpolation to enable higher frame rates.<br/><br/>Visual "
|
||||
"artifacts, animation glitches, or instability may occur.",
|
||||
"Emulate surround sound via HRTF. Recommended only for use with headphones!",
|
||||
.onChange = [](bool value) { audio::EnableHrtf = value; },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableDepthOfField,
|
||||
config_bool_select(leftPane, rightPane, getSettings().audio.menuSounds,
|
||||
{
|
||||
.key = "Enable Depth of Field",
|
||||
.key = "Dusk Menu Sounds",
|
||||
.helpText = "Play sound effects when navigating the Dusk menu.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableMapBackground,
|
||||
|
||||
leftPane.add_section("Tweaks");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.noLowHpSound,
|
||||
{
|
||||
.key = "Enable Mini-Map Shadows",
|
||||
.key = "No Low HP Sound",
|
||||
.helpText = "Disable the beeping sound when having low health.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.midnasLamentNonStop,
|
||||
{
|
||||
.key = "Non-Stop Midna's Lament",
|
||||
.helpText = "Prevents enemy music while Midna's Lament is playing.",
|
||||
});
|
||||
});
|
||||
|
||||
add_tab("Gameplay", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
auto addOption = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, value,
|
||||
{
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
});
|
||||
};
|
||||
auto addSpeedrunDisabledOption = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, value,
|
||||
{
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
};
|
||||
|
||||
leftPane.add_section("General");
|
||||
addOption("Mirror Mode", getSettings().game.enableMirrorMode,
|
||||
"Mirrors the world horizontally, matching the Wii version of the game.");
|
||||
addOption("Disable Main HUD", getSettings().game.disableMainHUD,
|
||||
"Disables the main HUD of the game.<br/>Useful for recording or a more immersive "
|
||||
"experience.");
|
||||
addOption("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches,
|
||||
"Restores patched glitches from Wii USA 1.0, the first released version.");
|
||||
addOption("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation,
|
||||
"Enables rotating Link in the collection menu with the C-Stick.");
|
||||
|
||||
leftPane.add_section("Difficulty");
|
||||
leftPane.register_control(
|
||||
leftPane.add_child<NumberButton>(NumberButton::Props{
|
||||
.key = "Damage Multiplier",
|
||||
.getValue = [] { return getSettings().game.damageMultiplier.getValue(); },
|
||||
.setValue =
|
||||
[](int value) {
|
||||
getSettings().game.damageMultiplier.setValue(value);
|
||||
config::Save();
|
||||
},
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.damageMultiplier.getValue() !=
|
||||
getSettings().game.damageMultiplier.getDefaultValue();
|
||||
},
|
||||
.min = 1,
|
||||
.max = 8,
|
||||
.suffix = "×",
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text("Multiplies incoming damage.");
|
||||
});
|
||||
addSpeedrunDisabledOption(
|
||||
"Instant Death", getSettings().game.instantDeath, "Any hit will instantly kill you.");
|
||||
addSpeedrunDisabledOption("No Heart Drops", getSettings().game.noHeartDrops,
|
||||
"Hearts will never drop from enemies, pots, and various other places.");
|
||||
|
||||
leftPane.add_section("Quality of Life");
|
||||
addOption("Bigger Wallets", getSettings().game.biggerWallets,
|
||||
"Wallet sizes are like in the HD version. (500, 1000, 2000)");
|
||||
addOption("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes,
|
||||
"Rupees will not play cutscenes after you have collected them the first time.");
|
||||
addOption("Faster Climbing", getSettings().game.fastClimbing,
|
||||
"Quicker climbing on ladders and vines like the HD version.");
|
||||
addOption("Faster Tears of Light", getSettings().game.fastTears,
|
||||
"Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.autoSave,
|
||||
{
|
||||
.key = "Autosave",
|
||||
.icon = "warning",
|
||||
.helpText =
|
||||
"Autosaves the game when going to a new area, opening a dungeon door, "
|
||||
"or getting a new item.<br/><br/><icon class=\"warning\"/> Experimental "
|
||||
"feature: Use at your own risk.",
|
||||
});
|
||||
addOption("Instant Saves", getSettings().game.instantSaves,
|
||||
"Skips the delay when writing to the Memory Card.");
|
||||
addOption("Hold B for Instant Text", getSettings().game.instantText,
|
||||
"Makes text scroll immediately by holding B.");
|
||||
addOption("No Climbing Miss Animation", getSettings().game.noMissClimbing,
|
||||
"Prevents Link from playing a struggle animation when grabbing ledges or "
|
||||
"climbing on vines.");
|
||||
addOption("No Rupee Returns", getSettings().game.noReturnRupees,
|
||||
"Always collect Rupees even if your Wallet is too full.");
|
||||
addOption("No Sword Recoil", getSettings().game.noSwordRecoil,
|
||||
"Link will not recoil when his sword hits walls.");
|
||||
addOption("No 2nd Fish for Cat", getSettings().game.no2ndFishForCat,
|
||||
"Skip needing to catch a second fish for Sera's cat.");
|
||||
addOption("Sun's Song (R+X)", getSettings().game.sunsSong,
|
||||
"Allows Wolf Link to howl and change the time of day.");
|
||||
addOption("Quick Transform (R+Y)", getSettings().game.enableQuickTransform,
|
||||
"Transform instantly by pressing R and Y simultaneously.");
|
||||
|
||||
leftPane.add_section("Speedrunning");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.speedrunMode,
|
||||
{
|
||||
.key = "Speedrun Mode",
|
||||
.helpText =
|
||||
"Enables speedrunning options while restricting certain gameplay modifiers.",
|
||||
.onChange = [](bool) { reset_for_speedrun_mode(); },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.liveSplitEnabled,
|
||||
{
|
||||
.key = "LiveSplit Connection",
|
||||
.helpText = "Connect to LiveSplit server on localhost:16834.",
|
||||
.onChange =
|
||||
[](bool enabled) {
|
||||
if (enabled) {
|
||||
speedrun::connectLiveSplit();
|
||||
} else {
|
||||
speedrun::disconnectLiveSplit();
|
||||
}
|
||||
},
|
||||
.isDisabled = [] { return !getSettings().game.speedrunMode; },
|
||||
});
|
||||
});
|
||||
|
||||
add_tab("Cheats", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
auto addCheat = [&](const Rml::String& key, ConfigVar<bool>& value,
|
||||
const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, value,
|
||||
{
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
};
|
||||
|
||||
leftPane.add_section("Resources");
|
||||
addCheat("Infinite Hearts", getSettings().game.infiniteHearts, "Keeps your health full.");
|
||||
addCheat(
|
||||
"Infinite Arrows", getSettings().game.infiniteArrows, "Keeps your arrow count full.");
|
||||
addCheat("Infinite Bombs", getSettings().game.infiniteBombs, "Keeps all bomb bags full.");
|
||||
addCheat("Infinite Oil", getSettings().game.infiniteOil, "Keeps your lantern oil full.");
|
||||
addCheat("Infinite Oxygen", getSettings().game.infiniteOxygen,
|
||||
"Keeps your underwater oxygen meter full.");
|
||||
addCheat(
|
||||
"Infinite Rupees", getSettings().game.infiniteRupees, "Keeps your rupee count full.");
|
||||
addCheat("No Item Timer", getSettings().game.enableIndefiniteItemDrops,
|
||||
"Item drops such as rupees and hearts will never disappear after they drop.");
|
||||
|
||||
leftPane.add_section("Abilities");
|
||||
addCheat(
|
||||
"Moon Jump (R+A)", getSettings().game.moonJump, "Hold R and A to rise into the air.");
|
||||
addCheat("Super Clawshot", getSettings().game.superClawshot,
|
||||
"Extends clawshot behavior beyond the normal game rules.");
|
||||
addCheat("Always Greatspin", getSettings().game.alwaysGreatspin,
|
||||
"Allows the Great Spin attack without requiring full health.");
|
||||
addCheat("Fast Iron Boots", getSettings().game.enableFastIronBoots,
|
||||
"Speeds up movement while wearing the Iron Boots.");
|
||||
addCheat("Can Transform Anywhere", getSettings().game.canTransformAnywhere,
|
||||
"Allows transforming even if NPCs are looking.");
|
||||
addCheat("Fast Spinner", getSettings().game.fastSpinner,
|
||||
"Speeds up Spinner movement while holding R.");
|
||||
addCheat("Free Magic Armor", getSettings().game.freeMagicArmor,
|
||||
"Lets the magic armor work without consuming rupees.");
|
||||
});
|
||||
|
||||
// TODO: Reorganize all of this?
|
||||
@@ -622,28 +571,41 @@ SettingsWindow::SettingsWindow() {
|
||||
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableAchievementNotifications,
|
||||
{
|
||||
.key = "Enable Achievement Notifications",
|
||||
.key = "Achievement Notifications",
|
||||
.helpText = "Display a toast when an achievement is unlocked.",
|
||||
});
|
||||
#if DUSK_ENABLE_SENTRY_NATIVE
|
||||
config_bool_select(leftPane, rightPane, getSettings().backend.enableCrashReporting,
|
||||
{
|
||||
.key = "Enable Crash Reporting",
|
||||
{.key = "Crash Reporting",
|
||||
.helpText = "Enable automatic reporting of crashes to the developers.<br/><br/>"
|
||||
"Submissions include logs which may contain sensitive information. Refrain from "
|
||||
"enabling reporting if you do not agree with the following inclusions:<br/><br/> "
|
||||
"- Operating System<br/>- CPU Architecture<br/>- GPU Model & Driver Version<br/>"
|
||||
"- Account Username"
|
||||
});
|
||||
"Submissions include logs which may contain sensitive information. "
|
||||
"Refrain from "
|
||||
"enabling reporting if you do not agree with the following "
|
||||
"inclusions:<br/><br/> "
|
||||
"- Operating System<br/>- CPU Architecture<br/>- GPU Model & Driver "
|
||||
"Version<br/>"
|
||||
"- Account Username"});
|
||||
#endif
|
||||
config_bool_select(leftPane, rightPane, getSettings().backend.skipPreLaunchUI,
|
||||
{
|
||||
.key = "Skip Pre-Launch UI",
|
||||
.key = "Skip Dusk Main Menu",
|
||||
.helpText = "When starting Dusk, skips the main menu and boots straight into the "
|
||||
"game if a disc image is available.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.hideTvSettingsScreen,
|
||||
{
|
||||
.key = "Skip TV Settings Screen",
|
||||
.helpText = "Skips the TV calibration screen shown when loading a save.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.skipWarningScreen,
|
||||
{
|
||||
.key = "Skip Warning Screen",
|
||||
.helpText = "Skips the warning screen shown when starting the game.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().backend.showPipelineCompilation,
|
||||
{
|
||||
.key = "Show Pipeline Compilation",
|
||||
.helpText = "Show an overlay when shaders are being compiled for your hardware."
|
||||
.helpText = "Show an overlay when shaders are being compiled for your hardware.",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+58
-12
@@ -1,5 +1,8 @@
|
||||
#include "tab_bar.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
@@ -41,7 +44,15 @@ TabBar::TabBar(Rml::Element* parent, Props props)
|
||||
: FluentComponent(createRoot(parent)), mProps(std::move(props)) {
|
||||
if (mProps.onClose) {
|
||||
mRoot->SetAttribute("closable", "");
|
||||
add_child<Button>(Button::Props{}, "close").on_pressed([this] { mProps.onClose(); });
|
||||
add_child<Button>(Button::Props{}, "close")
|
||||
.on_nav_command([this](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_CURSOR_CANCEL);
|
||||
mProps.onClose();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
mEndSpacer = append(mRoot, "tab-end-spacer");
|
||||
}
|
||||
|
||||
@@ -85,12 +96,14 @@ bool TabBar::focus() {
|
||||
if (mProps.selectedTabIndex >= 0 && mProps.selectedTabIndex < mTabs.size()) {
|
||||
// Try to focus the currently selected tab
|
||||
if (mTabs[mProps.selectedTabIndex].button.focus()) {
|
||||
mLastFocusedTabIndex = mProps.selectedTabIndex;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Otherwise, focus the first enabled tab
|
||||
for (const auto& tab : mTabs) {
|
||||
if (tab.button.focus()) {
|
||||
for (int i = 0; i < static_cast<int>(mTabs.size()); ++i) {
|
||||
if (mTabs[i].button.focus()) {
|
||||
mLastFocusedTabIndex = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +117,14 @@ void TabBar::add_tab(const Rml::String& title, TabCallback callback) {
|
||||
callback();
|
||||
}
|
||||
auto& button = add_child<Button>(Button::Props{title}, "tab");
|
||||
button.on_pressed([this, index] { set_active_tab(index); });
|
||||
button.on_nav_command([this, index](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
mDoAud_seStartMenu(mProps.autoSelect ? Z2SE_SY_NAME_CURSOR : Z2SE_SY_CURSOR_OK);
|
||||
set_active_tab(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (selected) {
|
||||
button.set_selected(true);
|
||||
}
|
||||
@@ -134,6 +154,7 @@ bool TabBar::set_active_tab(int index) {
|
||||
}
|
||||
const auto& tab = mTabs[index];
|
||||
if (tab.button.focus()) {
|
||||
mLastFocusedTabIndex = index;
|
||||
for (int i = 0; i < static_cast<int>(mTabs.size()); ++i) {
|
||||
mTabs[i].button.set_selected(i == index);
|
||||
}
|
||||
@@ -146,11 +167,28 @@ bool TabBar::set_active_tab(int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void TabBar::refresh_active_tab() {
|
||||
if (mProps.selectedTabIndex >= 0 && mProps.selectedTabIndex < static_cast<int>(mTabs.size())) {
|
||||
const auto& tab = mTabs[mProps.selectedTabIndex];
|
||||
if (tab.callback) {
|
||||
tab.callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int TabBar::focused_tab_index() const {
|
||||
return mLastFocusedTabIndex;
|
||||
}
|
||||
|
||||
bool TabBar::focus_tab(int index) {
|
||||
if (index < 0 || index >= mTabs.size() || index == mProps.selectedTabIndex) {
|
||||
return false;
|
||||
}
|
||||
return mTabs[index].button.focus();
|
||||
if (mTabs[index].button.focus()) {
|
||||
mLastFocusedTabIndex = index;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int TabBar::tab_containing(Rml::Element* element) const {
|
||||
@@ -169,21 +207,29 @@ bool TabBar::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
bool isNext = cmd == NavCommand::Right || cmd == NavCommand::Next;
|
||||
int currentComponent = mProps.selectedTabIndex;
|
||||
if (cmd == NavCommand::Left || cmd == NavCommand::Right) {
|
||||
currentComponent = tab_containing(event.GetTargetElement());
|
||||
int activeTab = tab_containing(event.GetTargetElement());
|
||||
if (activeTab != -1) {
|
||||
currentComponent = activeTab;
|
||||
}
|
||||
}
|
||||
int direction = isNext ? 1 : -1;
|
||||
int i = currentComponent + direction;
|
||||
if (currentComponent == -1) {
|
||||
// If the container itself is focused and right is pressed, focus the first element
|
||||
if (isNext) {
|
||||
i = 0;
|
||||
if (cmd == NavCommand::Left || cmd == NavCommand::Right) {
|
||||
// If the container itself is focused and right is pressed, focus the first element
|
||||
if (!isNext) {
|
||||
return false;
|
||||
}
|
||||
currentComponent = -1;
|
||||
} else {
|
||||
// Otherwise, allow event to bubble
|
||||
// Next/Previous require a currently selected tab to navigate from
|
||||
return false;
|
||||
}
|
||||
}
|
||||
int i = currentComponent + direction;
|
||||
while (i >= 0 && i < mTabs.size()) {
|
||||
if (mProps.autoSelect ? set_active_tab(i) : focus_tab(i)) {
|
||||
const bool changed = mProps.autoSelect ? set_active_tab(i) : focus_tab(i);
|
||||
if (changed) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
|
||||
return true;
|
||||
}
|
||||
i += direction;
|
||||
|
||||
@@ -28,7 +28,9 @@ public:
|
||||
|
||||
void add_tab(const Rml::String& title, TabCallback callback);
|
||||
bool set_active_tab(int index);
|
||||
void refresh_active_tab();
|
||||
bool focus_tab(int index);
|
||||
int focused_tab_index() const;
|
||||
bool handle_nav_command(Rml::Event& event, NavCommand cmd);
|
||||
|
||||
private:
|
||||
@@ -38,6 +40,7 @@ private:
|
||||
std::vector<Tab> mTabs;
|
||||
Rml::Element* mEndSpacer = nullptr;
|
||||
bool mRedirectingScroll = false;
|
||||
int mLastFocusedTabIndex = -1;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
+14
-3
@@ -52,9 +52,6 @@ void shutdown() noexcept {
|
||||
}
|
||||
|
||||
Document& push_document(std::unique_ptr<Document> doc, bool show) noexcept {
|
||||
if (auto* top = top_document()) {
|
||||
top->hide(false);
|
||||
}
|
||||
Document& ret = *doc;
|
||||
sDocuments.push_back({std::move(doc)});
|
||||
if (show) {
|
||||
@@ -90,9 +87,23 @@ void update() noexcept {
|
||||
for (const auto& doc : sDocuments) {
|
||||
doc->update();
|
||||
}
|
||||
|
||||
// Remove closed documents
|
||||
const auto [first, last] =
|
||||
std::ranges::remove_if(sDocuments, [](const auto& doc) { return doc->closed(); });
|
||||
sDocuments.erase(first, last);
|
||||
|
||||
// If no documents have focus, explicitly focus the top one
|
||||
if (auto* context = aurora::rmlui::get_context();
|
||||
context != nullptr && context->GetFocusElement() == nullptr)
|
||||
{
|
||||
for (auto& doc : std::views::reverse(sDocuments)) {
|
||||
if (!doc->closed() && !doc->pending_close() && doc->focus()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sync_input_block();
|
||||
}
|
||||
|
||||
|
||||
+19
-3
@@ -6,6 +6,9 @@
|
||||
#include "pane.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
@@ -136,6 +139,10 @@ bool Window::set_active_tab(int index) {
|
||||
return mTabBar->set_active_tab(index);
|
||||
}
|
||||
|
||||
void Window::refresh_active_tab() {
|
||||
mTabBar->refresh_active_tab();
|
||||
}
|
||||
|
||||
void Window::add_tab(const Rml::String& title, TabBuilder builder) {
|
||||
mTabBar->add_tab(title, [this, builder = std::move(builder)] {
|
||||
clear_content();
|
||||
@@ -168,11 +175,13 @@ bool Window::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
}
|
||||
}
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Down) {
|
||||
if (!mContentComponents.empty()) {
|
||||
return mContentComponents.front()->focus();
|
||||
if (!mContentComponents.empty() && mContentComponents.front()->focus()) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (cmd == NavCommand::Cancel) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_CURSOR_CANCEL);
|
||||
pop();
|
||||
return true;
|
||||
}
|
||||
@@ -184,7 +193,14 @@ bool Window::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
|
||||
bool Window::handle_content_nav(Rml::Event& event, NavCommand cmd) noexcept {
|
||||
if (cmd == NavCommand::Up) {
|
||||
return focus();
|
||||
if (focus()) {
|
||||
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (cmd == NavCommand::Down) {
|
||||
// End of content, avoid looping
|
||||
return true;
|
||||
} else if (cmd == NavCommand::Cancel) {
|
||||
int currentComponent = -1;
|
||||
for (int i = 0; i < mContentComponents.size(); ++i) {
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
|
||||
protected:
|
||||
void add_tab(const Rml::String& title, TabBuilder builder);
|
||||
void refresh_active_tab();
|
||||
void update_safe_area() noexcept;
|
||||
void clear_content() noexcept;
|
||||
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
|
||||
|
||||
@@ -52,15 +52,14 @@
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/game_clock.h"
|
||||
#include "dusk/gyro.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/imgui/ImGuiEngine.hpp"
|
||||
#include "dusk/logging.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "dusk/ui/editor.hpp"
|
||||
#include "dusk/ui/popup.hpp"
|
||||
#include "dusk/ui/prelaunch.hpp"
|
||||
#include "dusk/ui/settings.hpp"
|
||||
#include "dusk/ui/preset.hpp"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "version.h"
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
@@ -650,6 +649,9 @@ int game_main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::Popup>(), false);
|
||||
if (!dusk::getSettings().backend.wasPresetChosen) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::PresetWindow>());
|
||||
}
|
||||
|
||||
dusk::version::init();
|
||||
LanguageInit();
|
||||
|
||||
Reference in New Issue
Block a user