changeset 83:fc0294c745b6

Basic bullet handling! Clean up as soon as possible :p
author Thibaut Girka <thib@sitedethib.com>
date Sat, 03 Sep 2011 22:22:58 +0200
parents de48213a02bf
children 1a0c78e5a941
files eclviewer.py pytouhou/game/enemymanager.py pytouhou/game/game.py pytouhou/opengl/texture.py pytouhou/vm/eclrunner.py
diffstat 5 files changed, 220 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/eclviewer.py
+++ b/eclviewer.py
@@ -31,7 +31,7 @@ from pytouhou.game.sprite import AnmWrap
 from pytouhou.game.background import Background
 from pytouhou.game.enemymanager import EnemyManager
 from pytouhou.opengl.texture import TextureManager
-from pytouhou.game.game import GameState
+from pytouhou.game.game import GameState, Resources
 from pytouhou.game.player import Player
 
 import OpenGL
@@ -60,10 +60,34 @@ def main(path, stage_num):
     glEnableClientState(GL_VERTEX_ARRAY)
     glEnableClientState(GL_TEXTURE_COORD_ARRAY)
 
-    # Load data
-    with open(path, 'rb') as file:
+    texture_manager = TextureManager()
+
+    # Load common data
+    with open(os.path.join(path, 'CM.DAT'), 'rb') as file:
         archive = PBG3.read(file)
-        texture_manager = TextureManager(archive)
+        texture_manager.set_archive(archive)
+        etama_anm_wrappers = (AnmWrapper([Animations.read(BytesIO(archive.extract('etama3.anm')))]),
+                              AnmWrapper([Animations.read(BytesIO(archive.extract('etama4.anm')))]))
+        players_anm_wrappers = (AnmWrapper([Animations.read(BytesIO(archive.extract('player00.anm')))]),
+                                AnmWrapper([Animations.read(BytesIO(archive.extract('player01.anm')))]))
+        effects_anm_wrapper = AnmWrapper([Animations.read(BytesIO(archive.extract('eff00.anm')))])
+
+        for anm_wrapper in etama_anm_wrappers:
+            texture_manager.preload(anm_wrapper)
+
+        for anm_wrapper in players_anm_wrappers:
+            texture_manager.preload(anm_wrapper)
+
+        texture_manager.preload(effects_anm_wrapper)
+
+        resources = Resources(etama_anm_wrappers, players_anm_wrappers, effects_anm_wrapper)
+
+    game_state = GameState(resources, [Player()], stage_num, 3, 16)
+
+    # Load stage data
+    with open(os.path.join(path, 'ST.DAT'), 'rb') as file:
+        archive = PBG3.read(file)
+        texture_manager.set_archive(archive)
 
         stage = Stage.read(BytesIO(archive.extract('stage%d.std' % stage_num)), stage_num)
 
@@ -76,92 +100,92 @@ def main(path, stage_num):
             pass
         else:
             anims.append(enemies2_anim)
-        enemy_manager = EnemyManager(stage, AnmWrapper(anims), ecl, GameState([Player()], stage_num, 0, 16))
+        enemy_manager = EnemyManager(stage, AnmWrapper(anims), ecl, game_state)
         texture_manager.preload(anims)
 
         background_anim = Animations.read(BytesIO(archive.extract('stg%dbg.anm' % stage_num)))
         background = Background(stage, AnmWrapper((background_anim,)))
         texture_manager.preload((background_anim,))
 
-        print(enemy_manager.stage.name)
+    print(enemy_manager.stage.name)
 
-        frame = 0
+    frame = 0
 
-        # Main loop
-        clock = pygame.time.Clock()
-        while True:
-            # Check events
-            for event in pygame.event.get():
-                if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key in (pygame.K_ESCAPE, pygame.K_q)):
-                    sys.exit(0)
-                elif event.type == pygame.KEYDOWN:
-                    if event.key == pygame.K_RETURN and event.mod & pygame.KMOD_ALT:
-                        pygame.display.toggle_fullscreen()
+    # Main loop
+    clock = pygame.time.Clock()
+    while True:
+        # Check events
+        for event in pygame.event.get():
+            if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key in (pygame.K_ESCAPE, pygame.K_q)):
+                sys.exit(0)
+            elif event.type == pygame.KEYDOWN:
+                if event.key == pygame.K_RETURN and event.mod & pygame.KMOD_ALT:
+                    pygame.display.toggle_fullscreen()
 
-            # Update game
-            enemy_manager.update(frame)
-            background.update(frame)
+        # Update game
+        enemy_manager.update(frame)
+        background.update(frame)
 
-            frame += 1
+        frame += 1
 
-            # Draw everything
+        # Draw everything
 #            glClearColor(0.0, 0.0, 1.0, 0)
-            glClear(GL_DEPTH_BUFFER_BIT)
+        glClear(GL_DEPTH_BUFFER_BIT)
 
-            fog_b, fog_g, fog_r, _, fog_start, fog_end = background.fog_interpolator.values
-            x, y, z = background.position_interpolator.values
-            dx, dy, dz = background.position2_interpolator.values
+        fog_b, fog_g, fog_r, _, fog_start, fog_end = background.fog_interpolator.values
+        x, y, z = background.position_interpolator.values
+        dx, dy, dz = background.position2_interpolator.values
 
-            glFogi(GL_FOG_MODE, GL_LINEAR)
-            glFogf(GL_FOG_START, fog_start)
-            glFogf(GL_FOG_END,  fog_end)
-            glFogfv(GL_FOG_COLOR, (fog_r / 255., fog_g / 255., fog_b / 255., 1.))
+        glFogi(GL_FOG_MODE, GL_LINEAR)
+        glFogf(GL_FOG_START, fog_start)
+        glFogf(GL_FOG_END,  fog_end)
+        glFogfv(GL_FOG_COLOR, (fog_r / 255., fog_g / 255., fog_b / 255., 1.))
 
-            #TODO
-            glMatrixMode(GL_MODELVIEW)
-            glLoadIdentity()
-            # Some explanations on the magic constants:
-            # 192. = 384. / 2. = width / 2.
-            # 224. = 448. / 2. = height / 2.
-            # 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2))
-            # This is so that objects on the (O, x, y) plane use pixel coordinates
-            gluLookAt(192., 224., - 835.979370 * dz,
-                      192. + dx, 224. - dy, 0., 0., -1., 0.)
-            glTranslatef(-x, -y, -z)
+        #TODO
+        glMatrixMode(GL_MODELVIEW)
+        glLoadIdentity()
+        # Some explanations on the magic constants:
+        # 192. = 384. / 2. = width / 2.
+        # 224. = 448. / 2. = height / 2.
+        # 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2))
+        # This is so that objects on the (O, x, y) plane use pixel coordinates
+        gluLookAt(192., 224., - 835.979370 * dz,
+                  192. + dx, 224. - dy, 0., 0., -1., 0.)
+        glTranslatef(-x, -y, -z)
 
-            glEnable(GL_DEPTH_TEST)
-            for (texture_key, blendfunc), (nb_vertices, vertices, uvs, colors) in background.objects_by_texture.items():
-                glBlendFunc(GL_SRC_ALPHA, {0: GL_ONE_MINUS_SRC_ALPHA, 1: GL_ONE}[blendfunc])
-                glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
-                glVertexPointer(3, GL_FLOAT, 0, vertices)
-                glTexCoordPointer(2, GL_FLOAT, 0, uvs)
-                glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors)
-                glDrawArrays(GL_QUADS, 0, nb_vertices)
-            glDisable(GL_DEPTH_TEST)
+        glEnable(GL_DEPTH_TEST)
+        for (texture_key, blendfunc), (nb_vertices, vertices, uvs, colors) in background.objects_by_texture.items():
+            glBlendFunc(GL_SRC_ALPHA, {0: GL_ONE_MINUS_SRC_ALPHA, 1: GL_ONE}[blendfunc])
+            glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
+            glVertexPointer(3, GL_FLOAT, 0, vertices)
+            glTexCoordPointer(2, GL_FLOAT, 0, uvs)
+            glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors)
+            glDrawArrays(GL_QUADS, 0, nb_vertices)
+        glDisable(GL_DEPTH_TEST)
 
-            #TODO
-            glMatrixMode(GL_MODELVIEW)
-            glLoadIdentity()
-            # Some explanations on the magic constants:
-            # 192. = 384. / 2. = width / 2.
-            # 224. = 448. / 2. = height / 2.
-            # 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2))
-            # This is so that objects on the (O, x, y) plane use pixel coordinates
-            gluLookAt(192., 224., - 835.979370,
-                      192., 224., 0., 0., -1., 0.)
+        #TODO
+        glMatrixMode(GL_MODELVIEW)
+        glLoadIdentity()
+        # Some explanations on the magic constants:
+        # 192. = 384. / 2. = width / 2.
+        # 224. = 448. / 2. = height / 2.
+        # 835.979370 = 224./math.tan(math.radians(15)) = (height/2.)/math.tan(math.radians(fov/2))
+        # This is so that objects on the (O, x, y) plane use pixel coordinates
+        gluLookAt(192., 224., - 835.979370,
+                  192., 224., 0., 0., -1., 0.)
 
-            glDisable(GL_FOG)
-            for (texture_key, blendfunc), (nb_vertices, vertices, uvs, colors) in enemy_manager.objects_by_texture.items():
-                glBlendFunc(GL_SRC_ALPHA, {0: GL_ONE_MINUS_SRC_ALPHA, 1: GL_ONE}[blendfunc])
-                glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
-                glVertexPointer(3, GL_FLOAT, 0, vertices)
-                glTexCoordPointer(2, GL_FLOAT, 0, uvs)
-                glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors)
-                glDrawArrays(GL_QUADS, 0, nb_vertices)
-            glEnable(GL_FOG)
+        glDisable(GL_FOG)
+        for (texture_key, blendfunc), (nb_vertices, vertices, uvs, colors) in enemy_manager.objects_by_texture.items():
+            glBlendFunc(GL_SRC_ALPHA, {0: GL_ONE_MINUS_SRC_ALPHA, 1: GL_ONE}[blendfunc])
+            glBindTexture(GL_TEXTURE_2D, texture_manager[texture_key])
+            glVertexPointer(3, GL_FLOAT, 0, vertices)
+            glTexCoordPointer(2, GL_FLOAT, 0, uvs)
+            glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors)
+            glDrawArrays(GL_QUADS, 0, nb_vertices)
+        glEnable(GL_FOG)
 
-            pygame.display.flip()
-            clock.tick(120)
+        pygame.display.flip()
+        clock.tick(120)
 
 
 
@@ -169,7 +193,7 @@ try:
     file_path, stage_num = sys.argv[1:]
     stage_num = int(stage_num)
 except ValueError:
-    print('Usage: %s std_dat_path stage_num' % sys.argv[0])
+    print('Usage: %s game_dir_path stage_num' % sys.argv[0])
 else:
     main(file_path, stage_num)
 
--- a/pytouhou/game/enemymanager.py
+++ b/pytouhou/game/enemymanager.py
@@ -21,6 +21,7 @@ from pytouhou.utils.interpolator import 
 from pytouhou.vm.eclrunner import ECLRunner
 from pytouhou.vm.anmrunner import ANMRunner
 from pytouhou.game.sprite import Sprite
+from pytouhou.game.bullet import Bullet
 from math import cos, sin, atan2, pi
 
 
@@ -42,7 +43,7 @@ class Enemy(object):
         self.touchable = True
         self.damageable = True
         self.death_flags = 0
-        self.pending_bullets = []
+        self.bullets = []
         self.extended_bullet_attributes = (0, 0, 0, 0, 0., 0., 0., 0.)
         self.bullet_attributes = None
         self.bullet_launch_offset = (0, 0)
@@ -56,6 +57,7 @@ class Enemy(object):
         self.automatic_orientation = False
 
         self.bullet_launch_interval = 0
+        self.bullet_launch_timer = 0
         self.delay_attack = False
 
         self.death_anim = None
@@ -72,10 +74,10 @@ class Enemy(object):
         self.screen_box = None
 
 
-    def set_bullet_attributes(self, type_, bullet_anim, launch_anim,
+    def set_bullet_attributes(self, type_, anim, sprite_idx_offset,
                               bullets_per_shot, number_of_shots, speed, speed2,
                               launch_angle, angle, flags):
-        self.bullet_attributes = (type_, bullet_anim, launch_anim, bullets_per_shot,
+        self.bullet_attributes = (type_, anim, sprite_idx_offset, bullets_per_shot,
                                   number_of_shots, speed, speed2, launch_angle,
                                   angle, flags)
         if not self.delay_attack:
@@ -83,16 +85,32 @@ class Enemy(object):
 
 
     def fire(self):
-        (type_, bullet_anim, launch_anim, bullets_per_shot, number_of_shots,
+        (type_, anim, sprite_idx_offset, bullets_per_shot, number_of_shots,
          speed, speed2, launch_angle, angle, flags) = self.bullet_attributes
+
+        self.bullet_launch_timer = 0
+
+        player = self.select_player()
+
         if type_ in (67, 69, 71):
-            launch_angle += self.get_player_angle()
+            launch_angle += self.get_player_angle(player)
         if type_ in (69, 70, 71):
             angle = 2. * pi / bullets_per_shot
         if type_ == 71:
             launch_angle += pi / bullets_per_shot
-        #TODO
-        pass
+
+        launch_angle -= angle * (bullets_per_shot - 1) / 2.
+
+        for shot_nb in range(number_of_shots):
+            shot_speed = speed if shot_nb == 0 else speed + (speed2 - speed) * float(shot_nb) / float(number_of_shots)
+            bullet_angle = launch_angle
+            for bullet_nb in range(bullets_per_shot):
+                self.bullets.append(Bullet((self.x, self.y),
+                                           anim, sprite_idx_offset,
+                                           bullet_angle, shot_speed,
+                                           self.extended_bullet_attributes,
+                                           flags, player, self._game_state))
+                bullet_angle += angle
 
 
     def select_player(self, players=None):
@@ -156,10 +174,14 @@ class Enemy(object):
 
 
     def get_objects_by_texture(self):
+        objects_by_texture = {}
+
+        for bullet in self.bullets:
+            objects_by_texture.update(bullet.get_objects_by_texture())
+
         if not self._sprite:
-            return {}
+            return objects_by_texture
 
-        objects_by_texture = {}
         key = self._sprite.anm.first_name, self._sprite.anm.secondary_name
         key = (key, self._sprite.blendfunc)
         if not key in objects_by_texture:
@@ -231,6 +253,17 @@ class Enemy(object):
                     angle_base = self.angle if self.automatic_orientation else 0.
                     self._sprite.update_vertices_uvs_colors(angle_base=angle_base)
 
+
+        if self.bullet_launch_interval != 0:
+            self.bullet_launch_timer += 1
+            if self.bullet_launch_timer == self.bullet_launch_interval:
+                self.fire()
+
+
+        for bullet in self.bullets:
+            bullet.update()
+
+
         self.frame += 1
 
 
@@ -245,6 +278,7 @@ class EnemyManager(object):
         self.objects_by_texture = {}
         self.enemies = []
         self.processes = []
+        self.bullets = []
 
         # Populate main
         for frame, sub, instr_type, args in ecl.main:
@@ -280,6 +314,14 @@ class EnemyManager(object):
         # Update enemies
         for enemy in self.enemies:
             enemy.update()
+            for bullet in enemy.bullets:
+                if bullet._launched:
+                    enemy.bullets.remove(bullet)
+                self.bullets.append(bullet)
+
+        # Update bullets
+        for bullet in self.bullets:
+            bullet.update()
 
         # Filter out non-visible enemies
         visible_enemies = [enemy for enemy in self.enemies if enemy.is_visible(384, 448)] #TODO
@@ -292,6 +334,12 @@ class EnemyManager(object):
                 enemy._removed = True
                 self.enemies.remove(enemy)
 
+        # Filter out-of-scren bullets
+        for bullet in self.bullets:
+            if not bullet.is_visible(384, 448):
+                self.bullets.remove(bullet)
+
+
         #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
@@ -299,13 +347,22 @@ class EnemyManager(object):
         # Add enemies to vertices/uvs
         self.objects_by_texture = {}
         for enemy in visible_enemies:
-            if enemy.is_visible(384, 448): #TODO
-                for key, (count, vertices, uvs, colors) in enemy.get_objects_by_texture().items():
-                    if not key in self.objects_by_texture:
-                        self.objects_by_texture[key] = (0, [], [], [])
-                    self.objects_by_texture[key][1].extend(vertices)
-                    self.objects_by_texture[key][2].extend(uvs)
-                    self.objects_by_texture[key][3].extend(colors)
+            for key, (count, vertices, uvs, colors) in enemy.get_objects_by_texture().items():
+                if not key in self.objects_by_texture:
+                    self.objects_by_texture[key] = (0, [], [], [])
+                self.objects_by_texture[key][1].extend(vertices)
+                self.objects_by_texture[key][2].extend(uvs)
+                self.objects_by_texture[key][3].extend(colors)
+
+        # Add bullets to vertices/uvs
+        for bullet in self.bullets:
+            for key, (count, vertices, uvs, colors) in bullet.get_objects_by_texture().items():
+                if not key in self.objects_by_texture:
+                    self.objects_by_texture[key] = (0, [], [], [])
+                self.objects_by_texture[key][1].extend(vertices)
+                self.objects_by_texture[key][2].extend(uvs)
+                self.objects_by_texture[key][3].extend(colors)
+
         for key, (nb_vertices, vertices, uvs, colors) in self.objects_by_texture.items():
             nb_vertices = len(vertices)
             vertices = pack('f' * (3 * nb_vertices), *chain(*vertices))
--- a/pytouhou/game/game.py
+++ b/pytouhou/game/game.py
@@ -16,8 +16,10 @@
 from pytouhou.utils.random import Random
 
 class GameState(object):
-    __slots__ = ('players', 'rank', 'difficulty', 'frame', 'stage', 'boss', 'prng')
-    def __init__(self, players, stage, rank, difficulty):
+    __slots__ = ('resources', 'players', 'rank', 'difficulty', 'frame', 'stage', 'boss', 'prng')
+    def __init__(self, resources, players, stage, rank, difficulty):
+        self.resources = resources
+
         self.stage = stage
         self.players = players
         self.rank = rank
@@ -25,3 +27,11 @@ class GameState(object):
         self.boss = None
         self.prng = Random()
         self.frame = 0
+
+
+class Resources(object):
+    def __init__(self, etama_anm_wrappers, players_anm_wrappers, effects_anm_wrapper):
+        self.etama_anm_wrappers = etama_anm_wrappers
+        self.players_anm_wrappers = players_anm_wrappers
+        self.effects_anm_wrapper = effects_anm_wrapper
+
--- a/pytouhou/opengl/texture.py
+++ b/pytouhou/opengl/texture.py
@@ -23,7 +23,7 @@ from OpenGL.GLU import *
 
 
 class TextureManager(object):
-    def __init__(self, archive):
+    def __init__(self, archive=None):
         self.archive = archive
         self.textures = {}
 
@@ -33,7 +33,12 @@ class TextureManager(object):
         return self.textures[key]
 
 
-    def preload(self, anms):
+    def preload(self, anm_wrapper):
+        try:
+            anms = anm_wrapper.anm_files
+        except AttributeError:
+            anms = anm_wrapper
+
         for anm in anms:
             key = anm.first_name, anm.secondary_name
             texture = self[key]
--- a/pytouhou/vm/eclrunner.py
+++ b/pytouhou/vm/eclrunner.py
@@ -495,6 +495,38 @@ class ECLRunner(object):
                                           flags)
 
 
+    @instruction(74)
+    def set_bullet_attributes6(self, anim, sprite_idx_offset, bullets_per_shot,
+                               number_of_shots, speed, speed2, launch_angle,
+                               angle, flags):
+        #TODO
+        self._enemy.set_bullet_attributes(74, anim,
+                                          self._getval(sprite_idx_offset),
+                                          self._getval(bullets_per_shot),
+                                          self._getval(number_of_shots),
+                                          self._getval(speed),
+                                          self._getval(speed2),
+                                          self._getval(launch_angle),
+                                          self._getval(angle),
+                                          flags)
+
+
+    @instruction(75)
+    def set_bullet_attributes6(self, anim, sprite_idx_offset, bullets_per_shot,
+                               number_of_shots, speed, speed2, launch_angle,
+                               angle, flags):
+        #TODO
+        self._enemy.set_bullet_attributes(75, anim,
+                                          self._getval(sprite_idx_offset),
+                                          self._getval(bullets_per_shot),
+                                          self._getval(number_of_shots),
+                                          self._getval(speed),
+                                          self._getval(speed2),
+                                          self._getval(launch_angle),
+                                          self._getval(angle),
+                                          flags)
+
+
     @instruction(76)
     def set_bullet_interval(self, value):
         self._enemy.bullet_launch_interval = value