changeset 372:704bea2e4360

Use a future-proof ECL parser.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Mon, 06 Aug 2012 22:52:22 +0200
parents 6702bc0215dc
children 6deab6ad8be8
files eosd pytouhou/formats/ecl.py pytouhou/game/game.py pytouhou/vm/eclrunner.py
diffstat 4 files changed, 71 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/eosd
+++ b/eosd
@@ -49,7 +49,8 @@ class EoSDGameBossRush(EoSDGame):
 
 
     def cleanup(self):
-        if not (self.boss or self.msg_wait or self.ecl_runner.boss_wait):
+        boss_wait = any(ecl_runner.boss_wait for ecl_runner in self.ecl_runners)
+        if not (self.boss or self.msg_wait or boss_wait):
             self.enemies = [enemy for enemy in self.enemies
                                 if enemy.boss_callback != -1 or enemy.frame > 1]
             self.lasers = [laser for laser in self.lasers if laser.frame > 1]
--- a/pytouhou/formats/ecl.py
+++ b/pytouhou/formats/ecl.py
@@ -37,7 +37,7 @@ class ECL(object):
     enemy waves, triggering dialogs and level completion.
 
     Instance variables:
-    main -- list of instructions describing waves and triggering dialogs
+    mains -- list of lists of instructions describing waves and triggering dialogs
     subs -- list of subroutines
     """
 
@@ -162,28 +162,32 @@ class ECL(object):
                           10: ('II', 'resume_ecl'),
                           12: ('', 'stop_time')}
 
+    _parameters = {6: {'main_count': 1,
+                       'nb_main_offsets': 3,
+                       'jumps_list': {2: 1, 3: 1, 29: 1, 30: 1, 31: 1, 32: 1, 33: 1, 34: 1}}}
+
 
     def __init__(self):
-        self.main = []
-        self.subs = [[]]
+        self.mains = []
+        self.subs = []
 
 
     @classmethod
-    def read(cls, file):
+    def read(cls, file, version=6):
         """Read an ECL file.
 
         Raise an exception if the file is invalid.
         Return a ECL instance otherwise.
         """
 
-        sub_count, main_offset = unpack('<II', file.read(8))
-        if file.read(8) != b'\x00\x00\x00\x00\x00\x00\x00\x00':
-            raise Exception #TODO
+        parameters = cls._parameters[version]
+
+        sub_count, main_count = unpack('<HH', file.read(4))
+
+        main_offsets = unpack('<%dI' % parameters['nb_main_offsets'], file.read(4 * parameters['nb_main_offsets']))
         sub_offsets = unpack('<%dI' % sub_count, file.read(4 * sub_count))
 
         ecl = cls()
-        ecl.subs = []
-        ecl.main = []
 
         # Read subs
         for sub_offset in sub_offsets:
@@ -222,39 +226,44 @@ class ECL(object):
             # keep trace of where the jump is supposed to end up.
             for instr_offset, (i, instr) in zip(instruction_offsets, enumerate(ecl.subs[-1])):
                 time, opcode, rank_mask, param_mask, args = instr
-                if opcode in (2, 29, 30, 31, 32, 33, 34): # relative_jump
-                    frame, relative_offset = args
-                    args = frame, instruction_offsets.index(instr_offset + relative_offset)
-                elif opcode == 3: # relative_jump_ex
-                    frame, relative_offset, counter_id = args
-                    args = frame, instruction_offsets.index(instr_offset + relative_offset), counter_id
-                ecl.subs[-1][i] = time, opcode, rank_mask, param_mask, args
+                if opcode in parameters['jumps_list']:
+                    num = parameters['jumps_list'][opcode]
+                    args = list(args)
+                    args[num] = instruction_offsets.index(instr_offset + args[num])
+                    ecl.subs[-1][i] = time, opcode, rank_mask, param_mask, tuple(args)
 
 
         # Read main
-        file.seek(main_offset)
-        while True:
-            time, = unpack('<H', file.read(2))
-            if time == 0xffff:
+        for main_offset in main_offsets:
+            if main_offset == 0:
                 break
 
-            sub, opcode, size = unpack('<HHH', file.read(6))
-            data = file.read(size - 8)
+            file.seek(main_offset)
+            ecl.mains.append([])
+            while True:
+                time, sub = unpack('<HH', file.read(4))
+                if time == 0xffff and sub == 4:
+                    break
 
-            if opcode in cls._main_instructions:
-                args = unpack('<%s' % cls._main_instructions[opcode][0], data)
-            else:
-                args = (data,)
-                logger.warn('unknown main opcode %d', opcode)
+                opcode, size = unpack('<HH', file.read(4))
+                data = file.read(size - 8)
 
-            ecl.main.append((time, sub, opcode, args))
+                if opcode in cls._main_instructions:
+                    args = unpack('<%s' % cls._main_instructions[opcode][0], data)
+                else:
+                    args = (data,)
+                    logger.warn('unknown main opcode %d', opcode)
+
+                ecl.mains[-1].append((time, sub, opcode, args))
 
         return ecl
 
 
-    def write(self, file):
+    def write(self, file, version=6):
         """Write to an ECL file."""
 
+        parameters = self._parameters[version]
+
         sub_count = len(self.subs)
         sub_offsets = []
         main_offset = 0
@@ -286,15 +295,10 @@ class ECL(object):
             #TODO: clean up this mess
             for instruction, data, offset in zip(sub, instruction_datas, instruction_offsets):
                 time, opcode, rank_mask, param_mask, args = instruction
-                if opcode in (2, 29, 30, 31, 32, 33, 34): # relative_jump
-                    frame, index = args
-                    args = frame, instruction_offsets[index] - offset
-                    format = '<IHHHH%s' % self._instructions[opcode][0]
-                    size = calcsize(format)
-                    data = pack(format, time, opcode, size, rank_mask, param_mask, *args)
-                elif opcode == 3: # relative_jump_ex
-                    frame, index, counter_id = args
-                    args = frame, instruction_offsets[index] - offset, counter_id
+                if opcode in parameters['jumps_list']:
+                    num = parameters['jumps_list'][opcode]
+                    args = list(args)
+                    args[num] = instruction_offsets[args[num]] - offset
                     format = '<IHHHH%s' % self._instructions[opcode][0]
                     size = calcsize(format)
                     data = pack(format, time, opcode, size, rank_mask, param_mask, *args)
@@ -302,15 +306,17 @@ class ECL(object):
             file.write(b'\xff' * 6 + b'\x0c\x00\x00\xff\xff\x00')
 
         # Write main
-        main_offset = file.tell()
-        for time, sub, opcode, args in self.main:
-            format = '<HHHH%s' % self._main_instructions[opcode][0]
-            size = calcsize(format)
+        main_offsets = [0] * parameters['nb_main_offsets']
+        for i, main in enumerate(self.mains):
+            main_offsets[i] = file.tell()
+            for time, sub, opcode, args in main:
+                format = '<HHHH%s' % self._main_instructions[opcode][0]
+                size = calcsize(format)
 
-            file.write(pack(format, time, sub, opcode, size, *args))
-        file.write(b'\xff\xff\x04\x00')
+                file.write(pack(format, time, sub, opcode, size, *args))
+            file.write(b'\xff\xff\x04\x00')
 
         # Patch header
         file.seek(0)
-        file.write(pack('<IIII%dI' % sub_count, sub_count, main_offset, 0, 0, *sub_offsets))
+        file.write(pack('<I%dI%dI' % (parameters['nb_main_offsets'], sub_count), sub_count, *(main_offsets + sub_offsets)))
 
--- a/pytouhou/game/game.py
+++ b/pytouhou/game/game.py
@@ -79,7 +79,7 @@ class Game(object):
                                                                  'stg%denm2.anm' % stage))
         self.etama4 = resource_loader.get_anm_wrapper(('etama4.anm',))
         ecl = resource_loader.get_ecl('ecldata%d.ecl' % stage)
-        self.ecl_runner = ECLMainRunner(ecl, self)
+        self.ecl_runners = [ECLMainRunner(main, ecl.subs, self) for main in ecl.mains]
 
         self.spellcard_effect_anm_wrapper = resource_loader.get_anm_wrapper(('eff0%d.anm' % stage,))
         self.spellcard_effect = None
@@ -215,7 +215,8 @@ class Game(object):
 
     def run_iter(self, keystate):
         # 1. VMs.
-        self.ecl_runner.run_iter()
+        for runner in self.ecl_runners:
+            runner.run_iter()
 
         # 2. Modify difficulty
         if self.frame % (32*60) == (32*60): #TODO: check if that is really that frame.
--- a/pytouhou/vm/eclrunner.py
+++ b/pytouhou/vm/eclrunner.py
@@ -25,12 +25,13 @@ logger = get_logger(__name__)
 
 class ECLMainRunner(object):
     __metaclass__ = MetaRegistry
-    __slots__ = ('_ecl', '_game', 'frame',
+    __slots__ = ('_main', '_subs', '_game', 'frame',
                  'instruction_pointer',
                  'boss_wait')
 
-    def __init__(self, ecl, game):
-        self._ecl = ecl
+    def __init__(self, main, subs, game):
+        self._main = main
+        self._subs = subs
         self._game = game
         self.frame = 0
         self.boss_wait = False
@@ -44,7 +45,7 @@ class ECLMainRunner(object):
 
         while True:
             try:
-                frame, sub, instr_type, args = self._ecl.main[self.instruction_pointer]
+                frame, sub, instr_type, args = self._main[self.instruction_pointer]
             except IndexError:
                 break
 
@@ -76,7 +77,7 @@ class ECLMainRunner(object):
                 z = self._game.prng.rand_double() * 800
         enemy = self._game.new_enemy((x, y, z), life, instr_type,
                                      bonus_dropped, die_score)
-        enemy.process = ECLRunner(self._ecl, sub, enemy, self._game) #TODO
+        enemy.process = ECLRunner(self._subs, sub, enemy, self._game, self._pop_enemy) #TODO
         enemy.process.run_iteration()
 
 
@@ -120,15 +121,16 @@ class ECLMainRunner(object):
 
 class ECLRunner(object):
     __metaclass__ = MetaRegistry
-    __slots__ = ('_ecl', '_enemy', '_game', 'variables', 'sub', 'frame',
+    __slots__ = ('_subs', '_enemy', '_game', '_pop_enemy', 'variables', 'sub', 'frame',
                  'instruction_pointer', 'comparison_reg', 'stack',
                  'running')
 
-    def __init__(self, ecl, sub, enemy, game):
+    def __init__(self, subs, sub, enemy, game, pop_enemy):
         # Things not supposed to change
-        self._ecl = ecl
+        self._subs = subs
         self._enemy = enemy
         self._game = game
+        self._pop_enemy = pop_enemy
 
         self.running = True
 
@@ -155,7 +157,7 @@ class ECLRunner(object):
         # Process script
         while self.running:
             try:
-                frame, instr_type, rank_mask, param_mask, args = self._ecl.subs[self.sub][self.instruction_pointer]
+                frame, instr_type, rank_mask, param_mask, args = self._subs[self.sub][self.instruction_pointer]
             except IndexError:
                 self.running = False
                 break
@@ -759,10 +761,10 @@ class ECLRunner(object):
 
     @instruction(95)
     def pop_enemy(self, sub, x, y, z, life, bonus_dropped, die_score):
-        self._game.ecl_runner._pop_enemy(sub, 0, self._getval(x),
-                                                 self._getval(y),
-                                                 self._getval(z),
-                                                 life, bonus_dropped, die_score)
+        self._pop_enemy(sub, 0, self._getval(x),
+                                self._getval(y),
+                                self._getval(z),
+                                life, bonus_dropped, die_score)
 
 
     @instruction(96)