From ef64ac08f82d43708966b799e0ae07c4cd1f8509 Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sun, 12 Apr 2026 11:22:16 -0400 Subject: [PATCH 1/3] No Heart Drops --- include/dusk/settings.h | 1 + src/dusk/imgui/ImGuiMenuEnhancements.cpp | 6 ++++++ src/dusk/settings.cpp | 2 ++ src/f_op/f_op_actor_mng.cpp | 18 ++++++++++++++++++ 4 files changed, 27 insertions(+) diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 8418602251..a5642d6036 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -40,6 +40,7 @@ struct UserSettings { ConfigVar disableRupeeCutscenes; ConfigVar noSwordRecoil; ConfigVar damageMultiplier; + ConfigVar noHeartDrops; ConfigVar instantDeath; ConfigVar fastClimbing; ConfigVar noMissClimbing; diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index b505d84345..bef252467d 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -131,6 +131,12 @@ namespace dusk { 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."); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index dabed72f19..cc55403ddd 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -28,6 +28,7 @@ UserSettings g_userSettings = { .disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, .noSwordRecoil {"game.noSwordRecoil", false}, .damageMultiplier {"game.damageMultiplier", 1}, + .noHeartDrops{"game.noHeartDrops", false}, .instantDeath {"game.instantDeath", false}, .fastClimbing {"game.fastClimbing", false}, .noMissClimbing {"game.noMissClimbing", false}, @@ -96,6 +97,7 @@ void registerSettings() { Register(g_userSettings.game.disableRupeeCutscenes); Register(g_userSettings.game.noSwordRecoil); Register(g_userSettings.game.damageMultiplier); + Register(g_userSettings.game.noHeartDrops); Register(g_userSettings.game.instantDeath); Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastTears); diff --git a/src/f_op/f_op_actor_mng.cpp b/src/f_op/f_op_actor_mng.cpp index dca07f98ba..60d21123cb 100644 --- a/src/f_op/f_op_actor_mng.cpp +++ b/src/f_op/f_op_actor_mng.cpp @@ -1631,6 +1631,12 @@ fpc_ProcID fopAcM_createItem(const cXyz* i_pos, int i_itemNo, int i_itemBitNo, i JUT_ASSERT(4067, 0 <= i_itemNo && i_itemNo < 256 && (-1 <= i_itemBitNo && i_itemBitNo < (dSv_info_c::DAN_ITEM + dSv_info_c::MEMORY_ITEM + dSv_info_c::ZONE_ITEM )) || i_itemBitNo == 255); // clang-format on + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return fpcM_ERROR_PROCESS_ID_e; + } + #endif + if (i_itemNo == dItemNo_NONE_e) { return fpcM_ERROR_PROCESS_ID_e; } @@ -1695,6 +1701,12 @@ fopAc_ac_c* fopAcM_fastCreateItem2(const cXyz* i_pos, int i_itemNo, int i_itemBi JUT_ASSERT(4202, 0 <= i_itemNo && i_itemNo < 256 && (-1 <= i_itemBitNo && i_itemBitNo < (dSv_info_c::DAN_ITEM + dSv_info_c::MEMORY_ITEM + dSv_info_c::ZONE_ITEM )) || i_itemBitNo == 255); // clang-format on + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return NULL; + } + #endif + csXyz item_angle(csXyz::Zero); if (i_itemNo == dItemNo_NONE_e) { @@ -1761,6 +1773,12 @@ fopAc_ac_c* fopAcM_fastCreateItem(const cXyz* i_pos, int i_itemNo, int i_roomNo, f32* i_speedY, int i_itemBitNo, int param_9, createFunc i_createFunc) { JUT_ASSERT(4324, 0 <= i_itemNo && i_itemNo < 256); + + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return NULL; + } + #endif csXyz angle; if (i_itemNo == dItemNo_NONE_e) { From e816d1bdd96024303d40beac44726a5d526b365f Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sun, 12 Apr 2026 11:39:56 -0400 Subject: [PATCH 2/3] Allow hearts to spawn from girls in castle town & yeti love cutscene --- src/f_op/f_op_actor_mng.cpp | 54 ++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/f_op/f_op_actor_mng.cpp b/src/f_op/f_op_actor_mng.cpp index 60d21123cb..caaf01e1da 100644 --- a/src/f_op/f_op_actor_mng.cpp +++ b/src/f_op/f_op_actor_mng.cpp @@ -1390,6 +1390,12 @@ fpc_ProcID fopAcM_createItemForPresentDemo(cXyz const* i_pos, int i_itemNo, u8 p JUT_ASSERT(3214, 0 <= i_itemNo && i_itemNo < 256); dComIfGp_event_setGtItm(i_itemNo); + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return fpcM_ERROR_PROCESS_ID_e; + } + #endif + if (i_itemNo == dItemNo_NONE_e) { OS_REPORT("プレゼントデモ用なのに「ハズレ」です![%d]\n", i_itemNo); // Even though it is for a Present Demo, it is a 'Miss'! return fpcM_ERROR_PROCESS_ID_e; @@ -1404,6 +1410,12 @@ fpc_ProcID fopAcM_createItemForTrBoxDemo(cXyz const* i_pos, int i_itemNo, int i_ JUT_ASSERT(3259, 0 <= i_itemNo && i_itemNo < 256); dComIfGp_event_setGtItm(i_itemNo); + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return fpcM_ERROR_PROCESS_ID_e; + } + #endif + if (i_itemNo == dItemNo_NONE_e) { OS_REPORT("ゲットデモ用なのに「ハズレ」です![%d]\n", i_itemNo); // Even though it is for a Get Demo, it is a 'Miss'! return fpcM_ERROR_PROCESS_ID_e; @@ -1529,6 +1541,12 @@ fpc_ProcID fopAcM_createItemFromTable(cXyz const* i_pos, int i_itemNo, int i_ite JUT_ASSERT(3655, 0 <= i_itemNo && i_itemNo <= 255 && (-1 <= i_itemBitNo && i_itemBitNo < (dSv_info_c::DAN_ITEM + dSv_info_c::MEMORY_ITEM + dSv_info_c::ZONE_ITEM )) || i_itemBitNo == 255); // clang-format on + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return fpcM_ERROR_PROCESS_ID_e; + } + #endif + u8 tableNum; ItemTableList* tableList; tableList = (ItemTableList*)dComIfGp_getItemTable(); @@ -1572,6 +1590,12 @@ fpc_ProcID fopAcM_createDemoItem(const cXyz* i_pos, int i_itemNo, int i_itemBitN JUT_ASSERT(3824, 0 <= i_itemNo && i_itemNo < 256 && (-1 <= i_itemBitNo && i_itemBitNo < (dSv_info_c::DAN_ITEM + dSv_info_c::MEMORY_ITEM + dSv_info_c::ZONE_ITEM )) || i_itemBitNo == 255); // clang-format on + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return NULL; + } + #endif + if (i_itemNo == dItemNo_NONE_e) { return fpcM_ERROR_PROCESS_ID_e; } @@ -1583,6 +1607,12 @@ fpc_ProcID fopAcM_createDemoItem(const cXyz* i_pos, int i_itemNo, int i_itemBitN fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF, f32 i_speedY, int param_8) { + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return NULL; + } + #endif + int _ = -1; u32 params = 0xFFFF0000 | param_8 << 8 | (i_itemNo & 0xFF); @@ -1599,6 +1629,12 @@ fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomN fpc_ProcID fopAcM_createItemForMidBoss(const cXyz* i_pos, int i_itemNo, int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, int param_6, int param_7) { + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return NULL; + } + #endif + UNUSED(i_angle); UNUSED(param_6); fpc_ProcID ret = -1; @@ -1610,6 +1646,12 @@ fpc_ProcID fopAcM_createItemForMidBoss(const cXyz* i_pos, int i_itemNo, int i_ro fopAc_ac_c* fopAcM_createItemForDirectGet(const cXyz* i_pos, int i_itemNo, int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF, f32 i_speedY) { + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return NULL; + } + #endif + fopAc_ac_c* item = fopAcM_fastCreateItem(i_pos, i_itemNo, i_roomNo, i_angle, i_scale, &i_speedF, &i_speedY, -1, 0x7, NULL); fopAc_ac_c* ret = item; @@ -1619,6 +1661,12 @@ fopAc_ac_c* fopAcM_createItemForDirectGet(const cXyz* i_pos, int i_itemNo, int i fopAc_ac_c* fopAcM_createItemForSimpleDemo(const cXyz* i_pos, int i_itemNo, int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF, f32 i_speedY) { + #if TARGET_PC + if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { + return NULL; + } + #endif + fopAc_ac_c* item = fopAcM_fastCreateItem(i_pos, i_itemNo, i_roomNo, i_angle, i_scale, &i_speedF, &i_speedY, -1, 0x4, NULL); fopAc_ac_c* ret = item; @@ -1773,12 +1821,6 @@ fopAc_ac_c* fopAcM_fastCreateItem(const cXyz* i_pos, int i_itemNo, int i_roomNo, f32* i_speedY, int i_itemBitNo, int param_9, createFunc i_createFunc) { JUT_ASSERT(4324, 0 <= i_itemNo && i_itemNo < 256); - - #if TARGET_PC - if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { - return NULL; - } - #endif csXyz angle; if (i_itemNo == dItemNo_NONE_e) { From fe44e3adfffa07a35ebbd2e141d7df9b7407503a Mon Sep 17 00:00:00 2001 From: MelonSpeedruns Date: Sun, 12 Apr 2026 12:21:06 -0400 Subject: [PATCH 3/3] replaced wrong NULLs with error process id --- src/f_op/f_op_actor_mng.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/f_op/f_op_actor_mng.cpp b/src/f_op/f_op_actor_mng.cpp index caaf01e1da..208e868db2 100644 --- a/src/f_op/f_op_actor_mng.cpp +++ b/src/f_op/f_op_actor_mng.cpp @@ -1592,7 +1592,7 @@ fpc_ProcID fopAcM_createDemoItem(const cXyz* i_pos, int i_itemNo, int i_itemBitN #if TARGET_PC if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { - return NULL; + return fpcM_ERROR_PROCESS_ID_e; } #endif @@ -1609,7 +1609,7 @@ fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomN f32 i_speedY, int param_8) { #if TARGET_PC if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { - return NULL; + return fpcM_ERROR_PROCESS_ID_e; } #endif @@ -1631,7 +1631,7 @@ fpc_ProcID fopAcM_createItemForMidBoss(const cXyz* i_pos, int i_itemNo, int i_ro int param_7) { #if TARGET_PC if (dusk::getSettings().game.noHeartDrops && isHeart(i_itemNo)) { - return NULL; + return fpcM_ERROR_PROCESS_ID_e; } #endif