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