1
0
mirror of https://github.com/abakh/nbsdgames.git synced 2025-02-02 15:07:27 -05:00
nbsdgames/common/gamesrv.py
2022-02-03 13:46:22 +03:30

1380 lines
38 KiB
Python

from socket import *
from select import select
from struct import pack, unpack
import zlib, os, random, struct, hashlib, 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):
from . 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 = hashlib.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 list(map(lambda i, fn=self.geticon, w=w, h=h: fn(i*w, 0, w, h), list(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 list(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, xxx_todo_changeme):
(start, length) = xxx_todo_changeme
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 = list(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 = list(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 list(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(list(self.sounds.keys()) + [udpdata])
else:
broadcast_extras.update(self.sounds)
for key, value in list(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 as e:
print('ignored:', str(e), file=sys.stderr)
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 as 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 as 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 list(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 list(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 id in self.players:
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
from . 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 as e:
print("Cannot set UDP socket to", addr, str(e), file=sys.stderr)
self.udpsocket = None
self.udpsockcounter = sys.maxsize
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 as e:
print("Cannot set IPTOS_LOWDELAY:", str(e), file=sys.stderr)
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 list(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 list(bitmaps.values()):
b.defall(self)
self.finishinit(game)
for id, p in list(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(range(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:
from . import hostchooser
s = hostchooser.serverside_ping(only_port)
if s is None:
return None
def pingsocket_handler(s=s):
global game
from . 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 http.server import HTTPServer as ServerClass
if HandlerClass is None:
from . 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 as e:
print("cannot start HTTP server", str(e), file=sys.stderr)
return None
else:
raise
s = httpd.socket
addsocket('HTTP', s, httpd.handle_request)
return s
BROADCAST_PORT_RANGE = range(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 as e:
print("Cannot broadcast", str(e), file=sys.stderr)
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 list(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(list(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 as 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 + list(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("Unexpected socket error reported", file=sys.stderr)
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 = list(serversockets.keys())
try:
iwtd, owtd, ewtd = select(iwtd, [], iwtd, delay)
except Exception as 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("Unexpected socket error reported", file=sys.stderr)
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 as 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