Scripts:dc ass

From BF2 Technical Information Wiki
Revision as of 15:25, 22 June 2018 by Pireax (talk | contribs) (Created page with "__TOC__ == Introduction == The DontCamp Administrative Support System is a completely server-side, python based admin system for Battlefield 2 that uses in-game chat comman...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Introduction

The DontCamp Administrative Support System is a completely server-side, python based admin system for Battlefield 2 that uses in-game chat commands and cd keyhash authentication to provide easy to use and powerful administrative functions.

Some of the better features of dc_ass are:

  • kicking by playername, partial playername, or player ID
  • single command multiple kick options against clan tags or team number (only used for evil, I know)
  • variable userlevels via a PB style power points system
  • allows the general public to vote kick someone without the annoying prompts
  • completely an in-game and fast admin system so you can spend your time playing

The code

dc_ass.py

"""
DontCamp.com Administrative Support System v. 1.2.1
written by: [email protected]
 
Changelog:
    1.2 beta:
        * added support for multiple arguments in a chat command
        * no longer spews so much to the screen when someone is kicked
    1.1.1 beta:
        * playernames with prefixs can now issue commands
    1.1 beta:
        * added userlevels via a kick points system a la PB power users
        * added general public vote kicking
        * using keyhash rather than PB GUID for auth
        * moved to more OO codebase
        * extensions can now act on anything in the dc_ass object
 
Acknowledgements:
    Thanks to [email protected] for significant coding contributions.
    Thanks to [email protected] for his conceptual contributions.
    Thanks to my brother for helping me with fundamental coding concepts.
    Thanks to the guy that was willing to sell me a T40 ThinkPad on eBay for
        that insanely low price!
    Dear God, thanks be to O'Reilly!
    And a REALLY BIG thanks to Kevin Lockitt of blackbagops.com. This software
        is clearly a big stinkin' rip off of BFSM, a program that did as much
        as anything to make DontCamp.com the successful gaming community that
        it is.
 
Purpose:
    To provide an in-game chat based admin system similar to Kevin Lockitt's
    Battlefield Server Manager while also providing a kick vote system
    similar to PunkBuster's Poweruser system.
 
Requirements:
    1. dc_debug.py - a common debugging function for all DontCamp.com BF2
       python level tools
    2. dc_ass_extensions.py - this file is currently required to exist but
       can be empty. See dc_ass_extensions Instructions.
    3. dc_ass_cmds.ini - the configuration file for all in-game admin commands
    4. power.dat - a list of your admins' power levels and keyhashs
 
Known issues:
    1. writeToLogfile() is not currently tested or up-to-date and is therefore
        disabled.
    2. In general be very careful what you code in an extension. The BF2
       server may lag or crash when running poorly written extensions.
    3. dc_ass currently requires punkbuster to be enabled.
    4. There is almost no sanity checking for poorly configured INI files.
    5. Non permanent bans are not maintained between server restarts.
 
Setup:
    1. Drop dc_ass.py and dc_debug.py into the admin/standard_admin directory
       of your BF2 installation.
    2. Edit admin/standard_admin/__init__.py file to include these lines:
 
           import dc_ass
           dc_ass.init()
 
    3. Create an empty dc_ass_extensions.py file or populate it as per the
       dc_ass_extensions instructions below.
    4. Create a power.dat file as per the instructions below.
    5. Create / edit a dc_ass_cmds.ini file as per the instructions below.
 
power.dat Instructions:
    This file is simply a space delimited file with two values on each line
    that must exist in the admin/standard_admin directory. The first value is
    the power level assignment and the second value is the keyhash.
 
    Currently dc_ass is lacking some flexibily in that 10 kick points always
    causes a player to be kicked. Keep that in mind when assigning power
    levels.    The power level must be a positive integer but can otherwise be
    whatever you like.
 
    The keyhash for a user is a 32 digit hexidecimal number that be found when
    executing admin.listplayers in the console. I have written a dc_ass
    extensions that prints the keyhash for a user while in-game and I will like
    make that extensions available in the future. Please note that BF2
    keyhashes are NOT the same thing as PB GUID's.
 
    An example power.dat file:
 
    10 352a507c78da1dea6dd42a5867d3c2cc
    4 2365c8ef755723efc10b425fffb65ebe
 
    This examples shows that the player with the keyhash:
    352a507c78da1dea6dd42a5867d3c2cc can apply 10 kick points to each player
    and that the player with the keyhash: 2365c8ef755723efc10b425fffb65ebe can
    apply 4 kick points to each player.
   
    Since 10 kick points kicks a user, we can assume that any admin with a
    kick power of 10 is a full admin and therefore they can run "admin"
    commands. See the section on the dc_ass_cmds.ini file for more info.
   
dc_ass_cmds.ini Instructions:
    This file follows standard INI file formatting. Each section name is the
    name of a command that can be run from in-game text chat. Each section
    must have a "type" option. Supported types are:
        kick - a simple kick
        ban - a permanent ban (or possibly temporary)
        rcon - user defined rcon command execution
        extension - user defined python function execution
 
    Depending on the type, different options are required. Each type supports
    the "admin" option. When admin = 1, the user must have a kick power of 10
    to execute that command. When admin = 0, any user on your BF2 server is
    allowed to execute that command. The admin option is optional and defaults
    to 1.
 
    (* = required)
    Type: kick
    Options:
        *reason = (a non-delimited string contained the reason for this kick)
        length = (an integer representing how many minutes before the
            the offender will be allowed back into the game. If this option is
            missing, the default value will be 2.)
    Type: ban
    Options:
        *reason = (same as above)
        length = (same as above) (see known issue #5)
 
    Type: rcon
    Options:        
        *command = (a non-delimited string contained the rcon command you want
            to issue)
 
    Type: extension
    Options:
        *function = (the name of the function in dc_ass_extensions.py you want
            this command to run WITHOUT any (). See dc_ass_extensions
             Instructions.)
 
    An example dc_ass_cmds.ini file might look like this:
 
        [kfms]
        type = kick
        reason = for firing from a vehicle in/into an uncapturable spawn
        length = 5
 
        [brsc]
        type = ban
        reason = for a racist or slanderous comment
 
        [restart]
        type = rcon
        command = admin.restartmap
 
        [slap]
        type = extension
        function = slapPlayer
 
        [nextmap]
        type = extension
        function = sayNextMap
        admin = 0
 
    NOTE: Changes made to the dc_ass_cmds.ini file are in effect immediately
    and do not require a server restart.
 
dc_ass_extensions.py Instructions:
    The extensions provide a means to do more complex actions that cannot be
    achieved through a simple rcon command. This file works similarly to other
    python files inside the BF2 environment. It's simply a list of functions
    as well as the proper imports to provide the facility the functions
    require.
 
    NOTE: Each function that is run as an extension must accept one and only
    one parameter. That parameter will be the dc_ass admin object most noteably
    containing these properties:
   
    victim_id - a list of all integers of the victim IDs found via the
        parameter for this the command executed
       
    victim_tracker - a dictionary of all the people currently being tracked
        by dc_ass (in other words, people that have had kick points issued
        against    them). This is a multidemensional dictionary where the top
        level key is the player_id / slot number and value is another
        dictionary where the key is the keyhash of the issuer of the points and
        the value is the number of points issued by said issuer.
   
    issuer - an integer representing the player_id of the person that executed
        the current command.
       
    keyhash - a string that is the hexidecimal keyhash of the person that
        executed the current command.
 
    See http://dontcamp.com/code/ for an example dc_ass_extenions.py file.
 
    Note: Changes made to the dc_ass_extensions.py file are in effect
        immediately and do not require a server restart.
 
How to use all this once you've got it setup:
    All commands are issued with in-game chat, NOT console commands. To my
    knowledge, these commands should work in any chat mode. Each command MUST
    be prefixed with an ! and optionally suffixed with a space followed by a
    case insensitive playername, partial playername, or a . followed by a
    player number. Additionally, you may prefex an @ in front of a case
    insensitive string to execute an action against all players containing
    that string in their playername. You may also use a % followed by a team
    number to execute a command against an entire team.
 
    Using our previous example INI file and given that the server has two
    players on it: Butter (in slot 0) and MuttDog (in slot 1) here's how the
    following commands would react...
 
        !kfms
            [ Whenever no parameter for a victim is given, dc_ass will
              assume the player ID of the admin issuing the command. In this
              case the admin will be kicked for 5 minutes for firing into
              a main spawn. ]
 
        !kfms .0
            [ This will kick Butter for 5 minutes for firing into a main spawn
              because butter is player number 0 / in slot 0. ]
 
        !kfms butter
            [ This will also kick Butter because butter matches Butter in our
              case insensitive string comparison. ]
 
        !kfms ter
            [ This will also kick Butter because ter is a string match in
              Butter's name and ONLY in Butter's name. ]
 
        !kfms utt
            [ This will kick no one but print a global message in green at the
              top left of the screen that utt is ambiguous. ]
 
        !kfms dst
            [ This will kick no one and print a global message saying "Could
              not find ID for dst". ]
 
        !kfms @t
            [ This will kick both dst and butter as they both have a t in their
              their playername. ]
 
        !kfms @T
            [ This will still kick both dst and butter because our text search
              is case insensitive. ]
 
        !kfms %2
            [ This will kick everyone on team 2. ]
"""
 
 
import bf2
import host
import re
import ConfigParser
import time
from dc_debug import decho
from dc_debug import showLevel
import dc_ass_extensions
 
class ass:
    # some init vars
    victim_tracker = {}
    admin_level = 10
    default_weight = 2
    kick_threshold = 10
 
    """
        _getPlayerList(): returns a dictionary of players currently playing
        where the key is their player player_id/index and the value is list
        containing their playername, IP, outbound port, and CD-key hash.
        Thanks to Woody for doing all the hard work on this one.
    """
    def _getPlayerList(self):
        rawData = host.rcon_invoke( 'admin.listplayers' )
        # this patern is for line 0 of the output for each player
        # pattern1 = re.compile(r'''^Id:\ +(\d+)\ -\ (\S+)\ is\ remote\ ip:\ (\d+\.\d+\.\d+\.\d+):(\d+)''', re.VERBOSE)
        pattern1 = re.compile(r'''^Id:\ +(\d+)\ -\ (.*?)\ is\ remote\ ip:\ (\d+\.\d+\.\d+\.\d+):(\d+)''', re.VERBOSE)
        # this patern is for line 1 of the output for each player
        pattern2 = re.compile(r'''(?:.*hash:\ (\w{32}))?''', re.VERBOSE)
        players = {}
       
        i = 0
        for line in rawData.split("\n"):
            # if we're looking at a "line 0"
            if i == 0:
                matches = pattern1.findall(line)
                if len(matches) != 0:
                    p_id = int(matches[0][0])
                    players[p_id] = []
                    players[p_id].append(matches[0][1])
                    players[p_id].append(matches[0][2])
                    players[p_id].append(matches[0][3])
 
            # if we're looking at a "line 1"
            elif i == 1:
                matches = pattern2.findall(line)
                players[p_id].append(matches[0])
 
            # flop the value of the iter
            i ^= 1
       
        return players
 
 
    """
        _getWeight(): finds the admin level of the user at index
        returns true on success, false on failure (the true case being that a user is found at index)
        assigns string self.keyhash as the keyhash of the user at index
        assigns int self.user_weight as the admin level of the user at index
    """
    def _getWeight(self, index):
        return_value = False
        players = self._getPlayerList()
        self.user_weight = self.default_weight
 
        if players.has_key(index):
            return_value = True
            self.keyhash = players[index][3]
            # assigns the default weight in case this user is not found in the pbpower.dat file
            decho( "dc_ass: Attempting to authenticate keyhash: %s" % self.keyhash, 5 )
 
            # just in case there is no pbpower.dat file
            try:
                for line in open('admin/standard_admin/power.dat', 'r'):
                    if self.keyhash == line.split(' ')[1].strip():
                        self.user_weight = int(line.split(' ')[0].strip())
                        decho( "dc_ass: %s is level %d in power.dat!" % (self.keyhash, self.user_weight), 5 )
                        break
 
            except IOError:
                decho( "dc_ass: unable to open pb/pbpower.dat file", 5 )
 
        return return_value
 
 
    """
        _getVictimId(): finds all victims from the in-game chat command parameter
        returns true on success and false on failure (the failure case is not being able to determine with certainly which victims the command is for)
        assigns list self.victim_id
    """
    def _getVictimId(self, parameter):
        # returns true/false on success/failure and assigns self.victim_id if possible
        # self.victim_id is a list of all victims found
        # sorry for the silly parameter name, but it does make sense in this case.
 
        self.victim_id = []
        result = False # default value for result
        decho("dc_ass: Attempting to find victim ID...", 5)
       
        # if the parameter is the player number
        if parameter[0:1] == ".":
            id = int(parameter[1:])
   
            for p in bf2.playerManager.getPlayers():
                if p.index == id:
                    self.victim_id = [id]
                    decho( "dc_ass: parameter was an int, and id = %d" % self.victim_id[0], 5 )
                    result = True   
                    break
 
            if not result:
                decho( "dc_ass: (ERROR) no player found with ID: %d" % id, 1 )
           
        # if the parameter is a wildcard / clantag
        elif parameter[0:1] == "@":
            decho( "dc_ass: finding ALL matching players with parameter...", 5 )
            decho( "dc_ass: parameter was NOT an int", 5 )
 
            # for all players connected...
            for p in bf2.playerManager.getPlayers():
                decho( "dc_ass: checking player: %s" % p.getName(), 5 )
                # if we find a name that loosely matches...
                if p.getName().lower().find( parameter[1:].lower() ) != -1:
                    decho( "dc_ass: %s loosely matches %s" % ( parameter[1:], p.getName() ), 5 )
                    self.victim_id.append(p.index)
                    result = True
 
            if result:
                decho( "dc_ass: Victim ID(s) found @ %r" % self.victim_id, 5 )
            else:
                decho( "dc_ass: (ERROR) no players found with %s in their name" % parameter[1:], 1 )
       
        # if the parameter is a team number
        elif parameter[0:1] == "%":
            decho( "dc_ass: finding ALL matching players on team matching paramenter...", 5 )
           
            # for all players connected...
            for p in bf2.playerManager.getPlayers():
                decho( "dc_ass: checking player: %s" % p.getName(), 5 )
                # if we find a player on the team supplied
                if p.getTeam() == int(parameter[1:]):
                    decho( "dc_ass: %s is on team %s" % ( p.getName(), p.getTeam() ), 5 )
                    self.victim_id.append(p.index)
                    result = True               
 
            if result:
                decho( "dc_ass: Victim ID(s) found @ %r" % self.victim_id, 5 )
            else:
                decho( "dc_ass: (ERROR) there seems to be no one on team %s" % parameter[1:], 1 )
                   
        # if the parameter is a playername or partial playername
        else:
            id = -1
            decho("dc_ass: parameter was NOT an int", 5)
            # for all players connected...
            for p in bf2.playerManager.getPlayers():
                decho( "dc_ass: checking player: %s" % p.getName(), 5 )
                # if we find a name that loosely matches...
                if p.getName().lower().find( parameter.lower() ) != -1:
                    decho( "dc_ass: %s loosely matches %s" % ( parameter, p.getName() ), 5 )
                    # if this is the first victim we've found...
                    if id == -1:
                        decho("dc_ass: found %s in %s" % ( parameter, p.getName() ), 5)
                        id = [int(p.index)]
                        result = True
                    # if we've gotten another possible match...
                    else:
                        result = False
                        break
 
            if result:
                self.victim_id = id
                decho( "dc_ass: Victim ID found @ %d" % self.victim_id[0], 5 )
            else:
                decho( "dc_ass: %s is ambiguous." % parameter, 1 )
 
        return result
 
    """
    def writeLogFile(self, victim_id, admin_id, type, reason, length):
        v = bf2.PlayerManager.Player(victim_id)
        a = bf2.PlayerManager.Player(admin_id)
        decho("\"%s\",%s,%d,%s,%d,%s,%s,%d,%s\n" % (v.getName(), v.getAddress(), v.getProfileId(), a.getName(), int(time.time()), bf2.gameLogic.getMapName(), type, reason, length * 60), 5)
 
        fo = open('admin/standard_admin/adminlog.csv', 'a')
        # v.name, v.ip, v.profileId, a.name, timestamp, map, type, length, reason
        fo.write("\"%s\",%s,%d,%s,%d,%s,%s,%d,%s\n" % (v.getName(), v.getAddress(), v.getProfileId(), a.getName(), int(time.time()), bf2.gameLogic.getMapName(), type, reason, length * 60))
        fo.close()
    """
 
 
    """
        _stripPrefix(): removes context prefixes from a chati lines and returns the trimmed chat line
    """
    def _stripPrefix(self, text):
        text = text.replace( "HUD_TEXT_CHAT_TEAM", "" )
        text = text.replace( "HUD_TEXT_CHAT_SQUAD", "" )
        text = text.replace( "HUD_CHAT_DEADPREFIX", "" )
        text = text.replace( "*\xA71DEAD\xA70*", "" )
 
        return text.strip()
 
 
    def addPoints(self):
        # add entries in the victim_tracker foreach victim
        for v_id in self.victim_id:
            # check to see if we're already tracking the joker
            if self.victim_tracker.has_key(v_id):
                decho( "dc_ass: already tracking %d" % v_id, 5 )
                # check to see if this admin has already applied their weight to the victim
                if not self.victim_tracker[v_id].has_key(self.keyhash):
                    self.victim_tracker[v_id][self.keyhash] = self.user_weight
                    # added this conditional to make it a bit less noisy when someone is just kicked with one action
                    if self.user_weight < self.kick_threshold:
                        decho( "dc_ass: applied %d points from %s to player %d" % (self.user_weight, self.keyhash, v_id), 5 )
                # this elif was really added to deal with an extension used at DontCamp.com but it makes sense to have anyway
                elif self.user_weight == self.kick_threshold:
                    self.victim_tracker[v_id][self.keyhash] = self.user_weight
                else:
                    decho( "dc_ass: %s has already applied their points to %s" % (bf2.PlayerManager.Player(self.issuer).getName(), bf2.PlayerManager.Player(v_id).getName()), 1 )
            else:
                self.victim_tracker[v_id] = {}
                decho( "dc_ass: now tracking player: %d" % v_id, 5 )
                self.victim_tracker[v_id][self.keyhash] = self.user_weight
                decho( "dc_ass: applied %d points from %s to player %d" % (self.user_weight, self.keyhash, v_id), 5 )
 
 
    def checkPoints(self):
        for index, tracking in self.victim_tracker.iteritems():
            # reset total_points for this victim
            total_points = 0
 
            # added up all the points being applied to this victim
            for unused_key, points in tracking.iteritems():
                total_points += points   
               
            if index in self.victim_id:
                decho( "dc_ass: %s now has %d of %d kick points" % (bf2.PlayerManager.Player(index).getName(), total_points, self.kick_threshold), 1 )
 
            if total_points >= self.kick_threshold:
                self.reason += " - see dontcamp.com/forums for discussion"
                # issue the kick command
                decho( "dc_ass: Kicking player '%s' (%d) %s" % (bf2.PlayerManager.Player(index).getName(), index, self.reason), 1 )
                unused = host.rcon_invoke('pb_sv_kick %d %d %s' % (index + 1, self.length, self.reason) )
 
    # it might be helpful to create a new object to pass to extensions that
    # has a reference to the admin object. In that new object, that I hope
    # would only exist during the execution of a command, things like the
    # issuer and keyhash properties could be assigned.
    def onChatMessage(self, player_id, text, channel, flags):
        self.issuer = player_id
        # pull the potential prefix off the text line
        text = self._stripPrefix(text)
       
        # unless the first character is ! don't do anything
        if text[0:1] == "!":
            decho( "dc_ass: the first character of %s was !" % text, 5 )
 
            # As BF2 playernames cannot contain spaces except what is printed
            # between the prefix and the name, it is probably safe to assume
            # that we don't need to accept spaces in playernames for this
            # parameter. Therefore, I could use the space as a separator
            # between the victim name and another command parameter, like
            # a custom reason.
 
            # grab the parts of the chatline I need with a REGEX
            pattern = re.compile(r'!(\w*) ?(\S*) ?(.*)')
            matches = pattern.findall(text)
            command = matches[0][0]
            # set an arguments property for future use or for extensions
            self.arguments = matches[0][2]
            decho( "dc_ass: command = %s" % command, 5 ) 
 
            # in case the command doesn't require a victim parameter...
            if matches[0][1] != "":
                parameter = matches[0][1]
                decho( "dc_ass: parameter = %s" % parameter, 5 )
            else:
                parameter = None
                decho( "dc_ass: no parameter given", 5 )
 
            try: # read in the commands INI file
                self.config = ConfigParser.ConfigParser()
                self.config.read('admin/standard_admin/dc_ass_cmds.ini')
 
                # is this a defined command in the INI file
                if command in self.config.sections():
                    decho( "dc_ass: Command found in INI file!", 5 )
                    # define/declare some vars for this admin event (I really wouldn't need to do this if I know how to check if a var is defined or even exists
                    type = ""
                    # these values will be members of the object because they may be needed in an extension
                    self.length = 2 # the default length for a kick
                    self.reason = ""
                    admin = 1 # this makes all commands default to requiring admin privs
 
                    # get all possible options
                    # I could add sanity checks in here for poorly formated lines in the INI file
                    for option in self.config.options(command):
                        if option == "type":
                            type = self.config.get(command, option).strip()
                        elif option == "reason":
                            self.reason = self.config.get(command, option).strip()
                        elif option == "length":
                            self.length = int(self.config.get(command, option))
                        elif option == "command":
                            rcon_string = self.config.get(command, option).strip()
                        elif option == "admin":
                            admin = int(self.config.get(command, option))
                        elif option == "function":
                            function = self.config.get(command, option).strip()
 
                    # make sure we have a type
                    if type == "":
                        decho( "dc_ass: (ERROR) No type value for %s in dc_ass_cmds.ini file!" % command, 1 )
                   
                    # good, we're all set
                    else:
                        decho( "dc_ass: type: %s" % type, 5 )
 
                        # to get their keyhash and admin level
                        if self._getWeight(player_id):
 
                            # see if we can/need to get a victim ID to move forward
                            if ( parameter != None and self._getVictimId(parameter) ) or parameter == None:
 
                                # for non-perma bans, change type to kick_ban - this will be useful when we start to log this stuff
                                if type == "ban" and self.length != "":
                                    decho( "dc_ass: Length value given for ban. Converting type to kick_ban.", 5 )
                                    type = "kick_ban"
 
                                # if no parameter was given, make the command reflect on the issuer
                                if parameter == None:
                                    decho( "dc_ass: No parameter given... assigning victim_id to issuer.", 5 )
                                    self.victim_id = [player_id]
 
                                if ( admin and self.user_weight == self.admin_level ) or not admin:
 
                                    # the main switch like section that gets the shit done (if only I had switches)
                                    # Are reasons optional in these PB kicks/bans? If so, I guess I don't need to worry about sanity checking them
 
                                    if type == "kick" or type == "kick_ban":
                                        self.addPoints()
                                        self.checkPoints()
 
                                    elif type == "ban":
                                        for v_id in self.victim_id:
                                            decho( "dc_ass: Banning player '%s' (%d) %s" % (bf2.PlayerManager.Player(v_id).getName(), v_id, self.reason), 1 )
                                            unused = host.rcon_invoke( 'pb_sv_ban %d %s' % (v_id + 1, self.reason) )
           
                                    elif type == "rcon":
                                        decho( "dc_ass: Running rcon command: %s" % rcon_string, 2 )
                                        unused = host.rcon_invoke( rcon_string )
           
                                    elif type == "extension":
                                        reload( dc_ass_extensions )
                                        if function in dc_ass_extensions.__dict__:
                                            decho( "dc_ass: executing %s() extension" % function, 2 )
                                            # send the entire object to the extensions
                                            exec( "dc_ass_extensions.%s(%s)" % (function, "self") )
                                        else:
                                            decho( "dc_ass: %s() extension not found." % function, 2 )
 
                                    else:
                                        decho( "dc_ass: No such type: %s" % type, 1 )
 
                                else:
                                    decho ( "dc_ass: User in slot %d is not authorized for the requested action" % player_id, 1 )
 
                            else:
                                decho( "dc_ass: Victim ID not findable given the parameter", 2 )
 
                        else:
                            decho( "dc_ass: ERROR failed to get keyhash for player: %d" % player_id, 1 )
                               
                else: # end of is command in INI file conditional
                    decho( "dc_ass: (ERROR) %s command not found!" % command, 1 )
       
            except IOError:
                print "dc_ass: (FATAL ERROR) could not open one or more required files: (admin_profile_ids.csv | dc_ass_cmds.ini)"
 
        else: # a debug line just so I can see all the chat if I need to
            decho( "dc_ass: player %d: %s" % (player_id, text), 5 )
           
    def onPlayerDisconnect(self, p):
        # if they disconnect, which might have happened if they were kicked, drop them from the tracker, if they're even in it
        if self.victim_tracker.has_key(p.index):
            del(self.victim_tracker[p.index])
            decho( "dc_ass: player %d disconnected and no longer tracked" % p.index, 2 )
 
admin = ass()
 
def init():
    decho('dc_ass: initializing DontCamp.com Admin Support System', 2)
    host.registerHandler('ChatMessage', admin.onChatMessage, 1)
    host.registerHandler('PlayerDisconnect', admin.onPlayerDisconnect, 1)
    showLevel()

The source is downloadable from here.

Change Log

1.2 beta

  • added support for multiple arguments in a chat command
  • no longer spews so much to the screen when someone is kicked

1.1.1 beta

  • playernames with prefixs can now issue commands

1.1 beta

  • added userlevels via a kick points system a la PB power users
  • added general public vote kicking
  • using keyhash rather than PB GUID for auth * moved to more OO codebase
  • extensions can now act on anything in the dc_ass object

dc_debug.py

dc_debug.py is required for nearly all DontCamp BF2 python code. It manages the debuging and logging output of our software.

"""
DontCamp.com Common Debugging Function
written by: [email protected]
Revision: $Revision: 1.11 $
 
Purpose:
    To provide a central way for all DontCamp BF2 python code to output
    debugging and informational text.
 
Setup: (defaults should work well)
 
    1. Create a admin/standard_admin/dc_debug.level file and put the debug
       level you want in the first character of the first line of said file.
       Valid levels are 1-5.
    2. Set log_to_file to either False or True if you want a seperate dc_debug
       log file for all DontCamp BF2 python code debug output.
    3. Set log_filename to whatever filename and path you like (relative to
       your BF2 home directory). This is only necessary if log_to_file is set
       to True.
    4. Set log_to_stdout to either True or False if you want dc_debug to send
       out its output to the python stdout interface. This is useful if you
       are using BF2CC and want dc_debug's output in the pythonlog.txt file.
"""
 
import host
import time
 
try:
    debug_level_fh = open('admin/standard_admin/dc_debug.level', 'r')
    debug_level = int(debug_level_fh.read(1))
    debug_level_fh.close()
   
except:
    debug_level = 5
 
log_to_file = True
log_filename = 'dc_debug.log'
log_to_stdout = False
 
def decho(msg, level = 1):
    # 1 = info w/ in-game feedback
    # 2 = info
    # 5 = debug
 
    if debug_level > 0 and debug_level >= level:
        string = time.strftime('%Y-%m-%d %H:%M:%S') + ' - ' + msg
       
        if log_to_stdout:
            print string
           
        if log_to_file:
            fh = open(log_filename, 'a')
            fh.write(string + '\n')
            fh.close()
               
        if level == 1:
            host.rcon_invoke('game.sayall "%s"' % msg)
 
def showLevel():
    decho('dc_debug: The current debug level is %d' % debug_level, 2)

The source is downloadable from here