Skip to content
Permalink
Browse files

Merge pull request #876 from robootgit/master

Add ragdoll style overrides
  • Loading branch information
BenCat07 committed Nov 23, 2019
2 parents 0603b89 + 19ddd93 commit be1bc9837f3075af28ce875ec13ccb5131751097
Showing with 261 additions and 2 deletions.
  1. +13 −1 data/menu/nullifiedcat/visuals/world.xml
  2. +2 −0 include/core/netvars.hpp
  3. +1 −0 src/core/netvars.cpp
  4. +2 −1 src/hacks/CMakeLists.txt
  5. +243 −0 src/hacks/Ragdolls.cpp
@@ -21,9 +21,21 @@
<List width="180">
<AutoVariable width="fill" target="visual.night-mode" label="Nightmode" min="0" max="100"/>
<AutoVariable width="fill" target="explosionspheres.enabled" label="Draw Explosion Spheres"/>
<LabeledObject width="fill" label="Ragdoll Override">
<Select target="visual.ragdoll-mode">
<Option name="Auto" value="0"/>
<Option name="Gib" value="1"/>
<Option name="Burning" value="2"/>
<Option name="Electrocuted" value="3"/>
<Option name="Ash" value="4"/>
<Option name="Gold" value="5"/>
<Option name="Ice" value="6"/>
</Select>
</LabeledObject>
<AutoVariable width="fill" target="visual.ragdoll-only-local" label="Only Override Local Player Kills"/>
</List>
</Box>
<Box padding="12 6 6 6" width="content" height="content" name="Remove" x="195" y="52">
<Box padding="12 6 6 6" width="content" height="content" name="Remove" x="195" y="85">
<List width="180">
<AutoVariable width="fill" target="remove.arms" label="Remove Arms"/>
<AutoVariable width="fill" target="remove.cloak" label="Remove Cloak"/>
@@ -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;
@@ -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())
{
@@ -52,7 +52,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 "")
@@ -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<bool *>(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<bool *>(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<bool *>(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<bool *>(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<bool *>(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<bool *>(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<RecvPropRedef *>(&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

0 comments on commit be1bc98

Please sign in to comment.
You can’t perform that action at this time.