diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveBarinadeDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveBarinadeDefeat.png new file mode 100644 index 00000000000..534f34726c1 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveBarinadeDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveBarinadeDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveBarinadeDefeat_Grayed.png new file mode 100644 index 00000000000..935f2dc6234 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveBarinadeDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveBongoDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveBongoDefeat.png new file mode 100644 index 00000000000..21732d2b2c5 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveBongoDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveBongoDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveBongoDefeat_Grayed.png new file mode 100644 index 00000000000..bc21cee00e6 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveBongoDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveGanonDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveGanonDefeat.png new file mode 100644 index 00000000000..d7d55b31c98 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveGanonDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveGanonDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveGanonDefeat_Grayed.png new file mode 100644 index 00000000000..4ac5d6922cd Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveGanonDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveGanondorfDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveGanondorfDefeat.png new file mode 100644 index 00000000000..1f43634a07e Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveGanondorfDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveGanondorfDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveGanondorfDefeat_Grayed.png new file mode 100644 index 00000000000..58a985a1285 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveGanondorfDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveGohmaDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveGohmaDefeat.png new file mode 100644 index 00000000000..a461905087e Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveGohmaDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveGohmaDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveGohmaDefeat_Grayed.png new file mode 100644 index 00000000000..7613baf5d8f Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveGohmaDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveKDDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveKDDefeat.png new file mode 100644 index 00000000000..53d94c00e03 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveKDDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveKDDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveKDDefeat_Grayed.png new file mode 100644 index 00000000000..035147dfde4 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveKDDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveMorphaDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveMorphaDefeat.png new file mode 100644 index 00000000000..681bff8e9eb Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveMorphaDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveMorphaDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveMorphaDefeat_Grayed.png new file mode 100644 index 00000000000..f1a85b00632 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveMorphaDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchievePGDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchievePGDefeat.png new file mode 100644 index 00000000000..d899e87e4c6 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchievePGDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchievePGDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchievePGDefeat_Grayed.png new file mode 100644 index 00000000000..249b6626339 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchievePGDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveTwinrovaDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveTwinrovaDefeat.png new file mode 100644 index 00000000000..64d4127236e Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveTwinrovaDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveTwinrovaDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveTwinrovaDefeat_Grayed.png new file mode 100644 index 00000000000..b99dda72842 Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveTwinrovaDefeat_Grayed.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveVolvagiaDefeat.png b/soh/assets/custom/textures/achievement_icons/gAchieveVolvagiaDefeat.png new file mode 100644 index 00000000000..20be8d48baf Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveVolvagiaDefeat.png differ diff --git a/soh/assets/custom/textures/achievement_icons/gAchieveVolvagiaDefeat_Grayed.png b/soh/assets/custom/textures/achievement_icons/gAchieveVolvagiaDefeat_Grayed.png new file mode 100644 index 00000000000..579e624e65b Binary files /dev/null and b/soh/assets/custom/textures/achievement_icons/gAchieveVolvagiaDefeat_Grayed.png differ diff --git a/soh/include/z64save.h b/soh/include/z64save.h index 35f30c26398..adbcf754de0 100644 --- a/soh/include/z64save.h +++ b/soh/include/z64save.h @@ -222,6 +222,10 @@ typedef struct ShipQuestSaveContextData { ShipQuestSpecificSaveContextData data; } ShipQuestSaveContextData; +typedef struct ShipAchievementSaveContextData { + u32 achievementFlags; +} ShipAchievementSaveContextData; + typedef struct ShipSaveContextData { u16 pendingSale; u16 pendingSaleMod; @@ -229,6 +233,7 @@ typedef struct ShipSaveContextData { SohStats stats; FaroresWindData backupFW; ShipQuestSaveContextData quest; + ShipAchievementSaveContextData achievements; u8 maskMemory; u8 filenameLanguage; //TODO: Move non-rando specific flags to a new sohInf and move the remaining randomizerInf to ShipRandomizerSaveContextData diff --git a/soh/soh/Enhancements/achievements.cpp b/soh/soh/Enhancements/achievements.cpp new file mode 100644 index 00000000000..4cae2330cdb --- /dev/null +++ b/soh/soh/Enhancements/achievements.cpp @@ -0,0 +1,120 @@ +#include "soh/Enhancements/achievements.h" +#include "soh/SaveManager.h" +#include "soh/Notification/Notification.h" +#include "soh/ShipInit.hpp" +#include "z64.h" +#include "functions.h" +#include "variables.h" +#include + +extern "C" SaveContext gSaveContext; + +static RegisterShipInitFunc initFunc(Achievements_Init); + +void Achievements_InitDefaults(bool isDebug); +void Achievements_Load(); +void Achievements_Save(SaveContext* saveContext, int sectionID, bool fullSave); + +void Achievements_LoadIcons() { + auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); + + gui->LoadTextureFromRawImage("gAchieveGohmaDefeat", "textures/achievement_icons/gAchieveGohmaDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveGohmaDefeat_Grayed", "textures/achievement_icons/gAchieveGohmaDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchieveKDDefeat", "textures/achievement_icons/gAchieveKDDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveKDDefeat_Grayed", "textures/achievement_icons/gAchieveKDDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchieveBarinadeDefeat", "textures/achievement_icons/gAchieveBarinadeDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveBarinadeDefeat_Grayed", "textures/achievement_icons/gAchieveBarinadeDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchievePGDefeat", "textures/achievement_icons/gAchievePGDefeat.png"); + gui->LoadTextureFromRawImage("gAchievePGDefeat_Grayed", "textures/achievement_icons/gAchievePGDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchieveVolvagiaDefeat", "textures/achievement_icons/gAchieveVolvagiaDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveVolvagiaDefeat_Grayed", "textures/achievement_icons/gAchieveVolvagiaDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchieveMorphaDefeat", "textures/achievement_icons/gAchieveMorphaDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveMorphaDefeat_Grayed", "textures/achievement_icons/gAchieveMorphaDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchieveBongoDefeat", "textures/achievement_icons/gAchieveBongoDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveBongoDefeat_Grayed", "textures/achievement_icons/gAchieveBongoDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchieveTwinrovaDefeat", "textures/achievement_icons/gAchieveTwinrovaDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveTwinrovaDefeat_Grayed", "textures/achievement_icons/gAchieveTwinrovaDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchieveGanondorfDefeat", "textures/achievement_icons/gAchieveGanondorfDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveGanondorfDefeat_Grayed", "textures/achievement_icons/gAchieveGanondorfDefeat_Grayed.png"); + gui->LoadTextureFromRawImage("gAchieveGanonDefeat", "textures/achievement_icons/gAchieveGanonDefeat.png"); + gui->LoadTextureFromRawImage("gAchieveGanonDefeat_Grayed", "textures/achievement_icons/gAchieveGanonDefeat_Grayed.png"); +} + +void Achievements_Init() { + Achievements_LoadIcons(); + SaveManager::Instance->AddLoadFunction("achievements", 1, Achievements_Load); + SaveManager::Instance->AddSaveFunction("achievements", 1, Achievements_Save, true, SECTION_PARENT_NONE); + SaveManager::Instance->AddInitFunction(Achievements_InitDefaults); +} + +void Achievements_InitDefaults(bool isDebug) { + gSaveContext.ship.achievements.achievementFlags = 0; +} + +void Achievements_Load() { + SaveManager::Instance->LoadData("achievementFlags", gSaveContext.ship.achievements.achievementFlags); + + SPDLOG_INFO("Loaded achievementFlags={}",gSaveContext.ship.achievements.achievementFlags); +} + +void Achievements_Save(SaveContext* saveContext, int sectionID, bool fullSave) { + SaveManager::Instance->SaveData("achievementFlags", saveContext->ship.achievements.achievementFlags); +} + +extern "C" bool Achievements_IsUnlocked(AchievementId id) { + return (gSaveContext.ship.achievements.achievementFlags & (1ULL << id)) != 0; +} + +extern "C" void Achievements_TryUnlock(AchievementId id) { + if (!Achievements_IsUnlocked(id)) { + gSaveContext.ship.achievements.achievementFlags |= (1ULL << id); + const AchievementInfo* info = Achievements_GetInfo(id); + Notification::Emit({ + .itemIcon = info->icon, + .prefix = "Achievement Unlocked!", + .prefixColor = ImVec4(1.0f, 0.85f, 0.0f, 1.0f), + .message = info->name, + .messageColor = ImVec4(1.0f, 1.0f, 1.0f, 1.0f), + .achievement = true, + }); + Audio_PlaySoundGeneral(NA_SE_SY_KINSTA_MARK_APPEAR, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, + &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); + + SPDLOG_INFO("Unlock {} -> flags={}", static_cast(id), gSaveContext.ship.achievements.achievementFlags); + } +} + +const AchievementInfo* Achievements_GetInfo(AchievementId id) { + for (size_t i = 0; i < gAchievementCount; i++) { + if (gAchievements[i].id == id) { + return &gAchievements[i]; + } + } + return NULL; +} + +const AchievementInfo gAchievements[] = { + { ACHIEVEMENT_DEFEAT_GOHMA, "Parasitic Armored Arachnid", "gAchieveGohmaDefeat", "gAchieveGohmaDefeat_Grayed", + "Defeat Queen Gohma." }, + { ACHIEVEMENT_DEFEAT_KD, "Infernal Dinosaur", "gAchieveKDDefeat", "gAchieveKDDefeat_Grayed", + "Defeat King Dodongo." }, + { ACHIEVEMENT_DEFEAT_BARINADE, "Bio-Electric Anemone", "gAchieveBarinadeDefeat", "gAchieveBarinadeDefeat_Grayed", + "Defeat Barinade." }, + { ACHIEVEMENT_DEFEAT_PG, "Evil Spirit from Beyond", "gAchievePGDefeat", "gAchievePGDefeat_Grayed", + "Defeat Phantom Ganon." }, + { ACHIEVEMENT_DEFEAT_VOLVAGIA, "Subterranean Lava Dragon", "gAchieveVolvagiaDefeat", "gAchieveVolvagiaDefeat_Grayed", + "Defeat Volvagia." }, + { ACHIEVEMENT_DEFEAT_MORPHA, "Giant Aquatic Amoeba", "gAchieveMorphaDefeat", "gAchieveMorphaDefeat_Grayed", + "Defeat Morpha." }, + { ACHIEVEMENT_DEFEAT_BONGO, "Phantom Shadow Beast", "gAchieveBongoDefeat", "gAchieveBongoDefeat_Grayed", + "Defeat Bongo." }, + { ACHIEVEMENT_DEFEAT_TWINROVA, "Sorceress Sisters", "gAchieveTwinrovaDefeat", "gAchieveTwinrovaDefeat_Grayed", + "Defeat Twinrova." }, + { ACHIEVEMENT_DEFEAT_GANONDORF, "Great King of Evil", "gAchieveGanondorfDefeat", "gAchieveGanondorfDefeat_Grayed", + "Defeat Ganondorf." }, + { ACHIEVEMENT_DEFEAT_GANON, "Ganon", "gAchieveGanonDefeat", "gAchieveGanonDefeat_Grayed", + "Defeat Ganon." }, +}; + +const size_t gAchievementCount = + sizeof(gAchievements) / sizeof(gAchievements[0]); \ No newline at end of file diff --git a/soh/soh/Enhancements/achievements.h b/soh/soh/Enhancements/achievements.h new file mode 100644 index 00000000000..232760e4611 --- /dev/null +++ b/soh/soh/Enhancements/achievements.h @@ -0,0 +1,38 @@ +#pragma once + +typedef enum { + ACHIEVEMENT_DEFEAT_GOHMA = 0, + ACHIEVEMENT_DEFEAT_KD = 1, + ACHIEVEMENT_DEFEAT_BARINADE = 2, + ACHIEVEMENT_DEFEAT_PG = 3, + ACHIEVEMENT_DEFEAT_VOLVAGIA = 4, + ACHIEVEMENT_DEFEAT_MORPHA = 5, + ACHIEVEMENT_DEFEAT_BONGO = 6, + ACHIEVEMENT_DEFEAT_TWINROVA = 7, + ACHIEVEMENT_DEFEAT_GANONDORF = 8, + ACHIEVEMENT_DEFEAT_GANON = 9, +} AchievementId; + +typedef struct { + AchievementId id; + const char* name; + const char* icon; + const char* grayedIcon; + const char* description; +} AchievementInfo; + +extern const AchievementInfo gAchievements[]; +extern const size_t gAchievementCount; + +#ifdef __cplusplus +extern "C" { +#endif + +void Achievements_Init(); +bool Achievements_IsUnlocked(AchievementId id); +void Achievements_TryUnlock(AchievementId id); +const AchievementInfo* Achievements_GetInfo(AchievementId id); + +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/Notification/Notification.cpp b/soh/soh/Notification/Notification.cpp index b101e2e7531..576417e40fd 100644 --- a/soh/soh/Notification/Notification.cpp +++ b/soh/soh/Notification/Notification.cpp @@ -18,6 +18,9 @@ namespace Notification { static uint32_t nextId = 0; static std::vector notifications = {}; +void drawStandardNotification(const Options& notification); +void drawAchievementNotification(const Options& notification); + void Window::Draw() { auto vp = ImGui::GetMainViewport(); @@ -61,6 +64,11 @@ void Window::Draw() { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); } + if(notification.achievement) { + ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 215, 0, 255)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 3.0f); + } + ImGui::Begin(("notification#" + std::to_string(notification.id)).c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | @@ -91,24 +99,17 @@ void Window::Draw() { ImGui::SetWindowPos(notificationPos); - if (notification.itemIcon != nullptr) { - ImGui::Image( - std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()) - ->GetTextureByName(notification.itemIcon), - ImVec2(24, 24)); - ImGui::SameLine(); - } - if (!notification.prefix.empty()) { - ImGui::TextColored(notification.prefixColor, "%s", notification.prefix.c_str()); - ImGui::SameLine(); - } - ImGui::TextColored(notification.messageColor, "%s", notification.message.c_str()); - if (!notification.suffix.empty()) { - ImGui::SameLine(); - ImGui::TextColored(notification.suffixColor, "%s", notification.suffix.c_str()); + if (notification.achievement) { + drawAchievementNotification(notification); + } else { + drawStandardNotification(notification); } ImGui::End(); + if(notification.achievement) { + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + } ImGui::PopStyleVar(); } @@ -137,10 +138,48 @@ void Emit(Options notification) { notification.remainingTime = CVarGetFloat(CVAR_SETTING("Notifications.Duration"), 10.0f); } notifications.push_back(notification); - if (!notification.mute && !CVarGetInteger(CVAR_SETTING("Notifications.Mute"), 0)) { + if (!notification.mute && !notification.achievement && !CVarGetInteger(CVAR_SETTING("Notifications.Mute"), 0)) { Audio_PlaySoundGeneral(NA_SE_SY_METRONOME, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb); } } +void drawStandardNotification(const Options& notification) { + if (notification.itemIcon != nullptr) { + ImGui::Image( + std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()) + ->GetTextureByName(notification.itemIcon), + ImVec2(24, 24)); + ImGui::SameLine(); + } + if (!notification.prefix.empty()) { + ImGui::TextColored(notification.prefixColor, "%s", notification.prefix.c_str()); + ImGui::SameLine(); + } + ImGui::TextColored(notification.messageColor, "%s", notification.message.c_str()); + if (!notification.suffix.empty()) { + ImGui::SameLine(); + ImGui::TextColored(notification.suffixColor, "%s", notification.suffix.c_str()); + } +} + +void drawAchievementNotification(const Options& notification) { + if (notification.itemIcon != nullptr) { + ImGui::Image( + std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()) + ->GetTextureByName(notification.itemIcon), + ImVec2(80, 80)); + ImGui::SameLine(); + } + + ImGui::BeginGroup(); + + if (!notification.prefix.empty()) { + ImGui::TextColored(notification.prefixColor, "%s", notification.prefix.c_str()); + ImGui::Dummy(ImVec2(0.0f, 2.0f)); //spacing + } + ImGui::TextColored(notification.messageColor, "%s", notification.message.c_str()); + ImGui::EndGroup(); +} + } // namespace Notification diff --git a/soh/soh/Notification/Notification.h b/soh/soh/Notification/Notification.h index bf1d4ec04e3..35509ddd790 100644 --- a/soh/soh/Notification/Notification.h +++ b/soh/soh/Notification/Notification.h @@ -18,6 +18,7 @@ struct Options { ImVec4 suffixColor = ImVec4(1.0f, 0.5f, 0.5f, 1.0f); float remainingTime = 0.0f; // Seconds bool mute = false; // whether notification should make a noise + bool achievement = false; // whether this notification is for an achievement (custom visuals) }; class Window final : public Ship::GuiWindow { diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 67e250a58c3..b073f4b2608 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -593,6 +593,7 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, metaSaveBlock["sections"]["sohStats"]["data"]["buildVersion"], ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); + } void SaveManager::InitMeta(int fileNum) { diff --git a/soh/soh/SohGui/Menu.cpp b/soh/soh/SohGui/Menu.cpp index c41bfe74a41..b2823e952f8 100644 --- a/soh/soh/SohGui/Menu.cpp +++ b/soh/soh/SohGui/Menu.cpp @@ -672,6 +672,10 @@ void Menu::DrawElement() { headerWidth += 200.0f; } for (auto& label : menuOrder) { + if (label == "Achievements") { + continue; //ignore achievements for header sizing + } + ImVec2 size = ImGui::CalcTextSize(label.c_str()); headerSizes.push_back(size); headerWidth += size.x + style.FramePadding.x * 2 + style.ItemSpacing.x; @@ -713,10 +717,17 @@ void Menu::DrawElement() { ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_HorizontalScrollbar); uint8_t curIndex = 0; for (auto& label : menuOrder) { + auto& entry = menuEntries.at(label); + + if (label == "Achievements") { + if (headerIndex == label) { + sidebar = &entry.sidebars; + } + continue; //make the achievement menu header hidden + } if (curIndex != 0) { ImGui::SameLine(); } - auto& entry = menuEntries.at(label); std::string nextIndex = label; UIWidgets::PushStyleButton(menuThemeIndex); if (headerIndex != label) { @@ -763,7 +774,15 @@ void Menu::DrawElement() { ImGui::PopStyleColor(); } ImGui::EndChild(); - ImGui::SameLine(menuSize.x - (buttonSize.x * 3) - (style.ItemSpacing.x * 2)); + ImGui::SameLine(menuSize.x - (buttonSize.x * 4) - (style.ItemSpacing.x * 3)); + UIWidgets::ButtonOptions options4 = {}; + options4.color = UIWidgets::Colors::Violet; + options4.size = UIWidgets::Sizes::Inline; + options4.tooltip = "Achievements"; + if (UIWidgets::Button(ICON_FA_TROPHY, options4)) { + CVarSetString(CVAR_SETTING("Menu.ActiveHeader"), "Achievements"); + } + ImGui::SameLine(); UIWidgets::ButtonOptions options3 = {}; options3.color = UIWidgets::Colors::Red; options3.size = UIWidgets::Sizes::Inline; diff --git a/soh/soh/SohGui/SohMenu.cpp b/soh/soh/SohGui/SohMenu.cpp index ff793deb247..b63704c7af0 100644 --- a/soh/soh/SohGui/SohMenu.cpp +++ b/soh/soh/SohGui/SohMenu.cpp @@ -90,7 +90,8 @@ void SohMenu::AddMenuElements() { AddMenuRandomizer(); AddMenuNetwork(); AddMenuDevTools(); - + AddMenuAchievements(); + if (CVarGetInteger(CVAR_SETTING("Menu.SidebarSearch"), 0)) { InsertSidebarSearch(); } diff --git a/soh/soh/SohGui/SohMenu.h b/soh/soh/SohGui/SohMenu.h index c50c4367409..5d5a38322d2 100644 --- a/soh/soh/SohGui/SohMenu.h +++ b/soh/soh/SohGui/SohMenu.h @@ -47,6 +47,7 @@ class SohMenu : public Ship::Menu { void AddMenuDevTools(); void AddMenuRandomizer(); void AddMenuNetwork(); + void AddMenuAchievements(); static void UpdateLanguageMap(std::map& languageMap); private: diff --git a/soh/soh/SohGui/SohMenuAchievements.cpp b/soh/soh/SohGui/SohMenuAchievements.cpp new file mode 100644 index 00000000000..6d0198ebaf5 --- /dev/null +++ b/soh/soh/SohGui/SohMenuAchievements.cpp @@ -0,0 +1,59 @@ +#include "SohMenu.h" +#include "soh/OTRGlobals.h" +#include "soh/Enhancements/achievements.h" +#include + +extern "C" { +#include "variables.h" +} + +namespace SohGui { + +extern std::shared_ptr mSohMenu; +using namespace UIWidgets; + +void DrawAchievementEntry(const AchievementInfo& achievement, bool unlocked) { + auto gui = std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()); + const char* icon = unlocked ? achievement.icon : achievement.grayedIcon; + auto texture = gui->GetTextureByName(icon); + + ImGui::Image(texture, ImVec2(64, 64), ImVec2(0, 0), ImVec2(1, 1)); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", achievement.description); + } + ImGui::SameLine(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 16.0f); + ImGui::Text("%s", achievement.name); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", achievement.description); + } +} + +void DrawAchievementsWidget(WidgetInfo& widget) { + ImGui::SeparatorText("Obtained achievements"); + + for (size_t i = 0; i < gAchievementCount; i++) { + if (Achievements_IsUnlocked(gAchievements[i].id)) { + DrawAchievementEntry(gAchievements[i], true); + } + } + + ImGui::SeparatorText("Locked achievements"); + + for (size_t i = 0; i < gAchievementCount; i++) { + if (!Achievements_IsUnlocked(gAchievements[i].id)) { + DrawAchievementEntry(gAchievements[i], false); + } + } +} + +void SohMenu::AddMenuAchievements() { + // Add Achievements section + AddMenuEntry("Achievements", CVAR_SETTING("Menu.AchievementsSidebarSection")); + WidgetPath path = {"Achievements", "Progress", SECTION_COLUMN_1}; + AddSidebarEntry("Achievements", path.sidebarName, 2); + + AddWidget(path, "Achievements", WIDGET_CUSTOM).CustomFunction(DrawAchievementsWidget).HideInSearch(true); +} // namespace SohGui + +} \ No newline at end of file diff --git a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c index 16391b36f17..c3848b55d35 100644 --- a/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c +++ b/soh/src/overlays/actors/ovl_Boss_Dodongo/z_boss_dodongo.c @@ -7,6 +7,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/achievements.h" #include // malloc #include // memcpy @@ -23,6 +24,7 @@ void BossDodongo_Init(Actor* thisx, PlayState* play); void BossDodongo_Destroy(Actor* thisx, PlayState* play); void BossDodongo_Update(Actor* thisx, PlayState* play); void BossDodongo_Draw(Actor* thisx, PlayState* play); +void BossDodongo_TryUnlockDeathAchievement(); void BossDodongo_SetupIntroCutscene(BossDodongo* this, PlayState* play); void BossDodongo_IntroCutscene(BossDodongo* this, PlayState* play); @@ -1481,6 +1483,7 @@ void BossDodongo_UpdateDamage(BossDodongo* this, PlayState* play) { s16 i; if ((this->health <= 0) && (this->actionFunc != BossDodongo_DeathCutscene)) { + BossDodongo_TryUnlockDeathAchievement(); BossDodongo_SetupDeathCutscene(this); Enemy_StartFinishingBlow(play, &this->actor); return; @@ -1958,3 +1961,7 @@ void BossDodongo_DrawEffects(PlayState* play) { CLOSE_DISPS(gfxCtx); } + +void BossDodongo_TryUnlockDeathAchievement() { + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_KD); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c b/soh/src/overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c index 1a00b983b70..5fd8d18fe4e 100644 --- a/soh/src/overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c +++ b/soh/src/overlays/actors/ovl_Boss_Fd2/z_boss_fd2.c @@ -11,6 +11,7 @@ #include "vt.h" #include "soh/frame_interpolation.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -49,6 +50,7 @@ void BossFd2_Vulnerable(BossFd2* this, PlayState* play); void BossFd2_Damaged(BossFd2* this, PlayState* play); void BossFd2_Death(BossFd2* this, PlayState* play); void BossFd2_Wait(BossFd2* this, PlayState* play); +void BossFd2_TryUnlockDeathAchievement(); const ActorInit Boss_Fd2_InitVars = { ACTOR_BOSS_FD2, @@ -892,6 +894,7 @@ void BossFd2_CollisionCheck(BossFd2* this, PlayState* play) { if ((s8)bossFd->actor.colChkInfo.health <= 0) { bossFd->actor.colChkInfo.health = 0; + BossFd2_TryUnlockDeathAchievement(); BossFd2_SetupDeath(this, play); this->work[FD2_DAMAGE_FLASH_TIMER] = 10; this->work[FD2_INVINC_TIMER] = 30000; @@ -1227,3 +1230,7 @@ void BossFd2_Draw(Actor* thisx, PlayState* play) { } CLOSE_DISPS(play->state.gfxCtx); } + +void BossFd2_TryUnlockDeathAchievement(){ + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_VOLVAGIA); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c index 4aa8984a2b4..676528bc9ef 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon/z_boss_ganon.c @@ -13,6 +13,7 @@ #include "soh/frame_interpolation.h" #include "soh/OTRGlobals.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #include @@ -31,6 +32,7 @@ void func_808E229C(Actor* thisx, PlayState* play); // draw void func_808E324C(Actor* thisx, PlayState* play); // draw void BossGanon_LightBall_Draw(Actor* thisx, PlayState* play); void BossGanon_Reset(void); +void BossGanon_TryUnlockDeathAchievement(); void BossGanon_SetupIntroCutscene(BossGanon* this, PlayState* play); void BossGanon_SetupTowerCutscene(BossGanon* this, PlayState* play); @@ -2801,6 +2803,7 @@ void BossGanon_UpdateDamage(BossGanon* this, PlayState* play) { } if ((s8)this->actor.colChkInfo.health <= 0) { + BossGanon_TryUnlockDeathAchievement(); BossGanon_SetupDeathCutscene(this, play); Audio_PlayActorSound2(&this->actor, NA_SE_EN_GANON_DEAD); Audio_PlayActorSound2(&this->actor, NA_SE_EN_GANON_DD_THUNDER); @@ -5102,3 +5105,7 @@ void BossGanon_Reset(void) { sBossGanonCape = NULL; memset(sBossGanonEffectBuf, 0, sizeof(sBossGanonEffectBuf)); } + +void BossGanon_TryUnlockDeathAchievement(){ + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_GANONDORF); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c index 21a849cfaa3..ebb8061f0b3 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c @@ -10,6 +10,7 @@ #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #include @@ -22,6 +23,7 @@ void BossGanon2_Destroy(Actor* thisx, PlayState* play); void BossGanon2_Update(Actor* thisx, PlayState* play); void BossGanon2_Draw(Actor* thisx, PlayState* play); void BossGanon2_Reset(void); +void BossGanon2_TryUnlockDeathAchievement(); void func_808FD5C4(BossGanon2* this, PlayState* play); void func_808FD5F4(BossGanon2* this, PlayState* play); @@ -1998,6 +2000,7 @@ void BossGanon2_CollisionCheck(BossGanon2* this, PlayState* play) { if ((temp_v0_4 < 0x15) && (this->unk_334 == 0)) { func_80900818(this, play); } else if ((temp_v0_4 <= 0) && (phi_v1_2 >= 2)) { + BossGanon2_TryUnlockDeathAchievement(); func_80901020(this, play); } else { if (temp_v0_4 <= 0) { @@ -3156,3 +3159,7 @@ void BossGanon2_Reset(void) { memset(D_80910608, 0, sizeof(D_80910608)); memset(sBossGanon2Particles, 0, sizeof(sBossGanon2Particles)); } + +void BossGanon2_TryUnlockDeathAchievement(){ + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_GANON); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c index 984da2d915f..a3908862a32 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganondrof/z_boss_ganondrof.c @@ -13,6 +13,7 @@ #include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h" #include "soh/OTRGlobals.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -56,6 +57,7 @@ void BossGanondrof_SetupCharge(BossGanondrof* this, PlayState* play); void BossGanondrof_Charge(BossGanondrof* this, PlayState* play); void BossGanondrof_Stunned(BossGanondrof* this, PlayState* play); void BossGanondrof_Death(BossGanondrof* this, PlayState* play); +void BossGanondrof_TryUnlockDeathAchievement(); const ActorInit Boss_Ganondrof_InitVars = { ACTOR_BOSS_GANONDROF, @@ -1224,6 +1226,7 @@ void BossGanondrof_CollisionCheck(BossGanondrof* this, PlayState* play) { } if ((s8)this->actor.colChkInfo.health <= 0) { + BossGanondrof_TryUnlockDeathAchievement(); BossGanondrof_SetupDeath(this, play); Enemy_StartFinishingBlow(play, &this->actor); GameInteractor_ExecuteOnBossDefeat(&this->actor); @@ -1493,3 +1496,7 @@ void BossGanondrof_Draw(Actor* thisx, PlayState* play) { CLOSE_DISPS(play->state.gfxCtx); osSyncPrintf("DRAW END %d\n", this->actor.params); } + +void BossGanondrof_TryUnlockDeathAchievement(){ + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_PG); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c b/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c index 3920cca5e47..ad9e4a8c638 100644 --- a/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c +++ b/soh/src/overlays/actors/ovl_Boss_Goma/z_boss_goma.c @@ -7,6 +7,7 @@ #include "soh/OTRGlobals.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -54,6 +55,7 @@ void BossGoma_FloorMain(BossGoma* this, PlayState* play); void BossGoma_WallClimb(BossGoma* this, PlayState* play); void BossGoma_CeilingMoveToCenter(BossGoma* this, PlayState* play); void BossGoma_SpawnChildGohma(BossGoma* this, PlayState* play, s16 i); +void BossGoma_TryUnlockDeathAchievement(); const ActorInit Boss_Goma_InitVars = { ACTOR_BOSS_GOMA, @@ -410,6 +412,7 @@ void BossGoma_SetupDefeated(BossGoma* this, PlayState* play) { this->actor.shape.shadowScale = 0.0f; Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x100FF); Audio_PlayActorSound2(&this->actor, NA_SE_EN_GOMA_DEAD); + BossGoma_TryUnlockDeathAchievement(); } /** @@ -2190,3 +2193,7 @@ void BossGoma_SpawnChildGohma(BossGoma* this, PlayState* play, s16 i) { this->childrenGohmaState[i] = 1; } + +void BossGoma_TryUnlockDeathAchievement(){ + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_GOHMA); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Mo/z_boss_mo.c b/soh/src/overlays/actors/ovl_Boss_Mo/z_boss_mo.c index a13eb154774..d755cb78b11 100644 --- a/soh/src/overlays/actors/ovl_Boss_Mo/z_boss_mo.c +++ b/soh/src/overlays/actors/ovl_Boss_Mo/z_boss_mo.c @@ -14,6 +14,7 @@ #include "soh/frame_interpolation.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #include @@ -69,6 +70,7 @@ void BossMo_SetupTentacle(BossMo* this, PlayState* play); void BossMo_Tentacle(BossMo* this, PlayState* play); void BossMo_Unknown(void); +void BossMo_TryUnlockDeathAchievement(); typedef enum { /* 0 */ MO_FX_NONE, @@ -1801,6 +1803,7 @@ void BossMo_CoreCollisionCheck(BossMo* this, PlayState* play) { if ((s8)this->actor.colChkInfo.health <= 0) { if (((sMorphaTent1->csCamera == 0) && (sMorphaTent2 == NULL)) || ((sMorphaTent1->csCamera == 0) && (sMorphaTent2 != NULL) && (sMorphaTent2->csCamera == 0))) { + BossMo_TryUnlockDeathAchievement(); Enemy_StartFinishingBlow(play, &this->actor); GameInteractor_ExecuteOnBossDefeat(&this->actor); Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x100FF); @@ -3623,3 +3626,7 @@ void BossMo_Reset(void) { sBossGanonSeed2 = 0; sBossGanonSeed3 = 0; } + +void BossMo_TryUnlockDeathAchievement(){ + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_MORPHA); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c b/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c index aa68a5866b1..341e609db36 100644 --- a/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c +++ b/soh/src/overlays/actors/ovl_Boss_Sst/z_boss_sst.c @@ -14,6 +14,7 @@ #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -62,6 +63,7 @@ void BossSst_DrawHead(Actor* thisx, PlayState* play); void BossSst_UpdateEffect(Actor* thisx, PlayState* play); void BossSst_DrawEffect(Actor* thisx, PlayState* play); void BossSst_Reset(void); +void BossSst_TryUnlockDeathAchievement(); void BossSst_HeadSfx(BossSst* this, u16 sfxId); @@ -2572,6 +2574,7 @@ void BossSst_HeadCollisionCheck(BossSst* this, PlayState* play) { if ((this->actor.colChkInfo.damageEffect != 0) || (this->actor.colChkInfo.damage != 0)) { if (this->actionFunc == BossSst_HeadVulnerable) { if (Actor_ApplyDamage(&this->actor) == 0) { + BossSst_TryUnlockDeathAchievement(); Enemy_StartFinishingBlow(play, &this->actor); BossSst_HeadSetupDeath(this, play); GameInteractor_ExecuteOnBossDefeat(&this->actor); @@ -3302,3 +3305,7 @@ void BossSst_Reset(void) { sStaticColor.g = 0; sStaticColor.b = 0; } + +void BossSst_TryUnlockDeathAchievement() { + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_BONGO); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c b/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c index 3f83322c44d..145693cbd5b 100644 --- a/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c +++ b/soh/src/overlays/actors/ovl_Boss_Tw/z_boss_tw.c @@ -6,6 +6,7 @@ #include "soh/frame_interpolation.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #include @@ -30,6 +31,7 @@ void BossTw_Destroy(Actor* thisx, PlayState* play); void BossTw_Update(Actor* thisx, PlayState* play); void BossTw_Draw(Actor* thisx, PlayState* play); void BossTw_Reset(void); +void BossTw_TryUnlockDeathAchievement(); void BossTw_TwinrovaDamage(BossTw* this, PlayState* play, u8 arg2); void BossTw_TwinrovaSetupFly(BossTw* this, PlayState* play); @@ -5270,6 +5272,7 @@ void BossTw_TwinrovaDamage(BossTw* this, PlayState* play, u8 damage) { } if ((s8)this->actor.colChkInfo.health <= 0) { + BossTw_TryUnlockDeathAchievement(); BossTw_TwinrovaSetupDeathCS(this, play); Enemy_StartFinishingBlow(play, &this->actor); Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_DEAD); @@ -5470,3 +5473,7 @@ void BossTw_Reset(void) { sTwInitalized = false; memset(sTwEffects, 0, sizeof(sTwEffects)); } + +void BossTw_TryUnlockDeathAchievement(){ + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_TWINROVA); +} diff --git a/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c b/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c index f30f9660407..ecc71615378 100644 --- a/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c +++ b/soh/src/overlays/actors/ovl_Boss_Va/z_boss_va.c @@ -18,6 +18,7 @@ #include "soh/frame_interpolation.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/achievements.h" #define FLAGS \ (ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_CULLING_DISABLED | \ @@ -199,6 +200,7 @@ void BossVa_SpawnSparkBall(PlayState* play, BossVaEffect* effect, BossVa* this, void BossVa_SpawnBloodDroplets(PlayState* play, BossVaEffect* effect, Vec3f* pos, s16 scale, s16 phase, s16 yaw); void BossVa_Tumor(PlayState* play, BossVa* this, s32 count, s16 scale, f32 xzSpread, f32 ySpread, u8 mode, f32 range, u8 fixed); +void BossVa_TryUnlockDeathAchievement(); const ActorInit Boss_Va_InitVars = { ACTOR_BOSS_VA, @@ -1406,6 +1408,7 @@ void BossVa_BodyPhase4(BossVa* this, PlayState* play) { sFightPhase++; sPhase4HP += 3; if (sFightPhase >= PHASE_DEATH) { + BossVa_TryUnlockDeathAchievement(); BossVa_SetupBodyDeath(this, play); Enemy_StartFinishingBlow(play, &this->actor); GameInteractor_ExecuteOnBossDefeat(&this->actor); @@ -4051,3 +4054,7 @@ void BossVa_Reset(void) { sBodyBari[i] = 0; } } + +void BossVa_TryUnlockDeathAchievement(){ + Achievements_TryUnlock(ACHIEVEMENT_DEFEAT_BARINADE); +}