diff --git a/data/menu/nullifiedcat/visuals/world.xml b/data/menu/nullifiedcat/visuals/world.xml index cf2a45d90..19948764e 100755 --- a/data/menu/nullifiedcat/visuals/world.xml +++ b/data/menu/nullifiedcat/visuals/world.xml @@ -21,9 +21,21 @@ + + + + - + diff --git a/include/core/netvars.hpp b/include/core/netvars.hpp index 927402a41..71f6f62aa 100644 --- a/include/core/netvars.hpp +++ b/include/core/netvars.hpp @@ -174,6 +174,8 @@ class NetVars offset_t m_iHealing_Resource; offset_t m_iHealingAssist_Resource; offset_t m_iPlayerLevel_Resource; + + offset_t m_iPlayerIndex; }; extern NetVars netvar; diff --git a/src/core/netvars.cpp b/src/core/netvars.cpp index 7a1522aa1..3a57e36ab 100644 --- a/src/core/netvars.cpp +++ b/src/core/netvars.cpp @@ -122,6 +122,7 @@ void NetVars::Init() this->m_iHealing_Resource = gNetvars.get_offset("DT_TFPlayerResource", "m_iHealing"); this->m_iHealingAssist_Resource = gNetvars.get_offset("DT_TFPlayerResource", "m_iHealingAssist"); this->m_iPlayerLevel_Resource = gNetvars.get_offset("DT_TFPlayerResource", "m_iPlayerLevel"); + this->m_iPlayerIndex = gNetvars.get_offset("DT_TFRagdoll", "m_iPlayerIndex"); } IF_GAME(IsTF2C()) { diff --git a/src/hacks/CMakeLists.txt b/src/hacks/CMakeLists.txt index 2457c3790..3aed1141e 100755 --- a/src/hacks/CMakeLists.txt +++ b/src/hacks/CMakeLists.txt @@ -51,7 +51,8 @@ if(EnableVisuals) "${CMAKE_CURRENT_LIST_DIR}/SkinChanger.cpp" "${CMAKE_CURRENT_LIST_DIR}/SpyAlert.cpp" "${CMAKE_CURRENT_LIST_DIR}/Thirdperson.cpp" - "${CMAKE_CURRENT_LIST_DIR}/MCHealthbar.cpp") + "${CMAKE_CURRENT_LIST_DIR}/MCHealthbar.cpp" + "${CMAKE_CURRENT_LIST_DIR}/Ragdolls.cpp") target_sources(cathook PRIVATE ${files}) list(REMOVE_ITEM ignore_files ${files}) set(ignore_files ${ignore_files} CACHE INTERNAL "") diff --git a/src/hacks/Ragdolls.cpp b/src/hacks/Ragdolls.cpp new file mode 100644 index 000000000..e16fbeb46 --- /dev/null +++ b/src/hacks/Ragdolls.cpp @@ -0,0 +1,243 @@ +/* + * Ragdolls.cpp + * + * Created on: Nov 6, 2019 + * Author: Roboot + */ + +#include "common.hpp" +#include "sdk/dt_recv_redef.h" + +namespace hacks::shared::ragdolls +{ + +static settings::Int mode{ "visual.ragdoll-mode", "0" }; +static settings::Boolean only_local{ "visual.ragdoll-only-local", "1" }; + +/** + * Simple helper class for swapping out a RecvVarProxyFn + * and restoring it later on. + * You MUST call init(...) before calling setHook() or restore() + */ +class ProxyFnHook +{ +public: + ProxyFnHook() : hooked(false) + { + } + void init(RecvPropRedef *prop) + { + this->prop = prop; + } + void setHook(RecvVarProxyFn new_fn) + { + if (!prop || hooked) + return; + hooked = true; + original_fn = prop->m_ProxyFn; + prop->m_ProxyFn = new_fn; + } + void restore() + { + if (prop && hooked) + { + prop->m_ProxyFn = original_fn; + hooked = false; + } + } + +private: + bool hooked; + RecvPropRedef *prop; + RecvVarProxyFn original_fn; +}; + +// Ragdoll override style +enum RagdollOverride_t +{ + NONE = 0, + GIB = 1, + BURNING = 2, + ELECTROCUTED = 3, + ASH = 4, + GOLD = 5, + ICE = 6 +}; + +ProxyFnHook gib_hook; +ProxyFnHook burn_hook; +ProxyFnHook electro_hook; +ProxyFnHook ash_hook; +ProxyFnHook gold_hook; +ProxyFnHook ice_hook; + +/** + * Check to see if a ragdoll belongs to a player killed by the local player + */ +bool ragdollKillByLocal(void *ragdoll) +{ + // Get the owner of the ragdoll (TFPlayer) + auto owner = g_IEntityList->GetClientEntity(NET_INT(ragdoll, netvar.m_iPlayerIndex)); + if (!owner || owner->IsDormant()) + { + return false; + } + // Make sure this isn't the own player's ragdoll + // The player will spectate iteself when the player dies via suicide + if (owner->entindex() == g_pLocalPlayer->entity_idx) + { + return false; + } + // Check to see if the owner is spectating the local player + auto owner_observer = g_IEntityList->GetClientEntityFromHandle(NET_VAR(owner, netvar.hObserverTarget, CBaseHandle)); + if (!owner_observer || owner_observer->IsDormant()) + { + return false; + } + return owner_observer->entindex() == g_pLocalPlayer->entity_idx; +} + +/** + * Called for m_bGib + */ +void overrideGib(const CRecvProxyData *data, void *structure, void *out) +{ + auto gib = reinterpret_cast(out); + if (*mode == RagdollOverride_t::GIB && (!*only_local || ragdollKillByLocal(structure))) + *gib = true; + else + *gib = data->m_Value.m_Int; +} + +/** + * Called for m_bBurning + */ +void overrideBurning(const CRecvProxyData *data, void *structure, void *out) +{ + auto burning = reinterpret_cast(out); + if (*mode == RagdollOverride_t::BURNING && (!*only_local || ragdollKillByLocal(structure))) + *burning = true; + else + *burning = data->m_Value.m_Int; +} + +/** + * Called for m_bElectrocuted + */ +void overrideElectrocuted(const CRecvProxyData *data, void *structure, void *out) +{ + auto electrocuted = reinterpret_cast(out); + if (*mode == RagdollOverride_t::ELECTROCUTED && (!*only_local || ragdollKillByLocal(structure))) + *electrocuted = true; + else + *electrocuted = data->m_Value.m_Int; +} + +/** + * Called for m_bBecomeAsh + */ +void overrideAsh(const CRecvProxyData *data, void *structure, void *out) +{ + auto ash = reinterpret_cast(out); + if (*mode == RagdollOverride_t::ASH && (!*only_local || ragdollKillByLocal(structure))) + *ash = true; + else + *ash = data->m_Value.m_Int; +} + +/** + * Called for m_bGoldRagdoll + */ +void overrideGold(const CRecvProxyData *data, void *structure, void *out) +{ + auto gold = reinterpret_cast(out); + if (*mode == RagdollOverride_t::GOLD && (!*only_local || ragdollKillByLocal(structure))) + *gold = true; + else + *gold = data->m_Value.m_Int; +} + +/** + * Called for m_bIceRagdoll + */ +void overrideIce(const CRecvProxyData *data, void *structure, void *out) +{ + auto ice = reinterpret_cast(out); + if (*mode == RagdollOverride_t::ICE && (!*only_local || ragdollKillByLocal(structure))) + *ice = true; + else + *ice = data->m_Value.m_Int; +} + +/** + * Swap out the RecvVarProxyFns for TFRagdoll style props + */ +void hook() +{ + for (auto dt_class = g_IBaseClient->GetAllClasses(); dt_class; dt_class = dt_class->m_pNext) + { + auto table = dt_class->m_pRecvTable; + + if (strcmp(table->m_pNetTableName, "DT_TFRagdoll") == 0) + { + for (int i = 0; i < table->m_nProps; ++i) + { + auto prop = reinterpret_cast(&table->m_pProps[i]); + if (prop == nullptr) + continue; + + auto prop_name = prop->m_pVarName; + if (strcmp(prop_name, "m_bGib") == 0) + { + gib_hook.init(prop); + gib_hook.setHook(overrideGib); + } + else if (strcmp(prop_name, "m_bBurning") == 0) + { + burn_hook.init(prop); + burn_hook.setHook(overrideBurning); + } + else if (strcmp(prop_name, "m_bElectrocuted") == 0) + { + electro_hook.init(prop); + electro_hook.setHook(overrideElectrocuted); + } + else if (strcmp(prop_name, "m_bBecomeAsh") == 0) + { + ash_hook.init(prop); + ash_hook.setHook(overrideAsh); + } + else if (strcmp(prop_name, "m_bGoldRagdoll") == 0) + { + gold_hook.init(prop); + gold_hook.setHook(overrideGold); + } + else if (strcmp(prop_name, "m_bIceRagdoll") == 0) + { + ice_hook.init(prop); + ice_hook.setHook(overrideIce); + } + } + } + } +} + +/** + * Restore the RecvVarProxyFns that were swapped out earlier + */ +void unhook() +{ + gib_hook.restore(); + burn_hook.restore(); + electro_hook.restore(); + ash_hook.restore(); + gold_hook.restore(); + ice_hook.restore(); +} + +static InitRoutine init([]() { + hook(); + EC::Register(EC::Shutdown, unhook, "ragdoll_shutdown"); +}); + +} // namespace hacks::shared::ragdolls