PHP Code:
#pragma semicolon 1
#include <sourcemod>
#include <sdkhooks>
#include <sdktools>
int iTankClass, iReviver[MAXPLAYERS+1];
bool bIsL4D, bLateLoad;
Handle hBRFHook = null;
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
EngineVersion ev_RetVal = GetEngineVersion();
if (ev_RetVal != Engine_Left4Dead && ev_RetVal != Engine_Left4Dead2)
{
strcopy(error, err_max, "[BRF] Plugin Supports L4D And L4D2 Only!");
return APLRes_SilentFailure;
}
iTankClass = (ev_RetVal == Engine_Left4Dead2) ? 8 : 5;
bIsL4D = (ev_RetVal == Engine_Left4Dead);
bLateLoad = late;
return APLRes_Success;
}
public Plugin myinfo =
{
name = "Bot Revive Fix",
author = "BHaType, cravenge",
description = "Prevent Survivor bots from reviving others the same way a human player attempts to",
version = "1.0",
url = "https://forums.alliedmods.net/showpost.php?p=2753866&postcount=16"
};
public void OnPluginStart()
{
GameData gd_BRF = FetchGameData("bot_revive_fix");
if (gd_BRF == null)
{
SetFailState("[BRF] Game Data Not Found!");
}
StartPrepSDKCall(SDKCall_Player);
PrepSDKCall_SetFromConf(gd_GivenVal, SDKConf_Signature, "CTerrorPlayer::StopBeingRevived");
PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain);
hBRFHook = EndPrepSDKCall();
if (hBRFHook == null)
{
SetFailState("[BRF] Signature \"CTerrorPlayer::StopBeingRevived\" Broken!");
}
delete gd_BRF;
HookEvent("revive_begin", OnReviveBeginPre, EventHookMode_Pre);
HookEvent("revive_end", OnReviveEvents);
HookEvent("revive_success", OnReviveEvents);
if (bLateLoad)
{
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
{
OnClientPutInServer(i);
}
}
}
}
public void OnClientPutInServer(int client)
{
//Since the signature is called in here originally, it's best to call it during the time frame where the damage was already dealt and the "player_hurt" event hasn't been fired yet
SDKHook(client, SDKHook_OnTakeDamageAlivePost, OnTakeDamageAlive_Post);
}
public void OnTakeDamageAlive_Post(int victim, int attacker, int inflictor, float damage, int damagetype)
{
if ((GetClientTeam(victim) != 2 && GetClientTeam(victim) != 4) || !GetEntProp(victim, Prop_Send, "m_isIncapacitated", 1))
{
return;
}
if (inflictor > 0 && IsValidEdict(inflictor))
{
//Exclude fire (and spit puddles for L4D2) from stopping bots from reviving
char sInflictorClass[64];
GetEdictClassname(inflictor, sInflictorClass, sizeof(sInflictorClass));
if (strcmp(sInflictorClass, "inferno") == 0 || (!bIsL4D && (strcmp(sInflictorClass, "fire_cracker_blast") == 0 || strcmp(sInflictorClass, "insect_swarm") == 0)))
{
return;
}
//Include the exact cases where human players will be interrupted from reviving + support for external damages (i.e Smoker Cloud Damage)
if (IsCommonInfected(attacker) || IsWitch(attacker) || IsBigBadInfected(attacker, true) ||
(damagetype & DMG_NERVEGAS))
{
//Why not GetEntPropEnt(victim, Prop_Send, "m_reviveOwner")? It's cuz we need to determine if the one reviving the victim is a bot or not
int iReviveOwner = GetClientOfUserId(iReviver[victim]);
iReviver[victim] = 0;
if (!IsValidClient(iReviveOwner) || (GetClientTeam(iReviveOwner) != 2 && GetClientTeam(iReviveOwner) != 4) || !IsPlayerAlive(iReviveOwner) ||
(!IsFakeClient(iReviveOwner) && ((damagetype & DMG_NERVEGAS) || !IsBigBadInfected(attacker, false))))
{
return;
}
SDKCall(hBRFHook, victim, true);
}
}
}
public void OnReviveBeginPre(Event event, const char[] name, bool dontBroadcast)
{
int revived = GetClientOfUserId(event.GetInt("subject"));
if (!revived)
{
return;
}
int reviverID = event.GetInt("userid");
if (iReviver[revived] == 0 || reviverID != iReviver[revived])
{
iReviver[revived] = reviverID;
}
}
public void OnReviveEvents(Event event, const char[] name, bool dontBroadcast)
{
int revived = GetClientOfUserId(event.GetInt("subject"));
if (!revived)
{
return;
}
if (iReviver[revived] != 0)
{
iReviver[revived] = 0;
}
}
GameData FetchGameData(const char[] file)
{
char sFilePath[PLATFORM_MAX_PATH];
BuildPath(Path_SM, sFilePath, sizeof(sFilePath), "gamedata/%s.txt", file);
if (!FileExists(sFilePath))
{
File fileTemp = OpenFile(sFilePath, "w");
if (fileTemp == null)
{
SetFailState("[BRF] Game Data Creation Aborted!");
}
fileTemp.WriteLine("Games");
fileTemp.WriteLine("{");
fileTemp.WriteLine(" \"#default\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"Signatures\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"CTerrorPlayer::StopBeingRevived\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"library\" \"server\"");
fileTemp.WriteLine(" \"linux\" \"@_ZN13CTerrorPlayer16StopBeingRevivedEb\"");
fileTemp.WriteLine(" }");
fileTemp.WriteLine(" }");
fileTemp.WriteLine(" }");
fileTemp.WriteLine(" \"left4dead\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"Signatures\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"CTerrorPlayer::StopBeingRevived\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"library\" \"server\"");
fileTemp.WriteLine(" \"windows\" \"\\x55\\x56\\x57\\x68\\x2A\\x2A\\x2A\\x2A\\x6A\\x00\\x8B\\xE9\\xE8\\x2A\\x2A\\x2A\\x2A\\x8B\\x85\\x7C\\x1B\\x00\\x00\"");
fileTemp.WriteLine(" /* 55 56 57 68 ? ? ? ? 6A 00 8B E9 E8 ? ? ? ? 8B 85 7C 1B 00 00 */");
fileTemp.WriteLine(" }");
fileTemp.WriteLine(" }");
fileTemp.WriteLine(" }");
fileTemp.WriteLine(" \"left4dead2\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"Signatures\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"CTerrorPlayer::StopBeingRevived\"");
fileTemp.WriteLine(" {");
fileTemp.WriteLine(" \"library\" \"server\"");
fileTemp.WriteLine(" \"windows\" \"\\x55\\x8B\\xEC\\xD9\\xEE\\x56\\x57\\x51\"");
fileTemp.WriteLine(" /* 55 8B EC D9 EE 56 57 51 */");
fileTemp.WriteLine(" }");
fileTemp.WriteLine(" }");
fileTemp.WriteLine(" }");
fileTemp.WriteLine("}");
fileTemp.Close();
}
return new GameData(file);
}
stock bool IsValidClient(int client)
{
return (client > 0 && client <= MaxClients && IsClientInGame(client) && !IsClientSourceTV(client) && !IsClientReplay(client));
}
stock bool IsBigBadInfected(int client, bool includeBoss)
{
if (!IsValidClient(client) || GetClientTeam(client) != 3)
{
return false;
}
int iZombieClass = GetEntProp(client, Prop_Send, "m_zombieClass");
return (iZombieClass == 3 || (includeBoss && iZombieClass == iTankClass) || (!bIsL4D && iZombieClass == 6));
}
stock bool IsCommonInfected(int entity)
{
if (entity < 1 || !IsValidEntity(entity))
{
return false;
}
char sEntityClass[64];
GetEntityClassname(entity, sEntityClass, sizeof(sEntityClass));
return (strcmp(sEntityClass, "infected") == 0);
}
stock bool IsWitch(int entity)
{
if (entity < 1 || !IsValidEntity(entity))
{
return false;
}
char sEntityClass[64];
GetEntityClassname(entity, sEntityClass, sizeof(sEntityClass));
return (strcmp(sEntityClass, "witch") == 0);
}