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.
- the max number of players on a server
number of free slots required for real players
verbosity level
-directory option (to pass to bzflag)
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()
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.