mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-09-07 18:20:30 -04:00
Merge branch 'main' of https://github.com/TwilitRealm/dusk into randomizer
This commit is contained in:
@@ -51,10 +51,13 @@
|
||||
#include "d/actor/d_a_ni.h"
|
||||
#include "d/d_s_play.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "res/Object/Alink.h"
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
#include "dusk/randomizer/game/flags.h"
|
||||
#include "dusk/randomizer/game/stages.h"
|
||||
@@ -9370,6 +9373,12 @@ BOOL daAlink_c::spActionTrigger() {
|
||||
}
|
||||
|
||||
BOOL daAlink_c::midnaTalkTrigger() const {
|
||||
#if TARGET_PC
|
||||
// If we have a custom bind for Midna, check that instead
|
||||
if (dusk::isActionBound(dusk::ActionBinds::CALL_MIDNA, 0)) {
|
||||
return dusk::getActionBindTrig(dusk::ActionBinds::CALL_MIDNA, 0);
|
||||
}
|
||||
#endif
|
||||
return mItemTrigger & BTN_Z;
|
||||
}
|
||||
|
||||
@@ -16144,6 +16153,9 @@ int daAlink_c::procSlideLand() {
|
||||
|
||||
int daAlink_c::procFrontRollInit() {
|
||||
BOOL is_guard_anime = checkUpperGuardAnime();
|
||||
#ifdef TARGET_PC
|
||||
const f32 fastRollMultiplier = dusk::getSettings().game.fastRoll ? 2.0f : 1.0f;
|
||||
#endif
|
||||
|
||||
if (mProcID == PROC_FRONT_ROLL && mDemo.getDemoMode() == daPy_demo_c::DEMO_FRONT_ROLL_e) {
|
||||
return 0;
|
||||
@@ -16159,10 +16171,16 @@ int daAlink_c::procFrontRollInit() {
|
||||
roll_anm_speed = mpHIO->mFrontRoll.m.mRollAnm.mStartFrame;
|
||||
}
|
||||
|
||||
setSingleAnime(ANM_FRONT_ROLL, mpHIO->mFrontRoll.m.mRollAnm.mSpeed, roll_anm_speed,
|
||||
setSingleAnime(ANM_FRONT_ROLL,
|
||||
#ifdef TARGET_PC
|
||||
mpHIO->mFrontRoll.m.mRollAnm.mSpeed * fastRollMultiplier,
|
||||
#else
|
||||
mpHIO->mFrontRoll.m.mRollAnm.mSpeed,
|
||||
#endif
|
||||
roll_anm_speed,
|
||||
mpHIO->mFrontRoll.m.mRollAnm.mEndFrame,
|
||||
mpHIO->mFrontRoll.m.mRollAnm.mInterpolation);
|
||||
|
||||
|
||||
mNormalSpeed = speedF * mpHIO->mFrontRoll.m.mSpeedRate + mpHIO->mFrontRoll.m.mInitSpeed;
|
||||
|
||||
f32 max_speed = mpHIO->mFrontRoll.m.mInitSpeed + mpHIO->mMove.m.mMaxSpeed * mpHIO->mFrontRoll.m.mSpeedRate;
|
||||
@@ -16175,11 +16193,20 @@ int daAlink_c::procFrontRollInit() {
|
||||
}
|
||||
|
||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
#if TARGET_PC
|
||||
if (!(dusk::getSettings().game.enableFastIronBoots))
|
||||
#endif
|
||||
{
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
}
|
||||
} else if (checkHeavyStateOn(TRUE, TRUE)) {
|
||||
mNormalSpeed *= mHeavySpeedMultiplier;
|
||||
}
|
||||
|
||||
#ifdef TARGET_PC
|
||||
mNormalSpeed *= fastRollMultiplier;
|
||||
#endif
|
||||
|
||||
current.angle.y = shape_angle.y;
|
||||
voiceStart(Z2SE_AL_V_BACKTEN);
|
||||
mProcVar2.field_0x300c = 0;
|
||||
@@ -16310,8 +16337,13 @@ int daAlink_c::procFrontRollCrashInit() {
|
||||
speed.y = mpHIO->mFrontRoll.m.mCrashSpeedV;
|
||||
|
||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY;
|
||||
#if TARGET_PC
|
||||
if (!(dusk::getSettings().game.enableFastIronBoots))
|
||||
#endif
|
||||
{
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
speed.y *= mpHIO->mItem.mIronBoots.m.mWaterVelocityY;
|
||||
}
|
||||
}
|
||||
|
||||
ANGLE_ADD_2(current.angle.y, 0x8000);
|
||||
@@ -16405,6 +16437,9 @@ int daAlink_c::procFrontRollSuccess() {
|
||||
|
||||
int daAlink_c::procSideRollInit(int param_0) {
|
||||
BOOL is_prev_guardAnm = checkUpperGuardAnime();
|
||||
#ifdef TARGET_PC
|
||||
const f32 fastRollMultiplier = dusk::getSettings().game.fastRoll ? 2.0f : 1.0f;
|
||||
#endif
|
||||
|
||||
if (!commonProcInitNotSameProc(PROC_SIDE_ROLL)) {
|
||||
return 0;
|
||||
@@ -16421,17 +16456,30 @@ int daAlink_c::procSideRollInit(int param_0) {
|
||||
current.angle.y = shape_angle.y + -0x4000;
|
||||
}
|
||||
|
||||
setSingleAnime(anmID, mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed,
|
||||
setSingleAnime(anmID,
|
||||
#ifdef TARGET_PC
|
||||
mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed * fastRollMultiplier,
|
||||
#else
|
||||
mpHIO->mGuard.mTurnMove.m.mSideRollAnmSpeed,
|
||||
#endif
|
||||
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mStartFrame,
|
||||
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mEndFrame,
|
||||
mpHIO->mGuard.mTurnMove.m.mTurnAnm.mInterpolation);
|
||||
mNormalSpeed = mpHIO->mGuard.mTurnMove.m.mSideRollSpeed;
|
||||
|
||||
if (checkNoResetFlg0(FLG0_WATER_IN_MOVE)) {
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
#if TARGET_PC
|
||||
if (!(dusk::getSettings().game.enableFastIronBoots))
|
||||
#endif
|
||||
{
|
||||
mNormalSpeed *= mpHIO->mItem.mIronBoots.m.mWaterVelocityX;
|
||||
}
|
||||
} else if (checkHeavyStateOn(TRUE, TRUE)) {
|
||||
mNormalSpeed *= mHeavySpeedMultiplier;
|
||||
}
|
||||
#ifdef TARGET_PC
|
||||
mNormalSpeed *= fastRollMultiplier;
|
||||
#endif
|
||||
|
||||
setFootEffectProcType(0);
|
||||
field_0x2f9d = 4;
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#if TARGET_PC
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/randomizer/game/randomizer_context.hpp"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/gyro.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
#endif
|
||||
|
||||
bool daAlink_c::checkNoSubjectModeCamera() {
|
||||
@@ -144,8 +145,8 @@ BOOL daAlink_c::setBodyAngleToCamera() {
|
||||
f32 gy_pitch = 0.f;
|
||||
dusk::gyro::getAimDeltas(gy_yaw, gy_pitch);
|
||||
|
||||
shape_angle.y = shape_angle.y + cM_rad2s(gy_yaw * gyro_scale * (dusk::getSettings().game.invertFirstPersonXAxis ? -1.0f : 1.0f));
|
||||
sp8 = sp8 + cM_rad2s(gy_pitch * gyro_scale * (dusk::getSettings().game.invertFirstPersonYAxis ? -1.0f : 1.0f));
|
||||
shape_angle.y = shape_angle.y + cM_rad2s(gy_yaw * gyro_scale);
|
||||
sp8 = sp8 + cM_rad2s(gy_pitch * gyro_scale);
|
||||
|
||||
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
|
||||
sp8 = mBodyAngle.x;
|
||||
@@ -192,7 +193,9 @@ BOOL daAlink_c::subjectCancelTrigger() {
|
||||
BOOL daAlink_c::checkSubjectEnd(BOOL i_isPlaySe) {
|
||||
setDoStatus(BUTTON_STATUS_BACK);
|
||||
|
||||
if (checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
|
||||
// Allow pressing the first person binding to also leave first person
|
||||
if (IF_DUSK(dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, 0)) ||
|
||||
checkEventRun() || checkEquipAnime() || doTrigger() || checkSetItemTrigger(dItemNo_HAWK_EYE_e) || subjectCancelTrigger() || checkEndResetFlg0(ERFLG0_FORCE_SUBJECT_CANCEL) || dComIfGp_checkCameraAttentionStatus(field_0x317c, 0x2000)) {
|
||||
if (i_isPlaySe) {
|
||||
seStartSystem(Z2SE_SUBJ_VIEW_OUT);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
#include "d/d_msg_string.h"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
#include <dusk/autosave.h>
|
||||
|
||||
dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) {
|
||||
mArchive = i_archive;
|
||||
@@ -146,10 +148,12 @@ void dBrightCheck_c::modeMove() {
|
||||
if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) {
|
||||
// start a new run if a run isn't already in progress
|
||||
if (!dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::ImGuiMenuGame::resetForSpeedrunMode();
|
||||
dusk::resetForSpeedrunMode();
|
||||
dusk::m_speedrunInfo.startRun();
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutoSave(true);
|
||||
#endif
|
||||
mCompleteCheck = true;
|
||||
mMode = MODE_WAIT_e;
|
||||
|
||||
+95
-61
@@ -31,6 +31,7 @@
|
||||
#if TARGET_PC
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/logging.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "imgui.h"
|
||||
#endif
|
||||
|
||||
@@ -838,6 +839,12 @@ void dCamera_c::updatePad() {
|
||||
mTrigB = mDoCPd_c::getTrigB(mPadID) ? true : false;
|
||||
|
||||
#if TARGET_PC
|
||||
// If our custom action binding is triggered, and we're not already in first person, go into first person
|
||||
if (dusk::getActionBindTrig(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID) && mGear != -1) {
|
||||
setComStat(0x1000);
|
||||
mGear = 0;
|
||||
}
|
||||
|
||||
if (mCamParam.mManualMode) {
|
||||
return;
|
||||
}
|
||||
@@ -877,7 +884,8 @@ void dCamera_c::updatePad() {
|
||||
|
||||
if (mPadInfo.mCStick.mLastPosY < -mCamSetup.mCStick.SwTHH()) {
|
||||
if (mCStickYState != -1) {
|
||||
if (mGear == -1 && mCurMode == 4) {
|
||||
// Don't use regular first person trigger if custom mapping is set
|
||||
if (mGear == -1 && mCurMode == 4 IF_DUSK(&& !dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID))) {
|
||||
mGear = 0;
|
||||
setComStat(0x2000);
|
||||
} else if (mGear == 0 && sp6C) {
|
||||
@@ -888,7 +896,8 @@ void dCamera_c::updatePad() {
|
||||
mCStickYState = -1;
|
||||
} else if (mPadInfo.mCStick.mLastPosY > mCamSetup.mCStick.SwTHH()) {
|
||||
if (mCStickYState != 1) {
|
||||
if (mGear == 0 && sp6B) {
|
||||
// Don't use regular first person trigger if custom mapping is set
|
||||
if (mGear == 0 && sp6B IF_DUSK(&& !dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID))) {
|
||||
setComStat(0x1000);
|
||||
} else if (mGear == 1) {
|
||||
mGear = 0;
|
||||
@@ -7649,9 +7658,10 @@ bool dCamera_c::freeCamera() {
|
||||
f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY);
|
||||
|
||||
// If we aren't in manual cam mode, don't trigger it if the player tries to hit C-up
|
||||
// for first person
|
||||
if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY < 0 ||
|
||||
(mCamParam.mManualMode == 1 && mPadInfo.mCStick.mLastPosY != 0)) {
|
||||
// for first person unless they have first person bound to a custom binding
|
||||
if ((dusk::isActionBound(dusk::ActionBinds::FIRST_PERSON_CAMERA, mPadID) && mPadInfo.mCStick.mLastPosY != 0) ||
|
||||
mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY < 0 || (mCamParam.mManualMode == 1 && mPadInfo.mCStick.mLastPosY != 0))
|
||||
{
|
||||
mCamParam.mManualMode = 1;
|
||||
camMovement = camMovement.normalize();
|
||||
camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f;
|
||||
@@ -11234,6 +11244,62 @@ cXyz dCamera_c::Center() {
|
||||
return mCenter + mShake.field_0x24;
|
||||
}
|
||||
|
||||
#ifdef TARGET_PC
|
||||
f32 get_target_trim_height(camera_process_class* i_this) {
|
||||
const auto camera = &i_this->mCamera;
|
||||
if (camera->mCurState != 2) {
|
||||
switch (camera->mTrimSize) {
|
||||
case 0:
|
||||
case 4:
|
||||
return 0.0f;
|
||||
case 1:
|
||||
return camera->mCamSetup.VistaTrimHeight();
|
||||
case 2:
|
||||
case 3:
|
||||
return camera->mCamSetup.CinemaScopeTrimHeight();
|
||||
default:
|
||||
return camera->mTrimHeight;
|
||||
}
|
||||
}
|
||||
return camera->mTrimHeight;
|
||||
}
|
||||
|
||||
void widezoom_correction(camera_process_class* i_this, float trim_height) {
|
||||
camera_class* camera = (camera_class*)i_this;
|
||||
dDlst_window_c* window = get_window(camera);
|
||||
view_port_class* viewport = window->getViewPort();
|
||||
|
||||
auto trim_width = 0.0f;
|
||||
|
||||
if (mDoGph_gInf_c::isWideZoom()) {
|
||||
const auto target_ar = FB_WIDTH_BASE / (FB_HEIGHT_BASE - trim_height * 2.0f);
|
||||
const auto target_ar_real =
|
||||
FB_WIDTH_BASE / (FB_HEIGHT_BASE - get_target_trim_height(i_this) * 2.0f);
|
||||
const auto current_ar = camera->view.aspect;
|
||||
|
||||
if (current_ar < target_ar) {
|
||||
trim_height = FB_HEIGHT_BASE / 2.0f * (1.0f - current_ar / target_ar);
|
||||
} else {
|
||||
trim_height = 0.0f;
|
||||
trim_width = FB_WIDTH_BASE / 2.0f * (1.0f - target_ar_real / current_ar);
|
||||
}
|
||||
|
||||
if (dusk::frame_interp::is_sim_frame()) {
|
||||
constexpr auto base_ar =
|
||||
static_cast<f32>(FB_WIDTH_BASE) / static_cast<f32>(FB_HEIGHT_BASE);
|
||||
const auto ar_corr = base_ar / std::min(current_ar, target_ar_real);
|
||||
camera->view.fovy =
|
||||
MTXRadToDeg(2.0f * atanf(tanf(MTXDegToRad(camera->view.fovy) * 0.5f) * ar_corr));
|
||||
}
|
||||
}
|
||||
|
||||
trim_width *= viewport->width / FB_WIDTH_BASE;
|
||||
trim_height *= viewport->height / FB_HEIGHT_BASE;
|
||||
window->setScissor(trim_width, trim_height, viewport->width - trim_width * 2.0f,
|
||||
viewport->height - trim_height * 2.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int camera_execute(camera_process_class* i_this) {
|
||||
preparation(i_this);
|
||||
|
||||
@@ -11254,6 +11320,28 @@ static int camera_execute(camera_process_class* i_this) {
|
||||
store(i_this);
|
||||
|
||||
#ifdef TARGET_PC
|
||||
widezoom_correction(i_this, i_this->mCamera.TrimHeight());
|
||||
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
dusk::frame_interp::add_interpolation_callback([](bool _, void* pUserWork) {
|
||||
const auto i_this = static_cast<camera_process_class*>(pUserWork);
|
||||
const auto camera = &i_this->mCamera;
|
||||
|
||||
const auto trim_size = camera->mTrimSize;
|
||||
|
||||
if (camera->mCurState != 2 && trim_size >= 0 && trim_size <= 3) {
|
||||
// derive trim height at previous tick using current camera state
|
||||
const auto target = get_target_trim_height(i_this);
|
||||
const auto step = dusk::frame_interp::get_interpolation_step();
|
||||
const auto cur = camera->TrimHeight();
|
||||
const auto prev = (4.0f * cur - target) / 3.0f;
|
||||
const auto trim_height = prev + (cur - prev) * step;
|
||||
|
||||
widezoom_correction(i_this, trim_height);
|
||||
}
|
||||
}, i_this);
|
||||
}
|
||||
|
||||
// record new camera for our sim frame
|
||||
dusk::frame_interp::record_camera(i_this, get_camera_id(i_this));
|
||||
// interpolate the view now so that this sim frame's view matrix matches what
|
||||
@@ -11265,26 +11353,6 @@ static int camera_execute(camera_process_class* i_this) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef TARGET_PC
|
||||
void set_ar_corrected_trim(dDlst_window_c* window, float trim_height) {
|
||||
const auto viewport = window->getViewPort();
|
||||
|
||||
if (mDoGph_gInf_c::isWideZoom()) {
|
||||
const auto target_ar = FB_WIDTH / (FB_HEIGHT - trim_height * 2.0f);
|
||||
const auto current_ar = mDoGph_gInf_c::m_safeWidthF / mDoGph_gInf_c::m_safeHeightF;
|
||||
|
||||
if (current_ar < target_ar) {
|
||||
trim_height = FB_HEIGHT / 2.0f * (1.0f - current_ar / target_ar);
|
||||
} else {
|
||||
trim_height = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
trim_height *= viewport->height / FB_HEIGHT;
|
||||
window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int camera_draw(camera_process_class* i_this) {
|
||||
camera_class* a_this = (camera_class*)i_this;
|
||||
dCamera_c* body = &i_this->mCamera;
|
||||
@@ -11337,42 +11405,8 @@ static int camera_draw(camera_process_class* i_this) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TARGET_PC
|
||||
set_ar_corrected_trim(window, body->TrimHeight());
|
||||
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
dusk::frame_interp::add_interpolation_callback([](bool _, void* pUserWork) {
|
||||
const auto i_this = static_cast<camera_process_class*>(pUserWork);
|
||||
const auto camera = &i_this->mCamera;
|
||||
|
||||
const auto trim_size = camera->mTrimSize;
|
||||
|
||||
if (camera->mCurState != 2 && trim_size >= 0 && trim_size <= 3) {
|
||||
// derive trim height at previous tick using current camera state
|
||||
f32 target;
|
||||
switch (trim_size) {
|
||||
case 0:
|
||||
target = 0.0f;
|
||||
break;
|
||||
case 1:
|
||||
target = camera->mCamSetup.VistaTrimHeight();
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
target = camera->mCamSetup.CinemaScopeTrimHeight();
|
||||
break;
|
||||
}
|
||||
|
||||
const auto step = dusk::frame_interp::get_interpolation_step();
|
||||
const auto cur = camera->TrimHeight();
|
||||
const auto prev = (4.0f * cur - target) / 3.0f;
|
||||
const auto trim_height = prev + (cur - prev) * step;
|
||||
|
||||
set_ar_corrected_trim(get_window((camera_class*)i_this), trim_height);
|
||||
}
|
||||
}, i_this);
|
||||
}
|
||||
#else
|
||||
#if !TARGET_PC
|
||||
// trim handling moved to camera_execute for PC
|
||||
int trim_height = body->TrimHeight();
|
||||
|
||||
window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "f_op/f_op_actor_mng.h"
|
||||
#if TARGET_PC
|
||||
#include "dusk/achievements.h"
|
||||
#include "dusk/settings.h"
|
||||
#endif
|
||||
|
||||
static int plCutLRC[58] = {
|
||||
@@ -429,6 +430,13 @@ fopAc_ac_c* cc_at_check(fopAc_ac_c* i_enemy, dCcU_AtInfo* i_AtInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.invincibleEnemies &&
|
||||
fopAcM_GetGroup(i_enemy) == fopAc_ENEMY_e) {
|
||||
i_AtInfo->mAttackPower = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (i_AtInfo->mAttackPower != 0) {
|
||||
i_enemy->health -= i_AtInfo->mAttackPower;
|
||||
}
|
||||
|
||||
+10
-1
@@ -36,6 +36,7 @@
|
||||
#include "dusk/imgui/ImGuiBloomWindow.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/game_clock.h"
|
||||
#endif
|
||||
|
||||
static void GxXFog_set();
|
||||
@@ -2268,6 +2269,7 @@ void dKy_calc_color_set(GXColorS10* out_color_p, color_RGB_class* color_a_start_
|
||||
color_b_start_p->b, color_b_end_p->b, blend_ratio, add_col.b, scale);
|
||||
}
|
||||
|
||||
|
||||
void dScnKy_env_light_c::setLight() {
|
||||
f32 color_ratio;
|
||||
|
||||
@@ -2513,7 +2515,14 @@ void dScnKy_env_light_c::setLight() {
|
||||
static s16 S_fuwan_sin;
|
||||
|
||||
f32 sin = cM_ssin(S_fuwan_sin);
|
||||
S_fuwan_sin += (s16)cM_rndF(2000.0f) + 500;
|
||||
|
||||
#if TARGET_PC
|
||||
const f32 deltaTime = dusk::game_clock::consume_interval(this);
|
||||
const f32 timeScale = deltaTime / dusk::game_clock::period_for_original_frames(1.0f);
|
||||
S_fuwan_sin += (s16)((cM_rndF(2000.0f) + 500) * timeScale);
|
||||
#else
|
||||
S_fuwan_sin += (s16)cM_rndF(2000.0f) + 500;
|
||||
#endif
|
||||
|
||||
blure_size += (u8)(sin * (0.2f * blure_size));
|
||||
}
|
||||
|
||||
+26
-1
@@ -152,7 +152,7 @@ u8 STControl::checkTrigger() {
|
||||
field_0x22 = -field_0x24;
|
||||
}
|
||||
}
|
||||
|
||||
#if !TARGET_PC
|
||||
if (!(mDirectionTrig & 3)) {
|
||||
Xinit();
|
||||
}
|
||||
@@ -160,10 +160,35 @@ u8 STControl::checkTrigger() {
|
||||
if (!(mDirectionTrig & 0xC)) {
|
||||
Yinit();
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
mDirectionTrig = 0;
|
||||
#if !TARGET_PC
|
||||
Xinit();
|
||||
Yinit();
|
||||
#endif
|
||||
#if TARGET_PC
|
||||
if (mDoCPd_c::getHoldLeft(PAD_1)) {
|
||||
mDirectionTrig |= TRIG_LEFT;
|
||||
}
|
||||
if (mDoCPd_c::getHoldRight(PAD_1)) {
|
||||
mDirectionTrig |= TRIG_RIGHT;
|
||||
}
|
||||
if (mDoCPd_c::getHoldUp(PAD_1)) {
|
||||
mDirectionTrig |= TRIG_UP;
|
||||
}
|
||||
if (mDoCPd_c::getHoldDown(PAD_1)) {
|
||||
mDirectionTrig |= TRIG_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(mDirectionTrig & 3)) {
|
||||
Xinit();
|
||||
}
|
||||
|
||||
if (!(mDirectionTrig & 0xC)) {
|
||||
Yinit();
|
||||
#endif
|
||||
}
|
||||
|
||||
if ((field_0x0d & mDirectionTrig & 3) && field_0x0e > 0) {
|
||||
|
||||
@@ -1141,6 +1141,9 @@ dMap_c::dMap_c(int width, int height, int param_2, int param_3) {
|
||||
field_0x91 = 0;
|
||||
m_mySelfPointer = this;
|
||||
#endif
|
||||
#if TARGET_PC
|
||||
previousMirror = dusk::getSettings().game.enableMirrorMode;
|
||||
#endif
|
||||
|
||||
m_res = JKR_NEW_ARGS (0x20) dMap_prm_res_s;
|
||||
JUT_ASSERT(2559, m_res != NULL);
|
||||
@@ -1579,6 +1582,17 @@ bool dMap_c::isDrawRoomIcon(int param_0, int param_1) const {
|
||||
}
|
||||
|
||||
void dMap_c::_move(f32 i_centerX, f32 i_centerZ, int i_roomNo, f32 param_3) {
|
||||
#if TARGET_PC
|
||||
bool currentMirror = dusk::getSettings().game.enableMirrorMode;
|
||||
if (currentMirror != previousMirror) {
|
||||
previousMirror = currentMirror;
|
||||
if (currentMirror) {
|
||||
mCenterX -= 2.0f * mPackX;
|
||||
} else {
|
||||
mCenterX += 2.0f * mPackX;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (mStayRoomNo == -1) {
|
||||
mStayRoomNo = i_roomNo;
|
||||
field_0x80 = mStayRoomNo;
|
||||
|
||||
+5
-1
@@ -11,6 +11,7 @@
|
||||
#include "d/d_s_name.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/memory.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "f_op/f_op_overlap_mng.h"
|
||||
#include "f_op/f_op_scene_mng.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "m_Do/m_Do_machine.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "m_Do/m_Do_mtx.h"
|
||||
#include <dusk/autosave.h>
|
||||
|
||||
#if TARGET_PC
|
||||
#define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen)
|
||||
@@ -418,10 +420,12 @@ void dScnName_c::changeGameScene() {
|
||||
if (dusk::getSettings().game.speedrunMode && dusk::getSettings().game.hideTvSettingsScreen) {
|
||||
// start a new run on file load if a run isn't already in progress
|
||||
if (!dusk::m_speedrunInfo.m_isRunStarted) {
|
||||
dusk::ImGuiMenuGame::resetForSpeedrunMode();
|
||||
dusk::resetForSpeedrunMode();
|
||||
dusk::m_speedrunInfo.startRun();
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutoSave(true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1044,6 +1044,10 @@ static BOOL heapSizeCheck() {
|
||||
|
||||
bool dScnPly_c::resetGame() {
|
||||
if (fpcM_GetName(this) == fpcNm_OPENING_SCENE_e) {
|
||||
#if TARGET_PC
|
||||
toggleAutoSave(false);
|
||||
#endif
|
||||
|
||||
if (!dStage_roomControl_c::resetArchiveBank(0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,11 @@ static std::string FormatToString(const char* msg, va_list list) {
|
||||
size *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
while (!str.empty() && str[str.size()-1] == '\n') {
|
||||
str.pop_back();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
@@ -692,6 +692,12 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
return;
|
||||
}
|
||||
|
||||
// prevent stuff like https://github.com/TwilitRealm/dusklight/issues/949
|
||||
if (link->getDemoMode() != 0) {
|
||||
inJump = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inJump) {
|
||||
if (link->mProcID == daAlink_c::PROC_CUT_JUMP) {
|
||||
inJump = true;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
#include "dusk/action_bindings.h"
|
||||
|
||||
#include "aurora/lib/input.hpp"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
|
||||
namespace dusk {
|
||||
|
||||
static std::array<std::array<ActionBindPressData, static_cast<int>(ActionBinds::COUNT)>, PAD_CHANMAX> actionPressData{};
|
||||
|
||||
ActionBindsMap& getActionBinds() {
|
||||
static ActionBindsMap actionBinds = {
|
||||
{ActionBinds::FIRST_PERSON_CAMERA, {&getSettings().actionBindings.firstPersonCamera, "First Person Camera"}},
|
||||
{ActionBinds::CALL_MIDNA, {&getSettings().actionBindings.callMidna, "Call Midna"}},
|
||||
{ActionBinds::OPEN_DUSKLIGHT_MENU, {&getSettings().actionBindings.openDusklightMenu, "Open Dusklight Menu"}},
|
||||
{ActionBinds::TURBO_SPEED_BUTTON, {&getSettings().actionBindings.turboSpeedButton, "Turbo Speed Button"}},
|
||||
};
|
||||
return actionBinds;
|
||||
}
|
||||
|
||||
bool isActionBound(ActionBinds action, u32 port) {
|
||||
auto& actionBinds = getActionBinds();
|
||||
// Check to make sure action is properly bound
|
||||
if (!actionBinds.contains(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID;
|
||||
}
|
||||
|
||||
void updateActionBindings() {
|
||||
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
|
||||
// Move the current press to the previous frame
|
||||
for (auto& pressData : actionPressData[port]) {
|
||||
pressData.pressedPrevFrame = pressData.pressedCurFrame;
|
||||
pressData.pressedCurFrame = false;
|
||||
}
|
||||
|
||||
// Update current frame with whether action button is pressed
|
||||
for (auto& [action, boundAction] : getActionBinds()) {
|
||||
// If the action isn't bound, or if documents are visible and the action isn't
|
||||
// opening the dusklight menu, don't update. Otherwise, we may accidentally
|
||||
// perform actions while the dusklight menu is open.
|
||||
if (!isActionBound(action, port) ||
|
||||
(ui::any_document_visible() && action != ActionBinds::OPEN_DUSKLIGHT_MENU)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int button = boundAction.configVars->at(port);
|
||||
|
||||
// If keyboard is active for this port
|
||||
u32 count = 0;
|
||||
if (PADGetKeyButtonBindings(port, &count) != nullptr) {
|
||||
int numKeys = 0;
|
||||
const bool* kbState = SDL_GetKeyboardState(&numKeys);
|
||||
if (kbState[button]) {
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
|
||||
}
|
||||
} else {
|
||||
// If controller is active
|
||||
auto controller = aurora::input::get_controller_for_player(port);
|
||||
if (controller) {
|
||||
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(button))) {
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool getActionBindTrig(ActionBinds action, u32 port) {
|
||||
return isActionBound(action, port) &&
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
|
||||
!actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
|
||||
}
|
||||
|
||||
bool getActionBindHold(ActionBinds action, u32 port) {
|
||||
return isActionBound(action, port) &&
|
||||
actionPressData[port][static_cast<int>(action)].pressedCurFrame &&
|
||||
actionPressData[port][static_cast<int>(action)].pressedPrevFrame;
|
||||
}
|
||||
|
||||
bool getActionBindHoldAnyPort(ActionBinds action) {
|
||||
for (u32 port = 0; port < PAD_CHANMAX; ++port) {
|
||||
if (getActionBindHold(action, port)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int getActionBindButton(ActionBinds action, u32 port) {
|
||||
return (*getActionBinds()[action].configVars)[port];
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "imgui/ImGuiConsole.hpp"
|
||||
|
||||
bool shouldAutoSave = false;
|
||||
u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
|
||||
u8 mAutoSaveProc = 0;
|
||||
int autoSaveWriteState = 0;
|
||||
@@ -14,7 +15,7 @@ static AutoSaveFuncs AutoSaveFuncsProc[] = {
|
||||
void noAutoSave() {}
|
||||
|
||||
void triggerAutoSave() {
|
||||
if (dusk::getSettings().game.autoSave && mAutoSaveProc == 0 &&
|
||||
if (dusk::getSettings().game.autoSave && shouldAutoSave && mAutoSaveProc == 0 &&
|
||||
strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0)
|
||||
{
|
||||
mAutoSaveProc = 1;
|
||||
@@ -89,4 +90,8 @@ void endAutoSave() {
|
||||
.duration = std::chrono::milliseconds(1500),
|
||||
});
|
||||
mAutoSaveProc = 0;
|
||||
}
|
||||
|
||||
void toggleAutoSave(bool enabled) {
|
||||
shouldAutoSave = enabled;
|
||||
}
|
||||
+17
-2
@@ -11,6 +11,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
|
||||
using namespace dusk::config;
|
||||
|
||||
@@ -60,7 +61,7 @@ void ConfigImpl<T>::loadFromJson(ConfigVar<T>& cVar, const json& jsonValue) {
|
||||
|
||||
template<ConfigValue T>
|
||||
nlohmann::json ConfigImpl<T>::dumpToJson(const ConfigVar<T>& cVar) {
|
||||
return cVar.getValue();
|
||||
return cVar.getValueForSave();
|
||||
}
|
||||
|
||||
template<ConfigValue T> requires std::is_integral_v<T> && std::is_signed_v<T>
|
||||
@@ -248,7 +249,8 @@ void dusk::config::Save() {
|
||||
json j;
|
||||
|
||||
for (const auto& pair : RegisteredConfigVars) {
|
||||
if (pair.second->getLayer() == ConfigVarLayer::Value) {
|
||||
const auto layer = pair.second->getLayer();
|
||||
if (layer == ConfigVarLayer::Value || layer == ConfigVarLayer::Speedrun) {
|
||||
j[pair.first] = pair.second->getImpl()->dumpToJson(*pair.second);
|
||||
}
|
||||
}
|
||||
@@ -256,6 +258,13 @@ void dusk::config::Save() {
|
||||
io::FileStream::WriteAllText(reinterpret_cast<const char*>(configJsonPath.c_str()), j.dump(4));
|
||||
}
|
||||
|
||||
void dusk::config::ClearAllActionBindings(int port) {
|
||||
for (auto& actionBinding : getActionBinds() | std::views::values) {
|
||||
actionBinding.configVars->at(port).setValue(PAD_NATIVE_BUTTON_INVALID);
|
||||
}
|
||||
Save();
|
||||
}
|
||||
|
||||
ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
|
||||
const auto configVar = RegisteredConfigVars.find(name);
|
||||
if (configVar != RegisteredConfigVars.end()) {
|
||||
@@ -264,3 +273,9 @@ ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void dusk::config::EnumerateRegistered(std::function<void(ConfigVarBase&)> callback) {
|
||||
for (auto& pair : RegisteredConfigVars) {
|
||||
callback(*pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ std::string release_name() {
|
||||
}
|
||||
|
||||
std::filesystem::path sentry_database_path() {
|
||||
return dusk::ConfigPath / "sentry";
|
||||
return dusk::CachePath / "sentry";
|
||||
}
|
||||
|
||||
std::filesystem::path log_attachment_path() {
|
||||
|
||||
+1080
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) || \
|
||||
(defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST) || \
|
||||
(defined(__linux__) && !defined(__ANDROID__))
|
||||
#define DUSK_CAN_OPEN_DATA_FOLDER 1
|
||||
#else
|
||||
#define DUSK_CAN_OPEN_DATA_FOLDER 0
|
||||
#endif
|
||||
|
||||
#if (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || defined(__ANDROID__)
|
||||
#define DUSK_CAN_CHANGE_DATA_FOLDER 0
|
||||
#else
|
||||
#define DUSK_CAN_CHANGE_DATA_FOLDER 1
|
||||
#endif
|
||||
|
||||
namespace dusk::data {
|
||||
|
||||
struct Paths {
|
||||
std::filesystem::path userPath;
|
||||
std::filesystem::path cachePath;
|
||||
};
|
||||
|
||||
Paths initialize_data();
|
||||
std::filesystem::path configured_data_path();
|
||||
std::filesystem::path cache_path();
|
||||
bool open_data_path();
|
||||
bool set_custom_data_path(const char* path, std::string* errorOut);
|
||||
bool set_custom_data_path(const std::filesystem::path& path, std::string* errorOut);
|
||||
bool set_portable_data_path();
|
||||
bool reset_data_path();
|
||||
bool is_default_data_path();
|
||||
bool is_data_path_restart_pending();
|
||||
|
||||
} // namespace dusk::data
|
||||
@@ -81,7 +81,7 @@ void update_presence() {
|
||||
rpc::Presence presence{};
|
||||
presence.startTimestamp = g_startTime;
|
||||
presence.largeImageKey = "icon";
|
||||
presence.largeImageText = "Dusk";
|
||||
presence.largeImageText = "Dusklight";
|
||||
|
||||
if (IsGameLaunched) {
|
||||
const char* stageName = dComIfGp_getLastPlayStageName();
|
||||
|
||||
+124
-6
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <SDL3/SDL_dialog.h>
|
||||
#include <SDL3/SDL_error.h>
|
||||
#include <SDL3/SDL_init.h>
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
#if defined(__ANDROID__) || defined(ANDROID)
|
||||
@@ -16,6 +17,12 @@
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST
|
||||
#define USE_MACOS_FOLDER_DIALOG 1
|
||||
#else
|
||||
#define USE_MACOS_FOLDER_DIALOG 0
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST
|
||||
#define USE_IOS_DIALOG 1
|
||||
#include "ios/FileSelectDialog.h"
|
||||
@@ -23,6 +30,13 @@
|
||||
#define USE_IOS_DIALOG 0
|
||||
#endif
|
||||
|
||||
#if USE_MACOS_FOLDER_DIALOG
|
||||
namespace dusk {
|
||||
bool ShowMacOSFolderSelect(
|
||||
FileCallback callback, void* userdata, SDL_Window* window, const char* default_location);
|
||||
} // namespace dusk
|
||||
#endif
|
||||
|
||||
namespace dusk {
|
||||
namespace {
|
||||
|
||||
@@ -32,6 +46,10 @@ std::string fallback_display_name(std::string_view path) {
|
||||
}
|
||||
|
||||
std::string pathString(path);
|
||||
while (pathString.size() > 1 && (pathString.back() == '/' || pathString.back() == '\\')) {
|
||||
pathString.pop_back();
|
||||
}
|
||||
|
||||
const std::size_t slash = pathString.find_last_of("/\\");
|
||||
if (slash == std::string::npos || slash + 1 >= pathString.size()) {
|
||||
return pathString;
|
||||
@@ -98,8 +116,7 @@ std::string android_display_name(std::string_view path) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* displayName =
|
||||
static_cast<jstring>(env->CallObjectMethod(activity, getDisplayName, uri));
|
||||
auto* displayName = static_cast<jstring>(env->CallObjectMethod(activity, getDisplayName, uri));
|
||||
env->DeleteLocalRef(uri);
|
||||
env->DeleteLocalRef(activity);
|
||||
if (displayName == nullptr || clear_pending_exception(env)) {
|
||||
@@ -110,6 +127,76 @@ std::string android_display_name(std::string_view path) {
|
||||
env->DeleteLocalRef(displayName);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct AndroidFolderDialogState {
|
||||
FileCallback callback;
|
||||
void* userdata;
|
||||
std::string path;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
void onAndroidFolderDialogFinished(void* userdata) {
|
||||
std::unique_ptr<AndroidFolderDialogState> state(
|
||||
static_cast<AndroidFolderDialogState*>(userdata));
|
||||
|
||||
const char* path = state->path.empty() ? nullptr : state->path.c_str();
|
||||
const char* error = state->error.empty() ? nullptr : state->error.c_str();
|
||||
state->callback(state->userdata, path, error);
|
||||
}
|
||||
|
||||
bool show_android_folder_select(AndroidFolderDialogState* state) {
|
||||
auto* env = static_cast<JNIEnv*>(SDL_GetAndroidJNIEnv());
|
||||
if (env == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jobject activity = static_cast<jobject>(SDL_GetAndroidActivity());
|
||||
if (activity == nullptr || clear_pending_exception(env)) {
|
||||
if (activity != nullptr) {
|
||||
env->DeleteLocalRef(activity);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
jclass activityClass = env->GetObjectClass(activity);
|
||||
if (activityClass == nullptr || clear_pending_exception(env)) {
|
||||
env->DeleteLocalRef(activity);
|
||||
return false;
|
||||
}
|
||||
|
||||
jmethodID showFolderDialog =
|
||||
env->GetMethodID(activityClass, "showFolderDialog", "(J)Z");
|
||||
env->DeleteLocalRef(activityClass);
|
||||
if (showFolderDialog == nullptr || clear_pending_exception(env)) {
|
||||
env->DeleteLocalRef(activity);
|
||||
return false;
|
||||
}
|
||||
|
||||
const jboolean shown = env->CallBooleanMethod(
|
||||
activity, showFolderDialog, reinterpret_cast<jlong>(state));
|
||||
env->DeleteLocalRef(activity);
|
||||
if (clear_pending_exception(env)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shown == JNI_TRUE;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_dev_twilitrealm_dusk_DuskActivity_nativeFolderDialogResult(
|
||||
JNIEnv* env, jclass, jlong userdata, jstring path, jstring error) {
|
||||
auto* state = reinterpret_cast<AndroidFolderDialogState*>(userdata);
|
||||
if (state == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
state->path = to_string(env, path);
|
||||
state->error = to_string(env, error);
|
||||
|
||||
if (!SDL_RunOnMainThread(&onAndroidFolderDialogFinished, state, false)) {
|
||||
onAndroidFolderDialogFinished(state);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if USE_IOS_DIALOG
|
||||
@@ -159,8 +246,8 @@ void onSDLDialogFinished(void* userdata, const char* const* filelist, [[maybe_un
|
||||
} // namespace
|
||||
|
||||
void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
|
||||
const SDL_DialogFileFilter* filters, int nfilters, const char* default_location,
|
||||
bool allow_many) {
|
||||
const SDL_DialogFileFilter* filters, int nfilters, const char* default_location,
|
||||
bool allow_many) {
|
||||
if (callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -171,14 +258,45 @@ void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
|
||||
state->userdata = userdata;
|
||||
|
||||
Dusk_iOS_ShowFileSelect(&onIOSDialogFinished, state.release(), window, filters, nfilters,
|
||||
default_location, allow_many);
|
||||
default_location, allow_many);
|
||||
#else
|
||||
auto state = std::make_unique<SDLDialogCallbackState>();
|
||||
state->callback = callback;
|
||||
state->userdata = userdata;
|
||||
|
||||
SDL_ShowOpenFileDialog(&onSDLDialogFinished, state.release(), window, filters, nfilters,
|
||||
default_location, allow_many);
|
||||
default_location, allow_many);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ShowFolderSelect(
|
||||
FileCallback callback, void* userdata, SDL_Window* window, const char* default_location) {
|
||||
if (callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if USE_IOS_DIALOG
|
||||
callback(userdata, nullptr, "Folder selection is not supported on this platform");
|
||||
#elif USE_MACOS_FOLDER_DIALOG
|
||||
ShowMacOSFolderSelect(callback, userdata, window, default_location);
|
||||
#elif defined(__ANDROID__) || defined(ANDROID)
|
||||
auto state = std::make_unique<AndroidFolderDialogState>();
|
||||
state->callback = callback;
|
||||
state->userdata = userdata;
|
||||
|
||||
if (show_android_folder_select(state.get())) {
|
||||
state.release();
|
||||
return;
|
||||
}
|
||||
|
||||
callback(userdata, nullptr, "Folder selection is not supported on this platform");
|
||||
#else
|
||||
auto state = std::make_unique<SDLDialogCallbackState>();
|
||||
state->callback = callback;
|
||||
state->userdata = userdata;
|
||||
|
||||
SDL_ShowOpenFolderDialog(
|
||||
&onSDLDialogFinished, state.release(), window, default_location, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ using FileCallback = void (*)(void* userdata, const char* path, const char* erro
|
||||
void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window,
|
||||
const SDL_DialogFileFilter* filters, int nfilters, const char* default_location,
|
||||
bool allow_many);
|
||||
void ShowFolderSelect(
|
||||
FileCallback callback, void* userdata, SDL_Window* window, const char* default_location);
|
||||
|
||||
std::string display_name_for_path(std::string_view path);
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "file_select.hpp"
|
||||
|
||||
#include <SDL3/SDL_properties.h>
|
||||
#include <SDL3/SDL_video.h>
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
namespace dusk {
|
||||
namespace {
|
||||
|
||||
struct MacOSFolderDialogState {
|
||||
FileCallback callback;
|
||||
void* userdata;
|
||||
};
|
||||
|
||||
void finish_folder_dialog(MacOSFolderDialogState* state, NSURL* url, const char* error) {
|
||||
if (state == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error != nullptr) {
|
||||
state->callback(state->userdata, nullptr, error);
|
||||
delete state;
|
||||
return;
|
||||
}
|
||||
|
||||
if (url == nil) {
|
||||
state->callback(state->userdata, nullptr, nullptr);
|
||||
delete state;
|
||||
return;
|
||||
}
|
||||
|
||||
state->callback(state->userdata, [[url path] UTF8String], nullptr);
|
||||
delete state;
|
||||
}
|
||||
|
||||
void configure_default_location(NSOpenPanel* panel, const char* defaultLocation) {
|
||||
if (panel == nil || defaultLocation == nullptr || defaultLocation[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString* path = [NSString stringWithUTF8String:defaultLocation];
|
||||
if (path == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL isDirectory = NO;
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
NSURL* url = [NSURL fileURLWithPath:path];
|
||||
if ([fileManager fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory) {
|
||||
[panel setDirectoryURL:url];
|
||||
} else {
|
||||
[panel setDirectoryURL:[url URLByDeletingLastPathComponent]];
|
||||
}
|
||||
}
|
||||
|
||||
NSWindow* window_for_sdl_window(SDL_Window* window) {
|
||||
if (window == nullptr) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
auto props = SDL_GetWindowProperties(window);
|
||||
return (__bridge NSWindow*)SDL_GetPointerProperty(
|
||||
props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ShowMacOSFolderSelect(
|
||||
FileCallback callback, void* userdata, SDL_Window* window, const char* defaultLocation) {
|
||||
if (callback == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* state = new MacOSFolderDialogState{
|
||||
.callback = callback,
|
||||
.userdata = userdata,
|
||||
};
|
||||
|
||||
NSOpenPanel* panel = [NSOpenPanel openPanel];
|
||||
[panel setCanChooseFiles:NO];
|
||||
[panel setCanChooseDirectories:YES];
|
||||
[panel setAllowsMultipleSelection:NO];
|
||||
[panel setCanCreateDirectories:YES];
|
||||
configure_default_location(panel, defaultLocation);
|
||||
|
||||
NSWindow* modalWindow = window_for_sdl_window(window);
|
||||
if (modalWindow != nil) {
|
||||
[panel beginSheetModalForWindow:modalWindow
|
||||
completionHandler:^(NSModalResponse result) {
|
||||
finish_folder_dialog(
|
||||
state, result == NSModalResponseOK ? [panel URL] : nil, nullptr);
|
||||
}];
|
||||
return true;
|
||||
}
|
||||
|
||||
const NSModalResponse result = [panel runModal];
|
||||
finish_folder_dialog(state, result == NSModalResponseOK ? [panel URL] : nil, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
@@ -13,8 +13,10 @@
|
||||
#include "ImGuiEngine.hpp"
|
||||
#include "JSystem/JUtility/JUTGamePad.h"
|
||||
#include "SDL3/SDL_mouse.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/data.hpp"
|
||||
#include "dusk/dusk.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/livesplit.h"
|
||||
@@ -238,7 +240,8 @@ namespace dusk {
|
||||
}
|
||||
|
||||
void ImGuiConsole::UpdateSettings() {
|
||||
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab);
|
||||
getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind &&
|
||||
(ImGui::IsKeyDown(ImGuiKey_Tab) || getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON));
|
||||
|
||||
if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) {
|
||||
getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive;
|
||||
@@ -259,6 +262,12 @@ namespace dusk {
|
||||
config::Save();
|
||||
}
|
||||
|
||||
if (getSettings().game.enableResetKeybind && ImGui::GetIO().KeyCtrl &&
|
||||
ImGui::IsKeyPressed(ImGuiKey_R) && !fpcM_SearchByName(fpcNm_LOGO_SCENE_e))
|
||||
{
|
||||
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||
}
|
||||
|
||||
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
|
||||
if (getSettings().backend.enableAdvancedSettings) {
|
||||
m_isHidden = !m_isHidden;
|
||||
@@ -273,7 +282,6 @@ namespace dusk {
|
||||
// so make the window bg fully transparent temporarily
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
if (showMenu && ImGui::BeginMainMenuBar()) {
|
||||
m_menuGame.draw();
|
||||
m_menuRandomizer.draw();
|
||||
m_menuTools.draw();
|
||||
|
||||
@@ -283,7 +291,7 @@ namespace dusk {
|
||||
|
||||
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
|
||||
m_isLaunchInitialized = true;
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
if (getSettings().game.speedrunMode && getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::connectLiveSplit();
|
||||
}
|
||||
}
|
||||
@@ -310,13 +318,13 @@ namespace dusk {
|
||||
ImGui::Image(ImGuiEngine::duskLogo, ImVec2{width, iconSize});
|
||||
} else {
|
||||
ImGui::PushFont(ImGuiEngine::fontExtraLarge);
|
||||
ImGuiTextCenter("Dusk");
|
||||
ImGuiTextCenter("Dusklight");
|
||||
ImGui::PopFont();
|
||||
}
|
||||
ImGui::PushFont(ImGuiEngine::fontLarge);
|
||||
ImGuiTextCenter("Failed to initialize any graphics backend.");
|
||||
ImGuiTextCenter("\nYour system may be misconfigured, or your hardware may not support the required versions of any of the available backends.");
|
||||
ImGuiTextCenter("\nA clean reinstall of Dusk may help. For further assistance, please visit #tech-support on the Twilit Realm Discord server.");
|
||||
ImGuiTextCenter("\nA clean reinstall of Dusklight may help. For further assistance, please visit #tech-support on the Twilit Realm Discord server.");
|
||||
const auto& style = ImGui::GetStyle();
|
||||
const auto retrySize = ImGui::CalcTextSize("Retry (Auto backend)");
|
||||
const auto quitSize = ImGui::CalcTextSize("Quit");
|
||||
@@ -342,7 +350,7 @@ namespace dusk {
|
||||
}
|
||||
#if DUSK_CAN_OPEN_DATA_FOLDER
|
||||
if (ImGui::Button("Open Data Folder")) {
|
||||
OpenDataFolder();
|
||||
data::open_data_path();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
#endif
|
||||
@@ -354,15 +362,6 @@ namespace dusk {
|
||||
}
|
||||
|
||||
m_menuTools.ShowInputViewer();
|
||||
m_menuGame.drawSpeedrunTimerOverlay();
|
||||
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::updateLiveSplit();
|
||||
if (dusk::speedrun::consumeConnectedEvent())
|
||||
AddToast("LiveSplit connected");
|
||||
else if (dusk::speedrun::consumeDisconnectedEvent())
|
||||
AddToast("LiveSplit disconnected");
|
||||
}
|
||||
|
||||
if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) {
|
||||
m_menuTools.ShowDebugOverlay();
|
||||
@@ -370,7 +369,6 @@ namespace dusk {
|
||||
m_menuTools.ShowProcessManager();
|
||||
m_menuTools.ShowHeapOverlay();
|
||||
m_menuTools.ShowStubLog();
|
||||
m_menuTools.ShowMapLoader();
|
||||
m_menuTools.ShowBloomWindow();
|
||||
m_menuTools.ShowPlayerInfo();
|
||||
m_menuTools.ShowAudioDebug();
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
#include "ImGuiMenuGame.hpp"
|
||||
#include "ImGuiMenuTools.hpp"
|
||||
#include "ImGuiMenuRandomizer.hpp"
|
||||
#include "dusk/main.h"
|
||||
@@ -45,7 +44,6 @@ private:
|
||||
ImVec2 m_dragScrollLastMousePos = {};
|
||||
std::deque<Toast> m_toasts;
|
||||
|
||||
ImGuiMenuGame m_menuGame;
|
||||
ImGuiMenuRandomizer m_menuRandomizer;
|
||||
|
||||
// Keep always last
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
#include "imgui.h"
|
||||
#include <imgui_internal.h>
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
#include <dolphin/pad.h>
|
||||
|
||||
namespace dusk {
|
||||
void ImGuiMenuTools::ShowInputViewer() {
|
||||
if (!m_showInputViewer) {
|
||||
if (!getSettings().game.showInputViewer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -259,10 +260,10 @@ namespace dusk {
|
||||
size.y = 130 * scale;
|
||||
ImGui::Dummy(size);
|
||||
|
||||
if (PADHasSensor(PAD_1, PAD_SENSOR_GYRO) == TRUE) {
|
||||
if (getSettings().game.showInputViewerGyro)
|
||||
{
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Gyro Values", &m_showInputViewerGyro);
|
||||
if (m_showInputViewerGyro) {
|
||||
{
|
||||
ImGui::TextUnformatted("Gyro");
|
||||
|
||||
constexpr float kBarScale = 4.0f;
|
||||
|
||||
@@ -219,7 +219,7 @@ void ImGuiEngine_AddTextures() {
|
||||
ImGuiEngine::orgIcon = AddTexture("org-icon.png");
|
||||
}
|
||||
if (ImGuiEngine::duskLogo == 0) {
|
||||
ImGuiEngine::duskLogo = AddTexture("logo-mascot.png");
|
||||
ImGuiEngine::duskLogo = AddTexture("logo.png");
|
||||
}
|
||||
}
|
||||
} // namespace dusk
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
#include "d/d_com_inf_game.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include <imgui_internal.h>
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "ImGuiMenuTools.hpp"
|
||||
#include "dusk/map_loader_definitions.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace dusk {
|
||||
void ImGuiMenuTools::ShowMapLoader() {
|
||||
if (!getSettings().backend.enableAdvancedSettings ||
|
||||
!ImGuiConsole::CheckMenuViewToggle(ImGuiKey_F7, m_showMapLoader))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav;
|
||||
|
||||
// ImGui::SetNextWindowBgAlpha(0.65f);
|
||||
|
||||
if (!ImGui::Begin("Map Loader", &m_showMapLoader, windowFlags)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Show Internal Names", &m_mapLoaderInfo.showInternalNames);
|
||||
|
||||
const char* previewRegion = "None";
|
||||
if (m_mapLoaderInfo.regionIdx != -1) {
|
||||
previewRegion = gameRegions[m_mapLoaderInfo.regionIdx].regionName;
|
||||
}
|
||||
|
||||
if (ImGui::BeginCombo("Select Region", previewRegion)) {
|
||||
int idx = 0;
|
||||
for (const auto& region : gameRegions) {
|
||||
if (ImGui::Selectable(region.regionName)) {
|
||||
if (m_mapLoaderInfo.regionIdx != idx) {
|
||||
m_mapLoaderInfo.mapIdx = 0;
|
||||
m_mapLoaderInfo.roomNoIdx = 0;
|
||||
m_mapLoaderInfo.pointNoIdx = 0;
|
||||
}
|
||||
m_mapLoaderInfo.regionIdx = idx;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (m_mapLoaderInfo.regionIdx != -1) {
|
||||
const auto& region = gameRegions[m_mapLoaderInfo.regionIdx];
|
||||
|
||||
std::string previewMap = "None";
|
||||
if (m_mapLoaderInfo.mapIdx != -1) {
|
||||
const auto& map = region.maps[m_mapLoaderInfo.mapIdx];
|
||||
previewMap = m_mapLoaderInfo.showInternalNames ? fmt::format("{} ({})", map.mapName, map.mapFile) : map.mapName;
|
||||
}
|
||||
|
||||
if (ImGui::BeginCombo("Select Map", previewMap.data())) {
|
||||
int prevMapIdx = m_mapLoaderInfo.mapIdx;
|
||||
for (int i = 0; i < region.maps.size(); ++i) {
|
||||
const auto& map = region.maps[i];
|
||||
std::string label = m_mapLoaderInfo.showInternalNames ? fmt::format("{} ({})", map.mapName, map.mapFile) : map.mapName;
|
||||
if (ImGui::Selectable(label.data())) {
|
||||
m_mapLoaderInfo.mapIdx = i;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
if (m_mapLoaderInfo.mapIdx != prevMapIdx) {
|
||||
m_mapLoaderInfo.roomNoIdx = 0;
|
||||
m_mapLoaderInfo.pointNoIdx = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("No region selected.");
|
||||
}
|
||||
|
||||
if (m_mapLoaderInfo.regionIdx != -1 && m_mapLoaderInfo.mapIdx != -1) {
|
||||
const auto& region = gameRegions[m_mapLoaderInfo.regionIdx];
|
||||
const auto& map = region.maps[m_mapLoaderInfo.mapIdx];
|
||||
const auto& room = map.mapRooms[m_mapLoaderInfo.roomNoIdx];
|
||||
|
||||
if (map.mapRooms.size() > 1) {
|
||||
ImGui::Text("Selected Room: %2d", room.roomNo);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("-###RoomNoIdxDec")) {
|
||||
m_mapLoaderInfo.roomNoIdx--;
|
||||
if (m_mapLoaderInfo.roomNoIdx < 0) {
|
||||
m_mapLoaderInfo.roomNoIdx = map.mapRooms.size() - 1;
|
||||
}
|
||||
m_mapLoaderInfo.pointNoIdx = 0;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("+###RoomNoIdxInc")) {
|
||||
m_mapLoaderInfo.roomNoIdx++;
|
||||
if (m_mapLoaderInfo.roomNoIdx >= map.mapRooms.size()) {
|
||||
m_mapLoaderInfo.roomNoIdx = 0;
|
||||
}
|
||||
m_mapLoaderInfo.pointNoIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int MAX_LAYER = 14;
|
||||
|
||||
ImGui::Text("Selected Layer: %3d", m_mapLoaderInfo.layer);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("-###layerDec")) {
|
||||
m_mapLoaderInfo.layer--;
|
||||
if (m_mapLoaderInfo.layer < -1) {
|
||||
m_mapLoaderInfo.layer = MAX_LAYER;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("+###layerInc")) {
|
||||
m_mapLoaderInfo.layer++;
|
||||
if (m_mapLoaderInfo.layer > MAX_LAYER) {
|
||||
m_mapLoaderInfo.layer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (room.roomPoints.size() > 1) {
|
||||
ImGui::Text("Selected Point: %3d", room.roomPoints[m_mapLoaderInfo.pointNoIdx]);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("-###PointNoIdxDec")) {
|
||||
m_mapLoaderInfo.pointNoIdx--;
|
||||
if (m_mapLoaderInfo.pointNoIdx < 0) {
|
||||
m_mapLoaderInfo.pointNoIdx = room.roomPoints.size() - 1;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("+###PointNoIdxInc")) {
|
||||
m_mapLoaderInfo.pointNoIdx++;
|
||||
if (m_mapLoaderInfo.pointNoIdx >= room.roomPoints.size()) {
|
||||
m_mapLoaderInfo.pointNoIdx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button("Warp")) {
|
||||
dComIfGp_setNextStage(map.mapFile, room.roomPoints[m_mapLoaderInfo.pointNoIdx], room.roomNo, m_mapLoaderInfo.layer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
@@ -1,100 +0,0 @@
|
||||
#include "fmt/format.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "ImGuiConfig.hpp"
|
||||
|
||||
#include "dusk/main.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
|
||||
namespace dusk {
|
||||
ImGuiMenuGame::ImGuiMenuGame() {}
|
||||
|
||||
void ImGuiMenuGame::draw() {}
|
||||
|
||||
static std::string GetFormattedTime(OSTime ticks) {
|
||||
OSCalendarTime time;
|
||||
OSTicksToCalendarTime(ticks, &time);
|
||||
|
||||
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", time.hour, time.min, time.sec, time.msec);
|
||||
}
|
||||
|
||||
void ImGuiMenuGame::resetForSpeedrunMode() {
|
||||
// reset settings that should be off for speedrun mode
|
||||
mDoMain::developmentMode = -1;
|
||||
|
||||
getSettings().game.damageMultiplier.setValue(1);
|
||||
getSettings().game.instantDeath.setValue(false);
|
||||
getSettings().game.noHeartDrops.setValue(false);
|
||||
|
||||
getSettings().game.infiniteHearts.setValue(false);
|
||||
getSettings().game.infiniteArrows.setValue(false);
|
||||
getSettings().game.infiniteBombs.setValue(false);
|
||||
getSettings().game.infiniteOil.setValue(false);
|
||||
getSettings().game.infiniteOxygen.setValue(false);
|
||||
getSettings().game.infiniteRupees.setValue(false);
|
||||
getSettings().game.enableIndefiniteItemDrops.setValue(false);
|
||||
|
||||
getSettings().game.moonJump.setValue(false);
|
||||
getSettings().game.superClawshot.setValue(false);
|
||||
getSettings().game.alwaysGreatspin.setValue(false);
|
||||
getSettings().game.enableFastIronBoots.setValue(false);
|
||||
getSettings().game.canTransformAnywhere.setValue(false);
|
||||
getSettings().game.fastSpinner.setValue(false);
|
||||
getSettings().game.freeMagicArmor.setValue(false);
|
||||
|
||||
getSettings().game.enableTurboKeybind.setValue(false);
|
||||
getSettings().game.debugFlyCam.setValue(false);
|
||||
getSettings().game.autoSave.setValue(false);
|
||||
}
|
||||
|
||||
SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
void ImGuiMenuGame::drawSpeedrunTimerOverlay() {
|
||||
if (!getSettings().game.speedrunMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// L+R+A+Start to reset timer
|
||||
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1)) {
|
||||
m_speedrunInfo.reset();
|
||||
}
|
||||
|
||||
// L+R+A+Z to manually stop timer
|
||||
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigY(PAD_1)) {
|
||||
if (m_speedrunInfo.m_isRunStarted) {
|
||||
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||
m_speedrunInfo.m_isRunStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(0.65f);
|
||||
ImGuiWindowFlags flags =
|
||||
ImGuiWindowFlags_NoResize
|
||||
| ImGuiWindowFlags_NoDocking
|
||||
| ImGuiWindowFlags_NoTitleBar
|
||||
| ImGuiWindowFlags_NoScrollbar;
|
||||
|
||||
if (ImGui::Begin("##SpeedrunTimerWindow", nullptr, flags)) {
|
||||
OSTime elapsedTime = 0;
|
||||
if (m_speedrunInfo.m_isRunStarted) {
|
||||
elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||
} else if (m_speedrunInfo.m_endTimestamp != 0) {
|
||||
elapsedTime = m_speedrunInfo.m_endTimestamp;
|
||||
}
|
||||
|
||||
ImGui::Text("RTA");
|
||||
ImGui::SameLine(60.0f);
|
||||
ImGuiStringViewText(GetFormattedTime(elapsedTime));
|
||||
|
||||
if (!m_speedrunInfo.m_isPauseIGT) {
|
||||
m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime;
|
||||
}
|
||||
|
||||
ImGui::Text("IGT");
|
||||
ImGui::SameLine(60.0f);
|
||||
ImGuiStringViewText(GetFormattedTime(m_speedrunInfo.m_igtTimer));
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
#ifndef DUSK_IMGUI_MENUGAME_HPP
|
||||
#define DUSK_IMGUI_MENUGAME_HPP
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <pad.h>
|
||||
#include <string>
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
namespace dusk {
|
||||
struct SpeedrunInfo {
|
||||
void startRun() {
|
||||
m_isRunStarted = true;
|
||||
m_startTimestamp = OSGetTime();
|
||||
}
|
||||
|
||||
void stopRun() {
|
||||
m_isRunStarted = false;
|
||||
m_endTimestamp = OSGetTime() - m_startTimestamp;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_isRunStarted = false;
|
||||
m_startTimestamp = 0;
|
||||
m_endTimestamp = 0;
|
||||
m_isPauseIGT = false;
|
||||
m_loadStartTimestamp = 0;
|
||||
m_totalLoadTime = 0;
|
||||
m_igtTimer = 0;
|
||||
}
|
||||
|
||||
bool m_isRunStarted = false;
|
||||
OSTime m_startTimestamp = 0;
|
||||
OSTime m_endTimestamp = 0;
|
||||
|
||||
bool m_isPauseIGT = false;
|
||||
OSTime m_loadStartTimestamp = 0;
|
||||
OSTime m_totalLoadTime = 0;
|
||||
OSTime m_igtTimer = 0;
|
||||
};
|
||||
|
||||
extern SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
class ImGuiMenuGame {
|
||||
public:
|
||||
ImGuiMenuGame();
|
||||
void draw();
|
||||
|
||||
void drawSpeedrunTimerOverlay();
|
||||
|
||||
static void resetForSpeedrunMode();
|
||||
|
||||
private:
|
||||
bool m_showTimerWindow = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // DUSK_IMGUI_MENUGAME_HPP
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/actor/d_a_horse.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "dusk/data.hpp"
|
||||
#include "dusk/dusk.h"
|
||||
#include "dusk/main.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
@@ -39,7 +40,6 @@ namespace dusk {
|
||||
ImGui::BeginDisabled(getSettings().game.speedrunMode);
|
||||
|
||||
ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor);
|
||||
ImGui::MenuItem("Map Loader", hotkeys::SHOW_MAP_LOADER, &m_showMapLoader);
|
||||
ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare);
|
||||
|
||||
ImGui::EndDisabled();
|
||||
@@ -48,13 +48,10 @@ namespace dusk {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Show Input Viewer", &m_showInputViewer);
|
||||
|
||||
#if DUSK_CAN_OPEN_DATA_FOLDER
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Open Data Folder")) {
|
||||
OpenDataFolder();
|
||||
data::open_data_path();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace dusk {
|
||||
void ShowProcessManager();
|
||||
void ShowHeapOverlay();
|
||||
void ShowStubLog();
|
||||
void ShowMapLoader();
|
||||
void ShowBloomWindow();
|
||||
void ShowPlayerInfo();
|
||||
void ShowAudioDebug();
|
||||
@@ -43,22 +42,9 @@ namespace dusk {
|
||||
|
||||
bool m_showStubLog = false;
|
||||
|
||||
bool m_showMapLoader = false;
|
||||
|
||||
bool m_showBloomWindow = false;
|
||||
|
||||
bool m_showAudioDebug = false;
|
||||
struct {
|
||||
int mapIdx = -1;
|
||||
int regionIdx = -1;
|
||||
int roomNoIdx = 0;
|
||||
int pointNoIdx = 0;
|
||||
int roomNo = -1;
|
||||
int pointNo = -1;
|
||||
int spawnId = 0;
|
||||
int layer = -1;
|
||||
bool showInternalNames = false;
|
||||
} m_mapLoaderInfo;
|
||||
|
||||
bool m_showPlayerInfo = false;
|
||||
int m_playerInfoOverlayCorner = 1; // top-right
|
||||
@@ -69,8 +55,6 @@ namespace dusk {
|
||||
bool m_showStateShare = false;
|
||||
ImGuiStateShare m_stateShare;
|
||||
|
||||
bool m_showInputViewer = false;
|
||||
bool m_showInputViewerGyro = false;
|
||||
bool m_showActorSpawner = false;
|
||||
int m_inputOverlayCorner = 3;
|
||||
std::string m_controllerName;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <unordered_set>
|
||||
#include <zstd.h>
|
||||
#include <dusk/autosave.h>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
@@ -135,6 +136,8 @@ bool ImGuiStateShare::applyEncodedState(const std::string& encoded, const std::s
|
||||
return false;
|
||||
}
|
||||
|
||||
toggleAutoSave(false);
|
||||
|
||||
StateSharePacket pkt;
|
||||
memcpy(&pkt, raw.data(), sizeof(pkt));
|
||||
pkt.stageName[7] = '\0';
|
||||
|
||||
+12
-10
@@ -17,6 +17,8 @@ using namespace dusk::io;
|
||||
#else
|
||||
#define MODE(val) val
|
||||
#endif
|
||||
#define _SH_DENYNO 0
|
||||
#define _SH_DENYWR 0
|
||||
#endif
|
||||
|
||||
static FILE* ThrowIfNotOpen(const FileStream& file) {
|
||||
@@ -31,19 +33,19 @@ static FILE* ThrowIfNotOpen(const FileStream& file) {
|
||||
throw std::system_error(std::make_error_code(static_cast<std::errc>(code)));
|
||||
}
|
||||
|
||||
static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode) {
|
||||
static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode, int shareFlag) {
|
||||
FILE* file;
|
||||
|
||||
int err;
|
||||
errno = 0;
|
||||
#if _WIN32
|
||||
static_assert(std::is_same_v<std::filesystem::path::value_type, wchar_t>);
|
||||
err = _wfopen_s(&file, path.c_str(), mode);
|
||||
file = _wfsopen(path.c_str(), mode, shareFlag);
|
||||
#else
|
||||
errno = 0;
|
||||
static_assert(std::is_same_v<std::filesystem::path::value_type, char>);
|
||||
file = fopen(path.c_str(), mode);
|
||||
err = errno;
|
||||
#endif
|
||||
err = errno;
|
||||
|
||||
if (!file) {
|
||||
ThrowForError(err);
|
||||
@@ -52,8 +54,8 @@ static FILE* OpenCore(const std::filesystem::path& path, const MODE_TYPE* mode)
|
||||
return file;
|
||||
}
|
||||
|
||||
static FILE* OpenCore(const char* path, const MODE_TYPE* mode) {
|
||||
return OpenCore(reinterpret_cast<const char8_t*>(path), mode);
|
||||
static FILE* OpenCore(const char* path, const MODE_TYPE* mode, int shareFlag) {
|
||||
return OpenCore(reinterpret_cast<const char8_t*>(path), mode, shareFlag);
|
||||
}
|
||||
|
||||
FileStream::FileStream() noexcept : file(nullptr) {
|
||||
@@ -76,19 +78,19 @@ FileStream::~FileStream() {
|
||||
}
|
||||
|
||||
FileStream FileStream::OpenRead(const char* utf8Path) {
|
||||
return FileStream(OpenCore(utf8Path, MODE("rb")));
|
||||
return FileStream(OpenCore(utf8Path, MODE("rb"), _SH_DENYWR));
|
||||
}
|
||||
|
||||
FileStream FileStream::OpenRead(const std::filesystem::path& path) {
|
||||
return FileStream(OpenCore(path, MODE("rb")));
|
||||
return FileStream(OpenCore(path, MODE("rb"), _SH_DENYWR));
|
||||
}
|
||||
|
||||
FileStream FileStream::Create(const char* utf8Path) {
|
||||
return FileStream(OpenCore(utf8Path, MODE("wb")));
|
||||
return FileStream(OpenCore(utf8Path, MODE("wb"), _SH_DENYWR));
|
||||
}
|
||||
|
||||
FileStream FileStream::Create(const std::filesystem::path& path) {
|
||||
return FileStream(OpenCore(path, MODE("wb")));
|
||||
return FileStream(OpenCore(path, MODE("wb"), _SH_DENYWR));
|
||||
}
|
||||
|
||||
std::vector<u8> FileStream::ReadFull() {
|
||||
|
||||
@@ -23,7 +23,7 @@ static void RunOnMainThread(void (^block)(void))
|
||||
|
||||
static NSError *MakeError(NSString *message)
|
||||
{
|
||||
return [NSError errorWithDomain:@"org.twilitrealm.dusk.file-select"
|
||||
return [NSError errorWithDomain:@"dev.twilitrealm.dusk.file-select"
|
||||
code:1
|
||||
userInfo:@{NSLocalizedDescriptionKey: message}];
|
||||
}
|
||||
|
||||
+151
-32
@@ -2,19 +2,45 @@
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using socket_t = SOCKET;
|
||||
static void closeSocket(socket_t s) { closesocket(s); }
|
||||
static void closeSocket(socket_t s) {
|
||||
LINGER li{1, 0};
|
||||
setsockopt(s, SOL_SOCKET, SO_LINGER, reinterpret_cast<const char*>(&li), sizeof(li));
|
||||
closesocket(s);
|
||||
}
|
||||
static int socketError(socket_t s) {
|
||||
int err = 0; int len = sizeof(err);
|
||||
getsockopt(s, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&err), &len);
|
||||
return err;
|
||||
}
|
||||
static constexpr int kSendFlags = 0;
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
using socket_t = int;
|
||||
static void closeSocket(socket_t s) { close(s); }
|
||||
static void closeSocket(socket_t s) {
|
||||
struct linger li{1, 0};
|
||||
setsockopt(s, SOL_SOCKET, SO_LINGER, &li, sizeof(li));
|
||||
close(s);
|
||||
}
|
||||
static int socketError(socket_t s) {
|
||||
int err = 0; socklen_t len = sizeof(err);
|
||||
getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len);
|
||||
return err;
|
||||
}
|
||||
#ifndef INVALID_SOCKET
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
static constexpr int kSendFlags = 0;
|
||||
#else
|
||||
static constexpr int kSendFlags = MSG_NOSIGNAL;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
@@ -24,12 +50,17 @@
|
||||
namespace dusk::speedrun {
|
||||
|
||||
static bool running = false;
|
||||
static bool startPending = false;
|
||||
static uint64_t frameCount = 0;
|
||||
static socket_t sock = INVALID_SOCKET;
|
||||
static bool wasLoading = false;
|
||||
static bool connected = false;
|
||||
static bool connectPending = false;
|
||||
static bool disconnectPending = false;
|
||||
static uint32_t idleProbeCounter = 0;
|
||||
static uint32_t reconnectCounter = 0;
|
||||
static char storedHost[64] = "127.0.0.1";
|
||||
static int storedPort = 16834;
|
||||
|
||||
static void sendCmd(const char* cmd) {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
@@ -37,18 +68,20 @@ static void sendCmd(const char* cmd) {
|
||||
}
|
||||
|
||||
char msg[64];
|
||||
int len = snprintf(msg, sizeof(msg), "%s\r\n", cmd);
|
||||
const int len = snprintf(msg, sizeof(msg), "%s\r\n", cmd);
|
||||
if (len <= 0 || len >= static_cast<int>(sizeof(msg))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (send(sock, msg, len, 0) >= 0) {
|
||||
if (send(sock, msg, len, kSendFlags) >= 0) {
|
||||
if (!connected) {
|
||||
connected = connectPending = true;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
int err = WSAGetLastError();
|
||||
const int err = WSAGetLastError();
|
||||
if (err == WSAEWOULDBLOCK || err == WSAENOTCONN) {
|
||||
return;
|
||||
}
|
||||
@@ -58,10 +91,13 @@ static void sendCmd(const char* cmd) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (connected) disconnectPending = true;
|
||||
if (connected) {
|
||||
disconnectPending = true;
|
||||
}
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
connected = connectPending = false;
|
||||
reconnectCounter = 0;
|
||||
}
|
||||
|
||||
uint64_t getFrameCount() {
|
||||
@@ -89,57 +125,93 @@ void start() {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
running = true;
|
||||
startPending = true;
|
||||
frameCount = 0;
|
||||
wasLoading = false;
|
||||
sendCmd("initgametime");
|
||||
sendCmd("reset");
|
||||
sendCmd("starttimer");
|
||||
}
|
||||
|
||||
void reset() {
|
||||
running = false;
|
||||
startPending = false;
|
||||
frameCount = 0;
|
||||
wasLoading = false;
|
||||
sendCmd("reset");
|
||||
}
|
||||
|
||||
void connectLiveSplit(const char* host, int port) {
|
||||
#if _WIN32
|
||||
WSADATA wd{}; WSAStartup(MAKEWORD(2, 2), &wd);
|
||||
#endif
|
||||
|
||||
static void reconnect() {
|
||||
if (sock != INVALID_SOCKET) {
|
||||
closeSocket(sock); sock = INVALID_SOCKET;
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
connected = connectPending = false;
|
||||
|
||||
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
if (sock == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
u_long nb = 1;
|
||||
ioctlsocket(sock, FIONBIO, &nb);
|
||||
if (ioctlsocket(sock, FIONBIO, &nb) != 0) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
return;
|
||||
}
|
||||
#else
|
||||
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
|
||||
const int fl = fcntl(sock, F_GETFL, 0);
|
||||
if (fl < 0 || fcntl(sock, F_SETFL, fl | O_NONBLOCK) < 0) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
sockaddr_in addr{}; addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons((uint16_t)port);
|
||||
inet_pton(AF_INET, host, &addr.sin_addr);
|
||||
connect(sock, (sockaddr*)&addr, sizeof(addr));
|
||||
sendCmd("initgametime");
|
||||
#if defined(__APPLE__)
|
||||
{
|
||||
int opt = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
|
||||
}
|
||||
#endif
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<uint16_t>(storedPort));
|
||||
if (inet_pton(AF_INET, storedHost, &addr.sin_addr) != 1) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
return;
|
||||
}
|
||||
|
||||
const int cr = connect(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
|
||||
#if _WIN32
|
||||
const bool connectPending_ = cr < 0 && WSAGetLastError() == WSAEWOULDBLOCK;
|
||||
#else
|
||||
const bool connectPending_ = cr < 0 && errno == EINPROGRESS;
|
||||
#endif
|
||||
if (cr != 0 && !connectPending_) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
void connectLiveSplit(const char* host, int port) {
|
||||
#if _WIN32
|
||||
WSADATA wd{};
|
||||
WSAStartup(MAKEWORD(2, 2), &wd);
|
||||
#endif
|
||||
snprintf(storedHost, sizeof(storedHost), "%s", host);
|
||||
storedPort = port;
|
||||
reconnect();
|
||||
}
|
||||
|
||||
void disconnectLiveSplit() {
|
||||
if (sock != INVALID_SOCKET) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
connected = false;
|
||||
}
|
||||
connected = connectPending = disconnectPending = false;
|
||||
}
|
||||
|
||||
bool consumeConnectedEvent() { bool v = connectPending; connectPending = false; return v; }
|
||||
@@ -147,29 +219,76 @@ bool consumeDisconnectedEvent() { bool v = disconnectPending; disconnectPending
|
||||
|
||||
void updateLiveSplit() {
|
||||
if (sock == INVALID_SOCKET) {
|
||||
if ((reconnectCounter++ % 30) == 0) {
|
||||
reconnect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
fd_set writefds, errorfds;
|
||||
FD_ZERO(&writefds);
|
||||
FD_ZERO(&errorfds);
|
||||
FD_SET(sock, &writefds);
|
||||
FD_SET(sock, &errorfds);
|
||||
timeval tv{0, 0};
|
||||
#if _WIN32
|
||||
const int r = select(0, nullptr, &writefds, &errorfds, &tv);
|
||||
#else
|
||||
const int r = select(sock + 1, nullptr, &writefds, &errorfds, &tv);
|
||||
#endif
|
||||
if (r < 0 || FD_ISSET(sock, &errorfds) || socketError(sock) != 0) {
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
reconnectCounter = 0;
|
||||
return;
|
||||
}
|
||||
if (!FD_ISSET(sock, &writefds)) {
|
||||
return;
|
||||
}
|
||||
sendCmd("initgametime");
|
||||
return;
|
||||
}
|
||||
|
||||
if (startPending) {
|
||||
startPending = false;
|
||||
sendCmd("initgametime");
|
||||
sendCmd("reset");
|
||||
sendCmd("starttimer");
|
||||
}
|
||||
|
||||
if (!running) {
|
||||
if ((idleProbeCounter++ % 60) == 0) {
|
||||
char buf;
|
||||
const int r = recv(sock, &buf, 1, 0);
|
||||
if (r == 0
|
||||
#if _WIN32
|
||||
|| (r < 0 && WSAGetLastError() != WSAEWOULDBLOCK)
|
||||
#else
|
||||
|| (r < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
|
||||
#endif
|
||||
) {
|
||||
if (connected) {
|
||||
disconnectPending = true;
|
||||
}
|
||||
closeSocket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
connected = connectPending = false;
|
||||
reconnectCounter = 0;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t totalMs = frameCount * 1000 / 30;
|
||||
const uint64_t totalSec = totalMs / 1000;
|
||||
char cmd[32];
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "setgametime %u:%02u:%02u.%03u",
|
||||
(uint32_t)(totalSec / 3600),
|
||||
(uint32_t)((totalSec / 60) % 60),
|
||||
(uint32_t)(totalSec % 60),
|
||||
(uint32_t)(totalMs % 1000)
|
||||
static_cast<uint32_t>(totalSec / 3600),
|
||||
static_cast<uint32_t>((totalSec / 60) % 60),
|
||||
static_cast<uint32_t>(totalSec % 60),
|
||||
static_cast<uint32_t>(totalMs % 1000)
|
||||
);
|
||||
|
||||
sendCmd(cmd);
|
||||
}
|
||||
|
||||
|
||||
+20
-9
@@ -95,7 +95,7 @@ std::string MakeTimestampedLogName() {
|
||||
#endif
|
||||
|
||||
std::array<char, 32> buffer{};
|
||||
std::strftime(buffer.data(), buffer.size(), "dusk-%Y%m%d-%H%M%S.log", &localTime);
|
||||
std::strftime(buffer.data(), buffer.size(), "dusklight-%Y%m%d-%H%M%S.log", &localTime);
|
||||
return buffer.data();
|
||||
}
|
||||
|
||||
@@ -109,6 +109,16 @@ void WriteLogLine(FILE* out, const char* levelStr, const char* module, const cha
|
||||
std::fputc('\n', out);
|
||||
std::fflush(out);
|
||||
}
|
||||
|
||||
void WriteLogLineToFile(
|
||||
const char* levelStr, const char* module, const char* message, unsigned int len) {
|
||||
if (g_logStateAlive.load(std::memory_order_acquire)) {
|
||||
std::lock_guard lock(g_logState.mutex);
|
||||
if (g_logState.file != nullptr) {
|
||||
WriteLogLine(g_logState.file, levelStr, module, message, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
static bool IsForStubLog(const char* message) {
|
||||
@@ -132,6 +142,11 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m
|
||||
return;
|
||||
}
|
||||
|
||||
if (module == nullptr) {
|
||||
module = "";
|
||||
}
|
||||
|
||||
const char* levelStr = LogLevelString(level);
|
||||
int android_log_level = 0;
|
||||
switch (level) {
|
||||
case LOG_DEBUG:
|
||||
@@ -151,12 +166,14 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m
|
||||
break;
|
||||
}
|
||||
|
||||
std::stringstream msgStream(message);
|
||||
std::stringstream msgStream(std::string(message, len));
|
||||
std::string segment;
|
||||
while(std::getline(msgStream, segment)) {
|
||||
__android_log_print(android_log_level, module, "%s\n", segment.c_str());
|
||||
}
|
||||
|
||||
WriteLogLineToFile(levelStr, module, message, len);
|
||||
|
||||
if (level == LOG_FATAL) {
|
||||
abort();
|
||||
}
|
||||
@@ -177,13 +194,7 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m
|
||||
const char* levelStr = LogLevelString(level);
|
||||
FILE* out = LogStreamForLevel(level);
|
||||
WriteLogLine(out, levelStr, module, message, len);
|
||||
|
||||
if (g_logStateAlive.load(std::memory_order_acquire)) {
|
||||
std::lock_guard lock(g_logState.mutex);
|
||||
if (g_logState.file != nullptr) {
|
||||
WriteLogLine(g_logState.file, levelStr, module, message, len);
|
||||
}
|
||||
}
|
||||
WriteLogLineToFile(levelStr, module, message, len);
|
||||
|
||||
if (level == LOG_FATAL) {
|
||||
abort();
|
||||
|
||||
+3
-3
@@ -44,7 +44,7 @@ bool RestartProcess(int argc, char* argv[]) {
|
||||
if (!CreateProcessW(nullptr, commandLine.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr,
|
||||
&startupInfo, &processInfo))
|
||||
{
|
||||
fprintf(stderr, "Failed to restart Dusk: CreateProcessW error %lu\n", GetLastError());
|
||||
fprintf(stderr, "Failed to restart Dusklight: CreateProcessW error %lu\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ bool RestartProcess(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
if (executablePath.empty()) {
|
||||
fprintf(stderr, "Failed to restart Dusk: unable to resolve executable path\n");
|
||||
fprintf(stderr, "Failed to restart Dusklight: unable to resolve executable path\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ bool RestartProcess(int argc, char* argv[]) {
|
||||
execArgv.push_back(nullptr);
|
||||
|
||||
execv(executablePath.c_str(), execArgv.data());
|
||||
fprintf(stderr, "Failed to restart Dusk: execv failed: %s\n", std::strerror(errno));
|
||||
fprintf(stderr, "Failed to restart Dusklight: execv failed: %s\n", std::strerror(errno));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
+64
-1
@@ -52,6 +52,7 @@ UserSettings g_userSettings = {
|
||||
.enableLinkDollRotation {"game.enableLinkDollRotation", false},
|
||||
.enableAchievementToasts {"game.enableAchievementToasts", true},
|
||||
.enableControllerToasts {"game.enableControllerToasts", true},
|
||||
.enableDiscordPresence {"game.enableDiscordPresence", true},
|
||||
|
||||
// Graphics
|
||||
.bloomMode {"game.bloomMode", BloomMode::Dusk},
|
||||
@@ -62,6 +63,7 @@ UserSettings g_userSettings = {
|
||||
.shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1},
|
||||
.enableDepthOfField {"game.enableDepthOfField", true},
|
||||
.enableMapBackground {"game.enableMapBackground", true},
|
||||
.disableCutscenePillarboxing {"game.disableCutscenePillarboxing", false},
|
||||
|
||||
// Audio
|
||||
.noLowHpSound {"game.noLowHpSound", false},
|
||||
@@ -91,6 +93,7 @@ UserSettings g_userSettings = {
|
||||
// Cheats
|
||||
.infiniteHearts {"game.infiniteHearts", false},
|
||||
.infiniteArrows {"game.infiniteArrows", false},
|
||||
.infiniteSeeds {"game.infiniteSeeds", false},
|
||||
.infiniteBombs {"game.infiniteBombs", false},
|
||||
.infiniteOil {"game.infiniteOil", false},
|
||||
.infiniteOxygen {"game.infiniteOxygen", false},
|
||||
@@ -101,20 +104,26 @@ UserSettings g_userSettings = {
|
||||
.alwaysGreatspin {"game.alwaysGreatspin", false},
|
||||
.enableFastIronBoots {"game.enableFastIronBoots", false},
|
||||
.canTransformAnywhere {"game.canTransformAnywhere", false},
|
||||
.fastRoll {"game.fastRoll", false},
|
||||
.fastSpinner {"game.fastSpinner", false},
|
||||
.freeMagicArmor {"game.freeMagicArmor", false},
|
||||
.invincibleEnemies {"game.invincibleEnemies", false},
|
||||
|
||||
// Technical
|
||||
.restoreWiiGlitches {"game.restoreWiiGlitches", false},
|
||||
|
||||
// Controls
|
||||
.enableTurboKeybind {"game.enableTurboKeybind", false},
|
||||
.enableResetKeybind {"game.enableResetKeybind", false},
|
||||
|
||||
// Tools
|
||||
.speedrunMode {"game.speedrunMode", false},
|
||||
.liveSplitEnabled {"game.liveSplitEnabled", false},
|
||||
.recordingMode {"game.recordingMode", false},
|
||||
.randomizerEnabled = {"game.randomizerEnabled", false}
|
||||
.showSpeedrunRTATimer {"game.showSpeedrunRTATimer", true},
|
||||
.recordingMode {"game.recordingMode", false},
|
||||
.showInputViewer {"game.showInputViewer", false},
|
||||
.showInputViewerGyro {"game.showInputViewerGyro", false}
|
||||
},
|
||||
|
||||
.backend = {
|
||||
@@ -127,6 +136,34 @@ UserSettings g_userSettings = {
|
||||
.checkForUpdates {"backend.checkForUpdates", true},
|
||||
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)},
|
||||
.enableAdvancedSettings {"backend.enableAdvancedSettings", false},
|
||||
},
|
||||
|
||||
// Not sure if there's a better way to declare this
|
||||
.actionBindings = {
|
||||
.firstPersonCamera {
|
||||
ActionBindConfigVar{"actionBindings.firstPersonCamera_port0", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.firstPersonCamera_port1", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.firstPersonCamera_port2", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.firstPersonCamera_port3", PAD_NATIVE_BUTTON_INVALID},
|
||||
},
|
||||
.callMidna {
|
||||
ActionBindConfigVar{"actionBindings.callMidna_port0", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.callMidna_port1", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.callMidna_port2", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.callMidna_port3", PAD_NATIVE_BUTTON_INVALID},
|
||||
},
|
||||
.openDusklightMenu {
|
||||
ActionBindConfigVar{"actionBindings.openDusklightMenu_port0", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.openDusklightMenu_port1", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.openDusklightMenu_port2", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.openDusklightMenu_port3", PAD_NATIVE_BUTTON_INVALID},
|
||||
},
|
||||
.turboSpeedButton {
|
||||
ActionBindConfigVar{"actionBindings.turboButton_port0", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.turboButton_port1", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.turboButton_port2", PAD_NATIVE_BUTTON_INVALID},
|
||||
ActionBindConfigVar{"actionBindings.turboButton_port3", PAD_NATIVE_BUTTON_INVALID},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -178,6 +215,7 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.freeCameraSensitivity);
|
||||
Register(g_userSettings.game.minimalHUD);
|
||||
Register(g_userSettings.game.pauseOnFocusLost);
|
||||
Register(g_userSettings.game.enableDiscordPresence);
|
||||
Register(g_userSettings.game.bloomMode);
|
||||
Register(g_userSettings.game.bloomMultiplier);
|
||||
Register(g_userSettings.game.disableWaterRefraction);
|
||||
@@ -185,8 +223,10 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.shadowResolutionMultiplier);
|
||||
Register(g_userSettings.game.enableDepthOfField);
|
||||
Register(g_userSettings.game.enableMapBackground);
|
||||
Register(g_userSettings.game.disableCutscenePillarboxing);
|
||||
Register(g_userSettings.game.enableFastIronBoots);
|
||||
Register(g_userSettings.game.canTransformAnywhere);
|
||||
Register(g_userSettings.game.fastRoll);
|
||||
Register(g_userSettings.game.freeMagicArmor);
|
||||
Register(g_userSettings.game.restoreWiiGlitches);
|
||||
Register(g_userSettings.game.enableLinkDollRotation);
|
||||
@@ -196,12 +236,17 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.noLowHpSound);
|
||||
Register(g_userSettings.game.midnasLamentNonStop);
|
||||
Register(g_userSettings.game.enableTurboKeybind);
|
||||
Register(g_userSettings.game.enableResetKeybind);
|
||||
Register(g_userSettings.game.speedrunMode);
|
||||
Register(g_userSettings.game.liveSplitEnabled);
|
||||
Register(g_userSettings.game.showSpeedrunRTATimer);
|
||||
Register(g_userSettings.game.recordingMode);
|
||||
Register(g_userSettings.game.showInputViewer);
|
||||
Register(g_userSettings.game.showInputViewerGyro);
|
||||
Register(g_userSettings.game.fastSpinner);
|
||||
Register(g_userSettings.game.infiniteHearts);
|
||||
Register(g_userSettings.game.infiniteArrows);
|
||||
Register(g_userSettings.game.infiniteSeeds);
|
||||
Register(g_userSettings.game.infiniteBombs);
|
||||
Register(g_userSettings.game.infiniteOil);
|
||||
Register(g_userSettings.game.infiniteOxygen);
|
||||
@@ -210,6 +255,7 @@ void registerSettings() {
|
||||
Register(g_userSettings.game.moonJump);
|
||||
Register(g_userSettings.game.superClawshot);
|
||||
Register(g_userSettings.game.alwaysGreatspin);
|
||||
Register(g_userSettings.game.invincibleEnemies);
|
||||
Register(g_userSettings.game.enableFrameInterpolation);
|
||||
Register(g_userSettings.game.gyroMode);
|
||||
Register(g_userSettings.game.enableGyroAim);
|
||||
@@ -237,6 +283,23 @@ void registerSettings() {
|
||||
Register(g_userSettings.backend.checkForUpdates);
|
||||
Register(g_userSettings.backend.cardFileType);
|
||||
Register(g_userSettings.backend.enableAdvancedSettings);
|
||||
|
||||
Register(g_userSettings.actionBindings.firstPersonCamera[0]);
|
||||
Register(g_userSettings.actionBindings.firstPersonCamera[1]);
|
||||
Register(g_userSettings.actionBindings.firstPersonCamera[2]);
|
||||
Register(g_userSettings.actionBindings.firstPersonCamera[3]);
|
||||
Register(g_userSettings.actionBindings.callMidna[0]);
|
||||
Register(g_userSettings.actionBindings.callMidna[1]);
|
||||
Register(g_userSettings.actionBindings.callMidna[2]);
|
||||
Register(g_userSettings.actionBindings.callMidna[3]);
|
||||
Register(g_userSettings.actionBindings.openDusklightMenu[0]);
|
||||
Register(g_userSettings.actionBindings.openDusklightMenu[1]);
|
||||
Register(g_userSettings.actionBindings.openDusklightMenu[2]);
|
||||
Register(g_userSettings.actionBindings.openDusklightMenu[3]);
|
||||
Register(g_userSettings.actionBindings.turboSpeedButton[0]);
|
||||
Register(g_userSettings.actionBindings.turboSpeedButton[1]);
|
||||
Register(g_userSettings.actionBindings.turboSpeedButton[2]);
|
||||
Register(g_userSettings.actionBindings.turboSpeedButton[3]);
|
||||
}
|
||||
|
||||
// Transient settings
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
namespace dusk {
|
||||
|
||||
SpeedrunInfo m_speedrunInfo;
|
||||
|
||||
void resetForSpeedrunMode() {
|
||||
mDoMain::developmentMode = -1;
|
||||
|
||||
getSettings().game.enableTurboKeybind.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.damageMultiplier.setSpeedrunValue(1);
|
||||
getSettings().game.instantDeath.setSpeedrunValue(false);
|
||||
getSettings().game.noHeartDrops.setSpeedrunValue(false);
|
||||
getSettings().game.autoSave.setSpeedrunValue(false);
|
||||
getSettings().game.sunsSong.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.infiniteHearts.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteArrows.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteSeeds.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteBombs.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteOil.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteOxygen.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteRupees.setSpeedrunValue(false);
|
||||
getSettings().game.enableIndefiniteItemDrops.setSpeedrunValue(false);
|
||||
getSettings().game.moonJump.setSpeedrunValue(false);
|
||||
getSettings().game.superClawshot.setSpeedrunValue(false);
|
||||
getSettings().game.alwaysGreatspin.setSpeedrunValue(false);
|
||||
getSettings().game.enableFastIronBoots.setSpeedrunValue(false);
|
||||
getSettings().game.canTransformAnywhere.setSpeedrunValue(false);
|
||||
getSettings().game.fastRoll.setSpeedrunValue(false);
|
||||
getSettings().game.fastSpinner.setSpeedrunValue(false);
|
||||
getSettings().game.freeMagicArmor.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.pauseOnFocusLost.setSpeedrunValue(false);
|
||||
aurora_set_pause_on_focus_lost(false);
|
||||
|
||||
getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false);
|
||||
getSettings().game.recordingMode.setSpeedrunValue(false);
|
||||
getSettings().game.debugFlyCam.setSpeedrunValue(false);
|
||||
}
|
||||
|
||||
} // namespace dusk
|
||||
+11
-1
@@ -51,6 +51,9 @@ void Button::update_props(Props props) {
|
||||
}
|
||||
|
||||
void ControlledButton::update() {
|
||||
if (mIsDisabled) {
|
||||
set_disabled(mIsDisabled());
|
||||
}
|
||||
if (mIsSelected) {
|
||||
set_selected(mIsSelected());
|
||||
}
|
||||
@@ -64,4 +67,11 @@ bool ControlledButton::selected() const {
|
||||
return Button::selected();
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
bool ControlledButton::disabled() const {
|
||||
if (mIsDisabled) {
|
||||
return mIsDisabled();
|
||||
}
|
||||
return Button::disabled();
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -32,17 +32,21 @@ public:
|
||||
struct Props {
|
||||
Rml::String text;
|
||||
std::function<bool()> isSelected;
|
||||
std::function<bool()> isDisabled;
|
||||
};
|
||||
|
||||
ControlledButton(Rml::Element* parent, Props props, const Rml::String& tagName = "button")
|
||||
: Button(parent, {std::move(props.text)}, tagName),
|
||||
mIsSelected(std::move(props.isSelected)) {}
|
||||
mIsSelected(std::move(props.isSelected)),
|
||||
mIsDisabled(std::move(props.isDisabled)) {}
|
||||
|
||||
void update() override;
|
||||
bool selected() const override;
|
||||
bool disabled() const override;
|
||||
|
||||
private:
|
||||
std::function<bool()> mIsSelected;
|
||||
std::function<bool()> mIsDisabled;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "dusk/config.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
@@ -108,68 +111,6 @@ const std::vector<ButtonNames> kGamepadButtonNames = {
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
|
||||
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
|
||||
return "Not bound";
|
||||
}
|
||||
|
||||
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
|
||||
if (gamepad != nullptr) {
|
||||
switch (SDL_GetGamepadButtonLabel(gamepad, button)) {
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_A:
|
||||
return "A";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_B:
|
||||
return "B";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_X:
|
||||
return "X";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_Y:
|
||||
return "Y";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
|
||||
return "Cross";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
|
||||
return "Circle";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
|
||||
return "Triangle";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
|
||||
return "Square";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const SDL_GamepadType type =
|
||||
gamepad != nullptr ? SDL_GetGamepadType(gamepad) : SDL_GAMEPAD_TYPE_UNKNOWN;
|
||||
for (const auto& buttonNames : kGamepadButtonNames) {
|
||||
if (buttonNames.button != button) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& name : buttonNames.names) {
|
||||
if (name.type == type) {
|
||||
return name.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return "D-pad left";
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return "D-pad right";
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return "D-pad up";
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return "D-pad down";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (const char* name = PADGetNativeButtonName(buttonUntyped)) {
|
||||
return name;
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
Rml::String native_axis_name(const PADAxisMapping& mapping, SDL_Gamepad* gamepad) {
|
||||
if (mapping.nativeAxis.nativeAxis != -1) {
|
||||
Rml::String value = PADGetNativeAxisName(mapping.nativeAxis);
|
||||
@@ -301,7 +242,11 @@ int rumble_raw_to_percent(u16 raw) {
|
||||
|
||||
} // namespace
|
||||
|
||||
ControllerConfigWindow::ControllerConfigWindow() {
|
||||
ControllerConfigWindow::ControllerConfigWindow(bool prelaunch) {
|
||||
if (prelaunch) {
|
||||
mSuppressNavFallback = true;
|
||||
}
|
||||
|
||||
listen(
|
||||
Rml::EventId::Keydown,
|
||||
[this](Rml::Event& event) {
|
||||
@@ -363,6 +308,7 @@ void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
|
||||
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); }, [] { return false; });
|
||||
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); }, [] { return false; });
|
||||
addPageButton(Page::Rumble, "Rumble", [] { return Rml::String(">"); }, [port] { return !PADSupportsRumbleIntensity(static_cast<u32>(port)); });
|
||||
addPageButton(Page::Actions, "Custom Action Bindings", [] {return Rml::String(">"); }, [] { return false; });
|
||||
|
||||
leftPane.add_section("Options");
|
||||
leftPane.register_control(leftPane.add_child<BoolButton>(BoolButton::Props{
|
||||
@@ -424,6 +370,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
|
||||
PADClearPort(port);
|
||||
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
|
||||
PADSerializeMappings();
|
||||
ClearAllActionBindings(port);
|
||||
});
|
||||
|
||||
pane.add_button({
|
||||
@@ -436,6 +383,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
|
||||
PADClearPort(port);
|
||||
PADSetKeyboardActive(static_cast<u32>(port), TRUE);
|
||||
PADSerializeMappings();
|
||||
ClearAllActionBindings(port);
|
||||
});
|
||||
|
||||
const u32 controllerCount = PADCount();
|
||||
@@ -457,6 +405,7 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
|
||||
PADSetKeyboardActive(static_cast<u32>(port), FALSE);
|
||||
PADSetPortForIndex(i, port);
|
||||
PADSerializeMappings();
|
||||
ClearAllActionBindings(port);
|
||||
});
|
||||
}
|
||||
break;
|
||||
@@ -942,6 +891,77 @@ void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
|
||||
pane.add_text("Configure your desired rumble intensities, then run a test to check how they feel.");
|
||||
break;
|
||||
}
|
||||
case Page::Actions: {
|
||||
if (keyboard_active(port)) {
|
||||
auto addActionBinding = [&](auto actionBind, const std::string& key) {
|
||||
pane.add_select_button(
|
||||
{
|
||||
.key = key,
|
||||
.getValue =
|
||||
[this, actionBind] {
|
||||
if (mPendingActionBinding == actionBind) {
|
||||
return pending_key_label();
|
||||
}
|
||||
|
||||
return keyboard_key_name(actionBind->getValue());
|
||||
},
|
||||
})
|
||||
.on_pressed([this, port, actionBind] {
|
||||
cancel_pending_binding();
|
||||
mPendingPort = port;
|
||||
mPendingBindingArmed = false;
|
||||
mPendingActionBinding = actionBind;
|
||||
});
|
||||
};
|
||||
|
||||
pane.add_section("Custom Action Bindings");
|
||||
pane.add_text("A key bound to any action here will REPLACE the default control for"
|
||||
" that action. Only bind buttons here that aren't used anywhere else.");
|
||||
for (auto& [configVars, actionName] : getActionBinds() | std::views::values) {
|
||||
addActionBinding(&configVars->at(port), actionName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
u32 buttonCount = 0;
|
||||
PADButtonMapping* mappings = PADGetButtonMappings(port, &buttonCount);
|
||||
if (mappings == nullptr) {
|
||||
pane.add_text("No controller selected");
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_Gamepad* gamepad = gamepad_for_port(port);
|
||||
pane.add_section("Custom Action Bindings");
|
||||
pane.add_text("A button bound to any action here will REPLACE the default control for"
|
||||
" that action. Only bind buttons here that aren't used anywhere else. The glyphs"
|
||||
" shown for in game actions will not change. This is not recommended for "
|
||||
" regular Gamecube controllers.");
|
||||
auto addActionBinding = [&](auto actionBind, const std::string& key) {
|
||||
pane.add_select_button({
|
||||
.key = key,
|
||||
.getValue =
|
||||
[this, gamepad, actionBind] {
|
||||
if (mPendingActionBinding == actionBind) {
|
||||
return pending_button_label();
|
||||
}
|
||||
|
||||
return native_button_name(
|
||||
gamepad, actionBind->getValue());
|
||||
},
|
||||
})
|
||||
.on_pressed([this, port, actionBind] {
|
||||
cancel_pending_binding();
|
||||
mPendingPort = port;
|
||||
mPendingBindingArmed = false;
|
||||
mPendingActionBinding = actionBind;
|
||||
});
|
||||
};
|
||||
|
||||
for (auto& [configVars, actionName] : getActionBinds() | std::views::values) {
|
||||
addActionBinding(&configVars->at(port), actionName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1016,12 +1036,31 @@ void ControllerConfigWindow::poll_pending_binding() {
|
||||
mPendingAxisMapping->nativeButton = nativeButton;
|
||||
finish_pending_binding(completedPort);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPendingActionBinding != nullptr) {
|
||||
int button{};
|
||||
if (keyboard_active(mPendingPort)) {
|
||||
button = keyboard_key_pressed();
|
||||
} else {
|
||||
button = PADGetNativeButtonPressed(mPendingPort);
|
||||
}
|
||||
|
||||
if (button != -1) {
|
||||
const int completedPort = mPendingPort;
|
||||
mPendingActionBinding->setValue(button);
|
||||
config::Save();
|
||||
finish_pending_binding(completedPort);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerConfigWindow::finish_pending_binding(int completedPort) {
|
||||
mPendingButtonMapping = nullptr;
|
||||
mPendingAxisMapping = nullptr;
|
||||
mPendingActionBinding = nullptr;
|
||||
mPendingPort = -1;
|
||||
mPendingBindingArmed = false;
|
||||
mSuppressNavigationUntilNeutral = true;
|
||||
@@ -1031,7 +1070,7 @@ void ControllerConfigWindow::finish_pending_binding(int completedPort) {
|
||||
|
||||
void ControllerConfigWindow::unmap_pending_binding() {
|
||||
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
|
||||
mPendingKeyButton < 0 && mPendingKeyAxis < 0)
|
||||
mPendingActionBinding == nullptr && mPendingKeyButton < 0 && mPendingKeyAxis < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1044,6 +1083,9 @@ void ControllerConfigWindow::unmap_pending_binding() {
|
||||
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
|
||||
mPendingAxisMapping->nativeButton = -1;
|
||||
finish_pending_binding(completedPort);
|
||||
} else if (mPendingActionBinding != nullptr) {
|
||||
mPendingActionBinding->setValue(PAD_NATIVE_BUTTON_INVALID);
|
||||
finish_pending_binding(completedPort);
|
||||
} else if (mPendingKeyButton >= 0) {
|
||||
PADSetKeyButtonBinding(static_cast<u32>(completedPort),
|
||||
{PAD_KEY_INVALID, static_cast<PADButton>(mPendingKeyButton)});
|
||||
@@ -1057,7 +1099,7 @@ void ControllerConfigWindow::unmap_pending_binding() {
|
||||
|
||||
bool ControllerConfigWindow::capture_active() const {
|
||||
return mPendingButtonMapping != nullptr || mPendingAxisMapping != nullptr ||
|
||||
mPendingKeyButton >= 0 || mPendingKeyAxis >= 0;
|
||||
mPendingActionBinding != nullptr || mPendingKeyButton >= 0 || mPendingKeyAxis >= 0;
|
||||
}
|
||||
|
||||
bool ControllerConfigWindow::pending_input_neutral() const {
|
||||
@@ -1076,13 +1118,14 @@ Rml::String ControllerConfigWindow::pending_axis_label() const {
|
||||
}
|
||||
|
||||
void ControllerConfigWindow::cancel_pending_binding() {
|
||||
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
|
||||
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr && mPendingActionBinding == nullptr &&
|
||||
!mSuppressNavigationUntilNeutral && mPendingKeyButton < 0 && mPendingKeyAxis < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
mPendingButtonMapping = nullptr;
|
||||
mPendingAxisMapping = nullptr;
|
||||
mPendingActionBinding = nullptr;
|
||||
mPendingKeyButton = -1;
|
||||
mPendingKeyAxis = -1;
|
||||
mPendingPort = -1;
|
||||
@@ -1114,4 +1157,66 @@ void ControllerConfigWindow::stop_rumble_test() {
|
||||
mRumbleTestPort = -1;
|
||||
}
|
||||
|
||||
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
|
||||
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
|
||||
return "Not bound";
|
||||
}
|
||||
|
||||
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
|
||||
if (gamepad != nullptr) {
|
||||
switch (SDL_GetGamepadButtonLabel(gamepad, button)) {
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_A:
|
||||
return "A";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_B:
|
||||
return "B";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_X:
|
||||
return "X";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_Y:
|
||||
return "Y";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
|
||||
return "Cross";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
|
||||
return "Circle";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
|
||||
return "Triangle";
|
||||
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
|
||||
return "Square";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const SDL_GamepadType type =
|
||||
gamepad != nullptr ? SDL_GetGamepadType(gamepad) : SDL_GAMEPAD_TYPE_UNKNOWN;
|
||||
for (const auto& buttonNames : kGamepadButtonNames) {
|
||||
if (buttonNames.button != button) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& name : buttonNames.names) {
|
||||
if (name.type == type) {
|
||||
return name.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return "D-pad left";
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return "D-pad right";
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return "D-pad up";
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return "D-pad down";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (const char* name = PADGetNativeButtonName(buttonUntyped)) {
|
||||
return name;
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "window.hpp"
|
||||
#include "dusk/config_var.hpp"
|
||||
|
||||
#include <pad.h>
|
||||
|
||||
@@ -8,7 +9,7 @@ namespace dusk::ui {
|
||||
|
||||
class ControllerConfigWindow : public Window {
|
||||
public:
|
||||
ControllerConfigWindow();
|
||||
ControllerConfigWindow(bool prelaunch);
|
||||
|
||||
void update() override;
|
||||
void hide(bool close) override;
|
||||
@@ -20,6 +21,7 @@ private:
|
||||
Triggers,
|
||||
Sticks,
|
||||
Rumble,
|
||||
Actions,
|
||||
};
|
||||
|
||||
void build_port_tab(Rml::Element* content, int port);
|
||||
@@ -50,6 +52,9 @@ private:
|
||||
int mPendingKeyAxis = -1;
|
||||
bool mRumbleTestActive = false;
|
||||
int mRumbleTestPort = -1;
|
||||
ActionBindConfigVar* mPendingActionBinding = nullptr;
|
||||
};
|
||||
|
||||
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped);
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
+37
-38
@@ -26,6 +26,43 @@
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
Rml::String stage_option_label(const MapEntry& map, bool showInternalNames) {
|
||||
return showInternalNames ? fmt::format("{} ({})", map.mapName, map.mapFile) : map.mapName;
|
||||
}
|
||||
|
||||
Rml::String stage_label_for_file(const Rml::String& stageFile, bool showInternalNames) {
|
||||
for (const auto& region : gameRegions) {
|
||||
for (const auto& map : region.maps) {
|
||||
if (stageFile == map.mapFile) {
|
||||
return stage_option_label(map, showInternalNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
return stageFile;
|
||||
}
|
||||
|
||||
void populate_stage_picker(Pane& pane, std::function<Rml::String()> getStageFile,
|
||||
std::function<void(const char*)> setStageFile, bool showInternalNames) {
|
||||
pane.clear();
|
||||
for (const auto& region : gameRegions) {
|
||||
pane.add_section(region.regionName);
|
||||
for (const auto& map : region.maps) {
|
||||
pane.add_button({
|
||||
.text = stage_option_label(map, showInternalNames),
|
||||
.isSelected =
|
||||
[getStageFile, stageFile = map.mapFile] {
|
||||
return getStageFile() == stageFile;
|
||||
},
|
||||
})
|
||||
.on_pressed([setStageFile, stageFile = map.mapFile] {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
setStageFile(stageFile);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool has_save_data() {
|
||||
@@ -155,44 +192,6 @@ bool parse_vec3(const Rml::String& value, float& x, float& y, float& z) {
|
||||
return *cursor == '\0';
|
||||
}
|
||||
|
||||
Rml::String stage_option_label(const MapEntry& map) {
|
||||
// TODO: option to show internal name?
|
||||
// return fmt::format("{} ({})", map.mapName, map.mapFile);
|
||||
return map.mapName;
|
||||
}
|
||||
|
||||
Rml::String stage_label_for_file(const Rml::String& stageFile) {
|
||||
for (const auto& region : gameRegions) {
|
||||
for (const auto& map : region.maps) {
|
||||
if (stageFile == map.mapFile) {
|
||||
return stage_option_label(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
return stageFile;
|
||||
}
|
||||
|
||||
void populate_stage_picker(Pane& pane, std::function<Rml::String()> getStageFile,
|
||||
std::function<void(const char*)> setStageFile) {
|
||||
pane.clear();
|
||||
for (const auto& region : gameRegions) {
|
||||
pane.add_section(region.regionName);
|
||||
for (const auto& map : region.maps) {
|
||||
pane.add_button({
|
||||
.text = stage_option_label(map),
|
||||
.isSelected =
|
||||
[getStageFile, stageFile = map.mapFile] {
|
||||
return getStageFile() == stageFile;
|
||||
},
|
||||
})
|
||||
.on_pressed([setStageFile, stageFile = map.mapFile] {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
setStageFile(stageFile);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rml::String get_player_name() {
|
||||
if (!has_save_data()) {
|
||||
return "";
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "window.hpp"
|
||||
|
||||
struct MapEntry;
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class Pane;
|
||||
|
||||
Rml::String stage_option_label(const MapEntry& map, bool showInternalNames = false);
|
||||
Rml::String stage_label_for_file(const Rml::String& stageFile, bool showInternalNames = false);
|
||||
void populate_stage_picker(Pane& pane, std::function<Rml::String()> getStageFile,
|
||||
std::function<void(const char*)> setStageFile,
|
||||
bool showInternalNames = false);
|
||||
|
||||
class EditorWindow : public Window {
|
||||
public:
|
||||
EditorWindow();
|
||||
|
||||
+21
-5
@@ -12,6 +12,8 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "dusk/action_bindings.h"
|
||||
|
||||
namespace dusk::ui::input {
|
||||
namespace {
|
||||
|
||||
@@ -203,6 +205,9 @@ Rml::Input::KeyIdentifier map_raw_gamepad_button(SDL_GamepadButton button) noexc
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH:
|
||||
return Rml::Input::KI_RETURN;
|
||||
case SDL_GAMEPAD_BUTTON_BACK:
|
||||
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0)) {
|
||||
return Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
return Rml::Input::KI_F1;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return Rml::Input::KI_NEXT;
|
||||
@@ -216,6 +221,9 @@ Rml::Input::KeyIdentifier map_raw_gamepad_button(SDL_GamepadButton button) noexc
|
||||
Rml::Input::KeyIdentifier map_raw_button_alias(SDL_GamepadButton button) noexcept {
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_BACK:
|
||||
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0)) {
|
||||
return Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
return Rml::Input::KI_F1;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return Rml::Input::KI_NEXT;
|
||||
@@ -318,12 +326,20 @@ bool find_event_pad_button(
|
||||
|
||||
Rml::Input::KeyIdentifier map_gamepad_button(const SDL_GamepadButtonEvent& event) noexcept {
|
||||
const auto nativeButton = static_cast<SDL_GamepadButton>(event.button);
|
||||
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK) {
|
||||
u32 port = 0;
|
||||
bool foundEventPort = find_event_port(event.which, port);
|
||||
if (foundEventPort) {
|
||||
int openMenuButton = getActionBindButton(ActionBinds::OPEN_DUSKLIGHT_MENU, port);
|
||||
if (openMenuButton != PAD_NATIVE_BUTTON_INVALID && openMenuButton == nativeButton) {
|
||||
return Rml::Input::KI_F1;
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port)) {
|
||||
return Rml::Input::KI_F1;
|
||||
}
|
||||
|
||||
u32 port = 0;
|
||||
if (!find_event_port(event.which, port)) {
|
||||
if (!foundEventPort) {
|
||||
return map_raw_gamepad_button(nativeButton);
|
||||
}
|
||||
|
||||
@@ -631,7 +647,7 @@ void process_axis_direction(
|
||||
if (chorded) {
|
||||
consume_menu_chord(port, context);
|
||||
}
|
||||
const auto key = chorded ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
|
||||
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_axis(event, sign);
|
||||
if (key == Rml::Input::KI_UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
@@ -719,7 +735,7 @@ void handle_event(const SDL_Event& event) noexcept {
|
||||
if (chorded) {
|
||||
consume_menu_chord(port, *context);
|
||||
}
|
||||
const auto key = chorded ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
|
||||
const auto key = chorded && !isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, port) ? Rml::Input::KI_F1 : map_gamepad_button(event.gbutton);
|
||||
if (key != Rml::Input::KI_UNKNOWN) {
|
||||
bool deferred = false;
|
||||
if (repeat != nullptr) {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "achievements.hpp"
|
||||
#include "aurora/rmlui.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "editor.hpp"
|
||||
@@ -16,6 +18,7 @@
|
||||
#include "modal.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "warp.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
#include <chrono>
|
||||
@@ -51,11 +54,9 @@ MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById(
|
||||
.autoSelect = false,
|
||||
});
|
||||
mTabBar->add_tab("Settings", [this] { push(std::make_unique<SettingsWindow>()); });
|
||||
// mTabBar->add_tab("Warp", [] {
|
||||
// // TODO
|
||||
// });
|
||||
|
||||
if (getSettings().backend.enableAdvancedSettings) {
|
||||
mTabBar->add_tab("Warp", [this] { push(std::make_unique<WarpWindow>()); });
|
||||
mTabBar->add_tab("Editor", [this] { push(std::make_unique<EditorWindow>()); });
|
||||
}
|
||||
|
||||
@@ -103,7 +104,7 @@ MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById(
|
||||
mTabBar->set_active_tab(-1);
|
||||
const auto dismiss = [](Modal& modal) { modal.pop(); };
|
||||
push(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Quit Dusk",
|
||||
.title = "Quit Dusklight",
|
||||
.bodyRml = "Unsaved progress will be lost.",
|
||||
.actions =
|
||||
{
|
||||
@@ -130,6 +131,18 @@ MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById(
|
||||
}));
|
||||
});
|
||||
|
||||
if (getSettings().game.speedrunMode) {
|
||||
mTabBar->add_tab("Reset Timer", [this] {
|
||||
mTabBar->set_active_tab(-1);
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
m_speedrunInfo.reset();
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::reset();
|
||||
}
|
||||
hide(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Hide document after transition completion
|
||||
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
|
||||
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
|
||||
|
||||
+87
-1
@@ -2,6 +2,11 @@
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/achievements.h"
|
||||
#include "dusk/action_bindings.h"
|
||||
#include "controller_config.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "fmt/format.h"
|
||||
#include "magic_enum.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
@@ -9,6 +14,7 @@
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <algorithm>
|
||||
#include <dolphin/pad.h>
|
||||
#include <m_Do/m_Do_main.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
@@ -25,6 +31,10 @@ const Rml::String kDocumentSource = R"RML(
|
||||
</head>
|
||||
<body>
|
||||
<fps id="fps" />
|
||||
<speedrun-timer id="speedrun-timer">
|
||||
<speedrun-rta id="speedrun-rta" />
|
||||
<speedrun-igt id="speedrun-igt" />
|
||||
</speedrun-timer>
|
||||
</body>
|
||||
</rml>
|
||||
)RML";
|
||||
@@ -144,12 +154,22 @@ Rml::Element* create_menu_notification(Rml::Element* parent) {
|
||||
auto* elem = append(parent, "toast");
|
||||
elem->SetClass("menu-notification", true);
|
||||
|
||||
// Get name of button for action binding if the action is bound
|
||||
Rml::String padButton{};
|
||||
SDL_Gamepad* gamepad = gamepad_for_port(PAD_CHAN0);
|
||||
if (isActionBound(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0) && gamepad != nullptr) {
|
||||
padButton = native_button_name(gamepad,
|
||||
getActionBindButton(ActionBinds::OPEN_DUSKLIGHT_MENU, PAD_CHAN0));
|
||||
} else {
|
||||
padButton = back_button_name();
|
||||
}
|
||||
|
||||
auto* message = append(elem, "message");
|
||||
auto* row = append(message, "row");
|
||||
append(row, "span")->SetInnerRML(kMenuNotificationPrefix);
|
||||
auto* icon = append(row, "icon");
|
||||
icon->SetClass("controller", true);
|
||||
append(row, "span")->SetInnerRML(escape(back_button_name()));
|
||||
append(row, "span")->SetInnerRML(escape(padButton));
|
||||
append(row, "span")->SetInnerRML("to open menu");
|
||||
|
||||
return elem;
|
||||
@@ -204,8 +224,17 @@ void Overlay::advance_fps_counter(float& outFps, Uint64 perfFreq) {
|
||||
outFps = static_cast<float>(1.0 / avgSeconds);
|
||||
}
|
||||
|
||||
static std::string FormatTime(OSTime ticks) {
|
||||
OSCalendarTime t;
|
||||
OSTicksToCalendarTime(ticks, &t);
|
||||
return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", t.hour, t.min, t.sec, t.msec);
|
||||
}
|
||||
|
||||
Overlay::Overlay() : Document(kDocumentSource) {
|
||||
mFpsCounter = mDocument->GetElementById("fps");
|
||||
mSpeedrunTimer = mDocument->GetElementById("speedrun-timer");
|
||||
mSpeedrunRta = mDocument->GetElementById("speedrun-rta");
|
||||
mSpeedrunIgt = mDocument->GetElementById("speedrun-igt");
|
||||
|
||||
listen(mDocument, Rml::EventId::Focus, [](Rml::Event&) { Log.warn("Overlay received focus"); });
|
||||
listen(mDocument, Rml::EventId::Transitionend, [this](Rml::Event& event) {
|
||||
@@ -268,6 +297,63 @@ void Overlay::update() {
|
||||
}
|
||||
}
|
||||
|
||||
#if !(defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST))
|
||||
if (getSettings().game.speedrunMode && getSettings().game.liveSplitEnabled) {
|
||||
dusk::speedrun::updateLiveSplit();
|
||||
if (dusk::speedrun::consumeConnectedEvent()) {
|
||||
push_toast({.title = "LiveSplit connected", .duration = std::chrono::seconds(3)});
|
||||
}
|
||||
if (dusk::speedrun::consumeDisconnectedEvent()) {
|
||||
push_toast({.title = "LiveSplit disconnected", .duration = std::chrono::seconds(3)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mSpeedrunTimer != nullptr && mSpeedrunRta != nullptr && mSpeedrunIgt != nullptr) {
|
||||
if (getSettings().game.speedrunMode) {
|
||||
// L+R+A+Start to reset timer
|
||||
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) &&
|
||||
mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1))
|
||||
{
|
||||
m_speedrunInfo.reset();
|
||||
}
|
||||
|
||||
// L+R+A+Y to manually stop timer
|
||||
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) &&
|
||||
mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigY(PAD_1))
|
||||
{
|
||||
if (m_speedrunInfo.m_isRunStarted) {
|
||||
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||
m_speedrunInfo.m_isRunStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
OSTime elapsedTime = 0;
|
||||
if (m_speedrunInfo.m_isRunStarted) {
|
||||
elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||
} else if (m_speedrunInfo.m_endTimestamp != 0) {
|
||||
elapsedTime = m_speedrunInfo.m_endTimestamp;
|
||||
}
|
||||
|
||||
if (!m_speedrunInfo.m_isPauseIGT) {
|
||||
m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime;
|
||||
}
|
||||
|
||||
mSpeedrunTimer->SetAttribute("open", "");
|
||||
|
||||
if (getSettings().game.showSpeedrunRTATimer) {
|
||||
mSpeedrunRta->SetAttribute("open", "");
|
||||
mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatTime(elapsedTime))));
|
||||
} else {
|
||||
mSpeedrunRta->RemoveAttribute("open");
|
||||
}
|
||||
|
||||
mSpeedrunIgt->SetInnerRML(escape(fmt::format("IGT {}", FormatTime(m_speedrunInfo.m_igtTimer))));
|
||||
} else {
|
||||
mSpeedrunTimer->RemoveAttribute("open");
|
||||
}
|
||||
}
|
||||
|
||||
const bool showControllerWarning = PADGetIndexForPort(PAD_CHAN0) < 0 &&
|
||||
PADGetKeyButtonBindings(PAD_CHAN0, nullptr) == nullptr &&
|
||||
dynamic_cast<Window*>(top_document()) == nullptr &&
|
||||
|
||||
@@ -21,6 +21,9 @@ protected:
|
||||
Rml::Element* mCurrentToast = nullptr;
|
||||
Rml::Element* mControllerWarning = nullptr;
|
||||
Rml::Element* mMenuNotification = nullptr;
|
||||
Rml::Element* mSpeedrunTimer = nullptr;
|
||||
Rml::Element* mSpeedrunRta = nullptr;
|
||||
Rml::Element* mSpeedrunIgt = nullptr;
|
||||
clock::time_point mCurrentToastStartTime;
|
||||
clock::time_point mMenuNotificationStartTime;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "prelaunch.hpp"
|
||||
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/data.hpp"
|
||||
#include "dusk/file_select.hpp"
|
||||
#include "dusk/iso_validate.hpp"
|
||||
#include "dusk/main.h"
|
||||
@@ -43,8 +44,8 @@ const Rml::String kDocumentSource = R"RML(
|
||||
<content id="root" open>
|
||||
<menu>
|
||||
<hero class="intro-item delay-0">
|
||||
<div class="eyebrow"><span>Twilit Realm</span> presents</div>
|
||||
<img src="res/logo-mascot.png" />
|
||||
<eyebrow><span>Twilit Realm</span> presents</eyebrow>
|
||||
<img src="res/logo.png" />
|
||||
</hero>
|
||||
<div id="menu-list" />
|
||||
</menu>
|
||||
@@ -127,7 +128,7 @@ struct UpdateCheckTask {
|
||||
UpdateCheckTask() {
|
||||
worker = std::thread([this] {
|
||||
try {
|
||||
result = update_check::check_latest_github_release("TwilitRealm", "dusk");
|
||||
result = update_check::check_latest_github_release("TwilitRealm", "dusklight");
|
||||
} catch (const std::exception& e) {
|
||||
result = {
|
||||
.status = update_check::Status::Failed,
|
||||
@@ -287,12 +288,12 @@ std::string get_error_msg(iso::ValidationError error) {
|
||||
case iso::ValidationError::InvalidImage:
|
||||
return "The selected file is not a valid disc image.";
|
||||
case iso::ValidationError::WrongGame:
|
||||
return "The selected game is not supported by Dusk.";
|
||||
return "The selected game is not supported by Dusklight.";
|
||||
case iso::ValidationError::WrongVersion:
|
||||
return "Dusk currently supports GameCube USA and PAL disc images only.";
|
||||
return "Dusklight currently supports GameCube USA and PAL disc images only.";
|
||||
case iso::ValidationError::Canceled:
|
||||
return "Disc verification was canceled. Dusk cannot guarantee the selected disc image "
|
||||
"is compatible.";
|
||||
return "Disc verification was canceled. Dusklight cannot guarantee the selected disc "
|
||||
"image is compatible.";
|
||||
case iso::ValidationError::HashMismatch:
|
||||
return "The selected disc image did not pass hash verification. It may be corrupt or "
|
||||
"modified.";
|
||||
@@ -656,6 +657,9 @@ bool is_restart_pending() noexcept {
|
||||
if (!state.activeDiscPath.empty() && state.configuredDiscPath != state.activeDiscPath) {
|
||||
return true;
|
||||
}
|
||||
if (data::is_data_path_restart_pending()) {
|
||||
return true;
|
||||
}
|
||||
if (getSettings().backend.graphicsBackend.getValue() != state.initialGraphicsBackend) {
|
||||
return true;
|
||||
}
|
||||
@@ -798,7 +802,7 @@ void Prelaunch::show() {
|
||||
"A restart is required to apply selected options.<br/><br/>Restart now to "
|
||||
"apply them immediately?" :
|
||||
"A restart is required to apply selected options.<br/><br/>Close and reopen "
|
||||
"Dusk to apply them.",
|
||||
"Dusklight to apply them.",
|
||||
.actions = std::move(actions),
|
||||
.onDismiss = dismiss,
|
||||
}));
|
||||
|
||||
@@ -59,7 +59,7 @@ PresetWindow::PresetWindow() : WindowSmall("modal", "modal-dialog") {
|
||||
|
||||
auto* title = append(header, "div");
|
||||
title->SetClass("modal-title", true);
|
||||
title->SetInnerRML("Welcome to Dusk");
|
||||
title->SetInnerRML("Welcome to Dusklight");
|
||||
|
||||
auto* headIcon = append(header, "icon");
|
||||
headIcon->SetClass("celebration", true);
|
||||
|
||||
@@ -26,7 +26,7 @@ CrashReportWindow::CrashReportWindow() : WindowSmall("modal", "modal-dialog") {
|
||||
auto* intro = append(mDialog, "div");
|
||||
intro->SetClass("modal-body", true);
|
||||
intro->SetInnerRML(
|
||||
"Dusk can automatically send crash reports to the developers. Crash reports contain the "
|
||||
"Dusklight can automatically send crash reports to the developers. Crash reports contain the "
|
||||
"following:"
|
||||
"<br/>• Operating system version<br/>• CPU architecture<br/>• GPU model & driver version"
|
||||
"<br/>• File paths (may include account username)<br/>• Stack trace<br/><br/>"
|
||||
@@ -43,7 +43,7 @@ CrashReportWindow::CrashReportWindow() : WindowSmall("modal", "modal-dialog") {
|
||||
|
||||
static constexpr OptionInfo kOptions[] = {
|
||||
{"Enable",
|
||||
"Send crash reports to Dusk developers. Reports will include the information described "
|
||||
"Send crash reports to Dusklight developers. Reports will include the information described "
|
||||
"above.",
|
||||
[] { crash_reporting::set_consent(true); }},
|
||||
{"Disable",
|
||||
|
||||
+313
-67
@@ -3,27 +3,38 @@
|
||||
#include "aurora/gfx.h"
|
||||
#include "bool_button.hpp"
|
||||
#include "controller_config.hpp"
|
||||
#include "dusk/app_info.hpp"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/audio/DuskDsp.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/hotkeys.h"
|
||||
#include "dusk/data.hpp"
|
||||
#include "dusk/file_select.hpp"
|
||||
#include "dusk/imgui/ImGuiEngine.hpp"
|
||||
#include "dusk/io.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/discord_presence.hpp"
|
||||
#include "graphics_tuner.hpp"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "menu_bar.hpp"
|
||||
#include "modal.hpp"
|
||||
#include "number_button.hpp"
|
||||
#include "menu_bar.hpp"
|
||||
#include "pane.hpp"
|
||||
#include "prelaunch.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <aurora/lib/window.hpp>
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#if DUSK_ENABLE_SENTRY_NATIVE
|
||||
#include "dusk/crash_reporting.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
@@ -166,29 +177,172 @@ AuroraBackend configured_backend() {
|
||||
void reset_for_speedrun_mode() {
|
||||
mDoMain::developmentMode = -1;
|
||||
|
||||
getSettings().game.damageMultiplier.setValue(1);
|
||||
getSettings().game.instantDeath.setValue(false);
|
||||
getSettings().game.noHeartDrops.setValue(false);
|
||||
getSettings().game.enableTurboKeybind.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.infiniteHearts.setValue(false);
|
||||
getSettings().game.infiniteArrows.setValue(false);
|
||||
getSettings().game.infiniteBombs.setValue(false);
|
||||
getSettings().game.infiniteOil.setValue(false);
|
||||
getSettings().game.infiniteOxygen.setValue(false);
|
||||
getSettings().game.infiniteRupees.setValue(false);
|
||||
getSettings().game.enableIndefiniteItemDrops.setValue(false);
|
||||
getSettings().game.damageMultiplier.setSpeedrunValue(1);
|
||||
getSettings().game.instantDeath.setSpeedrunValue(false);
|
||||
getSettings().game.noHeartDrops.setSpeedrunValue(false);
|
||||
getSettings().game.autoSave.setSpeedrunValue(false);
|
||||
getSettings().game.sunsSong.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.moonJump.setValue(false);
|
||||
getSettings().game.superClawshot.setValue(false);
|
||||
getSettings().game.alwaysGreatspin.setValue(false);
|
||||
getSettings().game.enableFastIronBoots.setValue(false);
|
||||
getSettings().game.canTransformAnywhere.setValue(false);
|
||||
getSettings().game.fastSpinner.setValue(false);
|
||||
getSettings().game.freeMagicArmor.setValue(false);
|
||||
getSettings().game.infiniteHearts.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteArrows.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteSeeds.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteBombs.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteOil.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteOxygen.setSpeedrunValue(false);
|
||||
getSettings().game.infiniteRupees.setSpeedrunValue(false);
|
||||
getSettings().game.enableIndefiniteItemDrops.setSpeedrunValue(false);
|
||||
getSettings().game.moonJump.setSpeedrunValue(false);
|
||||
getSettings().game.superClawshot.setSpeedrunValue(false);
|
||||
getSettings().game.alwaysGreatspin.setSpeedrunValue(false);
|
||||
getSettings().game.enableFastIronBoots.setSpeedrunValue(false);
|
||||
getSettings().game.canTransformAnywhere.setSpeedrunValue(false);
|
||||
getSettings().game.fastRoll.setSpeedrunValue(false);
|
||||
getSettings().game.fastSpinner.setSpeedrunValue(false);
|
||||
getSettings().game.freeMagicArmor.setSpeedrunValue(false);
|
||||
getSettings().game.invincibleEnemies.setSpeedrunValue(false);
|
||||
|
||||
getSettings().game.enableTurboKeybind.setValue(false);
|
||||
getSettings().game.debugFlyCam.setValue(false);
|
||||
getSettings().game.autoSave.setValue(false);
|
||||
getSettings().game.pauseOnFocusLost.setSpeedrunValue(false);
|
||||
aurora_set_pause_on_focus_lost(false);
|
||||
|
||||
getSettings().backend.enableAdvancedSettings.setSpeedrunValue(false);
|
||||
getSettings().game.recordingMode.setSpeedrunValue(false);
|
||||
getSettings().game.debugFlyCam.setSpeedrunValue(false);
|
||||
}
|
||||
|
||||
void clear_speedrun_overrides() {
|
||||
config::EnumerateRegistered([](config::ConfigVarBase& cvar) {
|
||||
cvar.clearSpeedrunOverride();
|
||||
});
|
||||
}
|
||||
|
||||
void restore_from_speedrun_mode() {
|
||||
clear_speedrun_overrides();
|
||||
aurora_set_pause_on_focus_lost(getSettings().game.pauseOnFocusLost.getValue());
|
||||
}
|
||||
|
||||
std::filesystem::path normalized_display_path(const std::filesystem::path& path) {
|
||||
std::error_code ec;
|
||||
auto normalized = std::filesystem::weakly_canonical(path, ec);
|
||||
if (!ec) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
normalized = std::filesystem::absolute(path, ec);
|
||||
if (!ec) {
|
||||
return normalized.lexically_normal();
|
||||
}
|
||||
|
||||
return path.lexically_normal();
|
||||
}
|
||||
|
||||
std::filesystem::path user_home_path() {
|
||||
const char* homePath = SDL_GetUserFolder(SDL_FOLDER_HOME);
|
||||
if (homePath == nullptr || homePath[0] == '\0') {
|
||||
return {};
|
||||
}
|
||||
return std::filesystem::path{reinterpret_cast<const char8_t*>(homePath)};
|
||||
}
|
||||
|
||||
Rml::String abbreviated_data_path_string() {
|
||||
const auto path = data::configured_data_path();
|
||||
const auto homePath = user_home_path();
|
||||
if (path.empty() || homePath.empty()) {
|
||||
return io::fs_path_to_string(path);
|
||||
}
|
||||
|
||||
const auto normalizedPath = normalized_display_path(path);
|
||||
const auto normalizedHome = normalized_display_path(homePath);
|
||||
if (normalizedPath == normalizedHome) {
|
||||
return "~";
|
||||
}
|
||||
|
||||
const auto relativePath = normalizedPath.lexically_relative(normalizedHome);
|
||||
if (!relativePath.empty() && !relativePath.is_absolute()) {
|
||||
const auto it = relativePath.begin();
|
||||
if (it == relativePath.end() || *it != "..") {
|
||||
return io::fs_path_to_string(std::filesystem::path{"~"} / relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return io::fs_path_to_string(path);
|
||||
}
|
||||
|
||||
Rml::String configured_data_path_display_name() {
|
||||
const auto path = abbreviated_data_path_string();
|
||||
if (path.empty()) {
|
||||
return "(none)";
|
||||
}
|
||||
|
||||
auto display = display_name_for_path(path);
|
||||
if (display.empty()) {
|
||||
return path;
|
||||
}
|
||||
return display;
|
||||
}
|
||||
|
||||
class DataFolderPathText : public Component {
|
||||
public:
|
||||
explicit DataFolderPathText(Rml::Element* parent) : Component(append(parent, "div")) {}
|
||||
|
||||
void update() override {
|
||||
const Rml::String rml = "<span class=\"data-folder-current\">Current data folder:<br/>" +
|
||||
escape(abbreviated_data_path_string()) + "</span>";
|
||||
if (rml != mCurrentRml) {
|
||||
mRoot->SetInnerRML(rml);
|
||||
mCurrentRml = rml;
|
||||
}
|
||||
Component::update();
|
||||
}
|
||||
|
||||
private:
|
||||
Rml::String mCurrentRml;
|
||||
};
|
||||
|
||||
void show_data_folder_error_modal(std::string_view message) {
|
||||
auto dismiss = [](Modal& modal) {
|
||||
mDoAud_seStartMenu(kSoundWindowClose);
|
||||
modal.pop();
|
||||
};
|
||||
push_document(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Data Folder Not Changed",
|
||||
.bodyRml = escape(message),
|
||||
.actions =
|
||||
{
|
||||
ModalAction{
|
||||
.label = "OK",
|
||||
.onPressed = dismiss,
|
||||
},
|
||||
},
|
||||
.onDismiss = dismiss,
|
||||
.icon = "warning",
|
||||
}));
|
||||
if (auto* doc = top_document()) {
|
||||
doc->focus();
|
||||
}
|
||||
}
|
||||
|
||||
void data_folder_dialog_callback(void*, const char* path, const char* error) {
|
||||
if (error != nullptr) {
|
||||
show_data_folder_error_modal(error);
|
||||
return;
|
||||
}
|
||||
if (path == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string dataPathError;
|
||||
if (data::set_custom_data_path(path, &dataPathError)) {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dataPathError.empty()) {
|
||||
dataPathError =
|
||||
fmt::format("{} could not use the selected folder as its data folder.", AppName);
|
||||
}
|
||||
show_data_folder_error_modal(dataPathError);
|
||||
}
|
||||
|
||||
const Rml::String kInternalResolutionHelpText =
|
||||
@@ -198,7 +352,7 @@ const Rml::String kShadowResolutionHelpText =
|
||||
"Configure the shadow-map resolution. Higher values improve shadow quality but increase GPU "
|
||||
"and memory usage.";
|
||||
const Rml::String kBloomHelpText =
|
||||
"Configure the post-processing bloom effect. Classic uses the original bloom pass; Dusk uses "
|
||||
"Configure the post-processing bloom effect. Classic uses the original bloom pass; Dusklight uses "
|
||||
"a higher-quality bloom pass.";
|
||||
const Rml::String kBloomBrightnessHelpText =
|
||||
"Configure bloom intensity. Higher values make bright areas glow more strongly.";
|
||||
@@ -252,6 +406,15 @@ SelectButton& config_bool_select(
|
||||
return button;
|
||||
}
|
||||
|
||||
void add_speedrun_disabled_option(Pane& leftPane, Pane& rightPane, ConfigVar<bool>& var,
|
||||
const Rml::String& key, const Rml::String& helpText) {
|
||||
config_bool_select(leftPane, rightPane, var, {
|
||||
.key = key,
|
||||
.helpText = helpText,
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
}
|
||||
|
||||
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 = {}) {
|
||||
@@ -347,9 +510,52 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
})
|
||||
.on_pressed([] { open_iso_picker(); }),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.add_rml("Set the disc image that Dusk uses to launch the game.<br/><br/>"
|
||||
pane.add_rml("Set the disc image that Dusklight uses to launch the game.<br/><br/>"
|
||||
"Changes require a restart.");
|
||||
});
|
||||
#if DUSK_CAN_CHANGE_DATA_FOLDER
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Data Folder",
|
||||
.getValue = [] { return configured_data_path_display_name(); },
|
||||
.isModified = [] { return data::is_data_path_restart_pending(); },
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.add_text("The data folder is where Dusklight stores settings, saves, "
|
||||
"logs, texture replacements, and other app data.");
|
||||
pane.add_child<DataFolderPathText>();
|
||||
#if DUSK_CAN_OPEN_DATA_FOLDER
|
||||
pane.add_button("Open Data Folder").on_pressed([] {
|
||||
if (data::open_data_path()) {
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
pane.add_button("Change Data Folder").on_pressed([] {
|
||||
const auto defaultLocation =
|
||||
io::fs_path_to_string(data::configured_data_path());
|
||||
ShowFolderSelect(&data_folder_dialog_callback, nullptr,
|
||||
aurora::window::get_sdl_window(),
|
||||
defaultLocation.empty() ? nullptr : defaultLocation.c_str());
|
||||
});
|
||||
#if defined(_WIN32)
|
||||
pane.add_button("Portable Mode").on_pressed([] {
|
||||
if (data::set_portable_data_path()) {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
pane.add_button({
|
||||
.text = "Reset to Default",
|
||||
.isDisabled = [] { return data::is_default_data_path(); },
|
||||
}).on_pressed([] {
|
||||
if (data::reset_data_path()) {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
}
|
||||
});
|
||||
pane.add_rml("Data will be migrated automatically on restart.");
|
||||
});
|
||||
#endif
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Language",
|
||||
@@ -492,7 +698,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.pauseOnFocusLost,
|
||||
{
|
||||
.key = "Pause on Focus Lost",
|
||||
.isDisabled = [] { return IsMobile; },
|
||||
.helpText = "Pause the game when window focus is lost.",
|
||||
.onChange = [](bool value) { aurora_set_pause_on_focus_lost(value); },
|
||||
.isDisabled = [] { return IsMobile || getSettings().game.speedrunMode; },
|
||||
});
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
@@ -545,7 +753,6 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
pane.add_rml(
|
||||
"<br/>Display the current framerate in a corner of the screen while playing.");
|
||||
});
|
||||
|
||||
leftPane.add_section("Resolution");
|
||||
graphics_tuner_control(*this, leftPane, rightPane,
|
||||
getSettings().game.internalResolutionScale,
|
||||
@@ -602,6 +809,10 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
{
|
||||
.key = "Enable Mini-Map Shadows",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.disableCutscenePillarboxing,
|
||||
{
|
||||
.key = "Disable Cutscene Pillarboxing",
|
||||
});
|
||||
});
|
||||
|
||||
add_tab("Input", [this](Rml::Element* content) {
|
||||
@@ -620,7 +831,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
|
||||
leftPane.add_section("Controller");
|
||||
leftPane.register_control(leftPane.add_button("Configure Controller").on_pressed([this] {
|
||||
push(std::make_unique<ControllerConfigWindow>());
|
||||
push(std::make_unique<ControllerConfigWindow>(mPrelaunch));
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
@@ -646,9 +857,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
"Free Camera Sensitivity", "Adjusts twin-stick camera sensitivity.", 50, 200, 5,
|
||||
[] { return !getSettings().game.freeCamera; });
|
||||
addOption("Invert First Person X Axis", getSettings().game.invertFirstPersonXAxis,
|
||||
"Invert horizontal movement while aiming with items or first person camera. Applies to both stick and gyro aiming.");
|
||||
"Invert horizontal movement while aiming with items or first person camera. Applies only to the control stick (the gyroscope can be inverted in Input settings).");
|
||||
addOption("Invert First Person Y Axis", getSettings().game.invertFirstPersonYAxis,
|
||||
"Invert vertical movement while aiming with items or first person camera. Applies to both stick and gyro aiming.");
|
||||
"Invert vertical movement while aiming with items or first person camera. Applies only to the control stick (the gyroscope can be inverted in Input settings).");
|
||||
|
||||
leftPane.add_section("Gyro");
|
||||
leftPane.register_control(
|
||||
@@ -723,6 +934,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
addOption("Turbo Key", getSettings().game.enableTurboKeybind,
|
||||
"Hold Tab to increase game speed by up to 4x.",
|
||||
[] { return getSettings().game.speedrunMode; });
|
||||
addOption("Reset Key (" + Rml::String{hotkeys::DO_RESET} + ")",
|
||||
getSettings().game.enableResetKeybind,
|
||||
"Press " + Rml::String{hotkeys::DO_RESET} + " to reset the game.");
|
||||
});
|
||||
|
||||
add_tab("Audio", [this](Rml::Element* content) {
|
||||
@@ -770,8 +984,8 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().audio.menuSounds,
|
||||
{
|
||||
.key = "Dusk Menu Sounds",
|
||||
.helpText = "Play sound effects when navigating the Dusk menu.",
|
||||
.key = "Dusklight Menu Sounds",
|
||||
.helpText = "Play sound effects when navigating the Dusklight menu.",
|
||||
});
|
||||
|
||||
leftPane.add_section("Tweaks");
|
||||
@@ -801,12 +1015,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
};
|
||||
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; },
|
||||
});
|
||||
add_speedrun_disabled_option(leftPane, rightPane, value, key, helpText);
|
||||
};
|
||||
|
||||
leftPane.add_section("General");
|
||||
@@ -858,12 +1067,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
"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",
|
||||
.helpText = "Autosaves the game when going to a new area, opening a dungeon door, "
|
||||
"or getting a new item.",
|
||||
});
|
||||
addSpeedrunDisabledOption("Autosave", getSettings().game.autoSave,
|
||||
"Autosaves the game when going to a new area, opening a dungeon door, "
|
||||
"or getting a new item.");
|
||||
addOption("Instant Saves", getSettings().game.instantSaves,
|
||||
"Skips the delay when writing to the Memory Card.");
|
||||
addOption("Hold B for Instant Text", getSettings().game.instantText,
|
||||
@@ -877,7 +1083,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
"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,
|
||||
addSpeedrunDisabledOption("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.");
|
||||
@@ -888,12 +1094,29 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.key = "Speedrun Mode",
|
||||
.helpText =
|
||||
"Enables speedrunning options while restricting certain gameplay modifiers.",
|
||||
.onChange = [](bool) { reset_for_speedrun_mode(); },
|
||||
.onChange =
|
||||
[](bool enabled) {
|
||||
if (enabled) {
|
||||
reset_for_speedrun_mode();
|
||||
} else {
|
||||
restore_from_speedrun_mode();
|
||||
if (getSettings().game.liveSplitEnabled) {
|
||||
speedrun::disconnectLiveSplit();
|
||||
}
|
||||
}
|
||||
for (auto& doc : get_document_stack()) {
|
||||
if (dynamic_cast<MenuBar*>(doc.get())) {
|
||||
doc = std::make_unique<MenuBar>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.liveSplitEnabled,
|
||||
{
|
||||
.key = "LiveSplit Connection",
|
||||
.helpText = "Connect to LiveSplit server on localhost:16834.",
|
||||
.helpText = "Connect to LiveSplit server on localhost:16834. For this to work you must right click LiveSplit, and turn on Control -> Start TCP Server."
|
||||
" To see IGT in LiveSplit you must change your comparison to Game Time.",
|
||||
.onChange =
|
||||
[](bool enabled) {
|
||||
if (enabled) {
|
||||
@@ -902,6 +1125,12 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
speedrun::disconnectLiveSplit();
|
||||
}
|
||||
},
|
||||
.isDisabled = [] { return IsMobile || !getSettings().game.speedrunMode; },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.showSpeedrunRTATimer,
|
||||
{
|
||||
.key = "Show RTA",
|
||||
.helpText = "Display the RTA timer. IGT is always visible.",
|
||||
.isDisabled = [] { return !getSettings().game.speedrunMode; },
|
||||
});
|
||||
});
|
||||
@@ -912,18 +1141,14 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
|
||||
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; },
|
||||
});
|
||||
add_speedrun_disabled_option(leftPane, rightPane, value, key, helpText);
|
||||
};
|
||||
|
||||
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 Seeds", getSettings().game.infiniteSeeds, "Keeps your slingshot pellets (seeds) 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,
|
||||
@@ -944,26 +1169,30 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
"Speeds up movement while wearing the Iron Boots.");
|
||||
addCheat("Can Transform Anywhere", getSettings().game.canTransformAnywhere,
|
||||
"Allows transforming even if NPCs are looking.");
|
||||
addCheat("Fast Roll", getSettings().game.fastRoll,
|
||||
"Makes Link's roll animation and movement twice as fast.");
|
||||
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.");
|
||||
addCheat("Invincible Enemies", getSettings().game.invincibleEnemies,
|
||||
"Prevents enemies from taking damage.");
|
||||
});
|
||||
|
||||
add_tab("Interface", [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("Dusk");
|
||||
leftPane.add_section("Dusklight");
|
||||
#if DUSK_CAN_OPEN_DATA_FOLDER
|
||||
leftPane.register_control(
|
||||
leftPane.add_button("Open Data Folder").on_pressed([] {
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
dusk::OpenDataFolder();
|
||||
data::open_data_path();
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.add_text(
|
||||
"Open the folder where Dusk stores settings, saves, logs, texture "
|
||||
"Open the folder where Dusklight stores settings, saves, logs, texture "
|
||||
"replacements, and other app data.");
|
||||
});
|
||||
#endif
|
||||
@@ -1045,7 +1274,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
});
|
||||
leftPane.register_control(crashReporting, rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_rml("Dusk can automatically send crash reports to the developers. Crash "
|
||||
pane.add_rml("Dusklight can automatically send crash reports to the developers. Crash "
|
||||
"reports contain the following:<br/>• Operating system version<br/>• CPU "
|
||||
"architecture<br/>• GPU model & driver version<br/>• File paths (may "
|
||||
"include account username)<br/>• Stack trace");
|
||||
@@ -1053,8 +1282,8 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
#endif
|
||||
config_bool_select(leftPane, rightPane, getSettings().backend.skipPreLaunchUI,
|
||||
{
|
||||
.key = "Skip Dusk Main Menu",
|
||||
.helpText = "When starting Dusk, skip the main menu and boot straight into the "
|
||||
.key = "Skip Dusklight Main Menu",
|
||||
.helpText = "When starting Dusklight, skip the main menu and boot straight into the "
|
||||
"game if a disc image is available.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().backend.showPipelineCompilation,
|
||||
@@ -1065,15 +1294,23 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
config_bool_select(leftPane, rightPane, getSettings().backend.checkForUpdates,
|
||||
{
|
||||
.key = "Check for Updates",
|
||||
.helpText = "Checks GitHub releases for a new Dusk version on startup.<br/><br/>"
|
||||
.helpText = "Checks GitHub releases for a new Dusklight version on startup.<br/><br/>"
|
||||
"No personal information is transmitted or collected.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.pauseOnFocusLost,
|
||||
#ifdef DUSK_DISCORD
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableDiscordPresence,
|
||||
{
|
||||
.key = "Pause On Focus Lost",
|
||||
.helpText = "Pause the game when window focus is lost.",
|
||||
.onChange = [](bool value) { aurora_set_pause_on_focus_lost(value); },
|
||||
.key = "Enable Discord Rich Presence",
|
||||
.helpText = "Enable Dusk to integrate with Discord Rich Presence. This allows Discord to show your status in-game.",
|
||||
.onChange = [](bool enabled) {
|
||||
if (enabled) {
|
||||
dusk::discord::initialize();
|
||||
} else {
|
||||
dusk::discord::shutdown();
|
||||
}
|
||||
},
|
||||
});
|
||||
#endif
|
||||
config_bool_select(leftPane, rightPane, getSettings().backend.enableAdvancedSettings,
|
||||
{
|
||||
.key = "Enable Advanced Settings",
|
||||
@@ -1090,6 +1327,18 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
}
|
||||
}
|
||||
},
|
||||
.isDisabled = [] { return getSettings().game.speedrunMode; },
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.showInputViewer,
|
||||
{
|
||||
.key = "Show Input Viewer",
|
||||
.helpText = "Display a controller input overlay while playing.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.showInputViewerGyro,
|
||||
{
|
||||
.key = "Show Gyro Input Viewer",
|
||||
.helpText = "Show gyro sensor values in the input viewer.",
|
||||
.isDisabled = [] { return !getSettings().game.showInputViewer; },
|
||||
});
|
||||
|
||||
leftPane.add_section("Game");
|
||||
@@ -1098,12 +1347,9 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.key = "Skip TV Settings Screen",
|
||||
.helpText = "Skips the TV calibration screen shown when loading a save.",
|
||||
});
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.recordingMode,
|
||||
{
|
||||
.key = "Recording Mode",
|
||||
.helpText = "Disables the game HUD and all background music.<br/><br/>Useful for "
|
||||
"recording footage.",
|
||||
});
|
||||
add_speedrun_disabled_option(leftPane, rightPane, getSettings().game.recordingMode,
|
||||
"Recording Mode",
|
||||
"Disables the game HUD and all background music.<br/><br/>Useful for recording footage.");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ bool initialize() noexcept {
|
||||
load_font("AlegreyaSC-Regular.ttf");
|
||||
load_font("AlegreyaSC-Bold.ttf");
|
||||
load_font("MaterialSymbolsRounded-Regular.ttf");
|
||||
load_font("NotoMono-Regular.ttf");
|
||||
|
||||
sInitialized = true;
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
#include "warp.hpp"
|
||||
|
||||
#include "editor.hpp"
|
||||
#include "pane.hpp"
|
||||
|
||||
#include "dusk/map_loader_definitions.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
constexpr int kMinLayer = -1;
|
||||
constexpr int kMaxLayer = 14;
|
||||
|
||||
struct WarpSelectionState {
|
||||
int regionIdx = 0;
|
||||
int mapIdx = 0;
|
||||
int roomIdx = 0;
|
||||
int pointIdx = 0;
|
||||
int layer = -1;
|
||||
bool showInternalNames = false;
|
||||
};
|
||||
|
||||
WarpSelectionState& selection_state() {
|
||||
static WarpSelectionState state;
|
||||
return state;
|
||||
}
|
||||
|
||||
const RegionEntry* selected_region(const WarpSelectionState& state) {
|
||||
if (state.regionIdx < 0 || state.regionIdx >= static_cast<int>(gameRegions.size())) {
|
||||
return nullptr;
|
||||
}
|
||||
return &gameRegions[state.regionIdx];
|
||||
}
|
||||
|
||||
const MapEntry* selected_map(const WarpSelectionState& state) {
|
||||
const auto* region = selected_region(state);
|
||||
if (region == nullptr || state.mapIdx < 0 || state.mapIdx >= static_cast<int>(region->maps.size())) {
|
||||
return nullptr;
|
||||
}
|
||||
return ®ion->maps[state.mapIdx];
|
||||
}
|
||||
|
||||
const RoomEntry* selected_room(const WarpSelectionState& state) {
|
||||
const auto* map = selected_map(state);
|
||||
if (map == nullptr || state.roomIdx < 0 || state.roomIdx >= static_cast<int>(map->mapRooms.size())) {
|
||||
return nullptr;
|
||||
}
|
||||
return &map->mapRooms[state.roomIdx];
|
||||
}
|
||||
|
||||
const s16* selected_point(const WarpSelectionState& state) {
|
||||
const auto* room = selected_room(state);
|
||||
if (room == nullptr || state.pointIdx < 0 ||
|
||||
state.pointIdx >= static_cast<int>(room->roomPoints.size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return &room->roomPoints[state.pointIdx];
|
||||
}
|
||||
|
||||
void clamp_indices(WarpSelectionState& state) {
|
||||
if (gameRegions.empty()) {
|
||||
state.regionIdx = -1;
|
||||
state.mapIdx = -1;
|
||||
state.roomIdx = -1;
|
||||
state.pointIdx = -1;
|
||||
state.layer = std::clamp(state.layer, kMinLayer, kMaxLayer);
|
||||
return;
|
||||
}
|
||||
|
||||
state.regionIdx = std::clamp(state.regionIdx, 0, static_cast<int>(gameRegions.size()) - 1);
|
||||
const auto& region = gameRegions[state.regionIdx];
|
||||
if (region.maps.empty()) {
|
||||
state.mapIdx = -1;
|
||||
state.roomIdx = -1;
|
||||
state.pointIdx = -1;
|
||||
state.layer = std::clamp(state.layer, kMinLayer, kMaxLayer);
|
||||
return;
|
||||
}
|
||||
|
||||
state.mapIdx = std::clamp(state.mapIdx, 0, static_cast<int>(region.maps.size()) - 1);
|
||||
const auto& map = region.maps[state.mapIdx];
|
||||
if (map.mapRooms.empty()) {
|
||||
state.roomIdx = -1;
|
||||
state.pointIdx = -1;
|
||||
state.layer = std::clamp(state.layer, kMinLayer, kMaxLayer);
|
||||
return;
|
||||
}
|
||||
|
||||
state.roomIdx = std::clamp(state.roomIdx, 0, static_cast<int>(map.mapRooms.size()) - 1);
|
||||
const auto& room = map.mapRooms[state.roomIdx];
|
||||
if (room.roomPoints.empty()) {
|
||||
state.pointIdx = -1;
|
||||
} else {
|
||||
state.pointIdx = std::clamp(state.pointIdx, 0, static_cast<int>(room.roomPoints.size()) - 1);
|
||||
}
|
||||
|
||||
state.layer = std::clamp(state.layer, kMinLayer, kMaxLayer);
|
||||
}
|
||||
|
||||
bool can_warp(const WarpSelectionState& state) {
|
||||
return selected_point(state) != nullptr;
|
||||
}
|
||||
|
||||
void reset_selection(WarpSelectionState& state) {
|
||||
state.roomIdx = 0;
|
||||
state.pointIdx = 0;
|
||||
state.layer = kMinLayer;
|
||||
clamp_indices(state);
|
||||
}
|
||||
|
||||
void populate_map_picker(Pane& pane, WarpSelectionState& state) {
|
||||
pane.clear();
|
||||
clamp_indices(state);
|
||||
if (state.regionIdx < 0 || state.regionIdx >= static_cast<int>(gameRegions.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
pane.add_button({
|
||||
.text = "Show Internal Names",
|
||||
.isSelected = [&state] { return state.showInternalNames; },
|
||||
})
|
||||
.on_pressed([&pane, &state] {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
state.showInternalNames = !state.showInternalNames;
|
||||
populate_map_picker(pane, state);
|
||||
});
|
||||
|
||||
pane.add_section("Maps");
|
||||
const auto& region = gameRegions[state.regionIdx];
|
||||
for (int i = 0; i < static_cast<int>(region.maps.size()); ++i) {
|
||||
pane.add_button({
|
||||
.text = stage_option_label(region.maps[i], state.showInternalNames),
|
||||
.isSelected = [i, &state] { return state.mapIdx == i; },
|
||||
})
|
||||
.on_pressed([i, &state] {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
if (state.mapIdx != i) {
|
||||
state.mapIdx = i;
|
||||
reset_selection(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WarpWindow::WarpWindow() {
|
||||
add_tab("Warp", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
auto& state = selection_state();
|
||||
clamp_indices(state);
|
||||
|
||||
leftPane.add_section("Destination");
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Region",
|
||||
.getValue =
|
||||
[&state] {
|
||||
clamp_indices(state);
|
||||
const auto* region = selected_region(state);
|
||||
return region == nullptr ? Rml::String{"None"} :
|
||||
Rml::String{region->regionName};
|
||||
},
|
||||
}),
|
||||
rightPane, [&state](Pane& pane) {
|
||||
pane.clear();
|
||||
for (int i = 0; i < static_cast<int>(gameRegions.size()); ++i) {
|
||||
pane.add_button({
|
||||
.text = gameRegions[i].regionName,
|
||||
.isSelected = [i, &state] { return state.regionIdx == i; },
|
||||
})
|
||||
.on_pressed([i, &state] {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
if (state.regionIdx != i) {
|
||||
state.regionIdx = i;
|
||||
state.mapIdx = 0;
|
||||
reset_selection(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Map",
|
||||
.getValue =
|
||||
[&state] {
|
||||
clamp_indices(state);
|
||||
const auto* map = selected_map(state);
|
||||
return map == nullptr ? Rml::String{"None"} :
|
||||
stage_option_label(*map, state.showInternalNames);
|
||||
},
|
||||
}),
|
||||
rightPane, [&state](Pane& pane) { populate_map_picker(pane, state); });
|
||||
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Room",
|
||||
.getValue = [&state] {
|
||||
clamp_indices(state);
|
||||
const auto* room = selected_room(state);
|
||||
return room == nullptr ? Rml::String{"None"} :
|
||||
fmt::format("{}", room->roomNo);
|
||||
},
|
||||
.isDisabled = [&state] {
|
||||
clamp_indices(state);
|
||||
const auto* map = selected_map(state);
|
||||
return map == nullptr || map->mapRooms.size() <= 1;
|
||||
},
|
||||
}),
|
||||
rightPane, [&state](Pane& pane) {
|
||||
pane.clear();
|
||||
clamp_indices(state);
|
||||
if (state.regionIdx < 0 || state.regionIdx >= static_cast<int>(gameRegions.size())) {
|
||||
return;
|
||||
}
|
||||
const auto& region = gameRegions[state.regionIdx];
|
||||
if (state.mapIdx < 0 || state.mapIdx >= static_cast<int>(region.maps.size())) {
|
||||
return;
|
||||
}
|
||||
const auto& map = region.maps[state.mapIdx];
|
||||
for (int i = 0; i < static_cast<int>(map.mapRooms.size()); ++i) {
|
||||
pane.add_button({
|
||||
.text = fmt::format("{}", map.mapRooms[i].roomNo),
|
||||
.isSelected = [i, &state] { return state.roomIdx == i; },
|
||||
})
|
||||
.on_pressed([i, &state] {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
if (state.roomIdx != i) {
|
||||
state.roomIdx = i;
|
||||
state.pointIdx = 0;
|
||||
clamp_indices(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Point",
|
||||
.getValue = [&state] {
|
||||
clamp_indices(state);
|
||||
const auto* point = selected_point(state);
|
||||
return point == nullptr ? Rml::String{"None"} : fmt::format("{}", *point);
|
||||
},
|
||||
.isDisabled = [&state] {
|
||||
clamp_indices(state);
|
||||
const auto* room = selected_room(state);
|
||||
return room == nullptr || room->roomPoints.size() <= 1;
|
||||
},
|
||||
}),
|
||||
rightPane, [&state](Pane& pane) {
|
||||
pane.clear();
|
||||
clamp_indices(state);
|
||||
if (state.regionIdx < 0 || state.regionIdx >= static_cast<int>(gameRegions.size())) {
|
||||
return;
|
||||
}
|
||||
const auto& region = gameRegions[state.regionIdx];
|
||||
if (state.mapIdx < 0 || state.mapIdx >= static_cast<int>(region.maps.size())) {
|
||||
return;
|
||||
}
|
||||
const auto& map = region.maps[state.mapIdx];
|
||||
if (state.roomIdx < 0 || state.roomIdx >= static_cast<int>(map.mapRooms.size())) {
|
||||
return;
|
||||
}
|
||||
const auto& room = map.mapRooms[state.roomIdx];
|
||||
for (int i = 0; i < static_cast<int>(room.roomPoints.size()); ++i) {
|
||||
pane.add_button({
|
||||
.text = fmt::format("{}", room.roomPoints[i]),
|
||||
.isSelected = [i, &state] { return state.pointIdx == i; },
|
||||
})
|
||||
.on_pressed([i, &state] {
|
||||
if (state.pointIdx != i) {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
state.pointIdx = i;
|
||||
clamp_indices(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Layer",
|
||||
.getValue = [&state] { return fmt::format("{}", state.layer); },
|
||||
}),
|
||||
rightPane, [&state](Pane& pane) {
|
||||
pane.clear();
|
||||
for (int layer = kMinLayer; layer <= kMaxLayer; ++layer) {
|
||||
pane.add_button({
|
||||
.text = fmt::format("{}", layer),
|
||||
.isSelected = [layer, &state] { return state.layer == layer; },
|
||||
})
|
||||
.on_pressed([layer, &state] {
|
||||
if (state.layer != layer) {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
state.layer = layer;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
leftPane.add_section("Action");
|
||||
leftPane.register_control(
|
||||
leftPane.add_button({
|
||||
.text = "Warp",
|
||||
.isDisabled = [&state] {
|
||||
clamp_indices(state);
|
||||
return !can_warp(state);
|
||||
},
|
||||
})
|
||||
.on_pressed([&state] {
|
||||
clamp_indices(state);
|
||||
if (!can_warp(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDoAud_seStartMenu(kSoundClick);
|
||||
const auto& region = gameRegions[state.regionIdx];
|
||||
const auto& map = region.maps[state.mapIdx];
|
||||
const auto& room = map.mapRooms[state.roomIdx];
|
||||
dComIfGp_setNextStage( map.mapFile, room.roomPoints[state.pointIdx], room.roomNo, state.layer);
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.clear();
|
||||
pane.add_text("Warp to the selected destination.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "window.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class WarpWindow : public Window {
|
||||
public:
|
||||
WarpWindow();
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -335,7 +335,7 @@ Result check_latest_github_release(std::string_view owner, std::string_view repo
|
||||
if (!currentVersion) {
|
||||
return {
|
||||
.status = Status::Failed,
|
||||
.message = fmt::format("Failed to parse Dusk version '{}'", DUSK_WC_DESCRIBE),
|
||||
.message = fmt::format("Failed to parse Dusklight version '{}'", DUSK_WC_DESCRIBE),
|
||||
.latest = std::move(latest),
|
||||
};
|
||||
}
|
||||
@@ -343,7 +343,7 @@ Result check_latest_github_release(std::string_view owner, std::string_view repo
|
||||
const bool updateAvailable = compare_version(*latestVersion, *currentVersion) > 0;
|
||||
return {
|
||||
.status = updateAvailable ? Status::UpdateAvailable : Status::UpToDate,
|
||||
.message = updateAvailable ? "Update available" : "Dusk is up to date",
|
||||
.message = updateAvailable ? "Update available" : "Dusklight is up to date",
|
||||
.latest = std::move(latest),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -792,6 +792,10 @@ static void duskExecute() {
|
||||
dComIfGs_setArrowNum(dComIfGs_getArrowMax());
|
||||
}
|
||||
|
||||
if (dusk::getSettings().game.infiniteSeeds) {
|
||||
dComIfGs_setPachinkoNum(dComIfGs_getPachinkoMax());
|
||||
}
|
||||
|
||||
if (dusk::getSettings().game.infiniteBombs) {
|
||||
dComIfGs_setBombNum(0, 99);
|
||||
dComIfGs_setBombNum(1, 99);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "f_op/f_op_overlap_req.h"
|
||||
#include "f_pc/f_pc_manager.h"
|
||||
|
||||
#include "dusk/imgui/ImGuiMenuGame.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
|
||||
void fopOvlpReq_SetPeektime(overlap_request_class*, u16);
|
||||
|
||||
|
||||
@@ -636,7 +636,7 @@ u8 mDoGph_gInf_c::isWide() {
|
||||
}
|
||||
|
||||
void mDoGph_gInf_c::setWideZoomProjection(Mtx44& m) {
|
||||
if (!isWideZoom()) {
|
||||
IF_NOT_DUSK(if (!isWideZoom())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -682,7 +682,7 @@ void mDoGph_gInf_c::setWideZoomProjection(Mtx44& m) {
|
||||
}
|
||||
|
||||
void mDoGph_gInf_c::setWideZoomLightProjection(Mtx& m) {
|
||||
if (!isWideZoom()) {
|
||||
IF_NOT_DUSK(if (!isWideZoom())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1189,14 +1189,24 @@ static void trimming(view_class* param_0, view_port_class* param_1) {
|
||||
ZoneScoped;
|
||||
UNUSED(param_0);
|
||||
|
||||
#if !TARGET_PC
|
||||
s16 y_orig = (int)param_1->y_orig & ~7;
|
||||
s16 y_orig_pos = y_orig < 0 ? 0 : y_orig;
|
||||
if ((y_orig_pos == 0) && (param_1->scissor.y_orig != param_1->y_orig ||
|
||||
(param_1->scissor.height != param_1->height)))
|
||||
#endif
|
||||
{
|
||||
#if TARGET_PC
|
||||
f32 sc_top = param_1->scissor.y_orig;
|
||||
f32 sc_bottom = param_1->scissor.y_orig + param_1->scissor.height;
|
||||
f32 sc_bottom = sc_top + param_1->scissor.height;
|
||||
|
||||
f32 sc_left = 0.0f;
|
||||
f32 sc_right = param_1->width;
|
||||
|
||||
if (!dusk::getSettings().game.disableCutscenePillarboxing) {
|
||||
sc_left = param_1->scissor.x_orig;
|
||||
sc_right = sc_left + param_1->scissor.width;
|
||||
}
|
||||
#else
|
||||
s32 sc_top = (int)param_1->scissor.y_orig;
|
||||
s32 sc_bottom = param_1->scissor.y_orig + param_1->scissor.height;
|
||||
@@ -1232,17 +1242,32 @@ static void trimming(view_class* param_0, view_port_class* param_1) {
|
||||
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, DUSK_IF_ELSE(GX_F32, GX_RGBA4), 0);
|
||||
GXSetProjection(ortho, GX_ORTHOGRAPHIC);
|
||||
GXSetCurrentMtx(0);
|
||||
GXBegin(GX_QUADS, GX_VTXFMT0, 8);
|
||||
GXBegin(GX_QUADS, GX_VTXFMT0, DUSK_IF_ELSE(16, 8));
|
||||
|
||||
#if TARGET_PC
|
||||
// top trapezoid
|
||||
GXPosition3f32(0, 0, -5);
|
||||
GXPosition3f32(param_1->width, 0, -5);
|
||||
GXPosition3f32(param_1->width, sc_top, -5);
|
||||
GXPosition3f32(0, sc_top, -5);
|
||||
GXPosition3f32(0, sc_bottom, -5);
|
||||
GXPosition3f32(param_1->width, sc_bottom, -5);
|
||||
GXPosition3f32(sc_right, sc_top, -5);
|
||||
GXPosition3f32(sc_left, sc_top, -5);
|
||||
|
||||
// bottom trapezoid
|
||||
GXPosition3f32(sc_left, sc_bottom, -5);
|
||||
GXPosition3f32(sc_right, sc_bottom, -5);
|
||||
GXPosition3f32(param_1->width, param_1->height, -5);
|
||||
GXPosition3f32(0, param_1->height, -5);
|
||||
|
||||
// left trapezoid
|
||||
GXPosition3f32(0, 0, -5);
|
||||
GXPosition3f32(sc_left, sc_top, -5);
|
||||
GXPosition3f32(sc_left, sc_bottom, -5);
|
||||
GXPosition3f32(0, param_1->height, -5);
|
||||
|
||||
// right trapezoid
|
||||
GXPosition3f32(sc_right, sc_top, -5);
|
||||
GXPosition3f32(param_1->width, 0, -5);
|
||||
GXPosition3f32(param_1->width, param_1->height, -5);
|
||||
GXPosition3f32(sc_right, sc_bottom, -5);
|
||||
#else
|
||||
GXPosition3s16(0, 0, -5);
|
||||
GXPosition3s16(FB_WIDTH, 0, -5);
|
||||
|
||||
+50
-206
@@ -48,6 +48,7 @@
|
||||
#include "SSystem/SComponent/c_API.h"
|
||||
#include "dusk/app_info.hpp"
|
||||
#include "dusk/crash_reporting.h"
|
||||
#include "dusk/data.hpp"
|
||||
#include "dusk/dusk.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/game_clock.h"
|
||||
@@ -70,6 +71,7 @@
|
||||
#include <aurora/dvd.h>
|
||||
#include <dolphin/dvd.h>
|
||||
|
||||
#include "SDL3/SDL_init.h"
|
||||
#include "SDL3/SDL_filesystem.h"
|
||||
#include "SDL3/SDL_iostream.h"
|
||||
#include "SDL3/SDL_misc.h"
|
||||
@@ -78,6 +80,7 @@
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/audio/DuskDsp.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/speedrun.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/io.hpp"
|
||||
#include "dusk/version.hpp"
|
||||
@@ -122,6 +125,7 @@ bool dusk::IsShuttingDown = false;
|
||||
bool dusk::IsGameLaunched = false;
|
||||
bool dusk::RestartRequested = false;
|
||||
std::filesystem::path dusk::ConfigPath;
|
||||
std::filesystem::path dusk::CachePath;
|
||||
#endif
|
||||
|
||||
void dusk::RequestRestart() noexcept {
|
||||
@@ -129,32 +133,6 @@ void dusk::RequestRestart() noexcept {
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
bool dusk::OpenDataFolder() {
|
||||
#if DUSK_CAN_OPEN_DATA_FOLDER
|
||||
std::error_code ec;
|
||||
std::filesystem::path path = std::filesystem::absolute(ConfigPath, ec);
|
||||
if (ec) {
|
||||
DuskLog.warn("Failed to resolve absolute data folder path '{}': {}",
|
||||
io::fs_path_to_string(ConfigPath), ec.message());
|
||||
path = ConfigPath;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
const std::string url = "file:///" + path.generic_string();
|
||||
#else
|
||||
const std::string url = "file://" + path.generic_string();
|
||||
#endif
|
||||
if (!SDL_OpenURL(url.c_str())) {
|
||||
DuskLog.warn(
|
||||
"Failed to open data folder '{}': {}", io::fs_path_to_string(path), SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
s32 LOAD_COPYDATE(void*) {
|
||||
char buffer[32];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
@@ -433,175 +411,6 @@ static void ApplyCVarOverrides(const cxxopts::OptionValue& option) {
|
||||
}
|
||||
}
|
||||
|
||||
static void migrate_directory(const std::filesystem::path& from, const std::filesystem::path& to) {
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(to, ec);
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::filesystem::recursive_directory_iterator it(
|
||||
from, std::filesystem::directory_options::skip_permission_denied, ec);
|
||||
it != std::filesystem::recursive_directory_iterator(); it.increment(ec))
|
||||
{
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto relativePath = std::filesystem::relative(it->path(), from, ec);
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto targetPath = to / relativePath;
|
||||
if (it->is_directory(ec)) {
|
||||
std::filesystem::create_directories(targetPath, ec);
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
} else if (it->is_regular_file(ec) && !std::filesystem::exists(targetPath, ec)) {
|
||||
std::filesystem::create_directories(targetPath.parent_path(), ec);
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
std::filesystem::copy_file(
|
||||
it->path(), targetPath, std::filesystem::copy_options::skip_existing, ec);
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::filesystem::path calculate_config_path() {
|
||||
#ifdef __APPLE__
|
||||
#if TARGET_OS_IOS && !TARGET_OS_TV
|
||||
const char* documentsPath = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS);
|
||||
if (!documentsPath) {
|
||||
DuskLog.fatal("Unable to get iOS Documents path: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
std::filesystem::path configPath = reinterpret_cast<const char8_t*>(documentsPath);
|
||||
|
||||
char* oldPrefPath = SDL_GetPrefPath(dusk::OrgName, dusk::AppName);
|
||||
if (oldPrefPath) {
|
||||
const std::filesystem::path oldConfigPath = reinterpret_cast<const char8_t*>(oldPrefPath);
|
||||
SDL_free(oldPrefPath);
|
||||
|
||||
std::error_code ec;
|
||||
if (oldConfigPath != configPath && std::filesystem::exists(oldConfigPath, ec)) {
|
||||
migrate_directory(oldConfigPath, configPath);
|
||||
}
|
||||
}
|
||||
|
||||
return configPath;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName);
|
||||
if (!result) {
|
||||
DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError());
|
||||
}
|
||||
|
||||
return reinterpret_cast<const char8_t*>(result);
|
||||
}
|
||||
|
||||
static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) {
|
||||
if (configDir.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path pipelineCachePath = configDir / "pipeline_cache.db";
|
||||
if (std::filesystem::exists(pipelineCachePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string sourcePathString;
|
||||
SDL_IOStream* source = nullptr;
|
||||
|
||||
const char* basePath = SDL_GetBasePath();
|
||||
if (basePath != nullptr) {
|
||||
sourcePathString = dusk::io::fs_path_to_string(
|
||||
std::filesystem::path(basePath) / "initial_pipeline_cache.db");
|
||||
source = SDL_IOFromFile(sourcePathString.c_str(), "rb");
|
||||
}
|
||||
if (source == nullptr) {
|
||||
sourcePathString = "initial_pipeline_cache.db";
|
||||
source = SDL_IOFromFile(sourcePathString.c_str(), "rb");
|
||||
}
|
||||
if (source == nullptr) {
|
||||
DuskLog.info("No bundled initial pipeline cache found");
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(configDir, ec);
|
||||
if (ec) {
|
||||
DuskLog.warn("Failed to create config directory '{}' for pipeline cache: {}",
|
||||
dusk::io::fs_path_to_string(configDir), ec.message());
|
||||
SDL_CloseIO(source);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pipelineCacheString = dusk::io::fs_path_to_string(pipelineCachePath);
|
||||
SDL_IOStream* destination = SDL_IOFromFile(pipelineCacheString.c_str(), "wb");
|
||||
if (destination == nullptr) {
|
||||
DuskLog.warn("Failed to open '{}' for seeded pipeline cache: {}", pipelineCacheString,
|
||||
SDL_GetError());
|
||||
SDL_CloseIO(source);
|
||||
return;
|
||||
}
|
||||
|
||||
bool copied = true;
|
||||
std::array<char, 64 * 1024> buffer{};
|
||||
while (true) {
|
||||
const size_t bytesRead = SDL_ReadIO(source, buffer.data(), buffer.size());
|
||||
if (bytesRead > 0) {
|
||||
size_t bytesWritten = 0;
|
||||
while (bytesWritten < bytesRead) {
|
||||
const size_t written = SDL_WriteIO(
|
||||
destination, buffer.data() + bytesWritten, bytesRead - bytesWritten);
|
||||
if (written == 0) {
|
||||
DuskLog.warn("Failed to write seeded pipeline cache '{}': {}",
|
||||
pipelineCacheString, SDL_GetError());
|
||||
copied = false;
|
||||
break;
|
||||
}
|
||||
bytesWritten += written;
|
||||
}
|
||||
}
|
||||
|
||||
if (!copied) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (bytesRead < buffer.size()) {
|
||||
if (SDL_GetIOStatus(source) == SDL_IO_STATUS_EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
DuskLog.warn(
|
||||
"Failed to read bundled pipeline cache '{}': {}", sourcePathString, SDL_GetError());
|
||||
copied = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SDL_CloseIO(destination)) {
|
||||
DuskLog.warn("Failed to close seeded pipeline cache '{}': {}",
|
||||
dusk::io::fs_path_to_string(pipelineCachePath), SDL_GetError());
|
||||
copied = false;
|
||||
}
|
||||
SDL_CloseIO(source);
|
||||
|
||||
if (!copied) {
|
||||
std::filesystem::remove(pipelineCachePath, ec);
|
||||
return;
|
||||
}
|
||||
|
||||
DuskLog.info("Seeded pipeline cache from '{}'", sourcePathString);
|
||||
}
|
||||
|
||||
static constexpr PADDefaultMapping defaultPadMapping = {
|
||||
.buttons = {
|
||||
{SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_A},
|
||||
@@ -652,6 +461,19 @@ static void LanguageInit() {
|
||||
selectedLanguage = static_cast<u8>(dusk::getSettings().game.language.getValue());
|
||||
}
|
||||
|
||||
static std::string asset_path(const char* assetName) {
|
||||
const char* basePath = SDL_GetBasePath();
|
||||
if (basePath != nullptr && basePath[0] != '\0') {
|
||||
return std::string(basePath) + "res/" + assetName;
|
||||
}
|
||||
return std::string("res/") + assetName;
|
||||
}
|
||||
|
||||
static void log_build_info() {
|
||||
DuskLog.info("Build: {} (rev {}, built {}, type {})", DUSK_WC_DESCRIBE, DUSK_WC_REVISION, DUSK_WC_DATE, DUSK_BUILD_TYPE);
|
||||
DuskLog.info("Platform: {}", DUSK_PLATFORM_NAME);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PC ENTRY POINT
|
||||
// =========================================================================
|
||||
@@ -680,7 +502,7 @@ int game_main(int argc, char* argv[]) {
|
||||
cxxopts::ParseResult parsed_arg_options;
|
||||
|
||||
try {
|
||||
cxxopts::Options arg_options("Dusk", "PC Port of The Legend of Zelda: Twilight Princess");
|
||||
cxxopts::Options arg_options("Dusklight", "PC Port of a classic adventure game");
|
||||
|
||||
arg_options.add_options()
|
||||
("l,log-level", "Log level from " + std::to_string(AuroraLogLevel::LOG_DEBUG) + " to " + std::to_string(AuroraLogLevel::LOG_FATAL), cxxopts::value<uint8_t>()->default_value("0"))
|
||||
@@ -707,22 +529,42 @@ int game_main(int argc, char* argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dusk::ConfigPath = calculate_config_path();
|
||||
const auto startupLogLevel = static_cast<AuroraLogLevel>(parsed_arg_options["log-level"].as<uint8_t>());
|
||||
dusk::InitializeFileLogging(dusk::ConfigPath, startupLogLevel);
|
||||
const auto startupLogLevel =
|
||||
static_cast<AuroraLogLevel>(parsed_arg_options["log-level"].as<uint8_t>());
|
||||
const auto dataPaths = dusk::data::initialize_data();
|
||||
dusk::ConfigPath = dataPaths.userPath;
|
||||
dusk::CachePath = dataPaths.cachePath;
|
||||
dusk::InitializeFileLogging(dusk::CachePath, startupLogLevel);
|
||||
|
||||
log_build_info();
|
||||
|
||||
dusk::config::LoadFromUserPreferences();
|
||||
if (dusk::getSettings().game.speedrunMode) {
|
||||
dusk::resetForSpeedrunMode();
|
||||
}
|
||||
ApplyCVarOverrides(parsed_arg_options["cvar"]);
|
||||
dusk::crash_reporting::initialize();
|
||||
EnsureInitialPipelineCache(dusk::ConfigPath);
|
||||
// TODO: How to handle this?
|
||||
//PADSetDefaultMapping(&defaultPadMapping, PAD_TYPE_STANDARD);
|
||||
// PADSetDefaultMapping(&defaultPadMapping, PAD_TYPE_STANDARD);
|
||||
|
||||
{
|
||||
const auto configPathString = dusk::ConfigPath.u8string();
|
||||
// Load mappings from https://github.com/mdqinc/SDL_GameControllerDB
|
||||
const auto mappingsPath = asset_path("gamecontrollerdb.txt");
|
||||
if (SDL_AddGamepadMappingsFromFile(mappingsPath.c_str()) < 0) {
|
||||
DuskLog.warn("Failed to load gamecontrollerdb.txt: {}", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
// Set SDL metadata for audio mixers and macOS "About" menu
|
||||
SDL_SetAppMetadata("Dusklight", DUSK_VERSION_STRING, "dev.twilitrealm.dusk");
|
||||
|
||||
{
|
||||
const auto userPathString = dusk::ConfigPath.u8string();
|
||||
const auto cachePathString = dusk::CachePath.u8string();
|
||||
AuroraConfig config{};
|
||||
config.appName = dusk::AppName;
|
||||
config.configPath = reinterpret_cast<const char*>(configPathString.c_str());
|
||||
config.userPath = reinterpret_cast<const char*>(userPathString.c_str());
|
||||
config.cachePath = reinterpret_cast<const char*>(cachePathString.c_str());
|
||||
config.vsync = dusk::getSettings().video.enableVsync;
|
||||
config.startFullscreen = dusk::getSettings().video.enableFullscreen;
|
||||
config.windowPosX = -1;
|
||||
@@ -743,11 +585,13 @@ int game_main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
#ifdef DUSK_DISCORD
|
||||
dusk::discord::initialize();
|
||||
if (dusk::getSettings().game.enableDiscordPresence) {
|
||||
dusk::discord::initialize();
|
||||
}
|
||||
#endif
|
||||
|
||||
VISetWindowTitle(
|
||||
fmt::format("Dusk {} [{}]", DUSK_WC_DESCRIBE, dusk::backend_name(auroraInfo.backend))
|
||||
fmt::format("Dusklight {} [{}]", DUSK_WC_DESCRIBE, dusk::backend_name(auroraInfo.backend))
|
||||
.c_str());
|
||||
|
||||
if (dusk::getSettings().video.lockAspectRatio) {
|
||||
@@ -783,7 +627,7 @@ int game_main(int argc, char* argv[]) {
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::Overlay>(), true, true);
|
||||
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), false);
|
||||
|
||||
// Invalidate a bad saved isoPath so that Dusk can't get blocked from starting up.
|
||||
// Invalidate a bad saved isoPath so that Dusklight can't get blocked from starting up.
|
||||
// This is only a metadata check; full hash verification is handled by the prelaunch UI.
|
||||
bool forcePreLaunchUI = false;
|
||||
bool saveConfigBeforePrelaunch = false;
|
||||
|
||||
Reference in New Issue
Block a user