Mercurial > touhou
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)