Mercurial > touhou
comparison pytouhou/game/eclrunner.py @ 49:cbe1cb50f2fd
Refactor ECLRunner/EnemyManager so that all VM stuff goes to ECLRunner
author | Thibaut Girka <thib@sitedethib.com> |
---|---|
date | Mon, 22 Aug 2011 19:23:00 +0200 |
parents | 8353c33d53d4 |
children | 811cefefb5c8 |
comparison
equal
deleted
inserted
replaced
48:8353c33d53d4 | 49:cbe1cb50f2fd |
---|---|
1 class MetaRegistry(type): | |
2 def __new__(mcs, name, bases, classdict): | |
3 instruction_handlers = {} | |
4 for item in classdict.itervalues(): | |
5 try: | |
6 instruction_ids = item._instruction_ids | |
7 except AttributeError: | |
8 pass | |
9 else: | |
10 for id_ in instruction_ids: | |
11 instruction_handlers[id_] = item | |
12 classdict['_handlers'] = instruction_handlers | |
13 return type.__new__(mcs, name, bases, classdict) | |
14 | |
15 | |
16 | |
17 def instruction(instruction_id): | |
18 def _decorator(func): | |
19 if not hasattr(func, '_instruction_ids'): | |
20 func._instruction_ids = set() | |
21 func._instruction_ids.add(instruction_id) | |
22 return func | |
23 return _decorator | |
24 | |
25 | |
26 | |
1 class ECLRunner(object): | 27 class ECLRunner(object): |
2 def __init__(self, ecl, sub, frame=0, instruction_pointer=0, implementation=None): | 28 __metaclass__ = MetaRegistry |
3 self.ecl = ecl | 29 __slots__ = ('_ecl', '_enemy', '_game_state', 'variables', 'sub', 'frame', |
4 | 30 'instruction_pointer', 'stack') |
31 | |
32 def __init__(self, ecl, sub, enemy, game_state): | |
33 # Things not supposed to change | |
34 self._ecl = ecl | |
35 self._enemy = enemy | |
36 self._game_state = game_state | |
37 | |
38 # Things supposed to change (and be put in the stack) | |
5 self.variables = [0, 0, 0, 0, | 39 self.variables = [0, 0, 0, 0, |
6 0., 0., 0., 0., | 40 0., 0., 0., 0., |
7 0, 0, 0, 0] | 41 0, 0, 0, 0] |
8 self.sub = sub | 42 self.sub = sub |
9 self.frame = frame | 43 self.frame = 0 |
10 self.instruction_pointer = instruction_pointer | 44 self.instruction_pointer = 0 |
11 | 45 |
12 self.stack = [] | 46 self.stack = [] |
13 | 47 |
14 self.implementation = {4: self.set_variable, | 48 |
15 5: self.set_variable, | 49 def run_iteration(self): |
16 2: self.relative_jump, | 50 # First, if enemy is dead, return |
17 3: self.relative_jump_ex, | 51 if self._enemy._removed: |
18 20: self.add, | 52 return False |
19 21: self.substract, | 53 |
20 35: self.call, | 54 # Then, check for callbacks |
21 36: self.ret, | 55 #TODO |
22 109: self.memory_write} | 56 |
23 if implementation: | 57 # Now, process script |
24 self.implementation.update(implementation) | 58 frame = self.frame |
25 | 59 try: |
26 | 60 while frame <= self.frame: |
27 def _get_value(self, value): #TODO: -10013 and beyond! | 61 frame, instr_type, rank_mask, param_mask, args = self._ecl.subs[self.sub][self.instruction_pointer] |
28 assert not -10025 <= value <= -10013 | 62 |
63 #TODO: skip bad ranks | |
64 | |
65 if frame == self.frame: | |
66 try: | |
67 callback = self._handlers[instr_type] | |
68 except KeyError: | |
69 print('Warning: unhandled opcode %d!' % instr_type) #TODO | |
70 else: | |
71 callback(self, *args) | |
72 frame, instr_type, rank_mask, param_mask, args = self._ecl.subs[self.sub][self.instruction_pointer] | |
73 if frame <= self.frame: | |
74 self.instruction_pointer += 1 | |
75 except IndexError: | |
76 return False | |
77 | |
78 self.frame += 1 | |
79 return True | |
80 | |
81 | |
82 def _getval(self, value): | |
29 if -10012 <= value <= -10001: | 83 if -10012 <= value <= -10001: |
30 return self.variables[int(-10001-value)] | 84 return self.variables[int(-10001-value)] |
85 elif -10025 <= value <= -10013: | |
86 raise NotImplementedError #TODO | |
31 else: | 87 else: |
32 return value | 88 return value |
33 | 89 |
34 | 90 |
35 def add(self, variable_id, a, b): | 91 def _setval(self, variable_id, value): |
36 #TODO: proper variable handling | 92 if -10012 <= value <= -10001: |
37 #TODO: int vs float thing | 93 self.variables[int(-10001-variable_id)] = value |
38 self.variables[-10001-variable_id] = self._get_value(a) + self._get_value(b) | 94 elif -10025 <= value <= -10013: |
39 | 95 raise NotImplementedError #TODO |
40 | 96 else: |
41 def substract(self, variable_id, a, b): | 97 raise IndexError #TODO |
42 #TODO: proper variable handling | 98 |
43 #TODO: int vs float thing | 99 |
44 self.variables[-10001-variable_id] = self._get_value(a) - self._get_value(b) | 100 @instruction(1) |
45 | 101 def destroy(self, arg): |
46 | 102 #TODO: arg? |
47 | 103 self._enemy._removed = True |
48 def memory_write(self, value, index): | 104 |
49 #TODO | 105 |
50 #XXX: this is a hack to display bosses although we don't handle MSG :) | 106 @instruction(2) |
51 if index == 0: | 107 def relative_jump(self, frame, instruction_pointer): |
52 self.sub = value | 108 self.frame, self.instruction_pointer = frame, instruction_pointer |
53 self.frame = 0 | 109 |
54 self.instruction_pointer = 0 | 110 |
55 | 111 @instruction(3) |
56 | 112 def relative_jump_ex(self, frame, instruction_pointer, variable_id): |
113 if self.variables[-10001-variable_id]: | |
114 self.variables[-10001-variable_id] -= 1 | |
115 self.frame, self.instruction_pointer = frame, instruction_pointer | |
116 | |
117 | |
118 @instruction(4) | |
119 @instruction(5) | |
120 def set_variable(self, variable_id, value): | |
121 #TODO: -10013 and beyond! | |
122 self.variables[-10001-variable_id] = self._getval(value) | |
123 | |
124 | |
125 @instruction(35) | |
57 def call(self, sub, param1, param2): | 126 def call(self, sub, param1, param2): |
58 self.stack.append((self.sub, self.frame, self.instruction_pointer, | 127 self.stack.append((self.sub, self.frame, self.instruction_pointer, |
59 self.variables)) | 128 self.variables)) |
60 self.sub = sub | 129 self.sub = sub |
61 self.frame = 0 | 130 self.frame = 0 |
63 self.variables = [param1, 0, 0, 0, | 132 self.variables = [param1, 0, 0, 0, |
64 param2, 0., 0., 0., | 133 param2, 0., 0., 0., |
65 0, 0, 0, 0] | 134 0, 0, 0, 0] |
66 | 135 |
67 | 136 |
137 @instruction(36) | |
68 def ret(self): | 138 def ret(self): |
69 self.sub, self.frame, self.instruction_pointer, self.variables = self.stack.pop() | 139 self.sub, self.frame, self.instruction_pointer, self.variables = self.stack.pop() |
70 | 140 |
71 | 141 |
72 def set_variable(self, variable_id, value): | 142 @instruction(20) |
73 #TODO: -10013 and beyond! | 143 def add(self, variable_id, a, b): |
74 self.variables[-10001-variable_id] = self._get_value(value) | 144 #TODO: proper variable handling |
75 | 145 #TODO: int vs float thing |
76 | 146 self.variables[-10001-variable_id] = self._getval(a) + self._getval(b) |
77 def relative_jump(self, frame, instruction_pointer): | 147 |
78 self.frame, self.instruction_pointer = frame, instruction_pointer | 148 |
79 | 149 @instruction(21) |
80 | 150 def substract(self, variable_id, a, b): |
81 def relative_jump_ex(self, frame, instruction_pointer, variable_id): | 151 #TODO: proper variable handling |
82 if self.variables[-10001-variable_id]: | 152 #TODO: int vs float thing |
83 self.variables[-10001-variable_id] -= 1 | 153 self.variables[-10001-variable_id] = self._getval(a) - self._getval(b) |
84 self.frame, self.instruction_pointer = frame, instruction_pointer | 154 |
85 | 155 |
86 | 156 @instruction(43) |
87 def update(self): | 157 def set_pos(self, x, y, z): |
88 frame = self.frame | 158 self._enemy.set_pos(x, y, z) |
89 try: | 159 |
90 while frame <= self.frame: | 160 |
91 frame, instr_type, rank_mask, param_mask, args = self.ecl.subs[self.sub][self.instruction_pointer] | 161 @instruction(45) |
92 | 162 def set_angle_speed(self, angle, speed): |
93 if frame == self.frame: | 163 self._enemy.angle, self._enemy.speed = angle, speed |
94 try: | 164 |
95 callback = self.implementation[instr_type] | 165 |
96 except KeyError: | 166 @instruction(46) |
97 print('Warning: unhandled opcode %d!' % instr_type) #TODO | 167 def set_rotation_speed(self, speed): |
98 else: | 168 self._enemy.rotation_speed = speed |
99 callback(*args) | 169 |
100 frame, instr_type, rank_mask, param_mask, args = self.ecl.subs[self.sub][self.instruction_pointer] | 170 |
101 if frame <= self.frame: | 171 @instruction(47) |
102 self.instruction_pointer += 1 | 172 def set_speed(self, speed): |
103 except IndexError: | 173 self._enemy.speed = speed |
104 pass #TODO: script ended, destroy enemy | 174 |
105 | 175 |
106 self.frame += 1 | 176 @instruction(48) |
107 | 177 def set_acceleration(self, acceleration): |
178 self._enemy.acceleration = acceleration | |
179 | |
180 | |
181 @instruction(51) | |
182 def target_player(self, unknown, speed): | |
183 self._enemy.speed = speed #TODO: unknown | |
184 player_x, player_y = 192., 384.#TODO | |
185 self._enemy.angle = atan2(player_y - self._enemy.y, player_x - self._enemy.x) #TODO | |
186 | |
187 | |
188 @instruction(57) | |
189 def move_to(self, duration, x, y, z): | |
190 self._enemy.move_to(duration, x, y, z) | |
191 | |
192 | |
193 @instruction(77) | |
194 def set_bullet_interval(self, value): | |
195 self._enemy.bullet_launch_interval = value | |
196 | |
197 | |
198 @instruction(78) | |
199 def set_delay_attack(self): | |
200 self._enemy.delay_attack = True | |
201 | |
202 | |
203 @instruction(79) | |
204 def set_no_delay_attack(self): | |
205 self._enemy.delay_attack = False | |
206 | |
207 | |
208 @instruction(81) | |
209 def set_bullet_launch_offset(self, x, y, z): | |
210 self._enemy.bullet_launch_offset = (x, y) | |
211 | |
212 | |
213 @instruction(97) | |
214 def set_anim(self, sprite_index): | |
215 self._enemy.set_anim(sprite_index) | |
216 | |
217 | |
218 @instruction(98) | |
219 def set_multiple_anims(self, default, end_left, end_right, left, right): | |
220 self._enemy.movement_dependant_sprites = end_left, end_right, left, right | |
221 self._enemy.set_anim(default) | |
222 | |
223 | |
224 @instruction(100) | |
225 def set_death_anim(self, sprite_index): | |
226 self._enemy.death_anim = sprite_index % 256 #TODO | |
227 | |
228 | |
229 @instruction(103) | |
230 def set_hitbox(self, width, height, depth): | |
231 self._enemy.hitbox = (width, height) | |
232 | |
233 | |
234 @instruction(105) | |
235 def set_vulnerable(self, vulnerable): | |
236 self._enemy.vulnerable = bool(vulnerable & 1) | |
237 | |
238 | |
239 @instruction(108) | |
240 def set_death_callback(self, sub): | |
241 self._enemy.death_callback = sub | |
242 | |
243 | |
244 @instruction(109) | |
245 def memory_write(self, value, index): | |
246 #TODO | |
247 #XXX: this is a hack to display bosses although we don't handle MSG :) | |
248 if index == 0: | |
249 self.sub = value | |
250 self.frame = 0 | |
251 self.instruction_pointer = 0 | |
252 | |
253 | |
254 @instruction(113) | |
255 def set_low_life_trigger(self, value): | |
256 self._enemy.low_life_trigger = value | |
257 | |
258 | |
259 @instruction(114) | |
260 def set_low_life_callback(self, sub): | |
261 self._enemy.low_life_callback = sub | |
262 | |
263 | |
264 @instruction(115) | |
265 def set_timeout(self, timeout): | |
266 self._enemy.timeout = timeout | |
267 | |
268 | |
269 @instruction(126) | |
270 def set_remaining_lives(self, lives): | |
271 self._enemy.remaining_lives = lives | |
272 |