changeset 57:694f25881d0f

Fix move_to interpolation, add support for a few ANM and ECL instructions
author Thibaut Girka <thib@sitedethib.com>
date Tue, 23 Aug 2011 19:27:24 +0200
parents 299de3a9b69f
children 3da4de9decd0
files pytouhou/formats/anm0.py pytouhou/game/eclrunner.py pytouhou/game/enemymanager.py pytouhou/game/player.py pytouhou/game/sprite.py pytouhou/utils/interpolator.py
diffstat 6 files changed, 101 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/pytouhou/formats/anm0.py
+++ b/pytouhou/formats/anm0.py
@@ -79,6 +79,8 @@ class Animations(object):
                     args = unpack('<ff', data)
                 elif instr_type == 3: # set_alpha
                     args = unpack('<I', data)
+                elif instr_type == 4: # set_color
+                    args = unpack('<BBBx', data)
                 elif instr_type == 5: # jump
                     # Translate offset to instruction index
                     args = (instruction_offsets.index(unpack('<I', data)[0]),)
@@ -86,10 +88,18 @@ class Animations(object):
                     args = unpack('<fff', data)
                 elif instr_type == 10: # set_3d_rotation_speed
                     args = unpack('<fff', data)
+                elif instr_type == 11: # set_scale_speed
+                    args = unpack('<ff', data)
+                elif instr_type == 12: # fade
+                    args = unpack('<ii', data)
+                elif instr_type == 16: # set_random_sprite
+                    args = unpack('<ii', data)
                 elif instr_type == 27: # shift_texture_x
                     args = unpack('<f', data)
                 elif instr_type == 28: # shift_texture_y
                     args = unpack('<f', data)
+                elif instr_type == 30: # scale_in
+                    args = unpack('<ffi', data)
                 else:
                     args = (data,)
                 anm.scripts[i].append((time, instr_type, args))
--- a/pytouhou/game/eclrunner.py
+++ b/pytouhou/game/eclrunner.py
@@ -81,12 +81,17 @@ class ECLRunner(object):
             except IndexError:
                 return False
 
-            #TODO: skip bad ranks
-
             if frame > self.frame:
                 break
             else:
                 self.instruction_pointer += 1
+
+
+            #TODO: skip bad ranks
+            if not rank_mask & (0x100 << self._game_state.rank):
+                continue
+
+
             if frame == self.frame:
                 try:
                     callback = self._handlers[instr_type]
@@ -126,7 +131,9 @@ class ECLRunner(object):
                 return self._enemy.frame
             elif value == -10024:
                 return self._enemy.life
-            raise NotImplementedError #TODO
+            elif value == -10025:
+                return self._enemy.select_player(self._game_state.players).character
+            raise NotImplementedError(value) #TODO
         else:
             return value
 
@@ -302,6 +309,11 @@ class ECLRunner(object):
         self._enemy.move_to(duration, x, y, z)
 
 
+    @instruction(61)
+    def stop_in(self, duration):
+        self._enemy.stop_in(duration)
+
+
     @instruction(65)
     def set_screen_box(self, xmin, ymin, xmax, ymax):
         self._enemy.screen_box = xmin, ymin, xmax, ymax
@@ -312,6 +324,16 @@ class ECLRunner(object):
         self._enemy.screen_box = None
 
 
+    @instruction(67)
+    def set_bullet_attributes1(self, bullet_anim, launch_anim, bullets_per_shot,
+                              number_of_shots, speed, unknown, launch_angle,
+                              angle, flags):
+        self._enemy.set_bullet_attributes(1, bullet_anim, launch_anim,
+                                           bullets_per_shot, number_of_shots,
+                                           speed, unknown, launch_angle, angle,
+                                           flags)
+
+
     @instruction(77)
     def set_bullet_interval(self, value):
         self._enemy.bullet_launch_interval = value
@@ -348,6 +370,12 @@ class ECLRunner(object):
         self._enemy.death_anim = sprite_index % 256 #TODO
 
 
+    @instruction(101)
+    def set_boss_mode(self, unknown):
+        #TODO: unknown
+        self._game_state.boss = self._enemy
+
+
     @instruction(103)
     def set_hitbox(self, width, height, depth):
         self._enemy.hitbox = (width, height)
--- a/pytouhou/game/enemymanager.py
+++ b/pytouhou/game/enemymanager.py
@@ -54,6 +54,7 @@ class Enemy(object):
         self.movement_dependant_sprites = None
         self.direction = None
         self.interpolator = None #TODO
+        self.speed_interpolator = None
         self.angle = 0.
         self.speed = 0.
         self.rotation_speed = 0.
@@ -63,15 +64,19 @@ class Enemy(object):
         self.screen_box = None
 
 
-    def set_bullet_attributes(self, bullet_anim, launch_anim, bullets_per_shot,
-                              number_of_shots, speed, unknown, launch_angle,
-                              angle, flags):
-        self.bullet_attributes = (1, bullet_anim, launch_anim, bullets_per_shot,
+    def set_bullet_attributes(self, type_, bullet_anim, launch_anim,
+                              bullets_per_shot, number_of_shots, speed, unknown,
+                              launch_angle, angle, flags):
+        self.bullet_attributes = (type_, bullet_anim, launch_anim, bullets_per_shot,
                                   number_of_shots, speed, unknown, launch_angle,
                                   angle, flags)
         if not self.delay_attack:
-            pass
-            #TODO: actually fire
+            self.fire()
+
+
+    def fire(self):
+        #TODO
+        pass
 
 
     def select_player(self, players):
@@ -93,9 +98,19 @@ class Enemy(object):
 
 
     def move_to(self, duration, x, y, z):
+        if not self.interpolator:
+            self.interpolator = Interpolator((self.x, self.y))
+            self.interpolator.set_interpolation_start(self.frame, (self.x, self.y))
         self.interpolator.set_interpolation_end(self.frame + duration, (x, y))
 
 
+    def stop_in(self, duration):
+        if not self.speed_interpolator:
+            self.speed_interpolator = Interpolator((self.speed,))
+            self.speed_interpolator.set_interpolation_start(self.frame, (self.speed,))
+            self.speed_interpolator.set_interpolation_end(self.frame + duration, (0.,))
+
+
     def is_visible(self, screen_width, screen_height):
         if not self._sprite:
             return False
@@ -132,12 +147,19 @@ class Enemy(object):
 
     def update(self, frame):
         x, y = self.x, self.y
-        if self.interpolator and self.interpolator.update(self.frame):
+        if self.interpolator:
+            self.interpolator.update(self.frame)
             x, y = self.interpolator.values
 
         self.speed += self.acceleration #TODO: units? Execution order?
         self.angle += self.rotation_speed #TODO: units? Execution order?
 
+
+        if self.speed_interpolator:
+            self.speed_interpolator.update(self.frame)
+            self.speed, = self.speed_interpolator.values
+
+
         dx, dy = cos(self.angle) * self.speed, sin(self.angle) * self.speed
         if self._type & 2:
             x -= dx
@@ -146,7 +168,9 @@ class Enemy(object):
         y += dy
 
         if self.movement_dependant_sprites:
-            #TODO: is that really how it works?
+            #TODO: is that really how it works? Almost.
+            # Sprite determination is done only once per changement, and is
+            # superseeded by ins_97.
             if x < self.x:
                 self.set_anim(self.movement_dependant_sprites[2])
                 self.direction = -1
@@ -235,6 +259,12 @@ class EnemyManager(object):
                 enemy._removed = True
                 self.enemies.remove(enemy)
 
+
+        #TODO: disable boss mode if it is dead/it has timeout
+        if self._game_state.boss and self._game_state.boss._removed:
+            self._game_state.boss = None
+
+
         # Add enemies to vertices/uvs
         self.objects_by_texture = {}
         for enemy in visible_enemies:
--- a/pytouhou/game/player.py
+++ b/pytouhou/game/player.py
@@ -22,4 +22,4 @@ class Player(object):
         self.power = 0
         self.lives = 0
         self.bombs = 0
-        self.type = 0 # ReimuA/ReimuB/MarisaA/MarisaB/...
+        self.character = 0 # ReimuA/ReimuB/MarisaA/MarisaB/...
--- a/pytouhou/game/sprite.py
+++ b/pytouhou/game/sprite.py
@@ -13,6 +13,7 @@
 ##
 
 
+from random import randrange
 from struct import unpack
 
 from pytouhou.utils.matrix import Matrix
@@ -37,6 +38,7 @@ class Sprite(object):
         self.texoffsets = (0., 0.)
         self.mirrored = False
         self.rescale = (1., 1.)
+        self.scale_speed = (0., 0.)
         self.rotations_3d = (0., 0., 0.)
         self.rotations_speed_3d = (0., 0., 0.)
         self.corner_relative_placement = False
@@ -44,6 +46,7 @@ class Sprite(object):
         self.keep_still = False
         self.playing = True
         self.frame = 0
+        self.color = (255, 255, 255)
         self.alpha = 255
         self._uvs = []
         self._vertices = []
@@ -85,7 +88,7 @@ class Sprite(object):
 
         d = vertmat.data
         assert (d[3][0], d[3][1], d[3][2], d[3][3]) == (1., 1., 1., 1.)
-        self._colors = [(255, 255, 255, self.alpha)] * 4
+        self._colors = [(self.color[0], self.color[1], self.color[2], self.alpha)] * 4
         self._uvs, self._vertices = uvs, zip(d[0], d[1], d[2])
 
 
@@ -115,11 +118,15 @@ class Sprite(object):
                         self.playing = False
                         return False
                     if instr_type == 1:
+                        #TODO: handle out-of-anm sprites
                         self.texcoords = self.anm.sprites[args[0]]
                     elif instr_type == 2:
                         self.rescale = args
                     elif instr_type == 3:
                         self.alpha = args[0] % 256 #TODO
+                    elif instr_type == 4:
+                        b, g, r = args
+                        self.color = (r, g, b)
                     elif instr_type == 5:
                         self.instruction_pointer, = args
                         self.frame = script[self.instruction_pointer][0]
@@ -129,6 +136,12 @@ class Sprite(object):
                         self.rotations_3d = args
                     elif instr_type == 10:
                         self.rotations_speed_3d = args
+                    elif instr_type == 11:
+                        self.scale_speed = args
+                    elif instr_type == 16:
+                        #TODO: handle out-of-anm sprites
+                        #TODO: use the game's PRNG?
+                        self.texcoords = self.anm.sprites[args[0] + randrange(args[1])]
                     elif instr_type == 23:
                         self.corner_relative_placement = True #TODO
                     elif instr_type == 27:
@@ -137,7 +150,7 @@ class Sprite(object):
                     elif instr_type == 28:
                         tox, toy = self.texoffsets
                         self.texoffsets = tox, toy + args[0]
-                    elif instr_type == 15:
+                    elif instr_type in (15, 21):
                         self.keep_still = True
                         break
                     else:
@@ -147,8 +160,9 @@ class Sprite(object):
         ax, ay, az = self.rotations_3d
         sax, say, saz = self.rotations_speed_3d
         self.rotations_3d = ax + sax, ay + say, az + saz
+        self.rescale = self.rescale[0] + self.scale_speed[0], self.rescale[1] + self.scale_speed[1]
 
-        if self.rotations_speed_3d != (0., 0., 0.):
+        if self.rotations_speed_3d != (0., 0., 0.) or self.scale_speed != (0., 0.):
             return True
 
         return changed
--- a/pytouhou/utils/interpolator.py
+++ b/pytouhou/utils/interpolator.py
@@ -23,6 +23,10 @@ class Interpolator(object):
         self._frame = 0
 
 
+    def __nonzero__(self):
+        return self._frame <= self.end_frame
+
+
     def set_interpolation_start(self, frame, values):
         self.start_values = tuple(values)
         self.start_frame = frame
@@ -47,10 +51,8 @@ class Interpolator(object):
             self.values = tuple(self.end_values)
             self.start_values = tuple(self.end_values)
             self.start_frame = frame
-            return frame == self.end_frame
         else:
             truc = float(frame - self.start_frame) / float(self.end_frame - self.start_frame)
             self.values = tuple(start_value + truc * (end_value - start_value)
                                 for (start_value, end_value) in zip(self.start_values, self.end_values))
-            return True