changeset 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 f41a26971a19
children c9433188ffdb
files pytouhou/formats/anm0.py pytouhou/game/sprite.py pytouhou/resource/anmwrapper.py pytouhou/resource/loader.py pytouhou/ui/texture.pyx pytouhou/vm/anmrunner.py
diffstat 6 files changed, 387 insertions(+), 98 deletions(-) [+]
line wrap: on
line diff
--- a/pytouhou/formats/anm0.py
+++ b/pytouhou/formats/anm0.py
@@ -24,6 +24,7 @@ from struct import pack, unpack
 from pytouhou.utils.helpers import read_string, get_logger
 
 from pytouhou.formats import WrongFormatError
+from pytouhou.formats.thtx import Texture
 
 
 logger = get_logger(__name__)
@@ -39,36 +40,84 @@ class Script(list):
 
 
 class ANM0(object):
-    _instructions = {0: ('', 'delete'),
-                     1: ('I', 'set_sprite'),
-                     2: ('ff', 'set_scale'),
-                     3: ('I', 'set_alpha'),
-                     4: ('BBBx', 'set_color'),
-                     5: ('I', 'jump'),
-                     7: ('', 'toggle_mirrored'),
-                     9: ('fff', 'set_3d_rotations'),
-                     10: ('fff', 'set_3d_rotations_speed'),
-                     11: ('ff', 'set_scale_speed'),
-                     12: ('ii', 'fade'),
-                     13: ('', 'set_blendmode_add'),
-                     14: ('', 'set_blendmode_alphablend'),
-                     15: ('', 'keep_still'),
-                     16: ('ii', 'set_random_sprite'),
-                     17: ('fff', 'set_3d_translation'),
-                     18: ('fffi', 'move_to_linear'),
-                     19: ('fffi', 'move_to_decel'),
-                     20: ('fffi', 'move_to_accel'),
-                     21: ('', 'wait'),
-                     22: ('i', 'interrupt_label'),
-                     23: ('', 'set_corner_relative_placement'),
-                     24: ('', 'wait_ex'),
-                     25: ('i', 'set_allow_offset'), #TODO: better name
-                     26: ('i', 'set_automatic_orientation'),
-                     27: ('f', 'shift_texture_x'),
-                     28: ('f', 'shift_texture_y'),
-                     29: ('i', 'set_visible'),
-                     30: ('ffi', 'scale_in'),
-                     31: ('i', None)}
+    _instructions = {0: {0: ('', 'delete'),
+                         1: ('I', 'set_sprite'),
+                         2: ('ff', 'set_scale'),
+                         3: ('I', 'set_alpha'),
+                         4: ('BBBx', 'set_color'),
+                         5: ('I', 'jump'),
+                         7: ('', 'toggle_mirrored'),
+                         9: ('fff', 'set_3d_rotations'),
+                         10: ('fff', 'set_3d_rotations_speed'),
+                         11: ('ff', 'set_scale_speed'),
+                         12: ('ii', 'fade'),
+                         13: ('', 'set_blendmode_add'),
+                         14: ('', 'set_blendmode_alphablend'),
+                         15: ('', 'keep_still'),
+                         16: ('ii', 'set_random_sprite'),
+                         17: ('fff', 'set_3d_translation'),
+                         18: ('fffi', 'move_to_linear'),
+                         19: ('fffi', 'move_to_decel'),
+                         20: ('fffi', 'move_to_accel'),
+                         21: ('', 'wait'),
+                         22: ('i', 'interrupt_label'),
+                         23: ('', 'set_corner_relative_placement'),
+                         24: ('', 'wait_ex'),
+                         25: ('i', 'set_allow_offset'), #TODO: better name
+                         26: ('i', 'set_automatic_orientation'),
+                         27: ('f', 'shift_texture_x'),
+                         28: ('f', 'shift_texture_y'),
+                         29: ('i', 'set_visible'),
+                         30: ('ffi', 'scale_in'),
+                         31: ('i', None)},
+
+                     2: {0: ('', 'noop'),
+                         1: ('', 'delete'),
+                         2: ('', 'keep_still'),
+                         3: ('I', 'set_sprite'),
+                         4: ('II', 'jump_bis'),
+                         5: ('III', 'jump_ex'),
+                         6: ('fff', 'set_3d_translation'),
+                         7: ('ff', 'set_scale'),
+                         8: ('I', 'set_alpha'),
+                         9: ('BBBx', 'set_color'),
+                         10: ('', 'toggle_mirrored'),
+                         12: ('fff', 'set_3d_rotations'),
+                         13: ('fff', 'set_3d_rotations_speed'),
+                         14: ('ff', 'set_scale_speed'),
+                         15: ('ii', 'fade'),
+                         16: ('I', 'set_blendmode'),
+                         17: ('fffi', 'move_to_linear'),
+                         18: ('fffi', 'move_to_decel'),
+                         19: ('fffi', 'move_to_accel'),
+                         20: ('', 'wait'),
+                         21: ('i', 'interrupt_label'),
+                         22: ('', 'set_corner_relative_placement'),
+                         23: ('', 'wait_ex'),
+                         24: ('i', 'set_allow_offset'), #TODO: better name
+                         25: ('i', 'set_automatic_orientation'),
+                         26: ('f', 'shift_texture_x'),
+                         27: ('f', 'shift_texture_y'),
+                         28: ('i', 'set_visible'),
+                         29: ('ffi', 'scale_in'),
+                         30: ('i', None),
+                         31: ('I', None),
+                         32: ('IIfff', 'move_in_linear_bis'),
+                         33: ('IIBBBx', 'change_color_in'),
+                         34: ('III', 'fade_bis'),
+                         35: ('IIfff', 'rotate_in_bis'),
+                         36: ('IIff', 'scale_in_bis'),
+                         37: ('II', 'set_int'),
+                         38: ('ff', 'set_float'),
+                         42: ('ff', 'decrement_float'),
+                         50: ('fff', 'add_float'),
+                         52: ('fff', 'substract_float'),
+                         55: ('III', 'divide_int'),
+                         59: ('II', 'set_random_int'),
+                         60: ('ff', 'set_random_float'),
+                         69: ('IIII', 'branch_if_not_equal'),
+                         79: ('I', 'wait_duration'),
+                         80: ('I', None)}}
 
 
     def __init__(self):
@@ -82,67 +131,113 @@ class ANM0(object):
 
     @classmethod
     def read(cls, file):
-        nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12))
-        width, height, format, unknown1 = unpack('<IIII', file.read(16))
-        first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12))
-        version, unknown2, thtxoffset, hasdata, nextoffset, zero2 = unpack('<IIIIII', file.read(24))
-        if version != 0:
-            raise WrongFormatError(version)
-        assert (zero1, zero2) == (0, 0)
+        anm_list = []
+        start_offset = 0
+        while True:
+            file.seek(start_offset)
+            nb_sprites, nb_scripts, zero1 = unpack('<III', file.read(12))
+            width, height, fmt, unknown1 = unpack('<IIII', file.read(16))
+            first_name_offset, unused, secondary_name_offset = unpack('<III', file.read(12))
+            version, unknown2, texture_offset, has_data, next_offset, unknown3 = unpack('<IIIIII', file.read(24))
 
-        sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)]
-        script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)]
+            if version == 0:
+                assert zero1 == 0
+                assert unknown3 == 0
+                assert has_data == 0
+            elif version == 2:
+                assert zero1 == 0
+                assert secondary_name_offset == 0
+                assert has_data == 1 # Can be false but we don’t support that yet.
+            else:
+                raise WrongFormatError(version)
+
+            instructions = cls._instructions[version]
 
-        self = cls()
+            sprite_offsets = [unpack('<I', file.read(4))[0] for i in range(nb_sprites)]
+            script_offsets = [unpack('<II', file.read(8)) for i in range(nb_scripts)]
 
-        self.size = (width, height)
+            self = cls()
+
+            self.size = (width, height)
+            self.version = version
 
-        # Names
-        if first_name_offset:
-            file.seek(first_name_offset)
-            self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really?
-        if secondary_name_offset:
-            file.seek(secondary_name_offset)
-            self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really?
+            # Names
+            if first_name_offset:
+                file.seek(start_offset + first_name_offset)
+                self.first_name = read_string(file, 32, 'ascii') #TODO: 32, really?
+            if secondary_name_offset:
+                file.seek(start_offset + secondary_name_offset)
+                self.secondary_name = read_string(file, 32, 'ascii') #TODO: 32, really?
+
+
+            # Sprites
+            for offset in sprite_offsets:
+                file.seek(start_offset + offset)
+                idx, x, y, width, height = unpack('<Iffff', file.read(20))
+                self.sprites[idx] = x, y, width, height
 
 
-        # Sprites
-        file.seek(64)
-        self.sprites = {}
-        for offset in sprite_offsets:
-            file.seek(offset)
-            idx, x, y, width, height = unpack('<Iffff', file.read(20))
-            self.sprites[idx] = x, y, width, height
+            # Scripts
+            for i, offset in script_offsets:
+                self.scripts[i] = Script()
+                instruction_offsets = []
+                file.seek(start_offset + offset)
+                while True:
+                    instruction_offsets.append(file.tell() - (start_offset + offset))
+                    if version == 0:
+                        time, opcode, size = unpack('<HBB', file.read(4))
+                    elif version == 2:
+                        opcode, size, time, mask = unpack('<HHHH', file.read(8))
+                        if opcode == 0xffff:
+                            break
+                        size -= 8
+                    data = file.read(size)
+                    if opcode in instructions:
+                        args = unpack('<%s' % instructions[opcode][0], data)
+                    else:
+                        args = (data,)
+                        logger.warn('unknown opcode %d', opcode)
 
+                    self.scripts[i].append((time, opcode, args))
+                    if version == 0 and opcode == 0:
+                        break
 
-        # Scripts
-        self.scripts = {}
-        for i, offset in script_offsets:
-            self.scripts[i] = Script()
-            instruction_offsets = []
-            file.seek(offset)
-            while True:
-                instruction_offsets.append(file.tell() - offset)
-                time, opcode, size = unpack('<HBB', file.read(4))
+                # Translate offsets to instruction pointers and register interrupts
+                for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])):
+                    time, opcode, args = instr
+                    if version == 0:
+                        if opcode == 5:
+                            args = (instruction_offsets.index(args[0]),)
+                        elif opcode == 22:
+                            interrupt = args[0]
+                            self.scripts[i].interrupts[interrupt] = j + 1
+                    elif version == 2:
+                        if opcode == 4:
+                            args = (instruction_offsets.index(args[0]), args[1])
+                        elif opcode == 5:
+                            args = (args[0], instruction_offsets.index(args[1]), args[2])
+                        elif opcode == 21:
+                            interrupt = args[0]
+                            self.scripts[i].interrupts[interrupt] = j + 1
+                        elif opcode == 69:
+                            args = (args[0], args[1], instruction_offsets.index(args[2]), args[3])
+                    self.scripts[i][j] = time, opcode, args
+
+            # Texture
+            if has_data:
+                file.seek(start_offset + texture_offset)
+                magic = file.read(4)
+                assert magic == b'THTX'
+                zero, fmt, width, height, size = unpack('<HHHHI', file.read(12))
+                assert zero == 0
                 data = file.read(size)
-                if opcode in cls._instructions:
-                    args = unpack('<%s' % cls._instructions[opcode][0], data)
-                else:
-                    args = (data,)
-                    logger.warn('unknown opcode %d', opcode)
+                self.texture = Texture(width, height, fmt, data)
 
-                self.scripts[i].append((time, opcode, args))
-                if opcode == 0:
-                    break
+            anm_list.append(self)
 
-            # Translate offsets to instruction pointers and register interrupts
-            for instr_offset, (j, instr) in zip(instruction_offsets, enumerate(self.scripts[i])):
-                time, opcode, args = instr
-                if opcode == 5:
-                    args = (instruction_offsets.index(args[0]),)
-                elif opcode == 22:
-                    interrupt = args[0]
-                    self.scripts[i].interrupts[interrupt] = j + 1
-                self.scripts[i][j] = time, opcode, args
+            if next_offset:
+                start_offset += next_offset
+            else:
+                break
 
-        return self
+        return anm_list
--- a/pytouhou/game/sprite.py
+++ b/pytouhou/game/sprite.py
@@ -18,10 +18,12 @@ from pytouhou.utils.interpolator import 
 
 class Sprite(object):
     __slots__ = ('anm', 'removed', 'changed', 'width_override', 'height_override',
-                 'angle', 'force_rotation', 'scale_interpolator', 'fade_interpolator',
-                 'offset_interpolator', 'automatic_orientation', 'blendfunc',
-                 'texcoords', 'dest_offset', 'allow_dest_offset', 'texoffsets',
-                 'mirrored', 'rescale', 'scale_speed', 'rotations_3d',
+                 'angle', 'force_rotation', 'scale_interpolator',
+                 'fade_interpolator', 'offset_interpolator',
+                 'rotation_interpolator', 'color_interpolator',
+                 'automatic_orientation', 'blendfunc', 'texcoords',
+                 'dest_offset', 'allow_dest_offset', 'texoffsets', 'mirrored',
+                 'rescale', 'scale_speed', 'rotations_3d',
                  'rotations_speed_3d', 'corner_relative_placement', 'frame',
                  'color', 'alpha', 'visible', '_rendering_data')
     def __init__(self, width_override=0, height_override=0):
@@ -38,6 +40,8 @@ class Sprite(object):
         self.scale_interpolator = None
         self.fade_interpolator = None
         self.offset_interpolator = None
+        self.rotation_interpolator = None
+        self.color_interpolator = None
 
         self.automatic_orientation = False
 
@@ -78,6 +82,18 @@ class Sprite(object):
                                                 formula)
 
 
+    def rotate_in(self, duration, rx, ry, rz, formula):
+        self.rotation_interpolator = Interpolator(self.rotations_3d, self.frame,
+                                                  (rx, ry, rz), self.frame + duration,
+                                                  formula)
+
+
+    def change_color_in(self, duration, r, g, b, formula):
+        self.color_interpolator = Interpolator(self.color, self.frame,
+                                               (r, g, b), self.frame + duration,
+                                               formula)
+
+
     def update_orientation(self, angle_base=0., force_rotation=False):
         if (self.angle != angle_base or self.force_rotation != force_rotation):
             self.angle = angle_base
--- a/pytouhou/resource/anmwrapper.py
+++ b/pytouhou/resource/anmwrapper.py
@@ -12,7 +12,7 @@
 ## GNU General Public License for more details.
 ##
 
-from itertools import repeat
+from itertools import repeat, chain
 
 
 class AnmWrapper(object):
@@ -32,7 +32,7 @@ class AnmWrapper(object):
         if not offsets:
             offsets = repeat(0) # “offsets” defaults to zeroes
 
-        for anm, offset in zip(anm_files, offsets):
+        for anm, offset in zip(chain(*anm_files), offsets):
             for script_id, script in anm.scripts.iteritems():
                 self.scripts[script_id + offset] = (anm, script) #TODO: check
             for sprite_id, sprite in anm.sprites.iteritems():
--- a/pytouhou/resource/loader.py
+++ b/pytouhou/resource/loader.py
@@ -17,6 +17,7 @@ from glob import glob
 from itertools import chain
 from io import BytesIO
 
+from pytouhou.formats import WrongFormatError
 from pytouhou.formats.pbg3 import PBG3
 from pytouhou.formats.std import Stage
 from pytouhou.formats.ecl import ECL
@@ -140,7 +141,7 @@ class Loader(object):
     def get_anm(self, name):
         if name not in self.instanced_anms:
             file = self.get_file(name)
-            self.instanced_anms[name] = ANM0.read(file) #TODO: modular
+            self.instanced_anms[name] = ANM0.read(file)
         return self.instanced_anms[name]
 
 
--- a/pytouhou/ui/texture.pyx
+++ b/pytouhou/ui/texture.pyx
@@ -38,9 +38,12 @@ class TextureManager(object):
 
     def load(self, anm_list):
         for anm in anm_list:
-            if not hasattr(anm, 'texture'):
-                texture = decode_png(self.loader, anm.first_name, anm.secondary_name)
-                anm.texture = load_texture(texture)
+            for entry in anm:
+                if not hasattr(entry, 'texture'):
+                    texture = decode_png(self.loader, entry.first_name, entry.secondary_name)
+                    entry.texture = load_texture(texture)
+                elif not isinstance(entry.texture, TextureId):
+                    entry.texture = load_texture(entry.texture)
 
 
 cdef decode_png(loader, first_name, secondary_name):
--- 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