Mercurial > touhou
comparison pytouhou/vm/anmrunner.py @ 429:40d5f3083ebc
Implement PCB’s ANM2 format and vm.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Sat, 03 Aug 2013 15:48:57 +0200 |
parents | 69ec72b990a4 |
children | c9433188ffdb |
comparison
equal
deleted
inserted
replaced
428:f41a26971a19 | 429:40d5f3083ebc |
---|---|
11 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 ## GNU General Public License for more details. | 12 ## GNU General Public License for more details. |
13 ## | 13 ## |
14 | 14 |
15 | 15 |
16 from random import randrange | 16 from random import randrange, random |
17 | 17 |
18 from pytouhou.utils.helpers import get_logger | 18 from pytouhou.utils.helpers import get_logger |
19 from pytouhou.vm.common import MetaRegistry, instruction | 19 from pytouhou.vm.common import MetaRegistry, instruction |
20 | 20 |
21 logger = get_logger(__name__) | 21 logger = get_logger(__name__) |
23 | 23 |
24 class ANMRunner(object): | 24 class ANMRunner(object): |
25 __metaclass__ = MetaRegistry | 25 __metaclass__ = MetaRegistry |
26 __slots__ = ('_anm_wrapper', '_sprite', 'running', | 26 __slots__ = ('_anm_wrapper', '_sprite', 'running', |
27 'sprite_index_offset', 'script', 'instruction_pointer', | 27 'sprite_index_offset', 'script', 'instruction_pointer', |
28 'frame', 'waiting', 'handlers') | 28 'frame', 'waiting', 'handlers', 'variables', 'version', 'timeout') |
29 | 29 |
30 #TODO: check! | |
31 formulae = {0: lambda x: x, | |
32 1: lambda x: x ** 2, | |
33 2: lambda x: x ** 3, | |
34 3: lambda x: x ** 4, | |
35 4: lambda x: 2 * x - x ** 2, | |
36 5: lambda x: 2 * x - x ** 3, | |
37 6: lambda x: 2 * x - x ** 4, | |
38 7: lambda x: x, | |
39 255: lambda x: x} #XXX | |
30 | 40 |
31 def __init__(self, anm_wrapper, script_id, sprite, sprite_index_offset=0): | 41 def __init__(self, anm_wrapper, script_id, sprite, sprite_index_offset=0): |
32 self._anm_wrapper = anm_wrapper | 42 self._anm_wrapper = anm_wrapper |
33 self._sprite = sprite | 43 self._sprite = sprite |
34 self.handlers = self._handlers[6] | |
35 self.running = True | 44 self.running = True |
36 self.waiting = False | 45 self.waiting = False |
37 | 46 |
38 anm, self.script = anm_wrapper.get_script(script_id) | 47 anm, self.script = anm_wrapper.get_script(script_id) |
48 self.version = anm.version | |
49 self.handlers = self._handlers[{0: 6, 2: 7}[anm.version]] | |
39 self.frame = 0 | 50 self.frame = 0 |
51 self.timeout = -1 | |
40 self.instruction_pointer = 0 | 52 self.instruction_pointer = 0 |
53 self.variables = [0, 0, 0, 0, | |
54 0., 0., 0., 0., | |
55 0, 0, 0, 0] | |
41 | 56 |
42 self.sprite_index_offset = sprite_index_offset | 57 self.sprite_index_offset = sprite_index_offset |
43 | 58 |
44 | 59 |
45 def interrupt(self, interrupt): | 60 def interrupt(self, interrupt): |
73 try: | 88 try: |
74 callback = self.handlers[opcode] | 89 callback = self.handlers[opcode] |
75 except KeyError: | 90 except KeyError: |
76 logger.warn('unhandled opcode %d (args: %r)', opcode, args) | 91 logger.warn('unhandled opcode %d (args: %r)', opcode, args) |
77 else: | 92 else: |
93 logger.debug('[%d - %04d] anm_%d%r', id(self), | |
94 self.frame, opcode, args) | |
78 callback(self, *args) | 95 callback(self, *args) |
79 sprite.changed = True | 96 sprite.changed = True |
80 | 97 |
81 if not self.waiting: | 98 if not self.waiting: |
82 self.frame += 1 | 99 self.frame += 1 |
100 elif self.timeout == sprite.frame: #TODO: check if it’s happening at the correct frame. | |
101 self.waiting = False | |
83 | 102 |
84 # Update sprite | 103 # Update sprite |
85 sprite.frame += 1 | 104 sprite.frame += 1 |
86 | 105 |
87 if sprite.rotations_speed_3d != (0., 0., 0.): | 106 if sprite.rotations_speed_3d != (0., 0., 0.): |
88 ax, ay, az = sprite.rotations_3d | 107 ax, ay, az = sprite.rotations_3d |
89 sax, say, saz = sprite.rotations_speed_3d | 108 sax, say, saz = sprite.rotations_speed_3d |
90 sprite.rotations_3d = ax + sax, ay + say, az + saz | 109 sprite.rotations_3d = ax + sax, ay + say, az + saz |
91 sprite.changed = True | 110 sprite.changed = True |
111 elif sprite.rotation_interpolator: | |
112 sprite.rotation_interpolator.update(sprite.frame) | |
113 sprite.rotations_3d = sprite.rotation_interpolator.values | |
114 sprite.changed = True | |
92 | 115 |
93 if sprite.scale_speed != (0., 0.): | 116 if sprite.scale_speed != (0., 0.): |
94 rx, ry = sprite.rescale | 117 rx, ry = sprite.rescale |
95 rsx, rsy = sprite.scale_speed | 118 rsx, rsy = sprite.scale_speed |
96 sprite.rescale = rx + rsx, ry + rsy | 119 sprite.rescale = rx + rsx, ry + rsy |
109 if sprite.offset_interpolator: | 132 if sprite.offset_interpolator: |
110 sprite.offset_interpolator.update(sprite.frame) | 133 sprite.offset_interpolator.update(sprite.frame) |
111 sprite.dest_offset = sprite.offset_interpolator.values | 134 sprite.dest_offset = sprite.offset_interpolator.values |
112 sprite.changed = True | 135 sprite.changed = True |
113 | 136 |
137 if sprite.color_interpolator: | |
138 sprite.color_interpolator.update(sprite.frame) | |
139 sprite.color = sprite.color_interpolator.values | |
140 sprite.changed = True | |
141 | |
114 return self.running | 142 return self.running |
115 | 143 |
116 | 144 |
145 def _setval(self, variable_id, value): | |
146 if self.version == 2: | |
147 if 10000 <= variable_id <= 10011: | |
148 self.variables[int(variable_id-10000)] = value | |
149 | |
150 | |
151 def _getval(self, value): | |
152 if self.version == 2: | |
153 if 10000 <= value <= 10011: | |
154 return self.variables[int(value-10000)] | |
155 return value | |
156 | |
157 | |
117 @instruction(0) | 158 @instruction(0) |
159 @instruction(1, 7) | |
118 def remove(self): | 160 def remove(self): |
119 self._sprite.removed = True | 161 self._sprite.removed = True |
120 self.running = False | 162 self.running = False |
121 | 163 |
122 | 164 |
123 @instruction(1) | 165 @instruction(1) |
166 @instruction(3, 7) | |
124 def load_sprite(self, sprite_index): | 167 def load_sprite(self, sprite_index): |
168 #TODO: version 2 only: do not crash when assigning a non-existant sprite. | |
125 self._sprite.anm, self._sprite.texcoords = self._anm_wrapper.get_sprite(sprite_index + self.sprite_index_offset) | 169 self._sprite.anm, self._sprite.texcoords = self._anm_wrapper.get_sprite(sprite_index + self.sprite_index_offset) |
126 | 170 |
127 | 171 |
128 @instruction(2) | 172 @instruction(2) |
173 @instruction(7, 7) | |
129 def set_scale(self, sx, sy): | 174 def set_scale(self, sx, sy): |
130 self._sprite.rescale = sx, sy | 175 self._sprite.rescale = self._getval(sx), self._getval(sy) |
131 | 176 |
132 | 177 |
133 @instruction(3) | 178 @instruction(3) |
179 @instruction(8, 7) | |
134 def set_alpha(self, alpha): | 180 def set_alpha(self, alpha): |
135 self._sprite.alpha = alpha % 256 #TODO | 181 self._sprite.alpha = alpha % 256 #TODO |
136 | 182 |
137 | 183 |
138 @instruction(4) | 184 @instruction(4) |
185 @instruction(9, 7) | |
139 def set_color(self, b, g, r): | 186 def set_color(self, b, g, r): |
140 if not self._sprite.fade_interpolator: | 187 if not self._sprite.fade_interpolator: |
141 self._sprite.color = (r, g, b) | 188 self._sprite.color = (r, g, b) |
142 | 189 |
143 | 190 |
147 self.instruction_pointer = instruction_pointer | 194 self.instruction_pointer = instruction_pointer |
148 self.frame = self.script[self.instruction_pointer][0] | 195 self.frame = self.script[self.instruction_pointer][0] |
149 | 196 |
150 | 197 |
151 @instruction(7) | 198 @instruction(7) |
199 @instruction(10, 7) | |
152 def toggle_mirrored(self): | 200 def toggle_mirrored(self): |
153 self._sprite.mirrored = not self._sprite.mirrored | 201 self._sprite.mirrored = not self._sprite.mirrored |
154 | 202 |
155 | 203 |
156 @instruction(9) | 204 @instruction(9) |
205 @instruction(12, 7) | |
157 def set_rotations_3d(self, rx, ry, rz): | 206 def set_rotations_3d(self, rx, ry, rz): |
158 self._sprite.rotations_3d = rx, ry, rz | 207 self._sprite.rotations_3d = self._getval(rx), self._getval(ry), self._getval(rz) |
159 | 208 |
160 | 209 |
161 @instruction(10) | 210 @instruction(10) |
211 @instruction(13, 7) | |
162 def set_rotations_speed_3d(self, srx, sry, srz): | 212 def set_rotations_speed_3d(self, srx, sry, srz): |
163 self._sprite.rotations_speed_3d = srx, sry, srz | 213 self._sprite.rotations_speed_3d = self._getval(srx), self._getval(sry), self._getval(srz) |
164 | 214 |
165 | 215 |
166 @instruction(11) | 216 @instruction(11) |
217 @instruction(14, 7) | |
167 def set_scale_speed(self, ssx, ssy): | 218 def set_scale_speed(self, ssx, ssy): |
168 self._sprite.scale_speed = ssx, ssy | 219 self._sprite.scale_speed = ssx, ssy |
169 | 220 |
170 | 221 |
171 @instruction(12) | 222 @instruction(12) |
223 @instruction(15, 7) | |
172 def fade(self, new_alpha, duration): | 224 def fade(self, new_alpha, duration): |
173 self._sprite.fade(duration, new_alpha, lambda x: x) #TODO: formula | 225 self._sprite.fade(duration, new_alpha, lambda x: x) #TODO: formula |
174 | 226 |
175 | 227 |
176 @instruction(13) | 228 @instruction(13) |
182 def set_blendfunc_add(self): | 234 def set_blendfunc_add(self): |
183 self._sprite.blendfunc = 0 #TODO | 235 self._sprite.blendfunc = 0 #TODO |
184 | 236 |
185 | 237 |
186 @instruction(15) | 238 @instruction(15) |
239 @instruction(2, 7) | |
187 def keep_still(self): | 240 def keep_still(self): |
188 self.running = False | 241 self.running = False |
189 | 242 |
190 @instruction(16) | 243 @instruction(16) |
191 def load_random_sprite(self, min_idx, amp): | 244 def load_random_sprite(self, min_idx, amp): |
192 #TODO: use the game's PRNG? | 245 #TODO: use the game's PRNG? |
193 self.load_sprite(min_idx + randrange(amp)) | 246 self.load_sprite(min_idx + randrange(amp)) |
194 | 247 |
195 | 248 |
196 @instruction(17) | 249 @instruction(17) |
250 @instruction(6, 7) | |
197 def move(self, x, y, z): | 251 def move(self, x, y, z): |
198 self._sprite.dest_offset = (x, y, z) | 252 self._sprite.dest_offset = (x, y, z) |
199 | 253 |
200 | 254 |
201 @instruction(18) | 255 @instruction(18) |
256 @instruction(17, 7) | |
202 def move_in_linear(self, x, y, z, duration): | 257 def move_in_linear(self, x, y, z, duration): |
203 self._sprite.move_in(duration, x, y, z, lambda x: x) | 258 self._sprite.move_in(duration, x, y, z, lambda x: x) |
204 | 259 |
205 | 260 |
206 @instruction(19) | 261 @instruction(19) |
262 @instruction(18, 7) | |
207 def move_in_decel(self, x, y, z, duration): | 263 def move_in_decel(self, x, y, z, duration): |
208 self._sprite.move_in(duration, x, y, z, lambda x: 2. * x - x ** 2) | 264 self._sprite.move_in(duration, x, y, z, lambda x: 2. * x - x ** 2) |
209 | 265 |
210 | 266 |
211 @instruction(20) | 267 @instruction(20) |
268 @instruction(19, 7) | |
212 def move_in_accel(self, x, y, z, duration): | 269 def move_in_accel(self, x, y, z, duration): |
213 self._sprite.move_in(duration, x, y, z, lambda x: x ** 2) | 270 self._sprite.move_in(duration, x, y, z, lambda x: x ** 2) |
214 | 271 |
215 | 272 |
216 @instruction(21) | 273 @instruction(21) |
274 @instruction(20, 7) | |
217 def wait(self): | 275 def wait(self): |
218 """Wait for an interrupt. | 276 """Wait for an interrupt. |
219 """ | 277 """ |
220 self.waiting = True | 278 self.waiting = True |
221 | 279 |
222 | 280 |
223 @instruction(22) | 281 @instruction(22) |
282 @instruction(21, 7) | |
224 def interrupt_label(self, interrupt): | 283 def interrupt_label(self, interrupt): |
225 """Noop""" | 284 """Noop""" |
226 pass | 285 pass |
227 | 286 |
228 | 287 |
229 @instruction(23) | 288 @instruction(23) |
289 @instruction(22, 7) | |
230 def set_corner_relative_placement(self): | 290 def set_corner_relative_placement(self): |
231 self._sprite.corner_relative_placement = True #TODO | 291 self._sprite.corner_relative_placement = True #TODO |
232 | 292 |
233 | 293 |
234 @instruction(24) | 294 @instruction(24) |
295 @instruction(23, 7) | |
235 def wait_ex(self): | 296 def wait_ex(self): |
236 """Hide the sprite and wait for an interrupt. | 297 """Hide the sprite and wait for an interrupt. |
237 """ | 298 """ |
238 self._sprite.visible = False | 299 self._sprite.visible = False |
239 self.waiting = True | 300 self.waiting = True |
240 | 301 |
241 | 302 |
242 @instruction(25) | 303 @instruction(25) |
304 @instruction(24, 7) | |
243 def set_allow_dest_offset(self, value): | 305 def set_allow_dest_offset(self, value): |
244 self._sprite.allow_dest_offset = bool(value) | 306 self._sprite.allow_dest_offset = bool(value) |
245 | 307 |
246 | 308 |
247 @instruction(26) | 309 @instruction(26) |
310 @instruction(25, 7) | |
248 def set_automatic_orientation(self, value): | 311 def set_automatic_orientation(self, value): |
249 """If true, rotate by pi-angle around the z axis. | 312 """If true, rotate by pi-angle around the z axis. |
250 """ | 313 """ |
251 self._sprite.automatic_orientation = bool(value) | 314 self._sprite.automatic_orientation = bool(value) |
252 | 315 |
253 | 316 |
254 @instruction(27) | 317 @instruction(27) |
318 @instruction(26, 7) | |
255 def shift_texture_x(self, dx): | 319 def shift_texture_x(self, dx): |
256 tox, toy = self._sprite.texoffsets | 320 tox, toy = self._sprite.texoffsets |
257 self._sprite.texoffsets = tox + dx, toy | 321 self._sprite.texoffsets = tox + dx, toy |
258 | 322 |
259 | 323 |
260 @instruction(28) | 324 @instruction(28) |
325 @instruction(27, 7) | |
261 def shift_texture_y(self, dy): | 326 def shift_texture_y(self, dy): |
262 tox, toy = self._sprite.texoffsets | 327 tox, toy = self._sprite.texoffsets |
263 self._sprite.texoffsets = tox, toy + dy | 328 self._sprite.texoffsets = tox, toy + dy |
264 | 329 |
265 | 330 |
266 @instruction(29) | 331 @instruction(29) |
332 @instruction(28, 7) | |
267 def set_visible(self, visible): | 333 def set_visible(self, visible): |
268 self._sprite.visible = bool(visible & 1) | 334 self._sprite.visible = bool(visible & 1) |
269 | 335 |
270 | 336 |
271 @instruction(30) | 337 @instruction(30) |
338 @instruction(29, 7) | |
272 def scale_in(self, sx, sy, duration): | 339 def scale_in(self, sx, sy, duration): |
273 self._sprite.scale_in(duration, sx, sy, lambda x: x) #TODO: formula | 340 self._sprite.scale_in(duration, sx, sy, lambda x: x) #TODO: formula |
274 | 341 |
342 | |
343 # Now are the instructions new to anm2. | |
344 | |
345 | |
346 @instruction(0, 7) | |
347 def noop(self): | |
348 pass | |
349 | |
350 | |
351 @instruction(4, 7) | |
352 def jump_bis(self, instruction_pointer, frame): | |
353 self.instruction_pointer = instruction_pointer | |
354 self.frame = frame | |
355 | |
356 | |
357 @instruction(5, 7) | |
358 def jump_ex(self, variable_id, instruction_pointer, frame): | |
359 """If the given variable is non-zero, decrease it by 1 and jump to a | |
360 relative offset in the same subroutine. | |
361 """ | |
362 counter_value = self._getval(variable_id) - 1 | |
363 if counter_value > 0: | |
364 self._setval(variable_id, counter_value) | |
365 self.instruction_pointer = instruction_pointer | |
366 self.frame = frame | |
367 | |
368 | |
369 @instruction(16, 7) | |
370 def set_blendfunc(self, value): | |
371 self._sprite.blendfunc = bool(value & 1) | |
372 | |
373 | |
374 @instruction(32, 7) | |
375 def move_in_bis(self, duration, formula, x, y, z): | |
376 self._sprite.move_in(duration, x, y, z, self.formulae[formula]) | |
377 | |
378 | |
379 @instruction(33, 7) | |
380 def change_color_in(self, duration, formula, r, g, b): | |
381 self._sprite.change_color_in(duration, r, g, b, self.formulae[formula]) | |
382 | |
383 | |
384 @instruction(34, 7) | |
385 def fade_bis(self, duration, formula, new_alpha): | |
386 self._sprite.fade(duration, new_alpha, self.formulae[formula]) | |
387 | |
388 | |
389 @instruction(35, 7) | |
390 def rotate_in_bis(self, duration, formula, rx, ry, rz): | |
391 self._sprite.rotate_in(duration, rx, ry, rz, self.formulae[formula]) | |
392 | |
393 | |
394 @instruction(36, 7) | |
395 def scale_in_bis(self, duration, formula, sx, sy): | |
396 self._sprite.scale_in(duration, sx, sy, self.formulae[formula]) | |
397 | |
398 | |
399 @instruction(37, 7) | |
400 @instruction(38, 7) | |
401 def set_variable(self, variable_id, value): | |
402 self._setval(variable_id, value) | |
403 | |
404 | |
405 @instruction(42, 7) | |
406 def decrement(self, variable_id, value): | |
407 self._setval(variable_id, self._getval(variable_id) - self._getval(value)) | |
408 | |
409 | |
410 @instruction(50, 7) | |
411 def add(self, variable_id, a, b): | |
412 self._setval(variable_id, self._getval(a) + self._getval(b)) | |
413 | |
414 | |
415 @instruction(52, 7) | |
416 def substract(self, variable_id, a, b): | |
417 self._setval(variable_id, self._getval(a) - self._getval(b)) | |
418 | |
419 | |
420 @instruction(55, 7) | |
421 def divide_int(self, variable_id, a, b): | |
422 self._setval(variable_id, self._getval(a) // self._getval(b)) | |
423 | |
424 | |
425 @instruction(59, 7) | |
426 def set_random_int(self, variable_id, amp): | |
427 #TODO: use the game's PRNG? | |
428 self._setval(variable_id, randrange(amp)) | |
429 | |
430 | |
431 @instruction(60, 7) | |
432 def set_random_float(self, variable_id, amp): | |
433 #TODO: use the game's PRNG? | |
434 self._setval(variable_id, amp * random()) | |
435 | |
436 | |
437 @instruction(69, 7) | |
438 def branch_if_not_equal(self, variable_id, value, instruction_pointer, frame): | |
439 if self._getval(variable_id) != value: | |
440 self.instruction_pointer = instruction_pointer | |
441 self.frame = frame | |
442 assert self.frame == self.script[self.instruction_pointer][0] | |
443 | |
444 | |
445 @instruction(79, 7) | |
446 def wait_duration(self, duration): | |
447 self.timeout = self._sprite.frame + duration | |
448 self.waiting = True |