diff --git a/CMakeLists.txt b/CMakeLists.txt index cb9b6bf24c..d81877925b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,7 +297,7 @@ endif () # Edit & Continue if (MSVC) - if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL Debug) + if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug") set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue") endif () if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue") diff --git a/extern/aurora b/extern/aurora index 4dd23c74d8..aa83f6d915 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 4dd23c74d8bac5b1ba8be6ccb0270dc1bcb37ef9 +Subproject commit aa83f6d91545c304b8f62a2965c7dcd1ec8b511b diff --git a/files.cmake b/files.cmake index 2178c1066c..e91415b3be 100644 --- a/files.cmake +++ b/files.cmake @@ -1344,7 +1344,7 @@ set(DUSK_FILES src/dusk/extras.cpp src/dusk/frame_interpolation.cpp src/dusk/globals.cpp - src/dusk/gyro_aim.cpp + src/dusk/gyro.cpp src/dusk/io.cpp src/dusk/layout.cpp src/dusk/logging.cpp @@ -1358,6 +1358,8 @@ set(DUSK_FILES src/dusk/imgui/ImGuiEngine.hpp src/dusk/imgui/ImGuiMenuGame.cpp src/dusk/imgui/ImGuiMenuGame.hpp + src/dusk/imgui/ImGuiBloomWindow.cpp + src/dusk/imgui/ImGuiBloomWindow.hpp src/dusk/imgui/ImGuiMenuTools.cpp src/dusk/imgui/ImGuiMenuTools.hpp src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -1376,6 +1378,7 @@ set(DUSK_FILES src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.cpp + src/dusk/iso_validate.cpp src/dusk/offset_ptr.cpp src/dusk/OSContext.cpp src/dusk/OSThread.cpp diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 3aadfe8520..383d16d288 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -6,7 +6,6 @@ #include #include -struct cXyz; class camera_process_class; #ifdef __cplusplus @@ -15,7 +14,6 @@ namespace frame_interp { void ensure_initialized(); -void begin_record_camera(); void begin_record(); void end_record(); void interpolate(float step); @@ -39,8 +37,17 @@ void record_final_mtx_raw_tagged(const Mtx* dest, const Mtx src, uint64_t stable bool lookup_replacement(const void* source, Mtx out); bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); -void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye); -bool build_star_view(Mtx o_view, Mtx o_cam_billboard_base, cXyz* o_anchor_eye, float* o_fovy); +void begin_presentation_camera(); +void end_presentation_camera(); + +struct PresentationCameraScope { + PresentationCameraScope() { begin_presentation_camera(); } + ~PresentationCameraScope() { end_presentation_camera(); } + PresentationCameraScope(const PresentationCameraScope&) = delete; + PresentationCameraScope& operator=(const PresentationCameraScope&) = delete; + PresentationCameraScope(PresentationCameraScope&&) = delete; + PresentationCameraScope& operator=(PresentationCameraScope&&) = delete; +}; uint64_t alloc_simple_shadow_pair_base(); } // namespace frame_interp diff --git a/include/dusk/gyro.h b/include/dusk/gyro.h new file mode 100644 index 0000000000..4ee1c61737 --- /dev/null +++ b/include/dusk/gyro.h @@ -0,0 +1,17 @@ +#ifndef DUSK_GYRO_H +#define DUSK_GYRO_H + +namespace dusk::gyro { +void read(float dt); +void getAimDeltas(float& out_yaw, float& out_pitch); +bool queryGyroAimItemContext(); + +void rollgoalTick(bool play_active, s16 camera_yaw); +void rollgoalTableOffset(s16& out_ax, s16& out_az); + +extern bool s_sensor_keep_alive; +bool get_sensor_keep_alive(); +void set_sensor_keep_alive(bool value); +} // namespace dusk::gyro + +#endif diff --git a/include/dusk/gyro_aim.h b/include/dusk/gyro_aim.h deleted file mode 100644 index aafc926811..0000000000 --- a/include/dusk/gyro_aim.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef DUSK_GYRO_AIM_H -#define DUSK_GYRO_AIM_H - -namespace dusk::gyro_aim { -void read(float dt, bool context_active); -void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad); -bool queryGyroAimItemContext(); -} // namespace dusk::gyro_aim - -#endif diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 67dce3c067..2676c461cd 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -81,10 +81,14 @@ struct UserSettings { // Input ConfigVar enableGyroAim; - ConfigVar gyroAimSensitivityX; - ConfigVar gyroAimSensitivityY; - ConfigVar gyroAimInvertPitch; - ConfigVar gyroAimInvertYaw; + ConfigVar enableGyroRollgoal; + ConfigVar gyroSensitivityX; + ConfigVar gyroSensitivityY; + ConfigVar gyroSensitivityRollgoal; + ConfigVar gyroSmoothing; + ConfigVar gyroDeadband; + ConfigVar gyroInvertPitch; + ConfigVar gyroInvertYaw; // Cheats ConfigVar enableFastIronBoots; @@ -106,6 +110,7 @@ struct UserSettings { ConfigVar showPipelineCompilation; ConfigVar wasPresetChosen; ConfigVar enableCrashReporting; + ConfigVar duskMenuOpen; } backend; }; diff --git a/src/d/actor/d_a_alink_link.inc b/src/d/actor/d_a_alink_link.inc index 4f8f5fa7b6..6de02a7598 100644 --- a/src/d/actor/d_a_alink_link.inc +++ b/src/d/actor/d_a_alink_link.inc @@ -11,7 +11,7 @@ #include "d/actor/d_a_tag_mhint.h" #if TARGET_PC -#include "dusk/gyro_aim.h" +#include "dusk/gyro.h" #endif bool daAlink_c::checkNoSubjectModeCamera() { @@ -142,17 +142,7 @@ BOOL daAlink_c::setBodyAngleToCamera() { f32 gy_yaw = 0.f; f32 gy_pitch = 0.f; - dusk::gyro_aim::consumeAimDeltas(gy_yaw, gy_pitch); - - if (dusk::getSettings().game.gyroAimInvertPitch) { - gy_pitch = -gy_pitch; - } - if (dusk::getSettings().game.gyroAimInvertYaw) { - gy_yaw = -gy_yaw; - } - if (dusk::getSettings().game.enableMirrorMode) { - gy_yaw = -gy_yaw; - } + dusk::gyro::getAimDeltas(gy_yaw, gy_pitch); shape_angle.y = shape_angle.y + cM_rad2s(gy_yaw * gyro_scale); sp8 = sp8 + cM_rad2s(gy_pitch * gyro_scale); diff --git a/src/d/actor/d_a_dshutter.cpp b/src/d/actor/d_a_dshutter.cpp index 4a0cf397f4..fc122cd4e3 100644 --- a/src/d/actor/d_a_dshutter.cpp +++ b/src/d/actor/d_a_dshutter.cpp @@ -215,7 +215,16 @@ int daDsh_c::create() { mType = getType(); +#ifdef TARGET_PC + const char* l_resName[] = {l_arcName[mType], ""}; +#else + // !@bug By making this static, it is only initialized the first time it runs + // If gate types that use other arcs are loaded later (without reloading the code) + // this array never gets updated and will load the incorrect arc + // On GC/Wii, REL loading causes this to reset/reinitialize so the bug is avoided + // but TPHD is all statically linked so daDsh_c::CreateHeap fails to get model data and the gate unloads static const char* l_resName[] = {l_arcName[mType], ""}; +#endif int phase = mResLoader.load(l_resName, NULL); if (phase == cPhs_COMPLEATE_e) { diff --git a/src/d/actor/d_a_mg_fshop.cpp b/src/d/actor/d_a_mg_fshop.cpp index cb9cc12dea..1c93648eac 100644 --- a/src/d/actor/d_a_mg_fshop.cpp +++ b/src/d/actor/d_a_mg_fshop.cpp @@ -14,6 +14,10 @@ #include "d/d_s_play.h" #include "Z2AudioLib/Z2Instances.h" +#if TARGET_PC +#include "dusk/gyro.h" +#endif + enum koro2_parts { KORO2_PART_BOX = 1, KORO2_PART_CURVE_A_U_L, @@ -723,6 +727,14 @@ static void koro2_game(fshop_class* i_this) { cLib_addCalcAngleS2(&i_this->field_0x4020.x, 0, 2, 0x200); cLib_addCalcAngleS2(&i_this->field_0x4020.z, 0, 2, 0x200); case 2: +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + if (!dusk::gyro::get_sensor_keep_alive()) { + dusk::gyro::set_sensor_keep_alive(true); + } + } +#endif + actor->scale.x = 10.0f; if (i_this->field_0x4010 == 2) { static f32 old_stick_x = 0.0f; @@ -739,6 +751,11 @@ static void koro2_game(fshop_class* i_this) { old_stick_x = mDoCPd_c::getSubStickX(PAD_1); cLib_addCalcAngleS2(&i_this->field_0x4060, i_this->field_0x4062, 4, 0x1000); +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + dusk::gyro::rollgoalTick(true, i_this->field_0x4060); + } +#endif cMtx_YrotS(*calc_mtx, -i_this->field_0x4060); sp5C.x = mDoCPd_c::getStickX3D(PAD_1); @@ -765,9 +782,26 @@ static void koro2_game(fshop_class* i_this) { reg_f30 = 0.0f; } +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + s16 rg_add_x; + s16 rg_add_z; + dusk::gyro::rollgoalTableOffset(rg_add_x, rg_add_z); + s16 tgt_x = static_cast(reg_f30 * (-6000.0f + JREG_F(7))) + rg_add_x; + s16 tgt_z = static_cast(reg_f31 * (-6000.0f + JREG_F(8))) + rg_add_z; + cLib_addCalcAngleS2(&i_this->field_0x4020.x, tgt_x, 4, 0x200); + cLib_addCalcAngleS2(&i_this->field_0x4020.z, tgt_z, 4, 0x200); + } +#else cLib_addCalcAngleS2(&i_this->field_0x4020.x, reg_f30 * (-6000.0f + JREG_F(7)), 4, 0x200); cLib_addCalcAngleS2(&i_this->field_0x4020.z, reg_f31 * (-6000.0f + JREG_F(8)), 4, 0x200); +#endif } +#if TARGET_PC + if (i_this->field_0x4010 != 2) { + dusk::gyro::rollgoalTick(false, i_this->field_0x4060); + } +#endif break; } @@ -1145,6 +1179,12 @@ static int daFshop_Delete(fshop_class* i_this) { } } +#if TARGET_PC + if (dusk::getSettings().game.enableGyroRollgoal) { + dusk::gyro::set_sensor_keep_alive(false); + } +#endif + return 1; } diff --git a/src/d/actor/d_a_myna.cpp b/src/d/actor/d_a_myna.cpp index b23845a922..776f219f93 100644 --- a/src/d/actor/d_a_myna.cpp +++ b/src/d/actor/d_a_myna.cpp @@ -363,6 +363,18 @@ int daMyna_c::destroy() { mpMorf->stopZelAnime(); } +#ifdef TARGET_PC + // !@bug d_a_myna.rel unload used to zero these file-statics; with static linking they dangle across scenes. + daMyna_LightActor = NULL; + daMyna_evtTagActor0 = NULL; + daMyna_evtTagActor1 = NULL; + daMyna_actor_count = 0; + for (int i = 0; i < 10; i++) { + daMyna_targetActor[i] = NULL; + daMyna_subActor[i] = NULL; + } +#endif + #if DEBUG l_HOSTIO.removeHIO(); #endif diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 7b95a0d8ea..b93d569003 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -11079,7 +11079,6 @@ static int camera_draw(camera_process_class* i_this) { &process->view.lookat.up, process->view.bank); #ifdef TARGET_PC dusk::frame_interp::record_camera(process, camera_id); - dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(process->view.viewMtx), process->view.viewMtx); #endif #if WIDESCREEN_SUPPORT diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 60e4d619a4..9bb96efc82 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -33,6 +33,7 @@ #include #include #if TARGET_PC +#include "dusk/imgui/ImGuiBloomWindow.hpp" #include "dusk/settings.h" #endif @@ -2578,6 +2579,10 @@ void dScnKy_env_light_c::setLight() { mDoGph_gInf_c::getBloom()->setMode(mode); } +#if TARGET_PC + dusk::ApplyBloomOverride(); +#endif + f32 var_f30; if (dKy_Outdoor_check() == true) { static f32 now_shadow_alpha[] = {0.25f, 0.35f, 0.6f, 0.6f, 0.25f, 0.35f}; diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 953768a525..31ba59e33e 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -4110,54 +4110,20 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { color_reg0.b = 0xFF; color_reg0.a = 0xFF; -#if TARGET_PC - Mtx star_gx_view; - cXyz anchor_eye; - f32 star_fovy = 45.0f; - MtxP gx_load_view = drawMtx; - bool star_use_present_view = false; - - if (dusk::getSettings().game.enableFrameInterpolation) { - star_use_present_view = dusk::frame_interp::build_star_view(star_gx_view, camMtx, &anchor_eye, &star_fovy); - } - - if (star_use_present_view) { - gx_load_view = star_gx_view; - } else { - if (dComIfGd_getView() != NULL) { - MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); - anchor_eye = camera->view.lookat.eye; - star_fovy = dComIfGd_getView()->fovy; - } else { - return; - } - } -#else if (dComIfGd_getView() != NULL) { MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); } else { return; } -#endif if (strcmp(dComIfGp_getStartStageName(), "F_SP200") == 0 && dComIfG_play_c::getLayerNo(0) == 0) { moon_pos = envlight->moon_pos; } else { -#if TARGET_PC - moon_pos = anchor_eye + envlight->moon_pos; -#else moon_pos = camera->view.lookat.eye + envlight->moon_pos; -#endif if (sp38) { -#if TARGET_PC - moon_pos.x = 3900.0f + anchor_eye.x; - moon_pos.y = 8052.0f + anchor_eye.y; - moon_pos.z = -9072.0f + anchor_eye.z; -#else moon_pos.x = 3900.0f + camera->view.lookat.eye.x; moon_pos.y = 8052.0f + camera->view.lookat.eye.y; moon_pos.z = -9072.0f + camera->view.lookat.eye.z; -#endif } } @@ -4193,11 +4159,7 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); -#if TARGET_PC - GXLoadPosMtxImm(gx_load_view, GX_PNMTX0); -#else GXLoadPosMtxImm(drawMtx, GX_PNMTX0); -#endif GXSetCurrentMtx(GX_PNMTX0); rot += 0.65f; @@ -4205,23 +4167,12 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { rot = 0.0f; } -#if TARGET_PC - spBC = anchor_eye; -#else spBC.x = camera->view.lookat.eye.x; spBC.y = camera->view.lookat.eye.y; spBC.z = camera->view.lookat.eye.z; -#endif f32 sp34 = -1.0f; int sp30 = 0; -#if TARGET_PC - f32 var_f30 = star_fovy / 45.0f; - if (var_f30 >= 1.0f) { - var_f30 = 1.0f; - } - var_f30 = 1.0f - var_f30; -#else f32 var_f30 = 0.0f; if (dComIfGd_getView() != NULL) { @@ -4231,7 +4182,6 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { } var_f30 = 1.0f - var_f30; } -#endif f32 temp_f27 = 0.28f * (1.0f - var_f30); sp98.x = 0.0f; diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index f7ce0f1a51..a31ccdedfd 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -456,6 +456,12 @@ void dRenderingFDAmap_c::preRenderingMap() { GXSetClipMode(GX_CLIP_ENABLE); setTevSettingNonTextureDirectColor(); f32 right = field_0x8 * 0.5f; +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + right = field_0x8 * -0.5f; + } +#endif + f32 top = field_0xc * 0.5f; Mtx44 matrix; C_MTXOrtho(matrix, top, -top, -right, right, 0.0f, 10000.0f); diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index 0fa7d036e0..e3de48bcfc 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -1,12 +1,7 @@ -#include "f_op/f_op_camera_mng.h" #include "dusk/frame_interpolation.h" -#include -#include -#include #include -#include -#include +#include "f_op/f_op_camera_mng.h" namespace { enum class Op : uint8_t { @@ -80,6 +75,36 @@ std::vector g_current_path; std::unordered_map g_replacements; +struct CameraSnapshot { + cXyz eye{}; + cXyz center{}; + cXyz up{}; + s16 bank{}; + f32 fovy{}; + f32 aspect{}; + f32 near_{}; + f32 far_{}; + bool valid{}; +}; + +CameraSnapshot s_cam_prev{}; +CameraSnapshot s_cam_curr{}; + +view_class s_presentation_view_backup{}; +int s_presentation_depth = 0; + +void copy_view_to_snap(CameraSnapshot* dst, const view_class& v) { + dst->eye = v.lookat.eye; + dst->center = v.lookat.center; + dst->up = v.lookat.up; + dst->bank = v.bank; + dst->fovy = v.fovy; + dst->aspect = v.aspect; + dst->near_ = v.near_; + dst->far_ = v.far_; + dst->valid = true; +} + inline void copy_matrix(const Mtx src, Mtx dst) { MTXCopy(src, dst); } @@ -104,6 +129,12 @@ inline void lerp_xyz(cXyz* out, const cXyz& lhs, const cXyz& rhs, float step) { out->z = lhs.z * old_weight + rhs.z * step; } +static s16 lerp_bank(s16 a, s16 b, f32 t) { + const f32 ra = S2RAD(a); + const f32 d = remainderf(S2RAD(b) - ra, 2.0f * static_cast(M_PI)); + return cAngle::Radian_to_SAngle(ra + d * t); +} + inline bool matrix_differs(const Mtx lhs, const Mtx rhs, float epsilon = 0.0001f) { for (size_t row = 0; row < 3; ++row) { for (size_t col = 0; col < 4; ++col) { @@ -287,6 +318,7 @@ void ensure_initialized() { void begin_record() { ensure_initialized(); + if (!g_enabled) { g_interpolating = false; g_sync_presentation = false; @@ -294,6 +326,8 @@ void begin_record() { g_current_recording = {}; g_current_path.clear(); clear_replacements(); + s_cam_prev.valid = false; + s_cam_curr.valid = false; return; } @@ -305,6 +339,15 @@ void begin_record() { g_recording = true; g_interpolating = false; clear_replacements(); + + ::camera_process_class* cam = dComIfGp_getCamera(0); + if (cam == nullptr) { + s_cam_prev.valid = false; + s_cam_curr.valid = false; + return; + } else { + copy_view_to_snap(&s_cam_prev, cam->view); + } } void end_record() { @@ -454,84 +497,126 @@ bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { return true; } -// TODO: Is there already a built-in function for this? -void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye) { - o_eye->x = -(view_mtx[0][0] * view_mtx[0][3] + view_mtx[1][0] * view_mtx[1][3] + view_mtx[2][0] * view_mtx[2][3]); - o_eye->y = -(view_mtx[0][1] * view_mtx[0][3] + view_mtx[1][1] * view_mtx[1][3] + view_mtx[2][1] * view_mtx[2][3]); - o_eye->z = -(view_mtx[0][2] * view_mtx[0][3] + view_mtx[1][2] * view_mtx[1][3] + view_mtx[2][2] * view_mtx[2][3]); -} - -namespace { -struct CamSnap { - cXyz eye{}; - cXyz center{}; - cXyz up{}; - s16 bank{}; - f32 fovy{}; - bool valid{}; -}; - -CamSnap s_star_prev{}; -CamSnap s_star_curr{}; - -static void copy_view_to_snap(CamSnap* dst, const view_class& v) { - dst->eye = v.lookat.eye; - dst->center = v.lookat.center; - dst->up = v.lookat.up; - dst->bank = v.bank; - dst->fovy = v.fovy; - dst->valid = true; -} - -static void billboard_base_from_view(MtxP view_mtx, MtxP o_cam_billboard_base) { - Mtx rot; - MTXCopy(view_mtx, rot); - rot[0][3] = rot[1][3] = rot[2][3] = 0.0f; - MTXInverse(rot, o_cam_billboard_base); -} -} // namespace - -void begin_record_camera() { - ::camera_process_class* cam = dComIfGp_getCamera(0); - if (cam == nullptr) { - return; - } - copy_view_to_snap(&s_star_prev, cam->view); -} - void record_camera(::camera_process_class* cam, int camera_id) { - if (!getSettings().game.enableFrameInterpolation || camera_id != 0 || cam == nullptr) { + if (!g_enabled || camera_id != 0 || cam == nullptr) { return; } - copy_view_to_snap(&s_star_curr, cam->view); + copy_view_to_snap(&s_cam_curr, cam->view); } -bool build_star_view(Mtx o_view, Mtx o_cam_billboard_base, cXyz* o_anchor_eye, float* o_fovy) { - if (!getSettings().game.enableFrameInterpolation || !s_star_prev.valid || !s_star_curr.valid) { - return false; +void begin_presentation_camera() { + ensure_initialized(); + if (!g_enabled) { + return; } + if (s_presentation_depth > 0) { + s_presentation_depth++; + return; + } + if (!s_cam_prev.valid || !s_cam_curr.valid) { + return; + } + + view_class* const view = dComIfGd_getView(); + if (view == nullptr) { + return; + } + + std::memcpy(&s_presentation_view_backup, view, sizeof(view_class)); const f32 step = get_interpolation_step(); cXyz eye; cXyz center; cXyz up; - lerp_xyz(&eye, s_star_prev.eye, s_star_curr.eye, step); - lerp_xyz(¢er, s_star_prev.center, s_star_curr.center, step); - lerp_xyz(&up, s_star_prev.up, s_star_curr.up, step); + lerp_xyz(&eye, s_cam_prev.eye, s_cam_curr.eye, step); + lerp_xyz(¢er, s_cam_prev.center, s_cam_curr.center, step); + lerp_xyz(&up, s_cam_prev.up, s_cam_curr.up, step); if (!up.normalizeRS()) { - up = s_star_curr.up; + up = s_cam_curr.up; up.normalizeRS(); } - const f32 bank_rad = S2RAD(s_star_prev.bank) * (1.0f - step) + S2RAD(s_star_curr.bank) * step; - const s16 bank = cAngle::Radian_to_SAngle(bank_rad); + view->lookat.eye = eye; + view->lookat.center = center; + view->lookat.up = up; + view->bank = lerp_bank(s_cam_prev.bank, s_cam_curr.bank, step); + view->fovy = s_cam_prev.fovy + (s_cam_curr.fovy - s_cam_prev.fovy) * step; + view->aspect = s_cam_prev.aspect + (s_cam_curr.aspect - s_cam_prev.aspect) * step; + view->near_ = s_cam_prev.near_ + (s_cam_curr.near_ - s_cam_prev.near_) * step; + view->far_ = s_cam_prev.far_ + (s_cam_curr.far_ - s_cam_prev.far_) * step; - mDoMtx_lookAt(o_view, &eye, ¢er, &up, bank); - billboard_base_from_view(o_view, o_cam_billboard_base); + // FRAME INTERP TODO: Largely copied from d_camera's camera_draw function from this point, got any better ideas? + C_MTXPerspective(view->projMtx, view->fovy, view->aspect, view->near_, view->far_); + mDoMtx_lookAt(view->viewMtx, &view->lookat.eye, &view->lookat.center, &view->lookat.up, view->bank); +#if WIDESCREEN_SUPPORT + mDoGph_gInf_c::setWideZoomProjection(view->projMtx); +#endif + j3dSys.setViewMtx(view->viewMtx); + cMtx_inverse(view->viewMtx, view->invViewMtx); - *o_anchor_eye = eye; - *o_fovy = s_star_prev.fovy + (s_star_curr.fovy - s_star_prev.fovy) * step; - return true; + bool camera_attention_status = dComIfGp_getCameraAttentionStatus(0) & 0x80; + Z2GetAudience()->setAudioCamera(view->viewMtx, view->lookat.eye, view->lookat.center, view->fovy, view->aspect, camera_attention_status, 0, false); + + dBgS_GndChk gndchk; + gndchk.OnWaterGrp(); + gndchk.SetPos(&view->lookat.eye); + f32 cross = dComIfG_Bgsp().GroundCross(&gndchk); + if (cross != -G_CM3D_F_INF) { + if (dComIfG_Bgsp().ChkGrpInf(gndchk, 0x100)) { + mDoAud_getCameraMapInfo(6); + } else { + mDoAud_getCameraMapInfo(dComIfG_Bgsp().GetMtrlSndId(gndchk)); + } + mDoAud_setCameraGroupInfo(dComIfG_Bgsp().GetGrpSoundId(gndchk)); + Vec spDC; + spDC.x = view->lookat.eye.x; + spDC.y = cross; + spDC.z = view->lookat.eye.z; + Z2AudioMgr::getInterface()->setCameraPolygonPos(&spDC); + } else { + Z2AudioMgr::getInterface()->setCameraPolygonPos(nullptr); + } + + MTXCopy(view->viewMtx, view->viewMtxNoTrans); + view->viewMtxNoTrans[0][3] = 0.0f; + view->viewMtxNoTrans[1][3] = 0.0f; + view->viewMtxNoTrans[2][3] = 0.0f; + cMtx_concatProjView(view->projMtx, view->viewMtx, view->projViewMtx); + + f32 far_; + f32 var_f30; + if (dComIfGp_getCameraAttentionStatus(0) & 8) { + far_ = view->far_; + } else { +#if DEBUG + if (g_envHIO.mOther.mAdjustCullFar != 0) { + var_f30 = g_envHIO.mOther.mCullFarValue; + } else +#endif + { + var_f30 = dStage_stagInfo_GetCullPoint(dComIfGp_getStageStagInfo()); + } + far_ = var_f30; + } + + mDoLib_clipper::setup(view->fovy, view->aspect, view->near_, far_); + + s_presentation_depth = 1; +} + +void end_presentation_camera() { + if (s_presentation_depth == 0) { + return; + } + s_presentation_depth--; + if (s_presentation_depth > 0) { + return; + } + + view_class* const view = dComIfGd_getView(); + if (view != nullptr) { + std::memcpy(view, &s_presentation_view_backup, sizeof(view_class)); + } } uint64_t alloc_simple_shadow_pair_base() { diff --git a/src/dusk/gyro.cpp b/src/dusk/gyro.cpp new file mode 100644 index 0000000000..0905bd59fd --- /dev/null +++ b/src/dusk/gyro.cpp @@ -0,0 +1,123 @@ +#include "dusk/gyro.h" +#include "d/actor/d_a_alink.h" + +namespace dusk::gyro { +namespace { +constexpr s32 kRollgoalTableMaxOffset = 12000; +constexpr float kGyroEmaAlphaMin = 0.05f; +constexpr float kGyroEmaAlphaMax = 1.0f; + +bool s_sensor_enabled = false; +float s_smooth_gx = 0.0f; +float s_smooth_gy = 0.0f; +float s_smooth_gz = 0.0f; +float s_yaw_rad = 0.0f; +float s_pitch_rad = 0.0f; +float s_roll_rad = 0.0f; +s32 s_rollgoal_ax = 0; +s32 s_rollgoal_az = 0; + +void reset_filter_state() { + s_smooth_gx = s_smooth_gy = s_smooth_gz = 0.0f; + s_yaw_rad = s_pitch_rad = s_roll_rad = 0.0f; + s_rollgoal_ax = s_rollgoal_az = 0; +} + +float apply_deadband(float v, float deadband_rad_s) { + if (v > -deadband_rad_s && v < deadband_rad_s) { + return 0.0f; + } + return v; +} +} // namespace + +bool s_sensor_keep_alive = false; +bool get_sensor_keep_alive() { return s_sensor_keep_alive; } +void set_sensor_keep_alive(bool value) { s_sensor_keep_alive = value; } + +bool queryGyroAimItemContext() { + if (!static_cast(dusk::getSettings().game.enableGyroAim)) { + return false; + } + + daAlink_c* link = daAlink_getAlinkActorClass(); + if (link == nullptr) { + return false; + } + + return link->checkGyroAimItemContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10); +} + +void read(float dt) { + if (!s_sensor_keep_alive && !(dusk::getSettings().game.enableGyroAim && queryGyroAimItemContext())) { + if (s_sensor_enabled) { + PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, FALSE); + s_sensor_enabled = false; + } + reset_filter_state(); + return; + } + + if (!s_sensor_enabled) { + if (!PADHasSensor(PAD_CHAN0, PAD_SENSOR_GYRO)) { + return; + } + if (!PADSetSensorEnabled(PAD_CHAN0, PAD_SENSOR_GYRO, TRUE)) { + return; + } + s_sensor_enabled = true; + } + + f32 gyro[3]; + if (!PADGetSensorData(PAD_CHAN0, PAD_SENSOR_GYRO, gyro, 3)) { + return; + } + + const float smooth_alpha = kGyroEmaAlphaMax + dusk::getSettings().game.gyroSmoothing * (kGyroEmaAlphaMin - kGyroEmaAlphaMax); + const float deadband = dusk::getSettings().game.gyroDeadband; + + s_smooth_gx += smooth_alpha * (gyro[0] - s_smooth_gx); + 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 + + 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; + s_yaw_rad = dusk::getSettings().game.enableMirrorMode ? -s_yaw_rad : s_yaw_rad; +} + +void getAimDeltas(float& out_yaw, float& out_pitch) { + out_yaw = s_yaw_rad; + out_pitch = s_pitch_rad; +} + +void rollgoalTick(bool play_active, s16 camera_yaw) { + if (!play_active) { + reset_filter_state(); + return; + } + + float pitch_rad = -s_pitch_rad * dusk::getSettings().game.gyroSensitivityRollgoal; + float roll_rad = s_roll_rad * dusk::getSettings().game.gyroSensitivityRollgoal; + roll_rad = dusk::getSettings().game.enableMirrorMode ? -roll_rad : roll_rad; + + s_rollgoal_az += cM_rad2s(roll_rad); + cXyz in(roll_rad, 0.0f, pitch_rad); + cXyz out; + cMtx_YrotS(*calc_mtx, -camera_yaw); + MtxPosition(&in, &out); + + s_rollgoal_ax += cM_rad2s(out.z); + s_rollgoal_az += cM_rad2s(out.x); + s_rollgoal_ax = std::clamp(s_rollgoal_ax, -kRollgoalTableMaxOffset, kRollgoalTableMaxOffset); + s_rollgoal_az = std::clamp(s_rollgoal_az, -kRollgoalTableMaxOffset, kRollgoalTableMaxOffset); +} + +void rollgoalTableOffset(s16& out_ax, s16& out_az) { + out_ax = static_cast(s_rollgoal_ax); + out_az = static_cast(s_rollgoal_az); +} +} // namespace dusk::gyro diff --git a/src/dusk/gyro_aim.cpp b/src/dusk/gyro_aim.cpp deleted file mode 100644 index 48edd640b3..0000000000 --- a/src/dusk/gyro_aim.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "dusk/gyro_aim.h" - -#include -#include "d/actor/d_a_alink.h" - -namespace dusk::gyro_aim { -namespace { -// TODO: Make deadband and smoothing configurable -constexpr float kDeadbandRadS = 0.04f; -constexpr float kSmoothAlpha = 0.35f; -bool s_sensor_enabled = false; -float s_smooth_gx = 0.0f; -float s_smooth_gy = 0.0f; -float s_pending_yaw_rad = 0.0f; -float s_pending_pitch_rad = 0.0f; - -void reset_filter_state() { - s_smooth_gx = s_smooth_gy = 0.0f; - s_pending_yaw_rad = s_pending_pitch_rad = 0.0f; -} - -float apply_deadband(float v) { - if (v > -kDeadbandRadS && v < kDeadbandRadS) { - return 0.0f; - } - return v; -} -} // namespace - -void read(float dt, bool context_active) { - if (!context_active || !static_cast(dusk::getSettings().game.enableGyroAim)) { - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad != nullptr && s_sensor_enabled) { - SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, false); - s_sensor_enabled = false; - } - reset_filter_state(); - return; - } - - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad == nullptr || !SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) { - return; - } - - if (!s_sensor_enabled) { - if (!SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, true)) { - return; - } - s_sensor_enabled = true; - reset_filter_state(); - } - - float gyro[3]; - if (!SDL_GetGamepadSensorData(pad, SDL_SENSOR_GYRO, gyro, 3)) { - return; - } - - s_smooth_gx += kSmoothAlpha * (gyro[0] - s_smooth_gx); - s_smooth_gy += kSmoothAlpha * (gyro[1] - s_smooth_gy); - float yaw_rate = apply_deadband(s_smooth_gy); - float pitch_rate = apply_deadband(s_smooth_gx); - - s_pending_yaw_rad += yaw_rate * dt * dusk::getSettings().game.gyroAimSensitivityX; - s_pending_pitch_rad += pitch_rate * dt * dusk::getSettings().game.gyroAimSensitivityY; -} - -void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad) { - out_yaw_rad = s_pending_yaw_rad; - out_pitch_rad = s_pending_pitch_rad; - s_pending_yaw_rad = 0.0f; - s_pending_pitch_rad = 0.0f; -} - -bool queryGyroAimItemContext() { - if (!static_cast(dusk::getSettings().game.enableGyroAim)) { - return false; - } - - daAlink_c* link = daAlink_getAlinkActorClass(); - if (link == nullptr) { - return false; - } - - return link->checkGyroAimItemContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10); -} -} // namespace dusk::gyro_aim diff --git a/src/dusk/imgui/ImGuiBloomWindow.cpp b/src/dusk/imgui/ImGuiBloomWindow.cpp new file mode 100644 index 0000000000..1741f82cd8 --- /dev/null +++ b/src/dusk/imgui/ImGuiBloomWindow.cpp @@ -0,0 +1,126 @@ +#include + +#include "imgui.h" + +#include "ImGuiBloomWindow.hpp" +#include "ImGuiMenuTools.hpp" +#include "m_Do/m_Do_graphic.h" + +namespace dusk { +namespace { +struct BloomOverride { + bool enabled = false; + bool bloomEnabled = true; + int mode = 0; + int point = 128; + int blureSize = 64; + int blureRatio = 128; + GXColor blendColor = {255, 255, 255, 255}; + GXColor monoColor = {0, 0, 0, 0}; +}; + +BloomOverride s_bloomOverride; + +void SyncFromCurrentBloom() { + mDoGph_gInf_c::bloom_c* bloom = mDoGph_gInf_c::getBloom(); + s_bloomOverride.bloomEnabled = bloom->getEnable() != 0; + s_bloomOverride.mode = bloom->mMode; + s_bloomOverride.point = bloom->getPoint(); + s_bloomOverride.blureSize = bloom->getBlureSize(); + s_bloomOverride.blureRatio = bloom->getBlureRatio(); + s_bloomOverride.blendColor = *bloom->getBlendColor(); + s_bloomOverride.monoColor = *bloom->getMonoColor(); +} + +u8 ClampToByte(int value) { + return static_cast(std::clamp(value, 0, 255)); +} + +void DrawColorEdit(const char* label, GXColor& color) { + float colorValue[4] = { + color.r / 255.0f, + color.g / 255.0f, + color.b / 255.0f, + color.a / 255.0f, + }; + if (ImGui::ColorEdit4(label, colorValue, ImGuiColorEditFlags_Uint8)) { + color.r = ClampToByte(static_cast(colorValue[0] * 255.0f + 0.5f)); + color.g = ClampToByte(static_cast(colorValue[1] * 255.0f + 0.5f)); + color.b = ClampToByte(static_cast(colorValue[2] * 255.0f + 0.5f)); + color.a = ClampToByte(static_cast(colorValue[3] * 255.0f + 0.5f)); + } +} +} // namespace + +void ApplyBloomOverride() { + if (!s_bloomOverride.enabled) { + return; + } + + mDoGph_gInf_c::bloom_c* bloom = mDoGph_gInf_c::getBloom(); + bloom->setEnable(s_bloomOverride.bloomEnabled ? 1 : 0); + bloom->setMode(static_cast(std::clamp(s_bloomOverride.mode, 0, 1))); + bloom->setPoint(ClampToByte(s_bloomOverride.point)); + bloom->setBlureSize(ClampToByte(s_bloomOverride.blureSize)); + bloom->setBlureRatio(ClampToByte(s_bloomOverride.blureRatio)); + bloom->setBlendColor(s_bloomOverride.blendColor); + bloom->setMonoColor(s_bloomOverride.monoColor); +} + +void DrawBloomWindow(bool& open) { + if (!open) { + return; + } + + if (!ImGui::Begin("Bloom", &open)) { + ImGui::End(); + return; + } + + bool copyCurrent = false; + if (ImGui::Checkbox("Override", &s_bloomOverride.enabled) && s_bloomOverride.enabled) { + copyCurrent = true; + } + + ImGui::SameLine(); + if (ImGui::Button("Sync with current")) { + copyCurrent = true; + } + + if (copyCurrent) { + SyncFromCurrentBloom(); + } + + ImGui::SeparatorText("Values"); + ImGui::Checkbox("Enabled", &s_bloomOverride.bloomEnabled); + ImGui::SliderInt("Mode", &s_bloomOverride.mode, 0, 1); + ImGui::SliderInt("Threshold", &s_bloomOverride.point, 0, 255); + ImGui::SliderInt("Blur Size", &s_bloomOverride.blureSize, 0, 255); + ImGui::SliderInt("Blur Ratio", &s_bloomOverride.blureRatio, 0, 255); + DrawColorEdit("Blend Color", s_bloomOverride.blendColor); + DrawColorEdit("Mono Color", s_bloomOverride.monoColor); + + ApplyBloomOverride(); + + ImGui::SeparatorText("Current"); + mDoGph_gInf_c::bloom_c* bloom = mDoGph_gInf_c::getBloom(); + GXColor blendColor = *bloom->getBlendColor(); + GXColor monoColor = *bloom->getMonoColor(); + ImGui::Text("Enabled: %d", bloom->getEnable()); + ImGui::Text("Mode: %d", bloom->mMode); + ImGui::Text("Threshold: %u", static_cast(bloom->getPoint())); + ImGui::Text("Blur Size: %u", static_cast(bloom->getBlureSize())); + ImGui::Text("Blur Ratio: %u", static_cast(bloom->getBlureRatio())); + ImGui::Text("Blend RGBA: %u, %u, %u, %u", static_cast(blendColor.r), + static_cast(blendColor.g), static_cast(blendColor.b), + static_cast(blendColor.a)); + ImGui::Text("Mono RGBA: %u, %u, %u, %u", static_cast(monoColor.r), + static_cast(monoColor.g), static_cast(monoColor.b), + static_cast(monoColor.a)); + ImGui::End(); +} + +void ImGuiMenuTools::ShowBloomWindow() { + DrawBloomWindow(m_showBloomWindow); +} +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiBloomWindow.hpp b/src/dusk/imgui/ImGuiBloomWindow.hpp new file mode 100644 index 0000000000..58e644e96d --- /dev/null +++ b/src/dusk/imgui/ImGuiBloomWindow.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace dusk { +void DrawBloomWindow(bool& open); +void ApplyBloomOverride(); +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 612c95d7f3..eccf9b3c20 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -40,9 +40,15 @@ namespace dusk { void ImGuiTextCenter(std::string_view text) { ImGui::NewLine(); - float fontSize = ImGui::CalcTextSize(text.data(), text.data() + text.size()).x; + float fontSize = ImGui::CalcTextSize( + text.data(), + text.data() + text.size(), + false, + ImGui::GetWindowSize().x).x; ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2); + ImGui::PushTextWrapPos(ImGui::GetWindowSize().x); ImGuiStringViewText(text); + ImGui::PopTextWrapPos(); } bool ImGuiButtonCenter(std::string_view text) { @@ -228,31 +234,40 @@ namespace dusk { ImGuiMenuGame::ToggleFullscreen(); } - bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); + if (!dusk::IsGameLaunched) { + m_preLaunchWindow.draw(); + } + + m_isHidden = getSettings().backend.duskMenuOpen; + CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); - if (showMenu && ImGui::BeginMainMenuBar()) { + if (dusk::IsGameLaunched) { + if (getSettings().backend.duskMenuOpen != m_isHidden) { + m_isHidden = !getSettings().backend.duskMenuOpen; + getSettings().backend.duskMenuOpen.setValue(m_isHidden); + config::Save(); + } + } + + if ((!dusk::IsGameLaunched || getSettings().backend.duskMenuOpen) && ImGui::BeginMainMenuBar()) { m_menuGame.draw(); m_menuEnhancements.draw(); m_menuTools.draw(); - ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 80.0f * ImGuiScale()); + ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 100.0f * ImGuiScale()); ImGuiIO& io = ImGui::GetIO(); ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); ImGui::EndMainMenuBar(); } - if (!dusk::IsGameLaunched) { - m_preLaunchWindow.draw(); - } - if (!getSettings().backend.wasPresetChosen) { m_firstRunPreset.draw(); return; } if (dusk::IsGameLaunched && !m_isLaunchInitialized) { - m_toasts.emplace_back("Press F1 to toggle menu"s, 5.f); + m_toasts.emplace_back("Press F1 to toggle menu"s, 2.5f); m_isLaunchInitialized = true; } @@ -265,6 +280,7 @@ namespace dusk { m_menuTools.ShowHeapOverlay(); m_menuTools.ShowStubLog(); m_menuTools.ShowMapLoader(); + m_menuTools.ShowBloomWindow(); m_menuTools.ShowPlayerInfo(); m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); @@ -273,7 +289,8 @@ namespace dusk { DuskDebugPad(); // temporary, remove later // Only show cursor when menu or any windows are open - if (showMenu || ImGui::GetIO().MetricsRenderWindows > 0) { + if ((!dusk::IsGameLaunched || getSettings().backend.duskMenuOpen) || ImGui::GetIO().MetricsRenderWindows > 0) + { ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; // Imgui will re-show cursor. } else { diff --git a/src/dusk/imgui/ImGuiControllerOverlay.cpp b/src/dusk/imgui/ImGuiControllerOverlay.cpp index 8c24063375..dd0a15806c 100644 --- a/src/dusk/imgui/ImGuiControllerOverlay.cpp +++ b/src/dusk/imgui/ImGuiControllerOverlay.cpp @@ -5,7 +5,7 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuGame.hpp" -#include +#include namespace dusk { void ImGuiMenuGame::windowInputViewer() { @@ -260,31 +260,33 @@ namespace dusk { size.y = 130 * scale; ImGui::Dummy(size); - SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0); - if (pad != nullptr && SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) { + if (PADHasSensor(PAD_1, PAD_SENSOR_GYRO) == TRUE) { ImGui::Separator(); - ImGui::TextUnformatted("Gyro"); + ImGui::Checkbox("Gyro Values", &m_showInputViewerGyro); + if (m_showInputViewerGyro) { + ImGui::TextUnformatted("Gyro"); - constexpr float kBarScale = 4.0f; - auto bar = [kBarScale](const char* label, float v) { - const float a = std::fabs(v); - const float t = std::min(1.f, a / kBarScale); - char overlay[32]; - snprintf(overlay, sizeof(overlay), "%s %+.3f", label, v); - ImGui::ProgressBar(t, ImVec2(-1, 0), overlay); - }; + constexpr float kBarScale = 4.0f; + auto bar = [kBarScale](const char* label, float v) { + const float a = std::fabs(v); + const float t = std::min(1.f, a / kBarScale); + char overlay[32]; + snprintf(overlay, sizeof(overlay), "%s %+.3f", label, v); + ImGui::ProgressBar(t, ImVec2(-1, 0), overlay); + }; - if (SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, true)) { - float gyro[3]; - if (SDL_GetGamepadSensorData(pad, SDL_SENSOR_GYRO, gyro, 3)) { - bar("X", gyro[0]); - bar("Y", gyro[1]); - bar("Z", gyro[2]); + if (PADSetSensorEnabled(PAD_1, PAD_SENSOR_GYRO, TRUE) == TRUE) { + f32 gyro[3]; + if (PADGetSensorData(PAD_1, PAD_SENSOR_GYRO, gyro, 3) == TRUE) { + bar("X", gyro[0]); + bar("Y", gyro[1]); + bar("Z", gyro[2]); + } + } else { + bar("X", 0.f); + bar("Y", 0.f); + bar("Z", 0.f); } - } else { - bar("X", 0.f); - bar("Y", 0.f); - bar("Z", 0.f); } } diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index a6058592da..d47b4e8d4b 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -9,107 +9,117 @@ namespace dusk { void ImGuiMenuEnhancements::draw() { if (ImGui::BeginMenu("Enhancements")) { - if (ImGui::BeginMenu("Quality of Life")) { - config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); + if (ImGui::BeginMenu("Gameplay")) { + ImGui::SeparatorText("Preferences"); + + config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows you to quickly transform between forms\n" - "without having to talk to Midna."); + ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game."); } - config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong); + config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day."); + ImGui::SetTooltip("Disables the main HUD of the game.\n" + "Useful for recording or a more immersive experience!"); } + ImGui::SeparatorText("Difficulty"); + + config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); + + config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Any hit will instantly kill you."); + } + + config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hearts will never drop from enemies,\n" + "pots and various other places."); + } + + ImGui::SeparatorText("Quality of Life"); + config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)"); } - config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full."); - } - config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time."); } - config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Link won't recoil when his sword hits walls."); - } - config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version."); } + config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version."); + } + + config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); + } + config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" "when grabbing ledges or climbing on vines."); } - config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); + config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version."); + ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full."); } - config::ImGuiCheckbox("Hide TV Settings Screen", getSettings().game.hideTvSettingsScreen); + config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hides the TV calibration screen shown when loading a save."); + ImGui::SetTooltip("Link won't recoil when his sword hits walls."); + } + + config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save."); } config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skips the warning screen shown when loading the game."); + ImGui::SetTooltip("Skip the warning screen shown when starting the game."); } - config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); + config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); + ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day."); } - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Preferences")) { - config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); + config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Mirrors the world, matching the Wii version of the game."); - } - - config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); - - config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Disables the main HUD of the game.\n" - "Useful for recording or a more immersive experience!"); + ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously."); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Graphics")) { + config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Improves the shadow resolution, making them higher quality."); + } + config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation); const bool frameInterpolationHovered = ImGui::IsItemHovered(); - ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f)); ImGui::TextUnformatted("[EXPERIMENTAL]"); ImGui::PopStyleColor(); - if (frameInterpolationHovered || ImGui::IsItemHovered()) { ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur."); } - config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Improves the shadow resolution, making them higher quality."); - } - ImGui::EndMenu(); } @@ -128,20 +138,62 @@ namespace dusk { } if (ImGui::BeginMenu("Input")) { + config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); + + ImGui::SeparatorText("Gyro"); + config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Enables the gyroscope on supported controllers while aiming the\n" "Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s), Ball and Chain, and Dominion Rod."); } - config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroAimSensitivityY, 0.25f, 4.0f, "%.2f"); - config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroAimSensitivityX, 0.25f, 4.0f, "%.2f"); - config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroAimInvertPitch); - config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroAimInvertYaw); + config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n" + "tilt the Rollgoal table in Hena's Cabin."); + } + + if (getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal) { + config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroSensitivityY, 0.25f, 4.0f, "%.2f"); + config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroSensitivityX, 0.25f, 4.0f, "%.2f"); + + if (getSettings().game.enableGyroRollgoal) { + config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroSensitivityRollgoal, 0.25f, 4.0f, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Additional multiplier for scaling how strongly\n" + "the gyroscope affects the Rollgoal table."); + } + } + + config::ImGuiSliderFloat("Gyro Deadband", getSettings().game.gyroDeadband, 0.0f, 0.5f, "%.3f"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Angular rates below this magnitude are treated as zero,\n" + "reducing drift and jitter when the controller is still."); + } + + config::ImGuiSliderFloat("Gyro Smoothing", getSettings().game.gyroSmoothing, 0.0f, 1.0f, "%.2f"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Low values track raw gyro input more closely,\n" + "while higher values smooth out input over time."); + } + + config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroInvertPitch); + config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroInvertYaw); + } + + ImGui::SeparatorText("Tools"); + + config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); + } ImGui::EndMenu(); } + ImGui::Separator(); + if (ImGui::BeginMenu("Cheats")) { config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots); @@ -163,23 +215,6 @@ namespace dusk { ImGui::EndMenu(); } - if (ImGui::BeginMenu("Difficulty")) { - config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); - - config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hearts will never drop from enemies,\n" - "pots and various other places."); - } - - config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Any hit will instantly kill you."); - } - - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Technical")) { config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches); if (ImGui::IsItemHovered()) { @@ -190,15 +225,6 @@ namespace dusk { ImGui::EndMenu(); } - if (ImGui::BeginMenu("Tools")) { - config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); - } - - ImGui::EndMenu(); - } - ImGui::EndMenu(); } } diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index 48c64f8f0e..c40e362db8 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -304,6 +304,7 @@ namespace dusk { m_controllerConfig.m_pendingButtonMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); + PADSerializeMappings(); } } @@ -316,6 +317,7 @@ namespace dusk { m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); + PADSerializeMappings(); } else { auto nativeButton = PADGetNativeButtonPressed(m_controllerConfig.m_pendingPort); if (nativeButton != -1) { @@ -324,6 +326,7 @@ namespace dusk { m_controllerConfig.m_pendingAxisMapping = nullptr; m_controllerConfig.m_pendingPort = -1; PADBlockInput(false); + PADSerializeMappings(); } } } @@ -400,11 +403,6 @@ namespace dusk { // if "None" selected PADClearPort(m_controllerConfig.m_selectedPort); } - } - - // save mappings button - ImGui::SameLine(); - if (ImGui::Button("Save")) { PADSerializeMappings(); } @@ -412,6 +410,7 @@ namespace dusk { ImGui::SameLine(); if (ImGui::Button("Default")) { PADRestoreDefaultMapping(m_controllerConfig.m_selectedPort); + PADSerializeMappings(); } // buttons panel @@ -508,6 +507,7 @@ namespace dusk { float tmp = static_cast(deadZones->leftTriggerActivationZone * 100.f) / 32767.f; if (ImGui::DragFloat("##LThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->leftTriggerActivationZone = static_cast((tmp / 100.f) * 32767); + PADSerializeMappings(); } } } @@ -519,6 +519,7 @@ namespace dusk { float tmp = static_cast(deadZones->rightTriggerActivationZone * 100.f) / 32767.f; if (ImGui::DragFloat("##RThreshold", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->rightTriggerActivationZone = static_cast((tmp / 100.f) * 32767); + PADSerializeMappings(); } } } @@ -581,6 +582,7 @@ namespace dusk { float tmp = static_cast(deadZones->stickDeadZone * 100.f) / 32767.f; if (ImGui::DragFloat("##mainDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->stickDeadZone = static_cast((tmp / 100.f) * 32767); + PADSerializeMappings(); } } } @@ -643,6 +645,7 @@ namespace dusk { float tmp = static_cast(deadZones->substickDeadZone * 100.f) / 32767.f; if (ImGui::DragFloat("##subDeadZone", &tmp, 0.5f, 0.f, 100.f, "%.3f%%")) { deadZones->substickDeadZone = static_cast((tmp / 100.f) * 32767); + PADSerializeMappings(); } } } @@ -654,8 +657,12 @@ namespace dusk { ImGuiBeginGroupPanel("Options", ImVec2(150 * scale, 20 * scale)); if (deadZones != nullptr) { - ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones); - ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers); + if (ImGui::Checkbox("Enable Dead Zones", &deadZones->useDeadzones)) { + PADSerializeMappings(); + } + if (ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers)) { + PADSerializeMappings(); + } } ImGuiEndGroupPanel(); diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index 77e5952d1a..4d51cbc865 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -30,6 +30,7 @@ namespace dusk { bool m_showControllerConfig = false; bool m_showInputViewer = false; + bool m_showInputViewerGyro = false; int m_inputOverlayCorner = 3; std::string m_controllerName; }; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 7b317c978b..9bb7670e88 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -54,7 +54,7 @@ namespace dusk { ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare); ImGui::MenuItem("Debug Camera", hotkeys::SHOW_DEBUG_CAMERA, &m_showCameraOverlay); ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); - + ImGui::MenuItem("Bloom", nullptr, &m_showBloomWindow); ImGui::MenuItem("Stub Log", nullptr, &m_showStubLog); if (!dusk::IsGameLaunched) { diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 05bdd9364b..e48aab2742 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -21,6 +21,7 @@ namespace dusk { void ShowHeapOverlay(); void ShowStubLog(); void ShowMapLoader(); + void ShowBloomWindow(); void ShowPlayerInfo(); void ShowAudioDebug(); void ShowSaveEditor(); @@ -41,6 +42,8 @@ namespace dusk { bool m_showMapLoader = false; + bool m_showBloomWindow = false; + bool m_showAudioDebug = false; struct { int mapIdx = -1; diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index 5925fd8c31..c24dbd8247 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -7,6 +7,7 @@ #include "ImGuiConsole.hpp" #include "dusk/main.h" #include "dusk/settings.h" +#include "../iso_validate.hpp" #include #include @@ -26,15 +27,42 @@ static constexpr std::array skGameDiscFileFilters{{ {"All Files", "*"}, }}; +static std::string ShowIsoInvalidError(const iso::ValidationError code) { + using namespace std::literals::string_literals; + + switch (code) { + case iso::ValidationError::IOError: + return "Unknown IO error occurred"s; + case iso::ValidationError::InvalidImage: + return "Unable to interpret selected file as a disc image"s; + case iso::ValidationError::WrongGame: + return "Selected disc image is for a different game"s; + case iso::ValidationError::WrongVersion: + return "Selected disc image is for an unsupported version of the game. Only North American GameCube (NTSC/GZ2E01) is supported at this time."s; + case iso::ValidationError::ExecutableMismatch: + return "Selected disc image contains modified executable files."s; + default: + return "Unknown error"s; + } +} + void fileDialogCallback(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) { auto* self = static_cast(userdata); + self->m_errorString.clear(); if (filelist != nullptr) { if (filelist[0] == nullptr) { // Cancelled self->m_selectedIsoPath.clear(); } else { - self->m_selectedIsoPath = filelist[0]; - getSettings().backend.isoPath.setValue(self->m_selectedIsoPath); + const auto path = filelist[0]; + const auto ret = iso::validate(path); + if (ret != iso::ValidationError::Success) { + self->m_selectedIsoPath.clear(); + self->m_errorString = std::move(ShowIsoInvalidError(ret)); + return; + } + self->m_selectedIsoPath = path; + getSettings().backend.isoPath.setValue(path); config::Save(); } } else { @@ -111,6 +139,10 @@ void ImGuiPreLaunchWindow::drawMainMenu() { ImGui::PushFont(ImGuiEngine::fontLarge); if (!isSelectedPathValid()) { + if (!m_errorString.empty()) { + ImGuiTextCenter(m_errorString); + } + if (ImGuiButtonCenter("Select disc image...")) { SDL_ShowOpenFileDialog(&fileDialogCallback, this, aurora::window::get_sdl_window(), skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), @@ -148,6 +180,10 @@ void ImGuiPreLaunchWindow::drawOptions() { if (ImGui::BeginChild("OptionsChild", ImVec2(childWidth, endCursorY - cursorY), ImGuiChildFlags_None, ImGuiWindowFlags_NoBackground)) { + if (!m_errorString.empty()) { + ImGuiTextCenter(m_errorString); + } + ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); if (ImGui::Button("Set")) { diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index fd45789ccf..2c679308a2 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -445,7 +445,7 @@ namespace dusk { } if (ImGui::BeginTabItem("Minigame")) { - //DrawFlagsTab(); + drawMinigameTab(); ImGui::EndTabItem(); } @@ -815,48 +815,88 @@ namespace dusk { void ImGuiSaveEditor::drawInventoryTab() { dSv_player_item_c& item = dComIfGs_getSaveData()->getPlayer().getItem(); - if (ImGui::Button("Default All##inv_default_all")) { - for (int slot = 0; slot < 24; slot++) { - dComIfGs_setItem(slot, getSlotDefault(slot)); + if (ImGui::TreeNode("Item Wheel")) { + if (ImGui::Button("Default All##inv_default_all")) { + for (int slot = 0; slot < 24; slot++) { + dComIfGs_setItem(slot, getSlotDefault(slot)); + } } - } - ImGui::SameLine(); - if (ImGui::Button("Clear All##inv_clear_all")) { - for (int slot = 0; slot < 24; slot++) { - dComIfGs_setItem(slot, dItemNo_NONE_e); - } - } - - ImGuiBeginGroupPanel("Items", { 200, 100 }); - for (int slot = 0; slot < 24; slot++) { - ImGui::Text("Slot %02d: ", slot); ImGui::SameLine(); - if (ImGui::BeginCombo(fmt::format("##ItemComboBox{}", slot).c_str(), itemMap.find(item.mItems[slot])->second.m_name.c_str())) { - if (ImGui::Selectable("None")) { + if (ImGui::Button("Clear All##inv_clear_all")) { + for (int slot = 0; slot < 24; slot++) { dComIfGs_setItem(slot, dItemNo_NONE_e); } + } - for (int i = 0; i < 254; i++) { - if (itemMap.find(i)->second.m_type != ITEMTYPE_EQUIP_e) continue; - - if (ImGui::Selectable(fmt::format("{0}##item_{1}{2}", itemMap.find(i)->second.m_name, slot, i).c_str())) { - dComIfGs_setItem(slot, itemMap.find(i)->first); + ImGuiBeginGroupPanel("Items", { 200, 100 }); + for (int slot = 0; slot < 24; slot++) { + ImGui::Text("Slot %02d (%s): ", slot, itemMap.find(getSlotDefault(slot))->second.m_name.c_str()); + ImGui::SameLine(240.0f); + if (ImGui::BeginCombo(fmt::format("##ItemComboBox{}", slot).c_str(), itemMap.find(item.mItems[slot])->second.m_name.c_str())) { + if (ImGui::Selectable("None")) { + dComIfGs_setItem(slot, dItemNo_NONE_e); } + + for (int i = 0; i < 254; i++) { + if (itemMap.find(i)->second.m_type != ITEMTYPE_EQUIP_e) continue; + + if (ImGui::Selectable(fmt::format("{0}##item_{1}{2}", itemMap.find(i)->second.m_name, slot, i).c_str())) { + dComIfGs_setItem(slot, itemMap.find(i)->first); + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + if (ImGui::SmallButton(fmt::format("Default##slot_d_{}", slot).c_str())) { + dComIfGs_setItem(slot, getSlotDefault(slot)); + } + ImGui::SameLine(); + if (ImGui::SmallButton(fmt::format("Clear##slot_c_{}", slot).c_str())) { + dComIfGs_setItem(slot, dItemNo_NONE_e); } - ImGui::EndCombo(); - } - ImGui::SameLine(); - if (ImGui::SmallButton(fmt::format("Default##slot_d_{}", slot).c_str())) { - dComIfGs_setItem(slot, getSlotDefault(slot)); - } - ImGui::SameLine(); - if (ImGui::SmallButton(fmt::format("Clear##slot_c_{}", slot).c_str())) { - dComIfGs_setItem(slot, dItemNo_NONE_e); } + ImGuiEndGroupPanel(); + + ImGui::TreePop(); } + + if (ImGui::TreeNode("Get Item Flags")) { + for (int i = 0; i < 254; i++) { + if (itemMap.find(i)->second.m_name == "Reserved") continue; + + bool flag = dComIfGs_isItemFirstBit(i); + if (ImGui::Checkbox(fmt::format("{0}##item_{1}", itemMap.find(i)->second.m_name, i).c_str(), &flag)) { + if (flag) + dComIfGs_onItemFirstBit(i); + else + dComIfGs_offItemFirstBit(i); + } + } + + ImGui::TreePop(); + } + + dSv_player_item_record_c& itemRecord = dComIfGs_getSaveData()->getPlayer().getItemRecord(); + dSv_player_item_max_c& itemMax = dComIfGs_getSaveData()->getPlayer().getItemMax(); + + ImGuiBeginGroupPanel("Item Max Capacities", { 200, 100 }); + ImGui::InputScalar("Arrows Max", ImGuiDataType_U8, &itemMax.mItemMax[0]); + ImGui::InputScalar("Normal Bombs Max", ImGuiDataType_U8, &itemMax.mItemMax[1]); + ImGui::InputScalar("Water Bombs Max", ImGuiDataType_U8, &itemMax.mItemMax[2]); + ImGui::InputScalar("Bomblings Max", ImGuiDataType_U8, &itemMax.mItemMax[3]); ImGuiEndGroupPanel(); - + ImGuiBeginGroupPanel("Item Amounts", { 200, 100 }); + ImGui::InputScalar("Arrows Amount", ImGuiDataType_U8, &itemRecord.mArrowNum); + ImGui::InputScalar("Slingshot Amount", ImGuiDataType_U8, &itemRecord.mPachinkoNum); + ImGui::InputScalar("Bomb Bag 1 Amount", ImGuiDataType_U8, &itemRecord.mBombNum[0]); + ImGui::InputScalar("Bomb Bag 2 Amount", ImGuiDataType_U8, &itemRecord.mBombNum[1]); + ImGui::InputScalar("Bomb Bag 3 Amount", ImGuiDataType_U8, &itemRecord.mBombNum[2]); + ImGui::InputScalar("Bottle 1 Amount", ImGuiDataType_U8, &itemRecord.mBottleNum[0]); + ImGui::InputScalar("Bottle 2 Amount", ImGuiDataType_U8, &itemRecord.mBottleNum[1]); + ImGui::InputScalar("Bottle 3 Amount", ImGuiDataType_U8, &itemRecord.mBottleNum[2]); + ImGui::InputScalar("Bottle 4 Amount", ImGuiDataType_U8, &itemRecord.mBottleNum[3]); + ImGuiEndGroupPanel(); } static inline void setItemFirstBit(u8 itemNo, bool owned) { @@ -902,27 +942,42 @@ namespace dusk { void ImGuiSaveEditor::drawCollectionTab() { if (ImGui::TreeNode("Equipment")) { if (ImGui::TreeNode("Swords")) { + static u8 sword_list[] = { + dItemNo_SWORD_e, + dItemNo_MASTER_SWORD_e, + dItemNo_WOOD_STICK_e, + dItemNo_LIGHT_SWORD_e, + }; + for (int i = 0; i < 4; i++) { - bool got = dComIfGs_isCollectSword((u8)i) != 0; + bool got = dComIfGs_isItemFirstBit(sword_list[i]) != 0; if (ImGui::Checkbox(fmt::format("{0}##sword_{1}", sSwordNames[i], i).c_str(), &got)) { - if (got) dComIfGs_setCollectSword((u8)i); - else dComIfGs_offCollectSword((u8)i); + if (got) dComIfGs_onItemFirstBit(sword_list[i]); + else dComIfGs_offItemFirstBit(sword_list[i]); } } ImGui::TreePop(); } if (ImGui::TreeNode("Shields")) { + static u8 shield_list[] = { + dItemNo_SHIELD_e, + dItemNo_WOOD_SHIELD_e, + dItemNo_HYLIA_SHIELD_e, + }; + for (int i = 0; i < 3; i++) { - bool got = dComIfGs_isCollectShield((u8)i) != 0; + bool got = dComIfGs_isItemFirstBit(shield_list[i]) != 0; if (ImGui::Checkbox(fmt::format("{0}##shield_{1}", sShieldNames[i], i).c_str(), &got)) { - if (got) dComIfGs_setCollectShield((u8)i); - else dComIfGs_offCollectShield((u8)i); + if (got) dComIfGs_onItemFirstBit(shield_list[i]); + else dComIfGs_offItemFirstBit(shield_list[i]); } } ImGui::TreePop(); } + // TODO: the game checks if you're in ranch clothes, and if so won't display any other tunics in the menu + // find a way to deal with this later if (ImGui::TreeNode("Tunics")) { bool ordonClothes = dComIfGs_isItemFirstBit(dItemNo_WEAR_CASUAL_e) != 0; if (ImGui::Checkbox("Ordon Clothes##tunic_ordon", &ordonClothes)) { @@ -1228,6 +1283,16 @@ namespace dusk { } } + static inline void genDungeonItemCheckbox(dSv_memBit_c& membit, const char* label, int flag) { + bool tempFlag = membit.isDungeonItem(flag); + if (ImGui::Checkbox(label, &tempFlag)) { + if (tempFlag) + membit.onDungeonItem(flag); + else + membit.offDungeonItem(flag); + } + } + void genMembitFlags(const char* id, dSv_memBit_c& membit) { ImGuiBeginGroupPanel("Chest", { 100, 100 }); for (int j = 0; j < 2; j++) { @@ -1235,7 +1300,7 @@ namespace dusk { } ImGuiEndGroupPanel(); - ImVec2 cursor = ImGui::GetCursorPos(); + ImVec2 post_tbox_cursor = ImGui::GetCursorPos(); ImGui::SameLine(); @@ -1245,13 +1310,38 @@ namespace dusk { } ImGuiEndGroupPanel(); - ImGui::SetCursorPos(cursor); + ImVec2 post_switch_cursor = ImGui::GetCursorPos(); + + ImGui::SetCursorPos(post_tbox_cursor); ImGuiBeginGroupPanel("Item", { 100, 100 }); for (int j = 0; j < 1; j++) { drawFlagList(fmt::format("##_item{}", j).c_str(), membit.mItem[j]); } ImGuiEndGroupPanel(); + + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); + + genDungeonItemCheckbox(membit, "Got Map", dSv_memBit_c::MAP); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Got Compass", dSv_memBit_c::COMPASS); + + genDungeonItemCheckbox(membit, "Got Boss Key", dSv_memBit_c::BOSS_KEY); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Saw Boss Demo", dSv_memBit_c::STAGE_BOSS_DEMO); + + genDungeonItemCheckbox(membit, "Got Heart Container", dSv_memBit_c::STAGE_LIFE); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Defeated Boss", dSv_memBit_c::STAGE_BOSS_ENEMY); + + genDungeonItemCheckbox(membit, "Defeated Miniboss", dSv_memBit_c::STAGE_BOSS_ENEMY_2); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Got Ooccoo", dSv_memBit_c::OOCCOO_NOTE); + + int keyTemp = membit.getKeyNum(); + if (ImGui::SliderInt("Keys", &keyTemp, 0, 5)) { + membit.setKeyNum(keyTemp); + } } void ImGuiSaveEditor::drawFlagsTab() { @@ -1259,15 +1349,18 @@ namespace dusk { dSv_memBit_c& membit = g_dComIfG_gameInfo.info.mMemory.mBit; genMembitFlags("##TempSceneFlags", membit); - int stageNo = dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo()); - if (ImGui::Button("Save##SaveTempFlags")) { - dComIfGs_putSave(stageNo); - } + stage_stag_info_class* pstag = dComIfGp_getStageStagInfo(); + if (pstag != nullptr) { + int stageNo = dStage_stagInfo_GetSaveTbl(pstag); + if (ImGui::Button("Save##SaveTempFlags")) { + dComIfGs_putSave(stageNo); + } - ImGui::SameLine(); + ImGui::SameLine(); - if (ImGui::Button("Load##LoadSaveFlags")) { - dComIfGs_getSave(stageNo); + if (ImGui::Button("Load##LoadSaveFlags")) { + dComIfGs_getSave(stageNo); + } } ImGui::TreePop(); @@ -1280,7 +1373,7 @@ namespace dusk { "Faron", "Eldin", "Lanayru", - "Unknown", + "Reserved", "Hyrule Field", "Sacred Grove", "Snowpeak", @@ -1328,7 +1421,7 @@ namespace dusk { dSv_event_c& event = dComIfGs_getSaveData()->mEvent; for (int e = 0; e < 255; e++) { ImGui::Text("%03d ", e); - ImGui::SameLine(); + ImGui::SameLine(80.0f); for (int i = 7; i >= 0; i--) { bool flag = event.mEvent[e] & (1 << i); if (ImGui::Checkbox(fmt::format("##event{0}{1}", e, i).c_str(), &flag)) { @@ -1345,10 +1438,25 @@ namespace dusk { } } + void ImGuiSaveEditor::drawMinigameTab() { + dSv_MiniGame_c& minigame = dComIfGs_getSaveData()->getMiniGame(); + InputScalarBE("STAR Game Time (Milliseconds)", ImGuiDataType_U32, &minigame.mHookGameTime); + InputScalarBE("Snowboard Race Time (Milliseconds)", ImGuiDataType_U32, &minigame.mRaceGameTime); + InputScalarBE("Fruit-Pop-Flight Score", ImGuiDataType_U32, &minigame.mBalloonScore); + } + void ImGuiSaveEditor::drawConfigTab() { dSv_player_config_c& config = dComIfGs_getSaveData()->getPlayer().getConfig(); ImGui::Checkbox("Enable Vibration", (bool*)&config.mVibration); - if (ImGui::BeginCombo("Target Type", "Hold")) { + + static const char* kTargetTypeNames[] = {"Hold", "Switch"}; + if (ImGui::BeginCombo("Target Type", kTargetTypeNames[config.mAttentionType])) { + if (ImGui::Selectable("Hold")) { + config.mAttentionType = 0; + } + if (ImGui::Selectable("Switch")) { + config.mAttentionType = 1; + } ImGui::EndCombo(); } @@ -1366,4 +1474,4 @@ namespace dusk { ImGui::EndCombo(); } } -} \ No newline at end of file +} diff --git a/src/dusk/imgui/ImGuiSaveEditor.hpp b/src/dusk/imgui/ImGuiSaveEditor.hpp index 4b211311b0..dc7c122079 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.hpp +++ b/src/dusk/imgui/ImGuiSaveEditor.hpp @@ -17,6 +17,7 @@ namespace dusk { void drawInventoryTab(); void drawCollectionTab(); void drawFlagsTab(); + void drawMinigameTab(); void drawConfigTab(); private: diff --git a/src/dusk/iso_validate.cpp b/src/dusk/iso_validate.cpp new file mode 100644 index 0000000000..755b4eede0 --- /dev/null +++ b/src/dusk/iso_validate.cpp @@ -0,0 +1,126 @@ +#include "iso_validate.hpp" + +#include +#include + +#include "SDL3/SDL_iostream.h" + +namespace dusk::iso { + +constexpr const char* TP_GAME_IDS[] = { + "GZ2E01", // GCN USA + "GZ2P01", // GCN PAL + "GZ2J01", // GCN JPN + "RZDE01", // Wii USA + "RZDP01", // Wii PAL + "RZDJ01", // Wii JPN + "RZDK01", // Wii KOR +}; + +constexpr const char* SUPPORTED_TP_GAME_IDS[] = { + "GZ2E01", // GCN USA +}; + +template +constexpr bool matches(const char (&id)[6], const char* const (&valid)[N]) { + for (auto elem : valid) { + if (strncmp(id, elem, 6) == 0) { + return true; + } + } + + return false; +} + +struct NodHandleWrapper { + NodHandle* handle; + + NodHandleWrapper() : handle(nullptr) { + } + + ~NodHandleWrapper() { + if (handle != nullptr) { + nod_free(handle); + handle = nullptr; + } + } +}; + +static ValidationError convertNodError(NodResult result) { + switch (result) { + case NOD_RESULT_ERR_IO: + return ValidationError::IOError; + case NOD_RESULT_ERR_FORMAT: + return ValidationError::InvalidImage; + default: + return ValidationError::Unknown; + } +} + +s64 StreamReadAt(void* user_data, u64 offset, void* out, size_t len) { + if (len == 0) { + return 0; + } + + auto io = static_cast(user_data); + + auto ret = SDL_SeekIO(io, static_cast(offset), SDL_IO_SEEK_SET); + if (ret < 0) { + return -1; + } + + auto read = SDL_ReadIO(io, out, len); + if (read == 0) { + if (SDL_GetIOStatus(io) == SDL_IO_STATUS_EOF) { + return 0; + } + + return -1; + } + + return static_cast(read); +} + +s64 StreamLength(void* user_data) { + auto io = static_cast(user_data); + return SDL_GetIOSize(io); +} + +void StreamClose(void* user_data) { + auto io = static_cast(user_data); + SDL_CloseIO(io); +} + +ValidationError validate(const char* path) { + NodHandleWrapper disc; + + const auto sdlStream = SDL_IOFromFile(path, "rb"); + const NodDiscStream nod_stream { + .user_data = sdlStream, + .read_at = StreamReadAt, + .stream_len = StreamLength, + .close = StreamClose, + }; + + auto result = nod_disc_open_stream(&nod_stream, nullptr, &disc.handle); + if (disc.handle == nullptr || result != NOD_RESULT_OK) { + return convertNodError(result); + } + + NodDiscHeader header{}; + result = nod_disc_header(disc.handle, &header); + if (result != NOD_RESULT_OK) { + return convertNodError(result); + } + + if (!matches(header.game_id, TP_GAME_IDS)) { + return ValidationError::WrongGame; + } + + if (!matches(header.game_id, SUPPORTED_TP_GAME_IDS)) { + return ValidationError::WrongVersion; + } + + return ValidationError::Success; +} +} // namespace dusk::iso \ No newline at end of file diff --git a/src/dusk/iso_validate.hpp b/src/dusk/iso_validate.hpp new file mode 100644 index 0000000000..da1ef1f2a6 --- /dev/null +++ b/src/dusk/iso_validate.hpp @@ -0,0 +1,18 @@ +#ifndef DUSK_ISO_VALIDATE_HPP +#define DUSK_ISO_VALIDATE_HPP + +namespace dusk::iso { + enum class ValidationError : u8 { + Success = 0, + IOError, + InvalidImage, + WrongGame, + WrongVersion, + ExecutableMismatch, + Unknown + }; + + ValidationError validate(const char* path); +} + +#endif // DUSK_ISO_VALIDATE_HPP diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 99ff183e79..e4cace9ff0 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -55,10 +55,14 @@ UserSettings g_userSettings = { // Input .enableGyroAim {"game.enableGyroAim", false}, - .gyroAimSensitivityX {"game.gyroAimSensitivityX", 1.0f}, - .gyroAimSensitivityY {"game.gyroAimSensitivityY", 1.0f}, - .gyroAimInvertPitch {"game.gyroAimInvertPitch", false}, - .gyroAimInvertYaw {"game.gyroAimInvertYaw", false}, + .enableGyroRollgoal {"game.enableGyroRollgoal", false}, + .gyroSensitivityX {"game.gyroSensitivityX", 1.0f}, + .gyroSensitivityY {"game.gyroSensitivityY", 1.0f}, + .gyroSensitivityRollgoal {"game.gyroSensitivityRollgoal", 1.0f}, + .gyroSmoothing {"game.gyroSmoothing", 0.65f}, + .gyroDeadband {"game.gyroDeadband", 0.04f}, + .gyroInvertPitch {"game.gyroInvertPitch", false}, + .gyroInvertYaw {"game.gyroInvertYaw", false}, // Cheats .enableFastIronBoots {"game.enableFastIronBoots", false}, @@ -79,7 +83,8 @@ UserSettings g_userSettings = { .skipPreLaunchUI {"backend.skipPreLaunchUI", false}, .showPipelineCompilation {"backend.showPipelineCompilation", false}, .wasPresetChosen {"backend.wasPresetChosen", false}, - .enableCrashReporting {"backend.enableCrashReporting", true} + .enableCrashReporting {"backend.enableCrashReporting", true}, + .duskMenuOpen {"backend.duskMenuOpen", false} } }; @@ -134,10 +139,14 @@ void registerSettings() { Register(g_userSettings.game.fastSpinner); Register(g_userSettings.game.enableFrameInterpolation); Register(g_userSettings.game.enableGyroAim); - Register(g_userSettings.game.gyroAimSensitivityX); - Register(g_userSettings.game.gyroAimSensitivityY); - Register(g_userSettings.game.gyroAimInvertPitch); - Register(g_userSettings.game.gyroAimInvertYaw); + Register(g_userSettings.game.enableGyroRollgoal); + Register(g_userSettings.game.gyroSensitivityX); + Register(g_userSettings.game.gyroSensitivityY); + Register(g_userSettings.game.gyroSensitivityRollgoal); + Register(g_userSettings.game.gyroDeadband); + Register(g_userSettings.game.gyroSmoothing); + Register(g_userSettings.game.gyroInvertPitch); + Register(g_userSettings.game.gyroInvertYaw); Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.graphicsBackend); @@ -145,6 +154,7 @@ void registerSettings() { Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.wasPresetChosen); Register(g_userSettings.backend.enableCrashReporting); + Register(g_userSettings.backend.duskMenuOpen); } // Transient settings diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 84d2ab8e97..93a9148957 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -725,7 +725,6 @@ void fapGm_After() { #ifdef TARGET_PC static void fapGm_Before() { - dusk::frame_interp::begin_record_camera(); dusk::frame_interp::begin_record(); } diff --git a/src/m_Do/m_Do_ext.cpp b/src/m_Do/m_Do_ext.cpp index 577ada0837..024dd596a8 100644 --- a/src/m_Do/m_Do_ext.cpp +++ b/src/m_Do/m_Do_ext.cpp @@ -2383,8 +2383,8 @@ void mDoExt_3DlineMat0_c::draw() { int var_r26 = (field_0x14 << 1) & 0xFFFF; for (int i = 0; i < field_0x10; i++) { - GXSETARRAY(GX_VA_POS, field_0x18->field_0x8[field_0x16], sizeof(cXyz) * var_r26, sizeof(cXyz), true); - GXSETARRAY(GX_VA_NRM, field_0x18->field_0x10[field_0x16], 3 * var_r26, 3, true); + GXSETARRAY(GX_VA_POS, var_r28->field_0x8[field_0x16], sizeof(cXyz) * var_r26, sizeof(cXyz), true); + GXSETARRAY(GX_VA_NRM, var_r28->field_0x10[field_0x16], 3 * var_r26, 3, true); GXBegin(GX_TRIANGLESTRIP, GX_VTXFMT0, var_r26); for (u16 j = 0; j < (u16)var_r26; j++) { diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 92c8707782..d774a7de7d 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -1344,6 +1344,11 @@ void mDoGph_gInf_c::bloom_c::draw2() { static_cast(prev.h / 2), }; } + for (int i = 0; i < ARRAY_SIZE(divRects); i++) { + auto & rect = divRects[i]; + if (rect.w == 0) rect.w = 1; + if (rect.h == 0) rect.h = 1; + } auto divCopySrc = [&](int divNo) { auto const& rect = divRects[divNo]; @@ -1390,7 +1395,7 @@ void mDoGph_gInf_c::bloom_c::draw2() { if (enabled) { GXCreateFrameBuffer(width * 0.75f, height * 0.5f); - GXSetViewport(0.0f, 0.0f, width, height, 0.1f, 1.0f); // use oversized viewport to make the math easier + GXSetViewport(0.0f, 0.0f, width, height, 0.0f, 1.0f); // use oversized viewport to make the math easier GXSetNumTevStages(3); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); @@ -1432,16 +1437,16 @@ void mDoGph_gInf_c::bloom_c::draw2() { // Setup blur filter TEV. GXSetNumTexGens(8); - u32 texMtxID = GX_TEXMTX0; - int angle = 0; - for (int texCoord = (int)GX_TEXCOORD0; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { - GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); - mDoMtx_stack_c::transS((blurScale * cM_scos(angle)) * getInvScale(), - blurScale * cM_ssin(angle), 0.0f); - GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4); - texMtxID += 3; - angle += 0x2000; - } + u32 texMtxID = GX_TEXMTX0; + int angle = 0; + for (int texCoord = (int)GX_TEXCOORD0; texCoord < (int)GX_MAX_TEXCOORD; texCoord++) { + GXSetTexCoordGen((GXTexCoordID)texCoord, GX_TG_MTX2x4, GX_TG_TEX0, texMtxID); + mDoMtx_stack_c::transS((blurScale * cM_scos(angle)) * getInvScale(), + blurScale * cM_ssin(angle), 0.0f); + GXLoadTexMtxImm(mDoMtx_stack_c::get(), texMtxID, GX_MTX2x4); + texMtxID += 3; + angle += 0x2000; + } GXSetNumTevStages(8); for (int stage = 0; stage < 8; stage++) { @@ -1488,7 +1493,7 @@ void mDoGph_gInf_c::bloom_c::draw2() { GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_OR); for (int i = divNum; i > divStart; i--) { - float alpha = 255.0f * powf(0.25f * dusk::getSettings().game.bloomMultiplier.getValue(), 1.0f / (divNum - i + 1)); + float alpha = 255.0f * powf(0.25f * dusk::getSettings().game.bloomMultiplier.getValue(), 1.0f / (i - divStart + 1)); GXSetTevColorS10(GX_TEVREG0, {0, 0, 0, s16(alpha)}); divCopySrc(i); @@ -1503,7 +1508,6 @@ void mDoGph_gInf_c::bloom_c::draw2() { GXLoadTexObj(texFinal, GX_TEXMAP0); GXRestoreFrameBuffer(); - GXSetViewport(0.0f, 0.0f, width, height, 0.0f, 1.0f); // Now blend our bloom into the real FB. GXSetTevColor(GX_TEVREG0, mBlendColor); @@ -2229,13 +2233,11 @@ int mDoGph_Painter() { #if TARGET_PC if (dusk::getSettings().game.enableFrameInterpolation) { - cXyz pres_eye; - dusk::frame_interp::camera_eye_from_view_mtx(j3dSys.getViewMtx(), &pres_eye); // FRAME INTERP NOTE: Currently only recalculating points for Epona's reins. Need a more global solution. if (daHorse_c* horse = dComIfGp_getHorseActor()) { horse->lerpControlPoints(dusk::frame_interp::get_interpolation_step()); } - g_dComIfG_gameInfo.drawlist.refresh3DlineMats(pres_eye); + g_dComIfG_gameInfo.drawlist.refresh3DlineMats(camera_p->view.lookat.eye); } #endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 3568e044e1..715f9d3c84 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -51,7 +51,7 @@ #include "dusk/crash_reporting.h" #include "dusk/dusk.h" #include "dusk/frame_interpolation.h" -#include "dusk/gyro_aim.h" +#include "dusk/gyro.h" #include "dusk/imgui/ImGuiEngine.hpp" #include "dusk/logging.h" #include "dusk/main.h" @@ -245,23 +245,22 @@ void main01(void) { dusk::frame_interp::notify_presentation_frame(); while (accumulator >= kSimStepSeconds) { mDoCPd_c::read(); - if (dusk::getSettings().game.enableGyroAim) { - dusk::gyro_aim::read(static_cast(kSimStepSeconds), dusk::gyro_aim::queryGyroAimItemContext()); - } + dusk::gyro::read(kSimStepSeconds); fapGm_Execute(); mDoAud_Execute(); accumulator -= kSimStepSeconds; } dusk::frame_interp::interpolate(static_cast(accumulator / kSimStepSeconds)); - cAPIGph_Painter(); + { + dusk::frame_interp::PresentationCameraScope presentation_camera; + cAPIGph_Painter(); + } } else { accumulator = 0.0; // Game Inputs mDoCPd_c::read(); - if (dusk::getSettings().game.enableGyroAim) { - dusk::gyro_aim::read(static_cast(frame_seconds), dusk::gyro_aim::queryGyroAimItemContext()); - } + dusk::gyro::read(frame_seconds); // EXECUTE GAME LOGIC & RENDER // This calls mDoGph_Painter -> JFWDisplay -> GX Functions