diff --git a/extern/aurora b/extern/aurora index 1d10fa1bc5..1f84646de4 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 1d10fa1bc502910a6336fdac32f31cd0ac39710d +Subproject commit 1f84646de4c35483e580b07ab5c537c681a55e5e diff --git a/files.cmake b/files.cmake index 6fc4762be3..675ba70fdc 100644 --- a/files.cmake +++ b/files.cmake @@ -1436,6 +1436,7 @@ set(DUSK_FILES src/dusk/gamepad_color.cpp src/dusk/globals.cpp src/dusk/gyro.cpp + src/dusk/hq_minimap.cpp src/dusk/imgui/ImGuiActorSpawner.cpp src/dusk/imgui/ImGuiBloomWindow.cpp src/dusk/imgui/ImGuiBloomWindow.hpp diff --git a/include/d/actor/d_a_npc_zrc.h b/include/d/actor/d_a_npc_zrc.h index f4aeee6ac9..6f6a00061b 100644 --- a/include/d/actor/d_a_npc_zrc.h +++ b/include/d/actor/d_a_npc_zrc.h @@ -96,7 +96,7 @@ public: static DUSK_GAME_DATA char DUSK_CONST* DUSK_CONST mEvtCutNameList[2]; static DUSK_GAME_DATA EventFn DUSK_CONST mEvtCutList[2]; -private: +// private: /* 0xB48 */ Z2Creature mCreatureSound; /* 0xBD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0xBDC */ daNpcF_Lookat_c mLookat; diff --git a/include/d/actor/d_a_npc_zrz.h b/include/d/actor/d_a_npc_zrz.h index 76c782b7d3..9e03215aa4 100644 --- a/include/d/actor/d_a_npc_zrz.h +++ b/include/d/actor/d_a_npc_zrz.h @@ -114,7 +114,7 @@ public: void himoCalc(); void adjustShapeAngle() {} -private: +// private: /* 0x0B48 */ Z2Creature mCreatureSound; /* 0x0BD8 */ daNpcF_MatAnm_c* mpMatAnm; /* 0x0BDC */ mDoExt_invisibleModel mInvisibleModel; diff --git a/include/d/actor/d_a_obj_bosswarp.h b/include/d/actor/d_a_obj_bosswarp.h index 9b62f1a0f6..71b5b2b58f 100644 --- a/include/d/actor/d_a_obj_bosswarp.h +++ b/include/d/actor/d_a_obj_bosswarp.h @@ -60,7 +60,7 @@ public: bool isFirst() { return fopAcM_GetParamBit(this, 0x1b, 1); } void setAction(u8 action) { mAction = action; } -private: +// private: /* 0x568 */ request_of_phase_process_class mPhaseReq; /* 0x570 */ J3DModel* mpModel; /* 0x574 */ mDoExt_btkAnm* mpBtkAnm[2]; diff --git a/include/d/actor/d_a_tbox2.h b/include/d/actor/d_a_tbox2.h index 9b06e685a3..5e3989db57 100644 --- a/include/d/actor/d_a_tbox2.h +++ b/include/d/actor/d_a_tbox2.h @@ -67,7 +67,7 @@ public: u8 getModelType() { return fopAcM_GetParamBit(this, 8, 4); } u8 getItemNo() { return fopAcM_GetParamBit(this, 0, 8); } -private: +// private: /* 0x718 */ u8 mReturnRupee; /* 0x71C */ request_of_phase_process_class mPhase; /* 0x724 */ J3DModel* mpModel; diff --git a/libs/JSystem/src/JFramework/JFWDisplay.cpp b/libs/JSystem/src/JFramework/JFWDisplay.cpp index 0c26e77a53..466335eae9 100644 --- a/libs/JSystem/src/JFramework/JFWDisplay.cpp +++ b/libs/JSystem/src/JFramework/JFWDisplay.cpp @@ -38,7 +38,7 @@ void JFWDisplay::ctor_subroutine(bool enableAlpha) { mTickRate = 0; mCombinationRatio = 0.0f; field_0x30 = 0; - field_0x2c = OSGetTick(); + field_0x2c = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); field_0x34 = 0; field_0x48 = 0; field_0x4a = 0; @@ -258,7 +258,7 @@ void JFWDisplay::beginRender() { waitForTick(mTickRate, mFrameRate); JUTVideo::getManager()->waitRetraceIfNeed(); - OSTick tick = OSGetTick(); + OSTick tick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); field_0x30 = tick - field_0x2c; field_0x2c = tick; field_0x34 = field_0x2c - JUTVideo::getVideoLastTick(); @@ -371,7 +371,7 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast(sleepTime) / static_cast(targetNs)); } @@ -381,15 +381,12 @@ static void waitForTick(u32 p1, u16 p2) { #if TARGET_PC static Limiter limiter; - if (dusk::frame_interp::is_enabled() && !dusk::getTransientSettings().skipFrameRateLimit) { - dusk::frameUsagePct = 0.f; - return; + if (dusk::frame_interp::is_enabled() || dusk::getTransientSettings().turboMode) { + limiter.Reset(); + dusk::frameUsagePct = 0.f; + return; } - if (dusk::getTransientSettings().skipFrameRateLimit) { - p1 = OS_TIMER_CLOCK / 120; - } - if (fopOvlpM_IsPeek() && dusk::getTransientSettings().stateShareLoadActive) { return; } @@ -399,7 +396,6 @@ static void waitForTick(u32 p1, u16 p2) { if (p1 != 0) { #if TARGET_PC - static Limiter limiter; waitPrecise(limiter, static_cast(OSTicksToMicroseconds(p1)) * 1000ULL); #else static OSTime nextTick = OSGetTime(); @@ -413,7 +409,6 @@ static void waitForTick(u32 p1, u16 p2) { } else { u32 uVar1 = (p2 == 0) ? 1 : p2; #if TARGET_PC - static Limiter limiter; waitPrecise(limiter, static_cast((RETRACE_PERIOD * uVar1).count())); #else static u32 nextCount = VIGetRetraceCount(); diff --git a/libs/JSystem/src/JUtility/JUTVideo.cpp b/libs/JSystem/src/JUtility/JUTVideo.cpp index 2135c8fc22..b9a51ab6f3 100644 --- a/libs/JSystem/src/JUtility/JUTVideo.cpp +++ b/libs/JSystem/src/JUtility/JUTVideo.cpp @@ -42,7 +42,7 @@ JUTVideo::JUTVideo(GXRenderModeObj const* param_0) { mRetraceCount = VIGetRetraceCount(); field_0x10 = 1; field_0x18 = 0; - sVideoLastTick = OSGetTick(); + sVideoLastTick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); sVideoInterval = 670000; mPreRetraceCallback = VISetPreRetraceCallback(preRetraceProc); mPostRetraceCallback = VISetPostRetraceCallback(postRetraceProc); @@ -68,7 +68,7 @@ void JUTVideo::preRetraceProc(u32 retrace_count) { (*sManager->mPreCallback)(retrace_count); } - OSTick tick = OSGetTick(); + OSTick tick = DUSK_IF_ELSE(static_cast(OSGetNativeTime()), OSGetTick()); sVideoInterval = tick - sVideoLastTick; sVideoLastTick = tick; diff --git a/mods/randomizer/src/hooks.cpp b/mods/randomizer/src/hooks.cpp index ff15469e6e..28c71adc20 100644 --- a/mods/randomizer/src/hooks.cpp +++ b/mods/randomizer/src/hooks.cpp @@ -11,32 +11,42 @@ #include #include +#include "c/c_damagereaction.h" #include "d/actor/d_a_alink.h" #include "d/actor/d_a_b_bq.h" +#include "d/actor/d_a_demo_item.h" #include "d/actor/d_a_door_shutter.h" #include "d/actor/d_a_e_md.h" #include "d/actor/d_a_e_mk.h" #include "d/actor/d_a_kytag08.h" +#include "d/actor/d_a_mg_rod.h" #include "d/actor/d_a_npc4.h" #include "d/actor/d_a_npc_bans.h" -#include "d/actor/d_a_tag_kmsg.h" #include "d/actor/d_a_npc_fairy.h" -#include "d/actor/d_a_obj_master_sword.h" #include "d/actor/d_a_npc_shad.h" #include "d/actor/d_a_npc_yelia.h" #include "d/actor/d_a_npc_ykm.h" #include "d/actor/d_a_npc_ykw.h" +#include "d/actor/d_a_npc_zrc.h" +#include "d/actor/d_a_npc_zrz.h" +#include "d/actor/d_a_obj_bosswarp.h" +#include "d/actor/d_a_obj_master_sword.h" +#include "d/actor/d_a_obj_swBallC.h" +#include "d/actor/d_a_obj_zra_rock.h" +#include "d/actor/d_a_tag_kmsg.h" +#include "d/actor/d_a_tbox2.h" #include "d/d_door_param2.h" #include "d/d_file_sel_info.h" #include "d/d_file_select.h" +#include "d/d_gameover.h" #include "d/d_meter2_info.h" #include "d/d_msg_object.h" +#include "d/d_s_name.h" +#include "d/d_s_play.h" #include "d/d_save.h" #include "d/d_shop_system.h" -#include "d/d_s_name.h" #include "f_op/f_op_overlap_mng.h" #include "m_Do/m_Do_Reset.h" -#include "c/c_damagereaction.h" DEFINE_HOOK(&dFile_select_c::selectDataNameMove, dFile_select_c__selectDataNameMove); DEFINE_HOOK(&dFile_select_c::dataSelect, dFile_select_c__dataSelect); @@ -88,6 +98,10 @@ DEFINE_HOOK(&checkItemGet, dItem_checkItemGet); DEFINE_HOOK(&daAlink_c::decideDoStatus, daAlink_c__decideDoStatus); DEFINE_HOOK_SYMBOL("daAlink_searchBouDoor", void*(fopAc_ac_c*, void*), searchBouDoor); DEFINE_HOOK(&daAlink_c::checkGroundSpecialMode, daAlink_c__checkGroundSpecialMode); +DEFINE_HOOK(&daAlink_c::setGetItemFace, daAlink_c__setGetItemFace); +DEFINE_HOOK(&daAlink_c::setGetSubBgm, daAlink_c__setGetSubBgm); +DEFINE_HOOK(&daAlink_c::procCoGetItem, daAlink_c__procCoGetItem); +DEFINE_HOOK(&daAlink_c::procCoWarpInit, daAlink_c__procCoWarpInit); DEFINE_HOOK_SYMBOL("b_bq_end", void(b_bq_class*), bq_end); @@ -119,11 +133,46 @@ DEFINE_HOOK(&daNpc_ykW_c::isDelete, daNpc_ykW_c__isDelete); DEFINE_HOOK_SYMBOL("daE_MD_Create", int(fopAc_ac_c*), daE_MD_c__create); -DEFINE_HOOK(&daObjMasterSword_c::executeWait, daObjMasterSword_c__executeWait); +DEFINE_HOOK(&fopAcM_orderMapToolEvent, orderMapToolEvent); DEFINE_HOOK_SYMBOL("daObjMasterSword_Execute", int(daObjMasterSword_c*), daObjMasterSword_c__execute); DEFINE_HOOK(&dScnName_c::changeGameScene, dScnName_c__changeGameScene); +DEFINE_HOOK(&daNpc_zrZ_c::isDelete, daNpc_zrZ_c__isDelete); + +DEFINE_HOOK(&daTbox2_c::Create, daTbox2_c__Create); +DEFINE_HOOK(&daTbox2_c::setGetDemoItem, daTbox2_c__setGetDemoItem); + +DEFINE_HOOK(&dGameover_c::_create, dGameover_c___create); + +DEFINE_HOOK(&daObjSwBallC_c::Create, daObjSwBallC_c__Create); +DEFINE_HOOK(&daObjSwBallC_c::actionWait, daObjSwBallC_c__actionWait); + +DEFINE_HOOK_SYMBOL("daDitem_Execute", int(daDitem_c*), daDitem_c__execute); + +DEFINE_HOOK(&daObjBossWarp_c::demoProc, daObjBossWarp_c__demoProc); + +DEFINE_HOOK_SYMBOL("lure_heart", void(dmg_rod_class*), mgRod_lure_heart); +DEFINE_HOOK_SYMBOL("uki_catch", void(dmg_rod_class*), mgRod_uki_catch); + +#ifdef _MSVC_LANG +#define setEmptyBottle_noarg_sig "?setEmptyBottle@dSv_player_item_c@@QEAAXXZ" +#else +#define setEmptyBottle_noarg_sig "_ZN17dSv_player_item_c14setEmptyBottleEv" +#endif +DEFINE_HOOK_SYMBOL(setEmptyBottle_noarg_sig, void(dSv_player_item_c*), dSv_player_item_c__setEmptyBottle); + +DEFINE_HOOK(&daNpc_zrC_c::isDelete, daNpc_zrC_c__isDelete); + +DEFINE_HOOK(&daObjZraRock_c::create, daObjZraRock_c__create); + +#ifdef _MSVC_LANG +#define phase_1_dScnPly_sig "?phase_1@@YAHPAUdScnPly_c@@@Z" // sig is wrong? +#else +#define phase_1_dScnPly_sig "_Z7phase_1P9dScnPly_c" +#endif +// DEFINE_HOOK_SYMBOL(phase_1_dScnPly_sig, int(dScnPly_c*), phase_1__dScnPly_c); + namespace randomizer::ui { dialogSelectModeState g_dialogSelectModeState = SelectReady; } @@ -576,15 +625,49 @@ void hookPostSetLineUpItem(ModContext*, void* args, void*, void*) { } HookAction hookPreSaveInfoOnSwitch(ModContext*, void* args, void*, void*) { + auto* i_this = mods::arg(args, 0); + const int i_no = mods::arg(args, 1); + const int room_no = mods::arg(args, 2); + // Set custom flag for the Temple of Time pedestal strike - if (getStageID() == Sacred_Grove && mods::arg(args, 1) == 0xEE) { - mods::arg(args, 0)->onSwitch(0x63, mods::arg(args, 2)); + if (getStageID() == Sacred_Grove && i_no == 0xEE) { + i_this->onSwitch(0x63, room_no); + } + + // We check to see if the flag being set is for the UZR portal as a safety precaution. + if (daAlink_c::checkStageName("F_SP126") && i_no == 0x15 && + dComIfGs_getTransformStatus() == TF_STATUS_WOLF) + { + // Set the flag to make Iza 1 available and set the memory bit for having talked to her + // after opening the portal as human. + dComIfGs_onEventBit(0xB02); + i_this->onSwitch(0x37, room_no); + + // Note for the above stuff. This works for now. Eventually would like to adjust this to + // a FLW patch since I think we could accomplish similar results by having the conversation + // continue as normal regardless of form, but I haven't looked into it that much. } return HOOK_CONTINUE; } +bool hookLureHeart_isSkipGetItem = false; +HookAction hookPreLureHeart(ModContext*, void* args, void*, void*) { + hookLureHeart_isSkipGetItem = true; + return HOOK_CONTINUE; +} + +void hookPostLureHeart(ModContext*, void*, void*, void*) { + hookLureHeart_isSkipGetItem = false; +} + HookAction hookPreGetItemFunc(ModContext*, void* args, void*, void*) { const u8 item = mods::arg(args, 0); + + // coming from lure_heart, don't give item here. let FLW message handle it + if (hookLureHeart_isSkipGetItem && item == dItemNo_KAKERA_HEART_e) { + return HOOK_SKIP_ORIGINAL; + } + item::exec_item_get(item); return HOOK_SKIP_ORIGINAL; } @@ -1326,6 +1409,396 @@ HookAction hookPreCheckGroundSpecialMode(ModContext*, void* args, void* retval, return HOOK_CONTINUE; } +void hookPostSetGetItemFace(ModContext*, void* args, void*, void*) { + auto* i_this = mods::arg(args, 0); + const u16 i_itemNo = mods::arg(args, 1); + + switch (i_itemNo) { + case dItemNo_Randomizer_WOOD_STICK_e: + case dItemNo_Randomizer_SWORD_e: + case dItemNo_Randomizer_SHIELD_e: + case dItemNo_Randomizer_MASTER_SWORD_e: + case dItemNo_Randomizer_LIGHT_SWORD_e: + case dItemNo_Randomizer_MAGIC_LV1_e: + i_this->setFaceBasicBck(dRes_ID_ALANM_BCK_FI_e); + break; + case dItemNo_Randomizer_FOOLISH_ITEM_e: + i_this->setFaceBasicBck(dRes_ID_ALANM_BCK_FJ_e); + break; + } +} + +HookAction hookPreSetGetSubBgm(ModContext*, void* args, void*, void*) { + enum { + SETYPE_HEART, + SETYPE_ITEM_GET, + SETYPE_ITEM_GET_MINI, + SETYPE_ITEM_GET_ME, + SETYPE_ITEM_GET_INSECT, + SETYPE_ITEM_GET_SMELL, + SETYPE_ITEM_GET_POU, + SETYPE_ITEM_GET_ME_S, + SETYPE_NONE, + }; + + static constexpr u8 getSeTypeRandomizer[255] = { + /* dItemNo_Randomizer_HEART_e */ SETYPE_NONE, + /* dItemNo_Randomizer_GREEN_RUPEE_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_BLUE_RUPEE_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_YELLOW_RUPEE_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_RED_RUPEE_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_PURPLE_RUPEE_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_ORANGE_RUPEE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_SILVER_RUPEE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_S_MAGIC_e */ SETYPE_NONE, + /* dItemNo_Randomizer_L_MAGIC_e */ SETYPE_NONE, + /* dItemNo_Randomizer_BOMB_5_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_BOMB_10_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_BOMB_20_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_BOMB_30_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_ARROW_10_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_ARROW_20_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_ARROW_30_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_ARROW_1_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_PACHINKO_SHOT_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_FOOLISH_ITEM_e */ SETYPE_NONE, + /* dItemNo_Randomizer_NOENTRY_20_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_21_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WATER_BOMB_5_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_WATER_BOMB_10_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_WATER_BOMB_20_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_WATER_BOMB_30_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_BOMB_INSECT_5_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_BOMB_INSECT_10_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_BOMB_INSECT_20_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_BOMB_INSECT_30_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_RECOVERY_FAILY_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_TRIPLE_HEART_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_SMALL_KEY_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_KAKERA_HEART_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_UTAWA_HEART_e */ SETYPE_HEART, + /* dItemNo_Randomizer_MAP_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_COMPUS_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_DUNGEON_EXIT_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_BOSS_KEY_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_DUNGEON_BACK_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_SWORD_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_MASTER_SWORD_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_WOOD_SHIELD_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_SHIELD_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_HYLIA_SHIELD_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_TKS_LETTER_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WEAR_CASUAL_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WEAR_KOKIRI_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_ARMOR_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_WEAR_ZORA_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_MAGIC_LV1_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_DUNGEON_EXIT_2_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WALLET_LV1_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WALLET_LV2_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_WALLET_LV3_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_55_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_56_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_57_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_58_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_59_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_60_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_ZORAS_JEWEL_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_HAWK_EYE_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_WOOD_STICK_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_BOOMERANG_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_SPINNER_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_IRONBALL_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_BOW_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_HOOKSHOT_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_HVY_BOOTS_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_COPY_ROD_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_W_HOOKSHOT_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_KANTERA_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_LIGHT_SWORD_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_FISHING_ROD_1_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_PACHINKO_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_COPY_ROD_2_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_77_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_78_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_BOMB_BAG_LV2_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_BOMB_BAG_LV1_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_BOMB_IN_BAG_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_82_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LIGHT_ARROW_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_ARROW_LV1_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_ARROW_LV2_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_ARROW_LV3_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_87_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LURE_ROD_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_BOMB_ARROW_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_HAWK_ARROW_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_BEE_ROD_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_JEWEL_ROD_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WORM_ROD_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_JEWEL_BEE_ROD_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_JEWEL_WORM_ROD_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_EMPTY_BOTTLE_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_RED_BOTTLE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_GREEN_BOTTLE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_BLUE_BOTTLE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_MILK_BOTTLE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_HALF_MILK_BOTTLE_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_OIL_BOTTLE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WATER_BOTTLE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_OIL_BOTTLE_2_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_RED_BOTTLE_2_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_UGLY_SOUP_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_HOT_SPRING_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_FAIRY_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_HOT_SPRING_2_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_OIL2_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_OIL_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NORMAL_BOMB_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WATER_BOMB_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_POKE_BOMB_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_FAIRY_DROP_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_WORM_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_DROP_BOTTLE_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_BEE_CHILD_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_CHUCHU_RARE_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_CHUCHU_RED_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_CHUCHU_BLUE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_CHUCHU_GREEN_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_CHUCHU_YELLOW_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_CHUCHU_PURPLE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LV1_SOUP_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LV2_SOUP_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LV3_SOUP_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LETTER_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_BILL_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_WOOD_STATUE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_IRIAS_PENDANT_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_HORSE_FLUTE_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_133_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_134_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_135_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_136_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_137_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_138_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_139_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_140_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_141_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_142_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_143_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_RAFRELS_MEMO_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_ASHS_SCRIBBLING_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_146_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_147_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_148_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_149_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_150_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_151_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_152_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_153_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_154_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_155_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_CHUCHU_YELLOW2_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_OIL_BOTTLE3_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_SHOP_BEE_CHILD_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_CHUCHU_BLACK_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LIGHT_DROP_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_DROP_CONTAINER_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_DROP_CONTAINER02_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_DROP_CONTAINER03_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_FILLED_CONTAINER_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_MIRROR_PIECE_2_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_MIRROR_PIECE_3_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_MIRROR_PIECE_4_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_168_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_169_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_170_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_171_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_172_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_173_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_174_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_175_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_SMELL_YELIA_POUCH_e */ SETYPE_ITEM_GET_SMELL, + /* dItemNo_Randomizer_SMELL_PUMPKIN_e */ SETYPE_ITEM_GET_SMELL, + /* dItemNo_Randomizer_SMELL_POH_e */ SETYPE_ITEM_GET_SMELL, + /* dItemNo_Randomizer_SMELL_FISH_e */ SETYPE_ITEM_GET_SMELL, + /* dItemNo_Randomizer_SMELL_CHILDREN_e */ SETYPE_ITEM_GET_SMELL, + /* dItemNo_Randomizer_SMELL_MEDICINE_e */ SETYPE_ITEM_GET_SMELL, + /* dItemNo_Randomizer_NOENTRY_182_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_183_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_184_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_185_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_186_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_187_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_188_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_189_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_190_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_NOENTRY_191_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_M_BEETLE_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_BEETLE_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_BUTTERFLY_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_BUTTERFLY_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_STAG_BEETLE_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_STAG_BEETLE_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_GRASSHOPPER_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_GRASSHOPPER_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_NANAFUSHI_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_NANAFUSHI_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_DANGOMUSHI_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_DANGOMUSHI_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_MANTIS_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_MANTIS_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_LADYBUG_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_LADYBUG_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_SNAIL_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_SNAIL_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_DRAGONFLY_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_DRAGONFLY_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_ANT_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_ANT_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_M_MAYFLY_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_F_MAYFLY_e */ SETYPE_ITEM_GET_INSECT, + /* dItemNo_Randomizer_NOENTRY_216_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_217_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_218_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_219_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_220_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_221_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_222_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_223_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_POU_SPIRIT_e */ SETYPE_ITEM_GET_POU, + /* dItemNo_Randomizer_NOENTRY_225_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_226_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_227_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_228_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_229_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_230_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_231_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_NOENTRY_232_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_ANCIENT_DOCUMENT_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_AIR_LETTER_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_ANCIENT_DOCUMENT2_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_LV7_DUNGEON_EXIT_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LINKS_SAVINGS_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_SMALL_KEY2_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_POU_FIRE1_e */ SETYPE_NONE, + /* dItemNo_Randomizer_POU_FIRE2_e */ SETYPE_NONE, + /* dItemNo_Randomizer_POU_FIRE3_e */ SETYPE_NONE, + /* dItemNo_Randomizer_POU_FIRE4_e */ SETYPE_NONE, + /* dItemNo_Randomizer_BOSSRIDER_KEY_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_TOMATO_PUREE_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_TASTE_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_LV5_BOSS_KEY_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_SURFBOARD_e */ SETYPE_NONE, + /* dItemNo_Randomizer_KANTERA2_e */ SETYPE_ITEM_GET_ME, + /* dItemNo_Randomizer_L2_KEY_PIECES1_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_L2_KEY_PIECES2_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_L2_KEY_PIECES3_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_KEY_OF_CARAVAN_e */ SETYPE_ITEM_GET_MINI, + /* dItemNo_Randomizer_LV2_BOSS_KEY_e */ SETYPE_ITEM_GET, + /* dItemNo_Randomizer_KEY_OF_FILONE_e */ SETYPE_ITEM_GET_MINI, + }; + + static constexpr u32 bgmLabel[8] = { + Z2BGM_HEART_GET, Z2BGM_ITEM_GET, Z2BGM_ITEM_GET_MINI, Z2BGM_ITEM_GET_ME, + Z2BGM_ITEM_GET_INSECT, Z2BGM_ITEM_GET_SMELL, Z2BGM_ITEM_GET_POU, Z2BGM_ITEM_GET_ME_S, + }; + + auto* i_this = mods::arg(args, 0); + const int i_itemNo = mods::arg(args, 1); + u32 se_type = getSeTypeRandomizer[i_itemNo]; + + if (se_type == SETYPE_ITEM_GET_ME && i_this->mProcVar4.field_0x3010 == 0) { + se_type = SETYPE_ITEM_GET_ME_S; + } + + if (se_type != SETYPE_NONE || i_itemNo == dItemNo_Randomizer_FOOLISH_ITEM_e) { + mDoAud_subBgmStart(bgmLabel[se_type]); + dComIfGp_setMesgBgmOn(); + } + + return HOOK_SKIP_ORIGINAL; +} + +HookAction hookPreProcCoGetItem(ModContext*, void* args, void*, void*) { + auto* i_this = mods::arg(args, 0); + if (i_this->field_0x32cc != 0 || i_this->mProcVar2.field_0x300c != dItemNo_Randomizer_POU_SPIRIT_e) { + return HOOK_CONTINUE; + } + + auto* item_partner_p = static_cast(fopAcM_getItemEventPartner(i_this)); + if (item_partner_p == nullptr || fpcM_IsCreating(fpcM_GetID(item_partner_p))) { + return HOOK_CONTINUE; + } + + // Don't show special text in rando + if (dComIfGs_getPohSpiritNum() == 20) { + i_this->field_0x32cc = i_this->mProcVar2.field_0x300c + 0x65; + } + + return HOOK_CONTINUE; +} + +u8 hookProcCoWarpInit_prevSlot18 = dItemNo_NONE_e; +bool hookProcCoWarpInit_restoreSlot18 = false; +HookAction hookPreProcCoWarpInit(ModContext*, void* args, void*, void*) { + hookProcCoWarpInit_restoreSlot18 = false; + + auto* i_this = mods::arg(args, 0); + const int param_0 = mods::arg(args, 1); + const int param_1 = mods::arg(args, 2); + + // copy necessary functionality from og function to set up patch state + if (param_0 != 0 || i_this->checkWolf()) { + return HOOK_CONTINUE; + } + + const BOOL isSideWarp = param_1 == 0 && + ((daAlink_c::checkStageName("F_SP125") && fopAcM_GetRoomNo(i_this) == 4) || + (daAlink_c::checkStageName("D_MN08") && fopAcM_GetRoomNo(i_this) == 0)); + if (isSideWarp || !i_this->checkBossRoom() || fopAcM_GetRoomNo(i_this) != 50) { + return HOOK_CONTINUE; + } + + char stageName[32]; + SAFE_STRCPY(stageName, dComIfGp_getStartStageName()); + + for (int i = 0; i < 32; i++) { + if ((s64)stageName[i] == 0) { + stageName[i - 1] = 0; + break; + } + } + + if (!checkItemGet(dItemNo_DUNGEON_EXIT_e, 1) && + !(checkItemGet(dItemNo_DUNGEON_BACK_e, 1) && + strcmp(stageName, dComIfGs_getWarpStageName()) == 0)) + { + return HOOK_CONTINUE; + } + + // In rando, we only want to clear the Ooccoo slot if Ooccoo is in it. + // so continue with normal function if Ooccoo is in slot. + u8 ooccooSlot = dComIfGs_getItem(SLOT_18, false); + if (ooccooSlot == dItemNo_Randomizer_DUNGEON_EXIT_e || + ooccooSlot == dItemNo_Randomizer_DUNGEON_EXIT_2_e || + ooccooSlot == dItemNo_Randomizer_LV7_DUNGEON_EXIT_e) + { + return HOOK_CONTINUE; + } + + hookProcCoWarpInit_prevSlot18 = ooccooSlot; + hookProcCoWarpInit_restoreSlot18 = true; + return HOOK_CONTINUE; +} + +void hookPostProcCoWarpInit(ModContext*, void*, void*, void*) { + if (hookProcCoWarpInit_restoreSlot18) { + dComIfGs_setItem(SLOT_18, hookProcCoWarpInit_prevSlot18); + hookProcCoWarpInit_restoreSlot18 = false; + } +} + void hookPostBqEnd(ModContext*, void* args, void* retval, void*) { // If the player is wolf, they will softlock after the defeat cutscene is completed. checkTransformFromWolf(); @@ -1568,6 +2041,22 @@ void hookPostYkWIsDelete(ModContext*, void* args, void* retval, void*) { } } +void hookPostNpcZrzIsDelete(ModContext*, void* args, void* retval, void*) { + if (dComIfGs_isEventBit(GOT_ZORA_ARMOR_FROM_RUTELA)) { + return; + } + + auto* i_this = mods::arg(args, 0); + if (i_this->mDemoMode != 3) { + return; + } + + const int roomNo = fopAcM_GetRoomNo(i_this); + if (dComIfGs_isSwitch(i_this->mSwitch1, roomNo) && dComIfGs_isSwitch(i_this->mSwitch2, roomNo)) { + *static_cast(retval) = FALSE; + } +} + u8 hookNpcShadWaitType1_patchItemNo = dItemNo_NONE_e; bool hookNpcShadWaitType1_isPatchItemNo = false; HookAction hookPreNpcShadWaitType1(ModContext*, void* args, void* retval, void*) { @@ -1663,55 +2152,369 @@ void hookPostEMdCreate(ModContext*, void* args, void*, void*) { hookEMdCreate_prevSkipInfo = 0; } -// functions are simple enough that replacing isn't too bad -// todo: item service needs to be properly hooked up to this check i think instead of -// using "randomizer_getItemAtLocation" -void hookReplaceMasterSwordExecuteWait(ModContext*, void* args, void*, void*) { - auto* i_this = mods::arg(args, 0); - - if (daPy_getPlayerActorClass()->checkPriActorOwn(i_this)) { - for (int i = 0; i < dComIfGp_getAttention()->GetActionCount(); i++) { - if (dComIfGp_getAttention()->ActionTarget(i) == i_this) { - if (dComIfGp_getAttention()->getActionBtnB() != NULL && - dComIfGp_getAttention()->getActionBtnB()->mType == fopAc_attn_CARRY_e) - { - dComIfGp_setDoStatusForce(8, 0); - } - } - } - } - - if (fopAcM_checkCarryNow(i_this)) { - u8 itemToGive = randomizer_getItemAtLocation("Sacred Grove Pedestal Master Sword"); - g_randomizerState.addItemToEventQueue(itemToGive); - - itemToGive = randomizer_getItemAtLocation("Sacred Grove Pedestal Shadow Crystal"); - g_randomizerState.addItemToEventQueue(itemToGive); +HookAction hookPreOrderMapToolEvent(ModContext*, void* args, void*, void*) { + auto eventId = mods::arg(args, 1); + // skip starting the master sword cutscene so we can handle the checks manually + if (daAlink_c::checkStageName("F_SP117") && eventId == 2) { + // Set the necessary flags to de-spawn the MS and set the save file event flag. dComIfGs_onTmpBit(0x820); dComIfGs_onEventBit(0x2120); - } -} - -HookAction hookPreMasterSwordExecute(ModContext*, void* args, void* retval, void*) { - auto* i_this = mods::arg(args, 0); - - if (dComIfGs_isTmpBit(dSv_event_tmp_flag_c::tempBitLabels[73])) { - // Don't automatically give the master sword in randomizer - fopAcM_delete(i_this); - *static_cast(retval) = 1; return HOOK_SKIP_ORIGINAL; } return HOOK_CONTINUE; } +HookAction hookPreMasterSwordExecute(ModContext*, void* args, void* retval, void*) { + auto* i_this = mods::arg(args, 0); + + (i_this->*i_this->mActionFunc[1])(); + dComIfG_Ccsp()->Set(&i_this->mCyl); + + i_this->mBtk.play(); + i_this->mBrk.play(); + + if (dComIfGs_isTmpBit(dSv_event_tmp_flag_c::tempBitLabels[73])) { + // Don't automatically give the master sword in randomizer + fopAcM_delete(i_this); + } + + *static_cast(retval) = 1; + return HOOK_SKIP_ORIGINAL; +} + void hookPost_dScnName_c__changeGameScene(ModContext* ctx, void* args, void* retval, void* userdata) { if (!mDoRst::isReset() && !fopOvlpM_IsPeek()) { randomizer::session::registerStartingLocation(); } } +void hookPostTbox2Create(ModContext*, void* args, void* retval, void*) { + if (*static_cast(retval) != 1) { + return; + } + + auto* i_this = mods::arg(args, 0); + u8 tboxId = fopAcM_GetParamBit(i_this, 16, 8); + if (tboxId == 0xFF || !dComIfGs_isTbox(tboxId)) { + return; + } + // If the flag for this box is set, open it + + // Set the action for not allowing the player to open it + i_this->init_actionWait(); + + // Set the animation frame to open + i_this->mpBck->setFrame(i_this->mpBck->getEndFrame()); + + // Set collision to open + if (i_this->mpBgW != NULL) { + dComIfG_Bgsp().Release(i_this->mpBgW); + } + if (i_this->mBoxBgW != NULL) { + dComIfG_Bgsp().Regist(i_this->mBoxBgW, i_this); + } +} + +HookAction hookPreTbox2SetGetDemoItem(ModContext*, void* args, void*, void*) { + auto* i_this = mods::arg(args, 0); + if (i_this->mReturnRupee) { + return HOOK_CONTINUE; + } + + u8 tboxId = fopAcM_GetParamBit(i_this, 16, 8); + if (tboxId != 0xFF) { + dComIfGs_onTbox(tboxId); + } + + return HOOK_CONTINUE; +} + +u8 hookGameoverCreate_prevSlot18 = dItemNo_NONE_e; +bool hookGameoverCreate_restoreSlot18 = false; +HookAction hookPreGameoverCreate(ModContext*, void*, void*, void*) { + if (dMeter2Info_getGameOverType() == 1 && strcmp(dComIfGp_getLastPlayStageName(), "D_MN10A") == 0) { + // In rando, we only want to clear the Ooccoo slot if Ooccoo is in it. + // so continue with normal function if Ooccoo is in slot. + u8 ooccooSlot = dComIfGs_getItem(SLOT_18, false); + if (ooccooSlot == dItemNo_Randomizer_DUNGEON_EXIT_e || + ooccooSlot == dItemNo_Randomizer_DUNGEON_EXIT_2_e) + { + return HOOK_CONTINUE; + } + + hookGameoverCreate_prevSlot18 = ooccooSlot; + hookGameoverCreate_restoreSlot18 = true; + } + + return HOOK_CONTINUE; +} + +void hookPostGameoverCreate(ModContext*, void*, void*, void*) { + if (hookGameoverCreate_restoreSlot18) { + dComIfGs_setItem(SLOT_18, hookGameoverCreate_prevSlot18); + hookGameoverCreate_restoreSlot18 = false; + } +} + +inline void createPalaceSolsRewardItem(daObjSwBallC_c* i_this) { + cXyz scale{1.0f, 1.0f, 1.0f}; + cXyz position{250.0f, -200.0f, 11000.0f}; + initCreatePlayerItem( + dItemNo_Randomizer_WOOD_STICK_e, + 0x81, + &position, + fopAcM_GetRoomNo(i_this), + &i_this->shape_angle, + &scale); +} + +void hookPostSwBallCreate(ModContext*, void* args, void* retval, void*) { + auto* i_this = mods::arg(args, 0); + if (*static_cast(retval) != 1) { + return; + } + + if (fopAcM_isSwitch(i_this, 0x3d) && fopAcM_isSwitch(i_this, 0x3e)) { + createPalaceSolsRewardItem(i_this); + } +} + +HookAction hookPreSwBallActionWait(ModContext*, void* args, void*, void*) { + auto* i_this = mods::arg(args, 0); + if (!fopAcM_isSwitch(i_this, 0x3d) || + !fopAcM_isSwitch(i_this, 0x3e)) + { + return HOOK_CONTINUE; + } + + // Don't play the cutscene in rando, just spawn in the item for + // Palace of Twilight Collect Both Sols + + dComIfGs_onTbox(10); + dComIfGs_onTbox(11); + + i_this->calcLightBallScale(); + i_this->field_0x574->setPlaySpeed(1.0f); + if (i_this->field_0x574->play() != 0 && !fopAcM_isSwitch(i_this, 0x3f)) { + fopAcM_onSwitch(i_this, 0x3f); + fopAcM_onSwitch(i_this, 0x27); + createPalaceSolsRewardItem(i_this); + } + + return HOOK_SKIP_ORIGINAL; +} + +void hookPostDitemExecute(ModContext*, void* args, void* retval, void*) { + auto* i_this = mods::arg(args, 0); + + // Certain items use field models that are too big to fit in link's hands so we want to scale them down to fit. + switch (i_this->m_itemNo) { + case dItemNo_Randomizer_MIRROR_PIECE_1_e: + case dItemNo_Randomizer_MIRROR_PIECE_2_e: + case dItemNo_Randomizer_MIRROR_PIECE_3_e: + case dItemNo_Randomizer_MIRROR_PIECE_4_e: + { + i_this->scale.x = 0.05f; + break; + } + case dItemNo_Randomizer_MASTER_SWORD_e: + case dItemNo_Randomizer_LIGHT_SWORD_e: + { + i_this->scale.x = 0.001f; + break; + } + } +} + +// pretty ugly way of handling this, but oh well. basically, we reconstruct the actionTable +// and use it to check that we're in the correct action before proceeding. then store info +// about what level we're on, and use it in the post-hook to undo the flag that's normally set. +daObjBossWarp_c* hookBossWarpDemoProc_patchActor = nullptr; +int hookBossWarpDemoProc_nowLevel = -1; +HookAction hookPreBossWarpDemoProc(ModContext*, void* args, void*, void*) { + auto* i_this = mods::arg(args, 0); + + hookBossWarpDemoProc_patchActor = nullptr; + hookBossWarpDemoProc_nowLevel = -1; + + static const char* const actionTable[15] = { + "WAIT", + "APPEAR", + "DISAPPEAR", + "SCENE_CHG", + "STONE_FALL", + "STONE_MIDNA", + "WALK_TARGET1", + "APPEAR_END", + "STONE_DELETE", + "STONE_PUTAWAY", + "WCHECK", + "SETPOS", + "SCALING", + "STONE_SCALE", + "HEART_MOVE", + }; + + bool isRewardAction = + dComIfGp_evmng_getIsAddvance(i_this->mStaffId) + && dComIfGp_evmng_getMyActIdx(i_this->mStaffId, actionTable, 15, 0, 0) == 4; + if (!isRewardAction) { + return HOOK_CONTINUE; + } + + // this was a static function in the original TU, so reconstructing it for use here + auto getNowLevel = []() { + static const char* const stages[9] = { + "D_MN05A", + "D_MN04A", + "D_MN01A", + "D_MN10A", + "D_MN11A", + "D_MN06A", + "D_MN07A", + "D_MN08A", + "D_MN01A", + }; + + for (int i = 0; i < 9; i++) { + if (std::strcmp(dComIfGp_getStartStageName(), stages[i]) == 0) { + return i; + } + } + + return -1; + }; + + hookBossWarpDemoProc_patchActor = i_this; + hookBossWarpDemoProc_nowLevel = getNowLevel(); + return HOOK_CONTINUE; +} + +void hookPostBossWarpDemoProc(ModContext*, void* args, void*, void*) { + auto* i_this = mods::arg(args, 0); + if (hookBossWarpDemoProc_patchActor != i_this) { + return; + } + + int level = hookBossWarpDemoProc_nowLevel; + hookBossWarpDemoProc_patchActor = nullptr; + hookBossWarpDemoProc_nowLevel = -1; + + // undo the flag that was set in the original function + switch (level) { + case 0: + dComIfGs_offCollectCrystal(0); + break; + case 1: + dComIfGs_offCollectCrystal(1); + break; + case 2: + dComIfGs_offCollectCrystal(2); + break; + case 4: + dComIfGs_offCollectMirror(1); + break; + case 5: + dComIfGs_offCollectMirror(2); + break; + case 6: + dComIfGs_offCollectMirror(3); + break; + } +} + +bool hookUkiCatch_isSkipSetBottle = false; +HookAction hookPreUkiCatch(ModContext*, void* args, void*, void*) { + auto* i_this = mods::arg(args, 0); + hookUkiCatch_isSkipSetBottle = false; + + fopAc_ac_c* mgfish_a = fopAcM_SearchByID(i_this->mg_fish_id); + mg_fish_class* mgfish = (mg_fish_class*)mgfish_a; + if (mgfish == nullptr) { + return HOOK_CONTINUE; + } + + // replicating the bottle catch check here to remove rng check by overriding whatever catch + // you have if you meet the bottle catch conditions + if (!dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[468]) && + strcmp(dComIfGp_getStartStageName(), "F_SP127") == 0) + { + fopAc_ac_c* player = dComIfGp_getPlayer(0); + cXyz bin_pos(6800.0f, 30.0f, -270.0f); + bin_pos -= player->current.pos; + if (bin_pos.abs() < 2500.0f) { + s16 angle = player->shape_angle.y - cM_atan2s(bin_pos.x, bin_pos.z); + if (angle < 0x4000 && angle > -0x4000) { + mgfish->mCaughtType = MG_CATCH_BIN; + } + } + } + + // if catching a bottle, set a flag to skip giving an empty bottle so that the FLW patch + // can handle the check + if (mgfish->mCaughtType == MG_CATCH_BIN) { + hookUkiCatch_isSkipSetBottle = true; + } + + return HOOK_CONTINUE; +} + +void hookPostUkiCatch(ModContext*, void*, void*, void*) { + hookUkiCatch_isSkipSetBottle = false; +} + +HookAction hookPreSetEmptyBottle(ModContext*, void*, void*, void*) { + // coming from uki_catch, skip giving bottle. let FLW patch handle it + if (hookUkiCatch_isSkipSetBottle) { + return HOOK_SKIP_ORIGINAL; + } + + return HOOK_CONTINUE; +} + +HookAction hookPreNpcZrCIsDelete(ModContext*, void* args, void* retval, void*) { + auto* i_this = mods::arg(args, 0); + if (i_this->mType == 4 + || i_this->mType == 0 + || i_this->mType == 1 + || (i_this->mType == 2 && daNpcF_chkEvtBit(0x108)) + || i_this->mType == 3 + ) + { + *static_cast(retval) = FALSE; + return HOOK_SKIP_ORIGINAL; + } + + return HOOK_CONTINUE; +} + +void hookPostObjZraRockCreate(ModContext*, void* args, void* retval, void*) { + auto* i_this = mods::arg(args, 0); + if (*static_cast(retval) != cPhs_ERROR_e) { + return; + } + + // if returned cPhs_ERROR_e, check if it's because the switch was set. + if (dComIfGs_isSwitch((fopAcM_GetParam(i_this) >> 8) & 0xff, fopAcM_GetRoomNo(i_this))) { + // Don't delete the rock when we're following rutela + if (!dComIfGs_isEventBit(GOT_ZORA_ARMOR_FROM_RUTELA) && dComIfGs_isEventBit(ZORA_ESCORT_CLEARED)) { + *static_cast(retval) = cPhs_COMPLEATE_e; + } + } +} + +void hookPostScnPlyPhase1(ModContext*, void*, void*, void*) { + if (!strcmp(dComIfGp_getStartStageName(), "F_SP104") && dComIfGp_getStartStageRoomNo() == 1 && + dComIfGp_getStartStagePoint() == 23 && dComIfGp_getStartStageLayer() == 12) + { + // Undo item set for rando + dComIfGs_offItemFirstBit(dItemNo_HORSE_FLUTE_e); + dComIfGs_setItem(SLOT_21, dItemNo_NONE_e); + } +} + } ModResult initialize() { @@ -1776,6 +2579,11 @@ ModResult initialize() { ADD_HOOK_PRE(daAlink_c__decideDoStatus, hookPreDecideDoStatus); ADD_HOOK_PRE(searchBouDoor, hookPreSearchBouDoor); ADD_HOOK_PRE(daAlink_c__checkGroundSpecialMode, hookPreCheckGroundSpecialMode); + ADD_HOOK_POST(daAlink_c__setGetItemFace, hookPostSetGetItemFace); + ADD_HOOK_PRE(daAlink_c__setGetSubBgm, hookPreSetGetSubBgm); + ADD_HOOK_PRE(daAlink_c__procCoGetItem, hookPreProcCoGetItem); + ADD_HOOK_PRE(daAlink_c__procCoWarpInit, hookPreProcCoWarpInit); + ADD_HOOK_POST(daAlink_c__procCoWarpInit, hookPostProcCoWarpInit); ADD_HOOK_POST(bq_end, hookPostBqEnd); @@ -1809,11 +2617,41 @@ ModResult initialize() { ADD_HOOK_PRE(daE_MD_c__create, hookPreEMdCreate); ADD_HOOK_POST(daE_MD_c__create, hookPostEMdCreate); - ADD_HOOK_REPLACE(daObjMasterSword_c__executeWait, hookReplaceMasterSwordExecuteWait); + ADD_HOOK_PRE(orderMapToolEvent, hookPreOrderMapToolEvent); ADD_HOOK_PRE(daObjMasterSword_c__execute, hookPreMasterSwordExecute); ADD_HOOK_POST(dScnName_c__changeGameScene, hookPost_dScnName_c__changeGameScene); + ADD_HOOK_POST(daNpc_zrZ_c__isDelete, hookPostNpcZrzIsDelete); + + ADD_HOOK_POST(daTbox2_c__Create, hookPostTbox2Create); + ADD_HOOK_PRE(daTbox2_c__setGetDemoItem, hookPreTbox2SetGetDemoItem); + + ADD_HOOK_PRE(dGameover_c___create, hookPreGameoverCreate); + ADD_HOOK_POST(dGameover_c___create, hookPostGameoverCreate); + + ADD_HOOK_POST(daObjSwBallC_c__Create, hookPostSwBallCreate); + ADD_HOOK_PRE(daObjSwBallC_c__actionWait, hookPreSwBallActionWait); + + ADD_HOOK_POST(daDitem_c__execute, hookPostDitemExecute); + + ADD_HOOK_PRE(daObjBossWarp_c__demoProc, hookPreBossWarpDemoProc); + ADD_HOOK_POST(daObjBossWarp_c__demoProc, hookPostBossWarpDemoProc); + + ADD_HOOK_PRE(mgRod_lure_heart, hookPreLureHeart); + ADD_HOOK_POST(mgRod_lure_heart, hookPostLureHeart); + + ADD_HOOK_PRE(mgRod_uki_catch, hookPreUkiCatch); + ADD_HOOK_POST(mgRod_uki_catch, hookPostUkiCatch); + + ADD_HOOK_PRE(dSv_player_item_c__setEmptyBottle, hookPreSetEmptyBottle); + + ADD_HOOK_PRE(daNpc_zrC_c__isDelete, hookPreNpcZrCIsDelete); + + ADD_HOOK_POST(daObjZraRock_c__create, hookPostObjZraRockCreate); + + //ADD_HOOK_POST(phase_1__dScnPly_c, hookPostScnPlyPhase1); + return MOD_OK; } @@ -1862,6 +2700,10 @@ ModResult uninstall() { mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); @@ -1893,11 +2735,35 @@ ModResult uninstall() { mods::hook::uninstall(svc_hook); - mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); mods::hook::uninstall(svc_hook); mods::hook::uninstall(); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + mods::hook::uninstall(svc_hook); + + //mods::hook::uninstall(); + return MOD_OK; } } diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index e6f155a989..f9d77837f9 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -2346,7 +2346,7 @@ void dScnKy_env_light_c::setLight() { u8 next_pal_end_id; #if TARGET_PC const f32 deltaTime = dusk::game_clock::consume_interval(this); - timeScale = deltaTime / dusk::game_clock::period_for_original_frames(1.0f); + timeScale = deltaTime / dusk::game_clock::kSimPeriod; #endif setLight_palno_get(&g_env_light.PrevCol, &g_env_light.UseCol, &g_env_light.wether_pat0, &g_env_light.wether_pat1, &prev_pal_start_id, &prev_pal_end_id, diff --git a/src/d/d_kyeff.cpp b/src/d/d_kyeff.cpp index 984478f74d..1ba4ffa96f 100644 --- a/src/d/d_kyeff.cpp +++ b/src/d/d_kyeff.cpp @@ -118,7 +118,7 @@ static int dKyeff_Create(kankyo_class* i_this) { if (strcmp(dComIfGp_getStartStageName(), "Name") == 0) { camera_process_class* camera = dComIfGp_getCamera(0); - OSTime time = OSGetTime(); + OSTime time = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()); OSTicksToCalendarTime(time, &calendar); g_env_light.global_wind_influence.vec.x = 1.0f; diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index 08591007ec..41d5108e32 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -16,23 +16,14 @@ #ifdef TARGET_PC #include "dusk/settings.h" +#include "dusk/hq_minimap.hpp" #include "m_Do/m_Do_graphic.h" #include #include #include -#include #include -#include #include -#include -#include - -constexpr u16 kMapIconResolutionMultiplier = 4; -constexpr u16 kMapImageSide = 16 * kMapIconResolutionMultiplier; -constexpr u32 kMapImageTotalPixels = kMapImageSide * kMapImageSide; - -typedef std::function PaintI8Fn; u16 scaled_map_axis(u16 value, f32 scale) { const auto scaledValue = @@ -59,27 +50,6 @@ aurora::Vec2 map_render_size_for(u16 width, u16 height) { scaled_map_axis(height, irScaleY * hudScale), }; } - -void paint_i8(std::span dst, size_t width, PaintI8Fn paint) { - const auto blocksAcross = width >> 3; - - for (size_t i = 0; i < dst.size(); i++) { - // 8x4 block swizzling for I8 - const auto blockIdx = i >> 5; - const auto localIdx = i & 31; - - const auto blockY = blockIdx / blocksAcross; - const auto blockX = blockIdx % blocksAcross; - - const auto localY = localIdx >> 3; - const auto localX = localIdx & 7; - - const auto x = (blockX << 3) + localX; - const auto y = (blockY << 2) + localY; - - dst[i] = paint(x, y); - } -} #endif void dMpath_n::dTexObjAggregate_c::create() { @@ -100,106 +70,11 @@ void dMpath_n::dTexObjAggregate_c::create() { JUT_ASSERT(72, image != NULL); JUT_ASSERT(73, image->minFilter == GX_NEAR); JUT_ASSERT(74, image->magFilter == GX_NEAR); + IF_DUSK(dusk::hq_minimap::register_pointer(data[lp1], reinterpret_cast(image) + image->imageOffset)); mDoLib_setResTimgObj(image, mp_texObj[lp1], 0, NULL); } -#if TARGET_PC - static bool hqTexsDrawn = false; - - static u8 hqCircleData[kMapImageTotalPixels]; - static u8 hqCircleAltData[kMapImageTotalPixels]; - static u8 hqNijumaruData[kMapImageTotalPixels]; - static u8 hqEnterData[kMapImageTotalPixels]; - static u8 hqTryForceData[kMapImageTotalPixels]; - - if (!hqTexsDrawn) { - constexpr auto center = kMapImageSide / 2.0f; - constexpr auto radiusSq = center * center; - - // 6: map_icon_circle16x16_4i.bti - simple circle - paint_i8(std::span{hqCircleData}, kMapImageSide, [=](auto x, auto y) { - const auto dx = (x + 0.5f) - center; - const auto dy = (y + 0.5f) - center; - return (dx * dx + dy * dy < radiusSq) ? 0x11 : 0; - }); - - // 4: im_map_icon_circle_4i.bti - outlined circle - paint_i8(std::span{hqCircleAltData}, kMapImageSide, [=](auto x, auto y) { - constexpr auto innerRadius = kMapImageSide * 3.0f / 8.0f; - constexpr auto innerRadiusSq = innerRadius * innerRadius; - - const auto dx = (x + 0.5f) - center; - const auto dy = (y + 0.5f) - center; - const auto dSq = dx * dx + dy * dy; - - return dSq < radiusSq ? (dSq < innerRadiusSq ? 0x22 : 0x11) : 0; - }); - - // 3: im_map_icon_nijumaru_4i.bti - concentric rings - paint_i8(std::span{hqNijumaruData}, kMapImageSide, [=](auto x, auto y) { - constexpr u8 nijumaruRings[] = {0x11, 0x22, 0x11, 0x11, 0x22, 0x22}; - - const auto dx = (x + 0.5f) - center; - const auto dy = (y + 0.5f) - center; - const auto dSq = dx * dx + dy * dy; - - if (dSq < radiusSq) { - const auto ringIndex = - static_cast(std::trunc(std::sqrt(dSq) / kMapImageSide * 12)); - return nijumaruRings[ringIndex]; - } - return u8{0}; - }); - - // 2: im_map_icon_enter_4i.bti - outlined octagram - paint_i8(std::span{hqEnterData}, kMapImageSide, [=](auto x, auto y) { - constexpr auto outlineWidth = kMapImageSide / 6.0f; - - const auto adx = std::abs((x + 0.5f) - center); - const auto ady = std::abs((y + 0.5f) - center); - const auto dist = - std::min(adx + ady, std::max(adx, ady) * std::numbers::sqrt2_v) - - kMapImageSide / 2.0f; - - return dist > 0.0f ? 0 : (dist > -outlineWidth ? 0x22 : 0x33); - }); - - // 5: im_map_icon_try_force_4i.bti - outlined circle with triangle - paint_i8(std::span{hqTryForceData}, kMapImageSide, [=](auto x, auto y) { - constexpr auto innerRadiusNorm = 5.0f / 12.0f; - constexpr auto innerRadius = kMapImageSide * innerRadiusNorm; - constexpr auto innerRadiusSq = innerRadius * innerRadius; - constexpr auto triRadius = kMapImageSide * innerRadiusNorm / 2.0f; - - const auto dx = (x + 0.5f) - center; - const auto dy = (y + 0.5f) - center; - const auto dSq = dx * dx + dy * dy; - const auto triSideDist = (std::numbers::sqrt3_v * std::abs(dx) - dy) * 0.5f; - const auto insideTri = std::max(dy, triSideDist) < triRadius; - - return insideTri ? 0x22 : (dSq < radiusSq ? (dSq < innerRadiusSq ? 0x33 : 0x22) : 0); - }); - - hqTexsDrawn = true; - } - - constexpr auto replacements = std::to_array >({ - {2, hqEnterData}, - {3, hqNijumaruData}, - {4, hqCircleAltData}, - {5, hqTryForceData}, - {6, hqCircleData}, - }); - - for (const auto& [idx, data] : replacements) { - JKR_DELETE(mp_texObj[idx]); - const auto texobj = JKR_NEW TGXTexObj(); - GXInitTexObj( - texobj, data, kMapImageSide, kMapImageSide, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE); - GXInitTexObjLOD(texobj, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); - mp_texObj[idx] = texobj; - } -#endif + IF_DUSK(dusk::hq_minimap::initialize_if_needed()); } void dMpath_n::dTexObjAggregate_c::remove() { diff --git a/src/d/d_menu_ring.cpp b/src/d/d_menu_ring.cpp index 3fe55c2b6c..f0f44db166 100644 --- a/src/d/d_menu_ring.cpp +++ b/src/d/d_menu_ring.cpp @@ -1798,7 +1798,7 @@ void dMenu_Ring_c::drawSelectItem() { if (field_0x674[i] != 0) { #if TARGET_PC mSelectItemSlideElapsed[i] += dusk::game_clock::consume_interval(this); - const f32 u = std::min(mSelectItemSlideElapsed[i] / dusk::game_clock::period_for_original_frames(10.0f), 1.0f); + const f32 u = std::min(mSelectItemSlideElapsed[i] / (dusk::game_clock::kSimPeriod * 10.0f), 1.0f); if (u >= 1.0f) { setSelectItemForce(i); } else { diff --git a/src/d/d_menu_window.cpp b/src/d/d_menu_window.cpp index 7873632265..eb67822899 100644 --- a/src/d/d_menu_window.cpp +++ b/src/d/d_menu_window.cpp @@ -123,7 +123,13 @@ public: #endif } - void setCaptureFlag() { mFlag = 1; } + void setCaptureFlag() { + mFlag = 1; + #ifdef TARGET_PC + dusk::frame_interp::request_presentation_sync(); + #endif + } + bool checkDraw() { return mFlag; } u8 getAlpha() { return mAlpha; } u8 getTopFlag() { return mTopFlag; } @@ -1092,10 +1098,6 @@ void dMw_c::dMw_ring_create(u8 i_origin) { } mpCapture->setCaptureFlag(); - -#ifdef TARGET_PC - dusk::frame_interp::request_presentation_sync(); -#endif } bool dMw_c::dMw_ring_delete() { diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index cdd5c467ef..d634c7b21d 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -1828,7 +1828,7 @@ int dSv_info_c::memory_to_card(char* card_ptr, int dataNum) { savedata->getPlayer().getPlayerInfo().setTotalTime(play_time); } - savedata->getPlayer().getPlayerStatusB().setDateIpl(OSGetTime()); + savedata->getPlayer().getPlayerStatusB().setDateIpl(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime())); memcpy(card_ptr, savedata, sizeof(dSv_save_c)); card_ptr += 0x958; @@ -2035,7 +2035,7 @@ void flagFile_c::listenPropertyEvent(const JORPropertyEvent* i_event) { } case 102: { OSCalendarTime time; - OSTicksToCalendarTime(OSGetTime(), &time); + OSTicksToCalendarTime(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()), &time); const char* start_stage_name = dComIfGp_getStartStageName(); char filename[64]; diff --git a/src/dusk/action_bindings.cpp b/src/dusk/action_bindings.cpp index 68b07d6c0a..c919c9d4fe 100644 --- a/src/dusk/action_bindings.cpp +++ b/src/dusk/action_bindings.cpp @@ -41,6 +41,15 @@ bool isActionBound(ActionBinds action, u32 port) { return getActionBindButton(action, port) != PAD_NATIVE_BUTTON_INVALID; } +bool isActionBoundAnyPort(ActionBinds action) { + for (u32 port = 0; port < PAD_CHANMAX; ++port) { + if (isActionBound(action, port)) { + return true; + } + } + return false; +} + void updateActionBindings() { for (u32 port = 0; port < PAD_CHANMAX; ++port) { // Move the current press to the previous frame diff --git a/src/dusk/action_bindings.h b/src/dusk/action_bindings.h index a71dac5dfe..77ce9cb815 100644 --- a/src/dusk/action_bindings.h +++ b/src/dusk/action_bindings.h @@ -31,6 +31,7 @@ using ActionBindsMap = std::unordered_map; ActionBindsMap& getActionBinds(); bool isActionBound(ActionBinds action, u32 port); +bool isActionBoundAnyPort(ActionBinds action); void updateActionBindings(); diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index be03d51e96..ca30ec0db5 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -147,6 +147,10 @@ void begin_frame(FrameInterpMode mode, bool is_sim_frame, float step) { g_enabled = mode != FrameInterpMode::Off; g_is_sim_frame = is_sim_frame; g_step = std::clamp(step, 0.0f, 1.0f); + if (!g_enabled) { + g_interpolating = false; + clear_replacements(); + } } bool is_enabled() { diff --git a/src/dusk/game_clock.cpp b/src/dusk/game_clock.cpp index 1dc7fbf322..18ee24d542 100644 --- a/src/dusk/game_clock.cpp +++ b/src/dusk/game_clock.cpp @@ -1,104 +1,135 @@ #include "dusk/game_clock.h" #include +#include #include #include -#include #include +#include namespace dusk::game_clock { -using clock = std::chrono::steady_clock; +using native_clock = aurora::time::native_clock; +using game_clock = aurora::time::game_clock; +FrameTiming g_frameTiming; + +namespace { bool s_initialized = false; -clock::time_point s_previous_sample{}; -clock::time_point s_current_snapshot_time{}; +bool s_fixedStepActive = false; +bool s_simTickActive = false; +native_clock::time_point s_previousNativeSample{}; +game_clock::time_point s_latestGameSample{}; +game_clock::time_point s_currentSnapshotTime{}; +game_clock::time_point s_pendingSimTime{}; -std::unordered_map s_interval_last_sample; +std::unordered_map s_intervalLastSample; -constexpr clock::duration kSimPeriodDuration = - std::chrono::duration_cast(std::chrono::duration(sim_pace())); -constexpr clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250); -constexpr int kMaxSimTicksPerFrame = 2; +constexpr game_clock::duration kSimPeriodDuration = + std::chrono::duration_cast(std::chrono::duration(kSimPeriod)); +constexpr native_clock::duration kAbnormalGapResetThreshold = std::chrono::milliseconds(250); +constexpr int kMaxSimTicksPerFrame = static_cast(aurora::time::kMaximumTimeScale) * 4; +} // namespace -void ensure_initialized() { +void initialize() { if (s_initialized) { return; } - s_previous_sample = clock::now(); - s_current_snapshot_time = s_previous_sample; + s_previousNativeSample = native_clock::now(); + s_latestGameSample = game_clock::now(); + s_currentSnapshotTime = s_latestGameSample; + s_pendingSimTime = s_latestGameSample; s_initialized = true; } -void reset_frame_timer() { - s_previous_sample = clock::now(); - s_current_snapshot_time = s_previous_sample - kSimPeriodDuration; +void reset() { + s_previousNativeSample = native_clock::now(); + s_latestGameSample = game_clock::now(); + s_currentSnapshotTime = s_latestGameSample - kSimPeriodDuration; + s_pendingSimTime = s_currentSnapshotTime; + s_simTickActive = false; } -MainLoopPacer advance_main_loop() { - ensure_initialized(); +const FrameTiming& advance() { + const auto nativeNow = native_clock::now(); + const auto gameNow = game_clock::now(); + const auto nativeFrameGap = nativeNow - s_previousNativeSample; + s_previousNativeSample = nativeNow; + s_latestGameSample = gameNow; - const clock::time_point now = clock::now(); - const clock::duration frame_gap = now - s_previous_sample; - const float presentation_dt = std::chrono::duration(frame_gap).count(); - s_previous_sample = now; + auto& out = g_frameTiming; + out = {.dt = std::chrono::duration().count()}; - MainLoopPacer out{}; - out.presentation_dt_seconds = presentation_dt; + const float timeScale = aurora::time::scale(); + const bool interpolating = + getSettings().game.enableFrameInterpolation.getValue() != FrameInterpMode::Off; + const bool separatePresentation = interpolating || timeScale != 1.0f; + out.interpolating = interpolating; + out.separatePresentation = separatePresentation; + s_fixedStepActive = separatePresentation; - const bool should_interpolate = dusk::getSettings().game.enableFrameInterpolation.getValue() != - dusk::FrameInterpMode::Off && - !dusk::getTransientSettings().skipFrameRateLimit; - out.is_interpolating = should_interpolate; - out.sim_pace = sim_pace(); - - if (!should_interpolate) { - s_current_snapshot_time = now; - out.sim_ticks_to_run = 1; + if (!separatePresentation) { + s_currentSnapshotTime = gameNow; + out.numSimTicks = 1; return out; } - if (frame_gap > kAbnormalGapResetThreshold) { - s_current_snapshot_time = now - kSimPeriodDuration; - out.sim_ticks_to_run = 0; + const auto simulationTarget = interpolating ? gameNow - kSimPeriodDuration : gameNow; + if (timeScale == 0.f || nativeFrameGap > kAbnormalGapResetThreshold) { + s_currentSnapshotTime = simulationTarget; + out.numSimTicks = 0; return out; } - int sim_ticks_to_run = 0; - clock::time_point projected_snapshot_time = s_current_snapshot_time; - const clock::time_point render_time = now - kSimPeriodDuration; - while (sim_ticks_to_run < kMaxSimTicksPerFrame && projected_snapshot_time < render_time) { - projected_snapshot_time += kSimPeriodDuration; - sim_ticks_to_run++; + int numSimTicks = 0; + auto projectedSnapshotTime = s_currentSnapshotTime; + while (numSimTicks < kMaxSimTicksPerFrame) { + const bool tickDue = interpolating ? + projectedSnapshotTime < simulationTarget : + projectedSnapshotTime + kSimPeriodDuration <= simulationTarget; + if (!tickDue) { + break; + } + projectedSnapshotTime += kSimPeriodDuration; + numSimTicks++; } - out.sim_ticks_to_run = sim_ticks_to_run; + out.numSimTicks = numSimTicks; return out; } +void begin_sim_tick() { + s_pendingSimTime = + s_fixedStepActive ? s_currentSnapshotTime + kSimPeriodDuration : s_latestGameSample; + s_simTickActive = true; +} + void commit_sim_tick() { - ensure_initialized(); - s_current_snapshot_time += kSimPeriodDuration; + if (s_simTickActive) { + s_currentSnapshotTime = s_pendingSimTime; + s_simTickActive = false; + } else { + s_currentSnapshotTime += kSimPeriodDuration; + } } float sample_interpolation_step() { - ensure_initialized(); const float step = - std::chrono::duration(clock::now() - s_current_snapshot_time).count() / sim_pace(); + std::chrono::duration(game_clock::now() - s_currentSnapshotTime).count() / + kSimPeriod; return std::clamp(step, 0.0f, 1.0f); } float consume_interval(const void* consumer) { - ensure_initialized(); - const uintptr_t key = reinterpret_cast(consumer); - const clock::time_point now = clock::now(); - - float dt = ui_initial_dt(); - const auto it = s_interval_last_sample.find(key); - if (it != s_interval_last_sample.end()) { + const auto key = reinterpret_cast(consumer); + const auto now = s_simTickActive ? s_pendingSimTime : game_clock::now(); + const float timeScale = aurora::time::scale(); + float dt = kUiInitialDt * timeScale; + if (const auto it = s_intervalLastSample.find(key); it != s_intervalLastSample.end()) { dt = std::chrono::duration(now - it->second).count(); - dt = std::min(dt, ui_maximum_dt()); + const float maximumDt = std::max(kUiMaximumDt * timeScale, kSimPeriod); + dt = std::min(dt, maximumDt); } - s_interval_last_sample[key] = now; + s_intervalLastSample[key] = now; return dt; } diff --git a/src/dusk/game_clock.h b/src/dusk/game_clock.h index 8bb277e070..b300cf28d7 100644 --- a/src/dusk/game_clock.h +++ b/src/dusk/game_clock.h @@ -2,22 +2,27 @@ namespace dusk::game_clock { -void ensure_initialized(); -void reset_frame_timer(); +// Amount of time that a simulation tick advances +constexpr float kSimPeriod = 1.0f / 30.0f; +constexpr float kUiMaximumDt = 0.05f; +constexpr float kUiInitialDt = 1.0f / 60.0f; -constexpr float sim_pace() { return 1.0f / 30.0f; } -constexpr float period_for_original_frames(float frame_count) { return frame_count * sim_pace(); } -constexpr float ui_maximum_dt() { return 0.05f; } -constexpr float ui_initial_dt() { return 1.0f / 60.0f; } - -struct MainLoopPacer { - float presentation_dt_seconds; - bool is_interpolating; - int sim_ticks_to_run; - float sim_pace; +struct FrameTiming { + // Amount of time elapsed in seconds since the last advance + float dt; + // Whether interpolation is active + bool interpolating; + // Run simulation and presentation separately (for interpolation or time scaling) + bool separatePresentation; + // Number of simulation ticks to run + int numSimTicks; }; +extern FrameTiming g_frameTiming; -MainLoopPacer advance_main_loop(); +void initialize(); +void reset(); +const FrameTiming& advance(); +void begin_sim_tick(); void commit_sim_tick(); float sample_interpolation_step(); diff --git a/src/dusk/hq_minimap.cpp b/src/dusk/hq_minimap.cpp new file mode 100644 index 0000000000..a4d73cbcf1 --- /dev/null +++ b/src/dusk/hq_minimap.cpp @@ -0,0 +1,210 @@ +#include "aurora/texture.hpp" +#include "dusk/texture_replacements.hpp" +#include "fmt/format.h" + +#include +#include +#include +#include + +namespace { + +constexpr u16 kMapIconResolutionMultiplier = 4; +constexpr u16 kMapImageSide = 16 * kMapIconResolutionMultiplier; +constexpr u32 kMapImageTotalPixels = kMapImageSide * kMapImageSide; + +// give higher priority to user and mod replacements +constexpr auto kInternalTextureReplacementPriority = dusk::texture_replacements::kUserTextureReplacementPriority - 1; + +typedef std::function PaintI8Fn; + +enum class ArcIndex : int { + Circle16 = 82, // map_icon_circle16x16_4i.bti - simple circle + Circle = 76, // im_map_icon_circle_4i.bti - outlined circle + Nijumaru = 78, // im_map_icon_nijumaru_4i.bti - concentric rings + Enter = 77, // im_map_icon_enter_4i.bti - outlined octagram + TryForce = 81, // im_map_icon_try_force_4i.bti - outlined circle with triangle +}; + +struct Replacement { + ArcIndex index; + PaintI8Fn painter; +}; + +struct Icon { + u8* origData = nullptr; + std::unique_ptr newData; + std::string label; + std::optional reg; +}; + +bool s_initialized = false; +bool s_active = false; +std::unordered_map s_icons; + +void paint_i8(std::span dst, size_t width, PaintI8Fn paint) { + assert(width % 8 == 0 && dst.size() % 32 == 0); + + const auto blocksAcross = width >> 3; + + for (size_t i = 0; i < dst.size(); i++) { + // 8x4 block swizzling for I8 + const auto blockIdx = i >> 5; + const auto localIdx = i & 31; + + const auto blockY = blockIdx / blocksAcross; + const auto blockX = blockIdx % blocksAcross; + + const auto localY = localIdx >> 3; + const auto localX = localIdx & 7; + + const auto x = (blockX << 3) + localX; + const auto y = (blockY << 2) + localY; + + dst[i] = paint(x, y); + } +} + +void draw_all_replacements() { + constexpr auto center = kMapImageSide / 2.0f; + constexpr auto radiusSq = center * center; + + // clang-format off + const auto replacements = std::to_array({ + { + ArcIndex::Circle16, + [=](auto x, auto y) { + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + return (dx * dx + dy * dy < radiusSq) ? 0x11 : 0; + } + }, + { + ArcIndex::Circle, + [=](auto x, auto y) { + constexpr auto innerRadius = kMapImageSide * 3.0f / 8.0f; + constexpr auto innerRadiusSq = innerRadius * innerRadius; + + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + const auto dSq = dx * dx + dy * dy; + + return dSq < radiusSq ? (dSq < innerRadiusSq ? 0x22 : 0x11) : 0; + } + }, + { + ArcIndex::Nijumaru, + [=](auto x, auto y) { + constexpr u8 nijumaruRings[] = {0x11, 0x22, 0x11, 0x11, 0x22, 0x22}; + + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + const auto dSq = dx * dx + dy * dy; + + if (dSq < radiusSq) { + auto ringIndex = static_cast(std::trunc(std::sqrt(dSq) / kMapImageSide * 12)); + ringIndex = std::min(ringIndex, sizeof(nijumaruRings) - 1); + return nijumaruRings[ringIndex]; + } + return u8{0}; + } + }, + { + ArcIndex::Enter, + [=](auto x, auto y) { + constexpr auto outlineWidth = kMapImageSide / 6.0f; + + const auto adx = std::abs((x + 0.5f) - center); + const auto ady = std::abs((y + 0.5f) - center); + const auto dist = + std::min(adx + ady, std::max(adx, ady) * std::numbers::sqrt2_v) - + kMapImageSide / 2.0f; + + return dist > 0.0f ? 0 : (dist > -outlineWidth ? 0x22 : 0x33); + } + }, + { + ArcIndex::TryForce, + [=](auto x, auto y) { + constexpr auto innerRadiusNorm = 5.0f / 12.0f; + constexpr auto innerRadius = kMapImageSide * innerRadiusNorm; + constexpr auto innerRadiusSq = innerRadius * innerRadius; + constexpr auto triRadius = kMapImageSide * innerRadiusNorm / 2.0f; + + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + const auto dSq = dx * dx + dy * dy; + const auto triSideDist = (std::numbers::sqrt3_v * std::abs(dx) - dy) * 0.5f; + const auto insideTri = std::max(dy, triSideDist) < triRadius; + + return insideTri ? 0x22 : (dSq < radiusSq ? (dSq < innerRadiusSq ? 0x33 : 0x22) : 0); + } + } + }); + // clang-format on + + for (const auto r : replacements) { + auto pixels = std::make_unique_for_overwrite(kMapImageTotalPixels); + paint_i8(std::span{pixels.get(), kMapImageTotalPixels}, kMapImageSide, r.painter); + + auto& icon = s_icons[static_cast(r.index)]; + icon.newData = std::move(pixels); + icon.label = fmt::format("hq minimap icon {}", static_cast(r.index)); + } +} + +} // namespace + +namespace dusk::hq_minimap { + +void register_pointer(int idx, u8* ptr) { + if (s_initialized) { + return; + } + + s_icons[idx].origData = ptr; +} + +void set_active(bool active) { + s_active = active; +} + +void update() { + if (!s_initialized) { + return; + } + + for (auto& [idx, icon] : s_icons) { + const bool shouldBeRegistered = s_active && icon.origData && icon.newData; + + if (shouldBeRegistered && !icon.reg) { + aurora::texture::ReplacementKey key{aurora::texture::TexturePointerKey{icon.origData}}; + aurora::texture::RawTextureReplacement repl{ + .bytes = std::span{icon.newData.get(), kMapImageTotalPixels}, + .width = kMapImageSide, + .height = kMapImageSide, + .mipCount = 1, + .gxFormat = GX_TF_I8, + .label = icon.label, + }; + icon.reg = aurora::texture::register_replacement( + key, repl, {.priority = kInternalTextureReplacementPriority}); + } else if (!shouldBeRegistered && icon.reg) { + aurora::texture::unregister_replacement(*icon.reg); + icon.reg.reset(); + } + } +} + +void initialize_if_needed() { + if (s_initialized) { + return; + } + + draw_all_replacements(); + s_initialized = true; + + update(); +} + +} // namespace dusk::hq_minimap diff --git a/src/dusk/hq_minimap.hpp b/src/dusk/hq_minimap.hpp new file mode 100644 index 0000000000..5ca89ac7d1 --- /dev/null +++ b/src/dusk/hq_minimap.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace dusk::hq_minimap { + +/// Adds a mapping of an image resource index (into `Always.arc`) to the pointer to its image data. +/// If `initialize_if_needed` has been called, this is a no-op. Pointers are expected to be stable +/// and valid for the program's entire lifetime. +void register_pointer(int idx, u8* ptr); + +/// Sets whether HQ minimap texture replacements should be active or not. Does not manage +/// replacement registrations itself; see `update`. +void set_active(bool active); + +/// Registers or unregisters texture replacements depending on active state. +void update(); + +/// Called once after registering image pointers, in which their HQ replacements are procedurally +/// drawn and `update` is called. Further calls are no-ops. +void initialize_if_needed(); + +} diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index 3c0131c729..a102b0ca11 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -1,8 +1,9 @@ #include #include +#include +#include #include #include -#include #define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" @@ -20,6 +21,7 @@ #include "dusk/frame_interpolation.h" #include "dusk/livesplit.h" #include "dusk/main.h" +#include "dusk/presentation.hpp" #include "dusk/settings.h" #include "dusk/ui/ui.hpp" #include "f_pc/f_pc_manager.h" @@ -37,6 +39,8 @@ using namespace std::string_literals; using namespace std::string_view_literals; namespace { +constexpr float kTurboTimeScale = 4.f; + ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) { while (window != nullptr) { const bool canScrollX = window->ScrollMax.x > 0.0f; @@ -235,10 +239,32 @@ namespace dusk { } void ImGuiConsole::UpdateSettings() { - getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && - (ImGui::IsKeyDown(ImGuiKey_Tab) || getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON)); + static bool previousTurboActive = false; + static bool previousSlowActive = false; + static float previousTimeScale = 1.0f; - if (dusk::frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) { + const bool turboBound = isActionBoundAnyPort(ActionBinds::TURBO_SPEED_BUTTON); + const bool turboActive = + getSettings().game.enableTurboKeybind && + (turboBound ? getActionBindHoldAnyPort(ActionBinds::TURBO_SPEED_BUTTON) : + ImGui::IsKeyDown(ImGuiKey_Tab)); + const bool slowDown = turboActive && ImGui::GetIO().KeyShift; + if (turboActive != previousTurboActive) { + getTransientSettings().turboMode = turboActive; + presentation::update_frame_rate_preference(); + if (turboActive) { + previousTimeScale = aurora_get_timescale(); + aurora_set_timescale(slowDown ? 1.f / kTurboTimeScale : kTurboTimeScale); + } else { + aurora_set_timescale(previousTimeScale); + } + } else if (turboActive && slowDown != previousSlowActive) { + aurora_set_timescale(slowDown ? 1.f / kTurboTimeScale : kTurboTimeScale); + } + previousTurboActive = turboActive; + previousSlowActive = slowDown; + + if (frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) { getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive; } if (mDoMain::developmentMode != 1) { @@ -270,7 +296,7 @@ namespace dusk { m_isHidden = true; } } - + bool showMenu = !m_isHidden; // The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg, diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index b9ad5eb021..c2f5e45754 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -25,10 +25,6 @@ #include #endif -namespace aurora::gx { -extern bool enableLodBias; -} - namespace dusk { ImGuiMenuTools::ImGuiMenuTools() {} @@ -76,7 +72,6 @@ namespace dusk { getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction); config::save(); } - ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias); ImGui::EndMenu(); } diff --git a/src/dusk/presentation.cpp b/src/dusk/presentation.cpp index b55c11d93b..1443ab85f9 100644 --- a/src/dusk/presentation.cpp +++ b/src/dusk/presentation.cpp @@ -10,6 +10,10 @@ namespace dusk::presentation { namespace { float preferred_frame_rate() { + if (getTransientSettings().turboMode) { + return 0.0f; + } + switch (getSettings().game.enableFrameInterpolation.getValue()) { case FrameInterpMode::Off: return 30.0f; diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 00191c3015..1af01faa81 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -76,6 +76,7 @@ UserSettings g_userSettings = { .resampler {"game.resampler", Resampler::Bilinear}, .enableMapBackground {"game.enableMapBackground", true}, .disableCutscenePillarboxing {"game.disableCutscenePillarboxing", false}, + .enableHighQualityMinimapTextures {"game.enableHighQualityMinimapTextures", true}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -283,6 +284,7 @@ void registerSettings() { Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableMapBackground); Register(g_userSettings.game.disableCutscenePillarboxing); + Register(g_userSettings.game.enableHighQualityMinimapTextures); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); Register(g_userSettings.game.fastRoll); @@ -394,7 +396,7 @@ static TransientSettings g_transientSettings = { .colliderViewOpacity = 50.0f, .drawRange = 100.0f, }, - .skipFrameRateLimit = false, + .turboMode = false, }; TransientSettings& getTransientSettings() { diff --git a/src/dusk/settings.h b/src/dusk/settings.h index 19aad6ee09..914ef37efc 100644 --- a/src/dusk/settings.h +++ b/src/dusk/settings.h @@ -208,6 +208,7 @@ struct UserSettings { ConfigVar resampler; ConfigVar enableMapBackground; ConfigVar disableCutscenePillarboxing; + ConfigVar enableHighQualityMinimapTextures; // Audio ConfigVar noLowHpSound; @@ -326,7 +327,7 @@ struct CollisionViewSettings { struct TransientSettings { CollisionViewSettings collisionView; - bool skipFrameRateLimit; + bool turboMode; bool moveLinkActive; bool stateShareLoadActive; }; diff --git a/src/dusk/speedrun.h b/src/dusk/speedrun.h index d9e43c3ed7..887d9e6842 100644 --- a/src/dusk/speedrun.h +++ b/src/dusk/speedrun.h @@ -6,18 +6,23 @@ namespace dusk { struct SpeedrunInfo { void startRun() { m_isRunStarted = true; - m_startTimestamp = OSGetTime(); + m_rtaStartTimestamp = OSGetNativeTime(); + m_igtStartTimestamp = OSGetTime(); } void stopRun() { m_isRunStarted = false; - m_endTimestamp = OSGetTime() - m_startTimestamp; + m_rtaTimer = OSGetNativeTime() - m_rtaStartTimestamp; + if (!m_isPauseIGT) { + m_igtTimer = OSGetTime() - m_igtStartTimestamp - m_totalLoadTime; + } } void reset() { m_isRunStarted = false; - m_startTimestamp = 0; - m_endTimestamp = 0; + m_rtaStartTimestamp = 0; + m_rtaTimer = 0; + m_igtStartTimestamp = 0; m_isPauseIGT = false; m_loadStartTimestamp = 0; m_totalLoadTime = 0; @@ -25,8 +30,9 @@ struct SpeedrunInfo { } bool m_isRunStarted = false; - OSTime m_startTimestamp = 0; - OSTime m_endTimestamp = 0; + OSTime m_rtaStartTimestamp = 0; + OSTime m_rtaTimer = 0; + OSTime m_igtStartTimestamp = 0; bool m_isPauseIGT = false; OSTime m_loadStartTimestamp = 0; diff --git a/src/dusk/ui/overlay.cpp b/src/dusk/ui/overlay.cpp index 740b4febf0..c271cccfdb 100644 --- a/src/dusk/ui/overlay.cpp +++ b/src/dusk/ui/overlay.cpp @@ -203,10 +203,19 @@ void remove_element(Rml::Element*& elem) noexcept { } // namespace -static std::string FormatTime(OSTime ticks) { - OSCalendarTime t; - OSTicksToCalendarTime(ticks, &t); - return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", t.hour, t.min, t.sec, t.msec); +static std::string FormatElapsedTime(OSTime ticksElapsed) { + using namespace std::chrono; + + milliseconds ms{OSTicksToMilliseconds(ticksElapsed)}; + + const hours hr = duration_cast(ms); + ms -= hr; + const minutes min = duration_cast(ms); + ms -= min; + const seconds sec = duration_cast(ms); + ms -= sec; + + return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", hr.count(), min.count(), sec.count(), ms.count()); } Overlay::Overlay() : Document(kDocumentSource, true, DocumentScope::Overlay) { @@ -317,33 +326,33 @@ void Overlay::update() { mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigY(PAD_1)) { if (m_speedrunInfo.m_isRunStarted) { - m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp; - m_speedrunInfo.m_isRunStarted = false; + m_speedrunInfo.stopRun(); } } - OSTime elapsedTime = 0; + OSTime rtaElapsedTime = 0; if (m_speedrunInfo.m_isRunStarted) { - elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp; - } else if (m_speedrunInfo.m_endTimestamp != 0) { - elapsedTime = m_speedrunInfo.m_endTimestamp; + rtaElapsedTime = OSGetNativeTime() - m_speedrunInfo.m_rtaStartTimestamp; + } else if (m_speedrunInfo.m_rtaTimer != 0) { + rtaElapsedTime = m_speedrunInfo.m_rtaTimer; } - if (!m_speedrunInfo.m_isPauseIGT) { - m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime; + if (m_speedrunInfo.m_isRunStarted && !m_speedrunInfo.m_isPauseIGT) { + m_speedrunInfo.m_igtTimer = OSGetTime() - m_speedrunInfo.m_igtStartTimestamp - + m_speedrunInfo.m_totalLoadTime; } mSpeedrunTimer->SetAttribute("open", ""); if (getSettings().game.showSpeedrunRTATimer) { mSpeedrunRta->SetAttribute("open", ""); - mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatTime(elapsedTime)))); + mSpeedrunRta->SetInnerRML(escape(fmt::format("RTA {}", FormatElapsedTime(rtaElapsedTime)))); } else { mSpeedrunRta->RemoveAttribute("open"); } mSpeedrunIgt->SetInnerRML( - escape(fmt::format("IGT {}", FormatTime(m_speedrunInfo.m_igtTimer)))); + escape(fmt::format("IGT {}", FormatElapsedTime(m_speedrunInfo.m_igtTimer)))); } else { mSpeedrunTimer->RemoveAttribute("open"); } diff --git a/src/f_op/f_op_overlap_req.cpp b/src/f_op/f_op_overlap_req.cpp index 37b901c706..7d7f855c26 100644 --- a/src/f_op/f_op_overlap_req.cpp +++ b/src/f_op/f_op_overlap_req.cpp @@ -19,15 +19,16 @@ static int fopOvlpReq_phase_Done(overlap_request_class* i_overlapReq) { i_overlapReq->field_0x8 = 0; i_overlapReq->field_0xc = 0; - #if TARGET_PC +#if TARGET_PC if (dusk::getSettings().game.speedrunMode) { if (dusk::m_speedrunInfo.m_isRunStarted) { dusk::m_speedrunInfo.m_isPauseIGT = false; - dusk::m_speedrunInfo.m_totalLoadTime += OSGetTime() - dusk::m_speedrunInfo.m_loadStartTimestamp; + dusk::m_speedrunInfo.m_totalLoadTime += + OSGetTime() - dusk::m_speedrunInfo.m_loadStartTimestamp; dusk::m_speedrunInfo.m_loadStartTimestamp = OSGetTime(); } } - #endif +#endif return cPhs_NEXT_e; } diff --git a/src/f_pc/f_pc_manager.cpp b/src/f_pc/f_pc_manager.cpp index 3cda826854..22c705c720 100644 --- a/src/f_pc/f_pc_manager.cpp +++ b/src/f_pc/f_pc_manager.cpp @@ -22,7 +22,7 @@ #include "m_Do/m_Do_controller_pad.h" #if TARGET_PC -#include "dusk/frame_interpolation.h" +#include "dusk/game_clock.h" #endif #include "tracy/Tracy.hpp" @@ -68,8 +68,8 @@ void fpcM_Management(fpcM_ManagementFunc i_preExecuteFn, fpcM_ManagementFunc i_p } #ifdef TARGET_PC - // FRAME INTERP NOTE: Called in m_Do_main when interp is enabled - if (!dusk::frame_interp::is_enabled()) + // The main loop manages painting when simulation and presentation are separated. + if (!dusk::game_clock::g_frameTiming.separatePresentation) #endif { cAPIGph_Painter(); diff --git a/src/m_Do/m_Do_MemCardRWmng.cpp b/src/m_Do/m_Do_MemCardRWmng.cpp index 59688043a5..bb0326f623 100644 --- a/src/m_Do/m_Do_MemCardRWmng.cpp +++ b/src/m_Do/m_Do_MemCardRWmng.cpp @@ -320,7 +320,7 @@ static void mDoMemCdRWm_BuildHeader(mDoMemCdRWm_HeaderData* header) { #endif OSCalendarTime time; - OSTicksToCalendarTime(OSGetTime(), &time); + OSTicksToCalendarTime(DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()), &time); #if TARGET_PC if (dusk::version::isRegionPal()) { diff --git a/src/m_Do/m_Do_machine.cpp b/src/m_Do/m_Do_machine.cpp index a18657a2ad..3d0acca1a9 100644 --- a/src/m_Do/m_Do_machine.cpp +++ b/src/m_Do/m_Do_machine.cpp @@ -527,7 +527,7 @@ void myExceptionCallback(u16, OSContext*, u32, u32) { u32 btnHold; u32 btnTrig; - mDoMain::sHungUpTime = OSGetTime(); + mDoMain::sHungUpTime = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()); OSReportEnable(); cAPICPad_recalibrate(); // "Vibration stopping & resetting to default\n" diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index ccb6c35c1c..d169a7ad98 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -65,6 +65,7 @@ #include "dusk/iso_validate.hpp" #include "dusk/logging.h" #include "dusk/main.h" +#include "dusk/hq_minimap.hpp" #include "dusk/mod_loader.hpp" #include "dusk/mods/svc/window.hpp" #include "dusk/mouse.h" @@ -232,7 +233,7 @@ void main01(void) { OSReport("Entering Main Loop (main01)...\n"); - dusk::game_clock::ensure_initialized(); + dusk::game_clock::initialize(); do { // 1. Update Window Events @@ -247,7 +248,7 @@ void main01(void) { break; case AURORA_UNPAUSED: dusk::audio::SetPaused(false); - dusk::game_clock::reset_frame_timer(); + dusk::game_clock::reset(); dusk::mouse::on_focus_gained(); break; case AURORA_SDL_EVENT: @@ -289,46 +290,56 @@ void main01(void) { dusk::ui::update(); - const auto pacing = dusk::game_clock::advance_main_loop(); - if (pacing.is_interpolating) { - if (pacing.sim_ticks_to_run > 0) { - dusk::frame_interp::begin_frame(dusk::getSettings().game.enableFrameInterpolation, true, 0.0f); + const auto timing = dusk::game_clock::advance(); + const auto interpolationMode = dusk::getSettings().game.enableFrameInterpolation.getValue(); + if (timing.separatePresentation) { + if (timing.numSimTicks > 0) { + dusk::frame_interp::begin_frame(interpolationMode, true, 0.0f); dusk::frame_interp::set_ui_tick_pending(true); - - for (int sim_tick = 0; sim_tick < pacing.sim_ticks_to_run; ++sim_tick) { - dusk::frame_interp::begin_sim_tick(); + for (int i = 0; i < timing.numSimTicks; ++i) { + if (timing.interpolating) { + dusk::frame_interp::begin_sim_tick(); + } + dusk::game_clock::begin_sim_tick(); mDoCPd_c::read(); dusk::mouse::read(); - dusk::gyro::read(pacing.sim_pace); + dusk::gyro::read(dusk::game_clock::kSimPeriod); fapGm_Execute(); mDoAud_Execute(); dusk::game_clock::commit_sim_tick(); } } - dusk::frame_interp::begin_frame(dusk::getSettings().game.enableFrameInterpolation, false, - dusk::game_clock::sample_interpolation_step()); - dusk::frame_interp::interpolate(); - dusk::frame_interp::begin_presentation_camera(); - // run draw functions for anything specially marked to handle interp + const float interpolationStep = + timing.interpolating ? dusk::game_clock::sample_interpolation_step() : 1.0f; + dusk::frame_interp::begin_frame(interpolationMode, false, interpolationStep); + if (timing.interpolating) { + dusk::frame_interp::interpolate(); + dusk::frame_interp::begin_presentation_camera(); + } + fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw); cAPIGph_Painter(); - dusk::frame_interp::end_presentation_camera(); + if (timing.interpolating) { + dusk::frame_interp::end_presentation_camera(); + } dusk::frame_interp::set_ui_tick_pending(false); } else { dusk::frame_interp::begin_frame(dusk::FrameInterpMode::Off, true, 0.0f); dusk::frame_interp::set_ui_tick_pending(true); + dusk::game_clock::begin_sim_tick(); // Game Inputs mDoCPd_c::read(); dusk::mouse::read(); - dusk::gyro::read(pacing.presentation_dt_seconds); + dusk::gyro::read(timing.dt); // EXECUTE GAME LOGIC & RENDER // This calls mDoGph_Painter -> JFWDisplay -> GX Functions fapGm_Execute(); mDoAud_Execute(); + dusk::game_clock::commit_sim_tick(); } aurora_end_frame(); @@ -344,7 +355,10 @@ void main01(void) { static double last_fps_setting = 0.0; static Limiter::duration_t target_ns = 0; - if (dusk::getSettings().game.enableFrameInterpolation.getValue() == dusk::FrameInterpMode::Capped && !dusk::getTransientSettings().skipFrameRateLimit) { + if (dusk::getSettings().game.enableFrameInterpolation.getValue() == + dusk::FrameInterpMode::Capped && + !dusk::getTransientSettings().turboMode) + { ZoneScopedN("Frame limiter"); double current_fps = dusk::getSettings().video.maxFrameRate.getValue(); if (current_fps != last_fps_setting) { @@ -353,7 +367,8 @@ void main01(void) { } Limiter::duration_t sleepTime = main_loop_limiter.Sleep(target_ns); - dusk::frameUsagePct = 100.0f * (1.0f - static_cast(sleepTime) / static_cast(target_ns)); + dusk::frameUsagePct = + 100.0f * (1.0f - static_cast(sleepTime) / static_cast(target_ns)); } else { main_loop_limiter.Reset(); } @@ -711,6 +726,10 @@ int game_main(int argc, char* argv[]) { return 0; } + if (dusk::getSettings().game.enableHighQualityMinimapTextures.getValue()) { + dusk::hq_minimap::set_active(true); + } + dusk::texture_replacements::reload(); dusk::ui::initialize(); dusk::ui::push_document(std::make_unique(), true, true); @@ -840,7 +859,7 @@ int game_main(int argc, char* argv[]) { OSInit(); - mDoMain::sPowerOnTime = OSGetTime(); + mDoMain::sPowerOnTime = DUSK_IF_ELSE(OSGetSystemTime(), OSGetTime()); // Reset Data static mDoRstData sResetData = {0};