2081: Add support for dynamically loading an antibot module r=Learath2 a=heinrich5991

Support for this feature is turned off by default. This feature allows
to integrate with noby's anticheat feature.

Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
This commit is contained in:
bors[bot] 2020-03-13 17:08:34 +00:00 committed by GitHub
commit fdf7e4e918
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 396 additions and 0 deletions

1
.gitignore vendored
View file

@ -98,6 +98,7 @@ tags
*.res
*.sdf
*.sln
*.so
*.suo
*.swp
*.tar.gz

View file

@ -80,6 +80,7 @@ option(WEBSOCKETS "Enable websockets support" OFF)
option(MYSQL "Enable mysql support" OFF)
option(AUTOUPDATE "Enable the autoupdater" ${AUTOUPDATE_DEFAULT})
option(VIDEORECORDER "Enable video recording support via FFmpeg" OFF)
option(ANTIBOT "Enable support for a dynamic anticheat library" OFF)
option(CLIENT "Compile client" ON)
option(DOWNLOAD_GTEST "Download and compile GTest" ${AUTO_DEPENDENCIES_DEFAULT})
option(PREFER_BUNDLED_LIBS "Prefer bundled libraries over system libraries" ${AUTO_DEPENDENCIES_DEFAULT})
@ -1034,6 +1035,12 @@ endif()
########################################################################
# Sources
set_src(ANTIBOT_SRC GLOB src/antibot
antibot_data.h
antibot_interface.h
antibot_null.cpp
)
set_src(ENGINE_SERVER GLOB src/engine/server
authmanager.cpp
authmanager.h
@ -1051,6 +1058,8 @@ set_src(ENGINE_SERVER GLOB src/engine/server
sql_string_helpers.h
)
set_src(GAME_SERVER GLOB_RECURSE src/game/server
antibot.cpp
antibot.h
ddracechat.cpp
ddracechat.h
ddracecommands.cpp
@ -1112,10 +1121,18 @@ else()
set(SERVER_ICON)
endif()
# Antibot
if(ANTIBOT)
set(TARGET_ANTIBOT antibot)
add_library(${TARGET_ANTIBOT} SHARED ${ANTIBOT_SRC})
list(APPEND TARGETS_OWN ${TARGET_ANTIBOT})
endif()
# Libraries
set(LIBS_SERVER
${LIBS}
${MYSQL_LIBRARIES}
${TARGET_ANTIBOT}
# Add pthreads (on non-Windows) at the end, so that other libraries can depend
# on it.
${CMAKE_THREAD_LIBS_INIT}
@ -1687,6 +1704,9 @@ foreach(target ${TARGETS_OWN})
if(VIDEORECORDER)
target_compile_definitions(${target} PRIVATE CONF_VIDEORECORDER)
endif()
if(ANTIBOT)
target_compile_definitions(${target} PRIVATE CONF_ANTIBOT)
endif()
if(MYSQL)
target_compile_definitions(${target} PRIVATE CONF_SQL)
target_include_directories(${target} PRIVATE ${MYSQL_INCLUDE_DIRS})

View file

@ -0,0 +1,59 @@
#ifndef ANTIBOT_ANTIBOT_DATA_H
#define ANTIBOT_ANTIBOT_DATA_H
#include <base/vmath.h>
enum
{
ANTIBOT_MAX_CLIENTS=64,
};
struct CAntibotMapData
{
int m_Width;
int m_Height;
unsigned char *m_pTiles;
};
struct CAntibotInputData
{
int m_TargetX;
int m_TargetY;
};
// Defined by the network protocol, unlikely to change.
//enum
//{
//TEAM_SPECTATORS=-1,
//TEAM_RED=0,
//TEAM_BLUE=1,
//};
struct CAntibotCharacterData
{
char m_aName[16];
CAntibotInputData m_aLatestInputs[3];
bool m_Alive;
bool m_Pause;
int m_Team;
vec2 m_Pos;
vec2 m_Vel;
int m_Angle;
int m_HookedPlayer;
int m_SpawnTick;
int m_WeaponChangeTick;
};
struct CAntibotData
{
int m_Tick;
CAntibotCharacterData m_aCharacters[ANTIBOT_MAX_CLIENTS];
CAntibotMapData m_Map;
void (*m_pfnLog)(const char *pMessage, void *pUser);
void (*m_pfnReport)(int ClientID, const char *pMessage, void *pUser);
void *m_pUser;
};
#endif // ANTIBOT_ANTIBOT_DATA_H

View file

@ -0,0 +1,24 @@
#ifndef ANTIBOT_ANTIBOT_INTERFACE_H
#define ANTIBOT_ANTIBOT_INTERFACE_H
#include "antibot_data.h"
extern "C"
{
void AntibotInit(CAntibotData *pData);
void AntibotUpdateData(void);
void AntibotDestroy(void);
void AntibotDump(void);
void AntibotOnPlayerInit(int ClientID);
void AntibotOnPlayerDestroy(int ClientID);
void AntibotOnSpawn(int ClientID);
void AntibotOnHammerFireReloading(int ClientID);
void AntibotOnHammerFire(int ClientID);
void AntibotOnHammerHit(int ClientID);
void AntibotOnDirectInput(int ClientID);
void AntibotOnTick(int ClientID);
void AntibotOnHookAttach(int ClientID, bool Player);
}
#endif // ANTIBOT_ANTIBOT_INTERFACE_H

View file

@ -0,0 +1,35 @@
#include "antibot_data.h"
static CAntibotData *g_pAntibot;
extern "C"
{
void AntibotInit(CAntibotData *pData)
{
g_pAntibot = pData;
if(g_pAntibot->m_pfnLog)
{
g_pAntibot->m_pfnLog("null antibot initialized", g_pAntibot->m_pUser);
}
}
void AntibotUpdateData() { }
void AntibotDestroy() { g_pAntibot = 0; }
void AntibotDump()
{
if(g_pAntibot->m_pfnLog)
{
g_pAntibot->m_pfnLog("null antibot", g_pAntibot->m_pUser);
}
}
void AntibotOnPlayerInit(int ClientID) { (void)ClientID; }
void AntibotOnPlayerDestroy(int ClientID) { (void)ClientID; }
void AntibotOnSpawn(int ClientID) { (void)ClientID; }
void AntibotOnHammerFireReloading(int ClientID) { (void)ClientID; }
void AntibotOnHammerFire(int ClientID) { (void)ClientID; }
void AntibotOnHammerHit(int ClientID) { (void)ClientID; }
void AntibotOnDirectInput(int ClientID) { (void)ClientID; }
void AntibotOnTick(int ClientID) { (void)ClientID; }
void AntibotOnHookAttach(int ClientID, bool Player) { (void)ClientID; (void)Player; }
}

View file

@ -4,6 +4,8 @@
#include <base/math.h>
#include <base/vmath.h>
#include <antibot/antibot_data.h>
#include <math.h>
#include <engine/map.h>
#include <engine/kernel.h>
@ -152,6 +154,21 @@ void CCollision::Init(class CLayers *pLayers)
}
}
void CCollision::FillAntibot(CAntibotMapData *pMapData)
{
pMapData->m_Width = m_Width;
pMapData->m_Height = m_Height;
pMapData->m_pTiles = (unsigned char *)malloc(m_Width * m_Height);
for(int i = 0; i < m_Width * m_Height; i++)
{
pMapData->m_pTiles[i] = 0;
if(m_pTiles[i].m_Index >= TILE_SOLID && m_pTiles[i].m_Index <= TILE_NOLASER)
{
pMapData->m_pTiles[i] = m_pTiles[i].m_Index;
}
}
}
enum
{
MR_DIR_HERE=0,

View file

@ -19,6 +19,7 @@ enum
vec2 ClampVel(int MoveRestriction, vec2 Vel);
typedef bool (*CALLBACK_SWITCHACTIVE)(int Number, void *pUser);
struct CAntibotMapData;
class CCollision
{
@ -31,6 +32,7 @@ public:
CCollision();
~CCollision();
void Init(class CLayers *pLayers);
void FillAntibot(CAntibotMapData *pMapData);
bool CheckPoint(float x, float y) { return IsSolid(round_to_int(x), round_to_int(y)); }
bool CheckPoint(vec2 Pos) { return CheckPoint(Pos.x, Pos.y); }
int GetCollisionAt(float x, float y) { return GetTile(round_to_int(x), round_to_int(y)); }

View file

@ -0,0 +1,88 @@
#include "antibot.h"
#include <antibot/antibot_data.h>
#include <antibot/antibot_interface.h>
#include <game/server/gamecontext.h>
#ifdef CONF_ANTIBOT
static CAntibotData g_Data;
static void Log(const char *pMessage, void *pUser)
{
CGameContext *pGameContext = (CGameContext *)pUser;
pGameContext->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "antibot", pMessage);
}
static void Report(int ClientID, const char *pMessage, void *pUser)
{
char aBuf[256];
str_format(aBuf, sizeof(aBuf), "%d: %s", ClientID, pMessage);
Log(aBuf, pUser);
}
CAntibot::CAntibot()
: m_pGameContext(0)
{
}
CAntibot::~CAntibot()
{
AntibotDestroy();
free(g_Data.m_Map.m_pTiles);
g_Data.m_Map.m_pTiles = 0;
}
void CAntibot::Init(CGameContext *pGameContext)
{
m_pGameContext = pGameContext;
mem_zero(&g_Data, sizeof(g_Data));
g_Data.m_Map.m_pTiles = 0;
g_Data.m_pfnLog = Log;
g_Data.m_pfnReport = Report;
g_Data.m_pUser = m_pGameContext;
AntibotInit(&g_Data);
Update();
}
void CAntibot::Dump() { AntibotDump(); }
void CAntibot::Update()
{
m_pGameContext->FillAntibot(&g_Data);
AntibotUpdateData();
}
void CAntibot::OnPlayerInit(int ClientID) { Update(); AntibotOnPlayerInit(ClientID); }
void CAntibot::OnPlayerDestroy(int ClientID) { Update(); AntibotOnPlayerDestroy(ClientID); }
void CAntibot::OnSpawn(int ClientID) { Update(); AntibotOnSpawn(ClientID); }
void CAntibot::OnHammerFireReloading(int ClientID) { Update(); AntibotOnHammerFireReloading(ClientID); }
void CAntibot::OnHammerFire(int ClientID) { Update(); AntibotOnHammerFire(ClientID); }
void CAntibot::OnHammerHit(int ClientID) { Update(); AntibotOnHammerHit(ClientID); }
void CAntibot::OnDirectInput(int ClientID) { Update(); AntibotOnDirectInput(ClientID); }
void CAntibot::OnTick(int ClientID) { Update(); AntibotOnTick(ClientID); }
void CAntibot::OnHookAttach(int ClientID, bool Player) { Update(); AntibotOnHookAttach(ClientID, Player); }
#else
CAntibot::CAntibot() :
m_pGameContext(0)
{
}
CAntibot::~CAntibot()
{
}
void CAntibot::Init(CGameContext *pGameContext)
{
m_pGameContext = pGameContext;
}
void CAntibot::Dump()
{
m_pGameContext->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "antibot", "antibot support not compiled in");
}
void CAntibot::Update()
{
}
void CAntibot::OnPlayerInit(int ClientID) { }
void CAntibot::OnPlayerDestroy(int ClientID) { }
void CAntibot::OnSpawn(int ClientID) { }
void CAntibot::OnHammerFireReloading(int ClientID) { }
void CAntibot::OnHammerFire(int ClientID) { }
void CAntibot::OnHammerHit(int ClientID) { }
void CAntibot::OnDirectInput(int ClientID) { }
void CAntibot::OnTick(int ClientID) { }
void CAntibot::OnHookAttach(int ClientID, bool Player) { }
#endif

27
src/game/server/antibot.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef GAME_SERVER_ANTIBOT_H
#define GAME_SERVER_ANTIBOT_H
class CGameContext;
class CAntibot
{
CGameContext *m_pGameContext;
void Update();
public:
CAntibot();
~CAntibot();
void Init(CGameContext *pGameContext);
void Dump();
void OnPlayerInit(int ClientID);
void OnPlayerDestroy(int ClientID);
void OnSpawn(int ClientID);
void OnHammerFireReloading(int ClientID);
void OnHammerFire(int ClientID);
void OnHammerHit(int ClientID);
void OnDirectInput(int ClientID);
void OnTick(int ClientID);
void OnHookAttach(int ClientID, bool Player);
};
#endif // GAME_SERVER_ANTIBOT_H

View file

@ -731,3 +731,9 @@ void CGameContext::ConDrySave(IConsole::IResult *pResult, void *pUserData)
io_write(File, SavedTeam.GetString(), Len);
io_close(File);
}
void CGameContext::ConDumpAntibot(IConsole::IResult *pResult, void *pUserData)
{
CGameContext *pSelf = (CGameContext *)pUserData;
pSelf->Antibot()->Dump();
}

View file

@ -1,6 +1,7 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <new>
#include <antibot/antibot_data.h>
#include <engine/shared/config.h>
#include <game/server/gamecontext.h>
#include <game/mapitems.h>
@ -48,6 +49,11 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
m_pPlayer = pPlayer;
m_Pos = Pos;
mem_zero(&m_LatestPrevPrevInput, sizeof(m_LatestPrevPrevInput));
m_SpawnTick = Server()->Tick();
m_WeaponChangeTick = Server()->Tick();
Antibot()->OnSpawn(m_pPlayer->GetCID());
m_Core.Reset();
m_Core.Init(&GameServer()->m_World.m_Core, GameServer()->Collision(), &((CGameControllerDDRace*)GameServer()->m_pController)->m_Teams.m_Core, &((CGameControllerDDRace*)GameServer()->m_pController)->m_TeleOuts);
m_Core.m_ActiveWeapon = WEAPON_GUN;
@ -331,7 +337,13 @@ void CCharacter::HandleWeaponSwitch()
void CCharacter::FireWeapon()
{
if(m_ReloadTimer != 0)
{
if(m_LatestInput.m_Fire&1)
{
Antibot()->OnHammerFireReloading(m_pPlayer->GetCID());
}
return;
}
DoWeaponSwitch();
vec2 Direction = normalize(vec2(m_LatestInput.m_TargetX, m_LatestInput.m_TargetY));
@ -386,6 +398,8 @@ void CCharacter::FireWeapon()
m_NumObjectsHit = 0;
GameServer()->CreateSound(m_Pos, SOUND_HAMMER_FIRE, Teams()->TeamMask(Team(), -1, m_pPlayer->GetCID()));
Antibot()->OnHammerFire(m_pPlayer->GetCID());
if (m_Hit&DISABLE_HIT_HAMMER) break;
CCharacter *apEnts[MAX_CLIENTS];
@ -432,6 +446,8 @@ void CCharacter::FireWeapon()
if(m_FreezeHammer)
pTarget->Freeze();
Antibot()->OnHammerHit(m_pPlayer->GetCID());
Hits++;
}
@ -698,12 +714,15 @@ void CCharacter::OnDirectInput(CNetObj_PlayerInput *pNewInput)
if(m_LatestInput.m_TargetX == 0 && m_LatestInput.m_TargetY == 0)
m_LatestInput.m_TargetY = -1;
Antibot()->OnDirectInput(m_pPlayer->GetCID());
if(m_NumInputs > 2 && m_pPlayer->GetTeam() != TEAM_SPECTATORS)
{
HandleWeaponSwitch();
FireWeapon();
}
mem_copy(&m_LatestPrevPrevInput, &m_LatestPrevInput, sizeof(m_LatestInput));
mem_copy(&m_LatestPrevInput, &m_LatestInput, sizeof(m_LatestInput));
}
@ -735,14 +754,30 @@ void CCharacter::Tick()
DDRaceTick();
Antibot()->OnTick(m_pPlayer->GetCID());
m_Core.m_Input = m_Input;
m_Core.Tick(true);
if(!m_PrevInput.m_Hook && m_Input.m_Hook
&& !(m_Core.m_TriggeredEvents&COREEVENT_HOOK_ATTACH_PLAYER))
{
Antibot()->OnHookAttach(m_pPlayer->GetCID(), false);
}
// handle Weapons
HandleWeapons();
DDRacePostCoreTick();
if(m_Core.m_TriggeredEvents&COREEVENT_HOOK_ATTACH_PLAYER)
{
if(GameServer()->m_apPlayers[m_Core.m_HookedPlayer]->GetTeam() != -1)
{
Antibot()->OnHookAttach(m_pPlayer->GetCID(), true);
}
}
// Previnput
m_PrevInput = m_Input;
@ -1245,6 +1280,22 @@ CGameTeams* CCharacter::Teams()
return &((CGameControllerDDRace*)GameServer()->m_pController)->m_Teams;
}
void CCharacter::FillAntibot(CAntibotCharacterData *pData)
{
pData->m_Pos = m_Pos;
pData->m_Vel = m_Core.m_Vel;
pData->m_Angle = m_Core.m_Angle;
pData->m_HookedPlayer = m_Core.m_HookedPlayer;
pData->m_SpawnTick = m_SpawnTick;
pData->m_WeaponChangeTick = m_WeaponChangeTick;
pData->m_aLatestInputs[0].m_TargetX = m_LatestInput.m_TargetX;
pData->m_aLatestInputs[0].m_TargetY = m_LatestInput.m_TargetY;
pData->m_aLatestInputs[1].m_TargetX = m_LatestPrevInput.m_TargetX;
pData->m_aLatestInputs[1].m_TargetY = m_LatestPrevInput.m_TargetY;
pData->m_aLatestInputs[2].m_TargetX = m_LatestPrevPrevInput.m_TargetX;
pData->m_aLatestInputs[2].m_TargetY = m_LatestPrevPrevInput.m_TargetY;
}
void CCharacter::HandleBroadcast()
{
CPlayerData *pData = GameServer()->Score()->PlayerData(m_pPlayer->GetCID());
@ -2052,6 +2103,11 @@ void CCharacter::SendZoneMsgs()
}
}
CAntibot *CCharacter::Antibot()
{
return GameServer()->Antibot();
}
void CCharacter::DDRaceTick()
{
mem_copy(&m_Input, &m_SavedInput, sizeof(m_Input));

View file

@ -9,7 +9,9 @@
#include <game/gamecore.h>
class CAntibot;
class CGameTeams;
struct CAntibotCharacterData;
enum
{
@ -125,6 +127,7 @@ private:
int m_LastNoAmmoSound;
// these are non-heldback inputs
CNetObj_PlayerInput m_LatestPrevPrevInput;
CNetObj_PlayerInput m_LatestPrevInput;
CNetObj_PlayerInput m_LatestInput;
@ -170,6 +173,7 @@ private:
void HandleBroadcast();
void HandleTuneLayer();
void SendZoneMsgs();
CAntibot *Antibot();
bool m_SetSavePos;
vec2 m_PrevSavePos;
@ -177,6 +181,7 @@ private:
public:
CGameTeams* Teams();
void FillAntibot(CAntibotCharacterData *pData);
void Pause(bool Pause);
bool Freeze(int Time);
bool Freeze();
@ -233,6 +238,9 @@ public:
bool m_IsBlueTeleGunTeleport;
int m_StrongWeakID;
int m_SpawnTick;
int m_WeaponChangeTick;
// Setters/Getters because i don't want to modify vanilla vars access modifiers
int GetLastWeapon() { return m_LastWeapon; };
void SetLastWeapon(int LastWeap) { m_LastWeapon = LastWeap; };

View file

@ -4,6 +4,7 @@
#include <new>
#include <base/math.h>
#include <antibot/antibot_data.h>
#include <engine/shared/config.h>
#include <engine/map.h>
#include <engine/console.h>
@ -130,6 +131,48 @@ bool CGameContext::EmulateBug(int Bug)
return m_MapBugs.Contains(Bug);
}
void CGameContext::FillAntibot(CAntibotData *pData)
{
if(!pData->m_Map.m_pTiles)
{
Collision()->FillAntibot(&pData->m_Map);
}
pData->m_Tick = Server()->Tick();
mem_zero(pData->m_aCharacters, sizeof(pData->m_aCharacters));
for(int i = 0; i < MAX_CLIENTS; i++)
{
CAntibotCharacterData *pChar = &pData->m_aCharacters[i];
for(int i = 0; i < 3; i++)
{
pChar->m_aLatestInputs[i].m_TargetX = -1;
pChar->m_aLatestInputs[i].m_TargetY = -1;
}
pChar->m_Alive = false;
pChar->m_Pause = false;
pChar->m_Team = -1;
pChar->m_Pos = vec2(-1, -1);
pChar->m_Vel = vec2(0, 0);
pChar->m_Angle = -1;
pChar->m_HookedPlayer = -1;
pChar->m_SpawnTick = -1;
pChar->m_WeaponChangeTick = -1;
if(m_apPlayers[i])
{
str_copy(pChar->m_aName, Server()->ClientName(i), sizeof(pChar->m_aName));
CCharacter *pGameChar = m_apPlayers[i]->GetCharacter();
pChar->m_Alive = (bool)pGameChar;
pChar->m_Pause = m_apPlayers[i]->IsPaused();
pChar->m_Team = m_apPlayers[i]->GetTeam();
if(pGameChar)
{
pGameChar->FillAntibot(pChar);
}
}
}
}
void CGameContext::CreateDamageInd(vec2 Pos, float Angle, int Amount, int64_t Mask)
{
float a = 3 * 3.14159f / 2 + Angle;
@ -2529,6 +2572,7 @@ void CGameContext::OnConsoleInit()
m_pConsole = Kernel()->RequestInterface<IConsole>();
m_pEngine = Kernel()->RequestInterface<IEngine>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
m_Antibot.Init(this);
m_ChatPrintCBIndex = Console()->RegisterPrintCallback(0, SendChatResponse, this);
@ -2558,6 +2602,7 @@ void CGameContext::OnConsoleInit()
Console()->Register("force_vote", "s[name] s[command] ?r[reason]", CFGFLAG_SERVER, ConForceVote, this, "Force a voting option");
Console()->Register("clear_votes", "", CFGFLAG_SERVER, ConClearVotes, this, "Clears the voting options");
Console()->Register("vote", "r['yes'|'no']", CFGFLAG_SERVER, ConVote, this, "Force a vote to yes/no");
Console()->Register("dump_antibot", "", CFGFLAG_SERVER, ConDumpAntibot, this, "Dumps the antibot status");
Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this);

View file

@ -11,6 +11,7 @@
#include <game/mapbugs.h>
#include <game/voting.h>
#include "antibot.h"
#include "eventhandler.h"
#include "gamecontroller.h"
#include "gameworld.h"
@ -58,6 +59,7 @@ class IEngine;
class IStorage;
class CRandomMapResult;
class CMapVoteResult;
struct CAntibotData;
class CGameContext : public IGameServer
{
@ -76,6 +78,7 @@ class CGameContext : public IGameServer
ASYNCIO *m_pTeeHistorianFile;
CUuid m_GameUuid;
CMapBugs m_MapBugs;
CAntibot m_Antibot;
std::shared_ptr<CRandomMapResult> m_pRandomMapResult;
std::shared_ptr<CMapVoteResult> m_pMapVoteResult;
@ -112,6 +115,7 @@ class CGameContext : public IGameServer
static void ConVote(IConsole::IResult *pResult, void *pUserData);
static void ConVoteNo(IConsole::IResult *pResult, void *pUserData);
static void ConDrySave(IConsole::IResult *pResult, void *pUserData);
static void ConDumpAntibot(IConsole::IResult *pResult, void *pUserData);
static void ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
CGameContext(int Resetting);
@ -126,6 +130,7 @@ public:
CCollision *Collision() { return &m_Collision; }
CTuningParams *Tuning() { return &m_Tuning; }
CTuningParams *TuningList() { return &m_aTuningList[0]; }
CAntibot *Antibot() { return &m_Antibot; }
CGameContext();
~CGameContext();
@ -141,6 +146,7 @@ public:
// helper functions
class CCharacter *GetPlayerChar(int ClientID);
bool EmulateBug(int Bug);
void FillAntibot(CAntibotData *pData);
// voting
void StartVote(const char *pDesc, const char *pCommand, const char *pReason);

View file

@ -23,10 +23,12 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team)
m_Team = GameServer()->m_pController->ClampTeam(Team);
m_NumInputs = 0;
Reset();
GameServer()->Antibot()->OnPlayerInit(m_ClientID);
}
CPlayer::~CPlayer()
{
GameServer()->Antibot()->OnPlayerDestroy(m_ClientID);
delete m_pLastTarget;
delete m_pCharacter;
m_pCharacter = 0;