about summary refs log tree commit diff
path: root/src/game/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/server')
-rw-r--r--src/game/server/entities/character.cpp73
-rw-r--r--src/game/server/entities/character.h13
-rw-r--r--src/game/server/gamecontext.cpp83
-rw-r--r--src/game/server/gamecontext.h7
-rw-r--r--src/game/server/player.cpp16
-rw-r--r--src/game/server/player.h10
6 files changed, 202 insertions, 0 deletions
diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp
index 01ed8693..27be82f8 100644
--- a/src/game/server/entities/character.cpp
+++ b/src/game/server/entities/character.cpp
@@ -46,6 +46,10 @@ CCharacter::CCharacter(CGameWorld *pWorld)
 	m_Health = 0;
 	m_Armor = 0;
 	m_FreezeTicks = 0;
+	
+	// last positions
+	m_LastPositionsSize = Server()->TickSpeed() / 4;
+	m_LastPositions = new LastPosition[m_LastPositionsSize]();
 }
 
 void CCharacter::Reset()
@@ -106,6 +110,70 @@ void CCharacter::Destroy()
 {
 	GameServer()->m_World.m_Core.m_apCharacters[m_pPlayer->GetCID()] = 0;
 	m_Alive = false;
+	
+	// delete last positions
+	delete[] m_LastPositions;
+}
+
+// checks whether the player has been at those coords recently (like a few ticks ago)
+bool CCharacter::HasBeenThereRecently(float x, float y, const LastPosition *&pos, int firstTick, int lastTick) const
+{
+	// start with the most recent position
+	float dx, dy, dxp = 0, dyp = 0;
+	for(; lastTick > firstTick; --lastTick)
+	{
+		int i = lastTick % m_LastPositionsSize;
+		dx = abs(m_LastPositions[i].x - x);
+		dy = abs(m_LastPositions[i].y - y);
+		if(dx <= 1.0 && dy <= 1.0)
+		{
+			pos = &m_LastPositions[i];
+			return true;
+		}
+		// abort if too far away or if distance getting bigger
+		if(dx > 100.0 || dy > 100.0 || (dxp > 0 && dx > dxp && dy > dyp)) return false;
+		// write previous vals
+		dxp = dx;
+		dyp = dy;
+	}
+	return false;
+}
+
+// checks whether the player has been aiming at another character recently (like a few ticks ago)
+bool CCharacter::AimedAtCharRecently(float aimX, float aimY, const CCharacter *c, const LastPosition *&pos, const LastPosition *&posVictim, int firstTick)
+{
+	// The last few positions of both characters are saved. Since you cannot tell (due to the network) _when_ the player aimed at the other player, or even where he was when he aimed, we need to check each position of the one player against each position of the other player in the time before.
+	// start with the most recent position
+	firstTick = max(firstTick, Server()->Tick() - m_LastPositionsSize);
+	for(int lastTick = Server()->Tick(); lastTick > firstTick; --lastTick)
+	{
+		int b = lastTick % m_LastPositionsSize;
+		// check if the other player has been where the player aimed
+		if(c->HasBeenThereRecently(m_LastPositions[b].x + aimX, m_LastPositions[b].y + aimY, posVictim, firstTick, lastTick))
+		{
+			pos = &m_LastPositions[b];
+			return true;
+		}
+	}
+	return false;
+}
+
+float CCharacter::HowCloseToXRecently(vec2 x, const LastPosition *&pos, int firstTick)
+{
+	float lowest = 1000.0;
+	// start with the most recent position
+	firstTick = max(firstTick, Server()->Tick() - m_LastPositionsSize);
+	for(int lastTick = Server()->Tick(); lastTick > firstTick; --lastTick)
+	{
+		int i = lastTick % m_LastPositionsSize;
+		float d = distance(x, vec2(m_LastPositions[i].x, m_LastPositions[i].y));
+		if(d < lowest)
+		{
+			pos = &m_LastPositions[i];
+			lowest = d;
+		}
+	}
+	return lowest;
 }
 
 void CCharacter::SetWeapon(int W)
@@ -641,6 +709,11 @@ void CCharacter::Tick()
 
 	// Previnput
 	m_PrevInput = m_Input;
+	
+	// save position
+	m_LastPositions[Server()->Tick() % m_LastPositionsSize].x = m_Pos.x;
+	m_LastPositions[Server()->Tick() % m_LastPositionsSize].y = m_Pos.y;
+	
 	return;
 }
 
diff --git a/src/game/server/entities/character.h b/src/game/server/entities/character.h
index ece33e45..7b949e29 100644
--- a/src/game/server/entities/character.h
+++ b/src/game/server/entities/character.h
@@ -67,6 +67,15 @@ public:
 
 	void Freeze(int Tick);
 	int m_FreezeTicks;
+	
+	// bot detection
+	struct LastPosition {
+		float x;
+		float y;
+	};
+	bool HasBeenThereRecently(float x, float y, const LastPosition *&pos, int firstTick, int lastTick) const;
+	bool AimedAtCharRecently(float aimX, float aimY, const CCharacter *c, const LastPosition *&pos, const LastPosition *&posVictim, int firstTick);
+	float HowCloseToXRecently(vec2 x, const LastPosition *&pos, int firstTick);
 
 private:
 	// player controlling this character
@@ -134,6 +143,10 @@ private:
 	int m_ReckoningTick; // tick that we are performing dead reckoning From
 	CCharacterCore m_SendCore; // core that we should send
 	CCharacterCore m_ReckoningCore; // the dead reckoning core
+	
+	// bot detection
+	LastPosition *m_LastPositions;
+	int m_LastPositionsSize;
 
 };
 
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index fa36187b..5ad268cb 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -527,6 +527,89 @@ void CGameContext::OnTick()
 		}
 	}
 
+	// bot detection
+	// it is based on the behaviour of some bots to shoot at a player's _exact_ position
+	// check each player
+	if(g_Config.m_SvBotDetection)
+	{
+		for(int i = 0; i < MAX_CLIENTS; ++i)
+		{
+			CCharacter *ci, *cj;
+			CPlayer *p;
+			// abort if player is not ingame or already detected as a bot
+			if(!(p = m_apPlayers[i]) || p->m_IsAimBot || !(ci = GetPlayerChar(i)))
+				continue;
+			
+			// check against every other player
+			for(int j = 0; j < MAX_CLIENTS; ++j)
+			{
+
+				if(j != i && (cj = GetPlayerChar(j)))
+				{
+					const CCharacter::LastPosition *pos, *posVictim;
+					
+					// fast aiming bot detection
+					if(g_Config.m_SvBotDetection&BOT_DETECTION_FAST_AIM && p->m_AimBotTargetSpeed > 300.0 && !(p->m_AimBotLastDetectionPos.x == ci->m_Pos.x && p->m_AimBotLastDetectionPos.y == ci->m_Pos.y))
+					{
+						vec2 t(ci->m_Pos.x + p->m_LatestActivity.m_TargetX, ci->m_Pos.y + p->m_LatestActivity.m_TargetY);
+						float hc = cj->HowCloseToXRecently(t, posVictim, p->m_AimBotLastDetection);
+						float precision = p->m_AimBotTargetSpeed * (256.0 - hc * hc);
+						// don't detect same position twice
+						if(precision > 0 && !(posVictim->x == p->m_AimBotLastDetectionPosVictim.x && posVictim->y == p->m_AimBotLastDetectionPosVictim.y))
+						{
+							if(precision >= 50000.0)
+							{
+								p->m_AimBotLastDetection = Server()->Tick();
+								p->m_AimBotLastDetectionPos.x = ci->m_Pos.x;
+								p->m_AimBotLastDetectionPos.y = ci->m_Pos.y;
+								p->m_AimBotLastDetectionPosVictim = *posVictim;
+								p->m_AimBotIndex += 2 * min(3, (int)(precision / 50000));
+								// write to console
+								char aBuf[128];
+								str_format(aBuf, sizeof(aBuf), "player=%d victim=%d a_index=%d precision=%d speed=%d distance=%d", i, j, p->m_AimBotIndex, (int)precision, (int)p->m_AimBotTargetSpeed, (int)hc);
+								Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "botdetect", aBuf);
+								// don't check other players
+								break;
+							}
+						}
+					}
+					
+					// follow bot detection
+					// other placer needs to be ingame and in sight. don't detect the same position again
+					else if(g_Config.m_SvBotDetection&BOT_DETECTION_FOLLOW && cj->NetworkClipped(i) == 0 && ci->AimedAtCharRecently(p->m_LatestActivity.m_TargetX, p->m_LatestActivity.m_TargetY, cj, pos, posVictim, p->m_AimBotLastDetection) && !(pos->x == p->m_AimBotLastDetectionPos.x && pos->y == p->m_AimBotLastDetectionPos.y && posVictim->x == p->m_AimBotLastDetectionPosVictim.x && posVictim->y == p->m_AimBotLastDetectionPosVictim.y))
+					{
+						p->m_AimBotLastDetection = Server()->Tick();
+						p->m_AimBotLastDetectionPos = *pos;
+						p->m_AimBotLastDetectionPosVictim = *posVictim;
+						++p->m_AimBotIndex;
+						// write to console
+						char aBuf[128];
+						str_format(aBuf, sizeof(aBuf), "player=%d victim=%d a_index=%d", i, j, p->m_AimBotIndex);
+						Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "botdetect", aBuf);
+						// don't check other players
+						break;
+					}
+					
+				}
+			}
+			
+			// check if threshold is exceeded
+			if(p->m_AimBotIndex >= 10)
+			{
+				p->m_IsAimBot = Server()->Tick();
+				// alert the admins
+				char aBuf[128];
+				str_format(aBuf, sizeof(aBuf), "'%s' might be botting", Server()->ClientName(i));
+				for(int j = 0; j < MAX_CLIENTS; ++j)
+					if(Server()->IsAuthed(j))
+						SendChatTarget(j, aBuf);
+			}
+			
+			// reduce once every 2 seconds (tolerance)
+			if(((Server()->Tick() % (Server()->TickSpeed() * 2)) == 0) && p->m_AimBotIndex)
+				--p->m_AimBotIndex;
+		}
+	}
 
 #ifdef CONF_DEBUG
 	if(g_Config.m_DbgDummies)
diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h
index 27551ac8..eb73e019 100644
--- a/src/game/server/gamecontext.h
+++ b/src/game/server/gamecontext.h
@@ -195,6 +195,13 @@ public:
 	virtual const char *GameType();
 	virtual const char *Version();
 	virtual const char *NetVersion();
+	
+	// bot detection
+	enum
+	{
+		BOT_DETECTION_FAST_AIM=1,
+		BOT_DETECTION_FOLLOW=2,
+	};
 };
 
 inline int CmaskAll() { return -1; }
diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp
index d3e00f9c..17fcd566 100644
--- a/src/game/server/player.cpp
+++ b/src/game/server/player.cpp
@@ -36,6 +36,16 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, int Team)
 	m_ZCatchVictims = NULL;
 	m_zCatchNumVictims = 0;
 	m_zCatchNumKillsInARow = 0;
+	
+	// bot detection
+	m_IsAimBot = 0;
+	m_AimBotIndex = 0;
+	m_AimBotLastDetection = 0;
+	m_AimBotTargetSpeed = .0;
+	m_CurrentTarget.x = 0;
+	m_CurrentTarget.y = 0;
+	m_LastTarget.x = 0;
+	m_LastTarget.y = 0;
 }
 
 CPlayer::~CPlayer()
@@ -122,6 +132,12 @@ void CPlayer::Tick()
 		++m_LastActionTick;
 		++m_TeamChangeTick;
  	}
+	
+	// bot detection
+	m_LastTarget = m_CurrentTarget;
+	m_CurrentTarget.x = m_LatestActivity.m_TargetX;
+	m_CurrentTarget.y = m_LatestActivity.m_TargetY;
+	m_AimBotTargetSpeed = abs(distance(m_CurrentTarget, m_LastTarget));
 }
 
 void CPlayer::PostTick()
diff --git a/src/game/server/player.h b/src/game/server/player.h
index 51deaa5e..53466d0c 100644
--- a/src/game/server/player.h
+++ b/src/game/server/player.h
@@ -132,6 +132,16 @@ public:
 	bool HasZCatchVictims() { return (m_ZCatchVictims != NULL); }
 	int LastZCatchVictim() { return HasZCatchVictims() ? m_ZCatchVictims->ClientID : -1; }
 	
+	// bot detection
+	int m_IsAimBot;
+	int m_AimBotIndex;
+	int m_AimBotLastDetection;
+	float m_AimBotTargetSpeed;
+	vec2 m_CurrentTarget;
+	vec2 m_LastTarget;
+	CCharacter::LastPosition m_AimBotLastDetectionPos;
+	CCharacter::LastPosition m_AimBotLastDetectionPosVictim;
+	
 private:
 	CCharacter *m_pCharacter;
 	CGameContext *m_pGameServer;