Mercurial > touhou
diff 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 |
line wrap: on
line diff
--- a/pytouhou/vm/anmrunner.py +++ b/pytouhou/vm/anmrunner.py @@ -13,7 +13,7 @@ ## -from random import randrange +from random import randrange, random from pytouhou.utils.helpers import get_logger from pytouhou.vm.common import MetaRegistry, instruction @@ -25,19 +25,34 @@ class ANMRunner(object): __metaclass__ = MetaRegistry __slots__ = ('_anm_wrapper', '_sprite', 'running', 'sprite_index_offset', 'script', 'instruction_pointer', - 'frame', 'waiting', 'handlers') + 'frame', 'waiting', 'handlers', 'variables', 'version', 'timeout') + #TODO: check! + formulae = {0: lambda x: x, + 1: lambda x: x ** 2, + 2: lambda x: x ** 3, + 3: lambda x: x ** 4, + 4: lambda x: 2 * x - x ** 2, + 5: lambda x: 2 * x - x ** 3, + 6: lambda x: 2 * x - x ** 4, + 7: lambda x: x, + 255: lambda x: x} #XXX def __init__(self, anm_wrapper, script_id, sprite, sprite_index_offset=0): self._anm_wrapper = anm_wrapper self._sprite = sprite - self.handlers = self._handlers[6] self.running = True self.waiting = False anm, self.script = anm_wrapper.get_script(script_id) + self.version = anm.version + self.handlers = self._handlers[{0: 6, 2: 7}[anm.version]] self.frame = 0 + self.timeout = -1 self.instruction_pointer = 0 + self.variables = [0, 0, 0, 0, + 0., 0., 0., 0., + 0, 0, 0, 0] self.sprite_index_offset = sprite_index_offset @@ -75,11 +90,15 @@ class ANMRunner(object): except KeyError: logger.warn('unhandled opcode %d (args: %r)', opcode, args) else: + logger.debug('[%d - %04d] anm_%d%r', id(self), + self.frame, opcode, args) callback(self, *args) sprite.changed = True if not self.waiting: self.frame += 1 + elif self.timeout == sprite.frame: #TODO: check if it’s happening at the correct frame. + self.waiting = False # Update sprite sprite.frame += 1 @@ -89,6 +108,10 @@ class ANMRunner(object): sax, say, saz = sprite.rotations_speed_3d sprite.rotations_3d = ax + sax, ay + say, az + saz sprite.changed = True + elif sprite.rotation_interpolator: + sprite.rotation_interpolator.update(sprite.frame) + sprite.rotations_3d = sprite.rotation_interpolator.values + sprite.changed = True if sprite.scale_speed != (0., 0.): rx, ry = sprite.rescale @@ -111,31 +134,55 @@ class ANMRunner(object): sprite.dest_offset = sprite.offset_interpolator.values sprite.changed = True + if sprite.color_interpolator: + sprite.color_interpolator.update(sprite.frame) + sprite.color = sprite.color_interpolator.values + sprite.changed = True + return self.running + def _setval(self, variable_id, value): + if self.version == 2: + if 10000 <= variable_id <= 10011: + self.variables[int(variable_id-10000)] = value + + + def _getval(self, value): + if self.version == 2: + if 10000 <= value <= 10011: + return self.variables[int(value-10000)] + return value + + @instruction(0) + @instruction(1, 7) def remove(self): self._sprite.removed = True self.running = False @instruction(1) + @instruction(3, 7) def load_sprite(self, sprite_index): + #TODO: version 2 only: do not crash when assigning a non-existant sprite. self._sprite.anm, self._sprite.texcoords = self._anm_wrapper.get_sprite(sprite_index + self.sprite_index_offset) @instruction(2) + @instruction(7, 7) def set_scale(self, sx, sy): - self._sprite.rescale = sx, sy + self._sprite.rescale = self._getval(sx), self._getval(sy) @instruction(3) + @instruction(8, 7) def set_alpha(self, alpha): self._sprite.alpha = alpha % 256 #TODO @instruction(4) + @instruction(9, 7) def set_color(self, b, g, r): if not self._sprite.fade_interpolator: self._sprite.color = (r, g, b) @@ -149,26 +196,31 @@ class ANMRunner(object): @instruction(7) + @instruction(10, 7) def toggle_mirrored(self): self._sprite.mirrored = not self._sprite.mirrored @instruction(9) + @instruction(12, 7) def set_rotations_3d(self, rx, ry, rz): - self._sprite.rotations_3d = rx, ry, rz + self._sprite.rotations_3d = self._getval(rx), self._getval(ry), self._getval(rz) @instruction(10) + @instruction(13, 7) def set_rotations_speed_3d(self, srx, sry, srz): - self._sprite.rotations_speed_3d = srx, sry, srz + self._sprite.rotations_speed_3d = self._getval(srx), self._getval(sry), self._getval(srz) @instruction(11) + @instruction(14, 7) def set_scale_speed(self, ssx, ssy): self._sprite.scale_speed = ssx, ssy @instruction(12) + @instruction(15, 7) def fade(self, new_alpha, duration): self._sprite.fade(duration, new_alpha, lambda x: x) #TODO: formula @@ -184,6 +236,7 @@ class ANMRunner(object): @instruction(15) + @instruction(2, 7) def keep_still(self): self.running = False @@ -194,26 +247,31 @@ class ANMRunner(object): @instruction(17) + @instruction(6, 7) def move(self, x, y, z): self._sprite.dest_offset = (x, y, z) @instruction(18) + @instruction(17, 7) def move_in_linear(self, x, y, z, duration): self._sprite.move_in(duration, x, y, z, lambda x: x) @instruction(19) + @instruction(18, 7) def move_in_decel(self, x, y, z, duration): self._sprite.move_in(duration, x, y, z, lambda x: 2. * x - x ** 2) @instruction(20) + @instruction(19, 7) def move_in_accel(self, x, y, z, duration): self._sprite.move_in(duration, x, y, z, lambda x: x ** 2) @instruction(21) + @instruction(20, 7) def wait(self): """Wait for an interrupt. """ @@ -221,17 +279,20 @@ class ANMRunner(object): @instruction(22) + @instruction(21, 7) def interrupt_label(self, interrupt): """Noop""" pass @instruction(23) + @instruction(22, 7) def set_corner_relative_placement(self): self._sprite.corner_relative_placement = True #TODO @instruction(24) + @instruction(23, 7) def wait_ex(self): """Hide the sprite and wait for an interrupt. """ @@ -240,11 +301,13 @@ class ANMRunner(object): @instruction(25) + @instruction(24, 7) def set_allow_dest_offset(self, value): self._sprite.allow_dest_offset = bool(value) @instruction(26) + @instruction(25, 7) def set_automatic_orientation(self, value): """If true, rotate by pi-angle around the z axis. """ @@ -252,23 +315,134 @@ class ANMRunner(object): @instruction(27) + @instruction(26, 7) def shift_texture_x(self, dx): tox, toy = self._sprite.texoffsets self._sprite.texoffsets = tox + dx, toy @instruction(28) + @instruction(27, 7) def shift_texture_y(self, dy): tox, toy = self._sprite.texoffsets self._sprite.texoffsets = tox, toy + dy @instruction(29) + @instruction(28, 7) def set_visible(self, visible): self._sprite.visible = bool(visible & 1) @instruction(30) + @instruction(29, 7) def scale_in(self, sx, sy, duration): self._sprite.scale_in(duration, sx, sy, lambda x: x) #TODO: formula + +# Now are the instructions new to anm2. + + + @instruction(0, 7) + def noop(self): + pass + + + @instruction(4, 7) + def jump_bis(self, instruction_pointer, frame): + self.instruction_pointer = instruction_pointer + self.frame = frame + + + @instruction(5, 7) + def jump_ex(self, variable_id, instruction_pointer, frame): + """If the given variable is non-zero, decrease it by 1 and jump to a + relative offset in the same subroutine. + """ + counter_value = self._getval(variable_id) - 1 + if counter_value > 0: + self._setval(variable_id, counter_value) + self.instruction_pointer = instruction_pointer + self.frame = frame + + + @instruction(16, 7) + def set_blendfunc(self, value): + self._sprite.blendfunc = bool(value & 1) + + + @instruction(32, 7) + def move_in_bis(self, duration, formula, x, y, z): + self._sprite.move_in(duration, x, y, z, self.formulae[formula]) + + + @instruction(33, 7) + def change_color_in(self, duration, formula, r, g, b): + self._sprite.change_color_in(duration, r, g, b, self.formulae[formula]) + + + @instruction(34, 7) + def fade_bis(self, duration, formula, new_alpha): + self._sprite.fade(duration, new_alpha, self.formulae[formula]) + + + @instruction(35, 7) + def rotate_in_bis(self, duration, formula, rx, ry, rz): + self._sprite.rotate_in(duration, rx, ry, rz, self.formulae[formula]) + + + @instruction(36, 7) + def scale_in_bis(self, duration, formula, sx, sy): + self._sprite.scale_in(duration, sx, sy, self.formulae[formula]) + + + @instruction(37, 7) + @instruction(38, 7) + def set_variable(self, variable_id, value): + self._setval(variable_id, value) + + + @instruction(42, 7) + def decrement(self, variable_id, value): + self._setval(variable_id, self._getval(variable_id) - self._getval(value)) + + + @instruction(50, 7) + def add(self, variable_id, a, b): + self._setval(variable_id, self._getval(a) + self._getval(b)) + + + @instruction(52, 7) + def substract(self, variable_id, a, b): + self._setval(variable_id, self._getval(a) - self._getval(b)) + + + @instruction(55, 7) + def divide_int(self, variable_id, a, b): + self._setval(variable_id, self._getval(a) // self._getval(b)) + + + @instruction(59, 7) + def set_random_int(self, variable_id, amp): + #TODO: use the game's PRNG? + self._setval(variable_id, randrange(amp)) + + + @instruction(60, 7) + def set_random_float(self, variable_id, amp): + #TODO: use the game's PRNG? + self._setval(variable_id, amp * random()) + + + @instruction(69, 7) + def branch_if_not_equal(self, variable_id, value, instruction_pointer, frame): + if self._getval(variable_id) != value: + self.instruction_pointer = instruction_pointer + self.frame = frame + assert self.frame == self.script[self.instruction_pointer][0] + + + @instruction(79, 7) + def wait_duration(self, duration): + self.timeout = self._sprite.frame + duration + self.waiting = True