PageRenderTime 205ms CodeModel.GetById 135ms app.highlight 61ms RepoModel.GetById 1ms app.codeStats 0ms

/ladderslave.py

https://github.com/renemilk/SpringLadder
Python | 743 lines | 696 code | 26 blank | 21 comment | 170 complexity | 79288e9c9b860ed3910e1d140199e700 MD5 | raw file
  1# -*- coding: utf-8 -*-
  2from customlog import *
  3from ParseConfig import *
  4import commands, thread, signal, os, time, subprocess, traceback, platform, sys
  5import replay_upload
  6from db_entities import *
  7from ladderdb import *
  8from match import *
  9from ranking import GlobalRankingAlgoSelector
 10if platform.system() == "Windows":
 11	import win32api
 12
 13from utilities import *
 14
 15bstr_nonneg = lambda n: n>0 and bstr_nonneg(n>>1).lstrip('0')+str(n&1) or '0'
 16
 17"""
 18	*  b0 = undefined (reserved for future use)
 19	* b1 = ready (0=not ready, 1=ready)
 20	* b2..b5 = team no. (from 0 to 15. b2 is LSB, b5 is MSB)
 21	* b6..b9 = ally team no. (from 0 to 15. b6 is LSB, b9 is MSB)
 22	* b10 = mode (0 = spectator, 1 = normal player)
 23	* b11..b17 = handicap (7-bit number. Must be in range 0..100). Note: Only host can change handicap values of the players in the battle (with HANDICAP command). These 7 bits are always ignored in this command. They can only be changed using HANDICAP command.
 24	* b18..b21 = reserved for future use (with pre 0.71 versions these bits were used for team color index)
 25	* b22..b23 = sync status (0 = unknown, 1 = synced, 2 = unsynced)
 26	* b24..b27 = side (e.g.: arm, core, tll, ... Side index can be between 0 and 15, inclusive)
 27	* b28..b31 = undefined (reserved for future use)
 28"""
 29
 30class BattleStatus:
 31	def __init__(self, status, nick ):
 32		status = int(status)
 33		self.team = getteam(status)
 34		self.ally = getally(status)
 35		self.side = getside(status)
 36		self.spec = getspec(status)
 37		self.nick = nick
 38		self.decimal = int(status)
 39
 40	def __str__(self):
 41		return "nick: %s -- team:%d ally:%d side:%d spec:%d decimal:%d"%(self.nick,self.team,self.ally,self.side,self.spec,self.decimal)
 42
 43import helpstrings
 44helpstring_user = helpstrings.helpstring_user_slave
 45
 46def sendstatus(self, socket ):
 47	if self.ingame:
 48		socket.send("MYSTATUS 1\n")
 49	else:
 50		socket.send("MYSTATUS 0\n")
 51
 52class Main:
 53	sock = 0
 54	battleowner = ""
 55	battleid = -1
 56	script = ""
 57	ingame = False
 58	gamestarted = False
 59	joinedbattle = False
 60	toshutdown = False
 61	ladderid = -1
 62	if platform.system() == "Windows":
 63		scriptbasepath = os.environ['USERPROFILE']
 64	else:
 65		scriptbasepath = os.environ['HOME']
 66	battleusers = dict()
 67	battleoptions = dict()
 68	ladderlist = dict()
 69	battle_statusmap = dict()
 70	teams = dict()
 71	allies = dict()
 72	bots = dict()
 73	disabledunits = dict()
 74	battlefounder = ""
 75	hostip = ""
 76	hostport = 0
 77	def startspring(self,socket,g):
 78		currentworkingdir = os.getcwd()
 79		try:
 80			players = []
 81			for player in self.battle_statusmap:
 82				status = self.battle_statusmap[player]
 83				if not status.spec and player != self.app.config["nick"]:
 84					players.append(player)
 85			pregame_rankinfo = self.db.GetRankAndPositionInfo( players, self.ladderid )
 86			
 87			if self.ingame == True:
 88				self.saybattle( self.socket, self.battleid, "Error: game is already running")
 89				return
 90			self.output = ""
 91			self.ingame = True
 92			doSubmit = self.ladderid != -1 and self.db.LadderExists( self.ladderid ) and self.CheckValidSetup(self.ladderid,False,0)
 93			if doSubmit:
 94				self.saybattleex(socket, self.battleid, "will submit the result to the ladder")
 95			else:
 96				self.saybattleex(socket, self.battleid, "won't submit the result to the ladder")
 97			sendstatus( self, socket )
 98			st = time.time()
 99			self.log.Info("*** Starting spring: command line \"%s %s\"" % (self.app.config["springdedclientpath"], os.path.join(self.scriptbasepath,"%f.txt" % g )) )
100			if platform.system() == "Windows":
101				dedpath = "\\".join(self.app.config["springdedclientpath"].replace("/","\\").split("\\")[:self.app.config["springdedclientpath"].replace("/","\\").count("\\")])
102				if not dedpath in sys.path:
103					sys.path.append(dedpath)
104			if "springdatapath" in self.app.config:
105				springdatapath = self.app.config["springdatapath"]
106				if not springdatapath in sys.path:
107					sys.path.append(springdatapath)
108				os.chdir(springdatapath)
109			else:
110				springdatapath = None
111			if springdatapath!= None:
112				os.environ['SPRING_DATADIR'] = springdatapath
113			self.pr = subprocess.Popen((self.app.config["springdedclientpath"],os.path.join(self.scriptbasepath,"%f.txt" % g )),stdout=subprocess.PIPE,stderr=subprocess.STDOUT,cwd=springdatapath)
114			l = self.pr.stdout.readline()
115			while len(l) > 0:
116				self.output += l
117				l = self.pr.stdout.readline()
118			status = self.pr.wait()
119			et = time.time()
120			if status != 0:
121				self.saybattle( self.socket,self.battleid,"Error: Spring exited with status %i" % status)
122				self.log.Error( "Error: Spring exited with status %i" % status )
123				self.log.Error( self.output )
124			if doSubmit:
125				matchid = -1
126				try:
127					mr = AutomaticMatchToDbWrapper( self.output, self.ladderid )
128					matchid = self.db.ReportMatch( mr, True )
129					postgame_rankinfo = self.db.GetRankAndPositionInfo( players, self.ladderid )
130					news_string = '\n'.join( self.GetRankInfoDifference( pregame_rankinfo, postgame_rankinfo ) )
131					#self.saybattle( self.socket, self.battleid, news_string )
132					self.saybattleex(self.socket, self.battleid, "has submitted the score update to the ladder: http://ladder.springrts.com/viewmatch.py?id=%d"%matchid)
133				except:
134					exc = traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
135					self.log.Error( 'EXCEPTION: BEGIN\n%s\nEXCEPTION: END\nCLIENTLOG: BEGIN\n%s\nCLIENTLOG: END'%(exc,self.output) )
136					self.saybattleex(self.socket, self.battleid, "could not submit ladder score updates")
137				if matchid != -1:
138					reply = replay_upload.postReplay( os.getcwd() + "/"+ self.db.GetMatchReplay( matchid ), 'LadderBot', "Ladder: %s, Match #%d" % ( self.db.GetLadderName(self.ladderid), matchid ) )
139					replaysiteok = reply.split()[0] == 'SUCCESS'
140					if replaysiteok:
141						self.saybattleex(self.socket, self.battleid, reply.split()[1] )
142					else:
143						self.saybattleex(self.socket, self.battleid, "error uploading replay to http://replays.adune.nl")
144
145		except:
146			exc = traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
147			self.log.Error( 'EXCEPTION: BEGIN\n%s\nEXCEPTION: END'%exc )
148		try:
149			os.remove(os.path.join(self.scriptbasepath,"%f.txt" % g))
150		except:
151			pass
152		os.chdir(currentworkingdir)
153		self.ingame = False
154		sendstatus( self, socket )
155		if self.toshutdown:
156			self.KillBot()
157
158	def KillBot(self):
159		if platform.system() == "Windows":
160			handle = win32api.OpenProcess(1, 0, os.getpid())
161			win32api.TerminateProcess(handle, 0)
162		else:
163			os.kill(os.getpid(),signal.SIGKILL)
164
165	def CheckValidSetup( self, ladderid, echoerrors, socket ):
166		a = self.CheckvalidPlayerSetup(ladderid,echoerrors,socket)
167		b = self.CheckValidOptionsSetup(ladderid,echoerrors,socket)
168		return a and b
169
170	def CheckvalidPlayerSetup( self, ladderid, echoerrors, socket ):
171		IsOk = True
172		laddername = self.db.GetLadderName( ladderid )
173		teamcount = len(self.teams)
174		allycount = len(self.allies)
175		botcount = len(self.bots)
176
177		bannedplayers = ""
178		duplicatebots = ""
179		checkedbots = []
180		for player in self.battle_statusmap:
181			if not self.db.AccessCheck( ladderid, player, Roles.User ):
182				IsOk = False
183				bannedplayers += " " + player
184			if player in self.bots: # it's a bot
185				botlib = self.bots[player]
186				if not botlib in checkedbots:
187					checkedbots.append(botlib)
188				else:
189					IsOk = False
190					duplicatebots += " " + player
191		if not len(bannedplayers) == 0 and echoerrors:
192			self.saybattle( socket, self.battleid, "There are banned player for " + laddername  + " (" + bannedplayers + " )" )
193		if not len(duplicatebots) == 0 and echoerrors:
194			self.saybattle( socket, self.battleid, "There are too many bots of the same type (" + duplicatebots + " )" )
195
196		minbotcount = self.db.GetLadderOption( ladderid, "min_ai_count" )
197		maxbotcount = self.db.GetLadderOption( ladderid, "max_ai_count" )
198		minteamcount = self.db.GetLadderOption( ladderid, "min_team_count" )
199		maxteamcount = self.db.GetLadderOption( ladderid, "max_team_count" )
200		minallycount = self.db.GetLadderOption( ladderid, "min_ally_count" )
201		maxallycount = self.db.GetLadderOption( ladderid, "max_ally_count" )
202		if botcount < minbotcount:
203			if echoerrors:
204				self.saybattle( socket, self.battleid, "There are too few AIs for " + laddername  + " (" + str(botcount) + ")" )
205			IsOk =  False
206		if botcount > maxbotcount:
207			if echoerrors:
208				self.saybattle( socket, self.battleid, "There are too many AIs for " + laddername + " (" + str(botcount) + ")" )
209			IsOk = False
210		if teamcount < minteamcount:
211			if echoerrors:
212				self.saybattle( socket, self.battleid, "There are too few control teams for " + laddername  + " (" + str(teamcount) + ")" )
213			IsOk =  False
214		if teamcount > maxteamcount:
215			if echoerrors:
216				self.saybattle( socket, self.battleid, "There are too many control teams for " + laddername + " (" + str(teamcount) + ")" )
217			IsOk = False
218		if allycount < minallycount:
219			if echoerrors:
220				self.saybattle( socket, self.battleid, "There are too few allies for " + laddername  + " (" + str(allycount) + ")" )
221			IsOk = False
222		if allycount > maxallycount:
223			if echoerrors:
224				self.saybattle( socket, self.battleid, "There are too few allies for " + laddername  + " (" + str(allycount) + ")" )
225			IsOk = False
226		minteamsize = self.db.GetLadderOption( ladderid, "min_team_size" )
227		maxteamsize = self.db.GetLadderOption( ladderid, "max_team_size" )
228		minallysize = self.db.GetLadderOption( ladderid, "min_ally_size" )
229		maxallysize = self.db.GetLadderOption( ladderid, "max_ally_size" )
230		teamsizesok = True
231		errorstring = "The following control teams have too few players in them for " + laddername + ":\n"
232		for team in self.teams:
233			teamsize = self.teams[team]
234			if teamsize < minteamsize:
235				errorstring += str(team) + "=" + str(teamsize) + " "
236				teamsizesok = False
237				IsOk = False
238		if not teamsizesok and echoerrors:
239			self.saybattle( socket, self.battleid, errorstring )
240		teamsizesok = True
241		errorstring = "The following control teams have too many players in them for " + laddername + ":\n"
242		for team in self.teams:
243			if teamsize > maxteamsize:
244				IsOk = False
245				errorstring += str(team) + "=" + str(teamsize) + " "
246				teamsizesok = False
247		if not teamsizesok and echoerrors:
248			self.saybattle( socket, self.battleid, errorstring )
249		allysizesok = True
250		errorstring = "The following ally have too few players in them for " + laddername + ":\n"
251		for ally in self.allies:
252			allysize = self.allies[ally]
253			if allysize < minallysize:
254				IsOk = False
255				allysizesok = False
256				errorstring += str(team) + "=" + str(teamsize) + " "
257		if not allysizesok and echoerrors:
258			self.saybattle( socket, self.battleid, errorstring )
259		allysizesok = True
260		errorstring = "The following ally have too many players in them for " + laddername + ":\n"
261		for ally in self.allies:
262			allysize = self.allies[ally]
263			if allysize > maxallysize:
264				IsOk = False
265				allysizesok = False
266				errorstring += str(team) + "=" + str(teamsize) + " "
267		if not allysizesok and echoerrors:
268			self.saybattle( socket, self.battleid, errorstring )
269		return IsOk
270
271
272	def CheckValidOptionsSetup( self, ladderid, echoerrors, socket ):
273		IsOk = True
274		laddername = self.db.GetLadderName( ladderid )
275		for key in self.battleoptions:
276			value = self.battleoptions[key]
277			OptionOk = self.CheckOptionOk( ladderid, key, value )
278			if not OptionOk:
279				if IsOk and echoerrors:
280					self.saybattle( socket, self.battleid, "The following settings are not compatible with " + laddername + ":" )
281				IsOk = False
282				if echoerrors:
283					self.saybattle( socket, self.battleid, key + "=" + value )
284		return IsOk
285
286	def CheckOptionOk( self, ladderid, keyname, value ):
287		if self.db.GetOptionKeyValueExists( ladderid, False, keyname, value ): # option in the blacklist
288			return False
289		if self.db.GetOptionKeyExists( ladderid, True, keyname ): # whitelist not empty
290			return self.db.GetOptionKeyValueExists( ladderid, True, keyname, value )
291		else:
292			return True
293
294	def JoinGame(self,s):
295		if self.joinedbattle:
296			sendstatus( self, self.socket )
297			if not self.gamestarted:
298				return
299			if self.ingame:
300				return
301			#start spring
302			g = time.time()
303			if platform.system() == "Linux":
304				f = open(os.path.join(os.environ['HOME'],"%f.txt" % g),"a")
305			else:
306				f = open(os.path.join(os.environ['USERPROFILE'],"%f.txt" % g),"a")
307			self.script = "[GAME]\n{"
308			self.script += "\n\tHostIP=" + self.hostip + ";"
309			self.script += "\n\tHostPort=" + self.hostport + ";"
310			self.script += "\n\tIsHost=0;"
311			self.script += "\n\tMyPlayerName=" + self.app.config["nick"] + ";"
312			self.script += "\n}"
313			f.write(self.script)
314			f.close()
315			thread.start_new_thread(self.startspring,(s,g))
316
317	def onload(self,tasc):
318		self.app = tasc.main
319		self.tsc = tasc
320		self.hosttime = time.time()
321		self.battleid = int(self.app.config["battleid"])
322		self.ladderid = int(self.app.config["ladderid"])
323		self.battlepassword = self.app.config["battlepassword"]
324		self.log = CLog()
325		self.log.Init( self.app.config['nick']+'.log', self.app.config['nick']+'.err' )
326		self.db = LadderDB( parselist(self.app.config["alchemy-uri"],",")[0], [], parselist(self.app.config["alchemy-verbose"],",")[0] )
327
328	def oncommandfromserver(self,command,args,s):
329		#print "From server: %s | Args : %s" % (command,str(args))
330		self.socket = s
331		if command == "JOINBATTLE":
332			self.joinedbattle = True
333			good( "Joined battle: " + str(self.battleid) )
334		if command == "JOINBATTLEFAILED":
335			self.joinedbattle = False
336			bad( "Join battle failed, ID: " + str(self.battleid) + " reason: " + " ".join(args[0:] ) )
337			self.KillBot()
338		if command == "FORCEQUITBATTLE":
339			self.joinedbattle = False
340			bad( "Kicked from battle: " + str(self.battleid) )
341			self.toshutdown = True
342			if not self.ingame:
343				self.KillBot()
344		if command == "BATTLECLOSED" and len(args) == 1 and int(args[0]) == self.battleid:
345			self.joinedbattle = False
346			notice( "Battle closed: " + str(self.battleid) )
347			self.toshutdown = True
348			if not self.ingame:
349				self.KillBot()
350		if command == "ENABLEALLUNITS":
351			self.disabledunits = dict()
352		if command == "ENABLEUNITS" and len(args) > 1:
353			for unit in args[1:]:
354				del self.disabledunits[unit]
355		if command == "DISABLEUNITS":
356			for unit in args[1:]:
357				self.disabledunits[unit] = 0
358		if command == "SETSCRIPTTAGS":
359			for option in args[0].split():
360				pieces = parselist( option, "=" )
361				if len(pieces) != 2:
362					error( "parsing error of option string: " + option )
363				key = pieces[0]
364				if key.startswith("/game/"): # strip prefix
365					key = key[6:]
366				elif key.startswith("game/"):#  strip prefix
367					key = key[5:]
368				if key.startswith("restrict/"):
369					unitname = key[9:]
370					self.disabledunits[unitname] = int(value)
371				value = pieces[1]
372				self.battleoptions[key] = value
373		if command == "REQUESTBATTLESTATUS":
374			self.socket.send( "MYBATTLESTATUS 4194816 255\n" )#spectator+synced/white
375		if command == "SAIDBATTLE" and len(args) > 1 and args[1].startswith("!"):
376			who = args[0]
377			command = args[1]
378			args = args[2:]
379
380			if len(command) > 0 and command[0] == "!":
381				if not self.db.AccessCheck( -1, who, Roles.User ):
382					self.sayPermissionDenied( self.socket, who, command )
383					#log
384					return
385			else:
386				return
387
388			try:
389				if self.battle_statusmap[who].spec and who != self.battlefounder and not self.db.AccessCheck( -1, who, Roles.LadderAdmin ):
390					return
391			except:
392				pass
393
394			if command == "!ladderchecksetup":
395				ladderid = self.ladderid
396				if len(args) == 1 and args[0].isdigit():
397					ladderid = int(args[0])
398				if ladderid == -1:
399					self.saybattle( self.socket, self.battleid,"No ladder has been enabled.")
400				elif self.db.LadderExists( ladderid ):
401					laddername = self.db.GetLadderName( ladderid )
402					if self.CheckValidSetup( ladderid, True, self.socket ):
403						self.saybattle( self.socket, self.battleid, "All settings are compatible with the ladder " + laddername )
404				else:
405					self.saybattle( self.socket, self.battleid,"Invalid ladder ID.")
406			if command == "!ladderlist":
407				self.saybattle( self.socket, self.battleid, "Available ladders, format name: ID:" )
408				for l in self.db.GetLadderList(Ladder.name):
409					self.saybattle( self.socket, self.battleid, "%s: %d" %(l.name, l.id ) )
410			if command == "!ladder":
411				if len(args) == 1 and args[0].isdigit():
412					ladderid = int(args[0])
413					if ladderid != -1:
414						if self.db.LadderExists( ladderid ):
415							laddername = self.db.GetLadderName( ladderid )
416							self.saybattle( self.socket, self.battleid,"Enabled ladder reporting for ladder: " + laddername )
417							self.ladderid = ladderid
418							if self.CheckValidSetup( ladderid, True, self.socket ):
419								self.saybattle( self.socket, self.battleid, "All settings are compatible with the ladder " + laddername )
420						else:
421							self.saybattle( self.socket, self.battleid,"Invalid ladder ID.")
422					else:
423						self.ladderid = ladderid
424						self.saybattle( self.socket, self.battleid,"Ladder reporting disabled.")
425				else:
426					self.saybattle( self.socket, self.battleid,"Invalid command syntax, check !ladderhelp for usage.")
427			if command == "!ladderleave":
428				self.joinedbattle = False
429				good( "Leaving battle: " + str(self.battleid) )
430				self.socket.send("LEAVEBATTLE\n")
431				self.toshutdown = True
432				if not self.ingame:
433					self.KillBot()
434			if command == "!ladderhelp":
435				self.saybattle( self.socket, self.battleid,  "Hello, I am a bot to manage and keep stats of ladder games.\nYou can use the following commands:")
436				self.saybattle( self.socket, self.battleid, helpstring_user )
437			if command == '!ladderdebug':
438				if not self.db.AccessCheck( self.ladderid, who, Roles.Owner ):
439					self.sayPermissionDenied( self.socket, who, command )
440					#log
441					return
442				import fakeoutput
443				if len(args) > 0 and args[0].isdigit():
444					idx = max( int(args[0]), len(fakeoutput.fakeoutput) -1 )
445					output = fakeoutput.fakeoutput[idx]
446				else:
447					output = fakeoutput.fakeoutput[-1]
448				upd = GlobalRankingAlgoSelector.GetPrintableRepresentation( self.db.GetRanks( self.ladderid ), self.db )
449				players = ['doofus', 'idiot']
450				pregame_rankinfo = self.db.GetRankAndPositionInfo( players, self.ladderid )
451				self.saybattle( self.socket, self.battleid, 'before:\n' + upd )
452				try:
453					mr = AutomaticMatchToDbWrapper( output, self.ladderid )
454					repeats = int(args[1]) if len(args) > 1 else 1
455					for i in range(repeats):
456						self.db.ReportMatch( mr, False )#false skips validation check of output against ladder rules
457					upd = GlobalRankingAlgoSelector.GetPrintableRepresentation( self.db.GetRanks( self.ladderid ), self.db )
458					self.saybattle( self.socket, self.battleid, 'pre-recalc:\n' +upd )
459					self.db.RecalcRankings(self.ladderid)
460				except InvalidOptionSetup, e:
461					self.saybattle( self.socket, self.battleid, str(e) )
462					return
463
464				upd = GlobalRankingAlgoSelector.GetPrintableRepresentation( self.db.GetRanks( self.ladderid ), self.db )
465				self.saybattle( self.socket, self.battleid, 'after:\n' +upd )
466				postgame_rankinfo = self.db.GetRankAndPositionInfo( players, self.ladderid )
467				self.saybattle( self.socket, self.battleid, '\n'.join( self.GetRankInfoDifference( pregame_rankinfo, postgame_rankinfo ) ) )
468
469			if command == "!ladderforcestart":
470				if not self.db.AccessCheck( self.ladderid, who, Roles.User ):
471					self.sayPermissionDenied( self.socket, who, command )
472					#log
473					return
474				self.JoinGame(s)
475
476			if command == '!ladderstress':
477				if not self.db.AccessCheck( self.ladderid, who, Roles.Owner ):
478					self.sayPermissionDenied( self.socket, who, command )
479					#log
480					return
481				import fakeoutput
482				if len(args) > 0 and args[0].isdigit():
483					idx = max( int(args[0]), len(fakeoutput.fakeoutput) -1 )
484					output = fakeoutput.fakeoutput[idx]
485				else:
486					output = fakeoutput.fakeoutput[-1]
487				if len(args) > 1 and args[1].isdigit():
488					times = int(args[1])
489				else:
490					times = 1
491
492				now = datetime.datetime.now()
493				upd = GlobalRankingAlgoSelector.GetPrintableRepresentation( self.db.GetRanks( self.ladderid ), self.db )
494				for i in range ( times ):
495					try:
496						mr = AutomaticMatchToDbWrapper( output, self.ladderid )
497						repeats = int(args[1]) if len(args) > 1 else 1
498						for i in range(repeats):
499							self.db.ReportMatch( mr, False )#false skips validation check of output against ladder rules
500						upd = GlobalRankingAlgoSelector.GetPrintableRepresentation( self.db.GetRanks( self.ladderid ), self.db )
501						self.db.RecalcRankings(self.ladderid)
502					except InvalidOptionSetup, e:
503						self.saybattle( self.socket, self.battleid, str(e) )
504						return
505				upd = GlobalRankingAlgoSelector.GetPrintableRepresentation( self.db.GetRanks( self.ladderid ), self.db )
506				self.saybattle( self.socket, self.battleid, '%i recalcs took %s:\n'%(times, str(datetime.datetime.now() - now) ))
507
508			if command == "!ladderreportgame":
509				if len(args) < 2:
510					self.saybattle( self.socket, self.battleid, "Invalid command syntax (too few args), check !ladderhelp for usage." )
511				else:
512					ladderid = self.ladderid
513					try:
514						if not self.db.AccessCheck( ladderid, who, Roles.LadderAdmin ):
515							self.sayPermissionDenied( self.socket, who, command )
516							#log
517							return
518						ladder = self.db.GetLadder( ladderid )
519						usercounter = 0
520						userresults = dict()
521						while ( usercounter != len(args) ):
522							username, equal, result = args[usercounter].partition("=")
523							if ( len(result) == 0 ):
524								self.saybattle( self.socket, self.battleid, "Invalid command syntax, check !ladderhelp for usage." )
525								return
526							userresults[username] = int(result)
527							usercounter = usercounter +1
528
529						if  not self.CheckvalidPlayerSetup( ladderid, True , self.socket ):
530							self.saybattle( self.socket, self.battleid, "Invalid setup" )
531						players = []
532						teams_map = dict()
533						allies_map = dict()
534						for player in self.battle_statusmap:
535							status = self.battle_statusmap[player]
536							if not status.spec and player != self.app.config["nick"]:
537								players.append(player)
538								teams_map[player] = status.team
539								allies_map[player] = status.ally
540						mr = ManualMatchToDbWrapper( players, userresults, self.teams, ladderid, self.battleoptions, self.disabledunits, self.bots, self.allies, teams_map, allies_map )
541						try:
542							self.db.ReportMatch( mr )
543							self.saybattleex(self.socket, self.battleid, "has submitted ladder score updates")
544						except BannedPlayersDetectedException, b:
545							self.saybattle( self.socket,self.battleid,str(b) )
546							self.log.Error( b, 'BannedPlayersDetectedException' )
547						except Exception, e:
548							self.saybattle( self.socket,self.battleid,"There was an error reporting the battle outcome: %s"%str(e) )
549							self.log.Error( e, 'Exception' )
550
551					except ElementNotFoundException, e:
552						self.saybattle( self.socket,self.battleid, "Invalid ladder ID." )
553						self.log.Error( e, 'ElementNotFoundException' )
554			if command == "!ladderlistoptions":
555				if len(args) != 1 or not args[0].isdigit():
556					ladderid =  self.ladderid
557				else:
558					ladderid = int(args[0])
559					if self.db.LadderExists( ladderid ):
560						self.saybattle( self.socket,self.battleid, "Ladder: " + self.db.GetLadderName(ladderid) )
561						self.saybattle( self.socket,self.battleid, "Min AIs in a Match ( how many AIs ): " + str(self.db.GetLadderOption( ladderid, "min_ai_count" )) )
562						self.saybattle( self.socket,self.battleid, "Max Ais in a Match ( how many AIs ): " + str(self.db.GetLadderOption( ladderid, "max_ai_count" )) )
563						self.saybattle( self.socket,self.battleid, "Min Players in a Team ( sharing control ): " + str(self.db.GetLadderOption( ladderid, "min_team_size" )) )
564						self.saybattle( self.socket,self.battleid, "Max Players in a Team ( sharing control ): " + str(self.db.GetLadderOption( ladderid, "max_team_size" )) )
565						self.saybattle( self.socket,self.battleid, "Min Teams in an Ally ( being allied ): " + str(self.db.GetLadderOption( ladderid, "min_ally_size" )) )
566						self.saybattle( self.socket,self.battleid, "Max Teams in an Ally ( being allied ): " + str(self.db.GetLadderOption( ladderid, "max_ally_size" )) )
567						self.saybattle( self.socket,self.battleid, "Min Teams in a Match ( how many Teams ): " + str(self.db.GetLadderOption( ladderid, "min_team_count" )) )
568						self.saybattle( self.socket,self.battleid, "Max Teams in a Match ( how many Teams ): " + str(self.db.GetLadderOption( ladderid, "max_team_count" )) )
569						self.saybattle( self.socket,self.battleid, "Min Alliances in a Match ( how many Allys ): " + str(self.db.GetLadderOption( ladderid, "min_ally_count" )) )
570						self.saybattle( self.socket,self.battleid, "Max Alliances in a Match ( how many Allys ): " + str(self.db.GetLadderOption( ladderid, "max_ally_count" )) )
571						self.saybattle( self.socket,self.battleid, "Whitelisted options ( if a key is present, no other value except for those listed will be allowed for such key ):" )
572						for opt in self.db.GetFilteredOptions( ladderid, True ):
573							self.saybattle( self.socket,self.battleid, opt.key + ": " + opt.value )
574						self.saybattle( self.socket,self.battleid, "Blacklisted options ( if a value is present for a key, such value won't be allowed ):" )
575						for opt in self.db.GetFilteredOptions( ladderid, False ):
576							self.saybattle( self.socket,self.battleid, opt.key + ": " + opt.value )
577					else:
578						self.saybattle( self.socket,self.battleid, "Invalid ladder ID." )
579
580			if command == "!score":
581				if not self.db.AccessCheck( -1, who, Roles.User ):
582					self.sayPermissionDenied( self.socket, who, command )
583					#log
584					return
585				if len(args) > 2:
586					self.saybattle( self.socket,self.battleid, "Invalid command syntax, check !ladderhelp for usage." )
587				else:
588					ladderid = self.ladderid
589					playername = ""
590					rep = ''
591					if len(args) > 0:
592						if args[0].isdigit():
593							ladderid = int(args[0])
594							if len(args) > 1:
595								playername = args[1]
596						else:
597							playername = args[0]
598					if ladderid != -1 and len(playername) == 0:
599						rep = GlobalRankingAlgoSelector.GetPrintableRepresentation( self.db.GetRanks( ladderid ), self.db )
600					elif ladderid != -1 and len(playername) != 0:
601						rep = GlobalRankingAlgoSelector.GetPrintableRepresentation( self.db.GetRanks( ladderid, playername ), self.db )
602					elif ladderid == -1 and len(playername) != 0:
603						rep = GlobalRankingAlgoSelector.GetPrintableRepresentationPlayer( self.db.GetPlayerRanks( playername ), self.db )
604					self.saybattle( self.socket,self.battleid, rep )
605			if command == "!ladderopponent":
606				if len(args) > 1:
607					self.saybattle( self.socket,self.battleid, "Invalid command syntax, check !ladderhelp for usage." )
608					return
609				if len(args) == 1:
610					ladderid = int(args[0])
611				else:
612					ladderid = self.ladderid
613				if not self.db.AccessCheck( ladderid, who, Roles.User ):
614					self.sayPermissionDenied( self.socket, who, command )
615					#log
616					return
617				if not self.db.LadderExists( ladderid ):
618					self.saybattle( self.socket,self.battleid, "Invalid ladderID." )
619					return
620				userlist, ranks = GlobalRankingAlgoSelector.GetCandidateOpponents( who, ladderid, self.db )
621				opponent_found = False
622				for user in userlist:
623					try:
624						userstatus = self.tsc.users[user]
625					except: # skip offline
626						continue
627					if userstatus.ingame:
628						continue
629					if userstatus.afk:
630						continue
631					opponent_found = True
632					self.saybattle( self.socket,self.battleid, ranks[user] )
633				if not opponent_found:
634					self.saybattle( self.socket,self.battleid, "No suitable candidates as opponent are available currently, try again later." )
635		if command == "BATTLEOPENED" and len(args) > 12 and int(args[0]) == self.battleid:
636			self.battlefounder = args[3]
637			self.battleoptions["battletype"] = args[1]
638			self.hostip = args[4]
639			self.hostport = args[5]
640			tabbedstring = " ".join(args[10:])
641			tabsplit = parselist(tabbedstring,"\t")
642			self.battleoptions["mapname"] = tabsplit[0]
643			self.battleoptions["modname"] = tabsplit[2]
644		if command == "UPDATEBATTLEINFO" and len(args) > 4 and int(args[0]) == self.battleid:
645			tabbedstring = " ".join(args[4:])
646			tabsplit = parselist(tabbedstring,"\t")
647			self.battleoptions["mapname"] = tabsplit[0]
648
649		if command == "CLIENTSTATUS" and len(args) > 1 and len(self.battlefounder) != 0 and args[0] == self.battlefounder:
650			self.gamestarted = getingame(int(args[1]))
651			self.JoinGame(s)
652		if command == "CLIENTBATTLESTATUS":
653			if len(args) != 3:
654				error( "invalid CLIENTBATTLESTATUS:%s"%(args) )
655			bs = BattleStatus( args[1], args[0] )
656			self.battle_statusmap[ args[0] ] = bs
657			self.FillTeamAndAllies()
658		if command == "LEFTBATTLE":
659			if len(args) != 2:
660				error( "invalid LEFTBATTLE:%s"%(args) )
661			if int(args[0]) == self.battleid:
662				player = args[1]
663				if player in self.battle_statusmap:
664					del self.battle_statusmap[player]
665					self.FillTeamAndAllies()
666		if command == "ADDBOT":
667			if len(args) != 6:
668				error( "invalid ADDBOT:%s"%(args) )
669			if int(args[0]) == self.battleid:
670				botlib = args[5] # we'll use the bot's lib name intead of player name for ladder pourposes
671				name = args[1]
672				botlib = botlib.replace("|"," ")
673				bs = BattleStatus( args[3], name )
674				self.battle_statusmap[ name ] = bs
675				self.FillTeamAndAllies()
676				self.bots[name] = botlib
677		if command == "UPDATEBOT":
678			if len(args) < 2:
679				error( "invalid UPDATEBOT:%s"%(args) )
680			name = args[0]
681			bs = BattleStatus( args[1], name )
682			self.battle_statusmap[ botlib ] = bs
683			self.FillTeamAndAllies()
684		if command == "REMOVEBOT":
685			if len(args) != 2:
686				error( "invalid REMOVEBOT:%s"%(args) )
687			if int(args[0]) == self.battleid:
688				name = args[1]
689				if name in self.bots:
690					del self.bots[name]
691				if name in self.battle_statusmap:
692					del self.battle_statusmap[name]
693				self.FillTeamAndAllies()
694
695	def onloggedin(self,socket):
696		sendstatus( self, socket )
697		socket.send("JOINBATTLE " + str(self.battleid) + " " + self.battlepassword + "\n")
698
699	def FillTeamAndAllies(self):
700		self.teams = dict()
701		self.allies = dict()
702		for bs in self.battle_statusmap.values():
703			if not bs.spec:
704				if not bs.team in self.teams:
705					self.teams[bs.team] = 1
706				else:
707					self.teams[bs.team] += 1
708				if not bs.ally in self.allies:
709					self.allies[bs.ally] = 1
710				else:
711					self.allies[bs.ally] += 1
712#		print "allies:", self.allies
713#		print "teams: ",self.teams
714#		print "battle_statusmap",self.battle_statusmap
715
716	def saybattle(self,socket,battleid,message):
717		for line in message.split('\n'):
718			self.log.Info( "Battle:%i, Message: %s" %(battleid,line) )
719			socket.send("SAYBATTLE %s\n" % line)
720
721	def saybattleex(self,socket,battleid,message):
722		for line in message.split('\n'):
723			self.log.Info( "Battle:%i, Message: %s" %(battleid,line) )
724			socket.send("SAYBATTLEEX %s\n" % line)
725
726	def sayPermissionDenied(self,socket, command, username ):
727		socket.send("SAYPRIVATE %s You do not have sufficient access right to execute %s on this bot\n" %( username, command ) )
728
729	def GetRankInfoDifference(self, pre, post ):
730		#we cannot assume same ordering or even players in pre and post
731		res = []
732		for nick, info in post.iteritems():
733			post_rank = info[0]
734			post_pos = info[1]
735			rank_type = info[2]
736			if not nick in pre:
737				pre_rank = rank_type()
738				pre_pos = 0 #make num player on ladder +1
739			else:
740				pre_rank = pre[nick][0]
741				pre_pos = pre[nick][1]
742			res.append( '%s:\tNew position: %d (%d)\t New Rank: %s (was %s)'%(nick, post_pos, (pre_pos - post_pos),str(post_rank), str(pre_rank) ) )
743		return res