Mercurial > touhou
comparison pytouhou/vm/eclrunner.py @ 316:f0be7ea62330
Fix a bug with ECL instruction 96, and fix overall ECL handling.
The issue with instruction 96 was about death callbacks,
being executed on the caller of instruction 96 instead of the dying enemies.
This was introduced by changeset 5930b33a0370.
Additionnaly, ECL processes are now an attribute of the Enemy,
and death/timeout conditions are checked right after the ECL frame,
even if the ECL script has already ended, just like in the original game.
author | Thibaut Girka <thib@sitedethib.com> |
---|---|
date | Thu, 29 Mar 2012 21:18:35 +0200 |
parents | 2ba2462afc70 |
children | 1366cefd0334 |
comparison
equal
deleted
inserted
replaced
315:e935ed8dc5e6 | 316:f0be7ea62330 |
---|---|
23 | 23 |
24 | 24 |
25 | 25 |
26 class ECLMainRunner(object): | 26 class ECLMainRunner(object): |
27 __metaclass__ = MetaRegistry | 27 __metaclass__ = MetaRegistry |
28 __slots__ = ('_ecl', '_game', 'processes', 'frame', | 28 __slots__ = ('_ecl', '_game', 'frame', |
29 'instruction_pointer', | 29 'instruction_pointer', |
30 'boss_wait') | 30 'boss_wait') |
31 | 31 |
32 def __init__(self, ecl, game): | 32 def __init__(self, ecl, game): |
33 self._ecl = ecl | 33 self._ecl = ecl |
34 self._game = game | 34 self._game = game |
35 self.frame = 0 | 35 self.frame = 0 |
36 self.boss_wait = False | 36 self.boss_wait = False |
37 | |
38 self.processes = [] | |
39 | 37 |
40 self.instruction_pointer = 0 | 38 self.instruction_pointer = 0 |
41 | 39 |
42 | 40 |
43 def run_iter(self): | 41 def run_iter(self): |
61 callback = self._handlers[instr_type] | 59 callback = self._handlers[instr_type] |
62 except KeyError: | 60 except KeyError: |
63 logger.warn('unhandled main opcode %d (args: %r)', instr_type, args) | 61 logger.warn('unhandled main opcode %d (args: %r)', instr_type, args) |
64 else: | 62 else: |
65 callback(self, sub, instr_type, *args) | 63 callback(self, sub, instr_type, *args) |
66 | |
67 self.processes[:] = (process for process in self.processes | |
68 if process.run_iteration()) | |
69 | 64 |
70 if not (self._game.msg_wait or self.boss_wait): | 65 if not (self._game.msg_wait or self.boss_wait): |
71 self.frame += 1 | 66 self.frame += 1 |
72 | 67 |
73 | 68 |
77 x = self._game.prng.rand_double() * 368 | 72 x = self._game.prng.rand_double() * 368 |
78 if y < -990: #102h.exe@0x41184b | 73 if y < -990: #102h.exe@0x41184b |
79 y = self._game.prng.rand_double() * 416 | 74 y = self._game.prng.rand_double() * 416 |
80 if z < -990: #102h.exe@0x411881 | 75 if z < -990: #102h.exe@0x411881 |
81 z = self._game.prng.rand_double() * 800 | 76 z = self._game.prng.rand_double() * 800 |
82 enemy = self._game.new_enemy((x, y, z), life, instr_type, bonus_dropped, die_score) | 77 enemy = self._game.new_enemy((x, y, z), life, instr_type, |
83 process = ECLRunner(self._ecl, sub, enemy, self._game) | 78 bonus_dropped, die_score) |
84 self.processes.append(process) | 79 enemy.process = ECLRunner(self._ecl, sub, enemy, self._game) #TODO |
85 process.run_iteration() | 80 enemy.process.run_iteration() |
86 | 81 |
87 | 82 |
88 @instruction(0) | 83 @instruction(0) |
89 @instruction(2) | 84 @instruction(2) |
90 @instruction(4) | 85 @instruction(4) |
124 | 119 |
125 | 120 |
126 class ECLRunner(object): | 121 class ECLRunner(object): |
127 __metaclass__ = MetaRegistry | 122 __metaclass__ = MetaRegistry |
128 __slots__ = ('_ecl', '_enemy', '_game', 'variables', 'sub', 'frame', | 123 __slots__ = ('_ecl', '_enemy', '_game', 'variables', 'sub', 'frame', |
129 'instruction_pointer', 'comparison_reg', 'stack') | 124 'instruction_pointer', 'comparison_reg', 'stack', |
125 'running') | |
130 | 126 |
131 def __init__(self, ecl, sub, enemy, game): | 127 def __init__(self, ecl, sub, enemy, game): |
132 # Things not supposed to change | 128 # Things not supposed to change |
133 self._ecl = ecl | 129 self._ecl = ecl |
134 self._enemy = enemy | 130 self._enemy = enemy |
135 self._game = game | 131 self._game = game |
132 | |
133 self.running = True | |
136 | 134 |
137 # Things supposed to change (and be put in the stack) | 135 # Things supposed to change (and be put in the stack) |
138 self.variables = [0, 0, 0, 0, | 136 self.variables = [0, 0, 0, 0, |
139 0., 0., 0., 0., | 137 0., 0., 0., 0., |
140 0, 0, 0, 0] | 138 0, 0, 0, 0] |
143 | 141 |
144 self.stack = [] | 142 self.stack = [] |
145 | 143 |
146 | 144 |
147 def switch_to_sub(self, sub): | 145 def switch_to_sub(self, sub): |
146 self.running = True | |
148 self.frame = 0 | 147 self.frame = 0 |
149 self.sub = sub | 148 self.sub = sub |
150 self.instruction_pointer = 0 | 149 self.instruction_pointer = 0 |
151 | 150 |
152 | 151 |
205 enm.death_callback = -1 | 204 enm.death_callback = -1 |
206 enm.timeout = -1 #TODO: check | 205 enm.timeout = -1 #TODO: check |
207 else: | 206 else: |
208 raise Exception('What the hell, man!') | 207 raise Exception('What the hell, man!') |
209 | 208 |
209 | |
210 def run_iteration(self): | 210 def run_iteration(self): |
211 # First, if enemy is dead, return | 211 # Process script |
212 if self._enemy.removed: | 212 while self.running: |
213 return False | |
214 | |
215 # Then, check for callbacks | |
216 self.handle_callbacks() | |
217 | |
218 # Now, process script | |
219 while True: | |
220 try: | 213 try: |
221 frame, instr_type, rank_mask, param_mask, args = self._ecl.subs[self.sub][self.instruction_pointer] | 214 frame, instr_type, rank_mask, param_mask, args = self._ecl.subs[self.sub][self.instruction_pointer] |
222 except IndexError: | 215 except IndexError: |
223 return False | 216 self.running = False |
217 break | |
224 | 218 |
225 if frame > self.frame: | 219 if frame > self.frame: |
226 break | 220 break |
227 else: | 221 else: |
228 self.instruction_pointer += 1 | 222 self.instruction_pointer += 1 |
238 else: | 232 else: |
239 callback(self, *args) | 233 callback(self, *args) |
240 logger.debug('executed opcode %d (args: %r)', instr_type, args) | 234 logger.debug('executed opcode %d (args: %r)', instr_type, args) |
241 | 235 |
242 self.frame += 1 | 236 self.frame += 1 |
243 return True | 237 |
238 # Handle callbacks | |
239 self.handle_callbacks() | |
244 | 240 |
245 | 241 |
246 def _getval(self, value): | 242 def _getval(self, value): |
247 if -10012 <= value <= -10001: | 243 if -10012 <= value <= -10001: |
248 return self.variables[int(-10001-value)] | 244 return self.variables[int(-10001-value)] |
824 life, bonus_dropped, die_score) | 820 life, bonus_dropped, die_score) |
825 | 821 |
826 | 822 |
827 @instruction(96) | 823 @instruction(96) |
828 def kill_enemies(self): | 824 def kill_enemies(self): |
829 for proc in self._game.ecl_runner.processes: | 825 for enemy in self._game.enemies: |
830 if proc._enemy.boss: | 826 if enemy.boss: |
831 pass # Bosses are immune to 96 | 827 pass # Bosses are immune to 96 |
832 elif proc._enemy.touchable: | 828 elif enemy.touchable: |
833 proc._enemy.life = 0 | 829 enemy.life = 0 |
834 elif proc._enemy.death_callback > 0: | 830 elif enemy.death_callback > 0: |
835 #TODO: check | 831 #TODO: check |
836 #TODO: refactor | 832 enemy.process.switch_to_sub(enemy.death_callback) |
837 self.switch_to_sub(proc._enemy.death_callback) | 833 enemy.death_callback = -1 |
838 proc._enemy.death_callback = -1 | |
839 | 834 |
840 | 835 |
841 @instruction(97) | 836 @instruction(97) |
842 def set_anim(self, script): | 837 def set_anim(self, script): |
843 self._enemy.set_anim(script) | 838 self._enemy.set_anim(script) |