# HG changeset patch # User Thibaut Girka # Date 1315081378 -7200 # Node ID fc0294c745b67b3cfb30f2d8b712a018d6e44326 # Parent de48213a02bfce539bdc070924636823cb23a20a Basic bullet handling! Clean up as soon as possible :p diff --git a/eclviewer.py b/eclviewer.py --- 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) diff --git a/pytouhou/game/enemymanager.py b/pytouhou/game/enemymanager.py --- 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)) diff --git a/pytouhou/game/game.py b/pytouhou/game/game.py --- 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 + diff --git a/pytouhou/opengl/texture.py b/pytouhou/opengl/texture.py --- 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] diff --git a/pytouhou/vm/eclrunner.py b/pytouhou/vm/eclrunner.py --- 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