From 8aa040b274a7054e6bdaa3f9e3bfe6a976627cfc Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:12:15 +0200 Subject: [PATCH 1/9] add ZRecoilModifierEffect / "No Recoil" and "Extreme Recoil" --- src/Effects/Player/RecoilModifierEffects.cpp | 113 +++++++++++++++++++ src/Effects/Player/RecoilModifierEffects.h | 70 ++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 src/Effects/Player/RecoilModifierEffects.cpp create mode 100644 src/Effects/Player/RecoilModifierEffects.h diff --git a/src/Effects/Player/RecoilModifierEffects.cpp b/src/Effects/Player/RecoilModifierEffects.cpp new file mode 100644 index 0000000..6cb494e --- /dev/null +++ b/src/Effects/Player/RecoilModifierEffects.cpp @@ -0,0 +1,113 @@ +#include "RecoilModifierEffects.h" + +#include + +#include + +#include "Registry.h" +#include "Helpers/Utils.h" + +bool ZRecoilModifierEffect::Available() const +{ + return IChaosEffect::Available() && Hooks::ZHM5WeaponRecoilController_RecoilWeapon != nullptr; +} + +bool ZRecoilModifierEffect::IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const +{ + return IChaosEffect::IsCompatibleWith(p_pOtherEffect) + && !Utils::IsInstanceOf(p_pOtherEffect); +} + +void ZRecoilModifierEffect::OnDrawDebugUI() +{ + ImGui::DragFloat2("Recoil Multiplier", &m_vRecoilMultiplier.x); +} + +void ZRecoilModifierEffect::Start() +{ + m_bActive = true; +} + +void ZRecoilModifierEffect::Stop() +{ + m_bActive = false; +} + +// ZNoRecoilEffect +void ZNoRecoilEffect::OnModInitialized() +{ + if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) + { + Hooks::ZHM5WeaponRecoilController_RecoilWeapon->AddDetour(this, &ZNoRecoilEffect::ZHM5WeaponRecoilController_RecoilWeapon); + } +} + +void ZNoRecoilEffect::OnModUnload() +{ + if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) + { + Hooks::ZHM5WeaponRecoilController_RecoilWeapon->RemoveDetour(&ZNoRecoilEffect::ZHM5WeaponRecoilController_RecoilWeapon); + } +} + +DEFINE_PLUGIN_DETOUR( + ZNoRecoilEffect, + void, + ZHM5WeaponRecoilController_RecoilWeapon, + ZHM5WeaponRecoilController* th, + const TEntityRef& rWeapon +) +{ + if (!m_bActive) + { + return {HookAction::Continue()}; + } + + p_Hook->CallOriginal(th, rWeapon); + + th->m_vRecoil.x *= m_vRecoilMultiplier.x; + th->m_vRecoil.y *= m_vRecoilMultiplier.y; + + return {HookAction::Return()}; +} + +REGISTER_CHAOS_EFFECT(ZNoRecoilEffect); + +// ZExtremeRecoilEffect +void ZExtremeRecoilEffect::OnModInitialized() +{ + if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) + { + Hooks::ZHM5WeaponRecoilController_RecoilWeapon->AddDetour(this, &ZExtremeRecoilEffect::ZHM5WeaponRecoilController_RecoilWeapon); + } +} + +void ZExtremeRecoilEffect::OnModUnload() +{ + if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) + { + Hooks::ZHM5WeaponRecoilController_RecoilWeapon->RemoveDetour(&ZExtremeRecoilEffect::ZHM5WeaponRecoilController_RecoilWeapon); + } +} + +DEFINE_PLUGIN_DETOUR( + ZExtremeRecoilEffect, + void, + ZHM5WeaponRecoilController_RecoilWeapon, + ZHM5WeaponRecoilController* th, + const TEntityRef& rWeapon +) +{ + if (!m_bActive) + { + return {HookAction::Continue()}; + } + + p_Hook->CallOriginal(th, rWeapon); + + th->m_vRecoil.x *= m_vRecoilMultiplier.x; + th->m_vRecoil.y *= m_vRecoilMultiplier.y; + + return {HookAction::Return()}; +} +REGISTER_CHAOS_EFFECT(ZExtremeRecoilEffect); diff --git a/src/Effects/Player/RecoilModifierEffects.h b/src/Effects/Player/RecoilModifierEffects.h new file mode 100644 index 0000000..9386bde --- /dev/null +++ b/src/Effects/Player/RecoilModifierEffects.h @@ -0,0 +1,70 @@ +#pragma once +#include "IChaosEffect.h" + +#include // for DECLARE_PLUGIN_DETOUR + +#include +#include + +// NOTE: because this uses detours, each variant must define its own class implementing and handling the detour. +// this is because detours are registered via the method pointer, and must be unique. + +class ZRecoilModifierEffect : public IChaosEffect +{ + public: + ZRecoilModifierEffect(const SVector2 p_vRecoilMultiplier) : m_vRecoilMultiplier(p_vRecoilMultiplier) + { + } + + bool Available() const override; + bool IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const override; + + void OnDrawDebugUI() override; + + void Start() override; + void Stop() override; + + protected: + SVector2 m_vRecoilMultiplier; + bool m_bActive = false; +}; + +class ZNoRecoilEffect : public ZRecoilModifierEffect +{ + public: + ZNoRecoilEffect() : ZRecoilModifierEffect(SVector2(0.f, 0.f)) + { + } + + void OnModInitialized() override; + void OnModUnload() override; + + private: + DECLARE_PLUGIN_DETOUR( + ZNoRecoilEffect, + void, + ZHM5WeaponRecoilController_RecoilWeapon, + ZHM5WeaponRecoilController* th, + const TEntityRef& p_rWeapon + ); +}; + +class ZExtremeRecoilEffect : public ZRecoilModifierEffect +{ + public: + ZExtremeRecoilEffect() : ZRecoilModifierEffect(SVector2(5.f, 5.f)) + { + } + + void OnModInitialized() override; + void OnModUnload() override; + + private: + DECLARE_PLUGIN_DETOUR( + ZExtremeRecoilEffect, + void, + ZHM5WeaponRecoilController_RecoilWeapon, + ZHM5WeaponRecoilController* th, + const TEntityRef& p_rWeapon + ); +}; From a8532bba818cc75e4a778357bb6e4b9f4992c920 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 6 Jun 2026 15:01:54 +0200 Subject: [PATCH 2/9] update ZRecoilModifierEffect to use parameterization adds HookingHelpers to allow detours in base classes --- src/Effects/Player/RecoilModifierEffects.h | 70 ---------------- ...rEffects.cpp => ZRecoilModifierEffect.cpp} | 82 ++++++------------- src/Effects/Player/ZRecoilModifierEffect.h | 74 +++++++++++++++++ src/Helpers/HookingHelpers.h | 82 +++++++++++++++++++ 4 files changed, 179 insertions(+), 129 deletions(-) delete mode 100644 src/Effects/Player/RecoilModifierEffects.h rename src/Effects/Player/{RecoilModifierEffects.cpp => ZRecoilModifierEffect.cpp} (50%) create mode 100644 src/Effects/Player/ZRecoilModifierEffect.h create mode 100644 src/Helpers/HookingHelpers.h diff --git a/src/Effects/Player/RecoilModifierEffects.h b/src/Effects/Player/RecoilModifierEffects.h deleted file mode 100644 index 9386bde..0000000 --- a/src/Effects/Player/RecoilModifierEffects.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once -#include "IChaosEffect.h" - -#include // for DECLARE_PLUGIN_DETOUR - -#include -#include - -// NOTE: because this uses detours, each variant must define its own class implementing and handling the detour. -// this is because detours are registered via the method pointer, and must be unique. - -class ZRecoilModifierEffect : public IChaosEffect -{ - public: - ZRecoilModifierEffect(const SVector2 p_vRecoilMultiplier) : m_vRecoilMultiplier(p_vRecoilMultiplier) - { - } - - bool Available() const override; - bool IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const override; - - void OnDrawDebugUI() override; - - void Start() override; - void Stop() override; - - protected: - SVector2 m_vRecoilMultiplier; - bool m_bActive = false; -}; - -class ZNoRecoilEffect : public ZRecoilModifierEffect -{ - public: - ZNoRecoilEffect() : ZRecoilModifierEffect(SVector2(0.f, 0.f)) - { - } - - void OnModInitialized() override; - void OnModUnload() override; - - private: - DECLARE_PLUGIN_DETOUR( - ZNoRecoilEffect, - void, - ZHM5WeaponRecoilController_RecoilWeapon, - ZHM5WeaponRecoilController* th, - const TEntityRef& p_rWeapon - ); -}; - -class ZExtremeRecoilEffect : public ZRecoilModifierEffect -{ - public: - ZExtremeRecoilEffect() : ZRecoilModifierEffect(SVector2(5.f, 5.f)) - { - } - - void OnModInitialized() override; - void OnModUnload() override; - - private: - DECLARE_PLUGIN_DETOUR( - ZExtremeRecoilEffect, - void, - ZHM5WeaponRecoilController_RecoilWeapon, - ZHM5WeaponRecoilController* th, - const TEntityRef& p_rWeapon - ); -}; diff --git a/src/Effects/Player/RecoilModifierEffects.cpp b/src/Effects/Player/ZRecoilModifierEffect.cpp similarity index 50% rename from src/Effects/Player/RecoilModifierEffects.cpp rename to src/Effects/Player/ZRecoilModifierEffect.cpp index 6cb494e..d16bd55 100644 --- a/src/Effects/Player/RecoilModifierEffects.cpp +++ b/src/Effects/Player/ZRecoilModifierEffect.cpp @@ -1,4 +1,4 @@ -#include "RecoilModifierEffects.h" +#include "ZRecoilModifierEffect.h" #include @@ -7,6 +7,22 @@ #include "Registry.h" #include "Helpers/Utils.h" +void ZRecoilModifierEffect::OnModInitialized() +{ + if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) + { + Hooks::ZHM5WeaponRecoilController_RecoilWeapon->AddDetour(this, &ZRecoilModifierEffect::ZHM5WeaponRecoilController_RecoilWeapon); + } +} + +void ZRecoilModifierEffect::OnModUnload() +{ + if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) + { + Hooks::ZHM5WeaponRecoilController_RecoilWeapon->RemoveDetour(&ZRecoilModifierEffect::ZHM5WeaponRecoilController_RecoilWeapon); + } +} + bool ZRecoilModifierEffect::Available() const { return IChaosEffect::Available() && Hooks::ZHM5WeaponRecoilController_RecoilWeapon != nullptr; @@ -33,27 +49,13 @@ void ZRecoilModifierEffect::Stop() m_bActive = false; } -// ZNoRecoilEffect -void ZNoRecoilEffect::OnModInitialized() -{ - if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) - { - Hooks::ZHM5WeaponRecoilController_RecoilWeapon->AddDetour(this, &ZNoRecoilEffect::ZHM5WeaponRecoilController_RecoilWeapon); - } -} +DEFINE_BASE_CLASS_INSTANCES_HELPER(ZRecoilModifierEffect); -void ZNoRecoilEffect::OnModUnload() -{ - if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) - { - Hooks::ZHM5WeaponRecoilController_RecoilWeapon->RemoveDetour(&ZNoRecoilEffect::ZHM5WeaponRecoilController_RecoilWeapon); - } -} - -DEFINE_PLUGIN_DETOUR( - ZNoRecoilEffect, +DEFINE_BASE_CLASS_DETOUR( + ZRecoilModifierEffect, void, ZHM5WeaponRecoilController_RecoilWeapon, + MAKE_FORWARD_PARAMS(th, rWeapon), ZHM5WeaponRecoilController* th, const TEntityRef& rWeapon ) @@ -71,43 +73,5 @@ DEFINE_PLUGIN_DETOUR( return {HookAction::Return()}; } -REGISTER_CHAOS_EFFECT(ZNoRecoilEffect); - -// ZExtremeRecoilEffect -void ZExtremeRecoilEffect::OnModInitialized() -{ - if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) - { - Hooks::ZHM5WeaponRecoilController_RecoilWeapon->AddDetour(this, &ZExtremeRecoilEffect::ZHM5WeaponRecoilController_RecoilWeapon); - } -} - -void ZExtremeRecoilEffect::OnModUnload() -{ - if (Hooks::ZHM5WeaponRecoilController_RecoilWeapon) - { - Hooks::ZHM5WeaponRecoilController_RecoilWeapon->RemoveDetour(&ZExtremeRecoilEffect::ZHM5WeaponRecoilController_RecoilWeapon); - } -} - -DEFINE_PLUGIN_DETOUR( - ZExtremeRecoilEffect, - void, - ZHM5WeaponRecoilController_RecoilWeapon, - ZHM5WeaponRecoilController* th, - const TEntityRef& rWeapon -) -{ - if (!m_bActive) - { - return {HookAction::Continue()}; - } - - p_Hook->CallOriginal(th, rWeapon); - - th->m_vRecoil.x *= m_vRecoilMultiplier.x; - th->m_vRecoil.y *= m_vRecoilMultiplier.y; - - return {HookAction::Return()}; -} -REGISTER_CHAOS_EFFECT(ZExtremeRecoilEffect); +REGISTER_CHAOS_EFFECT_PARAM(zero, ZRecoilModifierEffect, "zero", "No Recoil", SVector2(0.f, 0.f)); +REGISTER_CHAOS_EFFECT_PARAM(extreme, ZRecoilModifierEffect, "extreme", "Extreme Recoil", SVector2(5.f, 5.f)); diff --git a/src/Effects/Player/ZRecoilModifierEffect.h b/src/Effects/Player/ZRecoilModifierEffect.h new file mode 100644 index 0000000..093ee30 --- /dev/null +++ b/src/Effects/Player/ZRecoilModifierEffect.h @@ -0,0 +1,74 @@ +#pragma once +#include "IChaosEffect.h" + +#include +#include + +#include "Helpers/HookingHelpers.h" + +class ZRecoilModifierEffect : public IChaosEffect +{ + public: + ZRecoilModifierEffect( + const std::string p_sNameSuffix, + const std::string s_sDisplayName, + const SVector2 p_vRecoilMultiplier, + const EDuration p_eDuration = EDuration::Full + ) + : m_sNameSuffix(p_sNameSuffix), + m_sDisplayName(s_sDisplayName), + m_vRecoilMultiplier(p_vRecoilMultiplier), + m_eDuration(p_eDuration) + { + ADD_BASE_CLASS_DETOUR_INSTANCE(); + } + + ~ZRecoilModifierEffect() + { + REMOVE_BASE_CLASS_DETOUR_INSTANCE(); + } + + void OnModInitialized() override; + void OnModUnload() override; + bool Available() const override; + bool IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const override; + + void OnDrawDebugUI() override; + + void Start() override; + void Stop() override; + + std::string GetName() const override + { + return IChaosEffect::GetName() + "_" + m_sNameSuffix; + } + + std::string GetDisplayName(const bool p_bVoting) const override + { + return m_sDisplayName; + } + + EDuration GetDuration() const override + { + return m_eDuration; + } + + private: + std::string m_sNameSuffix; + std::string m_sDisplayName; + EDuration m_eDuration; + SVector2 m_vRecoilMultiplier; + + private: + bool m_bActive = false; + + DECLARE_BASE_CLASS_INSTANCES_HELPER(ZRecoilModifierEffect); + + DECLARE_BASE_CLASS_DETOUR( + ZRecoilModifierEffect, + void, + ZHM5WeaponRecoilController_RecoilWeapon, + ZHM5WeaponRecoilController* th, + const TEntityRef& p_rWeapon + ); +}; diff --git a/src/Helpers/HookingHelpers.h b/src/Helpers/HookingHelpers.h new file mode 100644 index 0000000..e93da9e --- /dev/null +++ b/src/Helpers/HookingHelpers.h @@ -0,0 +1,82 @@ +/** + * This header defines some helpers to work around limitations of the SDK's hooking system + * mainly the part where, when registering a new hook, the context pointer is ignored during hook + * de-duplication, which means you cannot normally have a base class define a hook. + * + * example: + * header (ZSomeHookBaseEffect.h): + * class ZSomeHookBaseEffect (...) + * { + * // constructor and destructor must add/remove instances + * ZSomeHookBaseEffect(...) + * { + * ADD_BASE_CLASS_DETOUR_INSTANCE(); + * } + * ~ZSomeHookBaseEffect() + * { + * REMOVE_BASE_CLASS_DETOUR_INSTANCE(); + * } + * + * private: + * // declare list of instances, this must be done ONCE per base class + * DECLARE_BASE_CLASS_INSTANCES_HELPER(ZSomeHookBaseEffect); + * + * // declare detours as you normally would, only instead of DECLARE_PLUGIN_DETOUR, use DECLARE_BASE_CLASS_DETOUR + * // you can have multiple detours per class + * DECLARE_BASE_CLASS_DETOUR(ZSomeHookBaseEffect, void, SomeHook, ...); + * }; + * + * cpp (ZSomeHookBaseEffect.cpp): + * // define list of instances, this must be done ONCE per base class, in a cpp file + * DEFINE_BASE_CLASS_INSTANCES_HELPER(ZSomeHookBaseEffect); + * + * // define your hook implementation as you normally would, but use DEFINE_BASE_CLASS_DETOUR instead of DEFINE_PLUGIN_DETOUR + * // also, you need to specify the parameter names *without* type specifiers via MAKE_FORWARD_PARAMS. + * DEFINE_BASE_CLASS_DETOUR(ZSomeHookBaseEffect, void, SomeHook, MAKE_FORWARD_PARAMS(...), ...) + * { + * ... + * } + */ +#pragma once +#include // for DECLARE_PLUGIN_DETOUR + +#define DECLARE_BASE_CLASS_INSTANCES_HELPER(BaseClass) \ + static std::vector m_aInstancesForDetours; + +#define DEFINE_BASE_CLASS_INSTANCES_HELPER(BaseClass) \ + std::vector BaseClass::m_aInstancesForDetours; + +#define ADD_BASE_CLASS_DETOUR_INSTANCE() \ + m_aInstancesForDetours.push_back(this); + +#define REMOVE_BASE_CLASS_DETOUR_INSTANCE() \ + m_aInstancesForDetours.erase(std::remove(m_aInstancesForDetours.begin(), m_aInstancesForDetours.end(), this), m_aInstancesForDetours.end()); + +#define DECLARE_BASE_CLASS_DETOUR(BaseClass, ReturnType, DetourName, ...) \ + DECLARE_PLUGIN_DETOUR(BaseClass, ReturnType, DetourName, __VA_ARGS__); \ + HookResult __fastcall DetourName##_Impl( \ + Hook* p_Hook, \ + __VA_ARGS__ \ + ); + +#define DEFINE_BASE_CLASS_DETOUR(BaseClass, ReturnType, DetourName, ForwardParams, ...) \ + DEFINE_PLUGIN_DETOUR(BaseClass, ReturnType, DetourName, __VA_ARGS__) \ + { \ + for (auto& s_pInstance : m_aInstancesForDetours) \ + { \ + const auto s_Result = s_pInstance->DetourName##_Impl ForwardParams; \ + if (s_Result.m_HasReturnVal) \ + { \ + return s_Result; \ + } \ + } \ + \ + return {HookAction::Continue()}; \ + } \ + HookResult __fastcall BaseClass::DetourName##_Impl( \ + Hook* p_Hook, \ + __VA_ARGS__ \ + ) + +#define MAKE_FORWARD_PARAMS(...) \ + (p_Hook, __VA_ARGS__) From 50671a790e17aca67d8a37f51aa80f18ab02d1ac Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 6 Jun 2026 17:56:07 +0200 Subject: [PATCH 3/9] add ZMagazineUpdateModifierEffect based effects --- .../Player/ZMagazineUpdateModifierEffect.cpp | 140 ++++++++++++++++++ .../Player/ZMagazineUpdateModifierEffect.h | 84 +++++++++++ 2 files changed, 224 insertions(+) create mode 100644 src/Effects/Player/ZMagazineUpdateModifierEffect.cpp create mode 100644 src/Effects/Player/ZMagazineUpdateModifierEffect.h diff --git a/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp new file mode 100644 index 0000000..4d023fc --- /dev/null +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp @@ -0,0 +1,140 @@ +#include "ZMagazineUpdateModifierEffect.h" + +#include "Registry.h" +#include "Helpers/Utils.h" +#include "Helpers/PlayerUtils.h" + +void ZMagazineUpdateModifierEffect::OnModInitialized() +{ + if (Hooks::ZHM5ItemWeapon_SetBulletsInMagazine) + { + Hooks::ZHM5ItemWeapon_SetBulletsInMagazine->AddDetour(this, &ZMagazineUpdateModifierEffect::ZHM5ItemWeapon_SetBulletsInMagazine); + } +} + +void ZMagazineUpdateModifierEffect::OnModUnload() +{ + if (Hooks::ZHM5ItemWeapon_SetBulletsInMagazine) + { + Hooks::ZHM5ItemWeapon_SetBulletsInMagazine->RemoveDetour(&ZMagazineUpdateModifierEffect::ZHM5ItemWeapon_SetBulletsInMagazine); + } +} + +bool ZMagazineUpdateModifierEffect::Available() const +{ + return IChaosEffect::Available() && Hooks::ZHM5ItemWeapon_SetBulletsInMagazine != nullptr; +} + +bool ZMagazineUpdateModifierEffect::IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const +{ + return IChaosEffect::IsCompatibleWith(p_pOtherEffect) + && !Utils::IsInstanceOf(p_pOtherEffect); +} + +void ZMagazineUpdateModifierEffect::OnDrawDebugUI() +{ +} + +void ZMagazineUpdateModifierEffect::Start() +{ + m_bActive = true; +} + +void ZMagazineUpdateModifierEffect::Stop() +{ + m_bActive = false; +} + +DEFINE_BASE_CLASS_INSTANCES_HELPER(ZMagazineUpdateModifierEffect); + +DEFINE_BASE_CLASS_DETOUR( + ZMagazineUpdateModifierEffect, + void, + ZHM5ItemWeapon_SetBulletsInMagazine, + MAKE_FORWARD_PARAMS(th, p_nBullets), + IFirearm* th, + int32_t p_nBullets +) +{ + if (!m_bActive) + { + return {HookAction::Continue()}; + } + + ZHM5ItemWeapon* s_pWeapon = static_cast(th); + if (!s_pWeapon || !s_pWeapon->m_pOwner) + { + return {HookAction::Continue()}; + } + + // check if we should apply this effect based on weapon owner + // only needed if not applied to all + if (m_eApplyTo != EApplyTo::All) + { + // Note: GetLocalPlayer *may* technically return an empty ref, but in practice this should not happen + // all following code is written such that even an empty ref does not cause crashes though + const auto s_rPlayer = Utils::GetLocalPlayer(); + const auto s_bOwnedByPlayer = s_pWeapon->m_pOwner == s_rPlayer.m_entityRef; + + if (s_bOwnedByPlayer && m_eApplyTo == EApplyTo::Actor) + { + return {HookAction::Continue()}; + } + } + + // effect should apply to weapon holder, do the thing + switch (m_eKind) + { + case EKind::UnlimitedMags: + p_nBullets = s_pWeapon->GetMagazineCapacity(); + s_pWeapon->m_nBulletsFired = 0; + break; + case EKind::EmptyMags: + p_nBullets = 0; + s_pWeapon->m_nBulletsFired = s_pWeapon->GetMagazineCapacity(); + break; + case EKind::SingleBulletMags: + if (p_nBullets > 1) + { + p_nBullets = 1; + s_pWeapon->m_nBulletsFired = std::max(0, s_pWeapon->GetMagazineCapacity() - 1); + } + break; + } + + p_Hook->CallOriginal(th, p_nBullets); + return {HookAction::Return()}; +} + +REGISTER_CHAOS_EFFECT_PARAM( + UnlimitedMags, + ZMagazineUpdateModifierEffect, + "UnlimitedMags", + "Unlimited Ammo", + ZMagazineUpdateModifierEffect::EApplyTo::Player, + ZMagazineUpdateModifierEffect::EKind::UnlimitedMags +); +REGISTER_CHAOS_EFFECT_PARAM( + EmptyMagsPlayer, + ZMagazineUpdateModifierEffect, + "EmptyMagsPlayer", + "Oops, No Bullets!", + ZMagazineUpdateModifierEffect::EApplyTo::Player, + ZMagazineUpdateModifierEffect::EKind::EmptyMags +); +REGISTER_CHAOS_EFFECT_PARAM( + EmptyMagsActors, + ZMagazineUpdateModifierEffect, + "EmptyMagsActors", + "Security Budget Cuts", + ZMagazineUpdateModifierEffect::EApplyTo::Actor, + ZMagazineUpdateModifierEffect::EKind::EmptyMags +); +REGISTER_CHAOS_EFFECT_PARAM( + SingleUseMags, + ZMagazineUpdateModifierEffect, + "SingleUseMags", + "Single Shot", + ZMagazineUpdateModifierEffect::EApplyTo::All, + ZMagazineUpdateModifierEffect::EKind::SingleBulletMags +); diff --git a/src/Effects/Player/ZMagazineUpdateModifierEffect.h b/src/Effects/Player/ZMagazineUpdateModifierEffect.h new file mode 100644 index 0000000..81f7448 --- /dev/null +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.h @@ -0,0 +1,84 @@ +#pragma once +#include "IChaosEffect.h" + +#include +#include +#include + +#include "Helpers/HookingHelpers.h" + +class ZMagazineUpdateModifierEffect : public IChaosEffect +{ + public: + enum class EApplyTo + { + Player, + Actor, + All // Actors + Player + }; + + enum class EKind + { + UnlimitedMags, + EmptyMags, + SingleBulletMags + }; + + ZMagazineUpdateModifierEffect( + const std::string p_sNameSuffix, + const std::string s_sDisplayName, + const EApplyTo p_eApplyTo, + const EKind p_eKind + ) + : m_sNameSuffix(p_sNameSuffix), + m_sDisplayName(s_sDisplayName), + m_eApplyTo(p_eApplyTo), + m_eKind(p_eKind) + { + ADD_BASE_CLASS_DETOUR_INSTANCE(); + } + + ~ZMagazineUpdateModifierEffect() + { + REMOVE_BASE_CLASS_DETOUR_INSTANCE(); + } + + void OnModInitialized() override; + void OnModUnload() override; + bool Available() const override; + bool IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const override; + + void OnDrawDebugUI() override; + + void Start() override; + void Stop() override; + + std::string GetName() const override + { + return IChaosEffect::GetName() + "_" + m_sNameSuffix; + } + + std::string GetDisplayName(const bool p_bVoting) const override + { + return m_sDisplayName; + } + + private: + std::string m_sNameSuffix; + std::string m_sDisplayName; + EApplyTo m_eApplyTo; + EKind m_eKind; + + private: + bool m_bActive = false; + + DECLARE_BASE_CLASS_INSTANCES_HELPER(ZMagazineUpdateModifierEffect); + + DECLARE_BASE_CLASS_DETOUR( + ZMagazineUpdateModifierEffect, + void, + ZHM5ItemWeapon_SetBulletsInMagazine, + IFirearm* th, + int32_t p_nBullets + ); +}; \ No newline at end of file From 4445e5716919ea70bd2c1135c3d55f177199c945 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 6 Jun 2026 17:56:46 +0200 Subject: [PATCH 4/9] remove ZUnlimitedAmmoEffect based on ZPlayerCrippleBoxEffectBase redundant, also provided by ZMagazineUpdateModifierEffect --- src/Effects/Player/PlayerCrippleBoxEffects.cpp | 10 ---------- src/Effects/Player/PlayerCrippleBoxEffects.h | 11 ----------- 2 files changed, 21 deletions(-) diff --git a/src/Effects/Player/PlayerCrippleBoxEffects.cpp b/src/Effects/Player/PlayerCrippleBoxEffects.cpp index 6d8efe5..ca09147 100644 --- a/src/Effects/Player/PlayerCrippleBoxEffects.cpp +++ b/src/Effects/Player/PlayerCrippleBoxEffects.cpp @@ -3,16 +3,6 @@ #include "Registry.h" #include "Helpers/Utils.h" -void ZUnlimitedAmmoEffect::Start() -{ - auto s_rCripple = GetCrippleBox(); - s_rCripple.m_pInterfaceRef->m_bLimitedAmmo = false; - - ActivateCrippleBox(); -} - -REGISTER_CHAOS_EFFECT(ZUnlimitedAmmoEffect); - void ZPlayerPacifistEffect::Start() { auto s_rCripple = GetCrippleBox(); diff --git a/src/Effects/Player/PlayerCrippleBoxEffects.h b/src/Effects/Player/PlayerCrippleBoxEffects.h index f9a21e8..32f933c 100644 --- a/src/Effects/Player/PlayerCrippleBoxEffects.h +++ b/src/Effects/Player/PlayerCrippleBoxEffects.h @@ -1,17 +1,6 @@ #pragma once #include "Effects/Base/ZPlayerCrippleBoxEffectBase.h" -class ZUnlimitedAmmoEffect : public ZPlayerCrippleBoxEffectBase -{ - public: - void Start() override; - - std::string GetDisplayName(const bool p_bVoting) const override - { - return "Unlimited Ammo"; - } -}; - class ZPlayerPacifistEffect : public ZPlayerCrippleBoxEffectBase { public: From 6b1ac3e20ba4c17aeb4e6d066d6dbb6d458914f4 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:21:30 +0200 Subject: [PATCH 5/9] add ZWeaponPrecisionOverrideEffect / "Super Precison" and "Bad Aim" --- .../Player/ZMagazineUpdateModifierEffect.cpp | 4 - .../Player/ZMagazineUpdateModifierEffect.h | 2 - .../Player/ZWeaponPrecisionOverrideEffect.cpp | 96 +++++++++++++++++++ .../Player/ZWeaponPrecisionOverrideEffect.h | 67 +++++++++++++ 4 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp create mode 100644 src/Effects/Player/ZWeaponPrecisionOverrideEffect.h diff --git a/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp index 4d023fc..fa6b4de 100644 --- a/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp @@ -31,10 +31,6 @@ bool ZMagazineUpdateModifierEffect::IsCompatibleWith(const IChaosEffect* p_pOthe && !Utils::IsInstanceOf(p_pOtherEffect); } -void ZMagazineUpdateModifierEffect::OnDrawDebugUI() -{ -} - void ZMagazineUpdateModifierEffect::Start() { m_bActive = true; diff --git a/src/Effects/Player/ZMagazineUpdateModifierEffect.h b/src/Effects/Player/ZMagazineUpdateModifierEffect.h index 81f7448..bbd87ac 100644 --- a/src/Effects/Player/ZMagazineUpdateModifierEffect.h +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.h @@ -48,8 +48,6 @@ class ZMagazineUpdateModifierEffect : public IChaosEffect bool Available() const override; bool IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const override; - void OnDrawDebugUI() override; - void Start() override; void Stop() override; diff --git a/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp new file mode 100644 index 0000000..bfc8f92 --- /dev/null +++ b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp @@ -0,0 +1,96 @@ +#include "ZWeaponPrecisionOverrideEffect.h" + +#include "Registry.h" +#include "Helpers/Utils.h" +#include "Helpers/PlayerUtils.h" +#include "Helpers/ImGuiExtras.h" + +void ZWeaponPrecisionOverrideEffect::OnModInitialized() +{ + if (Hooks::ZHM5ItemWeapon_FireProjectiles) + { + Hooks::ZHM5ItemWeapon_FireProjectiles->AddDetour(this, &ZWeaponPrecisionOverrideEffect::ZHM5ItemWeapon_FireProjectiles); + } +} + +void ZWeaponPrecisionOverrideEffect::OnModUnload() +{ + if (Hooks::ZHM5ItemWeapon_FireProjectiles) + { + Hooks::ZHM5ItemWeapon_FireProjectiles->RemoveDetour(&ZWeaponPrecisionOverrideEffect::ZHM5ItemWeapon_FireProjectiles); + } +} + +bool ZWeaponPrecisionOverrideEffect::Available() const +{ + return IChaosEffect::Available() && Hooks::ZHM5ItemWeapon_FireProjectiles != nullptr; +} + +bool ZWeaponPrecisionOverrideEffect::IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const +{ + return IChaosEffect::IsCompatibleWith(p_pOtherEffect) + && !Utils::IsInstanceOf(p_pOtherEffect); +} + +void ZWeaponPrecisionOverrideEffect::OnDrawDebugUI() +{ + ImGuiEx::DragFloat("Precision Factor", &m_fPrecisionFactor, 0.f, 10.f); +} + +void ZWeaponPrecisionOverrideEffect::Start() +{ + m_bActive = true; +} + +void ZWeaponPrecisionOverrideEffect::Stop() +{ + m_bActive = false; +} + +DEFINE_BASE_CLASS_INSTANCES_HELPER(ZWeaponPrecisionOverrideEffect); + +DEFINE_BASE_CLASS_DETOUR( + ZWeaponPrecisionOverrideEffect, + bool, + ZHM5ItemWeapon_FireProjectiles, + MAKE_FORWARD_PARAMS(th, p_bMayStartSound), + ZHM5ItemWeapon* th, + bool p_bMayStartSound +) +{ + if (!m_bActive || !th) + { + return {HookAction::Continue()}; + } + + // apply only for the local player + const auto s_rPlayer = Utils::GetLocalPlayer(); + if (th->m_pOwner != s_rPlayer.m_entityRef) + { + return {HookAction::Continue()}; + } + + const auto s_fOriginalPrecisionFactor = th->m_fPrecisionFactor; + th->m_fPrecisionFactor = m_fPrecisionFactor; + + const auto s_bResult = p_Hook->CallOriginal(th, p_bMayStartSound); + + th->m_fPrecisionFactor = s_fOriginalPrecisionFactor; + + return {HookAction::Return(), s_bResult}; +} + +REGISTER_CHAOS_EFFECT_PARAM( + zero, + ZWeaponPrecisionOverrideEffect, + "zero", + "Super Precision", + 0.f +); +REGISTER_CHAOS_EFFECT_PARAM( + high, + ZWeaponPrecisionOverrideEffect, + "high", + "Bad Aim", + 500.f // too high and the game just kinda gives up... +); diff --git a/src/Effects/Player/ZWeaponPrecisionOverrideEffect.h b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.h new file mode 100644 index 0000000..f80d259 --- /dev/null +++ b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.h @@ -0,0 +1,67 @@ +#pragma once +#include "IChaosEffect.h" + +#include +#include +#include + +#include "Helpers/HookingHelpers.h" + +class ZWeaponPrecisionOverrideEffect : public IChaosEffect +{ + public: + ZWeaponPrecisionOverrideEffect( + const std::string p_sNameSuffix, + const std::string s_sDisplayName, + const float32 p_fPrecisionFactor + ) + : m_sNameSuffix(p_sNameSuffix), + m_sDisplayName(s_sDisplayName), + m_fPrecisionFactor(p_fPrecisionFactor) + { + ADD_BASE_CLASS_DETOUR_INSTANCE(); + } + + ~ZWeaponPrecisionOverrideEffect() + { + REMOVE_BASE_CLASS_DETOUR_INSTANCE(); + } + + void OnModInitialized() override; + void OnModUnload() override; + bool Available() const override; + bool IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const override; + + void OnDrawDebugUI() override; + + void Start() override; + void Stop() override; + + std::string GetName() const override + { + return IChaosEffect::GetName() + "_" + m_sNameSuffix; + } + + std::string GetDisplayName(const bool p_bVoting) const override + { + return m_sDisplayName; + } + + private: + std::string m_sNameSuffix; + std::string m_sDisplayName; + float32 m_fPrecisionFactor; + + private: + bool m_bActive = false; + + DECLARE_BASE_CLASS_INSTANCES_HELPER(ZWeaponPrecisionOverrideEffect); + + DECLARE_BASE_CLASS_DETOUR( + ZWeaponPrecisionOverrideEffect, + bool, + ZHM5ItemWeapon_FireProjectiles, + ZHM5ItemWeapon* th, + bool bMayStartSound + ); +}; \ No newline at end of file From 4101b60b86fd3a5b038b41e4109cc22ccd0c3bcc Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 6 Jun 2026 18:58:43 +0200 Subject: [PATCH 6/9] add ZWeaponRapidFireEffect "Rapid Fire" --- src/Effects/Player/ZWeaponRapidFireEffect.cpp | 77 +++++++++++++++++++ src/Effects/Player/ZWeaponRapidFireEffect.h | 47 +++++++++++ 2 files changed, 124 insertions(+) create mode 100644 src/Effects/Player/ZWeaponRapidFireEffect.cpp create mode 100644 src/Effects/Player/ZWeaponRapidFireEffect.h diff --git a/src/Effects/Player/ZWeaponRapidFireEffect.cpp b/src/Effects/Player/ZWeaponRapidFireEffect.cpp new file mode 100644 index 0000000..85cbb10 --- /dev/null +++ b/src/Effects/Player/ZWeaponRapidFireEffect.cpp @@ -0,0 +1,77 @@ +#include "ZWeaponRapidFireEffect.h" + +#include + +#include "Registry.h" +#include "Helpers/Utils.h" +#include "Helpers/PlayerUtils.h" + +void ZWeaponRapidFireEffect::OnModInitialized() +{ + if (Hooks::ZHM5ItemWeapon_IsFiring) + { + Hooks::ZHM5ItemWeapon_IsFiring->AddDetour(this, &ZWeaponRapidFireEffect::ZHM5ItemWeapon_IsFiring); + } +} + +void ZWeaponRapidFireEffect::OnModUnload() +{ + if (Hooks::ZHM5ItemWeapon_IsFiring) + { + Hooks::ZHM5ItemWeapon_IsFiring->RemoveDetour(&ZWeaponRapidFireEffect::ZHM5ItemWeapon_IsFiring); + } +} + +bool ZWeaponRapidFireEffect::Available() const +{ + return IChaosEffect::Available() && Hooks::ZHM5ItemWeapon_IsFiring != nullptr; +} + +bool ZWeaponRapidFireEffect::IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const +{ + return IChaosEffect::IsCompatibleWith(p_pOtherEffect) + && !Utils::IsInstanceOf(p_pOtherEffect); +} + +void ZWeaponRapidFireEffect::Start() +{ + m_bActive = true; +} + +void ZWeaponRapidFireEffect::Stop() +{ + m_bActive = false; +} + +DEFINE_BASE_CLASS_INSTANCES_HELPER(ZWeaponRapidFireEffect); + +DEFINE_BASE_CLASS_DETOUR( + ZWeaponRapidFireEffect, + bool, + ZHM5ItemWeapon_IsFiring, + MAKE_FORWARD_PARAMS(th), + IFirearm* th +) +{ + if (!m_bActive || !th) + { + return {HookAction::Continue()}; + } + + ZHM5ItemWeapon* s_pWeapon = static_cast(th); + if (!s_pWeapon || !s_pWeapon->m_pOwner) + { + return {HookAction::Continue()}; + } + + // rapid fire only applies to local player + const auto s_rPlayer = Utils::GetLocalPlayer(); + if (s_pWeapon->m_pOwner == s_rPlayer.m_entityRef) + { + s_pWeapon->m_tLastShootTime = ZGameTime(); + } + + return {HookAction::Continue()}; +} + +REGISTER_CHAOS_EFFECT(ZWeaponRapidFireEffect); diff --git a/src/Effects/Player/ZWeaponRapidFireEffect.h b/src/Effects/Player/ZWeaponRapidFireEffect.h new file mode 100644 index 0000000..83e0bcc --- /dev/null +++ b/src/Effects/Player/ZWeaponRapidFireEffect.h @@ -0,0 +1,47 @@ +#pragma once +#include "IChaosEffect.h" + +#include +#include +#include + +#include "Helpers/HookingHelpers.h" + +class ZWeaponRapidFireEffect : public IChaosEffect +{ + public: + ZWeaponRapidFireEffect() + { + ADD_BASE_CLASS_DETOUR_INSTANCE(); + } + + ~ZWeaponRapidFireEffect() + { + REMOVE_BASE_CLASS_DETOUR_INSTANCE(); + } + + void OnModInitialized() override; + void OnModUnload() override; + bool Available() const override; + bool IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const override; + + void Start() override; + void Stop() override; + + std::string GetDisplayName(const bool p_bVoting) const override + { + return "Rapid Fire"; + } + + private: + bool m_bActive = false; + + DECLARE_BASE_CLASS_INSTANCES_HELPER(ZWeaponRapidFireEffect); + + DECLARE_BASE_CLASS_DETOUR( + ZWeaponRapidFireEffect, + bool, + ZHM5ItemWeapon_IsFiring, + IFirearm* th + ); +}; \ No newline at end of file From fb5ded3505b7fd1aafc2b8abde50c728b4edf448 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:24:05 +0200 Subject: [PATCH 7/9] update HookingHelper.h to avoid static init order issues --- .../Player/ZMagazineUpdateModifierEffect.cpp | 2 -- src/Effects/Player/ZRecoilModifierEffect.cpp | 2 -- .../Player/ZWeaponPrecisionOverrideEffect.cpp | 2 -- src/Effects/Player/ZWeaponRapidFireEffect.cpp | 2 -- src/Helpers/HookingHelpers.h | 20 +++++++++---------- 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp index fa6b4de..6028b93 100644 --- a/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp @@ -41,8 +41,6 @@ void ZMagazineUpdateModifierEffect::Stop() m_bActive = false; } -DEFINE_BASE_CLASS_INSTANCES_HELPER(ZMagazineUpdateModifierEffect); - DEFINE_BASE_CLASS_DETOUR( ZMagazineUpdateModifierEffect, void, diff --git a/src/Effects/Player/ZRecoilModifierEffect.cpp b/src/Effects/Player/ZRecoilModifierEffect.cpp index d16bd55..9799e11 100644 --- a/src/Effects/Player/ZRecoilModifierEffect.cpp +++ b/src/Effects/Player/ZRecoilModifierEffect.cpp @@ -49,8 +49,6 @@ void ZRecoilModifierEffect::Stop() m_bActive = false; } -DEFINE_BASE_CLASS_INSTANCES_HELPER(ZRecoilModifierEffect); - DEFINE_BASE_CLASS_DETOUR( ZRecoilModifierEffect, void, diff --git a/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp index bfc8f92..f4efff0 100644 --- a/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp +++ b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp @@ -47,8 +47,6 @@ void ZWeaponPrecisionOverrideEffect::Stop() m_bActive = false; } -DEFINE_BASE_CLASS_INSTANCES_HELPER(ZWeaponPrecisionOverrideEffect); - DEFINE_BASE_CLASS_DETOUR( ZWeaponPrecisionOverrideEffect, bool, diff --git a/src/Effects/Player/ZWeaponRapidFireEffect.cpp b/src/Effects/Player/ZWeaponRapidFireEffect.cpp index 85cbb10..1dfb763 100644 --- a/src/Effects/Player/ZWeaponRapidFireEffect.cpp +++ b/src/Effects/Player/ZWeaponRapidFireEffect.cpp @@ -43,8 +43,6 @@ void ZWeaponRapidFireEffect::Stop() m_bActive = false; } -DEFINE_BASE_CLASS_INSTANCES_HELPER(ZWeaponRapidFireEffect); - DEFINE_BASE_CLASS_DETOUR( ZWeaponRapidFireEffect, bool, diff --git a/src/Helpers/HookingHelpers.h b/src/Helpers/HookingHelpers.h index e93da9e..c4c5a05 100644 --- a/src/Helpers/HookingHelpers.h +++ b/src/Helpers/HookingHelpers.h @@ -27,9 +27,6 @@ * }; * * cpp (ZSomeHookBaseEffect.cpp): - * // define list of instances, this must be done ONCE per base class, in a cpp file - * DEFINE_BASE_CLASS_INSTANCES_HELPER(ZSomeHookBaseEffect); - * * // define your hook implementation as you normally would, but use DEFINE_BASE_CLASS_DETOUR instead of DEFINE_PLUGIN_DETOUR * // also, you need to specify the parameter names *without* type specifiers via MAKE_FORWARD_PARAMS. * DEFINE_BASE_CLASS_DETOUR(ZSomeHookBaseEffect, void, SomeHook, MAKE_FORWARD_PARAMS(...), ...) @@ -40,17 +37,18 @@ #pragma once #include // for DECLARE_PLUGIN_DETOUR -#define DECLARE_BASE_CLASS_INSTANCES_HELPER(BaseClass) \ - static std::vector m_aInstancesForDetours; - -#define DEFINE_BASE_CLASS_INSTANCES_HELPER(BaseClass) \ - std::vector BaseClass::m_aInstancesForDetours; +#define DECLARE_BASE_CLASS_INSTANCES_HELPER(BaseClass) \ + static std::vector& __GetDetourInstances() \ + { \ + static std::vector g_aInstancesForDetours; \ + return g_aInstancesForDetours; \ + } #define ADD_BASE_CLASS_DETOUR_INSTANCE() \ - m_aInstancesForDetours.push_back(this); + __GetDetourInstances().push_back(this); #define REMOVE_BASE_CLASS_DETOUR_INSTANCE() \ - m_aInstancesForDetours.erase(std::remove(m_aInstancesForDetours.begin(), m_aInstancesForDetours.end(), this), m_aInstancesForDetours.end()); + __GetDetourInstances().erase(std::remove(__GetDetourInstances().begin(), __GetDetourInstances().end(), this), __GetDetourInstances().end()); #define DECLARE_BASE_CLASS_DETOUR(BaseClass, ReturnType, DetourName, ...) \ DECLARE_PLUGIN_DETOUR(BaseClass, ReturnType, DetourName, __VA_ARGS__); \ @@ -62,7 +60,7 @@ #define DEFINE_BASE_CLASS_DETOUR(BaseClass, ReturnType, DetourName, ForwardParams, ...) \ DEFINE_PLUGIN_DETOUR(BaseClass, ReturnType, DetourName, __VA_ARGS__) \ { \ - for (auto& s_pInstance : m_aInstancesForDetours) \ + for (auto& s_pInstance : __GetDetourInstances()) \ { \ const auto s_Result = s_pInstance->DetourName##_Impl ForwardParams; \ if (s_Result.m_HasReturnVal) \ From b370a4a041e0dbe513168d5835f7a6937091d48d Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:39:06 +0200 Subject: [PATCH 8/9] add ZPlayerOverpoweredEffect / "Unfair Advantage" --- .../Player/ZMagazineUpdateModifierEffect.h | 2 +- .../Player/ZPlayerOverpoweredEffect.cpp | 5 + src/Effects/Player/ZPlayerOverpoweredEffect.h | 93 +++++++++++++++++++ src/Effects/Player/ZRecoilModifierEffect.h | 2 +- .../Player/ZWeaponPrecisionOverrideEffect.h | 2 +- src/Effects/Player/ZWeaponRapidFireEffect.h | 2 +- 6 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 src/Effects/Player/ZPlayerOverpoweredEffect.cpp create mode 100644 src/Effects/Player/ZPlayerOverpoweredEffect.h diff --git a/src/Effects/Player/ZMagazineUpdateModifierEffect.h b/src/Effects/Player/ZMagazineUpdateModifierEffect.h index bbd87ac..6e74a18 100644 --- a/src/Effects/Player/ZMagazineUpdateModifierEffect.h +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.h @@ -7,7 +7,7 @@ #include "Helpers/HookingHelpers.h" -class ZMagazineUpdateModifierEffect : public IChaosEffect +class ZMagazineUpdateModifierEffect : public virtual IChaosEffect // virtual inheritance to allow for ZPlayerJuggernautEffect { public: enum class EApplyTo diff --git a/src/Effects/Player/ZPlayerOverpoweredEffect.cpp b/src/Effects/Player/ZPlayerOverpoweredEffect.cpp new file mode 100644 index 0000000..7676568 --- /dev/null +++ b/src/Effects/Player/ZPlayerOverpoweredEffect.cpp @@ -0,0 +1,5 @@ +#include "ZPlayerOverpoweredEffect.h" + +#include "Registry.h" + +REGISTER_CHAOS_EFFECT(ZPlayerOverpoweredEffect); diff --git a/src/Effects/Player/ZPlayerOverpoweredEffect.h b/src/Effects/Player/ZPlayerOverpoweredEffect.h new file mode 100644 index 0000000..16f6873 --- /dev/null +++ b/src/Effects/Player/ZPlayerOverpoweredEffect.h @@ -0,0 +1,93 @@ +#pragma once +#include "Effects/Player/ZRecoilModifierEffect.h" +#include "Effects/Player/ZMagazineUpdateModifierEffect.h" +#include "Effects/Player/ZWeaponPrecisionOverrideEffect.h" +#include "Effects/Player/ZWeaponRapidFireEffect.h" + +// this is such a stupid effect lol +// this effect combines four other effects to create a "juggernaut" mode for the player. +// you can probably see how stupidly overpowered this is :P +// Note: some of the sub-effects define name & display name via constructor parameters (for use with REGISTER_CHAOS_EFFECT_PARAM). +// here, we simply pass dummy values, as we override the methods anyway. +class ZPlayerOverpoweredEffect : public ZRecoilModifierEffect, public ZMagazineUpdateModifierEffect, public ZWeaponPrecisionOverrideEffect, public ZWeaponRapidFireEffect +{ + public: + ZPlayerOverpoweredEffect() + : ZRecoilModifierEffect("op", "", SVector2(0.f, 0.f)), + ZMagazineUpdateModifierEffect("op", "", EApplyTo::Player, EKind::UnlimitedMags), + ZWeaponPrecisionOverrideEffect("op", "", 1.f), + ZWeaponRapidFireEffect() + { + } + + void OnModInitialized() override + { + ZRecoilModifierEffect::OnModInitialized(); + ZMagazineUpdateModifierEffect::OnModInitialized(); + ZWeaponPrecisionOverrideEffect::OnModInitialized(); + ZWeaponRapidFireEffect::OnModInitialized(); + } + + void OnModUnload() override + { + ZRecoilModifierEffect::OnModUnload(); + ZMagazineUpdateModifierEffect::OnModUnload(); + ZWeaponPrecisionOverrideEffect::OnModUnload(); + ZWeaponRapidFireEffect::OnModUnload(); + } + + bool Available() const override + { + return ZRecoilModifierEffect::Available() + && ZMagazineUpdateModifierEffect::Available() + && ZWeaponPrecisionOverrideEffect::Available() + && ZWeaponRapidFireEffect::Available(); + } + + bool IsCompatibleWith(const IChaosEffect* p_pOtherEffect) const override + { + return ZRecoilModifierEffect::IsCompatibleWith(p_pOtherEffect) + && ZMagazineUpdateModifierEffect::IsCompatibleWith(p_pOtherEffect) + && ZWeaponPrecisionOverrideEffect::IsCompatibleWith(p_pOtherEffect) + && ZWeaponRapidFireEffect::IsCompatibleWith(p_pOtherEffect); + } + + void OnDrawDebugUI() override + { + ZRecoilModifierEffect::OnDrawDebugUI(); + ZMagazineUpdateModifierEffect::OnDrawDebugUI(); + ZWeaponPrecisionOverrideEffect::OnDrawDebugUI(); + ZWeaponRapidFireEffect::OnDrawDebugUI(); + } + + void Start() override + { + ZRecoilModifierEffect::Start(); + ZMagazineUpdateModifierEffect::Start(); + ZWeaponPrecisionOverrideEffect::Start(); + ZWeaponRapidFireEffect::Start(); + } + + void Stop() override + { + ZRecoilModifierEffect::Stop(); + ZMagazineUpdateModifierEffect::Stop(); + ZWeaponPrecisionOverrideEffect::Stop(); + ZWeaponRapidFireEffect::Stop(); + } + + std::string GetName() const override + { + return IChaosEffect::GetName(); + } + + std::string GetDisplayName(const bool p_bVoting) const override + { + return "Unfair Advantage"; // TODO name kinda sucks thb + } + + EDuration GetDuration() const override + { + return EDuration::Short; + } +}; diff --git a/src/Effects/Player/ZRecoilModifierEffect.h b/src/Effects/Player/ZRecoilModifierEffect.h index 093ee30..6927d16 100644 --- a/src/Effects/Player/ZRecoilModifierEffect.h +++ b/src/Effects/Player/ZRecoilModifierEffect.h @@ -6,7 +6,7 @@ #include "Helpers/HookingHelpers.h" -class ZRecoilModifierEffect : public IChaosEffect +class ZRecoilModifierEffect : public virtual IChaosEffect // virtual inheritance to allow for ZPlayerJuggernautEffect { public: ZRecoilModifierEffect( diff --git a/src/Effects/Player/ZWeaponPrecisionOverrideEffect.h b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.h index f80d259..23e6321 100644 --- a/src/Effects/Player/ZWeaponPrecisionOverrideEffect.h +++ b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.h @@ -7,7 +7,7 @@ #include "Helpers/HookingHelpers.h" -class ZWeaponPrecisionOverrideEffect : public IChaosEffect +class ZWeaponPrecisionOverrideEffect : public virtual IChaosEffect // virtual inheritance to allow for ZPlayerJuggernautEffect { public: ZWeaponPrecisionOverrideEffect( diff --git a/src/Effects/Player/ZWeaponRapidFireEffect.h b/src/Effects/Player/ZWeaponRapidFireEffect.h index 83e0bcc..571e0c7 100644 --- a/src/Effects/Player/ZWeaponRapidFireEffect.h +++ b/src/Effects/Player/ZWeaponRapidFireEffect.h @@ -7,7 +7,7 @@ #include "Helpers/HookingHelpers.h" -class ZWeaponRapidFireEffect : public IChaosEffect +class ZWeaponRapidFireEffect : public virtual IChaosEffect // virtual inheritance to allow for ZPlayerJuggernautEffect { public: ZWeaponRapidFireEffect() From 80cd832e970891fd49f914cb33c562e7ab0c0e25 Mon Sep 17 00:00:00 2001 From: shadow578 <52449218+shadow578@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:59:10 +0200 Subject: [PATCH 9/9] fix ZMagazineUpdateModifierEffect incorrectly targeting actor-only effect to the player --- src/Effects/Player/ZMagazineUpdateModifierEffect.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp index 6028b93..55325a8 100644 --- a/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp @@ -70,10 +70,17 @@ DEFINE_BASE_CLASS_DETOUR( const auto s_rPlayer = Utils::GetLocalPlayer(); const auto s_bOwnedByPlayer = s_pWeapon->m_pOwner == s_rPlayer.m_entityRef; + // player owns and effect targets actor only? if (s_bOwnedByPlayer && m_eApplyTo == EApplyTo::Actor) { return {HookAction::Continue()}; } + + // actor owns and effect targets player only? + if (!s_bOwnedByPlayer && m_eApplyTo == EApplyTo::Player) + { + return {HookAction::Continue()}; + } } // effect should apply to weapon holder, do the thing