From be82e606b2ef228595508ebb8ab1857c15796d5c Mon Sep 17 00:00:00 2001 From: madeline Date: Fri, 8 May 2026 11:20:17 -0700 Subject: [PATCH] 45 achievements --- include/dusk/achievements.h | 3 +- res/rml/window.rcss | 14 ++++ src/dusk/achievements.cpp | 125 +++++++++++++++++++++++++++++------ src/dusk/ui/achievements.cpp | 27 +++++++- src/dusk/ui/achievements.hpp | 2 + src/f_ap/f_ap_game.cpp | 2 + src/m_Do/m_Do_main.cpp | 2 - 7 files changed, 149 insertions(+), 26 deletions(-) diff --git a/include/dusk/achievements.h b/include/dusk/achievements.h index bf0021c9d7..5c2758b6b7 100644 --- a/include/dusk/achievements.h +++ b/include/dusk/achievements.h @@ -12,9 +12,8 @@ namespace dusk { enum class AchievementCategory : uint8_t { - Story, - Collection, Challenge, + Collection, Minigame, Misc, Glitched diff --git a/res/rml/window.rcss b/res/rml/window.rcss index 817f3dc333..8dd29b55e4 100644 --- a/res/rml/window.rcss +++ b/res/rml/window.rcss @@ -17,6 +17,7 @@ body { window { display: flex; flex-flow: column; + position: relative; height: 100%; width: 100%; max-width: 1088dp; @@ -291,6 +292,19 @@ icon.question-mark { decorator: text("" center center); } +.achievement-total { + position: absolute; + top: 0; + right: 64dp; + height: 64dp; + line-height: 64dp; + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 16dp; + color: rgba(224, 219, 200, 55%); + pointer-events: none; +} + .achievement-row { display: flex; align-items: flex-start; diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp index a39221988b..eefcda978f 100644 --- a/src/dusk/achievements.cpp +++ b/src/dusk/achievements.cpp @@ -48,13 +48,13 @@ static constexpr auto ACHIEVEMENTS_FILENAME = "achievements.json"; std::vector AchievementSystem::makeEntries() { return { - // Story + // Challenge { { "hero_of_twilight", "Hero of Twilight", "Deliver the finishing blow to Ganondorf.", - AchievementCategory::Story, + AchievementCategory::Challenge, false, 0, 0, false }, [](Achievement& a, json&) { @@ -69,8 +69,8 @@ std::vector AchievementSystem::makeEntries() { { "completionist", "Completionist", - "100% the game.", - AchievementCategory::Story, + "Complete the game after collecting all equipment, heart containers, portals, bugs, poes, and hidden skills.", + AchievementCategory::Challenge, false, 0, 0, false }, [](Achievement& a, json&) { @@ -352,7 +352,19 @@ std::vector AchievementSystem::makeEntries() { }, {} }, - // Challenge + { + { + "all_letters", + "We Deliver!", + "Collect all 16 postman letters.", + AchievementCategory::Collection, + true, 16, 0, false + }, + [](Achievement& a, json&) { + a.progress = dMeter2Info_getRecieveLetterNum(); + }, + {} + }, { { "cave_of_ordeals", @@ -428,7 +440,7 @@ std::vector AchievementSystem::makeEntries() { "dark_hammer_one_hit", "Mortal Edge", "Defeat Dark Hammer in a single hit.", - AchievementCategory::Challenge, + AchievementCategory::Misc, false, 0, 0, false }, [](Achievement& a, json&) { @@ -481,7 +493,7 @@ std::vector AchievementSystem::makeEntries() { "bow_100m_hit", "Long Shot", "Hit an enemy from over 100 meters away with the bow.", - AchievementCategory::Challenge, + AchievementCategory::Misc, false, 0, 0, false }, [](Achievement& a, json&) { @@ -616,6 +628,36 @@ std::vector AchievementSystem::makeEntries() { }, {} }, + { + { + "star_2_under_40s", + "Rising Star", + "Complete the STAR Prize 2 minigame in under 40 seconds.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if(dComIfGs_getHookGameTime() > 0 && dComIfGs_getHookGameTime() <= 40000) { + a.progress = 1; + } + }, + {} + }, + { + { + "star_2_under_30s", + "Shooting Star", + "Complete the STAR Prize 2 minigame in under 30 seconds.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if(dComIfGs_getHookGameTime() > 0 && dComIfGs_getHookGameTime() <= 30000) { + a.progress = 1; + } + }, + {} + }, // Misc { { @@ -800,19 +842,6 @@ std::vector AchievementSystem::makeEntries() { }, {} }, - { - { - "ultimate_delivery", - "The Ultimate Delivery", - "Have all 16 postman letters at the same time.", - AchievementCategory::Glitched, - true, 16, 0, false - }, - [](Achievement& a, json&) { - a.progress = dMeter2Info_getRecieveLetterNum(); - }, - {} - }, { { "speedrun_4h", @@ -918,6 +947,62 @@ std::vector AchievementSystem::makeEntries() { } }, {} + }, + { + { + "lakebed_before_lanayru", + "White Midna Glitch", + "Clear the Lakebed Temple before clearing Lanayru's Twilight.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_isEventBit(dSv_event_flag_c::M_045) && + !dComIfGs_isDarkClearLV(2)) { + a.progress = 1; + } + }, + {} + }, + { + { + "early_hidden_village", + "Quick Detour", + "Rescue the Hidden Village before clearing Goron Mines.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_isEventBit(dSv_event_flag_c::F_0278) && + !dComIfGs_isEventBit(dSv_event_flag_c::M_031)) { + a.progress = 1; + } + }, + {} + }, + { + { + "forest_temple_no_boomerang", + "Must Have Been The Wind", + "Complete the Forest Temple without obtaining the Gale Boomerang.", + AchievementCategory::Glitched, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (!dComIfGs_isEventBit(dSv_event_flag_c::M_022)) { + return; + } + if (daPy_getPlayerActorClass() == nullptr) { + return; + } + for (int i = 0; i < 24; ++i) { + if (dComIfGs_getItem(i, false) == dItemNo_BOOMERANG_e) { + return; + } + } + a.progress = 1; + }, + {} } }; } diff --git a/src/dusk/ui/achievements.cpp b/src/dusk/ui/achievements.cpp index 725d662ec1..b631d444f5 100644 --- a/src/dusk/ui/achievements.cpp +++ b/src/dusk/ui/achievements.cpp @@ -16,9 +16,8 @@ struct CategoryInfo { }; constexpr CategoryInfo kCategories[] = { - {AchievementCategory::Story, "Story"}, - {AchievementCategory::Collection, "Collection"}, {AchievementCategory::Challenge, "Challenge"}, + {AchievementCategory::Collection, "Collection"}, {AchievementCategory::Minigame, "Minigame"}, {AchievementCategory::Misc, "Misc"}, {AchievementCategory::Glitched, "Glitched"}, @@ -114,6 +113,13 @@ private: AchievementsWindow::AchievementsWindow() { const auto all = AchievementSystem::get().getAchievements(); + { + auto elem = mDocument->CreateElement("div"); + elem->SetClass("achievement-total", true); + mTotalEl = mRoot->AppendChild(std::move(elem)); + updateTotal(); + } + for (const auto& catInfo : kCategories) { int catTotal = 0; for (const auto& a : all) { @@ -201,8 +207,25 @@ void AchievementsWindow::update() { if (dirty) { mSnapshot = current; refresh_active_tab(); + updateTotal(); } Window::update(); } +void AchievementsWindow::updateTotal() { + if (mTotalEl == nullptr) { + return; + } + const auto all = AchievementSystem::get().getAchievements(); + int total = static_cast(all.size()); + int unlocked = 0; + for (const auto& a : all) { + if (a.unlocked) { + ++unlocked; + } + } + const int pct = total > 0 ? (unlocked * 100 / total) : 0; + mTotalEl->SetInnerRML(fmt::format("{}%", pct)); +} + } // namespace dusk::ui diff --git a/src/dusk/ui/achievements.hpp b/src/dusk/ui/achievements.hpp index 3a65705600..e0c6fe41ad 100644 --- a/src/dusk/ui/achievements.hpp +++ b/src/dusk/ui/achievements.hpp @@ -13,7 +13,9 @@ public: void update() override; private: + void updateTotal(); std::vector mSnapshot; + Rml::Element* mTotalEl = nullptr; }; } // namespace dusk::ui diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 68d244ef72..dc14933b50 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -14,6 +14,7 @@ #include "d/actor/d_a_midna.h" #include "d/d_model.h" #include "d/d_tresure.h" +#include "dusk/achievements.h" #include "dusk/frame_interpolation.h" #include "dusk/livesplit.h" #include "dusk/logging.h" @@ -828,6 +829,7 @@ void fapGm_Execute() { cCt_Counter(0); #ifdef TARGET_PC dusk::speedrun::onGameFrame(); + dusk::AchievementSystem::get().tick(); #endif } diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 61e65928f8..8719998bd5 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -46,7 +46,6 @@ #include #include #include "SSystem/SComponent/c_API.h" -#include "dusk/achievements.h" #include "dusk/app_info.hpp" #include "dusk/crash_reporting.h" #include "dusk/dusk.h" @@ -283,7 +282,6 @@ void main01(void) { dusk::gyro::read(pacing.sim_pace); fapGm_Execute(); mDoAud_Execute(); - dusk::AchievementSystem::get().tick(); dusk::game_clock::commit_sim_tick(); } }