# HG changeset patch # User Thibaut Girka # Date 1325094309 -3600 # Node ID 9d4d52793eca8c8b6db63fe85aec70c9eeece5da # Parent 8f4cd1c01d226c317ec0bb6b27acdda12ab47577 Experimental netplay! Yay! diff --git a/eosd b/eosd --- a/eosd +++ b/eosd @@ -27,7 +27,9 @@ from pytouhou.game.player import PlayerS from pytouhou.formats.t6rp import T6RP -def main(path, stage_num, rank, character, replay, data): +def main(path, stage_num, rank, character, replay, data, port, remote): + players = [PlayerState(character=character)] + if replay: with open(replay, 'rb') as file: replay = T6RP.read(file) @@ -40,10 +42,23 @@ def main(path, stage_num, rank, characte else: prng = None + if port != 0: + from pytouhou.network import Network + if remote: + remote_addr, remote_port = remote.split(':') + addr = remote_addr, int(remote_port) + else: + addr = None + + players.append(PlayerState(character=character)) + con = Network(port, addr) + else: + con = None + resource_loader = Loader() resource_loader.scan_archives(os.path.join(path, name) for name in data) - game = EoSDGame(resource_loader, [PlayerState(character=character)], stage_num, rank, 16, + game = EoSDGame(resource_loader, players, stage_num, rank, 16, prng=prng) # Load stage data @@ -53,7 +68,7 @@ def main(path, stage_num, rank, characte background = Background(stage, background_anm_wrapper) # Main loop - runner = GameRunner(resource_loader, game, background, replay=replay) + runner = GameRunner(resource_loader, game, background, replay=replay, con=con) runner.start() @@ -65,7 +80,9 @@ parser.add_argument('-s', '--stage', met parser.add_argument('-r', '--rank', metavar='RANK', type=int, default=0, help='Rank, from 0 (Easy, default) to 3 (Lunatic).') parser.add_argument('-c', '--character', metavar='CHARACTER', type=int, default=0, help='Select the character to use, from 0 (ReimuA, default) to 3 (MarisaB).') parser.add_argument('--replay', metavar='REPLAY', help='Select a replay') +parser.add_argument('--port', metavar='PORT', type=int, default=0, help='Port to use for netplay') +parser.add_argument('--remote', metavar='REMOTE', default=None, help='Remote address') args = parser.parse_args() -main(args.path, args.stage, args.rank, args.character, args.replay, tuple(args.data)) +main(args.path, args.stage, args.rank, args.character, args.replay, tuple(args.data), args.port, args.remote) diff --git a/pytouhou/game/game.py b/pytouhou/game/game.py --- a/pytouhou/game/game.py +++ b/pytouhou/game/game.py @@ -131,7 +131,7 @@ class Game(object): return enemy - def run_iter(self, keystate): + def run_iter(self, keystates): # 1. VMs. self.ecl_runner.run_iter() if self.frame % (32*60) == (32*60): #TODO: check if that is really that frame. @@ -151,7 +151,7 @@ class Game(object): # Pri 6 is background self.update_effect() #TODO: Pri unknown - self.update_players(keystate) # Pri 7 + self.update_players(keystates) # Pri 7 self.update_enemies() # Pri 9 self.update_effects() # Pri 10 self.update_bullets() # Pri 11 @@ -173,8 +173,8 @@ class Game(object): enemy.update() - def update_players(self, keystate): - for player in self.players: + def update_players(self, keystates): + for player, keystate in zip(self.players, keystates): player.update(keystate) #TODO: differentiate keystates (multiplayer mode) if player.state.x < 8.: player.state.x = 8. diff --git a/pytouhou/network.py b/pytouhou/network.py new file mode 100644 --- /dev/null +++ b/pytouhou/network.py @@ -0,0 +1,66 @@ +import socket +import struct +from select import select + +MSG_STRUCT = struct.Struct('!IHHB') + +class Network(object): + def __init__(self, port=8080, dest=None): + self.frame = 0 + self.keystate = 0 + self.old_keystate = 0 + + self.remote_addr = dest + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + self.sock.setblocking(0) + self.sock.bind(('', port)) + + + def read_messages(self): + messages = [] + + rlist, wlist, xlist = select([self.sock], [], [], 0) + while rlist: + msg, addr = rlist[0].recvfrom(MSG_STRUCT.size) + # Check whether the message comes from the right address + if self.remote_addr is None or addr == self.remote_addr: + self.remote_addr = addr + + frame, keystate, old_keystate, checksum = MSG_STRUCT.unpack(msg) + + # Check for well-formedness + if checksum == sum(ord(c) for c in msg[:-1]) & 0xFF: + messages.append((frame, keystate, old_keystate, checksum)) + else: + print('Mismatch', self.remote_addr, addr) + + rlist, wlist, xlist = select(rlist, [], [], 0) + + return messages + + + def send_message(self): + frame, keystate, old_keystate = self.frame, self.keystate, self.old_keystate + if self.remote_addr is not None: + checksum = frame + (frame >> 8) + (frame >> 16) + (frame >> 24) + checksum += keystate + (keystate >> 8) + checksum += old_keystate + (old_keystate >> 8) + checksum &= 0xFF + self.sock.sendto(MSG_STRUCT.pack(frame, keystate, old_keystate, checksum), self.remote_addr) + + + def run_iter(self, game, keystate): + if self.frame < game.frame: + self.old_keystate, self.keystate = self.keystate, keystate + self.frame = game.frame + + for frame, keystate, old_keystate, checksum in self.read_messages(): + if frame == game.frame: + game.run_iter([self.keystate, keystate]) + elif frame == game.frame + 1: + print('Skipped') + game.run_iter([self.keystate, old_keystate]) + game.run_iter([self.keystate, keystate]) + + self.send_message() + diff --git a/pytouhou/ui/gamerunner.py b/pytouhou/ui/gamerunner.py --- a/pytouhou/ui/gamerunner.py +++ b/pytouhou/ui/gamerunner.py @@ -32,13 +32,15 @@ logger = get_logger(__name__) class GameRunner(pyglet.window.Window, GameRenderer): - def __init__(self, resource_loader, game=None, background=None, replay=None): + def __init__(self, resource_loader, game=None, background=None, replay=None, + con=None): GameRenderer.__init__(self, resource_loader, game, background) width, height = (game.width, game.height) if game else (None, None) pyglet.window.Window.__init__(self, width=width, height=height, caption='PyTouhou', resizable=False) + self.con = con self.replay_level = None if not replay or not replay.levels[game.stage-1]: self.keys = pyglet.window.key.KeyStateHandler() @@ -125,7 +127,6 @@ class GameRunner(pyglet.window.Window, G keystate |= 128 if self.keys[pyglet.window.key.LCTRL]: keystate |= 256 - self.game.run_iter(keystate) else: keystate = 0 for frame, _keystate, unknown in self.replay_level.keys: @@ -134,7 +135,10 @@ class GameRunner(pyglet.window.Window, G else: keystate = _keystate - self.game.run_iter(keystate) + if self.con: + self.con.run_iter(self.game, keystate) + else: + self.game.run_iter([keystate]) def on_draw(self):