diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index fd226ae..3780f37 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -64,6 +64,7 @@ namespace Components Register(new GUI()); Register(new Markdown()); Register(new Changelog()); + Register(new Paintball()); Pregame = false; // Make sure preDestroy is called when the game shuts down diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 5058cb1..56fcf3b 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -82,3 +82,4 @@ namespace Components #include "Modules/GUI/GUI.hpp" #include "Modules/GUI/Markdown.hpp" #include "Modules/Changelog.hpp" +#include "Modules/Paintball.hpp" diff --git a/src/Components/Modules/Dvars.cpp b/src/Components/Modules/Dvars.cpp index bc4b6ea..f588b88 100644 --- a/src/Components/Modules/Dvars.cpp +++ b/src/Components/Modules/Dvars.cpp @@ -34,6 +34,7 @@ namespace Dvars // Player settings dvars Game::dvar_s* p_allowFire = nullptr; Game::dvar_s* player_sprintUnlimited = nullptr; + Game::dvar_s* paintball_mode = nullptr; Game::dvar_s* external_console = nullptr; diff --git a/src/Components/Modules/Dvars.hpp b/src/Components/Modules/Dvars.hpp index ffdb851..dbb142a 100644 --- a/src/Components/Modules/Dvars.hpp +++ b/src/Components/Modules/Dvars.hpp @@ -34,6 +34,7 @@ namespace Dvars // Player settings dvars extern Game::dvar_s* p_allowFire; extern Game::dvar_s* player_sprintUnlimited; + extern Game::dvar_s* paintball_mode; extern Game::dvar_s* external_console; diff --git a/src/Components/Modules/Events.cpp b/src/Components/Modules/Events.cpp index 7d33761..b075ac3 100644 --- a/src/Components/Modules/Events.cpp +++ b/src/Components/Modules/Events.cpp @@ -6,6 +6,8 @@ namespace Components Utils::ConcurrentList::Container Events::ShutdownSystemTasks_; Utils::ConcurrentList::Container Events::ScriptsShutdownTasks_; Utils::ConcurrentList::Container Events::OnMapLoadTasks_; + Utils::ConcurrentList::Container Events::RegisterGraphic_; + Utils::ConcurrentList::Container Events::RegisterSound_; void Events::OnDvarInit(const std::function& callback) { @@ -39,6 +41,22 @@ namespace Components }); } + void Events::RegisterGraphic(const std::function& callback) + { + RegisterGraphic_.access([&callback](Callback& tasks) + { + tasks.emplace_back(callback); + }); + } + + void Events::RegisterSound(const std::function& callback) + { + RegisterSound_.access([&callback](Callback& tasks) + { + tasks.emplace_back(callback); + }); + } + void Events::Com_InitDvars_stub() { DvarInitTasks_.access([](Callback& tasks) @@ -107,6 +125,32 @@ namespace Components } } + void Events::CG_InitStub(const char* mapname) + { + RegisterGraphic_.access([](Callback& tasks) + { + for (const auto& func : tasks) + { + func(); + } + }); + + Utils::Hook::Call(0x41FE10)(mapname); // CG_RegisterGraphics + } + + void Events::SV_SpawnServerStub() + { + RegisterSound_.access([](Callback& tasks) + { + for (const auto& func : tasks) + { + func(); + } + }); + + Utils::Hook::Call(0x41F8F0)(); //CG_RegisterSounds + } + Events::Events() { Utils::Hook(0x534B15, Com_InitDvars_stub, HOOK_CALL).install()->quick(); @@ -118,6 +162,10 @@ namespace Components Utils::Hook(0x5C657C, SV_ShutdownGameVM_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x5C7748, Scr_OnMapLoadStub, HOOK_CALL).install()->quick(); + + // For custom object 'cgMediaMod' + Utils::Hook(0x421011, CG_InitStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x5C7638, SV_SpawnServerStub, HOOK_CALL).install()->quick(); } Events::~Events() diff --git a/src/Components/Modules/Events.hpp b/src/Components/Modules/Events.hpp index 7053875..2c03be1 100644 --- a/src/Components/Modules/Events.hpp +++ b/src/Components/Modules/Events.hpp @@ -15,13 +15,19 @@ namespace Components static void OnScriptsShutdown(const std::function& callback); static void OnVMShutdown(const std::function& callback); static void OnMapLoad(const std::function& callback); + static void RegisterGraphic(const std::function& callback); + static void RegisterSound(const std::function& callback); private: static Utils::ConcurrentList::Container DvarInitTasks_; static Utils::ConcurrentList::Container ScriptsShutdownTasks_; static Utils::ConcurrentList::Container ShutdownSystemTasks_; static Utils::ConcurrentList::Container OnMapLoadTasks_; + static Utils::ConcurrentList::Container RegisterGraphic_; + static Utils::ConcurrentList::Container RegisterSound_; static void Com_InitDvars_stub(); + static void CG_InitStub(const char* mapname); + static void SV_SpawnServerStub(); static void Scr_OnMapLoadStub(int channel, const char* message, const char* format); static void Scr_ShutdownSystem_Hk(unsigned char sys); static void SV_ShutdownGameVM_Hk(); diff --git a/src/Components/Modules/Paintball.cpp b/src/Components/Modules/Paintball.cpp new file mode 100644 index 0000000..665fbc4 --- /dev/null +++ b/src/Components/Modules/Paintball.cpp @@ -0,0 +1,217 @@ +#include "STDInc.hpp" +#include +namespace Components +{ + bool Paintball::PB_Enabled() + { + return !Game::HasLoadedMod() && Dvars::paintball_mode->current.enabled; + } + + const Game::FxEffectDef* Paintball::GetEffect() + { + const std::vector effects = { + Game::cgMediaMod.fxPaintBallGreen, + Game::cgMediaMod.fxPaintBallOrange, + Game::cgMediaMod.fxPaintBallBlue, + Game::cgMediaMod.fxPaintBallRed, + Game::cgMediaMod.fxPaintBallYellow + }; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(0, effects.size() - 1); + + int randomIndex = distr(gen); + + return effects[randomIndex]; + } + + const Game::FxEffectDef* Paintball::GetExplosiveEffect() + { + const std::vector effects = { + Game::cgMediaMod.fxPaintBallExpGreen, + Game::cgMediaMod.fxPaintBallExpOrange + }; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distr(0, effects.size() - 1); + + int randomIndex = distr(gen); + + return effects[randomIndex]; + } + + void Paintball::FireSound(Game::snd_alias_list_t* aliasList/*eax*/, int localClientNum, float* origin) + { + Game::snd_alias_list_t* soundAlias; + if (PB_Enabled()) + { + std::string soundAliasName = aliasList->aliasName; + if (soundAliasName.ends_with("_plr")) + soundAlias = Game::cgMediaMod.paintBallFirePlayer; + else + soundAlias = Game::cgMediaMod.paintBallFireNPC; + } + else + soundAlias = aliasList; + + Game::CG_PlayClientSoundAlias(soundAlias, localClientNum, origin); + } + + void Paintball::ImpactSound(Game::snd_alias_list_t* aliasList/*eax*/, int localClientNum, float* origin) + { + Game::snd_alias_list_t* soundAlias; + if (PB_Enabled()) + { + // Apply effect for bullets only! + std::string soundAliasName = aliasList->aliasName; + if (soundAliasName.starts_with("bullet_")) + soundAlias = Game::cgMediaMod.paintBallImpact; + else + soundAlias = aliasList; + } + else + soundAlias = aliasList; + + Game::CG_PlayClientSoundAlias(soundAlias, localClientNum, origin); + } + + void Paintball::ImpactEffect(const Game::FxEffectDef* fxDef) + { + const Game::FxEffectDef* effect = PB_Enabled() ? GetEffect() : fxDef; + unsigned int weaponIndex = Game::ps->weapon; + Game::CG_PlayBoltedEffect(Game::cgs->localClientNum, + Game::scr_const->tag_knife_fx, + weaponIndex + 2176, + effect); + } + + void Paintball::ImpactMark(int axis/*edx*/, int markEntnum/*ecx*/, const Game::FxEffectDef* fxDef, int msecBegin, const float* origin) + { + const Game::FxEffectDef* effect = PB_Enabled() ? GetEffect() : fxDef; + Game::FX_SpawnOrientedEffect(axis, markEntnum, effect, msecBegin, origin); + } + + void Paintball::ImpactExplosion(const float* origin/*edx*/, int axis/*ecx*/, const Game::FxEffectDef* fxDef, int time) + { + const Game::FxEffectDef* effectExplosive = PB_Enabled() ? GetExplosiveEffect() : fxDef; + Game::DynEntCl_PlayEventFx(origin, axis, effectExplosive, time); + } + + void __declspec(naked) Paintball::CG_PlayClientSoundAliasStub1() + { + const static uint32_t retn_addr = 0x434776; + __asm + { + push eax; + call FireSound; + add esp, 4; + jmp retn_addr; + } + } + + void __declspec(naked) Paintball::CG_PlayClientSoundAliasStub2() + { + const static uint32_t retn_addr = 0x43536B; + __asm + { + push eax; + call ImpactSound; + add esp, 4; + jmp retn_addr; + } + } + + void __declspec(naked) Paintball::CG_PlayBoltedEffectStub() + { + const static uint32_t retn_addr = 0x435510; + __asm + { + call ImpactEffect + jmp retn_addr; + } + } + + void __declspec(naked) Paintball::FX_CreateImpactMarkStub() + { + const static uint32_t retn_addr = 0x435404; + __asm + { + //mov ecx, [esp + 4Ch]; + push ecx; + + //lea edx, [esp + 20h]; + push edx; + + call ImpactMark + add esp, 0x8; + jmp retn_addr; + } + } + + //void DynEntCl_PlayEventFx(const float* origin/*edx*/, int axis/*ecx*/, const Game::FxEffectDef* def, int time) + void __declspec(naked) Paintball::DynEntCl_PlayEventFxStub() + { + const static uint32_t retn_addr = 0x419A0F; + __asm + { + push ecx; + push edx; + call ImpactExplosion; + add esp, 0x8; + jmp retn_addr; + } + } + + Paintball::Paintball() + { + Events::OnDvarInit([] + { + Dvars::paintball_mode = Dvars::Register::Dvar_RegisterBool("paintball_mode", "Enable/Disable the paintball cheat", false, Game::saved_flag); + }); + + Events::RegisterGraphic([] + { + if(Game::HasLoadedMod()) + return; + // Bullets + Game::cgMediaMod.fxPaintBallGreen = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_FX, "impacts/flesh_hit_pball_green").fx; + Game::cgMediaMod.fxPaintBallOrange = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_FX, "impacts/flesh_hit_pball_orange").fx; + Game::cgMediaMod.fxPaintBallBlue = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_FX, "impacts/flesh_hit_pball_blue").fx; + Game::cgMediaMod.fxPaintBallRed = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_FX, "impacts/flesh_hit_pball_red").fx; + Game::cgMediaMod.fxPaintBallYellow = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_FX, "impacts/flesh_hit_pball_yellow").fx; + // Explosion + Game::cgMediaMod.fxPaintBallExpGreen = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_FX, "explosions/exp_paintball_green").fx; + Game::cgMediaMod.fxPaintBallExpOrange = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_FX, "explosions/exp_paintball_orange").fx; + }); + + Events::RegisterSound([] + { + if(Game::HasLoadedMod()) + return; + Game::cgMediaMod.paintBallFirePlayer = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_SOUND, "paintball_fire_plr").sound; + Game::cgMediaMod.paintBallFireNPC = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_SOUND, "paintball_fire_npc").sound; + Game::cgMediaMod.paintBallImpact = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_SOUND, "paintball_impact").sound; + }); + + // Hook the soundalias in 'CG_FireWeapon' + Utils::Hook(0x434771, CG_PlayClientSoundAliasStub1, HOOK_JUMP).install()->quick(); + + // Hook the impact sound effect in 'CG_BulletHitEvent_Internal' + Utils::Hook(0x435366, CG_PlayClientSoundAliasStub2, HOOK_JUMP).install()->quick(); + + // Hook the impact in 'CG_EntityEvent' + Utils::Hook(0x43550B, CG_PlayBoltedEffectStub, HOOK_JUMP).install()->quick(); + + // Hook the impact in 'FX_SpawnOrientedEffect' + Utils::Hook(0x4353FF, FX_CreateImpactMarkStub, HOOK_JUMP).install()->quick(); + + // Hook the explosion impact for grenade launchers, grenades, c4 and claymore in 'CG_EntityEvent' + Utils::Hook(0x419A0A, DynEntCl_PlayEventFxStub, HOOK_JUMP).install()->quick(); + } + + Paintball::~Paintball() + { + } +} \ No newline at end of file diff --git a/src/Components/Modules/Paintball.hpp b/src/Components/Modules/Paintball.hpp new file mode 100644 index 0000000..d2497f3 --- /dev/null +++ b/src/Components/Modules/Paintball.hpp @@ -0,0 +1,26 @@ +#pragma once + +namespace Components +{ + class Paintball : public Component + { + public: + Paintball(); + ~Paintball(); + private: + static bool PB_Enabled(); + static const Game::FxEffectDef* GetEffect(); + static const Game::FxEffectDef* GetExplosiveEffect(); + static void FireSound(Game::snd_alias_list_t* aliasList/*eax*/, int localClientNum, float* origin); + static void ImpactSound(Game::snd_alias_list_t* aliasList/*eax*/, int localClientNum, float* origin); + static void ImpactEffect(const Game::FxEffectDef* fxDef); + static void ImpactMark(int axis/*edx*/, int markEntnum/*ecx*/, const Game::FxEffectDef* fxDef, int msecBegin, const float* origin); + static void ImpactExplosion(const float* origin/*edx*/, int axis/*ecx*/, const Game::FxEffectDef* fxDef, int time); + + static void CG_PlayClientSoundAliasStub1(); + static void CG_PlayClientSoundAliasStub2(); + static void CG_PlayBoltedEffectStub(); + static void FX_CreateImpactMarkStub(); + static void DynEntCl_PlayEventFxStub(); + }; +} \ No newline at end of file diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index 4f77185..02a80b4 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -2,6 +2,7 @@ namespace Game { + Game::cgMedia_t_mod cgMediaMod = {}; Game::gui_t gui = {}; bool HasLoadedMod() diff --git a/src/Game/Game.hpp b/src/Game/Game.hpp index 1a44997..76a2f38 100644 --- a/src/Game/Game.hpp +++ b/src/Game/Game.hpp @@ -9,6 +9,23 @@ namespace Game { + // IW3SP-MOD cgMedia + struct cgMedia_t_mod + { + // Paintball assets + const FxEffectDef* fxPaintBallGreen; + const FxEffectDef* fxPaintBallOrange; + const FxEffectDef* fxPaintBallBlue; + const FxEffectDef* fxPaintBallRed; + const FxEffectDef* fxPaintBallYellow; + const FxEffectDef* fxPaintBallExpGreen; + const FxEffectDef* fxPaintBallExpOrange; + snd_alias_list_t* paintBallFirePlayer; + snd_alias_list_t* paintBallFireNPC; + snd_alias_list_t* paintBallImpact; + }; + + extern Game::cgMedia_t_mod cgMediaMod; extern Game::gui_t gui; bool HasLoadedMod();