Scripts:Kicking for high ping

From BF2 Technical Information Wiki
Jump to navigation Jump to search

Introduction

There are many things that may influence the quality of a BF2 server. One of them is the ping of the players. Playing against a lot of players with very high ping is straight out frustrating. Therefore a lot of server-operators choose to kick players with high ping, and the best way to do it is of course to do it automatically.

With that in mind, we at Battlefield.no have developed a ping kick script that solves this problem on our servers. And hopefully it can be of use to the rest of the community as well.

Current version is 1.2, added 2005-07-03.

Features

What the the ping kick script can offer is to

  • kick people with high ping based on
    • a configurable pinglimit
    • after a configurable number of warnings (that only appear in the debug log on the server, if enabled)
    • with a configurable interval between ping checks
  • wait until a player has started playing before checking his/her ping, to avoid kicks on mapchange etc


Getting Started

To get started with the ping kick script you need to:

  • Download the script here, and rename it to pingkick.py.
  • Place it in the admin/standard_admin directory in your BF2 directory.
  • Place the following code in the admin/standard_admin/__init__.py file in your BF2 directory:
import pingkick
pingkick.init()

For these changes to take effect you will need to restart your BF2 server.


The Ping Kick Script

This is the ping script as we use it at Battlefield.no. Feel free to copy it, and since this is a wiki, feel free to modify it. But we would be very grateful of you appended a note to the updates section if you do.

Configuration

The ping kick script comes with four settings you can change:

autokick = 1 # Enables/disables autockicking
pinglimit = 150 # The ping limit, obviously ;-p
warnings = 3 # The number of warnings a player get before being kicked
interval = 30 # The interval between ping checks

You can either change these settings directly in the script, or you can make sure there exists a dictionary in the bf2 object, called adminoptions, with the following keys:

pk.pingAutoKick
pk.pingLimit
pk.warnings
pk.interval

If they exist, they will override the defaults set in the ping kick script. If you use our modified admin script, the adminoptions dictionary is made available, and populated with the values in the adminsettings.con file. So remember to add the above keys with appropriate values to the file. See here for more details on that.

Output

There ping kick script prints a variety of messages, but to actually view them you will need to enable debug-logging. This is done by

  • setting g_debug to 1 in <bf2>/python/bf2/__init__.py
  • enabling a writer, like we've done in modified admin script

When a player is cought by the ping kick script with a ping that is too high, a message like the following is logged:

[2005-07-03 04:44:28] PINGKICK: Player Panic[bf2.no] has exceeded the ping limit 2 time(s) 

And when a player has been issued the maximum number of warnings, and is being kicked, a message like the following is logged:

[2005-07-03 04:44:43] PINGKICK: Kicking player Panic[bf2.no] for high ping ([120, 118, 118] > 100) 

The numbers inside the braces are ping values that has been detected and that are in violation of the ping limit. And the number to the right of the greater than sign is the ping limit.

And five seconds before a player in violation of the ping limit is being kicked, a message like the following is printed in-game:

BF2.NO PING KICKER: Kicking player Panic[bf2.no] for high ping. (Limit is 100, player caught with [120, 118, 118].) 

Again the numbers inside the braces are ping values that has been detected and that are in violation of the ping limit, and the first one is the ping limit.

The Code

# ------------------------------------------------------------------------
# Module: pingkick.py
# Authors: Kybber and Panic, Battlefield.no
# Version 1.2
# Description:
#   This script kicks players with high ping, based on three different
#   settings in the file 'adminsettings.con', loaded by the modified admin
#   script (sv.adminScript = bfno_v1.1.py). See requirements for details.
# Requirements:
#   None.
# Installation:
#   Save this script as 'pingkick.py' in your <bf2>/admin/standard_admin directory,
#   and add the lines 'import pingkick' and 'pingkick.init()' to the file
#   '<bf2>/admin/standard_admin/__init__.py'.
#
# Changes from 1.0 to 1.1:
# - Added support for stand-alone operation. (Will use bf2.adminoptions if available)
# - The script now only checks ping while game status is 'Playing'
# - Switched from 'bfno' to prefix 'pk' for ping kick settings in 'bf2.adminoptions'.
# - Other, minor updates.
#
# Changes from 1.1. to 1.2:
# - Added methods to deactivate checking while players are not really playing.
# - Added safeguard against kicking a player that has gotten a disconnected "ping violater's" index.
# - Made the logging more verbose and ingame messaging less verbose.
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
#                     C O N F I G U R A T I O N
# ------------------------------------------------------------------------
# Settings for auto-kicking:
autokick = 1      # Enables/disables autockicking
pinglimit = 150   # The ping limit, obviously ;-p
warnings = 3      # The number of warnings a player get before being kicked
interval = 30     # The interval between ping checks
# NB: These settings will be overridden by their counter-parts in
# bf2.adminoptions['pk.<autoKick|pingLimit|warnings|interval']
# if those are present.


# ------------------------------------------------------------------------
#                        I M P O R T
# ------------------------------------------------------------------------

import host
import bf2
from bf2 import g_debug

# ------------------------------------------------------------------------
#                       V A R I A B L E S
# ------------------------------------------------------------------------

# We need our own debug flag:
bfno_debug = False

# Some timers:
pktimer   = None  # Timer firing off the ping checker. Depends on the 'interval' variable.
kicktimer = None  # Timer that delays kicking after the final warning has been issued.
msgtimer  = None  # Timer that delays the "activated" message after entering bf2.GameStatus.Playing

# List of people to kick for high ping. Will contain touples of p.index and p.getProfileId():
kicklist = []


# For keeping track of the game status:
gamestatus = None


# ------------------------------------------------------------------------
#                       F U N C T I O N S
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# The init function run by the standard_admin/__init__ functions:
# ------------------------------------------------------------------------
def init():
   global interval,bfno_debug
   try:
      if bf2.bfno.debug: bfno_debug = True
   except:
      pass
   if bfno_debug or bfno_debug: print 'PINGKICK: initializing pingkick script'
   # Try to read the settings:
   getSettings()
   # Force at least 10 seconds between ping checks:
   if interval < 10: interval = 10
   # Print debug message if autokick is disabled:
   if 1 != autokick:
      if bfno_debug or bfno_debug: print 'PINGKICK: High ping autokick not enabled'
  
   # Autokick is enabled, so hook it up:
   else:
      if bfno_debug or bfno_debug: print 'PINGKICK: Enabling kick for high ping. Limit = %s, max warnings = %s.' % (pinglimit, warnings)

      # Register 'PlayerConnect' callback:
      host.registerHandler('PlayerConnect', onPlayerConnect, 1)
      host.registerHandler('PlayerSpawn', onPlayerSpawn, 1)
      host.registerGameStatusHandler(onGameStatusChanged)
  
      # Reset TK Warnings
      for p in bf2.playerManager.getPlayers():
         onPlayerConnect(p)

      # PingKickTimer:
      enableCheckTimer()
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Get the settings from the bf2.adminsettings object:
# ------------------------------------------------------------------------
def getSettings():
   global autokick,pinglimit,warnings,interval
   # Try to access the autokick setting:
   try:
      if bfno_debug: print "PINGKICK: Reading settings from bf2.adminoptions:"
      autokick  = int(bf2.adminoptions['pk.autoKick'])
      pinglimit = int(bf2.adminoptions['pk.pingLimit'])
      warnings  = int(bf2.adminoptions['pk.warnings'])
      interval  = int(bf2.adminoptions['pk.interval'])
   except:
      if bfno_debug: print "PINGKICK: WARNING - Could not get settings from 'bf2.adminoptions'. Using default values:"
   if bfno_debug:
      print "  autokick  : " + str(autokick)
      print "  pinglimit : " + str(pinglimit)
      print "  maxwarns  : " + str(warnings)
      print "  interval  : " + str(interval)
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Callback function for the "PlayerConnect" event:
# ------------------------------------------------------------------------
def onPlayerConnect(p):
   """Resets the ping warning counter for the player"""
   resetPlayer(p)
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Callback function for the "PlayerSpawn" event:
# ------------------------------------------------------------------------
def onPlayerSpawn(p,s):
   """Sets the 'actuallyPlaying' flag for the player"""
   p.actuallyPlaying = True
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Callback function for the "PreGame" game status event:
# ------------------------------------------------------------------------
def onGameStatusChanged(status):
   """Enables the ping check timer"""
   # Import variables, and update the game status:
   global gamestatus, kicklist, msgtimer
   gamestatus = status
   if gamestatus == bf2.GameStatus.Playing and autokick:
      if bfno_debug: print "PINGKICK: Setting msgtimer"
      msgtimer = bf2.Timer(onMsgTimer,10,1)
   # Return if not PreGame:
   elif gamestatus == bf2.GameStatus.PreGame:
      if bfno_debug: print "PINGKICK: Game status changed to PreGame. Get settings and and re-enabling check-timer"
      # Reset the kicklist
      kicklist = []
      # Read the settings again:
      getSettings()
      # Enable the ping check timer:
      enableCheckTimer()
   elif gamestatus == bf2.GameStatus.EndGame:
      # Reset players:
      resetPlayers()
# ------------------------------------------------------------------------



# ------------------------------------------------------------------------
# Reset the bfno pingscript variables placed in the player object:
# ------------------------------------------------------------------------
def resetPlayers():
   # Reset TK Warnings and more:
   for p in bf2.playerManager.getPlayers():
      resetPlayer(p)
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Reset the bfno pingscript variables placed in the player object:
# ------------------------------------------------------------------------
def resetPlayer(p):
   # Reset TK Warnings and more:
   p.pingWarnings = 0
   p.detectedPing = []
   p.actuallyPlaying = False
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Callback function for timer, that delays a message at round start:
# ------------------------------------------------------------------------
def onMsgTimer(data):
   global msgtimer
   if bfno_debug: print "PINGKICK: Entering onMsgTimer"
   msg = "Activated! Kicking at " + str(pinglimit) + " after " + str(warnings) + " warnings."
   sendMsg(msg)
   msgtimer.destroy()
   msgtimer = None
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Send text messages to the server, using rcon game.sayAll:
# ------------------------------------------------------------------------
def sendMsg(msg):
   # Sure you can edit this, and remove 'bf2.no'. But hey, why not give us the credit? ;-)
   host.rcon_invoke("game.sayAll \"BF2.NO PING KICKER: " + str(msg) + "\"")
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Enable the pktimer:
# ------------------------------------------------------------------------
def enableKickTimer():
   """Enables the timer that runs the 'kickPlayers' functions"""
   global kicktimer
   if g_debug or bfno_debug: print "PINGKICK: Enabling the kicktimer..."
   kicktimer = bf2.Timer(kickPlayers, 5, 1)
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Enable the pktimer:
# ------------------------------------------------------------------------
def enableCheckTimer():
   """Enables the timer that runs the 'checkPing' function"""
   global pktimer
   if pktimer:
      return True
   else:
      # Create the timer:
      if g_debug or bfno_debug: print "PINGKICK: Timer not enabled. Enabling..."
      pktimer = bf2.Timer(checkPing, interval, 1)
      pktimer.setRecurring(interval)
      # Print debug message telling the status of the timer:
      if pktimer:
         if g_debug and bfno_debug: print "PINGKICK: Time = %s" % (pktimer.getTime())
         return True
      else:
         if bfno_debug: print "PINGKICK: Not enabled"
         return False
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Loop all players, checking their ping, kick high-pinger:
# ------------------------------------------------------------------------
def checkPing(data):
   """Checks ping of all players, and kicks high-pingers"""

   global kicklist

   # We only want to check ping during rounds:
   #if gamestatus != bf2.GameStatus.Playing: return
  
   # Make sure our timer is enabled:
   if not enableCheckTimer(): return

   if bfno_debug: print "PINGKICK: Running checkPing"

   # Loop all players
   for p in bf2.playerManager.getPlayers():
      # Do not check players that hasn't completed their "connect" yet:
      #if not p.isConnected(): continue
      if not p.actuallyPlaying: continue
      # Get the player name. We'll use it often:
      name = str(p.getName())
      # Check the ping if the limit hasn't been reached:
      ping = p.getPing()
      # If we have a valid ping, check it, and issue a warning if the ping exceeds the limit:
      if ping:
         #if bfno_debug: print "PINGKICK: " + name + " has " + str(ping) + " in ping and has " + str(p.pingWarnings) + " Ping-warnings"
         if ping > pinglimit:
            p.pingWarnings += 1
            p.detectedPing.append(ping)
            if g_debug or bfno_debug: print "PINGKICK: Player " + name + " has exceeded the ping limit " + str(p.pingWarnings) + " time(s)"
            #sendMsg("WARNING: " + name + " has exeeded the ping limit " + str(p.pingWarnings) + " time(s)")
         # Issue a warning if a player has reached the maximum number of warnings. Will be kicked next time 'checkPing' is run:
         if warnings <= p.pingWarnings:
            sendMsg("Kicking player " + name + " for high ping. (Limit is " + str(pinglimit) + ", player caught with " + str(p.detectedPing) + ".)")
            # Add the player to the kicklist:
            kicklist.append((p.index,p.getProfileId()))
   # Check of we shall enable the kicktimer:
   if len(kicklist):
      enableKickTimer()
# ------------------------------------------------------------------------


# ------------------------------------------------------------------------
# Function run by the kicktimer:
# ------------------------------------------------------------------------
def kickPlayers(data):
   """Loops through 'kicklist', and kicks players accordingly."""
   #print "PINGKICK: kickPlayers..."
   global kicklist,kicktimer
   for i in kicklist:
      p = bf2.playerManager.getPlayerByIndex(i[0])
      # Make sure we're not kicking a player that has gotten a disconnected "ping violater's" index:
      if not p.getProfileId() == i[1]: continue
      # Check if this player has reached the limit for maximum warnongs
      if warnings <= p.pingWarnings:
         # Kick if limit is reached:
         if g_debug or bfno_debug: print "PINGKICK: Kicking player " + p.getName() + " for high ping (" + str(p.detectedPing) + " > "+str(pinglimit)+")"
         result = host.rcon_invoke("admin.kickPlayer " + str(p.index))
   kicklist = []
   kicktimer.destroy()
   kicktimer = None
# ------------------------------------------------------------------------

Updates

Please add a note here if you update the code above.

  • 2005-07-03: Updated code and documentation to v1.2 (Panic)
  • 2005-06-18: Updated code and documentation to v1.1 (Panic)
  • 2005-06-18: Added documentation (Panic)
  • 2005-06-17: Added v1.0 of the code (Panic)