diff --git a/extern/aurora b/extern/aurora index 8a2b80ecb1..c77a4d0c3c 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 8a2b80ecb104625319c2818fd6d752bab8617679 +Subproject commit c77a4d0c3c6a0d9f584a30e6ab1661634959c32b diff --git a/include/d/d_msg_object.h b/include/d/d_msg_object.h index b55ea73904..d2ac4ecb2a 100644 --- a/include/d/d_msg_object.h +++ b/include/d/d_msg_object.h @@ -67,6 +67,9 @@ public: bool isStaffMessage(); bool isSaveMessage(); bool isTalkMessage(); +#if TARGET_PC + bool isShopItemMessage(); +#endif const char* getSmellName(); const char* getPortalName(); const char* getBombName(); diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index f94cdc9475..eca620ef98 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -16,6 +16,7 @@ #ifdef TARGET_PC constexpr u16 kMapResolutionMultiplier = 4; +constexpr u16 kMapCircleSize = 16 * kMapResolutionMultiplier; #endif void dMpath_n::dTexObjAggregate_c::create() { @@ -32,6 +33,48 @@ void dMpath_n::dTexObjAggregate_c::create() { JUT_ASSERT(74, image->magFilter == GX_NEAR); mDoLib_setResTimgObj(image, mp_texObj[lp1], 0, NULL); } + +#if TARGET_PC + auto hqCircle = JKR_NEW TGXTexObj(); + + static bool hqCircleDrawn = false; + static u8 hqCircleData[kMapCircleSize * kMapCircleSize]; + + if (!hqCircleDrawn) { + const auto center = kMapCircleSize / 2.0f; + const auto radiusSq = center * center; + const auto blocksAcross = kMapCircleSize >> 3; + const auto totalPixels = sizeof(hqCircleData); + + for (size_t i = 0; i < totalPixels; 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; + + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + + // the original texture is in I4 format and uses 1 to indicate if inside the circle + // so we scale to I8 range: 255 / 15 = 17 + hqCircleData[i] = (dx * dx + dy * dy < radiusSq) ? 17 : 0; + } + hqCircleDrawn = true; + } + + GXInitTexObj(hqCircle, hqCircleData, kMapCircleSize, kMapCircleSize, GX_TF_I8, GX_CLAMP, + GX_CLAMP, GX_FALSE); + GXInitTexObjLOD(hqCircle, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); + mp_texObj[6] = hqCircle; +#endif } void dMpath_n::dTexObjAggregate_c::remove() { diff --git a/src/d/d_menu_letter.cpp b/src/d/d_menu_letter.cpp index a0155ef6b4..589b2553c0 100644 --- a/src/d/d_menu_letter.cpp +++ b/src/d/d_menu_letter.cpp @@ -17,6 +17,10 @@ #include "d/d_msg_scrn_arrow.h" #include "d/d_lib.h" +#ifdef TARGET_PC +#include "dusk/achievements.h" +#endif + #if VERSION == VERSION_GCN_JPN #define D_MENU_LETTER_LINE_MAX 9 #else @@ -514,6 +518,10 @@ void dMenu_Letter_c::read_open_init() { setAButtonString(0); setBButtonString(0); mpBlackTex->setAlpha(0); + + #ifdef TARGET_PC + dusk::AchievementSystem::get().signal("open_letter"); + #endif } void dMenu_Letter_c::read_open_move() { diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp index 4040eb0d7f..fab6906ee0 100644 --- a/src/d/d_msg_class.cpp +++ b/src/d/d_msg_class.cpp @@ -1987,13 +1987,6 @@ bool jmessage_tSequenceProcessor::do_isReady() { } #endif -#if TARGET_PC - if (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) { - field_0xb2 = 1; - pReference->setSendTimer(0); - } -#endif - if (dComIfGp_checkMesgBgm()) { bool isItemMusicPlaying = true; if (mDoAud_checkPlayingSubBgmFlag() != Z2BGM_ITEM_GET && @@ -2066,7 +2059,7 @@ bool jmessage_tSequenceProcessor::do_isReady() { case 0: case 5: case 6: - if (mDoCPd_c::getTrigA(PAD_1) || field_0xb2 != 0) { + if (mDoCPd_c::getTrigA(PAD_1) || field_0xb2 != 0 IF_DUSK(|| (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)))) { field_0xa4 = 0; pReference->onBatchFlag(); pReference->setCharCnt(D_MSG_CLASS_CHAR_CNT_MAX); diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index ae0e3d8427..00dfc6626e 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -32,6 +32,9 @@ #if TARGET_PC #include "dusk/settings.h" +#include +#include +#include #endif static void dMsgObject_addFundRaising(s16 param_0); @@ -1594,7 +1597,7 @@ u8 dMsgObject_c::isSend() { return 2; } } else { - if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) ||) + if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0) && !isShopItemMessage()) ||) mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { return 2; } @@ -1866,6 +1869,40 @@ bool dMsgObject_c::isTalkMessage() { return true; } +#if TARGET_PC +bool dMsgObject_c::isShopItemMessage() { + + // Probably a better way to do this than just listing every message id, but this works for now + // Note: Keep contents sorted so we can use binary search + const auto shopMsgIds = std::to_array>({ + {}, + // zel_01.bmg - Seras Shop + {7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7013, 7014, 7022, 7023, 7028, 7029, + 7044, 7045, 7053}, + // zel_02.bmg - Kakariko Shops + {5251, 5253, 5254, 5256, 5258, 5259, 5653, 5654, 5656, 5660, 5661, 5664, 5665, 5697, 5698, + 5699, 5803, 5804, 5806, 5810, 5811, 5812, 5814, 5821, 5823, 5824, 5987, 5988, 5989, 5990, + 5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999}, + // zel_03.bmg - Death Mountain Shop + {5303, 5304, 5306, 5310, 5311, 5314, 5315, 5322, 5323, 5324, 5496, 5497, 5498, 5499}, + // zel_04.bmg - Castle Town Shops + {5407, 5408, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5416, 5417, 5418, 5419, 5420, 5431, + 5432, 5433, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5444, 5449, 5450, 5451, 5452, + 5462}, + // zel_05.bmg - Oocca Shop + {9428, 9429, 9430, 9431, 9432, 9437, 9443, 9448, 9449, 9451, 9459} + }); + + u16 id = mMessageID; + s16 group = dMsgObject_getGroupID(); + if (group < shopMsgIds.size()) { + return std::ranges::binary_search(shopMsgIds[group], id); + } + return false; + +} +#endif + const char* dMsgObject_c::getSmellName() { JMSMesgInfo_c* info_header_p = (JMSMesgInfo_c*)((char*)mpMsgRes + 0x20); char* data_ptr = (char*)info_header_p + info_header_p->header.size; diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp index e0730447d5..26a302678e 100644 --- a/src/dusk/achievements.cpp +++ b/src/dusk/achievements.cpp @@ -335,18 +335,13 @@ std::vector AchievementSystem::makeEntries() { false, 0, 0, false }, [](Achievement& a, json&) { - static int titleNoDemoFrames = 0; if (fopAcM_SearchByName(fpcNm_TITLE_e) == nullptr) { - titleNoDemoFrames = 0; return; } - const auto* link = static_cast(daPy_getPlayerActorClass()); - if (link != nullptr && dDemo_c::getMode() == 0) { - if (++titleNoDemoFrames >= 60) { + const auto* player = static_cast(daPy_getPlayerActorClass()); + + if (player != nullptr && player->mDemo.getDemoMode() == 1) { a.progress = 1; - } - } else { - titleNoDemoFrames = 0; } }, {} @@ -413,6 +408,41 @@ std::vector AchievementSystem::makeEntries() { } }, {} + }, + { + { + "email_me", + "Email Me", + "Read a letter during the Dark Beast Ganon fight.", + AchievementCategory::Misc, + false, 0, 0, false + }, + [](Achievement& a, json&) { + void* dbgExists = fopAcM_SearchByName(fpcNm_B_MGN_e); + if (dbgExists && AchievementSystem::get().hasSignal("open_letter")) { + a.progress = 1; + } + }, + {} + }, + { + { + "heavy-hitter", + "Heavy Hitter", + "Wear the Iron Boots during the end credits.", + AchievementCategory::Misc, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) { + return; + } + if (daPy_getPlayerActorClass()->checkEquipHeavyBoots()) { + a.progress = 1; + } + }, + {} } }; }