Original Post: Ace of Spades Forums • View topic - Anti-aimbot script
Download: https://raw.github.com/matpow2/pyspades-userscripts/master/aimbot2.py
PLEASE READ THE CONFIGURATION CAREFULLY BEFORE YOU RUN THIS SCRIPT
The anti-aimbot script statistically analyzes player behavior to kick or ban aimbots. The description of all the configuration options are all commented in the script.
Here is a brief overview of the different detection methods using the default values. The values are of course completely configurable.
[]Headshot snap detection: Kick/ban if the player’s orientation changes more than 90 degrees to align with the head of an enemy 6 or more times in 20 seconds
[]Hit percent: Kick/ban if hit accuracy exceeds 90% (rifle), 80% (smg), or 90% (shotgun) and the minimum number of shots is met (45 rifle, 90 smg, 45 shotgun)
[]Detect damage hack: Invalid damage values are ignored. The current aimbot doesn’t actually modify the damage values, but keep this on just in case.
[]Detect kills in time: Kick/ban the player if they kill 15 enemies in 20 seconds (grenade and airstrike kills don’t count)
[*]Detect multiple bullets: Kick/ban the player if the server receives 8 or more hit packets in 0.25 seconds (rifle) or 0.05 seconds (smg). Note that this method should kick aimbotters with 100% accuracy with an extremely small chance of false positives.
This script also contains a command available to all players to check on how accurate someone’s shots are. The name parameter is optional.
/accuracy name
What should I do if I’m worried about false positives?
Feel free to change the configuration of the script to your liking. For the person extremely paranoid about false positives, I recommend this setting:
DETECT_SNAP_HEADSHOT = False
DETECT_HIT_PERCENT = False
DETECT_DAMAGE_HACK = True
DETECT_KILLS_IN_TIME = False
DETECT_MULTIPLE_BULLETS = True
Code in case the script source is deleted.
Click for details
from twisted.internet.task import LoopingCall
from pyspades.constants import *
from math import sqrt, cos, acos, pi, tan
from commands import add, admin, get_player
from twisted.internet import reactor
import re
DISABLED, KICK, BAN, WARN_ADMIN = xrange(4)
# This is an option for data collection. Data is outputted to aimbot2log.txt
DATA_COLLECTION = False
# This controls which detection methods are enabled. If a player is detected
# using one of these methods, the player is kicked.
HEADSHOT_SNAP = WARN_ADMIN
HIT_PERCENT = WARN_ADMIN
KILLS_IN_TIME = WARN_ADMIN
MULTIPLE_BULLETS = WARN_ADMIN
DETECT_DAMAGE_HACK = True
# Minimum amount of time that must pass between admin warnings that are
# triggered by the same detection method. Time is in seconds.
WARN_INTERVAL_MINIMUM = 300
# These controls are only used if banning is enabled
# Time is given in minutes. Set to 0 for a permaban
HEADSHOT_SNAP_BAN_DURATION = 1400
HIT_PERCENT_BAN_DURATION = 1440
KILLS_IN_TIME_BAN_DURATION = 2880
MULTIPLE_BULLETS_BAN_DURATION = 10080
# If more than or equal to this number of weapon hit packets are recieved
# from the client in half the weapon delay time, then an aimbot is detected.
# This method of detection should have 100% detection and no false positives
# with the current aimbot.
# Note that the current aimbot does not modify the number of bullets
# of the shotgun, so this method will not work if the player uses a shotgun.
# These values may need to be changed if an update to the aimbot is released.
RIFLE_MULTIPLE_BULLETS_MAX = 8
SMG_MULTIPLE_BULLETS_MAX = 8
# The minimum number of near misses + hits that are fired before kicking,
# banning, or warning an admin about someone using the hit percentage check
RIFLE_KICK_MINIMUM = 45
SMG_KICK_MINIMUM = 90
SHOTGUN_KICK_MINIMUM = 45
# Kick, ban, or warn when the above minimum is met and the
# bullet hit percentage is greater than or equal to this amount
RIFLE_KICK_PERC = 0.90
SMG_KICK_PERC = 0.80
SHOTGUN_KICK_PERC = 0.90
# If a player gets more kills than the KILL_THRESHOLD in the given
# KILL_TIME, kick, ban, or warn. This check is performed every
# time somebody kills someone with a gun
KILL_TIME = 20.0
KILL_THRESHOLD = 15
# If the number of headshot snaps exceeds the HEADSHOT_SNAP_THRESHOLD in the
# given HEADSHOT_SNAP_TIME, kick, ban, or warn. This check is performed every
# time somebody performs a headshot snap
HEADSHOT_SNAP_TIME = 20.0
HEADSHOT_SNAP_THRESHOLD = 6
# When the user's orientation angle (degrees) changes more than this amount,
# check if the user snapped to an enemy's head. If it is aligned with a head,
# record this as a headshot snap
HEADSHOT_SNAP_ANGLE = 90.0
# A near miss occurs when the player is NEAR_MISS_ANGLE degrees or less off
# of an enemy
NEAR_MISS_ANGLE = 10.0
# Valid damage values for each gun
RIFLE_DAMAGE = (33, 49, 100)
SMG_DAMAGE = (18, 29, 75)
SHOTGUN_DAMAGE = (16, 27, 37)
# Approximate size of player's heads in blocks
HEAD_RADIUS = 0.7
# 128 is the approximate fog distance, but bump it up a little
# just in case
FOG_DISTANCE = 135.0
# Don't touch any of this stuff
FOG_DISTANCE2 = FOG_DISTANCE**2
NEAR_MISS_COS = cos(NEAR_MISS_ANGLE * (pi/180.0))
HEADSHOT_SNAP_ANGLE_COS = cos(HEADSHOT_SNAP_ANGLE * (pi/180.0))
aimbot_pattern = re.compile(".*(aim|bot|ha(ck|x)|cheat).*", re.IGNORECASE)
def aimbot_match(msg):
return (not aimbot_pattern.match(msg) is None)
def point_distance2(c1, c2):
if c1.world_object is not None and c2.world_object is not None:
p1 = c1.world_object.position
p2 = c2.world_object.position
return (p1.x - p2.x)**2 + (p1.y - p2.y)**2 + (p1.z - p2.z)**2
def dot3d(v1, v2):
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
def magnitude(v):
return sqrt(v[0]**2 + v[1]**2 + v[2]**2)
def scale(v, scale):
return (v[0]*scale, v[1]*scale, v[2]*scale)
def subtract(v1, v2):
return (v1[0]-v2[0], v1[1]-v2[1], v1[2]-v2[2])
def accuracy(connection, name = None):
if name is None:
player = connection
else:
player = get_player(connection.protocol, name)
return accuracy_player(player)
def accuracy_player(player, name_info = True):
if player.rifle_count != 0:
rifle_percent = str(int(100.0 * (float(player.rifle_hits)/float(player.rifle_count)))) + '%'
else:
rifle_percent = 'None'
if player.smg_count != 0:
smg_percent = str(int(100.0 * (float(player.smg_hits)/float(player.smg_count)))) + '%'
else:
smg_percent = 'None'
if player.shotgun_count != 0:
shotgun_percent = str(int(100.0 * (float(player.shotgun_hits)/float(player.shotgun_count)))) + '%'
else:
shotgun_percent = 'None'
s = ''
if name_info:
s += player.name + ' has an accuracy of: '
s += 'Rifle: %s SMG: %s Shotgun: %s.' % (rifle_percent, smg_percent, shotgun_percent)
return s
add(accuracy)
@admin
def hackinfo(connection, name):
player = get_player(connection.protocol, name)
return hackinfo_player(player)
def hackinfo_player(player):
info = "%s #%s (%s) has an accuracy of: " % (player.name, player.player_id, player.address[0])
info += accuracy_player(player, False)
ratio = player.ratio_kills/float(max(1,player.ratio_deaths))
info += " Kill-death ratio of %.2f (%s kills, %s deaths)." % (ratio, player.ratio_kills, player.ratio_deaths)
info += " %i kills in the last %i seconds." % (player.get_kill_count(), KILL_TIME)
info += " %i headshot snaps in the last %i seconds." % (player.get_headshot_snap_count(), HEADSHOT_SNAP_TIME)
return info
add(hackinfo)
def apply_script(protocol, connection, config):
class Aimbot2Protocol(protocol):
def start_votekick(self, payload):
if aimbot_match(payload.reason):
payload.target.warn_admin('Hack related votekick.')
return protocol.start_votekick(self, payload)
class Aimbot2Connection(connection):
def __init__(self, *arg, **kw):
connection.__init__(self, *arg, **kw)
self.rifle_hits = self.smg_hits = self.shotgun_hits = 0
self.rifle_count = self.smg_count = self.shotgun_count = 0
self.last_target = None
self.first_orientation = True
self.kill_times = []
self.headshot_snap_times = []
self.bullet_loop = LoopingCall(self.on_bullet_fire)
self.shot_time = 0.0
self.multiple_bullets_count = 0
self.headshot_snap_warn_time = self.hit_percent_warn_time = 0.0
self.kills_in_time_warn_time = self.multiple_bullets_warn_time = 0.0
def warn_admin(self, prefix = 'Possible aimbot detected.'):
prefix += ' '
message = hackinfo_player(self)
for player in self.protocol.players.values():
if player.admin:
player.send_chat(prefix + message)
irc_relay = self.protocol.irc_relay
if irc_relay:
if irc_relay.factory.bot and irc_relay.factory.bot.colors:
prefix = '\x0304' + prefix + '\x0f'
irc_relay.send(prefix + message)
def on_spawn(self, pos):
self.first_orientation = True
return connection.on_spawn(self, pos)
def bullet_loop_start(self, interval):
if not self.bullet_loop.running:
self.bullet_loop.start(interval)
def bullet_loop_stop(self):
if self.bullet_loop.running:
self.bullet_loop.stop()
def get_headshot_snap_count(self):
pop_count = 0
headshot_snap_count = 0
current_time = reactor.seconds()
for old_time in self.headshot_snap_times:
if current_time - old_time <= HEADSHOT_SNAP_TIME:
headshot_snap_count += 1
else:
pop_count += 1
for i in xrange(0, pop_count):
self.headshot_snap_times.pop(0)
return headshot_snap_count
def on_orientation_update(self, x, y, z):
if not self.first_orientation and self.world_object is not None:
orient = self.world_object.orientation
old_orient_v = (orient.x, orient.y, orient.z)
new_orient_v = (x, y, z)
theta = dot3d(old_orient_v, new_orient_v)
if theta <= HEADSHOT_SNAP_ANGLE_COS:
self_pos = self.world_object.position
for enemy in self.team.other.get_players():
enemy_pos = enemy.world_object.position
position_v = (enemy_pos.x - self_pos.x, enemy_pos.y - self_pos.y, enemy_pos.z - self_pos.z)
c = scale(new_orient_v, dot3d(new_orient_v, position_v))
h = magnitude(subtract(position_v, c))
if h <= HEAD_RADIUS:
current_time = reactor.seconds()
self.headshot_snap_times.append(current_time)
if self.get_headshot_snap_count() >= HEADSHOT_SNAP_THRESHOLD:
if HEADSHOT_SNAP == BAN:
self.ban('Aimbot detected - headshot snap', HEADSHOT_SNAP_BAN_DURATION)
return
elif HEADSHOT_SNAP == KICK:
self.kick('Aimbot detected - headshot snap')
return
elif HEADSHOT_SNAP == WARN_ADMIN:
if (current_time - self.headshot_snap_warn_time) > WARN_INTERVAL_MINIMUM:
self.headshot_snap_warn_time = current_time
self.warn_admin()
else:
self.first_orientation = False
return connection.on_orientation_update(self, x, y, z)
def on_shoot_set(self, shoot):
if self.tool == WEAPON_TOOL:
if shoot and not self.bullet_loop.running:
self.possible_targets = []
for enemy in self.team.other.get_players():
if point_distance2(self, enemy) <= FOG_DISTANCE2:
self.possible_targets.append(enemy)
self.bullet_loop_start(self.weapon_object.delay)
elif not shoot:
self.bullet_loop_stop()
return connection.on_shoot_set(self, shoot)
def get_kill_count(self):
current_time = reactor.seconds()
kill_count = 0
pop_count = 0
for old_time in self.kill_times:
if current_time - old_time <= KILL_TIME:
kill_count += 1
else:
pop_count += 1
for i in xrange(0, pop_count):
self.kill_times.pop(0)
return kill_count
def on_kill(self, by, type, grenade):
if by is not None and by is not self:
if type == WEAPON_KILL or type == HEADSHOT_KILL:
by.kill_times.append(reactor.seconds())
if by.get_kill_count() >= KILL_THRESHOLD:
if KILLS_IN_TIME == BAN:
by.ban('Aimbot detected - kills in time window', KILLS_IN_TIME_BAN_DURATION)
return
elif KILLS_IN_TIME == KICK:
by.kick('Aimbot detected - kills in time window')
return
elif KILLS_IN_TIME == WARN_ADMIN:
current_time = reactor.seconds()
if (current_time - by.kills_in_time_warn_time) > WARN_INTERVAL_MINIMUM:
by.kills_in_time_warn_time = current_time
by.warn_admin()
return connection.on_kill(self, by, type, grenade)
def multiple_bullets_eject(self):
if MULTIPLE_BULLETS == BAN:
self.ban('Aimbot detected - multiple bullets', MULTIPLE_BULLETS_BAN_DURATION)
elif MULTIPLE_BULLETS == KICK:
self.kick('Aimbot detected - multiple bullets')
elif MULTIPLE_BULLETS == WARN_ADMIN:
current_time = reactor.seconds()
if (current_time - self.multiple_bullets_warn_time) > WARN_INTERVAL_MINIMUM:
self.multiple_bullets_warn_time = current_time
self.warn_admin()
def on_hit(self, hit_amount, hit_player, type, grenade):
if self.team is not hit_player.team:
if type == WEAPON_KILL or type == HEADSHOT_KILL:
current_time = reactor.seconds()
shotgun_use = False
if current_time - self.shot_time > (0.5 * hit_player.weapon_object.delay):
shotgun_use = True
self.multiple_bullets_count = 0
self.shot_time = current_time
if type == HEADSHOT_KILL:
self.multiple_bullets_count += 1
if self.weapon == RIFLE_WEAPON:
if (not (hit_amount in RIFLE_DAMAGE)) and DETECT_DAMAGE_HACK:
return False
else:
self.rifle_hits += 1
if self.multiple_bullets_count >= RIFLE_MULTIPLE_BULLETS_MAX:
self.multiple_bullets_eject()
return False
elif self.weapon == SMG_WEAPON:
if (not (hit_amount in SMG_DAMAGE)) and DETECT_DAMAGE_HACK:
return False
else:
self.smg_hits += 1
if self.multiple_bullets_count >= SMG_MULTIPLE_BULLETS_MAX:
self.multiple_bullets_eject()
return False
elif self.weapon == SHOTGUN_WEAPON:
if (not (hit_amount in SHOTGUN_DAMAGE)) and DETECT_DAMAGE_HACK:
return False
elif shotgun_use:
self.shotgun_hits += 1
return connection.on_hit(self, hit_amount, hit_player, type, grenade)
def hit_percent_eject(self, accuracy):
message = 'Aimbot detected - %i%% %s hit accuracy' %\
(100.0 * accuracy, self.weapon_object.name)
if HIT_PERCENT == BAN:
self.ban(message, HIT_PERCENT_BAN_DURATION)
elif HIT_PERCENT == KICK:
self.kick(message)
elif HIT_PERCENT == WARN_ADMIN:
current_time = reactor.seconds()
if (current_time - self.hit_percent_warn_time) > WARN_INTERVAL_MINIMUM:
self.hit_percent_warn_time = current_time
self.warn_admin()
def check_percent(self):
if self.weapon == RIFLE_WEAPON:
rifle_perc = float(self.rifle_hits)/float(self.rifle_count)
if self.rifle_count >= RIFLE_KICK_MINIMUM:
if rifle_perc >= RIFLE_KICK_PERC:
self.hit_percent_eject(rifle_perc)
elif self.weapon == SMG_WEAPON:
smg_perc = float(self.smg_hits)/float(self.smg_count)
if self.smg_count >= SMG_KICK_MINIMUM:
if smg_perc >= SMG_KICK_PERC:
self.hit_percent_eject(smg_perc)
elif self.weapon == SHOTGUN_WEAPON:
shotgun_perc = float(self.shotgun_hits)/float(self.shotgun_count)
if self.shotgun_count >= SHOTGUN_KICK_MINIMUM:
if shotgun_perc >= SHOTGUN_KICK_PERC:
self.hit_percent_eject(shotgun_perc)
def on_bullet_fire(self):
# Remembering the past offers a performance boost, particularly with the SMG
if self.last_target is not None:
if self.last_target.hp is not None:
if self.check_near_miss(self.last_target):
self.check_percent()
return
for enemy in self.possible_targets:
if enemy.hp is not None and enemy is not self.last_target:
if self.check_near_miss(enemy):
self.last_target = enemy
self.check_percent()
return
def check_near_miss(self, target):
if self.world_object is not None and target.world_object is not None:
p_self = self.world_object.position
p_targ = target.world_object.position
position_v = (p_targ.x - p_self.x, p_targ.y - p_self.y, p_targ.z - p_self.z)
orient = self.world_object.orientation
orient_v = (orient.x, orient.y, orient.z)
position_v_mag = magnitude(position_v)
if position_v_mag != 0 and (dot3d(orient_v, position_v)/position_v_mag) >= NEAR_MISS_COS:
if self.weapon == RIFLE_WEAPON:
self.rifle_count += 1
elif self.weapon == SMG_WEAPON:
self.smg_count += 1
elif self.weapon == SHOTGUN_WEAPON:
self.shotgun_count += 1
return True
return False
# Data collection stuff
def on_disconnect(self):
self.bullet_loop_stop()
if DATA_COLLECTION:
if self.name != None:
with open('aimbot2log.txt','a') as myfile:
output = self.name.encode('ascii','ignore').replace(',','') + ','
output += str(self.rifle_hits) + ',' + str(self.rifle_count) + ','
output += str(self.smg_hits) + ',' + str(self.smg_count) + ','
output += str(self.shotgun_hits) + ',' + str(self.shotgun_count) + '\n'
myfile.write(output)
myfile.close()
return connection.on_disconnect(self)
return Aimbot2Protocol, Aimbot2Connection