changeset 187:46793ccfedca

Implement replays.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Wed, 26 Oct 2011 17:54:03 -0700
parents 84da28ae7ee4
children 008f90ebfdc0
files eclviewer.py pytouhou/formats/t6rp.py pytouhou/opengl/gamerenderer.pyx pytouhou/opengl/gamerunner.py
diffstat 4 files changed, 129 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/eclviewer.py
+++ b/eclviewer.py
@@ -24,9 +24,18 @@ from pytouhou.game.background import Bac
 from pytouhou.opengl.gamerunner import GameRunner
 from pytouhou.game.games import EoSDGame
 from pytouhou.game.player import PlayerState
+from pytouhou.formats.t6rp import T6RP
 
 
-def main(path, stage_num, rank, character):
+def main(path, stage_num, rank, character, replay):
+    if replay:
+        with open(replay, 'rb') as file:
+            replay = T6RP.read(file)
+        rank = replay.rank
+        character = replay.character
+        if not replay.levels[stage_num-1]:
+            raise Exception
+
     resource_loader = Loader()
     resource_loader.scan_archives(os.path.join(path, name)
                                     for name in ('CM.DAT', 'ST.DAT'))
@@ -42,7 +51,7 @@ def main(path, stage_num, rank, characte
     print(stage.name)
 
     # Main loop
-    runner = GameRunner(resource_loader, game, background)
+    runner = GameRunner(resource_loader, game, background, replay=replay)
     runner.start()
 
 
@@ -52,7 +61,8 @@ parser.add_argument('-p', '--path', meta
 parser.add_argument('-s', '--stage', metavar='STAGE', type=int, required=True, help='Stage, 1 to 7 (Extra).')
 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')
 
 args = parser.parse_args()
 
-main(args.path, args.stage, args.rank, args.character)
+main(args.path, args.stage, args.rank, args.character, args.replay)
new file mode 100644
--- /dev/null
+++ b/pytouhou/formats/t6rp.py
@@ -0,0 +1,70 @@
+# -*- encoding: utf-8 -*-
+##
+## Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+
+from struct import unpack
+from pytouhou.utils.helpers import read_string
+
+from pytouhou.utils.helpers import get_logger
+
+logger = get_logger(__name__)
+
+
+class Level(object):
+    def __init__(self):
+        self.keys = []
+
+
+class T6RP(object):
+    def __init__(self):
+        self.levels = []
+
+
+    @classmethod
+    def read(cls, file):
+        if file.read(4) != b'T6RP':
+            raise Exception
+        if file.read(2) != b'\x02\x01':
+            raise Exception
+
+        replay = cls()
+
+        replay.character, replay.rank, checksum, unknown, key, unknown = unpack('<BBHIBB', file.read(10))
+        replay.date = read_string(file, 9, 'ascii')
+        replay.name = read_string(file, 9, 'ascii').rstrip()
+        unknown, replay.score, unknown, replay.slowdown, unknown = unpack('<HIIfI', file.read(18))
+
+        stages_offsets = unpack('<7I', file.read(28))
+
+        replay.levels = []
+
+        for offset in stages_offsets:
+            replay.levels.append(None)
+
+            if offset == 0:
+                continue
+
+            level = Level()
+            replay.levels[-1] = level
+
+            file.seek(offset)
+            level.score, level.random_seed, unknown, level.power, level.lives, level.bombs, level.difficulty, unknown = unpack('<IHHBbbBI', file.read(16))
+
+            while True:
+                time, keys, unknown = unpack('<IHH', file.read(8))
+
+                if time == 9999999:
+                    break
+
+                level.keys.append((time, keys))
+
--- a/pytouhou/opengl/gamerenderer.pyx
+++ b/pytouhou/opengl/gamerenderer.pyx
@@ -42,7 +42,7 @@ cdef class GameRenderer:
     cdef Vertex *vertex_buffer
 
 
-    def __cinit__(self, resource_loader, game=None, background=None):
+    def __cinit__(self):
         # Allocate buffers
         self.vertex_buffer = <Vertex*> malloc(MAX_ELEMENTS * sizeof(Vertex))
 
--- a/pytouhou/opengl/gamerunner.py
+++ b/pytouhou/opengl/gamerunner.py
@@ -21,11 +21,17 @@ from pytouhou.opengl.gamerenderer import
 
 
 class GameRunner(pyglet.window.Window, GameRenderer):
-    def __init__(self, resource_loader, game=None, background=None):
+    def __init__(self, resource_loader, game=None, background=None, replay=None):
         GameRenderer.__init__(self, resource_loader, game, background)
         pyglet.window.Window.__init__(self, caption='PyTouhou', resizable=False)
-        self.keys = pyglet.window.key.KeyStateHandler()
-        self.push_handlers(self.keys)
+        self.replay_level = None
+        if not replay or not replay.levels[game.stage-1]:
+            self.keys = pyglet.window.key.KeyStateHandler()
+            self.push_handlers(self.keys)
+        else:
+            self.keys = 0
+            self.instruction_pointer = 0
+            self.replay_level = replay.levels[game.stage-1]
 
         self.fps_display = pyglet.clock.ClockDisplay()
 
@@ -85,28 +91,42 @@ class GameRunner(pyglet.window.Window, G
         if self.background:
             self.background.update(self.game.frame)
         if self.game:
-            #TODO: allow user settings
-            keystate = 0
-            if self.keys[pyglet.window.key.W]:
-                keystate |= 1
-            if self.keys[pyglet.window.key.X]:
-                keystate |= 2
-            #TODO: on some configurations, LSHIFT is Shift_L when pressed
-            # and ISO_Prev_Group when released, confusing the hell out of pyglet
-            # and leading to a always-on LSHIFT...
-            if self.keys[pyglet.window.key.LSHIFT]:
-                keystate |= 4
-            if self.keys[pyglet.window.key.UP]:
-                keystate |= 16
-            if self.keys[pyglet.window.key.DOWN]:
-                keystate |= 32
-            if self.keys[pyglet.window.key.LEFT]:
-                keystate |= 64
-            if self.keys[pyglet.window.key.RIGHT]:
-                keystate |= 128
-            if self.keys[pyglet.window.key.LCTRL]:
-                keystate |= 256
-            self.game.run_iter(keystate) #TODO: self.keys...
+            if not self.replay_level:
+                #TODO: allow user settings
+                keystate = 0
+                if self.keys[pyglet.window.key.W]:
+                    keystate |= 1
+                if self.keys[pyglet.window.key.X]:
+                    keystate |= 2
+                #TODO: on some configurations, LSHIFT is Shift_L when pressed
+                # and ISO_Prev_Group when released, confusing the hell out of pyglet
+                # and leading to a always-on LSHIFT...
+                if self.keys[pyglet.window.key.LSHIFT]:
+                    keystate |= 4
+                if self.keys[pyglet.window.key.UP]:
+                    keystate |= 16
+                if self.keys[pyglet.window.key.DOWN]:
+                    keystate |= 32
+                if self.keys[pyglet.window.key.LEFT]:
+                    keystate |= 64
+                if self.keys[pyglet.window.key.RIGHT]:
+                    keystate |= 128
+                if self.keys[pyglet.window.key.LCTRL]:
+                    keystate |= 256
+                self.game.run_iter(keystate) #TODO: self.keys...
+            else:
+                frame, keys = self.replay_level.keys[self.instruction_pointer]
+
+                if frame > self.game.frame:
+                    self.game.run_iter(self.keys)
+                    return
+
+                self.instruction_pointer += 1
+
+                if frame == self.game.frame:
+                    self.keys = keys
+
+                self.game.run_iter(self.keys)
 
 
     def on_draw(self):