# HG changeset patch # User Thibaut Girka # Date 1328481715 -3600 # Node ID f037bca24f2db23b2ab9b98477da7a9abe3c7af6 # Parent 595b227886b1feee74beab6655bb020373b3de9e Partially implement lasers. “Launch animations”/“energy circles” are missing, aswell as collision and grazing. diff --git a/pytouhou/formats/ecl.py b/pytouhou/formats/ecl.py --- a/pytouhou/formats/ecl.py +++ b/pytouhou/formats/ecl.py @@ -108,7 +108,7 @@ class ECL(object): 86: ('hhffffffiiiiii', 'laser2'), 87: ('i', 'set_upcoming_id'), 88: ('if','alter_laser_angle'), - 90: ('iiii', 'translate_laser'), + 90: ('ifff', 'reposition_laser'), 92: ('i', 'cancel_laser'), 93: ('hhs', 'set_spellcard'), 94: ('', 'end_spellcard'), diff --git a/pytouhou/game/enemy.py b/pytouhou/game/enemy.py --- a/pytouhou/game/enemy.py +++ b/pytouhou/game/enemy.py @@ -17,6 +17,7 @@ from pytouhou.utils.interpolator import from pytouhou.vm.anmrunner import ANMRunner from pytouhou.game.sprite import Sprite from pytouhou.game.bullet import Bullet +from pytouhou.game.laser import Laser from pytouhou.game.effect import Effect from math import cos, sin, atan2, pi @@ -46,6 +47,8 @@ class Enemy(object): self.boss = False self.difficulty_coeffs = (-.5, .5, 0, 0, 0, 0) self.extended_bullet_attributes = (0, 0, 0, 0, 0., 0., 0., 0.) + self.current_laser_id = 0 + self.laser_by_id = {} self.bullet_attributes = None self.bullet_launch_offset = (0, 0) self.death_callback = -1 @@ -161,6 +164,22 @@ class Enemy(object): bullet_angle += angle + def new_laser(self, variant, laser_type, sprite_idx_offset, angle, speed, + start_offset, end_offset, max_length, width, + start_duration, duration, end_duration, + grazing_delay, grazing_extra_duration, unknown): + launch_pos = self.x, self.y + if variant == 86: + angle += self.get_player_angle(self.select_player(), launch_pos) + laser = Laser(launch_pos, self._game.laser_types[laser_type], + sprite_idx_offset, angle, speed, + start_offset, end_offset, max_length, width, + start_duration, duration, end_duration, grazing_delay, + grazing_extra_duration, self._game) + self._game.lasers.append(laser) + self.laser_by_id[self.current_laser_id] = laser + + def select_player(self, players=None): return (players or self._game.players)[0] #TODO diff --git a/pytouhou/game/game.py b/pytouhou/game/game.py --- a/pytouhou/game/game.py +++ b/pytouhou/game/game.py @@ -27,7 +27,7 @@ from pytouhou.game.effect import Particl class Game(object): def __init__(self, resource_loader, players, stage, rank, difficulty, - bullet_types, item_types, + bullet_types, laser_types, item_types, nb_bullets_max=None, width=384, height=448, prng=None): self.resource_loader = resource_loader @@ -35,12 +35,14 @@ class Game(object): self.nb_bullets_max = nb_bullets_max self.bullet_types = bullet_types + self.laser_types = laser_types self.item_types = item_types self.players = players self.enemies = [] self.effects = [] self.bullets = [] + self.lasers = [] self.cancelled_bullets = [] self.players_bullets = [] self.items = [] @@ -113,7 +115,12 @@ class Game(object): def change_bullets_into_star_items(self): player = self.players[0] #TODO item_type = self.item_types[6] - self.items.extend(Item((bullet.x, bullet.y), 6, item_type, self, player=player) for bullet in self.bullets) + self.items.extend(Item((bullet.x, bullet.y), 6, item_type, self, player=player) + for bullet in self.bullets) + for laser in self.lasers: + self.items.extend(Item(pos, 6, item_type, self, player=player) + for pos in laser.get_bullets_pos()) + laser.cancel() self.bullets = [] @@ -155,6 +162,8 @@ class Game(object): self.update_enemies() # Pri 9 self.update_effects() # Pri 10 self.update_bullets() # Pri 11 + for laser in self.lasers: #TODO: what priority is it? + laser.update() # Pri 12 is HUD # 4. Cleaning @@ -273,6 +282,9 @@ class Game(object): self.cancelled_bullets = [bullet for bullet in self.cancelled_bullets if not bullet._removed] + # Filter “timed-out” lasers + self.lasers = [laser for laser in self.lasers if not laser._removed] + # Filter out-of-scren items items = [] for item in self.items: diff --git a/pytouhou/game/laser.py b/pytouhou/game/laser.py new file mode 100644 --- /dev/null +++ b/pytouhou/game/laser.py @@ -0,0 +1,121 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2012 Thibaut Girka +## +## 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 math import cos, sin, pi + +from pytouhou.vm.anmrunner import ANMRunner +from pytouhou.game.sprite import Sprite + + +STARTING, STARTED, STOPPING = range(3) + + +class Laser(object): + def __init__(self, pos, laser_type, sprite_idx_offset, + angle, speed, start_offset, end_offset, max_length, width, + start_duration, duration, stop_duration, + grazing_delay, grazing_extra_duration, + game): + self._game = game + #TODO: aux sprite + self._sprite = None + self._anmrunner = None + self._removed = False + self._laser_type = laser_type + self.state = STARTING + + #TODO: hitbox + + self.frame = 0 + self.start_duration = start_duration + self.duration = duration + self.stop_duration = stop_duration + self.grazing_delay = grazing_delay + self.grazing_extra_duration = grazing_extra_duration + + self.sprite_idx_offset = sprite_idx_offset + self.x, self.y = pos + self.angle = angle + self.speed = speed + self.start_offset = start_offset + self.end_offset = end_offset + self.max_length = max_length + self.width = width + + self.set_anim() + + + def set_anim(self, sprite_idx_offset=None): + if sprite_idx_offset is not None: + self.sprite_idx_offset = sprite_idx_offset + + lt = self._laser_type + self._sprite = Sprite() + self._sprite.angle = self.angle + self._anmrunner = ANMRunner(lt.anm_wrapper, lt.anim_index, + self._sprite, self.sprite_idx_offset) + self._anmrunner.run_frame() + + + def get_bullets_pos(self): + #TODO: check + offset = self.start_offset + length = min(self.end_offset - self.start_offset, self.max_length) + dx, dy = cos(self.angle), sin(self.angle) + while 0 <= offset - self.start_offset <= length: + yield (self.x + offset * dx, self.y + offset * dy) + offset += 48. + + + def cancel(self): + if self.state != STOPPING: + self.frame = 0 + self.state = STOPPING + + + def update(self): + if self._anmrunner is not None and not self._anmrunner.run_frame(): + self._anmrunner = None + + self.end_offset += self.speed + + length = min(self.end_offset - self.start_offset, self.max_length) # TODO + if self.state == STARTING: + if self.frame == self.start_duration: + self.frame = 0 + self.state = STARTED + else: + width = self.width * float(self.frame) / self.start_duration #TODO + if self.state == STARTED: + width = self.width #TODO + if self.frame == self.duration: + self.frame = 0 + self.state = STOPPING + if self.state == STOPPING: + if self.frame == self.stop_duration: + width = 0. + self._removed = True + else: + width = self.width * (1. - float(self.frame) / self.stop_duration) #TODO + + self._sprite.allow_dest_offset = True + self._sprite.dest_offset = (0., self.end_offset - length / 2., 0.) + self._sprite.width_override = width or 0.01 #TODO + self._sprite.height_override = length or 0.01 #TODO + + self._sprite.update_orientation(pi/2. - self.angle, True) + self._sprite._changed = True #TODO + + self.frame += 1 + diff --git a/pytouhou/game/lasertype.py b/pytouhou/game/lasertype.py new file mode 100644 --- /dev/null +++ b/pytouhou/game/lasertype.py @@ -0,0 +1,4 @@ +class LaserType(object): + def __init__(self, anm_wrapper, anim_index): + self.anm_wrapper = anm_wrapper + self.anim_index = anim_index diff --git a/pytouhou/game/player.py b/pytouhou/game/player.py --- a/pytouhou/game/player.py +++ b/pytouhou/game/player.py @@ -239,6 +239,8 @@ class Player(object): if time > 30: for bullet in self._game.bullets: bullet.cancel() + for laser in self._game.lasers: + laser.cancel() if time > 90: # start the bullet hell again self.death_time = 0 diff --git a/pytouhou/games/eosd.py b/pytouhou/games/eosd.py --- a/pytouhou/games/eosd.py +++ b/pytouhou/games/eosd.py @@ -16,6 +16,7 @@ from pytouhou.utils.interpolator import from pytouhou.game.game import Game from pytouhou.game.bullettype import BulletType +from pytouhou.game.lasertype import LaserType from pytouhou.game.itemtype import ItemType from pytouhou.game.player import Player from pytouhou.game.orb import Orb @@ -27,33 +28,43 @@ SQ2 = 2. ** 0.5 / 2. class EoSDGame(Game): - def __init__(self, resource_loader, player_states, stage, rank, difficulty, **kwargs): - etama3 = resource_loader.get_anm_wrapper(('etama3.anm',)) - etama4 = resource_loader.get_anm_wrapper(('etama4.anm',)) - bullet_types = [BulletType(etama3, 0, 11, 14, 15, 16, hitbox_size=4), - BulletType(etama3, 1, 12, 17, 18, 19, hitbox_size=6), - BulletType(etama3, 2, 12, 17, 18, 19, hitbox_size=4), - BulletType(etama3, 3, 12, 17, 18, 19, hitbox_size=6), - BulletType(etama3, 4, 12, 17, 18, 19, hitbox_size=5), - BulletType(etama3, 5, 12, 17, 18, 19, hitbox_size=4), - BulletType(etama3, 6, 13, 20, 20, 20, hitbox_size=16), - BulletType(etama3, 7, 13, 20, 20, 20, hitbox_size=11), - BulletType(etama3, 8, 13, 20, 20, 20, hitbox_size=9), - BulletType(etama4, 0, 1, 2, 2, 2, hitbox_size=32)] + def __init__(self, resource_loader, player_states, stage, rank, difficulty, + bullet_types=None, laser_types=None, item_types=None, + nb_bullets_max=640, width=384, height=448, prng=None): - 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 + if not bullet_types: + etama3 = resource_loader.get_anm_wrapper(('etama3.anm',)) + etama4 = resource_loader.get_anm_wrapper(('etama4.anm',)) + bullet_types = [BulletType(etama3, 0, 11, 14, 15, 16, hitbox_size=4), + BulletType(etama3, 1, 12, 17, 18, 19, hitbox_size=6), + BulletType(etama3, 2, 12, 17, 18, 19, hitbox_size=4), + BulletType(etama3, 3, 12, 17, 18, 19, hitbox_size=6), + BulletType(etama3, 4, 12, 17, 18, 19, hitbox_size=5), + BulletType(etama3, 5, 12, 17, 18, 19, hitbox_size=4), + BulletType(etama3, 6, 13, 20, 20, 20, hitbox_size=16), + BulletType(etama3, 7, 13, 20, 20, 20, hitbox_size=11), + BulletType(etama3, 8, 13, 20, 20, 20, hitbox_size=9), + BulletType(etama4, 0, 1, 2, 2, 2, hitbox_size=32)] + + if not laser_types: + laser_types = [LaserType(etama3, 9), + LaserType(etama3, 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 characters = resource_loader.get_eosd_characters() players = [EoSDPlayer(state, self, resource_loader, characters[state.character]) for state in player_states] Game.__init__(self, resource_loader, players, stage, rank, difficulty, - bullet_types, item_types, nb_bullets_max=640, **kwargs) + bullet_types, laser_types, item_types, nb_bullets_max, + width, height, prng) diff --git a/pytouhou/games/pcb.py b/pytouhou/games/pcb.py --- a/pytouhou/games/pcb.py +++ b/pytouhou/games/pcb.py @@ -16,6 +16,7 @@ from pytouhou.utils.interpolator import from pytouhou.game.game import Game from pytouhou.game.bullettype import BulletType +from pytouhou.game.bullettype import LaserType from pytouhou.game.itemtype import ItemType from pytouhou.game.player import Player from pytouhou.game.orb import Orb @@ -24,32 +25,40 @@ from math import pi class PCBGame(Game): - def __init__(self, resource_loader, player_states, stage, rank, difficulty, **kwargs): - etama3 = resource_loader.get_anm_wrapper(('etama3.anm',)) - etama4 = resource_loader.get_anm_wrapper(('etama4.anm',)) - bullet_types = [BulletType(etama3, 0, 11, 14, 15, 16, hitbox_size=4), - BulletType(etama3, 1, 12, 17, 18, 19, hitbox_size=6), - BulletType(etama3, 2, 12, 17, 18, 19, hitbox_size=4), - BulletType(etama3, 3, 12, 17, 18, 19, hitbox_size=6), - BulletType(etama3, 4, 12, 17, 18, 19, hitbox_size=5), - BulletType(etama3, 5, 12, 17, 18, 19, hitbox_size=4), - BulletType(etama3, 6, 13, 20, 20, 20, hitbox_size=16), - BulletType(etama3, 7, 13, 20, 20, 20, hitbox_size=11), - BulletType(etama3, 8, 13, 20, 20, 20, hitbox_size=9), - BulletType(etama4, 0, 1, 2, 2, 2, hitbox_size=32)] + def __init__(self, resource_loader, player_states, stage, rank, difficulty, + bullet_types=None, laser_types=None, item_types=None, + nb_bullets_max=640, width=384, height=448, prng=None): + if not bullet_types: + etama3 = resource_loader.get_anm_wrapper(('etama3.anm',)) + etama4 = resource_loader.get_anm_wrapper(('etama4.anm',)) + bullet_types = [BulletType(etama3, 0, 11, 14, 15, 16, hitbox_size=4), + BulletType(etama3, 1, 12, 17, 18, 19, hitbox_size=6), + BulletType(etama3, 2, 12, 17, 18, 19, hitbox_size=4), + BulletType(etama3, 3, 12, 17, 18, 19, hitbox_size=6), + BulletType(etama3, 4, 12, 17, 18, 19, hitbox_size=5), + BulletType(etama3, 5, 12, 17, 18, 19, hitbox_size=4), + BulletType(etama3, 6, 13, 20, 20, 20, hitbox_size=16), + BulletType(etama3, 7, 13, 20, 20, 20, hitbox_size=11), + BulletType(etama3, 8, 13, 20, 20, 20, hitbox_size=9), + BulletType(etama4, 0, 1, 2, 2, 2, hitbox_size=32)] - 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 + if not laser_types: + laser_types = [] #TODO + + 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 players = [PCBPlayer(state, self, resource_loader) for state in player_states] Game.__init__(self, resource_loader, players, stage, rank, difficulty, - bullet_types, item_types, nb_bullets_max=640, **kwargs) + bullet_types, laser_types, item_types, nb_bullets_max, + width, height, prng) diff --git a/pytouhou/ui/gamerenderer.pyx b/pytouhou/ui/gamerenderer.pyx --- a/pytouhou/ui/gamerenderer.pyx +++ b/pytouhou/ui/gamerenderer.pyx @@ -86,7 +86,7 @@ cdef class GameRenderer(Renderer): self.render_elements(chain(game.players_bullets, game.players, *(player.objects() for player in game.players))) - self.render_elements(chain(game.bullets, game.cancelled_bullets, game.items)) + self.render_elements(chain(game.bullets, game.lasers, game.cancelled_bullets, game.items)) #TODO: display item indicators glEnable(GL_FOG) diff --git a/pytouhou/ui/sprite.pyx b/pytouhou/ui/sprite.pyx --- a/pytouhou/ui/sprite.pyx +++ b/pytouhou/ui/sprite.pyx @@ -38,6 +38,8 @@ cpdef object get_sprite_rendering_data(o if sprite.mirrored: vertmat.flip() + if sprite.allow_dest_offset: + vertmat.translate(sprite.dest_offset[0], sprite.dest_offset[1], sprite.dest_offset[2]) rx, ry, rz = sprite.rotations_3d if sprite.automatic_orientation: rz += pi/2. - sprite.angle @@ -51,8 +53,6 @@ cpdef object get_sprite_rendering_data(o vertmat.rotate_y(ry) if rz: vertmat.rotate_z(-rz) #TODO: minus, really? - if sprite.allow_dest_offset: - vertmat.translate(sprite.dest_offset[0], sprite.dest_offset[1], sprite.dest_offset[2]) if sprite.corner_relative_placement: # Reposition vertmat.translate(width / 2., height / 2., 0.) diff --git a/pytouhou/vm/eclrunner.py b/pytouhou/vm/eclrunner.py --- a/pytouhou/vm/eclrunner.py +++ b/pytouhou/vm/eclrunner.py @@ -723,6 +723,63 @@ class ECLRunner(object): self._game.change_bullets_into_star_items() + @instruction(85) + def new_laser(self, laser_type, sprite_idx_offset, angle, speed, + start_offset, end_offset, max_length, width, + start_duration, duration, end_duration, + grazing_delay, grazing_extra_duration, unknown): + self._enemy.new_laser(85, laser_type, sprite_idx_offset, self._getval(angle), speed, + start_offset, end_offset, max_length, width, + start_duration, duration, end_duration, + grazing_delay, grazing_extra_duration, unknown) + + + @instruction(86) + def new_laser_towards_player(self, laser_type, sprite_idx_offset, angle, speed, + start_offset, end_offset, max_length, width, + start_duration, duration, end_duration, + grazing_delay, grazing_extra_duration, unknown): + self._enemy.new_laser(86, laser_type, sprite_idx_offset, self._getval(angle), speed, + start_offset, end_offset, max_length, width, + start_duration, duration, end_duration, + grazing_delay, grazing_extra_duration, unknown) + + + @instruction(87) + def set_upcoming_laser_id(self, laser_id): + self._enemy.current_laser_id = laser_id + + + @instruction(88) + def alter_laser_angle(self, laser_id, delta): + try: + laser = self._enemy.laser_by_id[laser_id] + except KeyError: + pass #TODO + else: + laser.angle += self._getval(delta) + + + @instruction(90) + def reposition_laser(self, laser_id, ox, oy, oz): + try: + laser = self._enemy.laser_by_id[laser_id] + except KeyError: + pass #TODO + else: + laser.x, laser.y = self._enemy.x + ox, self._enemy.y + oy + + + @instruction(92) + def cancel_laser(self, laser_id): + try: + laser = self._enemy.laser_by_id[laser_id] + except KeyError: + pass #TODO + else: + laser.cancel() + + @instruction(93) def set_spellcard(self, unknown, number, name): #TODO: display it on the game.