40 achievements

This commit is contained in:
madeline
2026-04-30 01:48:03 -07:00
parent 2623c44cab
commit 3f560b060c
8 changed files with 643 additions and 135 deletions
+6
View File
@@ -1845,6 +1845,12 @@ inline void dComIfGs_addDeathCount() {
g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().addDeathCount();
}
#if TARGET_PC
inline u16 dComIfGs_getDeathCount() {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getDeathCount();
}
#endif
inline char* dComIfGs_getPlayerName() {
return g_dComIfG_gameInfo.info.getPlayer().getPlayerInfo().getPlayerName();
}
+3
View File
@@ -486,6 +486,9 @@ public:
mDeathCount++;
}
}
#if TARGET_PC
u16 getDeathCount() const { return mDeathCount; }
#endif
char* getPlayerName() const { return const_cast<char*>(mPlayerName); }
void setPlayerName(const char* i_name) {
#if AVOID_UB
+3
View File
@@ -205,6 +205,9 @@ int daAlink_c::setDamagePoint(int i_dmgAmount, BOOL i_checkZoraMag, BOOL i_setDm
dComIfGp_setItemLifeCount(-i_dmgAmount, 0);
}
#if TARGET_PC
dusk::AchievementSystem::get().signal("player_damaged");
#endif
onResetFlg1(RFLG1_DAMAGE_IMPACT);
mSwordUpTimer = 0;
+9 -1
View File
@@ -17,6 +17,9 @@
#include "d/actor/d_a_e_pz.h"
#include "d/actor/d_a_horse.h"
#include "d/actor/d_a_hozelda.h"
#if TARGET_PC
#include "dusk/achievements.h"
#endif
int daArrow_c::createHeap() {
J3DModelData* model_data;
@@ -92,7 +95,12 @@ void daArrow_c::atHitCallBack(dCcD_GObjInf* i_atObjInf, fopAc_ac_c* i_tgActor, d
if (dist_to_hitpos < field_0x998) {
field_0x998 = dist_to_hitpos;
mHitAcID = fopAcM_GetID(i_tgActor);
#if TARGET_PC
if (fopAcM_GetGroup(i_tgActor) == fopAc_ENEMY_e &&
current.pos.abs(mStartPos) > 10000.0f) {
dusk::AchievementSystem::get().signal("arrow_hit_100m");
}
#endif
if (mArrowType == 1) {
field_0x9a8 = *hit_pos_p;
} else if (i_tgObjInf->ChkTgShield()) {
+6
View File
@@ -19,6 +19,9 @@
#include "dusk/frame_interpolation.h"
#include "dusk/settings.h"
#if TARGET_PC
#include "dusk/achievements.h"
#endif
class daB_GND_HIO_c : public JORReflexible {
public:
@@ -1289,6 +1292,9 @@ static void b_gnd_g_wait(b_gnd_class* i_this) {
if (i_this->mMoveMode < 5 && i_this->mPlayerDistXZ < 600.0f) {
i_this->mMoveMode = 5;
i_this->field_0xc44[0] = 10;
#if TARGET_PC
dusk::AchievementSystem::get().signal("ganondorf_fishing_rod");
#endif
}
} else if (i_this->mMoveMode == 5) {
i_this->mMoveMode = 6;
+9
View File
@@ -12,6 +12,9 @@
#include "c/c_damagereaction.h"
#include "f_op/f_op_actor_enemy.h"
#include "f_op/f_op_camera_mng.h"
#if TARGET_PC
#include "dusk/achievements.h"
#endif
class daE_TH_HIO_c : public JORReflexible {
public:
@@ -542,6 +545,7 @@ static void damage_check(e_th_class* i_this) {
if (i_this->field_0x6a4 == 0 && i_this->mAction != ACTION_SPIN) {
daPy_py_c* player = (daPy_py_c*)dComIfGp_getPlayer(0);
OS_REPORT("E_th HP1 %d\n", i_this->health);
s16 prevHealth = i_this->health;
cc_at_check(i_this, &i_this->mAtInfo);
OS_REPORT("E_th HP2 %d\n", i_this->health);
@@ -554,6 +558,11 @@ static void damage_check(e_th_class* i_this) {
dComIfGs_onOneZoneSwitch(3, -1);
if (i_this->health <= 0) {
#if TARGET_PC
if (prevHealth == i_this->field_0x560) {
dusk::AchievementSystem::get().signal("dark_hammer_one_hit");
}
#endif
i_this->mAction = ACTION_END;
i_this->mMode = 0;
i_this->field_0x68a |= 4;
+8
View File
@@ -13,6 +13,9 @@
#include "d/d_s_play.h"
#include "d/d_com_inf_game.h"
#include "f_op/f_op_actor_mng.h"
#if TARGET_PC
#include "dusk/achievements.h"
#endif
static int plCutLRC[58] = {
0, //
@@ -434,6 +437,11 @@ fopAc_ac_c* cc_at_check(fopAc_ac_c* i_enemy, dCcU_AtInfo* i_AtInfo) {
if (i_AtInfo->mAttackPower != 0 && i_enemy->health <= 0) {
i_AtInfo->mHitStatus = 2;
i_enemy->health = 0;
#if TARGET_PC
if (fopAcM_GetGroup(i_enemy) == fopAc_ENEMY_e) {
dusk::AchievementSystem::get().signal("enemy_killed");
}
#endif
}
int uvar8;
+599 -134
View File
@@ -2,11 +2,16 @@
#include "dusk/io.hpp"
#include "dusk/main.h"
#include "d/d_com_inf_game.h"
#include "d/d_item_data.h"
#include "d/d_map_path_fmap.h"
#include "d/d_stage.h"
#include "d/d_menu_fmap.h"
#include "JSystem/JKernel/JKRArchive.h"
#include "d/d_meter2_info.h"
#include "d/actor/d_a_alink.h"
#include "d/actor/d_a_ni.h"
#include "d/actor/d_a_npc4.h"
#include "d/actor/d_a_player.h"
#include "d/d_demo.h"
#include "f_pc/f_pc_name.h"
#include "f_op/f_op_actor_mng.h"
@@ -17,6 +22,14 @@ namespace dusk {
using json = nlohmann::json;
static void* s_cucco_play_search(void* i_actor, void*) {
if (!fopAcM_IsActor(i_actor) || fopAcM_GetName((fopAc_ac_c*)i_actor) != fpcNm_NI_e) {
return nullptr;
}
auto* ni = static_cast<ni_class*>(i_actor);
return ni->mAction == ACTION_PLAY_e ? i_actor : nullptr;
}
static void checkGoatHerding(Achievement& a, int32_t threshMs) {
if (dMeter2Info_getMaxCount() != 20 || dMeter2Info_getNowCount() != 20) {
return;
@@ -31,6 +44,7 @@ static constexpr auto ACHIEVEMENTS_FILENAME = "achievements.json";
std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
return {
// Story
{
{
"hero_of_twilight",
@@ -47,6 +61,433 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
},
{}
},
{
{
"completionist",
"Completionist",
"100% the game.",
AchievementCategory::Story,
false, 0, 0, false
},
[](Achievement& a, json&) {
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
return;
}
if (dComIfGs_getMaxLife() < 100) {
return;
}
for (int i = 0; i < 4; ++i) {
if (!dComIfGs_isCollectMirror(i)) {
return;
}
}
for (int i = 0; i < 3; ++i) {
if (!dComIfGs_isCollectCrystal(i)) {
return;
}
}
static const u16 skillBits[] = {
dSv_event_flag_c::F_0338, dSv_event_flag_c::F_0339,
dSv_event_flag_c::F_0340, dSv_event_flag_c::F_0341,
dSv_event_flag_c::F_0342, dSv_event_flag_c::F_0343,
dSv_event_flag_c::F_0344
};
for (u16 bit : skillBits) {
if (!dComIfGs_isEventBit(bit)) {
return;
}
}
if (dComIfGs_checkGetInsectNum() < 24) {
return;
}
if (dComIfGs_getPohSpiritNum() < 60) {
return;
}
if (dComIfGs_getWalletSize() < 2) {
return;
}
if (dComIfGs_getArrowMax() < 100) {
return;
}
if (!dComIfGs_isCollectSword(COLLECT_MASTER_SWORD)) {
return;
}
if (!dComIfGs_isCollectShield(COLLECT_HYLIAN_SHIELD)) {
return;
}
if (!dComIfGs_isCollectClothes(KOKIRI_CLOTHES_FLAG)) {
return;
}
if (!dComIfGs_isItemFirstBit(dItemNo_WEAR_ZORA_e)) {
return;
}
if (!dComIfGs_isItemFirstBit(dItemNo_ARMOR_e)) {
return;
}
static const struct { int stage; int sw; } warpPortals[] = {
{ dStage_SaveTbl_ORDON, 52 }, // Ordon Spring
{ dStage_SaveTbl_FARON, 71 }, // South Faron Woods
{ dStage_SaveTbl_FARON, 2 }, // North Faron Woods
{ dStage_SaveTbl_GROVE, 100 }, // Sacred Grove
{ dStage_SaveTbl_FIELD, 21 }, // Gorge
{ dStage_SaveTbl_ELDIN, 31 }, // Kakariko Village
{ dStage_SaveTbl_ELDIN, 21 }, // Death Mountain
{ dStage_SaveTbl_FIELD, 99 }, // Bridge of Eldin
{ dStage_SaveTbl_FIELD, 3 }, // Castle Town
{ dStage_SaveTbl_LANAYRU, 10 }, // Lake Hylia
{ dStage_SaveTbl_LANAYRU, 2 }, // Zora's Domain
{ dStage_SaveTbl_LANAYRU, 21 }, // Upper Zora's River
{ dStage_SaveTbl_SNOWPEAK, 21 }, // Snowpeak
{ dStage_SaveTbl_DESERT, 21 }, // Gerudo Mesa
{ dStage_SaveTbl_DESERT, 40 }, // Mirror Chamber
};
for (const auto& p : warpPortals) {
if (!g_dComIfG_gameInfo.info.getSavedata().getSave(p.stage).getBit().isSwitch(p.sw)) {
return;
}
}
if (dComIfGs_getCollectSmell() == dItemNo_NONE_e) {
return;
}
if (dMeter2Info_getRecieveLetterNum() == 0) {
return;
}
bool hasJournal = false;
for (int fi = 0; fi < 6; ++fi) {
if (dComIfGs_getFishNum(fi) != 0) {
hasJournal = true;
break;
}
}
if (!hasJournal) {
return;
}
int bottleCount = 0;
for (int i = 0; i < dSv_player_item_c::BOTTLE_MAX; ++i) {
if (dComIfGs_getItem(SLOT_11 + i, false) != dItemNo_NONE_e) {
bottleCount++;
}
}
if (bottleCount < 4) {
return;
}
int bombBagCount = 0;
for (int i = 0; i < dSv_player_item_c::BOMB_BAG_MAX; ++i) {
if (dComIfGs_getItem(SLOT_15 + i, false) != dItemNo_NONE_e) {
bombBagCount++;
}
}
if (bombBagCount < 3) {
return;
}
bool hasJewelRod = false;
for (int slot = 0; slot < 24 && !hasJewelRod; ++slot) {
const u8 item = dComIfGs_getItem(slot, false);
if (item == dItemNo_JEWEL_ROD_e || item == dItemNo_JEWEL_BEE_ROD_e || item == dItemNo_JEWEL_WORM_ROD_e) {
hasJewelRod = true;
}
}
if (!hasJewelRod) {
return;
}
static const u8 requiredWheelItems[] = {
dItemNo_BOOMERANG_e,
dItemNo_BOW_e,
dItemNo_W_HOOKSHOT_e,
dItemNo_SPINNER_e,
dItemNo_IRONBALL_e,
dItemNo_COPY_ROD_e,
dItemNo_HVY_BOOTS_e,
dItemNo_KANTERA_e,
dItemNo_PACHINKO_e,
dItemNo_HAWK_EYE_e,
dItemNo_ANCIENT_DOCUMENT_e,
dItemNo_HORSE_FLUTE_e,
};
for (u8 required : requiredWheelItems) {
bool found = false;
for (int slot = 0; slot < 24; ++slot) {
if (dComIfGs_getItem(slot, false) == required) {
found = true;
break;
}
}
if (!found) {
return;
}
}
a.progress = 1;
},
{}
},
// Collection
{
{
"princess_of_bugs",
"The Princess of Bugs",
"Deliver all 24 golden bugs to Agitha.",
AchievementCategory::Collection,
true, 24, 0, false
},
[](Achievement& a, json&) {
a.progress = dComIfGs_checkGetInsectNum();
},
{}
},
{
{
"all_poes",
"Poe Collector",
"Collect all 60 Poe Souls.",
AchievementCategory::Collection,
true, 60, 0, false
},
[](Achievement& a, json&) {
a.progress = dComIfGs_getPohSpiritNum();
},
{}
},
{
{
"hylian_loach",
"Legendary Catch",
"Catch a Hylian Loach.",
AchievementCategory::Collection,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (dComIfGs_getFishNum(1) > 0) {
a.progress = 1;
}
},
{}
},
{
{
"all_fish",
"Gone Fishin'",
"Catch all 6 species of fish.",
AchievementCategory::Collection,
true, 6, 0, false
},
[](Achievement& a, json&) {
int nUniqueFish = 0;
for (int i = 0; i < 6; ++i) {
if (dComIfGs_getFishNum(i) != 0) {
nUniqueFish++;
}
}
a.progress = nUniqueFish;
},
{}
},
{
{
"a_big_heart",
"A Big Heart",
"Reach maximum health with all 20 heart containers.",
AchievementCategory::Collection,
true, 20, 0, false
},
[](Achievement& a, json&) {
a.progress = dComIfGs_getMaxLife() / 5;
},
{}
},
{
{
"all_bottles",
"Glassware Guardian",
"Obtain all 4 bottles.",
AchievementCategory::Collection,
true, 4, 0, false
},
[](Achievement& a, json&) {
if (daPy_getPlayerActorClass() == nullptr) {
return;
}
int count = 0;
for (int i = 0; i < dSv_player_item_c::BOTTLE_MAX; ++i) {
if (dComIfGs_getItem(SLOT_11 + i, false) != dItemNo_NONE_e) {
count++;
}
}
a.progress = count;
},
{}
},
{
{
"all_hidden_skills",
"Master of Secrets",
"Learn all 7 Hidden Skills.",
AchievementCategory::Collection,
true, 7, 0, false
},
[](Achievement& a, json&) {
static const u16 skillBits[] = {
dSv_event_flag_c::F_0338, dSv_event_flag_c::F_0339,
dSv_event_flag_c::F_0340, dSv_event_flag_c::F_0341,
dSv_event_flag_c::F_0342, dSv_event_flag_c::F_0343,
dSv_event_flag_c::F_0344
};
int count = 0;
for (u16 bit : skillBits) {
if (dComIfGs_isEventBit(bit)) {
count++;
}
}
a.progress = count;
},
{}
},
// Challenge
{
{
"cave_of_ordeals",
"Conqueror of Ordeals",
"Clear all 50 floors of the Cave of Ordeals.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (daNpcF_chkEvtBit(0x1F9)) {
a.progress = 1;
}
},
{}
},
{
{
"cave_of_ordeals_heartless",
"Indomitable",
"Clear all 50 floors of the Cave of Ordeals with only 3 heart containers.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (daNpcF_chkEvtBit(0x1F9) && dComIfGs_getMaxLife() <= 15) {
a.progress = 1;
}
},
{}
},
{
{
"speedrun_12h",
"Been There Done That",
"Defeat Ganondorf with a total save file play time under 12 hours.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
return;
}
const int64_t ticks = (static_cast<int64_t>(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime();
if (ticks / OS_TIMER_CLOCK < 12 * 3600) {
a.progress = 1;
}
},
{}
},
{
{
"speedrun_8h",
"Swift Blade",
"Defeat Ganondorf with a total save file play time under 6 hours.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
return;
}
const int64_t ticks = (static_cast<int64_t>(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime();
if (ticks / OS_TIMER_CLOCK < 8 * 3600) {
a.progress = 1;
}
},
{}
},
{
{
"dark_hammer_one_hit",
"Mortal Edge",
"Defeat Dark Hammer in a single hit.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (AchievementSystem::get().hasSignal("dark_hammer_one_hit")) {
a.progress = 1;
}
},
{}
},
{
{
"no_deaths_clear",
"Deathless",
"Defeat Ganondorf with 0 deaths on your save file.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
return;
}
if (dComIfGs_getDeathCount() == 0) {
a.progress = 1;
}
},
{}
},
{
{
"untouchable",
"Untouchable",
"Kill 25 enemies in a row without taking damage.",
AchievementCategory::Challenge,
true, 25, 0, false
},
[](Achievement& a, json&) {
auto& sys = AchievementSystem::get();
if (sys.hasSignal("player_damaged")) {
a.progress = 0;
}
if (sys.hasSignal("enemy_killed")) {
a.progress++;
}
},
{}
},
{
{
"bow_100m_hit",
"Long Shot",
"Hit an enemy from over 100 meters away with the bow.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (AchievementSystem::get().hasSignal("arrow_hit_100m")) {
a.progress = 1;
}
},
{}
},
// Minigame
{
{
"plumm_max",
@@ -133,14 +574,16 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
},
{
{
"cave_of_ordeals",
"Conqueror of Ordeals",
"Clear all 50 floors of the Cave of Ordeals.",
AchievementCategory::Challenge,
"snowboard_70s",
"Downhill Dash",
"Finish the snowboarding minigame in under 70 seconds.",
AchievementCategory::Minigame,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (daNpcF_chkEvtBit(0x1F9)) {
const int32_t bestMs = dComIfGs_getRaceGameTime();
if (dComIfGs_isEventBit(dSv_event_flag_c::F_0481) &&
bestMs > 0 && bestMs <= 70000) {
a.progress = 1;
}
},
@@ -148,132 +591,28 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
},
{
{
"cave_of_ordeals_heartless",
"Indomitable",
"Clear all 50 floors of the Cave of Ordeals with only 3 heart containers.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (daNpcF_chkEvtBit(0x1F9) && dComIfGs_getMaxLife() <= 15) {
a.progress = 1;
}
},
{}
},
{
{
"speedrun_12h",
"Been There Done That",
"Defeat Ganondorf with a total save file play time under 12 hours.",
AchievementCategory::Challenge,
"canoe_perfect",
"River Raider",
"Achieve a perfect score in the canoe minigame.",
AchievementCategory::Minigame,
false, 0, 0, false
},
[](Achievement& a, json&) {
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
if (link == nullptr) {
return;
}
const int64_t ticks = (static_cast<int64_t>(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime();
if (ticks / OS_TIMER_CLOCK < 12 * 3600) {
static bool wasInCanoe = false;
bool inCanoe = link->mProcID >= daAlink_c::PROC_CANOE_RIDE &&
link->mProcID <= daAlink_c::PROC_CANOE_KANDELAAR_POUR;
if (wasInCanoe && !inCanoe && dMeter2Info_getNowCount() >= 30) {
a.progress = 1;
}
wasInCanoe = inCanoe;
},
{}
},
{
{
"speedrun_8h",
"Swift Blade",
"Defeat Ganondorf with a total save file play time under 6 hours.",
AchievementCategory::Challenge,
false, 0, 0, false
},
[](Achievement& a, json&) {
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
return;
}
const int64_t ticks = (static_cast<int64_t>(OSGetTime()) - dComIfGs_getSaveStartTime()) + dComIfGs_getSaveTotalTime();
if (ticks / OS_TIMER_CLOCK < 8 * 3600) {
a.progress = 1;
}
},
{}
},
{
{
"princess_of_bugs",
"The Princess of Bugs",
"Deliver all 24 golden bugs to Agitha.",
AchievementCategory::Collection,
true, 24, 0, false
},
[](Achievement& a, json&) {
a.progress = dComIfGs_checkGetInsectNum();
},
{}
},
{
{
"all_poes",
"Poe Collector",
"Collect all 60 Poe Souls.",
AchievementCategory::Collection,
true, 60, 0, false
},
[](Achievement& a, json&) {
a.progress = dComIfGs_getPohSpiritNum();
},
{}
},
{
{
"hylian_loach",
"Legendary Catch",
"Catch a Hylian Loach.",
AchievementCategory::Collection,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (dComIfGs_getFishNum(1) > 0) {
a.progress = 1;
}
},
{}
},
{
{
"all_fish",
"Gone Fishin'",
"Catch all 6 species of fish.",
AchievementCategory::Collection,
true, 6, 0, false
},
[](Achievement& a, json&) {
int nUniqueFish = 0;
for (int i = 0; i < 6; ++i) {
if (dComIfGs_getFishNum(i) != 0) {
nUniqueFish++;
}
}
a.progress = nUniqueFish;
},
{}
},
{
{
"a_big_heart",
"A Big Heart",
"Reach maximum health with all 20 heart containers.",
AchievementCategory::Collection,
true, 20, 0, false
},
[](Achievement& a, json&) {
a.progress = dComIfGs_getMaxLife() / 5;
},
{}
},
// Misc
{
{
"friendly_fire",
@@ -326,6 +665,87 @@ std::vector<AchievementSystem::Entry> 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<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
return;
}
if (daPy_getPlayerActorClass()->checkEquipHeavyBoots()) {
a.progress = 1;
}
},
{}
},
{
{
"fishing_rod_ganondorf",
"Here Fishy Fishy",
"Confuse Ganondorf with the fishing rod.",
AchievementCategory::Misc,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (AchievementSystem::get().hasSignal("ganondorf_fishing_rod")) {
a.progress = 1;
}
},
{}
},
{
{
"steal_from_trill",
"Petty Theft",
"Steal from Trill.",
AchievementCategory::Misc,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (dComIfGs_isEventBit(dSv_event_flag_c::F_0758)) {
a.progress = 1;
}
},
{}
},
{
{
"cucco_control",
"Cucco Whisperer",
"Take control of a cucco.",
AchievementCategory::Misc,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (fopAcM_Search(s_cucco_play_search, nullptr) != nullptr) {
a.progress = 1;
}
},
{}
},
// Glitched
{
{
"back_in_time",
@@ -411,34 +831,79 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
},
{
{
"email_me",
"Email Me",
"Read a letter during the Dark Beast Ganon fight.",
AchievementCategory::Misc,
"no_fish_suit",
"No Fish Suit No Problem",
"Defeat Morpheel without equipping Zora Armor.",
AchievementCategory::Glitched,
false, 0, 0, false
},
[](Achievement& a, json&) {
void* dbgExists = fopAcM_SearchByName(fpcNm_B_MGN_e);
if (dbgExists && AchievementSystem::get().hasSignal("open_letter")) {
static bool prevMorpheelAlive = false;
static bool inArena = false;
static bool zoraWorn = false;
const bool morpheelAlive = fopAcM_SearchByName(fpcNm_B_OH_e) != nullptr;
const bool lakebedCleared = dComIfGs_isEventBit(dSv_event_flag_c::M_045);
if (morpheelAlive && !lakebedCleared) {
inArena = true;
if (daPy_py_c::checkZoraWearFlg()) {
zoraWorn = true;
}
}
if (prevMorpheelAlive && !morpheelAlive && inArena && !zoraWorn) {
a.progress = 1;
}
prevMorpheelAlive = morpheelAlive;
},
{}
},
{
{
"null_item",
"Null Item",
"Obtain the mysterious black rupee in the item wheel.",
AchievementCategory::Glitched,
false, 0, 0, false
},
[](Achievement& a, json&) {
if (daPy_getPlayerActorClass() == nullptr) {
return;
}
for (int i = 0; i < 24; ++i) {
if (dComIfGs_getItem(i, false) == 0x00) {
a.progress = 1;
break;
}
}
},
{}
},
{
{
"heavy-hitter",
"Heavy Hitter",
"Wear the Iron Boots during the end credits.",
AchievementCategory::Misc,
"stallord_skip",
"Stallord Skip",
"Leave Stallord's arena through the exit without defeating Stallord.",
AchievementCategory::Glitched,
false, 0, 0, false
},
[](Achievement& a, json&) {
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) {
static bool seenStallord = false;
if (strcmp(dComIfGp_getStartStageName(), "D_MN10A") != 0) {
seenStallord = false;
return;
}
if (daPy_getPlayerActorClass()->checkEquipHeavyBoots()) {
if (dComIfGs_isEventBit(dSv_event_flag_c::F_0265)) {
seenStallord = false;
return;
}
if (fopAcM_SearchByName(fpcNm_B_DS_e) != nullptr) {
seenStallord = true;
}
if (seenStallord &&
dComIfGp_isEnableNextStage() &&
strcmp(dComIfGp_getNextStageName(), "F_SP125") == 0) {
a.progress = 1;
}
},