changeset 430:c9433188ffdb

Remove AnmWrapper, since ANMs are lists of entries now.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sat, 03 Aug 2013 15:49:04 +0200
parents 40d5f3083ebc
children 77c0e9a53795
files anmviewer pytouhou/game/background.py pytouhou/game/bullet.pyx pytouhou/game/bullettype.py pytouhou/game/effect.py pytouhou/game/enemy.py pytouhou/game/face.py pytouhou/game/game.py pytouhou/game/itemtype.py pytouhou/game/laser.py pytouhou/game/lasertype.py pytouhou/game/orb.py pytouhou/game/player.py pytouhou/game/text.py pytouhou/games/eosd.py pytouhou/resource/anmwrapper.py pytouhou/resource/loader.py pytouhou/ui/anmrenderer.pyx pytouhou/vm/anmrunner.py pytouhou/vm/eclrunner.py
diffstat 20 files changed, 154 insertions(+), 207 deletions(-) [+]
line wrap: on
line diff
--- a/anmviewer
+++ b/anmviewer
@@ -24,16 +24,16 @@ from pytouhou.resource.loader import Loa
 from pytouhou.ui.anmrenderer import ANMRenderer
 
 
-def main(path, data, name, script, sprites, fixed_pipeline):
+def main(path, data, name, script, entry, sprites, fixed_pipeline):
     resource_loader = Loader()
     resource_loader.scan_archives(os.path.join(path, name) for name in data)
 
     window = Window((384, 448), fixed_pipeline=fixed_pipeline, sound=False)
 
     # Get out animation
-    anm_wrapper = resource_loader.get_anm_wrapper(name.split(','))
-    anm = ANMRenderer(window, resource_loader, anm_wrapper, script, sprites)
-    window.set_runner(anm)
+    anm = resource_loader.get_anm(name)
+    renderer = ANMRenderer(window, resource_loader, anm[entry], script, sprites)
+    window.set_runner(renderer)
     window.run()
 
 
@@ -43,10 +43,11 @@ parser.add_argument('data', metavar='DAT', default=('CM.DAT', 'ST.DAT'), nargs='*', help='Game’s .DAT data files')
 parser.add_argument('-p', '--path', metavar='DIRECTORY', default='.', help='Game directory path.')
 parser.add_argument('--anm', metavar='ANM', required=True, help='Select an ANM')
 parser.add_argument('--script', metavar='SCRIPT', type=int, default=0, help='First script to play')
+parser.add_argument('--entry', metavar='ENTRY', type=int, default=0, help='Entry to display, in multi-entries ANMs.')
 parser.add_argument('--sprites', action='store_true', default=False, help='Display sprites instead of scripts.')
 parser.add_argument('--fixed-pipeline', action='store_true', help='Use the fixed pipeline instead of the new programmable one.')
 
 args = parser.parse_args()
 
-main(args.path, tuple(args.data), args.anm, args.script, args.sprites,
+main(args.path, tuple(args.data), args.anm, args.script, args.entry, args.sprites,
      args.fixed_pipeline)
--- a/pytouhou/game/background.py
+++ b/pytouhou/game/background.py
@@ -19,9 +19,9 @@ from pytouhou.game.sprite import Sprite
 
 
 class Background(object):
-    def __init__(self, stage, anm_wrapper):
+    def __init__(self, stage, anm):
         self.stage = stage
-        self.anm_wrapper = anm_wrapper
+        self.anm = anm
         self.last_frame = -1
 
         self.models = []
@@ -53,7 +53,7 @@ class Background(object):
             quads = []
             for script_index, ox, oy, oz, width_override, height_override in obj.quads:
                 sprite = Sprite(width_override, height_override)
-                anm_runner = ANMRunner(self.anm_wrapper, script_index, sprite)
+                anm_runner = ANMRunner(self.anm, script_index, sprite)
                 anm_runner.run_frame()
                 quads.append((ox, oy, oz, width_override, height_override, sprite))
                 self.anm_runners.append(anm_runner)
--- a/pytouhou/game/bullet.pyx
+++ b/pytouhou/game/bullet.pyx
@@ -79,7 +79,7 @@ cdef class Bullet(object):
                 launch_mult = bullet_type.launch_anim_penalties[2]
             self.dx, self.dy = self.dx * launch_mult, self.dy * launch_mult
             self.sprite = Sprite()
-            self.anmrunner = ANMRunner(bullet_type.anm_wrapper,
+            self.anmrunner = ANMRunner(bullet_type.anm,
                                         index, self.sprite,
                                         bullet_type.launch_anim_offsets[sprite_idx_offset])
             self.anmrunner.run_frame()
@@ -117,7 +117,7 @@ cdef class Bullet(object):
             self.sprite.angle = self.angle - pi
         else:
             self.sprite.angle = self.angle
-        self.anmrunner = ANMRunner(bt.anm_wrapper, bt.anim_index,
+        self.anmrunner = ANMRunner(bt.anm, bt.anim_index,
                                    self.sprite, self.sprite_idx_offset)
         self.anmrunner.run_frame()
 
@@ -146,7 +146,7 @@ cdef class Bullet(object):
             self.sprite.angle = self.angle - pi
         else:
             self.sprite.angle = self.angle
-        self.anmrunner = ANMRunner(bt.anm_wrapper, bt.cancel_anim_index,
+        self.anmrunner = ANMRunner(bt.anm, bt.cancel_anim_index,
                                    self.sprite, bt.launch_anim_offsets[self.sprite_idx_offset])
         self.anmrunner.run_frame()
         self.dx, self.dy = self.dx / 2., self.dy / 2.
--- a/pytouhou/game/bullettype.py
+++ b/pytouhou/game/bullettype.py
@@ -1,12 +1,12 @@
 class BulletType(object):
-    def __init__(self, anm_wrapper, anim_index, cancel_anim_index,
+    def __init__(self, anm, anim_index, cancel_anim_index,
                  launch_anim2_index, launch_anim4_index, launch_anim8_index,
                  hitbox_size,
                  launch_anim_penalties=(0.5, 0.4, 1./3.),
                  launch_anim_offsets=(0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0),
                  type_id=0):
         self.type_id = type_id
-        self.anm_wrapper = anm_wrapper
+        self.anm = anm
         self.anim_index = anim_index
         self.cancel_anim_index = cancel_anim_index
         self.launch_anim2_index = launch_anim2_index
--- a/pytouhou/game/effect.py
+++ b/pytouhou/game/effect.py
@@ -20,9 +20,9 @@ from pytouhou.utils.interpolator import 
 
 
 class Effect(object):
-    def __init__(self, pos, index, anm_wrapper):
+    def __init__(self, pos, index, anm):
         self.sprite = Sprite()
-        self.anmrunner = ANMRunner(anm_wrapper, index, self.sprite)
+        self.anmrunner = ANMRunner(anm, index, self.sprite)
         self.anmrunner.run_frame()
         self.removed = False
         self.objects = [self]
@@ -42,8 +42,8 @@ class Effect(object):
 
 
 class Particle(Effect):
-    def __init__(self, pos, index, anm_wrapper, amp, game, reverse=False, duration=24):
-        Effect.__init__(self, pos, index, anm_wrapper)
+    def __init__(self, pos, index, anm, amp, game, reverse=False, duration=24):
+        Effect.__init__(self, pos, index, anm)
 
         self.frame = 0
         self.duration = duration
--- a/pytouhou/game/enemy.py
+++ b/pytouhou/game/enemy.py
@@ -24,9 +24,9 @@ from pytouhou.game.bullet import LAUNCHE
 
 
 class Enemy(object):
-    def __init__(self, pos, life, _type, bonus_dropped, die_score, anm_wrapper, game):
+    def __init__(self, pos, life, _type, bonus_dropped, die_score, anms, game):
         self._game = game
-        self._anm_wrapper = anm_wrapper
+        self._anms = anms
         self._type = _type
 
         self.process = None
@@ -221,8 +221,9 @@ class Enemy(object):
 
 
     def set_anim(self, index):
+        entry = 0 if index in self._anms[0].scripts else 1
         self.sprite = Sprite()
-        self.anmrunner = ANMRunner(self._anm_wrapper, index, self.sprite)
+        self.anmrunner = ANMRunner(self._anms[entry], index, self.sprite)
         self.anmrunner.run_frame()
 
 
@@ -241,8 +242,9 @@ class Enemy(object):
             self._game.new_particle((self.x, self.y), color, 256) #TODO: find the real size.
 
 
-    def set_aux_anm(self, number, script):
-        self.aux_anm[number] = Effect((self.x, self.y), script, self._anm_wrapper)
+    def set_aux_anm(self, number, index):
+        entry = 0 if index in self._anms[0].scripts else 1
+        self.aux_anm[number] = Effect((self.x, self.y), index, self._anms[entry])
 
 
     def set_pos(self, x, y, z):
--- a/pytouhou/game/face.py
+++ b/pytouhou/game/face.py
@@ -18,12 +18,12 @@ from pytouhou.vm.anmrunner import ANMRun
 
 
 class Face(object):
-    __slots__ = ('_anm_wrapper', 'sprite', 'anmrunner', 'side', 'x', 'y', 'objects')
+    __slots__ = ('_anms', 'sprite', 'anmrunner', 'side', 'x', 'y', 'objects')
 
-    def __init__(self, anm_wrapper, effect, side):
-        self._anm_wrapper = anm_wrapper
+    def __init__(self, anms, effect, side):
+        self._anms = anms
         self.sprite = Sprite()
-        self.anmrunner = ANMRunner(anm_wrapper, side * 2, self.sprite)
+        self.anmrunner = ANMRunner(self._anms[0][0][0], side * 2, self.sprite)
         self.side = side
         self.load(0)
         self.animate(effect)
@@ -40,10 +40,9 @@ class Face(object):
 
 
     def load(self, index):
-        self.sprite.anm, self.sprite.texcoords = self._anm_wrapper.get_sprite(self.side * 8 + index)
+        self.sprite.anm, self.sprite.texcoords = self._anms[self.side][index]
         self.anmrunner.run_frame()
 
 
     def update(self):
         self.anmrunner.run_frame()
-
--- a/pytouhou/game/game.py
+++ b/pytouhou/game/game.py
@@ -105,7 +105,7 @@ class Game(object):
 
     def enable_spellcard_effect(self):
         self.spellcard_effect = Effect((-32., -16.), 0,
-                                       self.spellcard_effect_anm_wrapper) #TODO: find why this offset is necessary.
+                                       self.spellcard_effect_anm) #TODO: find why this offset is necessary.
         self.spellcard_effect.sprite.allow_dest_offset = True #TODO: should be the role of anm’s 25th instruction. Investigate!
 
 
@@ -174,20 +174,20 @@ class Game(object):
                 enemy.death_callback = -1
 
 
-    def new_effect(self, pos, anim, anm_wrapper=None, number=1):
+    def new_effect(self, pos, anim, anm=None, number=1):
         number = min(number, self.nb_bullets_max - len(self.effects))
         for i in xrange(number):
-            self.effects.append(Effect(pos, anim, anm_wrapper or self.etama))
+            self.effects.append(Effect(pos, anim, anm or self.etama[1]))
 
 
     def new_particle(self, pos, anim, amp, number=1, reverse=False, duration=24):
         number = min(number, self.nb_bullets_max - len(self.effects))
         for i in xrange(number):
-            self.effects.append(Particle(pos, anim, self.etama, amp, self, reverse=reverse, duration=duration))
+            self.effects.append(Particle(pos, anim, self.etama[1], amp, self, reverse=reverse, duration=duration))
 
 
     def new_enemy(self, pos, life, instr_type, bonus_dropped, die_score):
-        enemy = Enemy(pos, life, instr_type, bonus_dropped, die_score, self.enm_anm_wrapper, self)
+        enemy = Enemy(pos, life, instr_type, bonus_dropped, die_score, self.enm_anm, self)
         self.enemies.append(enemy)
         return enemy
 
@@ -198,7 +198,7 @@ class Game(object):
 
 
     def new_label(self, pos, text):
-        label = Text(pos, self.interface.ascii_wrapper, text=text, xspacing=8, shift=48)
+        label = Text(pos, self.interface.ascii_anm, text=text, xspacing=8, shift=48)
         label.set_timeout(60, effect='move')
         self.labels.append(label)
         return label
@@ -209,7 +209,7 @@ class Game(object):
         #TODO: Scale
 
         pos = pos[0] + 192, pos[1]
-        label = Text(pos, self.interface.ascii_wrapper, text=hint['Text'], align=hint['Align'])
+        label = Text(pos, self.interface.ascii_anm, text=hint['Text'], align=hint['Align'])
         label.set_timeout(hint['Time'])
         label.set_alpha(hint['Alpha'])
         label.set_color(hint['Color'], text=False)
@@ -218,7 +218,7 @@ class Game(object):
 
 
     def new_face(self, side, effect):
-        face = Face(self.msg_anm_wrapper, effect, side)
+        face = Face(self.msg_anm, effect, side)
         self.faces[side] = face
         return face
 
--- a/pytouhou/game/itemtype.py
+++ b/pytouhou/game/itemtype.py
@@ -1,9 +1,11 @@
 from pytouhou.game.sprite import Sprite
 
 class ItemType(object):
-    def __init__(self, anm_wrapper, sprite_index, indicator_sprite_index):
-        self.anm_wrapper = anm_wrapper
+    def __init__(self, anm, sprite_index, indicator_sprite_index):
+        self.anm = anm
         self.sprite = Sprite()
-        self.sprite.anm, self.sprite.texcoords = anm_wrapper.get_sprite(sprite_index)
+        self.sprite.anm = anm
+        self.sprite.texcoords = anm.sprites[sprite_index]
         self.indicator_sprite = Sprite()
-        self.indicator_sprite.anm, self.indicator_sprite.texcoords = anm_wrapper.get_sprite(indicator_sprite_index)
+        self.indicator_sprite.anm = anm
+        self.indicator_sprite.texcoords = anm.sprites[indicator_sprite_index]
--- a/pytouhou/game/laser.py
+++ b/pytouhou/game/laser.py
@@ -22,10 +22,11 @@ STARTING, STARTED, STOPPING = range(3)
 
 
 class LaserLaunchAnim(object):
-    def __init__(self, laser, anm_wrapper, index):
+    def __init__(self, laser, anm, index):
         self._laser = laser
         self.sprite = Sprite()
-        self.sprite.anm, self.sprite.texcoords = anm_wrapper.get_sprite(index)
+        self.sprite.anm = anm
+        self.sprite.texcoords = anm.sprites[index]
         self.sprite.blendfunc = 1
         self.removed = False
         self.objects = [self]
@@ -57,7 +58,7 @@ class Laser(object):
                        grazing_delay, grazing_extra_duration,
                        game):
         self._game = game
-        launch_anim = LaserLaunchAnim(self, laser_type.anm_wrapper,
+        launch_anim = LaserLaunchAnim(self, laser_type.anm,
                                       laser_type.launch_anim_offsets[sprite_idx_offset]
                                       + laser_type.launch_sprite_idx)
         self._game.effects.append(launch_anim)
@@ -97,7 +98,7 @@ class Laser(object):
         lt = self._laser_type
         self.sprite = Sprite()
         self.sprite.angle = self.angle
-        self.anmrunner = ANMRunner(lt.anm_wrapper, lt.anim_index,
+        self.anmrunner = ANMRunner(lt.anm, lt.anim_index,
                                    self.sprite, self.sprite_idx_offset)
         self.anmrunner.run_frame()
 
@@ -234,7 +235,7 @@ class PlayerLaser(object):
 
         lt = self._laser_type
         self.sprite = Sprite()
-        self.anmrunner = ANMRunner(lt.anm_wrapper, lt.anim_index,
+        self.anmrunner = ANMRunner(lt.anm, lt.anim_index,
                                    self.sprite, self.sprite_idx_offset)
         #self.sprite.blendfunc = 1 #XXX
         self.anmrunner.run_frame()
--- a/pytouhou/game/lasertype.py
+++ b/pytouhou/game/lasertype.py
@@ -1,8 +1,8 @@
 class LaserType(object):
-    def __init__(self, anm_wrapper, anim_index,
+    def __init__(self, anm, anim_index,
                  launch_sprite_idx=140,
                  launch_anim_offsets=(0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0)):
-        self.anm_wrapper = anm_wrapper
+        self.anm = anm
         self.anim_index = anim_index
         self.launch_sprite_idx = launch_sprite_idx
         self.launch_anim_offsets = launch_anim_offsets
--- a/pytouhou/game/orb.py
+++ b/pytouhou/game/orb.py
@@ -21,9 +21,9 @@ class Orb(object):
     __slots__ = ('sprite', 'anmrunner', 'offset_x', 'offset_y', 'player_state',
                  'fire')
 
-    def __init__(self, anm_wrapper, index, player_state, fire_func):
+    def __init__(self, anm, index, player_state, fire_func):
         self.sprite = Sprite()
-        self.anmrunner = ANMRunner(anm_wrapper, index, self.sprite)
+        self.anmrunner = ANMRunner(anm, index, self.sprite)
         self.anmrunner.run_frame()
 
         self.offset_x = 0
--- a/pytouhou/game/player.py
+++ b/pytouhou/game/player.py
@@ -53,11 +53,11 @@ class PlayerState(object):
 
 
 class Player(object):
-    def __init__(self, state, game, anm_wrapper):
+    def __init__(self, state, game, anm):
         self._game = game
         self.sprite = None
         self.anmrunner = None
-        self.anm_wrapper = anm_wrapper
+        self.anm = anm
 
         self.speeds = (self.sht.horizontal_vertical_speed,
                        self.sht.diagonal_speed,
@@ -86,7 +86,7 @@ class Player(object):
 
     def set_anim(self, index):
         self.sprite = Sprite()
-        self.anmrunner = ANMRunner(self.anm_wrapper, index, self.sprite)
+        self.anmrunner = ANMRunner(self.anm, index, self.sprite)
         self.anmrunner.run_frame()
 
 
@@ -134,7 +134,7 @@ class Player(object):
                 if lasers[number]:
                     continue
 
-                laser_type = LaserType(self.anm_wrapper, shot.sprite % 256, 68)
+                laser_type = LaserType(self.anm, shot.sprite % 256, 68)
                 lasers[number] = PlayerLaser(laser_type, 0, shot.hitbox, shot.damage, shot.angle, shot.speed, shot.interval, origin)
                 continue
 
@@ -148,7 +148,7 @@ class Player(object):
             y = origin.y + shot.pos[1]
 
             #TODO: find a better way to do that.
-            bullet_type = BulletType(self.anm_wrapper, shot.sprite % 256,
+            bullet_type = BulletType(self.anm, shot.sprite % 256,
                                      shot.sprite % 256 + 32, #TODO: find the real cancel anim
                                      0, 0, 0, 0.)
             #TODO: Type 1 (homing bullets)
@@ -274,7 +274,7 @@ class Player(object):
                 self.direction = None
 
                 self.sprite = Sprite()
-                self.anmrunner = ANMRunner(self.anm_wrapper, 0, self.sprite)
+                self.anmrunner = ANMRunner(self.anm, 0, self.sprite)
                 self.sprite.alpha = 128
                 self.sprite.rescale = 0.0, 2.5
                 self.sprite.fade(30, 255, lambda x: x)
--- a/pytouhou/game/text.py
+++ b/pytouhou/game/text.py
@@ -28,7 +28,7 @@ class Glyph(object):
 
 
 class Widget(object):
-    def __init__(self, pos, back_wrapper=None, back_script=22):
+    def __init__(self, pos, back_anm=None, back_script=22):
         self.sprite = None
         self.removed = False
         self.changed = True
@@ -36,10 +36,10 @@ class Widget(object):
         self.frame = 0
 
         # Set up the backround sprite
-        self.back_wrapper = back_wrapper
-        if back_wrapper:
+        self.back_anm = back_anm
+        if back_anm:
             self.sprite = Sprite()
-            self.anmrunner = ANMRunner(back_wrapper, back_script, self.sprite)
+            self.anmrunner = ANMRunner(back_anm, back_script, self.sprite)
             self.anmrunner.run_frame()
 
         self.x, self.y = pos
@@ -54,17 +54,17 @@ class Widget(object):
 
 
 class GlyphCollection(Widget):
-    def __init__(self, pos, anm_wrapper, back_wrapper=None, ref_script=0,
+    def __init__(self, pos, anm, back_anm=None, ref_script=0,
                  xspacing=14, back_script=22):
-        Widget.__init__(self, pos, back_wrapper, back_script)
+        Widget.__init__(self, pos, back_anm, back_script)
 
         self.ref_sprite = Sprite()
-        self.anm_wrapper = anm_wrapper
+        self.anm = anm
         self.glyphes = []
         self.xspacing = xspacing
 
         # Set up ref sprite
-        anm_runner = ANMRunner(anm_wrapper, ref_script, self.ref_sprite)
+        anm_runner = ANMRunner(anm, ref_script, self.ref_sprite)
         anm_runner.run_frame()
         self.ref_sprite.corner_relative_placement = True #TODO: perhaps not right
 
@@ -82,7 +82,8 @@ class GlyphCollection(Widget):
     def set_sprites(self, sprite_indexes):
         self.set_length(len(sprite_indexes))
         for glyph, idx in zip(self.glyphes, sprite_indexes):
-            glyph.sprite.anm, glyph.sprite.texcoords = self.anm_wrapper.get_sprite(idx)
+            glyph.sprite.anm = self.anm
+            glyph.sprite.texcoords = self.anm.sprites[idx]
             glyph.sprite.changed = True
 
 
@@ -105,9 +106,9 @@ class GlyphCollection(Widget):
 
 
 class Text(GlyphCollection):
-    def __init__(self, pos, ascii_wrapper, back_wrapper=None, text='',
+    def __init__(self, pos, ascii_anm, back_anm=None, text='',
                  xspacing=14, shift=21, back_script=22, align='left'):
-        GlyphCollection.__init__(self, pos, ascii_wrapper, back_wrapper,
+        GlyphCollection.__init__(self, pos, ascii_anm, back_anm,
                                  xspacing=xspacing, back_script=back_script)
         self.text = ''
         self.shift = shift
@@ -193,10 +194,10 @@ class Text(GlyphCollection):
 
 
 class Counter(GlyphCollection):
-    def __init__(self, pos, anm_wrapper, back_wrapper=None, script=0,
+    def __init__(self, pos, anm, back_anm=None, script=0,
                  xspacing=16, value=0, back_script=22):
-        GlyphCollection.__init__(self, pos, anm_wrapper,
-                                 back_wrapper=back_wrapper, ref_script=script,
+        GlyphCollection.__init__(self, pos, anm,
+                                 back_anm=back_anm, ref_script=script,
                                  xspacing=xspacing, back_script=back_script)
 
         self.value = value
@@ -221,9 +222,9 @@ class Counter(GlyphCollection):
 
 
 class Gauge(object):
-    def __init__(self, pos, anm_wrapper, max_length=280, maximum=1, value=0):
+    def __init__(self, pos, anm, max_length=280, maximum=1, value=0):
         self.sprite = Sprite()
-        self.anmrunner = ANMRunner(anm_wrapper, 21, self.sprite)
+        self.anmrunner = ANMRunner(anm, 21, self.sprite)
         self.anmrunner.run_frame()
         self.removed = False
         self.sprite.corner_relative_placement = True #TODO: perhaps not right
--- a/pytouhou/games/eosd.py
+++ b/pytouhou/games/eosd.py
@@ -34,52 +34,54 @@ class EoSDGame(Game):
                  continues=0, hints=None):
 
         if not bullet_types:
-            etama3 = resource_loader.get_anm_wrapper(('etama3.anm',))
-            self.etama = resource_loader.get_anm_wrapper(('etama4.anm',))
-            bullet_types = [BulletType(etama3, 0, 11, 14, 15, 16, hitbox_size=2,
+            self.etama = resource_loader.get_multi_anm(('etama3.anm', 'etama4.anm'))
+            bullet_types = [BulletType(self.etama[0], 0, 11, 14, 15, 16, hitbox_size=2,
                                        type_id=0),
-                            BulletType(etama3, 1, 12, 17, 18, 19, hitbox_size=3,
+                            BulletType(self.etama[0], 1, 12, 17, 18, 19, hitbox_size=3,
                                        type_id=1),
-                            BulletType(etama3, 2, 12, 17, 18, 19, hitbox_size=2,
+                            BulletType(self.etama[0], 2, 12, 17, 18, 19, hitbox_size=2,
                                        type_id=2),
-                            BulletType(etama3, 3, 12, 17, 18, 19, hitbox_size=3,
+                            BulletType(self.etama[0], 3, 12, 17, 18, 19, hitbox_size=3,
                                        type_id=3),
-                            BulletType(etama3, 4, 12, 17, 18, 19, hitbox_size=2.5,
+                            BulletType(self.etama[0], 4, 12, 17, 18, 19, hitbox_size=2.5,
                                        type_id=4),
-                            BulletType(etama3, 5, 12, 17, 18, 19, hitbox_size=2,
+                            BulletType(self.etama[0], 5, 12, 17, 18, 19, hitbox_size=2,
                                        type_id=5),
-                            BulletType(etama3, 6, 13, 20, 20, 20, hitbox_size=8,
+                            BulletType(self.etama[0], 6, 13, 20, 20, 20, hitbox_size=8,
                                        launch_anim_offsets=(0, 1, 1, 2, 2, 3, 4, 0),
                                        type_id=6),
-                            BulletType(etama3, 7, 13, 20, 20, 20, hitbox_size=5.5,
+                            BulletType(self.etama[0], 7, 13, 20, 20, 20, hitbox_size=5.5,
                                        launch_anim_offsets=(1,)*28,
                                        type_id=7),
-                            BulletType(etama3, 8, 13, 20, 20, 20, hitbox_size=4.5,
+                            BulletType(self.etama[0], 8, 13, 20, 20, 20, hitbox_size=4.5,
                                        launch_anim_offsets=(0, 1, 1, 2, 2, 3, 4, 0),
                                        type_id=8),
-                            BulletType(self.etama, 0, 1, 2, 2, 2, hitbox_size=16,
+                            BulletType(self.etama[1], 0, 1, 2, 2, 2, hitbox_size=16,
                                        launch_anim_offsets=(0, 1, 2, 3, 4, 5, 6, 7, 8),
                                        type_id=9)]
 
         if not laser_types:
-            laser_types = [LaserType(etama3, 9),
-                           LaserType(etama3, 10)]
+            laser_types = [LaserType(self.etama[0], 9),
+                           LaserType(self.etama[0], 10)]
 
         if not item_types:
-            item_types = [ItemType(etama3, 0, 7), #Power
-                          ItemType(etama3, 1, 8), #Point
-                          ItemType(etama3, 2, 9), #Big power
-                          ItemType(etama3, 3, 10), #Bomb
-                          ItemType(etama3, 4, 11), #Full power
-                          ItemType(etama3, 5, 12), #1up
-                          ItemType(etama3, 6, 13)] #Star
+            item_types = [ItemType(self.etama[0], 0, 7), #Power
+                          ItemType(self.etama[0], 1, 8), #Point
+                          ItemType(self.etama[0], 2, 9), #Big power
+                          ItemType(self.etama[0], 3, 10), #Bomb
+                          ItemType(self.etama[0], 4, 11), #Full power
+                          ItemType(self.etama[0], 5, 12), #1up
+                          ItemType(self.etama[0], 6, 13)] #Star
 
-        self.enm_anm_wrapper = resource_loader.get_anm_wrapper2(('stg%denm.anm' % stage,
-                                                                 'stg%denm2.anm' % stage))
+        try:
+            self.enm_anm = resource_loader.get_multi_anm(('stg%denm.anm' % stage,
+                                                          'stg%denm2.anm' % stage))
+        except KeyError:
+            self.enm_anm = resource_loader.get_anm('stg%denm.anm' % stage)
         ecl = resource_loader.get_ecl('ecldata%d.ecl' % stage)
         self.ecl_runners = [ECLMainRunner(main, ecl.subs, self) for main in ecl.mains]
 
-        self.spellcard_effect_anm_wrapper = resource_loader.get_anm_wrapper(('eff0%d.anm' % stage,))
+        self.spellcard_effect_anm = resource_loader.get_single_anm('eff0%d.anm' % stage)
 
         player_face = player_states[0].character // 2
         enemy_face = [('face03a.anm', 'face03b.anm'),
@@ -90,11 +92,16 @@ class EoSDGame(Game):
                       ('face09b.anm', 'face10a.anm', 'face10b.anm'),
                       ('face08a.anm', 'face12a.anm', 'face12b.anm', 'face12c.anm')]
         self.msg = resource_loader.get_msg('msg%d.dat' % stage)
-        self.msg_anm_wrapper = resource_loader.get_anm_wrapper2(('face0%da.anm' % player_face,
-                                                                 'face0%db.anm' % player_face,
-                                                                 'face0%dc.anm' % player_face)
-                                                                + enemy_face[stage - 1],
-                                                                (0, 2, 4, 8, 10, 11, 12))
+        msg_anm = [resource_loader.get_multi_anm(('face0%da.anm' % player_face,
+                                                  'face0%db.anm' % player_face,
+                                                  'face0%dc.anm' % player_face)),
+                   resource_loader.get_multi_anm(enemy_face[stage - 1])]
+
+        self.msg_anm = [[], []]
+        for i, anms in enumerate(msg_anm):
+            for anm in anms:
+                for sprite in anm.sprites.values():
+                    self.msg_anm[i].append((anm, sprite))
 
         characters = resource_loader.get_eosd_characters()
         players = [EoSDPlayer(state, self, resource_loader, characters[state.character]) for state in player_states]
@@ -105,8 +112,8 @@ class EoSDGame(Game):
         # Load stage data
         self.std = resource_loader.get_stage('stage%d.std' % stage)
 
-        background_anm_wrapper = resource_loader.get_anm_wrapper(('stg%dbg.anm' % stage,))
-        self.background = Background(self.std, background_anm_wrapper)
+        background_anm = resource_loader.get_single_anm('stg%dbg.anm' % stage)
+        self.background = Background(self.std, background_anm)
 
         self.resource_loader = resource_loader #XXX: currently used for texture preload in pytouhou.ui.gamerunner. Wipe it!
 
@@ -119,8 +126,8 @@ class EoSDGame(Game):
 class EoSDInterface(object):
     def __init__(self, game, resource_loader):
         self.game = game
-        front = resource_loader.get_anm_wrapper(('front.anm',))
-        self.ascii_wrapper = resource_loader.get_anm_wrapper(('ascii.anm',))
+        front = resource_loader.get_single_anm('front.anm')
+        self.ascii_anm = resource_loader.get_single_anm('ascii.anm')
 
         self.width = 640
         self.height = 480
@@ -136,25 +143,25 @@ class EoSDInterface(object):
         for item in self.items:
             item.sprite.allow_dest_offset = True #XXX
 
-        self.level_start = [Text((176, 200), self.ascii_wrapper, text='STAGE %d' % game.stage)] #TODO: find the exact location.
+        self.level_start = [Text((176, 200), self.ascii_anm, text='STAGE %d' % game.stage)] #TODO: find the exact location.
         self.level_start[0].set_timeout(240, effect='fadeout', duration=60, start=120)
         self.level_start[0].set_color('yellow')
         #TODO: use the system text for the stage name, and the song name.
 
         self.labels = {
-            'highscore': Text((500, 58), self.ascii_wrapper, front, text='0'),
-            'score': Text((500, 82), self.ascii_wrapper, front, text='0'),
+            'highscore': Text((500, 58), self.ascii_anm, front, text='0'),
+            'score': Text((500, 82), self.ascii_anm, front, text='0'),
             'player': Counter((500, 122), front, front, script=16, value=0),
             'bombs': Counter((500, 146), front, front, script=17, value=0),
-            'power': Text((500, 186), self.ascii_wrapper, front, text='0'),
-            'graze': Text((500, 206), self.ascii_wrapper, front, text='0'),
-            'points': Text((500, 226), self.ascii_wrapper, front, text='0'),
-            'framerate': Text((512, 464), self.ascii_wrapper, front),
-            'debug?': Text((0, 464), self.ascii_wrapper, front),
+            'power': Text((500, 186), self.ascii_anm, front, text='0'),
+            'graze': Text((500, 206), self.ascii_anm, front, text='0'),
+            'points': Text((500, 226), self.ascii_anm, front, text='0'),
+            'framerate': Text((512, 464), self.ascii_anm, front),
+            'debug?': Text((0, 464), self.ascii_anm, front),
 
             # Only when there is a boss.
-            'boss_lives': Text((80, 16), self.ascii_wrapper),
-            'timeout': Text((384, 16), self.ascii_wrapper),
+            'boss_lives': Text((80, 16), self.ascii_anm),
+            'timeout': Text((384, 16), self.ascii_anm),
         }
         self.labels['boss_lives'].set_color('yellow')
 
@@ -237,13 +244,12 @@ class EoSDPlayer(Player):
     def __init__(self, state, game, resource_loader, character):
         self.sht = character[0]
         self.focused_sht = character[1]
-        anm_wrapper = resource_loader.get_anm_wrapper(('player0%d.anm' % (state.character // 2),))
-        self.anm_wrapper = anm_wrapper
+        self.anm = resource_loader.get_single_anm('player0%d.anm' % (state.character // 2))
 
-        Player.__init__(self, state, game, anm_wrapper)
+        Player.__init__(self, state, game, self.anm)
 
-        self.orbs = [Orb(self.anm_wrapper, 128, self.state, None),
-                     Orb(self.anm_wrapper, 129, self.state, None)]
+        self.orbs = [Orb(self.anm, 128, self.state, None),
+                     Orb(self.anm, 129, self.state, None)]
 
         self.orbs[0].offset_x = -24
         self.orbs[1].offset_x = 24
deleted file mode 100644
--- a/pytouhou/resource/anmwrapper.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- encoding: utf-8 -*-
-##
-## Copyright (C) 2012 Thibaut Girka <thib@sitedethib.com>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 3 only.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU General Public License for more details.
-##
-
-from itertools import repeat, chain
-
-
-class AnmWrapper(object):
-    def __init__(self, anm_files, offsets=None):
-        """Wrapper for scripts and sprites described in “anm_files”.
-
-        The optional “offsets” argument specifies a list of offsets to be added
-        to script and sprite numbers of each file described in “anm_files”.
-
-        That is, if anm_files[0] and anm_files[1] each have only one sprite,
-        numbered 0 in both cases, and offsets=(0, 1), the first file's sprite
-        will be numbered 0 and the second file's will be numbered 1.
-        """
-        self.scripts = {}
-        self.sprites = {}
-
-        if not offsets:
-            offsets = repeat(0) # “offsets” defaults to zeroes
-
-        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():
-                self.sprites[sprite_id + offset] = (anm, sprite)
-
-
-    def get_sprite(self, sprite_index):
-        return self.sprites[sprite_index]
-
-
-    def get_script(self, script_index):
-        return self.scripts[script_index]
-
--- a/pytouhou/resource/loader.py
+++ b/pytouhou/resource/loader.py
@@ -28,9 +28,6 @@ from pytouhou.formats.exe import SHT as 
 from pytouhou.formats.music import Track
 from pytouhou.formats.fmt import FMT
 
-
-from pytouhou.resource.anmwrapper import AnmWrapper
-
 from pytouhou.utils.helpers import get_logger
 
 logger = get_logger(__name__)
@@ -188,28 +185,13 @@ class Loader(object):
         return FMT.read(file) #TODO: modular
 
 
-    def get_anm_wrapper(self, names, offsets=None):
-        """Create an AnmWrapper for ANM files “names”.
-
-        If one of the files “names” does not exist or is not a valid ANM file,
-        raises an exception.
-        """
-        return AnmWrapper((self.get_anm(name) for name in names), offsets)
+    def get_single_anm(self, name):
+        """Hack for EoSD, since it doesn’t support multi-entries ANMs."""
+        anm = self.get_anm(name)
+        assert len(anm) == 1
+        return anm[0]
 
 
-    def get_anm_wrapper2(self, names, offsets=None):
-        """Create an AnmWrapper for ANM files “names”.
-
-        Stop at the first non-existent or invalid ANM file if there is one,
-        and return an AnmWrapper for all the previous correct files.
-        """
-        anms = []
-
-        try:
-            for name in names:
-                anms.append(self.get_anm(name))
-        except KeyError:
-            pass
-
-        return AnmWrapper(anms, offsets)
-
+    def get_multi_anm(self, names):
+        """Hack for EoSD, since it doesn’t support multi-entries ANMs."""
+        return sum((self.get_anm(name) for name in names), [])
--- a/pytouhou/ui/anmrenderer.pyx
+++ b/pytouhou/ui/anmrenderer.pyx
@@ -31,7 +31,7 @@ logger = get_logger(__name__)
 
 
 class ANMRenderer(Renderer):
-    def __init__(self, window, resource_loader, anm_wrapper, index=0, sprites=False):
+    def __init__(self, window, resource_loader, anm, index=0, sprites=False):
         self.use_fixed_pipeline = window.use_fixed_pipeline #XXX
 
         Renderer.__init__(self, resource_loader)
@@ -39,7 +39,7 @@ class ANMRenderer(Renderer):
         self.window = window
         self.texture_manager.load(resource_loader.instanced_anms.values())
 
-        self._anm_wrapper = anm_wrapper
+        self._anm = anm
         self.sprites = sprites
         self.clear_color = (0., 0., 0., 1.)
         self.force_allow_dest_offset = False
@@ -74,10 +74,11 @@ class ANMRenderer(Renderer):
             index = self.num
         self.sprite = Sprite()
         if self.sprites:
-            self.sprite.anm, self.sprite.texcoords = self._anm_wrapper.get_sprite(index)
+            self.sprite.anm = self._anm
+            self.sprite.texcoords = self._anm.sprites[index]
             print('Loaded sprite %d' % index)
         else:
-            self.anmrunner = ANMRunner(self._anm_wrapper, index, self.sprite)
+            self.anmrunner = ANMRunner(self._anm, index, self.sprite)
             print('Loading anim %d, handled events: %r' % (index, self.anmrunner.script.interrupts.keys()))
         self.num = index
 
@@ -93,9 +94,9 @@ class ANMRenderer(Renderer):
     def index_items(self):
         self.items = {}
         if self.sprites:
-            self.items = self._anm_wrapper.sprites
+            self.items = self._anm.sprites
         else:
-            self.items = self._anm_wrapper.scripts
+            self.items = self._anm.scripts
 
 
     def toggle_sprites(self):
--- a/pytouhou/vm/anmrunner.py
+++ b/pytouhou/vm/anmrunner.py
@@ -23,9 +23,9 @@ logger = get_logger(__name__)
 
 class ANMRunner(object):
     __metaclass__ = MetaRegistry
-    __slots__ = ('_anm_wrapper', '_sprite', 'running',
-                 'sprite_index_offset', 'script', 'instruction_pointer',
-                 'frame', 'waiting', 'handlers', 'variables', 'version', 'timeout')
+    __slots__ = ('_anm', '_sprite', 'running', 'sprite_index_offset', 'script',
+                 'instruction_pointer', 'frame', 'waiting', 'handlers',
+                 'variables', 'version', 'timeout')
 
     #TODO: check!
     formulae = {0: lambda x: x,
@@ -38,13 +38,13 @@ class ANMRunner(object):
                 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
+    def __init__(self, anm, script_id, sprite, sprite_index_offset=0):
+        self._anm = anm
         self._sprite = sprite
         self.running = True
         self.waiting = False
 
-        anm, self.script = anm_wrapper.get_script(script_id)
+        self.script = anm.scripts[script_id]
         self.version = anm.version
         self.handlers = self._handlers[{0: 6, 2: 7}[anm.version]]
         self.frame = 0
@@ -166,7 +166,7 @@ class ANMRunner(object):
     @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)
+        self._sprite.anm, self._sprite.texcoords = self._anm, self._anm.sprites[sprite_index + self.sprite_index_offset]
 
 
     @instruction(2)
--- a/pytouhou/vm/eclrunner.py
+++ b/pytouhou/vm/eclrunner.py
@@ -920,7 +920,7 @@ class ECLRunner(object):
         elif 4 <= anim <= 15:
             self._game.new_particle((self._enemy.x, self._enemy.y), anim + 5, 192, number=number)
         elif anim == 16:
-            self._game.new_effect((self._enemy.x, self._enemy.y), 0, self._game.spellcard_effect_anm_wrapper, number=number)
+            self._game.new_effect((self._enemy.x, self._enemy.y), 0, self._game.spellcard_effect_anm, number=number)
         elif anim == 17:
             self._game.new_particle((self._enemy.x, self._enemy.y), anim - 10, 640, number=number, reverse=True, duration=60)
         elif anim == 18: