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: diff --git a/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp new file mode 100644 index 0000000..55325a8 --- /dev/null +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.cpp @@ -0,0 +1,141 @@ +#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::Start() +{ + m_bActive = true; +} + +void ZMagazineUpdateModifierEffect::Stop() +{ + m_bActive = false; +} + +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; + + // 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 + 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..6e74a18 --- /dev/null +++ b/src/Effects/Player/ZMagazineUpdateModifierEffect.h @@ -0,0 +1,82 @@ +#pragma once +#include "IChaosEffect.h" + +#include +#include +#include + +#include "Helpers/HookingHelpers.h" + +class ZMagazineUpdateModifierEffect : public virtual IChaosEffect // virtual inheritance to allow for ZPlayerJuggernautEffect +{ + 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 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 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.cpp b/src/Effects/Player/ZRecoilModifierEffect.cpp new file mode 100644 index 0000000..9799e11 --- /dev/null +++ b/src/Effects/Player/ZRecoilModifierEffect.cpp @@ -0,0 +1,75 @@ +#include "ZRecoilModifierEffect.h" + +#include + +#include + +#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; +} + +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; +} + +DEFINE_BASE_CLASS_DETOUR( + ZRecoilModifierEffect, + void, + ZHM5WeaponRecoilController_RecoilWeapon, + MAKE_FORWARD_PARAMS(th, rWeapon), + 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_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..6927d16 --- /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 virtual IChaosEffect // virtual inheritance to allow for ZPlayerJuggernautEffect +{ + 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/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp new file mode 100644 index 0000000..f4efff0 --- /dev/null +++ b/src/Effects/Player/ZWeaponPrecisionOverrideEffect.cpp @@ -0,0 +1,94 @@ +#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_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..23e6321 --- /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 virtual IChaosEffect // virtual inheritance to allow for ZPlayerJuggernautEffect +{ + 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 diff --git a/src/Effects/Player/ZWeaponRapidFireEffect.cpp b/src/Effects/Player/ZWeaponRapidFireEffect.cpp new file mode 100644 index 0000000..1dfb763 --- /dev/null +++ b/src/Effects/Player/ZWeaponRapidFireEffect.cpp @@ -0,0 +1,75 @@ +#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_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..571e0c7 --- /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 virtual IChaosEffect // virtual inheritance to allow for ZPlayerJuggernautEffect +{ + 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 diff --git a/src/Helpers/HookingHelpers.h b/src/Helpers/HookingHelpers.h new file mode 100644 index 0000000..c4c5a05 --- /dev/null +++ b/src/Helpers/HookingHelpers.h @@ -0,0 +1,80 @@ +/** + * 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 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& __GetDetourInstances() \ + { \ + static std::vector g_aInstancesForDetours; \ + return g_aInstancesForDetours; \ + } + +#define ADD_BASE_CLASS_DETOUR_INSTANCE() \ + __GetDetourInstances().push_back(this); + +#define REMOVE_BASE_CLASS_DETOUR_INSTANCE() \ + __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__); \ + 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 : __GetDetourInstances()) \ + { \ + 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__)