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