diff --git a/extern/aurora b/extern/aurora index 6d69b7822e..63550a8375 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 6d69b7822e95ad9e82537848c968058be4fbbca5 +Subproject commit 63550a83759974dd18bc13cd420888188be9caf9 diff --git a/include/d/d_menu_dmap.h b/include/d/d_menu_dmap.h index 78c659e2cf..e50c533654 100644 --- a/include/d/d_menu_dmap.h +++ b/include/d/d_menu_dmap.h @@ -103,6 +103,10 @@ public: field_0xd98 = param_1; } +#if TARGET_PC + void resetScrollArrowMask() { field_0xdda = 0; } +#endif + /* 0xC98 */ JKRExpHeap* mpHeap; /* 0xC9C */ JKRExpHeap* mpTalkHeap; /* 0xCA0 */ STControl* mpStick; diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 5eb333c6fd..145212b0a9 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -101,6 +101,7 @@ struct UserSettings { ConfigVar infiniteOil; ConfigVar infiniteOxygen; ConfigVar infiniteRupees; + ConfigVar enableIndefiniteItemDrops; ConfigVar moonJump; ConfigVar superClawshot; ConfigVar alwaysGreatspin; diff --git a/include/dusk/time.h b/include/dusk/time.h index 948a2fc171..c43437f639 100644 --- a/include/dusk/time.h +++ b/include/dusk/time.h @@ -1,9 +1,10 @@ #ifndef DUSK_TIME_H #define DUSK_TIME_H -#include -#include #include +#include + +#include "SDL3/SDL_timer.h" #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -15,28 +16,26 @@ #include #include #include -#else -#include "SDL3/SDL_timer.h" #endif class Limiter { - using delta_clock = std::chrono::high_resolution_clock; - using duration_t = std::chrono::nanoseconds; - public: - void Reset() { m_oldTime = delta_clock::now(); } + using duration_t = Uint64; + + void Reset() { m_oldTime = SDL_GetTicksNS(); } void Sleep(duration_t targetFrameTime) { - if (targetFrameTime.count() == 0) { + if (targetFrameTime == 0) { return; } - auto start = delta_clock::now(); + const Uint64 start = SDL_GetTicksNS(); duration_t adjustedSleepTime = SleepTime(targetFrameTime); - if (adjustedSleepTime.count() > 0) { + if (adjustedSleepTime > 0) { NanoSleep(adjustedSleepTime); - duration_t overslept = TimeSince(start) - adjustedSleepTime; - if (overslept < duration_t{targetFrameTime}) { + const duration_t elapsed = TimeSince(start); + const duration_t overslept = elapsed > adjustedSleepTime ? elapsed - adjustedSleepTime : 0; + if (overslept < targetFrameTime) { m_overheadTimes[m_overheadTimeIdx] = overslept; m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size(); } @@ -45,23 +44,23 @@ public: } duration_t SleepTime(duration_t targetFrameTime) { - const auto sleepTime = duration_t{targetFrameTime} - TimeSince(m_oldTime); - m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{}) / m_overheadTimes.size(); + const duration_t elapsed = TimeSince(m_oldTime); + const duration_t sleepTime = elapsed < targetFrameTime ? targetFrameTime - elapsed : 0; + m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{0}) / + m_overheadTimes.size(); if (sleepTime > m_overhead) { return sleepTime - m_overhead; } - return duration_t{0}; + return 0; } private: - delta_clock::time_point m_oldTime; + Uint64 m_oldTime = 0; std::array m_overheadTimes{}; size_t m_overheadTimeIdx = 0; - duration_t m_overhead = duration_t{0}; + duration_t m_overhead = 0; - duration_t TimeSince(delta_clock::time_point start) { - return std::chrono::duration_cast(delta_clock::now() - start); - } + duration_t TimeSince(Uint64 start) const { return SDL_GetTicksNS() - start; } #if _WIN32 void NanoSleep(const duration_t duration) { @@ -85,9 +84,10 @@ private: LARGE_INTEGER start, current; QueryPerformanceCounter(&start); - LONGLONG ticksToWait = static_cast(duration.count() * countPerNs); - if (DWORD ms = std::chrono::duration_cast(duration).count(); ms > 1) { - ::Sleep(ms - 1); + const LONGLONG ticksToWait = static_cast(duration * countPerNs); + const Uint64 ms = duration / 1'000'000ULL; + if (ms > 1) { + ::Sleep(static_cast(ms - 1)); } do { QueryPerformanceCounter(¤t); @@ -99,7 +99,7 @@ private: } while (current.QuadPart - start.QuadPart < ticksToWait); } #else - void NanoSleep(const duration_t duration) { SDL_DelayPrecise(duration.count()); } + void NanoSleep(const duration_t duration) { SDL_DelayPrecise(duration); } #endif }; diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 889fe0ebea..b8054e2130 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -369,11 +369,11 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast(1001.0 / 30000.0)); constexpr auto RETRACE_PERIOD = FRAME_PERIOD / 2; -static void waitPrecise(Limiter& limiter, Uint64 targetNs) { - const auto sleepTime = limiter.SleepTime(std::chrono::nanoseconds(targetNs)); +static void waitPrecise(Limiter& limiter, Limiter::duration_t targetNs) { + const auto sleepTime = limiter.SleepTime(targetNs); dusk::frameUsagePct = - 100.0f * (1.0f - static_cast(sleepTime.count()) / static_cast(targetNs)); - limiter.Sleep(std::chrono::nanoseconds(targetNs)); + 100.0f * (1.0f - static_cast(sleepTime) / static_cast(targetNs)); + limiter.Sleep(targetNs); } #endif diff --git a/src/d/actor/d_a_b_bq.cpp b/src/d/actor/d_a_b_bq.cpp index 70ab84705b..637e82360e 100644 --- a/src/d/actor/d_a_b_bq.cpp +++ b/src/d/actor/d_a_b_bq.cpp @@ -2059,7 +2059,15 @@ static void demo_camera(b_bq_class* i_this) { for (int i = 0; i < 5; i++) { static u16 g_e_i[] = {0x83EB, 0x83EC, 0x83ED, 0x83EE, 0x83EF}; - dComIfGp_particle_set(g_e_i[i], &pos, NULL, NULL); + #if TARGET_PC + if (i == 0) { + static const cXyz effWideScale = {mDoGph_gInf_c::getAspect(), 1.0f, 1.0f}; + dComIfGp_particle_set(g_e_i[i], &pos, NULL, &effWideScale); + } else + #endif + { + dComIfGp_particle_set(g_e_i[i], &pos, NULL, NULL); + } } i_this->mSound.startCreatureSound(Z2SE_EN_BOSS_CONVERGE, 0, 0); diff --git a/src/d/actor/d_a_b_ob.cpp b/src/d/actor/d_a_b_ob.cpp index 5622375fec..f7aa9b1f0b 100644 --- a/src/d/actor/d_a_b_ob.cpp +++ b/src/d/actor/d_a_b_ob.cpp @@ -2725,7 +2725,16 @@ static void demo_camera(b_ob_class* i_this) { for (int i = 0; i < 5; i++) { static u16 ex_eff[] = {dPa_RM(ID_ZI_S_OI_CONVERGE_FILTER), dPa_RM(ID_ZI_S_OI_CONVERGE_FILTEROUT), dPa_RM(ID_ZI_S_OI_CONVERGE_HIDE), dPa_RM(ID_ZI_S_OI_CONVERGE_POLYGON_A), dPa_RM(ID_ZI_S_OI_CONVERGE_POLYGON_B)}; - dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &sc); + + #if TARGET_PC + if (i == 0) { + static const cXyz effWideScale = {mDoGph_gInf_c::getAspect() * 10.0f, 10.0f, 10.0f}; + dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &effWideScale); + } else + #endif + { + dComIfGp_particle_set(ex_eff[i], &room_pos, NULL, &sc); + } } i_this->mDemoCamEye.set(-4820.0f, -18600.0f, -510.0f); diff --git a/src/d/actor/d_a_e_fm.cpp b/src/d/actor/d_a_e_fm.cpp index ca01f73c29..81a1b404f0 100644 --- a/src/d/actor/d_a_e_fm.cpp +++ b/src/d/actor/d_a_e_fm.cpp @@ -1677,7 +1677,16 @@ static void demo_camera(e_fm_class* i_this) { cXyz spBC(0.0f, 0.0f, 0.0f); for (int i = 0; i < 4; i++) { static u16 g_e_i[] = {0x847B, 0x847C, 0x847D, 0x847E}; - dComIfGp_particle_set(g_e_i[i], &spBC, NULL, NULL); + + #if TARGET_PC + if (i == 0) { + static const cXyz effWideScale = {mDoGph_gInf_c::getAspect(), 1.0f, 1.0f}; + dComIfGp_particle_set(g_e_i[i], &spBC, NULL, &effWideScale); + } else + #endif + { + dComIfGp_particle_set(g_e_i[i], &spBC, NULL, NULL); + } } i_this->mDemoCamFovy = 55.0f + NREG_F(10); diff --git a/src/d/actor/d_a_obj_item.cpp b/src/d/actor/d_a_obj_item.cpp index 0db3ac3a55..6c7e82ab3e 100644 --- a/src/d/actor/d_a_obj_item.cpp +++ b/src/d/actor/d_a_obj_item.cpp @@ -390,6 +390,9 @@ void daItem_c::procMainNormal() { cLib_chaseF(&scale.z, mItemScale.z, step_z); } + #if TARGET_PC + if (!dusk::getSettings().game.enableIndefiniteItemDrops) { + #endif if (mWaitTimer == 0) { if (mDisappearTimer == 0) { deleteItem(); @@ -399,6 +402,9 @@ void daItem_c::procMainNormal() { changeDraw(); } } + #if TARGET_PC + } + #endif mCcCyl.SetC(current.pos); dComIfG_Ccsp()->Set(&mCcCyl); @@ -1058,9 +1064,16 @@ int daItem_c::CountTimer() { if (checkCountTimer()) { if (mWaitTimer > 0) { mWaitTimer--; - } else if (mDisappearTimer > 0) { + } + #if TARGET_PC + else if (!dusk::getSettings().game.enableIndefiniteItemDrops && mDisappearTimer > 0) { mDisappearTimer--; } + #else + else if (mDisappearTimer > 0) { + mDisappearTimer--; + } + #endif } cLib_calcTimer(&mBoomWindTgTimer); diff --git a/src/d/d_menu_dmap.cpp b/src/d/d_menu_dmap.cpp index 17d9193241..bb557efdac 100644 --- a/src/d/d_menu_dmap.cpp +++ b/src/d/d_menu_dmap.cpp @@ -21,6 +21,7 @@ #include "d/d_msg_string.h" #include "d/d_meter_haihai.h" #include "d/d_menu_window.h" +#include "dusk/settings.h" #include "f_op/f_op_msg_mng.h" #include "m_Do/m_Do_graphic.h" #include @@ -945,9 +946,15 @@ void dMenu_DmapBg_c::draw() { mpMeterHaihai->drawHaihai(field_0xdda, x1 + (local_224.x + local_218.x) / 2, y1 + (local_224.y + local_218.y) / 2, - -35.0f + (local_224.x - local_218.x), + -35.0f + (local_224.x - local_218.x), -35.0f + (local_224.y - local_218.y)); +#if TARGET_PC + if (!dusk::getSettings().game.enableFrameInterpolation) { + field_0xdda = 0; + } +#else field_0xdda = 0; +#endif } dMenu_Dmap_c::myclass->drawFloorScreenTop(mFloorScreen, field_0xd94, field_0xd98, grafContext); @@ -2574,6 +2581,11 @@ void dMenu_Dmap_c::zoomIn_proc() { } void dMenu_Dmap_c::zoomOut_init_proc() { +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + mpDrawBg->resetScrollArrowMask(); + } +#endif Z2GetAudioMgr()->seStart(Z2SE_SY_MAP_ZOOMOUT, NULL, 0, 0, 1.0f, 1.0f, -1.0f, -1.0f, 0); mMapCtrl->initZoomOut(10); mpDrawBg->iconScaleAnmInit(1.0f, 0.0f, 10); diff --git a/src/dusk/gyro.cpp b/src/dusk/gyro.cpp index 5b6e880a1f..abe22909c1 100644 --- a/src/dusk/gyro.cpp +++ b/src/dusk/gyro.cpp @@ -1,16 +1,29 @@ #include "dusk/gyro.h" #include "d/actor/d_a_alink.h" +#include namespace dusk::gyro { namespace { constexpr s32 kRollgoalTableMaxOffset = 6500; constexpr float kGyroEmaAlphaMin = 0.05f; constexpr float kGyroEmaAlphaMax = 1.0f; +// Smooth gravity separately so the yaw/roll blend doesn't twitch with raw accel noise. +constexpr float kGravityEmaAlpha = 0.1f; +constexpr float kMinGravityProjection = 0.2f; +// Let roll contribute more strongly as the pad approaches an upright posture. +constexpr float kRollAimBoostMax = 2.0f; bool s_sensor_enabled = false; +bool s_accel_enabled = false; +bool s_was_aiming = false; +bool s_have_gravity_baseline = false; float s_smooth_gx = 0.0f; float s_smooth_gy = 0.0f; float s_smooth_gz = 0.0f; +float s_gravity_y = 0.0f; +float s_gravity_z = 0.0f; +float s_baseline_gravity_y = 0.0f; +float s_baseline_gravity_z = 0.0f; float s_yaw_rad = 0.0f; float s_pitch_rad = 0.0f; float s_roll_rad = 0.0f; @@ -19,6 +32,10 @@ s32 s_rollgoal_az = 0; void reset_filter_state() { s_smooth_gx = s_smooth_gy = s_smooth_gz = 0.0f; + s_gravity_y = s_gravity_z = 0.0f; + s_baseline_gravity_y = s_baseline_gravity_z = 0.0f; + s_was_aiming = false; + s_have_gravity_baseline = false; s_yaw_rad = s_pitch_rad = s_roll_rad = 0.0f; s_rollgoal_ax = s_rollgoal_az = 0; } @@ -49,15 +66,30 @@ bool queryGyroAimContext() { } void read(float dt) { - if (!s_sensor_keep_alive && !queryGyroAimContext()) { + const bool aim_active = queryGyroAimContext(); + const bool aim_just_started = aim_active && !s_was_aiming; + const bool aim_just_ended = !aim_active && s_was_aiming; + s_was_aiming = aim_active; + + if (!s_sensor_keep_alive && !aim_active) { if (s_sensor_enabled) { PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, FALSE); s_sensor_enabled = false; } + if (s_accel_enabled) { + PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_ACCEL, FALSE); + s_accel_enabled = false; + } reset_filter_state(); return; } + if (aim_just_started || aim_just_ended) { + s_gravity_y = s_gravity_z = 0.0f; + s_baseline_gravity_y = s_baseline_gravity_z = 0.0f; + s_have_gravity_baseline = false; + } + if (!s_sensor_enabled) { if (!PADHasSensor(PAD_CHAN0, PAD_SENSOR_GYRO)) { return; @@ -68,6 +100,13 @@ void read(float dt) { s_sensor_enabled = true; } + if (!s_accel_enabled && PADHasSensor(PAD_CHAN0, PAD_SENSOR_ACCEL) && + PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_ACCEL, TRUE)) + { + // We only need accel for the gravity-aware yaw/roll mix. + s_accel_enabled = true; + } + f32 gyro[3]; if (!PADGetSensorData(PAD_CHAN0, PAD_SENSOR_GYRO, gyro, 3)) { return; @@ -80,9 +119,50 @@ void read(float dt) { s_smooth_gy += smooth_alpha * (gyro[1] - s_smooth_gy); s_smooth_gz += smooth_alpha * (gyro[2] - s_smooth_gz); - s_pitch_rad = -apply_deadband(s_smooth_gx, deadband) * dt * dusk::getSettings().game.gyroSensitivityX; - s_yaw_rad = apply_deadband(s_smooth_gy, deadband) * dt * dusk::getSettings().game.gyroSensitivityY; - s_roll_rad = apply_deadband(s_smooth_gz, deadband) * dt * dusk::getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X + const float pitch_rate = apply_deadband(s_smooth_gx, deadband); + const float yaw_rate = apply_deadband(s_smooth_gy, deadband); + const float roll_rate = apply_deadband(s_smooth_gz, deadband); + + s_pitch_rad = -pitch_rate * dt * dusk::getSettings().game.gyroSensitivityX; + s_roll_rad = roll_rate * dt * dusk::getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X + + float horizontal_rate = yaw_rate; + if (aim_active && s_accel_enabled) { + f32 accel[3]; + if (PADGetSensorData(PAD_CHAN0, PAD_SENSOR_ACCEL, accel, 3)) { + if (!s_have_gravity_baseline) { + s_gravity_y = accel[1]; + s_gravity_z = accel[2]; + } else { + s_gravity_y += kGravityEmaAlpha * (accel[1] - s_gravity_y); + s_gravity_z += kGravityEmaAlpha * (accel[2] - s_gravity_z); + } + + // Compare the current gravity projection against the gravity vector from + // aim start so the user's resting hold angle becomes the neutral baseline. + const float gravity_yz_len = std::sqrt((s_gravity_y * s_gravity_y) + (s_gravity_z * s_gravity_z)); + if (gravity_yz_len >= kMinGravityProjection) { + const float current_gravity_y = s_gravity_y / gravity_yz_len; + const float current_gravity_z = s_gravity_z / gravity_yz_len; + + if (!s_have_gravity_baseline) { + s_baseline_gravity_y = current_gravity_y; + s_baseline_gravity_z = current_gravity_z; + s_have_gravity_baseline = true; + } + + const float yaw_weight = + (s_baseline_gravity_y * current_gravity_y) + (s_baseline_gravity_z * current_gravity_z); + const float roll_weight = + (s_baseline_gravity_y * current_gravity_z) - (s_baseline_gravity_z * current_gravity_y); + const float roll_mix = std::fabs(roll_weight); + const float roll_boost = 1.0f + (roll_mix * (kRollAimBoostMax - 1.0f)); + horizontal_rate = (yaw_rate * yaw_weight) + (roll_rate * roll_weight * roll_boost); + } + } + } + + s_yaw_rad = horizontal_rate * dt * dusk::getSettings().game.gyroSensitivityY; s_pitch_rad = dusk::getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad; s_yaw_rad = dusk::getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad; diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 0b32fd5078..085b34dc2d 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -305,6 +305,10 @@ namespace dusk { ImGuiMenuGame::ToggleFullscreen(); } + if (ImGui::IsKeyPressed(ImGuiKey_Escape) && getSettings().video.enableFullscreen) { + ImGuiMenuGame::ToggleFullscreen(); + } + if (!dusk::IsGameLaunched) { m_preLaunchWindow.draw(); } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 448bd531ff..c55836c68d 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -288,6 +288,8 @@ namespace dusk { config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil); config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen); config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees); + config::ImGuiCheckbox("Items Don't Despawn", getSettings().game.enableIndefiniteItemDrops); + ImGui::SetItemTooltip("Items Don't Despawn Unless You Load A Different Room In Which Case They Do But Even Under Some Circumstances They Don't, It Is Quite Rare Though"); ImGui::SeparatorText("Abilities"); config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index aacca0dbc9..28397f80f2 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -75,6 +75,7 @@ UserSettings g_userSettings = { .infiniteOil{"game.infiniteOil", false}, .infiniteOxygen{"game.infiniteOxygen", false}, .infiniteRupees{"game.infiniteRupees", false}, + .enableIndefiniteItemDrops {"game.enableIndefiniteItemDrops", false}, .moonJump{"game.moonJump", false}, .superClawshot{"game.superClawshot", false}, .alwaysGreatspin{"game.alwaysGreatspin", false}, @@ -160,6 +161,7 @@ void registerSettings() { Register(g_userSettings.game.infiniteOil); Register(g_userSettings.game.infiniteOxygen); Register(g_userSettings.game.infiniteRupees); + Register(g_userSettings.game.enableIndefiniteItemDrops); Register(g_userSettings.game.moonJump); Register(g_userSettings.game.superClawshot); Register(g_userSettings.game.alwaysGreatspin);