Updated bot control script.

Place for people to discuss public servers, and also for admins to lay out the details of their servers
Post Reply
User avatar
Teppic
Private First Class
Private First Class
Posts: 576
Joined: Mon Mar 07, 2005 10:00 pm
Location: The North Block

Updated bot control script.

Post by Teppic »

The original patches and scripts are here.

The updated script is below, and has the following new features;
  • After finally working out how to deal with bzadmin segfaulting (yes it happens) there is no need for you to have/give a server password
    or point the script to a bzfs log file, making adding bots to remote servers much simpler, it uses the bzadmin output to add/remove bots.

    This does mean that 'adminasker' can no longer kick/ban people, and has been removed from the scripts, same goes for kicking the '/report 4robotplayers' , but it does warn them still, and not change the bots until they do it right.

    There are new routines to keep space on the server for 'real' players. When a player takes up the last slot on a server, a bot quits automatically, and rejoins if they leave.

    When the last player leaves the server, the bots are reset to the default number.

    If someone requests more bots than can fit on the server and leave a slot free, then it waits until a slot is free to add a bot.
There are new command line switches for
  • the max number of players on a server
    number of free slots required for real players
    verbosity level
    -directory option (to pass to bzflag)
Oh yeah, the script

Code: Select all

#!/usr/bin/python
import sys
import os
import getopt
import time

class BzfsWrap:

        def __init__(self):
                self.bzDirectory='/usr/share/bzflag'
                self.maxPlayers=12
                self.keepFree=1
                self.players=0
                self.Observers=[]
                self.server='127.0.0.1'
                self.port='5154'
                self.password='password'
                self.exePath='/usr/bin/'
                self.botAbusers=[]
                self.reportAbusers={}
                self.abuseFile=''
                self.maxBots=6
                self.startBots=3
                self.lastBots=3
                self.echo=0
                self.helpstring="Wrapper Options\
\n-a or --serveraddress <server ip>: Listen ip of server, default <127.0.0.1>\
\n-A or --abusefile <path>:File containg callsigns of players known to abuse the autobot scripts.\
\n-d or --display <X display>: Set the DISPLAY environment variable, so script can run in tty with robots in X\
\n-D or --directory <path>: Give -directory <path> option to bzrobot, default </usr/share/bzflag>\
\n-e or --exepath <path>: Path to bz executables folder, default </usr/bin>\
\n-E or --echo : Echo server output to stdout\
\n-m or --maxbots <int>: Maximum number of bots to spawn on a server, default <6> max <19> (0=option off)\
\n-M or --maxplayers <int>: The maximum Number of players allowed on the server, used to leave space for real players to spawn, default <12>\
\n-p or --port <port>: Listen port of server, default <5154>\
\n-s or --startbots <int>: Number of bots to spawn on server startup, default <3>\
\n-S or --slotsfree <int>: Number of player slots to keep free for real players, default <1>\
\n-v or --verbose <int>: Debug level 0-3, default <0>\
\n-h or --help : Show this helpfile\n"

                self.debug=0
                self.setvars()
                self.maxBots+=1
                self.bots=0
                self.confno=0
                self.confs={}
                self.botpid={}
                self.adminpid=''
                self.botnames=("","Kryten", "Bishop", "Hal", "Tweeky", "Bender", "Rachel","Sonny","C3P0","ASIMO","Data","robot1","robot2","robot3","robot4","robot5","robot6","robot7")
                a=0
                if self.abuseFile:
                        try:
                                abfile=open(self.abuseFile,'r')
                                for player in abfile.readlines():
                                        self.botAbusers.append(player[:-1])
                                abfile.close()
                        except IOError:
                                os.spawnlp(os.P_WAIT,'touch %s' %self.abuseFile)
        #enddef

        def setvars(self):
                letters = 'a:A:d:D:e:Em:M:p:s:S:v:h'
                keywords = ['slotsfree=','maxplayers=','directory=','serveraddress=','port=','password=','exepath=','maxbots=','startbots=','display=','abusefile=','echo','help','verbose=']
                opts, extraparams = getopt.getopt(sys.argv[1:],letters,keywords)
                if self.debug:
                        print 'Opts:',opts
                        print 'Extra parameters:',extraparams
                for opt,val in opts:
                        if opt in ['-a','--serveraddress']:
                                self.server=val
                        elif opt in ['-p','--port']:
                                self.port=int(val)
                        elif opt in ['-P', '--password']:
                                self.password=val
                        elif opt in ['-e', '--exepath']:
                                self.exePath=val
                        elif opt in ['-m', '--maxbots']:
                                self.maxBots=int(val)
                        elif opt in ['-M', '--maxplayers']:
                                self.maxPlayers=int(val)
                        elif opt in ['-s','--startBots']:
                                self.startBots=int(val)
                                self.lastBots=self.startBots
                        elif opt in ['-A','--abusefile']:
                                self.abuseFile=val
                        elif opt in ['-S','--slotsfree']:
                                self.keepFree=int(val)
                        elif opt in ['-v','--verbose']:
                                self.debug=int(val)
                        elif opt in ['-D','--directory']:
                                self.bzDirectory=val
                        elif opt in ['-d','--display']:
                                os.environ['DISPLAY']=val
                        elif opt in ['-E','--echo']:
                                self.echo=1
                        elif opt in ['-h','--help']:
                                print self.helpstring
                                sys.exit()
                if extraparams:
                        print "Unknown option: %s" % extraparams
        #enddef

        def watch(self,line):
                self.remServerCheck()
                line=line[:-1]
                if self.debug>2: print self.botpid
                if self.echo:print line
                if self.maxBots:self.watchbots(line)
                self.checkLevels(line)
        #enddef

        def checkLevels(self,line):
                players=self.players
                found=line.find('joined the game as')
                if found>1:
                        name=self.getPlayerName(line)
                        if line.find('joined the game as Observer')>1:
                                if name not in self.Observers and name!='Bot Control-v1.4' :self.Observers.append(name)
                        elif name not in self.botnames:
                                self.players+=1
                else:
                        found=line.find('left the game.')
                        if found>1:
                                name=self.getPlayerName(line)
                                if name in self.Observers:
                                        while name in self.Observers:
                                                self.Observers.remove(self.getPlayerName(line))
                                elif name not in self.botnames:
                                        self.players-=1
                                        if int(self.players)<0:self.players=0
                if players!=self.players:
                        if (self.bots+self.players)>(self.maxPlayers-self.keepFree):
                                self.addbots(int(self.bots)-1)
                        elif int(players)>int(self.players) and int(self.bots)<int(self.lastBots):
                                self.addbots(int(self.bots)+1)
                if self.players==0 and int(self.bots)<int(self.startBots):
                        self.lastBots=self.startBots
                        self.addbots(int(self.startBots))
                if self.debug>2:print 'Players=%s Bots=%s Observers=%s' % (self.players,self.bots,self.Observers)
        #enddef

        def getPlayerName(self,line):
                return line[line.find('\'')+1:line[line.find('\'')+1:].find('\'')+line.find('\'')+1]
        #enddef

        def watchbots(self,line):
                found=line.find('robotplayers')
                if found>1:
                        if line[(found-2)] in ['1','2','3','4','5','6','7','8','9']:
                                pnum=line[(found-2):(found)]
                                if self.debug>1:print num;print line
                        else:
                                pnum=line[(found-1)]
                        try:
                                num=int(pnum)
                        except:
                                return
                        playername=line.strip()
                        playername=playername[:playername.find(':')]
                        player=self.checkAbuse(playername)
                        if self.debug: print 'Abusive player check returned %s' %player
                        if player <> 0:
                                self.sendMsg('/msg "'+player+'" '+' You no longer have permission to change the number of bots on this server.')
                                return
                        if line.find('/report')>0:
                                if self.reportAbusers.has_key(playername):
                                        self.reportAbusers[playername]+=1
                                else:
                                        self.reportAbusers[playername]=1
                                if self.reportAbusers[playername]<6:
                                        self.sendMsg('/msg "'+playername+'" '+' Please do not use /report to alter the number of bots, thank you.')
                                else:
                                        self.sendMsg('/msg "'+playername+'" '+' DO NOT use /report to alter bots, you are now unable to change the bots, you were warned.')

                                        #self.reportAbusers[playername]=2

                                        if self.abuseFile=='':return
                                        self.botAbusers.append(playername)
                                return
                        if int(num) > int(self.maxBots-1):
                                ##server message
                                self.sendMsg('/msg "'+playername+'" '+' The maximum number of robots is currently set to %s' % (self.maxBots-1))
                                return
                        self.lastBots=num #store the 'requested' number of bots
                        if int(num)>0 and (int(num)+int(self.players)>(self.maxPlayers-self.keepFree)): #if above our keepFree threshold
                                orig=int(num)
                                num=int(num)
                                while int(num)+int(self.players)>(self.maxPlayers-self.keepFree):
                                        num-=1
                                self.sendMsg('/msg "'+playername+'" '+' %s robots makes too many players, I\'ll add them if someone leaves.' % (orig))
                                if int(num)>int(self.bots):
                                        self.sendMsg('/msg "'+playername+'" '+' Setting %s robots for now.' % (num))
                        self.addbots(num)
                        return
                found=line.find('norobotsplease')
                if found>=0:
                        for i in self.botpid:
                                try:
                                        os.kill(self.botpid[i],9)
                                        self.remdefunct(self.botpid[i])
                                        if self.debug:print 'killed %s (%s)' %(self.botpid[i],self.botnames[i])
                                except:
                                        pass
                        self.bots=0
                        self.lastBots=0
                        return
                if self.abuseFile=='':return
                found=line.find('nobotcontrolfor {')
                if found>0:
                        playername=line[line.find('{')+1:line.find('}')]
                        if self.debug>1:print 'adding player "'+playername+'" to abuse list'
                        self.botAbusers.append(playername)
                        perm=line.find('permanently')
                        if perm >0:
                                self.sendMsg('/say Player "'+playername+'" was permanently added to the robot command ban list.')
                                if self.debug>1:print 'added player "'+playername+'" to bot abuse file'
                                abfile=open(self.abuseFile,'a')
                                abfile.write(playername+'\n')
                                abfile.close()
                        else:
                                self.sendMsg('/say Player "'+playername+'" was added to the robot command ban list.')
        #enddef

        def remdefunct(self,pid):
                while 1:
                        try: os.waitpid(pid,os.WNOHANG)
                        except : return
        #enddef

        def addbots(self,num):
                if int(num)==int(self.bots):
                        for a in range(1,int(num)+1):
                                try:os.kill (self.botpid[a],9)
                                except: pass
                                try:self.remdefunct(self.botpid[a])
                                except: pass
                                self.botpid[a]=os.spawnvp(os.P_NOWAIT,self.exePath+'/bzrobot',['bzrobot','-window','-directory',self.bzDirectory,'-team','auto','-g','10x10','%s@%s:%s' % (self.botnames[a], self.server, self.port)])
                                if self.debug:
                                        print 'killed %s (%s)' %(self.botpid[a],self.botnames[a])
                                        print 'Spawned '+self.botnames[a]
                        self.bots=num

                elif int(num) > int(self.bots):
                        for a in range(int(self.bots)+1, int(num)+1):
                                self.botpid[a]=os.spawnvp(os.P_NOWAIT,self.exePath+'/bzrobot',['bzrobot','-window','-directory',self.bzDirectory,'-team','auto','-g','10x10','%s@%s:%s' % (self.botnames[a], self.server, self.port)])
                                if self.debug:
                                        print 'Spawned '+self.botnames[a]
                        self.bots=num

                elif int(num) < int(self.bots):
                        for a in range(int(num)+1, int(self.bots)+1):
                                try:os.kill(self.botpid[a],9)
                                except:pass
                                self.remdefunct(self.botpid[a])
                                if self.debug:
                                        print 'killed %s (%s)' %(self.botpid[a],self.botnames[a])
                        self.bots=num
                #endif
                return
        #enddef

        def remServerCheck(self):
                line=''
                try:
                        inp,stest=os.popen4('/bin/ps|/bin/grep -a bzadmin','r')
                        line=stest.readline()
                        pid=line.lstrip()[0:line.find(' ')]
                        line=line[line.find('bzadmin'):-1]
                        #print line
                except:
                        #print 'Error'
                        pass
                if line=='bzadmin':
                        return
                elif line=='bzadmin <defunct>' or line=='':
                        try:self.say.close()  ##should reap the bzadmin command
                        except AttributeError: pass
                        self.reportAbusers={}
                        self.spawnAdmin()
                else:
                        print 'Returned:%s' % stest

        def spawnAdmin(self):
                try:
                        self.say.write('')
                        self.say.flush()
                except :
                        self.say,self.read=os.popen4(self.exePath+'/bzadmin -ui stdboth "Bot Control-v1.4"@%s:%s' % (self.server,self.port))
                        try:
                                self.say.write('Whoa, WTF just happened?\n')
                                self.say.flush()
                        except IOError:
                                        ##pipe broken on startup
                                        ##leave for next servercheck
                                        pass
                        return #no or closed pipe instance taken care of
                #endtry
                return#the pipe is fine

        #enddef

        def sendMsg(self,message=''):
                self.spawnAdmin()#check we exist
                self.say.write('%s \n' % message)
                self.say.flush()
                return

        #enddef

        def checkAbuse(self,line):
                for player in self.botAbusers:
                        if self.debug>1:
                                print player
                                print line
                        if player in  [line]:
                                return line
                return 0

         #enddef


        def runme(self):
                self.say=()
                self.spawnAdmin()
                while True:#nasty loop
                        line=self.read.readline()
                        self.watch(line)

        #enddef

def main ():
        run=BzfsWrap()
        run.runme()
#enddef


main()
Save it, chmod a+x it, the run with -h to see all the options.
This script does require you have a bzrobot binary, check the thread in the link at the top for how to get one. It's still beta, so report bugs if it won't work.
Post Reply