Ported new TF2 critical hit detection logic from sm-central (bug 5894).

--HG--
extra : rebase_source : 687edebc87d34924fdb7c70924a2910389dedd47
This commit is contained in:
Nicholas Hastings 2013-10-03 10:44:53 -04:00
parent 46d71790be
commit c82f2b4659
5 changed files with 227 additions and 195 deletions

View File

@ -30,217 +30,171 @@
*/
#include "criticals.h"
#include "util.h"
IServerGameEnts *gameents = NULL;
CDetour *calcIsAttackCriticalDetour = NULL;
CDetour *calcIsAttackCriticalMeleeDetour = NULL;
CDetour *calcIsAttackCriticalBowDetour = NULL;
CritManager g_CritManager;
IForward *g_critForward = NULL;
enum DetourResult
SH_DECL_MANUALHOOK0(CalcIsAttackCriticalHelper, 0, 0, 0, bool);
SH_DECL_MANUALHOOK0(CalcIsAttackCriticalHelperNoCrits, 0, 0, 0, bool);
const char TF_WEAPON_DATATABLE[] = "DT_TFWeaponBase";
CritManager::CritManager() :
m_enabled(false),
m_hooksSetup(false)
{
Result_Ignore,
Result_NoCrit,
Result_Crit,
};
int CheckBaseHandle(CBaseHandle &hndl)
{
if (!hndl.IsValid())
{
return -1;
}
int index = hndl.GetEntryIndex();
edict_t *pStoredEdict;
pStoredEdict = engine->PEntityOfEntIndex(index);
if (pStoredEdict == NULL)
{
return -1;
}
IServerEntity *pSE = pStoredEdict->GetIServerEntity();
if (pSE == NULL)
{
return -1;
}
if (pSE->GetRefEHandle() != hndl)
{
return -1;
}
return index;
m_entsHooked.Init();
}
DetourResult DetourCallback(CBaseEntity *pEnt)
bool CritManager::TryEnable()
{
edict_t *pEdict = gameents->BaseEntityToEdict((CBaseEntity *)pEnt);
if (!pEdict)
if (!m_hooksSetup)
{
g_pSM->LogMessage(myself, "Entity Error");
return Result_Ignore;
int offset;
if (!g_pGameConf->GetOffset("CalcIsAttackCriticalHelper", &offset))
{
g_pSM->LogError(myself, "Failed to find CalcIsAttackCriticalHelper offset");
return false;
}
SH_MANUALHOOK_RECONFIGURE(CalcIsAttackCriticalHelper, offset, 0, 0);
if (!g_pGameConf->GetOffset("CalcIsAttackCriticalHelperNoCrits", &offset))
{
g_pSM->LogError(myself, "Failed to find CalcIsAttackCriticalHelperNoCrits offset");
return false;
}
SH_MANUALHOOK_RECONFIGURE(CalcIsAttackCriticalHelperNoCrits, offset, 0, 0);
m_hooksSetup = true;
}
for (size_t i = playerhelpers->GetMaxClients() + 1; i < MAX_EDICTS; ++i)
{
CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i);
if (pEntity == NULL)
continue;
IServerUnknown *pUnknown = (IServerUnknown *)pEntity;
IServerNetworkable *pNetworkable = pUnknown->GetNetworkable();
if (!pNetworkable)
continue;
if (!UTIL_ContainsDataTable(pNetworkable->GetServerClass()->m_pTable, TF_WEAPON_DATATABLE))
continue;
SH_ADD_MANUALHOOK(CalcIsAttackCriticalHelper, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelpers), false);
SH_ADD_MANUALHOOK(CalcIsAttackCriticalHelperNoCrits, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelpers), false);
m_entsHooked.Set(i);
}
m_enabled = true;
return true;
}
void CritManager::Disable()
{
int i = m_entsHooked.FindNextSetBit(playerhelpers->GetMaxClients() + 1);
for (i; i != -1; i = m_entsHooked.FindNextSetBit(i))
{
CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i);
SH_REMOVE_MANUALHOOK(CalcIsAttackCriticalHelper, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelpers), false);
SH_REMOVE_MANUALHOOK(CalcIsAttackCriticalHelperNoCrits, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelpers), false);
m_entsHooked.Set(i, false);
}
m_enabled = false;
}
void CritManager::OnEntityCreated(CBaseEntity *pEntity, const char *classname)
{
if (!m_enabled)
return;
IServerUnknown *pUnknown = (IServerUnknown *)pEntity;
IServerNetworkable *pNetworkable = pUnknown->GetNetworkable();
if (!pNetworkable)
return;
if (!UTIL_ContainsDataTable(pNetworkable->GetServerClass()->m_pTable, TF_WEAPON_DATATABLE))
return;
SH_ADD_MANUALHOOK(CalcIsAttackCriticalHelper, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelpers), false);
SH_ADD_MANUALHOOK(CalcIsAttackCriticalHelperNoCrits, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelpers), false);
m_entsHooked.Set(gamehelpers->EntityToBCompatRef(pEntity));
}
void CritManager::OnEntityDestroyed(CBaseEntity *pEntity)
{
if (!m_enabled)
return;
int index = gamehelpers->EntityToBCompatRef(pEntity);
if (index < 0 || index >= MAX_EDICTS)
return;
if (!m_entsHooked.IsBitSet(index))
return;
SH_REMOVE_MANUALHOOK(CalcIsAttackCriticalHelper, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelpers), false);
SH_REMOVE_MANUALHOOK(CalcIsAttackCriticalHelperNoCrits, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelpers), false);
m_entsHooked.Set(index, false);
}
bool CritManager::Hook_CalcIsAttackCriticalHelpers()
{
CBaseEntity *pWeapon = META_IFACEPTR(CBaseEntity);
// If there's an invalid ent or invalid networkable here, we've got issues elsewhere.
IServerNetworkable *pNetWeapon = ((IServerUnknown *)pWeapon)->GetNetworkable();
ServerClass *pServerClass = pNetWeapon->GetServerClass();
if (!pServerClass)
{
g_pSM->LogError(myself, "Invalid server class on weapon.");
RETURN_META_VALUE(MRES_IGNORED, false);
}
sm_sendprop_info_t info;
if (!gamehelpers->FindSendPropInfo(pEdict->GetNetworkable()->GetServerClass()->GetName(), "m_hOwnerEntity", &info))
if (!gamehelpers->FindSendPropInfo(pServerClass->GetName(), "m_hOwnerEntity", &info))
{
g_pSM->LogMessage(myself, "Offset Error");
return Result_Ignore;
g_pSM->LogError(myself, "Could not find m_hOwnerEntity on %s", pServerClass->GetName());
RETURN_META_VALUE(MRES_IGNORED, false);
}
if (!g_critForward)
int returnValue = 0;
int ownerIndex = -1;
CBaseHandle &hndl = *(CBaseHandle *) ((intptr_t)pWeapon + info.actual_offset);
CBaseEntity *pHandleEntity = gamehelpers->ReferenceToEntity(hndl.GetEntryIndex());
if (pHandleEntity != NULL && hndl == reinterpret_cast<IHandleEntity *>(pHandleEntity)->GetRefEHandle())
{
g_pSM->LogMessage(myself, "Invalid Forward");
return Result_Ignore;
ownerIndex = hndl.GetEntryIndex();
}
int returnValue=0;
CBaseHandle &hndl = *(CBaseHandle *)((uint8_t *)pEnt + info.actual_offset);
int index = CheckBaseHandle(hndl);
g_critForward->PushCell(index); //Client index
g_critForward->PushCell(engine->IndexOfEdict(pEdict)); // Weapon index
g_critForward->PushString(pEdict->GetClassName()); //Weapon classname
g_critForward->PushCell(ownerIndex); //Client index
g_critForward->PushCell(gamehelpers->EntityToBCompatRef(pWeapon)); // Weapon index
g_critForward->PushString(gamehelpers->GetEntityClassname(pWeapon)); //Weapon classname
g_critForward->PushCellByRef(&returnValue); //return value
cell_t result = 0;
g_critForward->Execute(&result);
if (result)
if (result && returnValue)
{
if (returnValue)
{
return Result_Crit;
}
else
{
return Result_NoCrit;
}
}
else
{
return Result_Ignore;
}
}
DETOUR_DECL_MEMBER0(CalcIsAttackCriticalHelperMelee, bool)
{
DetourResult result = DetourCallback((CBaseEntity *)this);
if (result == Result_Ignore)
{
return DETOUR_MEMBER_CALL(CalcIsAttackCriticalHelperMelee)();
}
else if (result == Result_NoCrit)
{
return 0;
}
else
{
return 1;
}
}
DETOUR_DECL_MEMBER0(CalcIsAttackCriticalHelper, bool)
{
DetourResult result = DetourCallback((CBaseEntity *)this);
if (result == Result_Ignore)
{
return DETOUR_MEMBER_CALL(CalcIsAttackCriticalHelper)();
}
else if (result == Result_NoCrit)
{
return 0;
}
else
{
return 1;
}
}
DETOUR_DECL_MEMBER0(CalcIsAttackCriticalHelperBow, bool)
{
DetourResult result = DetourCallback((CBaseEntity *)this);
if (result == Result_Ignore)
{
return DETOUR_MEMBER_CALL(CalcIsAttackCriticalHelperBow)();
}
else if (result == Result_NoCrit)
{
return 0;
}
else
{
return 1;
}
}
bool InitialiseCritDetours()
{
calcIsAttackCriticalDetour = DETOUR_CREATE_MEMBER(CalcIsAttackCriticalHelper, "CalcCritical");
calcIsAttackCriticalMeleeDetour = DETOUR_CREATE_MEMBER(CalcIsAttackCriticalHelperMelee, "CalcCriticalMelee");
calcIsAttackCriticalBowDetour = DETOUR_CREATE_MEMBER(CalcIsAttackCriticalHelperBow, "CalcCriticalBow");
bool HookCreated = false;
if (calcIsAttackCriticalDetour != NULL)
{
calcIsAttackCriticalDetour->EnableDetour();
HookCreated = true;
}
if (calcIsAttackCriticalMeleeDetour != NULL)
{
calcIsAttackCriticalMeleeDetour->EnableDetour();
HookCreated = true;
}
if (calcIsAttackCriticalBowDetour != NULL)
{
calcIsAttackCriticalBowDetour->EnableDetour();
HookCreated = true;
}
if (HookCreated)
{
return true;
}
g_pSM->LogError(myself, "No critical hit forwards could be initialized - Disabled critical hit hooks");
return false;
}
void RemoveCritDetours()
{
if (calcIsAttackCriticalDetour != NULL)
{
calcIsAttackCriticalDetour->Destroy();
calcIsAttackCriticalDetour = NULL;
}
if (calcIsAttackCriticalMeleeDetour != NULL)
{
calcIsAttackCriticalMeleeDetour->Destroy();
calcIsAttackCriticalMeleeDetour = NULL;
}
if (calcIsAttackCriticalBowDetour != NULL)
{
calcIsAttackCriticalBowDetour->Destroy();
calcIsAttackCriticalBowDetour = NULL;
RETURN_META_VALUE(MRES_SUPERCEDE, true);
}
RETURN_META_VALUE(MRES_IGNORED, false);
}

View File

@ -36,12 +36,38 @@
#include <jit/jit_helpers.h>
#include <jit/x86/x86_macros.h>
#include "CDetour/detours.h"
#include "ISDKHooks.h"
bool InitialiseCritDetours();
void RemoveCritDetours();
class CritManager : public ISMEntityListener
{
public:
CritManager();
public:
bool TryEnable();
void Disable();
// ISMEntityListener
public:
virtual void OnEntityCreated(CBaseEntity *pEntity, const char *classname);
virtual void OnEntityDestroyed(CBaseEntity *pEntity);
private:
void HookEntityIfWeapon(CBaseEntity *pEntity);
void UnhookEntityIfHooked(CBaseEntity *pEntity);
public:
// CritHook
bool Hook_CalcIsAttackCriticalHelpers();
private:
bool m_enabled;
bool m_hooksSetup;
CBitVec<MAX_EDICTS> m_entsHooked;
};
extern CritManager g_CritManager;
extern IForward *g_critForward;
extern IServerGameEnts *gameents;
#endif //_INCLUDE_SOURCEMOD_CRITICALS_H_

View File

@ -41,6 +41,7 @@
#include "gameplayrules.h"
#include "teleporter.h"
#include "CDetour/detours.h"
#include "ISDKHooks.h"
/**
* @file extension.cpp
@ -52,6 +53,7 @@ TF2Tools g_TF2Tools;
IGameConfig *g_pGameConf = NULL;
IBinTools *g_pBinTools = NULL;
ISDKHooks *g_pSDKHooks = NULL;
SMEXT_LINK(&g_TF2Tools);
@ -91,6 +93,7 @@ bool TF2Tools::SDK_OnLoad(char *error, size_t maxlength, bool late)
}
sharesys->AddDependency(myself, "bintools.ext", true, true);
sharesys->AddDependency(myself, "sdkhooks.ext", true, true);
char conf_error[255] = "";
if (!gameconfs->LoadGameConfigFile("sm-tf2.games", &g_pGameConf, conf_error, sizeof(conf_error)))
@ -151,8 +154,6 @@ bool TF2Tools::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool
GET_V_IFACE_CURRENT(GetEngineFactory, icvar, ICvar, CVAR_INTERFACE_VERSION);
GET_V_IFACE_CURRENT(GetServerFactory, gameents, IServerGameEnts, INTERFACEVERSION_SERVERGAMEENTS);
GET_V_IFACE_CURRENT(GetEngineFactory, m_GameEventManager, IGameEventManager2, INTERFACEVERSION_GAMEEVENTSMANAGER2);
m_GameEventManager->AddListener(this, "teamplay_restart_round", true);
@ -177,11 +178,22 @@ void TF2Tools::SDK_OnUnload()
forwards->ReleaseForward(g_waitingPlayersStartForward);
forwards->ReleaseForward(g_waitingPlayersEndForward);
forwards->ReleaseForward(g_teleportForward);
if (g_pSDKHooks != NULL)
{
g_pSDKHooks->RemoveEntityListener(&g_CritManager);
}
}
void TF2Tools::SDK_OnAllLoaded()
{
SM_GET_LATE_IFACE(BINTOOLS, g_pBinTools);
SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks);
if (g_pSDKHooks != NULL)
{
g_pSDKHooks->AddEntityListener(&g_CritManager);
}
}
bool TF2Tools::RegisterConCommandBase(ConCommandBase *pVar)
@ -198,6 +210,7 @@ void TF2Tools::FireGameEvent( IGameEvent *event )
bool TF2Tools::QueryRunning(char *error, size_t maxlength)
{
SM_CHECK_IFACE(BINTOOLS, g_pBinTools);
SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks);
return true;
}
@ -209,6 +222,11 @@ bool TF2Tools::QueryInterfaceDrop(SMInterface *pInterface)
return false;
}
if (pInterface == g_pSDKHooks)
{
return false;
}
return IExtensionInterface::QueryInterfaceDrop(pInterface);
}
@ -321,7 +339,7 @@ void TF2Tools::OnPluginLoaded(IPlugin *plugin)
{
if (!m_CritDetoursEnabled && g_critForward->GetFunctionCount())
{
m_CritDetoursEnabled = InitialiseCritDetours();
m_CritDetoursEnabled = g_CritManager.TryEnable();
}
if (!m_IsHolidayDetourEnabled && g_isHolidayForward->GetFunctionCount())
@ -353,7 +371,7 @@ void TF2Tools::OnPluginUnloaded(IPlugin *plugin)
{
if (m_CritDetoursEnabled && !g_critForward->GetFunctionCount())
{
RemoveCritDetours();
g_CritManager.Disable();
m_CritDetoursEnabled = false;
}
if (m_IsHolidayDetourEnabled && !g_isHolidayForward->GetFunctionCount())

View File

@ -88,6 +88,38 @@ bool UTIL_FindDataTable(SendTable *pTable,
return false;
}
bool UTIL_ContainsDataTable(SendTable *pTable, const char *name)
{
const char *pname = pTable->GetName();
int props = pTable->GetNumProps();
SendProp *prop;
SendTable *table;
if (pname && strcmp(name, pname) == 0)
return true;
for (int i = 0; i < props; i++)
{
prop = pTable->GetProp(i);
if ((table = prop->GetDataTable()) != NULL)
{
pname = table->GetName();
if (pname && strcmp(name, pname) == 0)
{
return true;
}
if (UTIL_ContainsDataTable(table, name))
{
return true;
}
}
}
return false;
}
ServerClass *UTIL_FindServerClass(const char *classname)
{
ServerClass *sc = gamedll->GetAllServerClasses();

View File

@ -49,6 +49,8 @@ bool UTIL_FindDataTable(SendTable *pTable,
sm_sendprop_info_t *info,
unsigned int offset);
bool UTIL_ContainsDataTable(SendTable *pTable, const char *name);
ServerClass *UTIL_FindServerClass(const char *classname);
CBaseEntity *UTIL_GetCBaseEntity(int num, bool onlyPlayers);