diff options
Diffstat (limited to 'common/gamesrv.py')
-rw-r--r-- | common/gamesrv.py | 1378 |
1 files changed, 1378 insertions, 0 deletions
diff --git a/common/gamesrv.py b/common/gamesrv.py new file mode 100644 index 0000000..bb27d66 --- /dev/null +++ b/common/gamesrv.py @@ -0,0 +1,1378 @@ +from __future__ import generators +from socket import * +from select import select +from struct import pack, unpack +import zlib, os, random, struct, md5, sys +from time import time, ctime +from msgstruct import * +from errno import EWOULDBLOCK + + +SERVER_TIMEOUT = 7200 # 2 hours without any connection or port activity + + +def protofilepath(filename): + dirpath = filename + path = [] + while dirpath: + dirpath, component = os.path.split(dirpath) + assert component, "invalid file path %r" % (filename,) + path.insert(0, component) + path.insert(0, game.FnBasePath) + return '/'.join(path) + + +class Icon: + count = 0 + + def __init__(self, bitmap, code, x,y,w,h, alpha=255): + self.w = w + self.h = h + self.origin = (bitmap, x, y) + self.code = code + if alpha == 255: + self.msgdef = message(MSG_DEF_ICON, bitmap.code, code, x,y,w,h) + else: + self.msgdef = message(MSG_DEF_ICON, bitmap.code, code, x,y,w,h, alpha) + framemsgappend(self.msgdef) + + def getimage(self): + import pixmap + bitmap, x, y = self.origin + image = pixmap.decodepixmap(bitmap.read()) + return pixmap.cropimage(image, (x, y, self.w, self.h)) + + def getorigin(self): + bitmap, x, y = self.origin + return bitmap, (x, y, self.w, self.h) + + +class DataChunk: + + def __init__(self): + for c in clients: + if c.initialized == 2: + self.defall(c) + if recording and game: + self.defall(recording) + + def read(self, slice=None): + f = open(self.filename, "rb") + data = f.read() + f.close() + if slice: + start, length = slice + data = data[start:start+length] + return data + + def defall(self, client): + if client.proto == 1 or not self.filename: + # protocol 1 + try: + msgdef = self.msgdef + except AttributeError: + data = zlib.compress(self.read()) + msgdef = self.msgdef = self.getmsgdef(data) + else: + # protocol >= 2 + try: + msgdef = self.sendmsgdef + except AttributeError: + fileid = len(filereaders) + filereaders[fileid] = self.read + data = self.read() + msgdef = self.sendmsgdef = (self.getmd5def(fileid, data) + + self.getmsgdef(fileid)) + client.msgl.append(msgdef) + + def getmd5def(self, fileid, data, offset=0): + checksum = md5.new(data).digest() + return message(MSG_MD5_FILE, fileid, protofilepath(self.filename), + offset, len(data), checksum) + + +class Bitmap(DataChunk): + + def __init__(self, code, filename, colorkey=None): + self.code = code + self.filename = filename + self.icons = {} + self.colorkey = colorkey + DataChunk.__init__(self) + + def geticon(self, x,y,w,h, alpha=255): + rect = (x,y,w,h) + try: + return self.icons[rect] + except: + ico = Icon(self, Icon.count, x,y,w,h, alpha) + Icon.count += 1 + self.icons[rect] = ico + return ico + + def geticonlist(self, w, h, count): + return map(lambda i, fn=self.geticon, w=w, h=h: fn(i*w, 0, w, h), range(count)) + + def getmsgdef(self, data): + if self.colorkey is not None: + return message(MSG_DEF_BITMAP, self.code, data, self.colorkey) + else: + return message(MSG_DEF_BITMAP, self.code, data) + + def defall(self, client): + DataChunk.defall(self, client) + for i in self.icons.values(): + client.msgl.append(i.msgdef) + + +class MemoryBitmap(Bitmap): + + def __init__(self, code, data, colorkey=None): + self.data = data + Bitmap.__init__(self, code, None, colorkey) + + def read(self, slice=None): + data = self.data + if slice: + start, length = slice + data = data[start:start+length] + return data + + +class Sample(DataChunk): + + def __init__(self, code, filename, freqfactor=1): + self.code = code + self.filename = filename + self.freqfactor = freqfactor + DataChunk.__init__(self) + + def defall(self, client): + if client.has_sound > 0: + DataChunk.defall(self, client) + + def getmsgdef(self, data): + return message(MSG_DEF_SAMPLE, self.code, data) + + def read(self, slice=None): + f = open(self.filename, "rb") + data = f.read() + f.close() + if self.freqfactor != 1: + freq, = unpack("<i", data[24:28]) + freq = int(freq * self.freqfactor) + data = data[:24] + pack("<i", freq) + data[28:] + if slice: + start, length = slice + data = data[start:start+length] + return data + + def getmd5def(self, fileid, data): + if self.freqfactor == 1: + return DataChunk.getmd5def(self, fileid, data) + else: + datahead = data[:28] + datatail = data[28:] + return (message(MSG_PATCH_FILE, fileid, 0, datahead) + + DataChunk.getmd5def(self, fileid, datatail, offset=28)) + + def play(self, lvolume=1.0, rvolume=None, pad=0.5, singleclient=None): + if rvolume is None: + rvolume = lvolume + lvolume *= 2.0*(1.0-pad) + rvolume *= 2.0*pad + if lvolume < 0.0: + lvolume = 0.0 + elif lvolume > 1.0: + lvolume = 1.0 + if rvolume < 0.0: + rvolume = 0.0 + elif rvolume > 1.0: + rvolume = 1.0 + message = pack("!hBBh", self.code, int(lvolume*255.0), + int(rvolume*255.0), -1) + if singleclient is None: + clist = clients[:] + else: + clist = [singleclient] + for c in clist: + if c.has_sound: + c.sounds.setdefault(message, 4) + + +class Music(DataChunk): + + def __init__(self, filename, filerate=44100): + self.filename = filename + self.filerate = filerate + self.f = open(filename, 'rb') + self.f.seek(0, 2) + filesize = self.f.tell() + self.endpos = max(self.filerate, filesize - self.filerate) + self.fileid = len(filereaders) + filereaders[self.fileid] = self.read + self.md5msgs = {} + DataChunk.__init__(self) + + def read(self, (start, length)): + self.f.seek(start) + return self.f.read(length) + + def msgblock(self, position, limited=1): + blocksize = self.filerate + if limited and position+blocksize > self.endpos: + blocksize = self.endpos-position + if blocksize <= 0: + return '' + #self.f.seek(position) + #return message(MSG_DEF_MUSIC, self.code, position, self.f.read(blocksize)) + try: + msg = self.md5msgs[position] + except KeyError: + data = self.read((position, blocksize)) + checksum = md5.new(data).digest() + msg = message(MSG_MD5_FILE, self.fileid, protofilepath(self.filename), + position, blocksize, checksum) + self.md5msgs[position] = msg + return msg + + def clientsend(self, clientpos): + msg = self.msgblock(clientpos) + #print 'clientsend:', self.code, len(msg), clientpos + if msg: + return [msg], clientpos + self.filerate + else: + return [], None + + def initialsend(self, c): + return [self.msgblock(0), self.msgblock(self.endpos, 0)], self.filerate + + def defall(self, client): + pass + + +def clearsprites(): + sprites_by_n.clear() + sprites[:] = [''] + +def compactsprites(insert_new=None, insert_before=None): + global sprites, sprites_by_n + if insert_before is not None: + if insert_new.alive: + insert_before = insert_before.alive + else: + insert_before = None + newsprites = [''] + newd = {} + l = sprites_by_n.items() + l.sort() + for n, s in l: + if n == insert_before: + prevn = insert_new.alive + newn = insert_new.alive = len(newsprites) + newsprites.append(sprites[prevn]) + newd[newn] = insert_new + l.remove((prevn, insert_new)) + newn = s.alive = len(newsprites) + newsprites.append(sprites[n]) + newd[newn] = s + sprites = newsprites + sprites_by_n = newd + + +class Sprite: + +## try: +## import psyco.classes +## except ImportError: +## pass +## else: +## __slots__ = ['x', 'y', 'ico', 'alive'] +## __metaclass__ = psyco.classes.psymetaclass + + def __init__(self, ico, x,y): + self.x = x + self.y = y + self.ico = ico + self.alive = len(sprites) + if (-ico.w < x < game.width and + -ico.h < y < game.height): + sprites.append(pack("!hhh", x, y, ico.code)) + else: + sprites.append('') # starts off-screen + sprites_by_n[self.alive] = self + + def move(self, x,y, ico=None): + self.x = x + self.y = y + if ico is not None: + self.ico = ico + sprites[self.alive] = pack("!hhh", x, y, self.ico.code) + + def setdisplaypos(self, x, y): + # special use only (self.x,y are not updated) + s = sprites[self.alive] + if len(s) == 6: + sprites[self.alive] = pack("!hh", x, y) + s[4:] + + def setdisplayicon(self, ico): + # special use only (self.ico is not updated) + s = sprites[self.alive] + if len(s) == 6: + sprites[self.alive] = s[:4] + pack("!h", ico.code) + + #sizeof_displaypos = struct.calcsize("!hh") + def getdisplaypos(self): + # special use only (normally, read self.x,y,ico directly) + s = sprites[self.alive] + if self.alive and len(s) == 6: + return unpack("!hh", s[:4]) + else: + return None, None + + def step(self, dx,dy): + x = self.x = self.x + dx + y = self.y = self.y + dy + sprites[self.alive] = pack("!hhh", x, y, self.ico.code) + + def seticon(self, ico): + self.ico = ico + sprites[self.alive] = pack("!hhh", self.x, self.y, ico.code) + + def hide(self): + sprites[self.alive] = '' + + def kill(self): + if self.alive: + del sprites_by_n[self.alive] + sprites[self.alive] = '' + self.alive = 0 + + def prefix(self, n, m=0): + pass #sprites[self.alive] = pack("!hhh", n, m, 32767) + sprites[self.alive] + + def to_front(self): + if self.alive and self.alive < len(sprites)-1: + self._force_to_front() + + def _force_to_front(self): + info = sprites[self.alive] + sprites[self.alive] = '' + del sprites_by_n[self.alive] + self.alive = len(sprites) + sprites_by_n[self.alive] = self + sprites.append(info) + + def to_back(self, limit=None): + assert self is not limit + if limit: + n1 = limit.alive + 1 + else: + n1 = 1 + if self.alive > n1: + if n1 in sprites_by_n: + keys = sprites_by_n.keys() + keys.remove(self.alive) + keys.sort() + keys = keys[keys.index(n1):] + reinsert = [sprites_by_n[n] for n in keys] + for s1 in reinsert: + s1._force_to_front() + assert n1 not in sprites_by_n + info = sprites[self.alive] + sprites[self.alive] = '' + del sprites_by_n[self.alive] + self.alive = n1 + sprites_by_n[n1] = self + sprites[n1] = info + + def __repr__(self): + if self.alive: + return "<sprite %d at %d,%d>" % (self.alive, self.x, self.y) + else: + return "<killed sprite>" + + +class Player: + standardplayericon = None + + def playerjoin(self): + pass + + def playerleaves(self): + pass + + def _playerleaves(self): + if self.isplaying(): + self._client.killplayer(self) + del self._client + self.playerleaves() + + def isplaying(self): + return hasattr(self, "_client") + + +class Client: + SEND_BOUND_PER_FRAME = 0x6000 # bytes + KEEP_ALIVE = 2.2 # seconds + + def __init__(self, socket, addr): + socket.setblocking(0) + self.socket = socket + self.addr = addr + self.udpsocket = None + self.udpsockcounter = 0 + self.initialdata = MSG_WELCOME + self.initialized = 0 + self.msgl = [message(MSG_PING)] + self.buf = "" + self.players = { } + self.sounds = None + self.has_sound = 0 + self.has_music = 0 + self.musicpos = { } + self.proto = 1 + self.dyncompress = None + addsocket('CLIENT', self.socket, self.input_handler) + clients.append(self) + self.log('connected') + self.send_buffer(self.initialdata) + + def opengame(self, game): + if self.initialized == 0: + self.initialdata += game.FnDesc + '\n' + self.initialized = 1 + if self.initialized == 1: + if game.broadcast_port: + self.initialdata += message(MSG_BROADCAST_PORT, game.broadcast_port) + game.trigger_broadcast() + self.initialdata += game.deffieldmsg() + else: + self.msgl.append(game.deffieldmsg()) + self.activity = self.last_ping = time() + self.force_ping_delay = 0.6 + for c in clients: + for id in c.players.keys(): + self.msgl.append(message(MSG_PLAYER_JOIN, id, c is self)) + + def emit(self, udpdata, broadcast_extras): + if self.initialdata: + self.send_buffer(self.initialdata) + elif self.initialized == 2: + buffer = ''.join(self.msgl) + if buffer: + self.send_buffer(buffer) + if self.udpsocket is not None: + if self.sounds: + if broadcast_extras is None or self not in broadcast_clients: + udpdata = ''.join(self.sounds.keys() + [udpdata]) + else: + broadcast_extras.update(self.sounds) + for key, value in self.sounds.items(): + if value: + self.sounds[key] = value-1 + else: + del self.sounds[key] + if broadcast_extras is None or self not in broadcast_clients: + if self.dyncompress is not None: + udpdatas = self.dynamic_compress(udpdata) + else: + udpdatas = [udpdata] + for udpdata in udpdatas: + try: + self.udpsockcounter += self.udpsocket.send(udpdata) + except error, e: + print >> sys.stderr, 'ignored:', str(e) + pass # ignore UDP send errors (buffer full, etc.) + if self.has_music > 1 and NOW >= self.musicstreamer: + self.musicstreamer += 0.99 + self.sendmusicdata() + if not self.msgl: + if abs(NOW - self.activity) <= self.KEEP_ALIVE: + if abs(NOW - self.last_ping) <= self.force_ping_delay: + return + if self.udpsockcounter < 1024: + return + self.force_ping_delay += 0.2 + self.msgl.append(message(MSG_PING, self.udpsockcounter>>10)) + self.last_ping = NOW + + def setup_dyncompress(self): + def dyncompress(): + # See comments in pclient.Playfield.dynamic_decompress(). + threads = [] + for t in range(3): + co = zlib.compressobj(6) + threads.append((chr(0x88 + t) + chr(t), co)) + frame = 0 + globalsync = 0 + + while 1: + # write three normal packets, one on each thread + for t in range(3): + head, co = threads.pop(0) + yield head + chr(frame), co + threads.append((chr(ord(head[0]) & 0x87) + chr(frame), co)) + yield None, None + frame = (frame + 1) & 0xFF + + # sync frame, write two packets (on two threads) + # and restart compression at the current frame for these threads + head, co = threads.pop(0) + yield head + chr(frame), co + co1 = zlib.compressobj(6) + co2 = zlib.compressobj(6) + globalsync += 1 + if globalsync == 4: + # next on this thread will be a global sync packet + nextframe = (frame + 2) & 0xFF + globalsync = 0 + else: + # next of this thread will be a local sync packet + yield None, co1 + nextframe = frame + threads.append((chr(ord(head[0]) | 8) + chr(nextframe), co1)) + + # 2nd packet of the current frame + head, co = threads.pop(0) + yield head + chr(frame), co + yield None, co2 + threads.append((chr(ord(head[0]) | 8) + chr(frame), co2)) + + yield None, None + frame = (frame + 1) & 0xFF + + self.dyncompress = dyncompress() + + def dynamic_compress(self, framedata): + result = [] + for head, co in self.dyncompress: + if not co: + return result + data = [head, co.compress(framedata), co.flush(zlib.Z_SYNC_FLUSH)] + if head: + result.append(''.join(data)) + + def send_can_mix(self): + return not self.msgl and self.socket is not None + + def send_buffer(self, buffer): + try: + count = self.socket.send(buffer[:self.SEND_BOUND_PER_FRAME]) + except error, e: + if e.args[0] != EWOULDBLOCK: + self.msgl = [] + self.initialdata = "" + self.disconnect(e, 'emit') + return + else: + #g = open('log', 'ab'); g.write(buffer[:count]); g.close() + buffer = buffer[count:] + self.activity = NOW + if self.initialdata: + self.initialdata = buffer + elif buffer: + self.msgl = [buffer] + else: + self.msgl = [] + + def receive(self, data): + #print "receive:", `data` + try: + data = self.buf + data + while data: + values, data = decodemessage(data) + if not values: + break # incomplete message + fn = self.MESSAGES.get(values[0]) + if fn: + fn(self, *values[1:]) + else: + print "unknown message from", self.addr, ":", values + self.buf = data + except struct.error: + import traceback + traceback.print_exc() + self.socket.send('\n\n<h1>Protocol Error</h1>\n') + hs = findsocket('HTTP') + if hs is not None: + url = 'http://%s:%s' % (HOSTNAME, displaysockport(hs)) + self.socket.send(''' +If you meant to point your web browser to this server, +then use the following address: + +<a href="%s">%s</a> +''' % (url, url)) + self.disconnect('protocol error', 'receive') + + def input_handler(self): + try: + data = self.socket.recv(2048) + except error, e: + self.disconnect(e, "socket.recv") + else: + if data: + self.activity = NOW + self.receive(data) + elif not hasattr(self.socket, 'RECV_CAN_RETURN_EMPTY'): + # safecheck that this means disconnected + iwtd, owtd, ewtd = select([self.socket], [], [], 0.0) + if self.socket in iwtd: + self.disconnect('end of data', 'socket.recv') + + def disconnect(self, err=None, infn=None): + removesocket('CLIENT', self.socket) + if err: + extra = ": " + str(err) + else: + extra = "" + if infn: + extra += " in " + infn + print 'Disconnected by', self.addr, extra + self.log('disconnected' + extra) + for p in self.players.values(): + p._playerleaves() + try: + del broadcast_clients[self] + except KeyError: + pass + clients.remove(self) + try: + self.socket.close() + except: + pass + self.socket = None + if not clients and game is not None: + game.FnDisconnected() + + def killplayer(self, player): + for id, p in self.players.items(): + if p is player: + framemsgappend(message(MSG_PLAYER_KILL, id)) + del self.players[id] + if game: + game.updateplayers() + + def joinplayer(self, id, *rest): + if self.players.has_key(id): + print "Note: player %s is already playing" % (self.addr+(id,),) + return + if game is None: + return # refusing new player before the game starts + p = game.FnPlayers()[id] + if p is None: + print "Too many players. New player %s refused." % (self.addr+(id,),) + self.msgl.append(message(MSG_PLAYER_KILL, id)) + elif p.isplaying(): + print "Note: player %s is already played by another client" % (self.addr+(id,),) + else: + print "New player %s" % (self.addr+(id,),) + p._client = self + p.playerjoin() + p.setplayername('') + self.players[id] = p + game.updateplayers() + for c in clients: + c.msgl.append(message(MSG_PLAYER_JOIN, id, c is self)) + + def remove_player(self, id, *rest): + try: + p = self.players[id] + except KeyError: + print "Note: player %s is not playing" % (self.addr+(id,),) + else: + p._playerleaves() + + def set_player_name(self, id, name, *rest): + p = game.FnPlayers()[id] + p.setplayername(name) + + def set_udp_port(self, port, addr=None, *rest): + if port == MSG_BROADCAST_PORT: + self.log('set_udp_port: broadcast') + broadcast_clients[self] = 1 + #print "++++ Broadcasting ++++ to", self.addr + else: + try: + del broadcast_clients[self] + except KeyError: + pass + if port == MSG_INLINE_FRAME or port == 0: + # client requests data in-line on the TCP stream + self.dyncompress = None + import udpovertcp + self.udpsocket = udpovertcp.SocketMarshaller(self.socket, self) + s = self.udpsocket.tcpsock + self.log('set_udp_port: udp-over-tcp') + else: + try: + if hasattr(self.socket, 'udp_over_udp_mixer'): + # for SocketOverUdp + self.udpsocket = self.socket.udp_over_udp_mixer() + else: + self.udpsocket = socket(AF_INET, SOCK_DGRAM) + self.udpsocket.setblocking(0) + addr = addr or self.addr[0] + self.udpsocket.connect((addr, port)) + except error, e: + print >> sys.stderr, "Cannot set UDP socket to", addr, str(e) + self.udpsocket = None + self.udpsockcounter = sys.maxint + else: + if self.proto >= 3: + self.setup_dyncompress() + s = self.udpsocket + self.log('set_udp_port: %s:%d' % (addr, port)) + if s: + try: + s.setsockopt(SOL_IP, IP_TOS, 0x10) # IPTOS_LOWDELAY + except error, e: + print >> sys.stderr, "Cannot set IPTOS_LOWDELAY:", str(e) + + def enable_sound(self, sound_mode=1, *rest): + if sound_mode != self.has_sound: + self.sounds = {} + self.has_sound = sound_mode + if self.has_sound > 0: + for snd in samples.values(): + snd.defall(self) + #self.log('enable_sound %s' % sound_mode) + + def enable_music(self, mode, *rest): + if mode != self.has_music: + self.has_music = mode + self.startmusic() + #self.log('enable_music') + + def startmusic(self): + if self.has_music: + self.musicstreamer = time() + for cde in currentmusics[1:]: + if cde not in self.musicpos: + msgl, self.musicpos[cde] = music_by_id[cde].initialsend(self) + self.msgl += msgl + if self.has_music > 1: + self.sendmusicdata() + self.msgl.append(message(MSG_PLAY_MUSIC, *currentmusics)) + + def sendmusicdata(self): + for cde in currentmusics[1:]: + if self.musicpos[cde] is not None: + msgl, self.musicpos[cde] = music_by_id[cde].clientsend(self.musicpos[cde]) + self.msgl += msgl + return + + def ping(self, *rest): + if self.initialized < 2: + # send all current bitmap data + self.initialized = 2 + for b in bitmaps.values(): + b.defall(self) + self.finishinit(game) + for id, p in game.FnPlayers().items(): + if p.standardplayericon is not None: + self.msgl.append(message(MSG_PLAYER_ICON, id, p.standardplayericon.code)) + self.msgl.append(message(MSG_PONG, *rest)) + + def finishinit(self, game): + pass + + def pong(self, *rest): + pass + + def log(self, message): + print self.addr, message + + def protocol_version(self, version, *rest): + self.proto = version + + def md5_data_request(self, fileid, position, size, *rest): + data = filereaders[fileid]((position, size)) + data = zlib.compress(data) + self.msgl.append(message(MSG_ZPATCH_FILE, fileid, position, data)) + +## def def_file(self, filename, md5sum): +## fnp = [] +## while filename: +## filename, tail = os.path.split(filename) +## fnp.insert(0, tail) +## if fnp[:len(FnBasePath)] == FnBasePath: +## filename = os.path.join(*fnp[len(FnBasePath):]) +## self.known_files[filename] = md5sum + + MESSAGES = { + CMSG_PROTO_VERSION: protocol_version, + CMSG_ADD_PLAYER : joinplayer, + CMSG_REMOVE_PLAYER: remove_player, + CMSG_UDP_PORT : set_udp_port, + CMSG_ENABLE_SOUND : enable_sound, + CMSG_ENABLE_MUSIC : enable_music, + CMSG_PING : ping, + CMSG_PONG : pong, + CMSG_DATA_REQUEST : md5_data_request, + CMSG_PLAYER_NAME : set_player_name, +## CMSG_DEF_FILE : def_file, + } + + +class SimpleClient(Client): + + def finishinit(self, game): + num = 0 + for keyname, icolist, fn in game.FnKeys: + self.msgl.append(message(MSG_DEF_KEY, keyname, num, + *[ico.code for ico in icolist])) + num += 1 + + def cmsg_key(self, pid, keynum): + if game is not None: + try: + player = self.players[pid] + fn = game.FnKeys[keynum][2] + except (KeyError, IndexError): + game.FnUnknown() + else: + getattr(player, fn) () + + MESSAGES = Client.MESSAGES.copy() + MESSAGES.update({ + CMSG_KEY: cmsg_key, + }) + + +MAX_CLIENTS = 32 + +clients = [] +FnClient = SimpleClient +broadcast_clients = {} +filereaders = {} +bitmaps = {} +samples = {} +music_by_id = {} +currentmusics = [0] +sprites = [''] +sprites_by_n = {} +recording = None +game = None +serversockets = {} +socketsbyrole = {} +socketports = {} + +def framemsgappend(msg): + for c in clients: + c.msgl.append(msg) + if recording: + recording.write(msg) + +##def sndframemsgappend(msg): +## for c in clients: +## if c.has_sound: +## c.msgl.append(msg) + +def set_udp_port(port): + hostchooser.UDP_PORT = port + +def has_loop_music(): + return currentmusics[0] < len(currentmusics)-1 + +def finalsegment(music1, music2): + intro1 = music1[1:1+music1[0]] + intro2 = music2[1:1+music2[0]] + loop1 = music1[1+music1[0]:] + loop2 = music2[1+music2[0]:] + return loop1 == loop2 and intro1 == intro2[len(intro2)-len(intro1):] + +def set_musics(musics_intro, musics_loop, reset=1): + mlist = [] + loop_from = len(musics_intro) + mlist.append(loop_from) + for m in musics_intro + musics_loop: + mlist.append(m.fileid) + reset = reset or not finalsegment(mlist, currentmusics) + currentmusics[:] = mlist + if reset: + for c in clients: + c.startmusic() + +def fadeout(time=1.0): + msg = message(MSG_FADEOUT, int(time*1000)) + for c in clients: + if c.has_music > 1: + c.msgl.append(msg) + currentmusics[:] = [0] + + +def getbitmap(filename, colorkey=None): + try: + return bitmaps[filename] + except: + bmp = Bitmap(len(bitmaps), filename, colorkey) + bitmaps[filename] = bmp + return bmp + +def getsample(filename, freqfactor=1): + try: + return samples[filename, freqfactor] + except: + snd = Sample(len(samples), filename, freqfactor) + samples[filename, freqfactor] = snd + return snd + +def getmusic(filename, filerate=44100): + try: + return samples[filename] + except: + mus = Music(filename, filerate) + samples[filename] = mus + music_by_id[mus.fileid] = mus + return mus + +def newbitmap(data, colorkey=None): + bmp = MemoryBitmap(len(bitmaps), data, colorkey) + bitmaps[bmp] = bmp + return bmp + + +def addsocket(role, socket, handler=None, port=None): + if port is None: + host, port = socket.getsockname() + if handler is not None: + serversockets[socket] = handler + socketsbyrole.setdefault(role, []).append(socket) + socketports[socket] = port + +def findsockets(role): + return socketsbyrole.get(role, []) + +def findsocket(role): + l = findsockets(role) + if l: + return l[-1] + else: + return None + +def removesocket(role, socket=None): + if socket is None: + for socket in socketsbyrole.get(role, [])[:]: + removesocket(role, socket) + return + try: + del serversockets[socket] + except KeyError: + pass + try: + socketsbyrole.get(role, []).remove(socket) + except ValueError: + pass + try: + del socketports[socket] + except KeyError: + pass + +def opentcpsocket(port=None): + port = port or PORTS.get('LISTEN', INADDR_ANY) + s = findsocket('LISTEN') + if s is None: + s = socket(AF_INET, SOCK_STREAM) + try: + s.bind(('', port)) + s.listen(1) + except error: + if port == INADDR_ANY: + for i in range(10): + port = random.choice(xrange(8000, 12000)) + try: + s.bind(('', port)) + s.listen(1) + except error: + pass + else: + break + else: + raise error, "server cannot find a free TCP socket port" + else: + raise + + def tcpsocket_handler(s=s): + conn, addr = s.accept() + game.newclient(conn, addr) + + addsocket('LISTEN', s, tcpsocket_handler) + return s + +def openpingsocket(only_port=None): + only_port = only_port or PORTS.get('PING', None) + s = findsocket('PING') + if s is None: + import hostchooser + s = hostchooser.serverside_ping(only_port) + if s is None: + return None + def pingsocket_handler(s=s): + global game + import hostchooser + if game is not None: + args = game.FnDesc, ('', game.address[1]), game.FnExtraDesc() + else: + ts = findsocket('LISTEN') + if ts: + address = '', displaysockport(ts) + else: + address = '', '' + args = 'Not playing', address, '' + hs = findsocket('HTTP') + args = args + (displaysockport(hs),) + hostchooser.answer_ping(s, *args) + addsocket('PING', s, pingsocket_handler) + return s + +def openhttpsocket(ServerClass=None, HandlerClass=None, + port=None): + port = port or PORTS.get('HTTP', None) + s = findsocket('HTTP') + if s is None: + if ServerClass is None: + from BaseHTTPServer import HTTPServer as ServerClass + if HandlerClass is None: + import javaserver + from httpserver import MiniHandler as HandlerClass + server_address = ('', port or 8000) + try: + httpd = ServerClass(server_address, HandlerClass) + except error: + if port is None: + server_address = ('', INADDR_ANY) + try: + httpd = ServerClass(server_address, HandlerClass) + except error, e: + print >> sys.stderr, "cannot start HTTP server", str(e) + return None + else: + raise + s = httpd.socket + addsocket('HTTP', s, httpd.handle_request) + return s + +BROADCAST_PORT_RANGE = xrange(18000, 19000) +#BROADCAST_MESSAGE comes from msgstruct +BROADCAST_DELAY = 0.6180 +BROADCAST_DELAY_INCR = 2.7183 + +def openbroadcastsocket(broadcastport=None): + s = findsocket('BROADCAST') + if s is None: + try: + s = socket(AF_INET, SOCK_DGRAM) + s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + except error, e: + print >> sys.stderr, "Cannot broadcast", str(e) + return None + port = broadcastport or random.choice(BROADCAST_PORT_RANGE) + addsocket('BROADCAST', s, port=port) + return s + +def displaysockport(s): + return socketports.get(s, 'off') + + +class Game: + width = 640 + height = 480 + backcolor = 0x000000 + + FnDesc = "NoName" + FnFrame = lambda self: 1.0 + FnExcHandler=lambda self, k: 0 + FnServerInfo=lambda self, s: None + FnPlayers = lambda self: {} + FnKeys = [] + FnUnknown = lambda self: None + FnDisconnected = lambda self: None + + def __init__(self): + global game + s = opentcpsocket() + self.address = HOSTNAME, socketports[s] + bs = self.broadcast_s = openbroadcastsocket() + self.broadcast_port = socketports.get(bs) + self.broadcast_next = None + self.nextframe = time() + clearsprites() + game = self + if recording: + for b in bitmaps.values(): + b.defall(recording) + self.pendingclients = [] + + def openserver(self): + ps = openpingsocket() + print '%s server at %s:%d, Broadcast %d, UDP %d' % ( + self.FnDesc, self.address[0], self.address[1], + displaysockport(self.broadcast_s), displaysockport(ps)) + + hs = openhttpsocket() + if hs: + print 'HTTP server: http://%s:%d' % ( + self.address[0], displaysockport(hs)) + + try: + from localmsg import autonotify + except ImportError: + pass + else: + autonotify(self.FnDesc, *self.address) + + if clients: + for c in clients: + c.opengame(self) + if recording: + recording.start() + recording.write(self.deffieldmsg()) + + def trigger_broadcast(self): + assert self.broadcast_s is not None + game.broadcast_delay = BROADCAST_DELAY + game.broadcast_next = time() + self.broadcast_delay + + def deffieldmsg(self): + return message(MSG_DEF_PLAYFIELD, + self.width, self.height, self.backcolor, + self.FnDesc) + + def socketerrors(self, ewtd): + for c in clients[:]: + if c.socket in ewtd: + del ewtd[c.socket] + c.disconnect("error", "select") + + def mainstep(self): + global NOW + if self.pendingclients: + self.newclient(*self.pendingclients.pop()) + NOW = time() + delay = self.nextframe - NOW + if delay<=0.0: + self.nextframe += self.FnFrame() + self.sendudpdata() + NOW = time() + delay = self.nextframe - NOW + if delay<0.0: + self.nextframe = NOW + delay = 0.0 + if self.broadcast_next is not None and NOW >= self.broadcast_next: + if not clients: + self.broadcast_next = None + else: + try: + self.broadcast_s.sendto(BROADCAST_MESSAGE, + ('<broadcast>', self.broadcast_port)) + #print "Broadcast ping" + except error: + pass # ignore failed broadcasts + self.broadcast_next = time() + self.broadcast_delay + self.broadcast_delay *= BROADCAST_DELAY_INCR + return delay + + def sendudpdata(self): + sprites[0] = '' + udpdata = ''.join(sprites) + if len(broadcast_clients) >= 2: + broadcast_extras = {} + else: + broadcast_extras = None + for c in clients[:]: + c.emit(udpdata, broadcast_extras) + if recording: + recording.udpdata(udpdata) + if broadcast_extras is not None: + udpdata = ''.join(broadcast_extras.keys() + [udpdata]) + try: + self.broadcast_s.sendto(udpdata, + ('<broadcast>', self.broadcast_port)) + #print "Broadcast UDP data" + except error: + pass # ignore failed broadcasts + + def FnExtraDesc(self): + players = 0 + for c in clients: + players += len(c.players) + if players == 0: + return 'no player' + elif players == 1: + return 'one player' + else: + return '%d players' % players + + def updateplayers(self): + pass + + def updateboard(self): + pass + + def newclient(self, conn, addr): + if len(clients)==MAX_CLIENTS: + print "Too many connections; refusing new connection from", addr + conn.close() + else: + try: + addrname = (gethostbyaddr(addr[0])[0],) + addr[1:] + except: + addrname = addr + print 'Connected by', addrname + try: + c = FnClient(conn, addrname) + except error, e: + print 'Connexion already lost!', e + else: + if game is not None: + c.opengame(game) + + def newclient_threadsafe(self, conn, addr): + self.pendingclients.insert(0, (conn, addr)) + + +def recursiveloop(endtime, extra_sockets): + global game + timediff = 1 + while timediff: + if game is not None: + delay = game.mainstep() + else: + delay = 5.0 + iwtd = extra_sockets + serversockets.keys() + timediff = max(0.0, endtime - time()) + iwtd, owtd, ewtd = select(iwtd, [], iwtd, min(delay, timediff)) + if ewtd: + if game: + game.socketerrors(ewtd) + if ewtd: + print >> sys.stderr, "Unexpected socket error reported" + for s in iwtd: + if s in serversockets: + serversockets[s]() # call handler + elif s in extra_sockets: + return s + if not extra_sockets and timediff: + return 1 + return None + +SERVER_SHUTDOWN = 0.0 + +def mainloop(): + global game, SERVER_SHUTDOWN + servertimeout = None + try: + while serversockets: + try: + if game is not None: + delay = game.mainstep() + else: + delay = SERVER_SHUTDOWN or 5.0 + iwtd = serversockets.keys() + try: + iwtd, owtd, ewtd = select(iwtd, [], iwtd, delay) + except Exception, e: + from select import error as select_error + if not isinstance(e, select_error): + raise + iwtd, owtd, ewtd = [], [], [] + if ewtd: + if game: + game.socketerrors(ewtd) + if ewtd: + print >> sys.stderr, "Unexpected socket error reported" + servertimeout = None + if iwtd: + for s in iwtd: + if s in serversockets: + serversockets[s]() # call handler + servertimeout = None + elif SERVER_SHUTDOWN and not ewtd and not owtd: + SERVER_SHUTDOWN -= delay + if SERVER_SHUTDOWN <= 0.001: + raise SystemExit, "Server shutdown requested." + elif clients or getattr(game, 'autoreset', 0): + servertimeout = None + elif servertimeout is None: + servertimeout = time() + SERVER_TIMEOUT + elif time() > servertimeout: + raise SystemExit, "No more server activity, timing out." + except KeyboardInterrupt: + if game is None or not game.FnExcHandler(1): + raise + except SystemExit: + raise + except: + if game is None or not game.FnExcHandler(0): + raise + finally: + removesocket('LISTEN') + removesocket('PING') + if clients: + print "Server crash -- waiting for clients to terminate..." + while clients: + iwtd = [c.socket for c in clients] + try: + iwtd, owtd, ewtd = select(iwtd, [], iwtd, 120.0) + except KeyboardInterrupt: + break + if not (iwtd or owtd or ewtd): + break # timeout - give up + for c in clients[:]: + if c.socket in ewtd: + c.disconnect("select reported an error") + elif c.socket in iwtd: + try: + data = c.socket.recv(2048) + except error, e: + c.disconnect(e) + else: + if not data and not hasattr(c.socket, 'RECV_CAN_RETURN_EMPTY'): + c.disconnect("end of data") + print "Server closed." + +def closeeverything(): + global SERVER_SHUTDOWN + SERVER_SHUTDOWN = 2.5 + if game is not None: + game.FnServerInfo("Server is stopping!") + +# ____________________________________________________________ + +try: + from localmsg import recordfilename +except ImportError: + pass +else: + + class RecordFile: + proto = 2 + has_sound = 0 + + def __init__(self, filename, sampling=1/7.77): + self.filename = filename + self.f = None + self.sampling = sampling + self.msgl = [] + self.write = self.msgl.append + + def start(self): + if not self.f: + import gzip, atexit + self.f = gzip.open(self.filename, 'wb') + atexit.register(self.f.close) + self.recnext = time() + self.sampling + + def udpdata(self, udpdata): + if self.f: + now = time() + if now >= self.recnext: + while now >= self.recnext: + self.recnext += self.sampling + self.write(message(MSG_RECORDED, udpdata)) + self.f.write(''.join(self.msgl)) + del self.msgl[:] + + recording = RecordFile(recordfilename) + del recordfilename |