From 534e0112966d4bb7402418cd8a57383c81c75c4a Mon Sep 17 00:00:00 2001 From: madeline Date: Sun, 17 May 2026 12:17:24 -0700 Subject: [PATCH] 3 kill rollstab achievement --- include/d/actor/d_a_alink.h | 1 + include/dusk/achievements.h | 5 +++-- src/d/actor/d_a_alink_cut.inc | 4 ++++ src/d/d_cc_uty.cpp | 5 +++++ src/dusk/achievements.cpp | 33 +++++++++++++++++++++++++++++++-- 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index 107afc1ed3..8f6a40ae13 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -4564,6 +4564,7 @@ public: cXyz mIBChainInterpCurrHandRoot; bool mIBChainInterpPrevValid; bool mIBChainInterpCurrValid; + bool mIsRollstab = false; #endif }; // Size: 0x385C diff --git a/include/dusk/achievements.h b/include/dusk/achievements.h index 5c2758b6b7..c93ea5d626 100644 --- a/include/dusk/achievements.h +++ b/include/dusk/achievements.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #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 getAchievements() const; @@ -62,7 +63,7 @@ private: void processEntry(Entry& e); std::vector m_entries; - std::unordered_set m_signals; + std::unordered_map m_signals; bool m_loaded = false; bool m_dirty = false; }; diff --git a/src/d/actor/d_a_alink_cut.inc b/src/d/actor/d_a_alink_cut.inc index f33d2935c5..ba3c0ba89e 100644 --- a/src/d/actor/d_a_alink_cut.inc +++ b/src/d/actor/d_a_alink_cut.inc @@ -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; diff --git a/src/d/d_cc_uty.cpp b/src/d/d_cc_uty.cpp index 218cb362f4..91fc6677c1 100644 --- a/src/d/d_cc_uty.cpp +++ b/src/d/d_cc_uty.cpp @@ -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(daPy_getPlayerActorClass()); + if (link != nullptr && link->mProcID == daAlink_c::PROC_CUT_FINISH && link->mIsRollstab) { + dusk::AchievementSystem::get().signal("rollstab_kill"); + } } #endif } diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp index 8df61145db..a33221ffff 100644 --- a/src/dusk/achievements.cpp +++ b/src/dusk/achievements.cpp @@ -506,6 +506,29 @@ std::vector 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(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 { { @@ -1088,11 +1111,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) {