Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ragdoll style overrides #876

Merged
merged 4 commits into from Nov 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 13 additions & 1 deletion data/menu/nullifiedcat/visuals/world.xml
Expand Up @@ -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"/>
Expand Down
2 changes: 2 additions & 0 deletions include/core/netvars.hpp
Expand Up @@ -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;
1 change: 1 addition & 0 deletions src/core/netvars.cpp
Expand Up @@ -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())
{
Expand Down
3 changes: 2 additions & 1 deletion src/hacks/CMakeLists.txt
Expand Up @@ -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 "")
Expand Down
243 changes: 243 additions & 0 deletions 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
BenCat07 marked this conversation as resolved.
Show resolved Hide resolved
{
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
BenCat07 marked this conversation as resolved.
Show resolved Hide resolved
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