mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-05 02:37:50 -04:00
Achievements improvements v2 (#1553)
* make LJA achievement more attainable glitchlessly * update loach achievement description * 3 kill rollstab achievement * update gone fishin description * gorge skip achievement * early city achievement * make goats and snowboarding safety check stage * fix indomitable requirement, add hero mode achievement * properly check skybook completion when returned * prototype ganondorf achievement * Autospin Annihilation
This commit is contained in:
@@ -4564,6 +4564,7 @@ public:
|
||||
cXyz mIBChainInterpCurrHandRoot;
|
||||
bool mIBChainInterpPrevValid;
|
||||
bool mIBChainInterpCurrValid;
|
||||
bool mIsRollstab = false;
|
||||
#endif
|
||||
}; // Size: 0x385C
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
// Signals are visible to all achievement checks within the same tick, then cleared.
|
||||
void signal(const char* key);
|
||||
bool hasSignal(const char* key) const;
|
||||
int signalCount(const char* key) const;
|
||||
|
||||
std::vector<Achievement> getAchievements() const;
|
||||
|
||||
@@ -62,7 +63,7 @@ private:
|
||||
void processEntry(Entry& e);
|
||||
|
||||
std::vector<Entry> m_entries;
|
||||
std::unordered_set<std::string_view> m_signals;
|
||||
std::unordered_map<std::string_view, int> m_signals;
|
||||
bool m_loaded = false;
|
||||
bool m_dirty = false;
|
||||
};
|
||||
|
||||
@@ -1165,6 +1165,10 @@ int daAlink_c::procCutFinishInit(int i_type) {
|
||||
const daAlink_cutParamTbl* cutParams = &cutParamTable[i_type];
|
||||
BOOL is_proc_frontRoll = mProcID == PROC_FRONT_ROLL;
|
||||
|
||||
#if TARGET_PC
|
||||
mIsRollstab = (is_proc_frontRoll != FALSE) && (i_type == CUT_FINISH_PARAM_STAB);
|
||||
#endif
|
||||
|
||||
commonProcInit(PROC_CUT_FINISH);
|
||||
setCutType(cutParams->m_cutType);
|
||||
field_0x3198 = cutParams->m_recoilAnmID;
|
||||
|
||||
@@ -2192,6 +2192,9 @@ static void damage_check(b_gnd_class* i_this) {
|
||||
i_this->mDamageInvulnerabilityTimer = 100;
|
||||
}
|
||||
}
|
||||
#if TARGET_PC
|
||||
dusk::AchievementSystem::get().signal("ganondorf_hit");
|
||||
#endif
|
||||
}
|
||||
|
||||
cXyz hitmark_size(1.0f, 1.0f, 1.0f);
|
||||
@@ -2218,6 +2221,7 @@ static void damage_check(b_gnd_class* i_this) {
|
||||
i_this->field_0xc7c = 0;
|
||||
dScnPly_c::setPauseTimer(7);
|
||||
a_this->health = 100;
|
||||
dusk::AchievementSystem::get().signal("ganondorf_knocked_down");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#if TARGET_PC
|
||||
#include "dusk/achievements.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#endif
|
||||
|
||||
static int plCutLRC[58] = {
|
||||
@@ -448,6 +449,10 @@ fopAc_ac_c* cc_at_check(fopAc_ac_c* i_enemy, dCcU_AtInfo* i_AtInfo) {
|
||||
#if TARGET_PC
|
||||
if (fopAcM_GetGroup(i_enemy) == fopAc_ENEMY_e) {
|
||||
dusk::AchievementSystem::get().signal("enemy_killed");
|
||||
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
|
||||
if (link != nullptr && link->mProcID == daAlink_c::PROC_CUT_FINISH && link->mIsRollstab) {
|
||||
dusk::AchievementSystem::get().signal("rollstab_kill");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+173
-8
@@ -11,6 +11,7 @@
|
||||
#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_b_gnd.h"
|
||||
#include "d/actor/d_a_b_ob.h"
|
||||
#include "d/actor/d_a_player.h"
|
||||
#include "d/d_demo.h"
|
||||
@@ -18,6 +19,7 @@
|
||||
#include "f_pc/f_pc_name.h"
|
||||
#include "f_op/f_op_actor_mng.h"
|
||||
#include "f_pc/f_pc_name.h"
|
||||
#include "dusk/logging.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
@@ -35,6 +37,9 @@ static void* s_cucco_play_search(void* i_actor, void*) {
|
||||
}
|
||||
|
||||
static void checkGoatHerding(Achievement& a, int32_t threshMs) {
|
||||
if (strcmp(dComIfGp_getStartStageName(), "F_SP00") != 0) {
|
||||
return;
|
||||
}
|
||||
if (dMeter2Info_getMaxCount() != 20 || dMeter2Info_getNowCount() != 20) {
|
||||
return;
|
||||
}
|
||||
@@ -65,6 +70,25 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
},
|
||||
{}
|
||||
},
|
||||
{
|
||||
{
|
||||
"three_heart_clear",
|
||||
"Hero Mode",
|
||||
"Defeat Ganondorf with only 3 heart containers.",
|
||||
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_getMaxLife() < 20) {
|
||||
a.progress = 1;
|
||||
}
|
||||
},
|
||||
{}
|
||||
},
|
||||
{
|
||||
{
|
||||
"completionist",
|
||||
@@ -201,7 +225,7 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
hasAncientDoc = true;
|
||||
}
|
||||
}
|
||||
if (!hasJewelRod || !hasAncientDoc) {
|
||||
if (!hasJewelRod || (!hasAncientDoc && !dComIfGs_isEventBit(dSv_event_flag_c::F_0302))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,7 +289,7 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
{
|
||||
"hylian_loach",
|
||||
"Legendary Catch",
|
||||
"Catch a Hylian Loach.",
|
||||
"Obtain the Hylian Loach in your fishing journal.",
|
||||
AchievementCategory::Collection,
|
||||
false, 0, 0, false
|
||||
},
|
||||
@@ -280,7 +304,7 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
{
|
||||
"all_fish",
|
||||
"Gone Fishin'",
|
||||
"Catch all 6 species of fish.",
|
||||
"Obtain all 6 species of fish in your fishing journal.",
|
||||
AchievementCategory::Collection,
|
||||
true, 6, 0, false
|
||||
},
|
||||
@@ -392,7 +416,7 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
false, 0, 0, false
|
||||
},
|
||||
[](Achievement& a, json&) {
|
||||
if (daNpcF_chkEvtBit(0x1F9) && dComIfGs_getMaxLife() <= 15) {
|
||||
if (daNpcF_chkEvtBit(0x1F9) && dComIfGs_getMaxLife() < 20) {
|
||||
a.progress = 1;
|
||||
}
|
||||
},
|
||||
@@ -506,6 +530,29 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
},
|
||||
{}
|
||||
},
|
||||
{
|
||||
{
|
||||
"rollstab_triple",
|
||||
"Surgical Skewer",
|
||||
"Kill 3 enemies with a single rollstab.",
|
||||
AchievementCategory::Misc,
|
||||
false, 0, 0, false
|
||||
},
|
||||
[](Achievement& a, json&) {
|
||||
static int rollstabKills = 0;
|
||||
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
|
||||
const bool inRollstab = link != nullptr && link->mProcID == daAlink_c::PROC_CUT_FINISH && link->mIsRollstab;
|
||||
if (!inRollstab) {
|
||||
rollstabKills = 0;
|
||||
return;
|
||||
}
|
||||
rollstabKills += AchievementSystem::get().signalCount("rollstab_kill");
|
||||
if (rollstabKills >= 3) {
|
||||
a.progress = 1;
|
||||
}
|
||||
},
|
||||
{}
|
||||
},
|
||||
// Minigame
|
||||
{
|
||||
{
|
||||
@@ -600,6 +647,9 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
false, 0, 0, false
|
||||
},
|
||||
[](Achievement& a, json&) {
|
||||
if (strcmp(dComIfGp_getStartStageName(), "F_SP114") != 0) {
|
||||
return;
|
||||
}
|
||||
const int32_t bestMs = dComIfGs_getRaceGameTime();
|
||||
if (dComIfGs_isEventBit(dSv_event_flag_c::F_0481) &&
|
||||
bestMs > 0 && bestMs <= 70000) {
|
||||
@@ -681,7 +731,7 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
{
|
||||
"long_jump_attack",
|
||||
"Long Jump Attack",
|
||||
"Travel more than 20 meters in a single jump attack before landing.",
|
||||
"Travel more than 15 meters in a single jump attack before landing.",
|
||||
AchievementCategory::Misc,
|
||||
false, 0, 0, false
|
||||
},
|
||||
@@ -711,7 +761,7 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
inJump = false;
|
||||
const float dx = link->current.pos.x - startX;
|
||||
const float dz = link->current.pos.z - startZ;
|
||||
if (dx * dx + dz * dz >= 2000.0f * 2000.0f) {
|
||||
if (dx * dx + dz * dz >= 1500.0f * 1500.0f) {
|
||||
a.progress = 1;
|
||||
}
|
||||
} else if (link->mProcID != daAlink_c::PROC_CUT_JUMP) {
|
||||
@@ -800,6 +850,66 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
},
|
||||
{}
|
||||
},
|
||||
{
|
||||
{
|
||||
"ganondorf_3hit",
|
||||
"Autospin Annihilation",
|
||||
"Finish off Ganondorf in the final duel after only 3 attacks.",
|
||||
AchievementCategory::Misc,
|
||||
false, 0, 0, false
|
||||
},
|
||||
[](Achievement& a, json&) {
|
||||
auto& sys = AchievementSystem::get();
|
||||
const auto* link = static_cast<const daAlink_c*>(daPy_getPlayerActorClass());
|
||||
|
||||
static int autospinCount = 0;
|
||||
static int pendingHits = 0;
|
||||
static bool invalidated = false;
|
||||
static bool wasInFight = false;
|
||||
|
||||
auto* gnd = static_cast<b_gnd_class*>(fopAcM_SearchByName(fpcNm_B_GND_e));
|
||||
const bool inFight = gnd != nullptr && !gnd->checkRide();
|
||||
|
||||
if (inFight && !wasInFight) {
|
||||
autospinCount = 0;
|
||||
pendingHits = 0;
|
||||
invalidated = false;
|
||||
}
|
||||
wasInFight = inFight;
|
||||
|
||||
if (!inFight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool hitOccurred = sys.hasSignal("ganondorf_hit");
|
||||
const bool knockedDown = sys.hasSignal("ganondorf_knocked_down");
|
||||
|
||||
if (hitOccurred && knockedDown) {
|
||||
// Spin completing an autospin: pendingHits should be exactly 1 (the spin attack)
|
||||
if (pendingHits == 1) {
|
||||
autospinCount++;
|
||||
pendingHits = 0;
|
||||
} else {
|
||||
invalidated = true;
|
||||
}
|
||||
} else if (hitOccurred) {
|
||||
pendingHits++;
|
||||
if (pendingHits > 1) {
|
||||
invalidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (link != nullptr && link->mProcID == daAlink_c::PROC_GANON_FINISH) {
|
||||
if (!invalidated && autospinCount == 3) {
|
||||
a.progress = 1;
|
||||
}
|
||||
autospinCount = 0;
|
||||
pendingHits = 0;
|
||||
invalidated = false;
|
||||
}
|
||||
},
|
||||
{}
|
||||
},
|
||||
// Glitched
|
||||
{
|
||||
{
|
||||
@@ -1012,6 +1122,55 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
|
||||
a.progress = 1;
|
||||
},
|
||||
{}
|
||||
},
|
||||
{
|
||||
{
|
||||
"early_city",
|
||||
"Early City",
|
||||
"Obtain the Double Clawshots without obtaining the Dominion Rod.",
|
||||
AchievementCategory::Glitched,
|
||||
false, 0, 0, false
|
||||
},
|
||||
[](Achievement& a, json&) {
|
||||
if (daPy_getPlayerActorClass() == nullptr) {
|
||||
return;
|
||||
}
|
||||
bool hasDoubleClawshot = false;
|
||||
bool hasDominionRod = false;
|
||||
for (int i = 0; i < 24; ++i) {
|
||||
const auto item = dComIfGs_getItem(i, false);
|
||||
if (item == dItemNo_W_HOOKSHOT_e) {
|
||||
hasDoubleClawshot = true;
|
||||
}
|
||||
if (item == dItemNo_COPY_ROD_e || item == dItemNo_COPY_ROD_2_e) {
|
||||
hasDominionRod = true;
|
||||
}
|
||||
}
|
||||
if (hasDoubleClawshot && !hasDominionRod) {
|
||||
a.progress = 1;
|
||||
}
|
||||
},
|
||||
{}
|
||||
},
|
||||
{
|
||||
{
|
||||
"early_kakariko",
|
||||
"Gorge Skip",
|
||||
"Collect the Kakariko warp portal without warping the gorge bridge.",
|
||||
AchievementCategory::Glitched,
|
||||
false, 0, 0, false
|
||||
},
|
||||
[](Achievement& a, json&) {
|
||||
if (dComIfGs_isEventBit(dSv_event_flag_c::M_018)) {
|
||||
return;
|
||||
}
|
||||
const bool savedPortal = g_dComIfG_gameInfo.info.getSavedata().getSave(dStage_SaveTbl_ELDIN).getBit().isSwitch(31);
|
||||
const bool livePortal = dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo()) == dStage_SaveTbl_ELDIN && dComIfGs_isSaveSwitch(31);
|
||||
if (savedPortal || livePortal) {
|
||||
a.progress = 1;
|
||||
}
|
||||
},
|
||||
{}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1088,11 +1247,17 @@ void AchievementSystem::clearAll() {
|
||||
}
|
||||
|
||||
void AchievementSystem::signal(const char* key) {
|
||||
m_signals.insert(key);
|
||||
m_signals[key]++;
|
||||
}
|
||||
|
||||
bool AchievementSystem::hasSignal(const char* key) const {
|
||||
return m_signals.count(key) > 0;
|
||||
const auto it = m_signals.find(key);
|
||||
return it != m_signals.end() && it->second > 0;
|
||||
}
|
||||
|
||||
int AchievementSystem::signalCount(const char* key) const {
|
||||
const auto it = m_signals.find(key);
|
||||
return it != m_signals.end() ? it->second : 0;
|
||||
}
|
||||
|
||||
void AchievementSystem::clearOne(const char* key) {
|
||||
|
||||
Reference in New Issue
Block a user