Mercurial > touhou
changeset 441:e8dc95a2a287
Make pytouhou.game.enemy an extension type.
| author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
|---|---|
| date | Sat, 10 Aug 2013 19:59:17 +0200 |
| parents | b9d2db93972f |
| children | 6b4c3e250bd6 |
| files | pytouhou/game/enemy.pxd pytouhou/game/enemy.py pytouhou/game/enemy.pyx pytouhou/vm/eclrunner.py |
| diffstat | 4 files changed, 576 insertions(+), 527 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pytouhou/game/enemy.pxd Sat Aug 10 19:59:17 2013 +0200 @@ -0,0 +1,40 @@ +from pytouhou.game.element cimport Element +from pytouhou.utils.interpolator cimport Interpolator + +cdef class Enemy(Element): + cdef public double z, angle, speed, rotation_speed, acceleration + cdef public long _type, bonus_dropped, die_score, frame, life, death_flags, current_laser_id, death_callback, boss_callback, low_life_callback, low_life_trigger, timeout, timeout_callback, remaining_lives, bullet_launch_interval, bullet_launch_timer, death_anim, direction, update_mode + cdef public bint visible, was_visible, touchable, collidable, damageable, boss, automatic_orientation, delay_attack + cdef public tuple difficulty_coeffs, extended_bullet_attributes, bullet_attributes, bullet_launch_offset, movement_dependant_sprites, screen_box + cdef public dict laser_by_id + cdef public list aux_anm + cdef public Interpolator interpolator, speed_interpolator + cdef public object _game, _anms, process + + cdef double[2] hitbox_half_size + + cpdef play_sound(self, index) + cpdef set_hitbox(self, double width, double height) + cpdef set_bullet_attributes(self, type_, anim, sprite_idx_offset, + bullets_per_shot, number_of_shots, speed, speed2, + launch_angle, angle, flags) + cpdef set_bullet_launch_interval(self, long value, unsigned long start=*) + cpdef fire(self, offset=*, bullet_attributes=*, launch_pos=*) + cpdef 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, + offset=*) + cpdef select_player(self, players=*) + cpdef get_player_angle(self, player=*, pos=*) + cpdef set_anim(self, index) + cdef void die_anim(self) + cdef void drop_particles(self, long number, long color) + cpdef set_aux_anm(self, long number, long index) + cpdef set_pos(self, x, y, z) + cpdef move_to(self, duration, x, y, z, formula) + cpdef stop_in(self, duration, formula) + cpdef bint is_visible(self, long screen_width, long screen_height) + cdef void check_collisions(self) + cdef void handle_callbacks(self) + cpdef update(self)
--- a/pytouhou/game/enemy.py Fri Aug 30 14:16:08 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,525 +0,0 @@ -# -*- encoding: utf-8 -*- -## -## Copyright (C) 2011 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 pytouhou.utils.interpolator import Interpolator -from pytouhou.vm.anmrunner import ANMRunner -from pytouhou.game.element import Element -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 -from pytouhou.game.bullet import LAUNCHED - - -class Enemy(Element): - def __init__(self, pos, life, _type, bonus_dropped, die_score, anms, game): - Element.__init__(self) - - self._game = game - self._anms = anms - self._type = _type - - self.process = None - self.visible = True - self.was_visible = False - self.bonus_dropped = bonus_dropped - self.die_score = die_score - - self.frame = 0 - - self.x, self.y, self.z = pos - self.life = 1 if life < 0 else life - self.touchable = True - self.collidable = True - self.damageable = True - self.death_flags = 0 - 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 - self.boss_callback = -1 - self.low_life_callback = -1 - self.low_life_trigger = None - self.timeout = -1 - self.timeout_callback = -1 - self.remaining_lives = 0 - - self.automatic_orientation = False - - self.bullet_launch_interval = 0 - self.bullet_launch_timer = 0 - self.delay_attack = False - - self.death_anim = 0 - self.movement_dependant_sprites = None - self.direction = None - self.interpolator = None #TODO - self.speed_interpolator = None - self.update_mode = 0 - self.angle = 0. - self.speed = 0. - self.rotation_speed = 0. - self.acceleration = 0. - - self.hitbox = (0, 0) - self.hitbox_half_size = (0, 0) - self.screen_box = None - - self.aux_anm = 8 * [None] - - - @property - def objects(self): - return [self] + [anm for anm in self.aux_anm if anm] - - - def play_sound(self, index): - name = { - 5: 'power0', - 6: 'power1', - 7: 'tan00', - 8: 'tan01', - 9: 'tan02', - 14: 'cat00', - 16: 'lazer00', - 17: 'lazer01', - 18: 'enep01', - 22: 'tan00', #XXX - 24: 'tan02', #XXX - 25: 'kira00', - 26: 'kira01', - 27: 'kira02' - }[index] - self._game.sfx_player.play('%s.wav' % name) - - - def set_bullet_attributes(self, type_, anim, sprite_idx_offset, - bullets_per_shot, number_of_shots, speed, speed2, - launch_angle, angle, flags): - - # Apply difficulty-specific modifiers - speed_a, speed_b, nb_a, nb_b, shots_a, shots_b = self.difficulty_coeffs - diff_coeff = self._game.difficulty / 32. - - speed += speed_a * (1. - diff_coeff) + speed_b * diff_coeff - speed2 += (speed_a * (1. - diff_coeff) + speed_b * diff_coeff) / 2. - bullets_per_shot += int(nb_a * (1. - diff_coeff) + nb_b * diff_coeff) - number_of_shots += int(shots_a * (1. - diff_coeff) + shots_b * diff_coeff) - - 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: - self.fire() - - - def set_bullet_launch_interval(self, value, start=0): - # Apply difficulty-specific modifiers: - #TODO: check every value possible! Look around 102h.exe@0x408720 - value -= value * (self._game.difficulty - 16) // 80 - - self.bullet_launch_interval = value - self.bullet_launch_timer = start % value if value else 0 - - - def fire(self, offset=None, bullet_attributes=None, launch_pos=None): - (type_, type_idx, sprite_idx_offset, bullets_per_shot, number_of_shots, - speed, speed2, launch_angle, angle, flags) = bullet_attributes or self.bullet_attributes - - bullet_type = self._game.bullet_types[type_idx] - - if not launch_pos: - ox, oy = offset or self.bullet_launch_offset - launch_pos = self.x + ox, self.y + oy - - if speed < 0.3 and speed != 0.0: - speed = 0.3 - if speed2 < 0.3: - speed2 = 0.3 - - self.bullet_launch_timer = 0 - - player = self.select_player() - - if type_ in (67, 69, 71): - launch_angle += self.get_player_angle(player, launch_pos) - if type_ == 71 and bullets_per_shot % 2 or type_ in (69, 70) and not bullets_per_shot % 2: - launch_angle += pi / bullets_per_shot - if type_ != 75: - launch_angle -= angle * (bullets_per_shot - 1) / 2. - - bullets = self._game.bullets - nb_bullets_max = self._game.nb_bullets_max - - 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 - if type_ in (69, 70, 71, 74): - launch_angle += angle - for bullet_nb in range(bullets_per_shot): - if nb_bullets_max is not None and len(bullets) == nb_bullets_max: - break - - if type_ == 75: # 102h.exe@0x4138cf - bullet_angle = self._game.prng.rand_double() * (launch_angle - angle) + angle - if type_ in (74, 75): # 102h.exe@0x4138cf - shot_speed = self._game.prng.rand_double() * (speed - speed2) + speed2 - bullets.append(Bullet(launch_pos, bullet_type, sprite_idx_offset, - bullet_angle, shot_speed, - self.extended_bullet_attributes, - flags, player, self._game)) - - if type_ in (69, 70, 71, 74): - bullet_angle += 2. * pi / bullets_per_shot - else: - 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, - offset=None): - ox, oy = offset or self.bullet_launch_offset - launch_pos = self.x + ox, self.y + oy - 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 - - - def get_player_angle(self, player=None, pos=None): - player = player or self.select_player() - x, y = pos or (self.x, self.y) - return atan2(player.y - y, player.x - x) - - - def set_anim(self, index): - entry = 0 if index in self._anms[0].scripts else 1 - self.sprite = Sprite() - self.anmrunner = ANMRunner(self._anms[entry], index, self.sprite) - - - def die_anim(self): - anim = {0: 3, 1: 4, 2: 5}[self.death_anim % 256] # The TB is wanted, if index isn’t in these values the original game crashs. - self._game.new_effect((self.x, self.y), anim) - self._game.sfx_player.play('enep00.wav') - - - def drop_particles(self, number, color): - if color == 0: - if self._game.stage in [1, 2, 7]: - color = 3 - color += 9 - for i in range(number): - self._game.new_particle((self.x, self.y), color, 256) #TODO: find the real size. - - - 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): - self.x, self.y = x, y - self.update_mode = 1 - self.interpolator = Interpolator((x, y), self._game.frame) - - - def move_to(self, duration, x, y, z, formula): - frame = self._game.frame - self.speed_interpolator = None - self.update_mode = 1 - self.interpolator = Interpolator((self.x, self.y), frame, - (x, y), frame + duration - 1, - formula) - - self.angle = atan2(y - self.y, x - self.x) - - - def stop_in(self, duration, formula): - frame = self._game.frame - self.interpolator = None - self.update_mode = 1 - self.speed_interpolator = Interpolator((self.speed,), frame, - (0.,), frame + duration - 1, - formula) - - - def is_visible(self, screen_width, screen_height): - if self.sprite: - tx, ty, tw, th = self.sprite.texcoords - if self.sprite.corner_relative_placement: - raise Exception #TODO - else: - tx, ty, tw, th = 0., 0., 0., 0. - - x, y = self.x, self.y - max_x = tw / 2. - max_y = th / 2. - - if (max_x < x - screen_width - or max_x < -x - or max_y < y - screen_height - or max_y < -y): - return False - return True - - - def check_collisions(self): - # Check for collisions - ex, ey = self.x, self.y - ehalf_size_x, ehalf_size_y = self.hitbox_half_size - ex1, ex2 = ex - ehalf_size_x, ex + ehalf_size_x - ey1, ey2 = ey - ehalf_size_y, ey + ehalf_size_y - - damages = 0 - - # Check for enemy-bullet collisions - for bullet in self._game.players_bullets: - if bullet.state != LAUNCHED: - continue - half_size = bullet.hitbox - bx, by = bullet.x, bullet.y - bx1, bx2 = bx - half_size[0], bx + half_size[0] - by1, by2 = by - half_size[1], by + half_size[1] - - if not (bx2 < ex1 or bx1 > ex2 - or by2 < ey1 or by1 > ey2): - bullet.collide() - damages += bullet.damage - self._game.sfx_player.play('damage00.wav') - - # Check for enemy-laser collisions - for laser in self._game.players_lasers: - if not laser: - continue - - half_size = laser.hitbox - lx, ly = laser.x, laser.y * 2. - lx1, lx2 = lx - half_size[0], lx + half_size[0] - - if not (lx2 < ex1 or lx1 > ex2 - or ly < ey1): - damages += laser.damage - self._game.sfx_player.play('damage00.wav') - self.drop_particles(1, 1) #TODO: don’t call each frame. - - # Check for enemy-player collisions - ex1, ex2 = ex - ehalf_size_x * 2. / 3., ex + ehalf_size_x * 2. / 3. - ey1, ey2 = ey - ehalf_size_y * 2. / 3., ey + ehalf_size_y * 2. / 3. - if self.collidable: - for player in self._game.players: - px, py = player.x, player.y - phalf_size = player.sht.hitbox - px1, px2 = px - phalf_size, px + phalf_size - py1, py2 = py - phalf_size, py + phalf_size - - #TODO: box-box or point-in-box? - if not (ex2 < px1 or ex1 > px2 or ey2 < py1 or ey1 > py2): - if not self.boss: - damages += 10 - player.collide() - - # Adjust damages - damages = min(70, damages) - score = (damages // 5) * 10 - self._game.players[0].state.score += score #TODO: better distribution amongst the players. - - if self.damageable: - if self._game.spellcard: - #TODO: there is a division by 3, somewhere... where is it? - if damages <= 7: - damages = 1 if damages else 0 - else: - damages //= 7 - - # Apply damages - self.life -= damages - - - def handle_callbacks(self): - #TODO: implement missing callbacks and clean up! - if self.life <= 0 and self.touchable: - self.timeout = -1 #TODO: not really true, the timeout is frozen - self.timeout_callback = -1 - death_flags = self.death_flags & 7 - - self.die_anim() - - #TODO: verify if the score is added with all the different flags. - self._game.players[0].state.score += self.die_score #TODO: better distribution amongst the players. - - #TODO: verify if that should really be there. - if self.boss: - self._game.change_bullets_into_bonus() - - if death_flags < 4: - if self.bonus_dropped > -1: - self.drop_particles(7, 0) - self._game.drop_bonus(self.x, self.y, self.bonus_dropped) - elif self.bonus_dropped == -1: - if self._game.deaths_count % 3 == 0: - self.drop_particles(10, 0) - self._game.drop_bonus(self.x, self.y, self._game.bonus_list[self._game.next_bonus]) - self._game.next_bonus = (self._game.next_bonus + 1) % 32 - else: - self.drop_particles(4, 0) - self._game.deaths_count += 1 - else: - self.drop_particles(4, 0) - - if death_flags == 0: - self.removed = True - return - - if death_flags == 1: - if self.boss: - self.boss = False #TODO: really? - self._game.boss = None - self.touchable = False - elif death_flags == 2: - pass # Just that? - elif death_flags == 3: - if self.boss: - self.boss = False #TODO: really? - self._game.boss = None - self.damageable = False - self.life = 1 - self.death_flags = 0 - - if death_flags != 0 and self.death_callback > -1: - self.process.switch_to_sub(self.death_callback) - self.death_callback = -1 - elif self.life <= self.low_life_trigger and self.low_life_callback > -1: - self.process.switch_to_sub(self.low_life_callback) - self.low_life_callback = -1 - self.low_life_trigger = -1 - self.timeout_callback = -1 - elif self.timeout != -1 and self.frame == self.timeout: - self.frame = 0 - self.timeout = -1 - self._game.kill_enemies() - self._game.cancel_bullets() - - if self.low_life_trigger > 0: - self.life = self.low_life_trigger - self.low_life_trigger = -1 - - if self.timeout_callback > -1: - self.process.switch_to_sub(self.timeout_callback) - self.timeout_callback = -1 - #TODO: this is only done under certain (unknown) conditions! - # but it shouldn't hurt anyway, as the only option left is to crash! - elif self.death_callback > -1: - self.life = 0 #TODO: do this next frame? Bypass self.touchable? - else: - raise Exception('What the hell, man!') - - - def update(self): - if self.process: - self.process.run_iteration() - - x, y = self.x, self.y - - if self.update_mode == 1: - speed = 0.0 - if self.interpolator: - self.interpolator.update(self._game.frame) - x, y = self.interpolator.values - if self.speed_interpolator: - self.speed_interpolator.update(self._game.frame) - speed, = self.speed_interpolator.values - else: - speed = self.speed - self.speed += self.acceleration - self.angle += self.rotation_speed - - dx, dy = cos(self.angle) * speed, sin(self.angle) * speed - if self._type & 2: - x -= dx - else: - x += dx - y += dy - - if self.movement_dependant_sprites: - #TODO: is that really how it works? Almost. - # Sprite determination is done only once per changement, and is - # superseeded by ins_97. - end_left, end_right, left, right = self.movement_dependant_sprites - if x < self.x and self.direction != -1: - self.set_anim(left) - self.direction = -1 - elif x > self.x and self.direction != +1: - self.set_anim(right) - self.direction = +1 - elif x == self.x and self.direction is not None: - self.set_anim({-1: end_left, +1: end_right}[self.direction]) - self.direction = None - - - if self.screen_box: - xmin, ymin, xmax, ymax = self.screen_box - x = max(xmin, min(x, xmax)) - y = max(ymin, min(y, ymax)) - - - self.x, self.y = x, y - - #TODO - if self.anmrunner and not self.anmrunner.run_frame(): - self.anmrunner = None - - if self.sprite and self.visible: - if self.sprite.removed: - self.sprite = None - else: - self.sprite.update_orientation(self.angle, - self.automatic_orientation) - - - if self.bullet_launch_interval != 0: - self.bullet_launch_timer += 1 - if self.bullet_launch_timer == self.bullet_launch_interval: - self.fire() - - # Check collisions - if self.touchable: - self.check_collisions() - - for anm in self.aux_anm: - if anm: - anm.x, anm.y = self.x, self.y - anm.update() - - self.handle_callbacks() - - self.frame += 1 -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pytouhou/game/enemy.pyx Sat Aug 10 19:59:17 2013 +0200 @@ -0,0 +1,535 @@ +# -*- encoding: utf-8 -*- +## +## Copyright (C) 2011 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 libc.math cimport cos, sin, atan2, M_PI as pi + +from pytouhou.vm.anmrunner import ANMRunner +from pytouhou.game.sprite import Sprite +from pytouhou.game.bullet import Bullet, LAUNCHED +from pytouhou.game.laser import Laser +from pytouhou.game.effect import Effect + + +cdef class Enemy(Element): + def __init__(self, pos, long life, long _type, long bonus_dropped, long die_score, anms, game): + Element.__init__(self) + + self._game = game + self._anms = anms + self._type = _type + + self.process = None + self.visible = True + self.was_visible = False + self.bonus_dropped = bonus_dropped + self.die_score = die_score + + self.frame = 0 + + self.x, self.y, self.z = pos + self.life = 1 if life < 0 else life + self.touchable = True + self.collidable = True + self.damageable = True + self.death_flags = 0 + 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 + self.boss_callback = -1 + self.low_life_callback = -1 + self.low_life_trigger = -1 + self.timeout = -1 + self.timeout_callback = -1 + self.remaining_lives = 0 + + self.automatic_orientation = False + + self.bullet_launch_interval = 0 + self.bullet_launch_timer = 0 + self.delay_attack = False + + self.death_anim = 0 + self.movement_dependant_sprites = None + self.direction = 0 + self.interpolator = None #TODO + self.speed_interpolator = None + self.update_mode = 0 + self.angle = 0. + self.speed = 0. + self.rotation_speed = 0. + self.acceleration = 0. + + self.hitbox_half_size[:] = [0, 0] + self.screen_box = None + + self.aux_anm = 8 * [None] + + + property objects: + def __get__(self): + return [self] + [anm for anm in self.aux_anm if anm is not None] + + + cpdef play_sound(self, index): + name = { + 5: 'power0', + 6: 'power1', + 7: 'tan00', + 8: 'tan01', + 9: 'tan02', + 14: 'cat00', + 16: 'lazer00', + 17: 'lazer01', + 18: 'enep01', + 22: 'tan00', #XXX + 24: 'tan02', #XXX + 25: 'kira00', + 26: 'kira01', + 27: 'kira02' + }[index] + self._game.sfx_player.play('%s.wav' % name) + + + cpdef set_hitbox(self, double width, double height): + self.hitbox_half_size[:] = [width / 2, height / 2] + + + cpdef set_bullet_attributes(self, type_, anim, sprite_idx_offset, + bullets_per_shot, number_of_shots, speed, speed2, + launch_angle, angle, flags): + + # Apply difficulty-specific modifiers + speed_a, speed_b, nb_a, nb_b, shots_a, shots_b = self.difficulty_coeffs + diff_coeff = self._game.difficulty / 32. + + speed += speed_a * (1. - diff_coeff) + speed_b * diff_coeff + speed2 += (speed_a * (1. - diff_coeff) + speed_b * diff_coeff) / 2. + bullets_per_shot += int(nb_a * (1. - diff_coeff) + nb_b * diff_coeff) + number_of_shots += int(shots_a * (1. - diff_coeff) + shots_b * diff_coeff) + + 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: + self.fire() + + + cpdef set_bullet_launch_interval(self, long value, unsigned long start=0): + # Apply difficulty-specific modifiers: + #TODO: check every value possible! Look around 102h.exe@0x408720 + value -= value * (<long>self._game.difficulty - 16) // 80 + + self.bullet_launch_interval = value + self.bullet_launch_timer = start % value if value > 0 else 0 + + + cpdef fire(self, offset=None, bullet_attributes=None, launch_pos=None): + (type_, type_idx, sprite_idx_offset, bullets_per_shot, number_of_shots, + speed, speed2, launch_angle, angle, flags) = bullet_attributes or self.bullet_attributes + + bullet_type = self._game.bullet_types[type_idx] + + if launch_pos is None: + ox, oy = offset or self.bullet_launch_offset + launch_pos = self.x + ox, self.y + oy + + if speed < 0.3 and speed != 0.0: + speed = 0.3 + if speed2 < 0.3: + speed2 = 0.3 + + self.bullet_launch_timer = 0 + + player = self.select_player() + + if type_ in (67, 69, 71): + launch_angle += self.get_player_angle(player, launch_pos) + if type_ == 71 and bullets_per_shot % 2 or type_ in (69, 70) and not bullets_per_shot % 2: + launch_angle += pi / bullets_per_shot + if type_ != 75: + launch_angle -= angle * (bullets_per_shot - 1) / 2. + + bullets = self._game.bullets + nb_bullets_max = self._game.nb_bullets_max + + for shot_nb in xrange(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 + if type_ in (69, 70, 71, 74): + launch_angle += angle + for bullet_nb in xrange(bullets_per_shot): + if nb_bullets_max is not None and len(bullets) == nb_bullets_max: + break + + if type_ == 75: # 102h.exe@0x4138cf + bullet_angle = self._game.prng.rand_double() * (launch_angle - angle) + angle + if type_ in (74, 75): # 102h.exe@0x4138cf + shot_speed = self._game.prng.rand_double() * (speed - speed2) + speed2 + bullets.append(Bullet(launch_pos, bullet_type, sprite_idx_offset, + bullet_angle, shot_speed, + self.extended_bullet_attributes, + flags, player, self._game)) + + if type_ in (69, 70, 71, 74): + bullet_angle += 2. * pi / bullets_per_shot + else: + bullet_angle += angle + + + cpdef 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, + offset=None): + ox, oy = offset or self.bullet_launch_offset + launch_pos = self.x + ox, self.y + oy + 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 + + + cpdef select_player(self, players=None): + return (players or self._game.players)[0] #TODO + + + cpdef get_player_angle(self, player=None, pos=None): + player_state = (player or self.select_player()).state + x, y = pos or (self.x, self.y) + return atan2(player_state.y - y, player_state.x - x) + + + cpdef set_anim(self, index): + entry = 0 if index in self._anms[0].scripts else 1 + self.sprite = Sprite() + self.anmrunner = ANMRunner(self._anms[entry], index, self.sprite) + + + cdef void die_anim(self): + anim = {0: 3, 1: 4, 2: 5}[self.death_anim % 256] # The TB is wanted, if index isn’t in these values the original game crashs. + self._game.new_effect((self.x, self.y), anim) + self._game.sfx_player.play('enep00.wav') + + + cdef void drop_particles(self, long number, long color): + if color == 0: + if self._game.stage in [1, 2, 7]: + color = 3 + color += 9 + for i in xrange(number): + self._game.new_particle((self.x, self.y), color, 256) #TODO: find the real size. + + + cpdef set_aux_anm(self, long number, long 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]) + + + cpdef set_pos(self, x, y, z): + self.x, self.y = x, y + self.update_mode = 1 + self.interpolator = Interpolator((x, y), self._game.frame) + + + cpdef move_to(self, duration, x, y, z, formula): + frame = self._game.frame + self.speed_interpolator = None + self.update_mode = 1 + self.interpolator = Interpolator((self.x, self.y), frame, + (x, y), frame + duration - 1, + formula) + + self.angle = atan2(y - self.y, x - self.x) + + + cpdef stop_in(self, duration, formula): + frame = self._game.frame + self.interpolator = None + self.update_mode = 1 + self.speed_interpolator = Interpolator((self.speed,), frame, + (0.,), frame + duration - 1, + formula) + + + cpdef bint is_visible(self, long screen_width, long screen_height): + cdef double tw, th + + if self.sprite is not None: + if self.sprite.corner_relative_placement: + raise Exception #TODO + _, _, tw, th = self.sprite.texcoords + else: + tw, th = 0, 0 + + x, y = self.x, self.y + max_x = tw / 2 + max_y = th / 2 + + if (max_x < x - screen_width + or max_x < -x + or max_y < y - screen_height + or max_y < -y): + return False + return True + + + cdef void check_collisions(self): + cdef long damages + cdef double half_size[2], bx, by, lx, ly, px, py, phalf_size + + # Check for collisions + ex, ey = self.x, self.y + ehalf_size_x = self.hitbox_half_size[0] + ehalf_size_y = self.hitbox_half_size[1] + ex1, ex2 = ex - ehalf_size_x, ex + ehalf_size_x + ey1, ey2 = ey - ehalf_size_y, ey + ehalf_size_y + + damages = 0 + + # Check for enemy-bullet collisions + for bullet in self._game.players_bullets: + if bullet.state != LAUNCHED: + continue + half_size[0] = bullet.hitbox[0] + half_size[1] = bullet.hitbox[1] + bx, by = bullet.x, bullet.y + bx1, bx2 = bx - half_size[0], bx + half_size[0] + by1, by2 = by - half_size[1], by + half_size[1] + + if not (bx2 < ex1 or bx1 > ex2 + or by2 < ey1 or by1 > ey2): + bullet.collide() + damages += bullet.damage + self._game.sfx_player.play('damage00.wav') + + # Check for enemy-laser collisions + for laser in self._game.players_lasers: + if not laser: + continue + + half_size[0] = laser.hitbox[0] + half_size[1] = laser.hitbox[1] + lx, ly = laser.x, laser.y * 2. + lx1, lx2 = lx - half_size[0], lx + half_size[0] + + if not (lx2 < ex1 or lx1 > ex2 + or ly < ey1): + damages += laser.damage + self._game.sfx_player.play('damage00.wav') + self.drop_particles(1, 1) #TODO: don’t call each frame. + + # Check for enemy-player collisions + ex1, ex2 = ex - ehalf_size_x * 2. / 3., ex + ehalf_size_x * 2. / 3. + ey1, ey2 = ey - ehalf_size_y * 2. / 3., ey + ehalf_size_y * 2. / 3. + if self.collidable: + for player in self._game.players: + px, py = player.state.x, player.state.y + phalf_size = player.sht.hitbox + px1, px2 = px - phalf_size, px + phalf_size + py1, py2 = py - phalf_size, py + phalf_size + + #TODO: box-box or point-in-box? + if not (ex2 < px1 or ex1 > px2 or ey2 < py1 or ey1 > py2): + if not self.boss: + damages += 10 + player.collide() + + # Adjust damages + damages = min(70, damages) + score = (damages // 5) * 10 + self._game.players[0].state.score += score #TODO: better distribution amongst the players. + + if self.damageable: + if self._game.spellcard is not None: + #TODO: there is a division by 3, somewhere... where is it? + if damages <= 7: + damages = 1 if damages else 0 + else: + damages //= 7 + + # Apply damages + self.life -= damages + + + cdef void handle_callbacks(self): + #TODO: implement missing callbacks and clean up! + if self.life <= 0 and self.touchable: + self.timeout = -1 #TODO: not really true, the timeout is frozen + self.timeout_callback = -1 + death_flags = self.death_flags & 7 + + self.die_anim() + + #TODO: verify if the score is added with all the different flags. + self._game.players[0].state.score += self.die_score #TODO: better distribution amongst the players. + + #TODO: verify if that should really be there. + if self.boss: + self._game.change_bullets_into_bonus() + + if death_flags < 4: + if self.bonus_dropped > -1: + self.drop_particles(7, 0) + self._game.drop_bonus(self.x, self.y, self.bonus_dropped) + elif self.bonus_dropped == -1: + if self._game.deaths_count % 3 == 0: + self.drop_particles(10, 0) + self._game.drop_bonus(self.x, self.y, self._game.bonus_list[self._game.next_bonus]) + self._game.next_bonus = (self._game.next_bonus + 1) % 32 + else: + self.drop_particles(4, 0) + self._game.deaths_count += 1 + else: + self.drop_particles(4, 0) + + if death_flags == 0: + self.removed = True + return + + if death_flags == 1: + if self.boss: + self.boss = False #TODO: really? + self._game.boss = None + self.touchable = False + elif death_flags == 2: + pass # Just that? + elif death_flags == 3: + if self.boss: + self.boss = False #TODO: really? + self._game.boss = None + self.damageable = False + self.life = 1 + self.death_flags = 0 + + if death_flags != 0 and self.death_callback > -1: + self.process.switch_to_sub(self.death_callback) + self.death_callback = -1 + elif self.life <= self.low_life_trigger and self.low_life_callback > -1: + self.process.switch_to_sub(self.low_life_callback) + self.low_life_callback = -1 + self.low_life_trigger = -1 + self.timeout_callback = -1 + elif self.timeout != -1 and self.frame == self.timeout: + self.frame = 0 + self.timeout = -1 + self._game.kill_enemies() + self._game.cancel_bullets() + + if self.low_life_trigger > 0: + self.life = self.low_life_trigger + self.low_life_trigger = -1 + + if self.timeout_callback > -1: + self.process.switch_to_sub(self.timeout_callback) + self.timeout_callback = -1 + #TODO: this is only done under certain (unknown) conditions! + # but it shouldn't hurt anyway, as the only option left is to crash! + elif self.death_callback > -1: + self.life = 0 #TODO: do this next frame? Bypass self.touchable? + else: + raise Exception('What the hell, man!') + + + cpdef update(self): + cdef double x, y, speed + + if self.process: + self.process.run_iteration() + + x, y = self.x, self.y + + if self.update_mode == 1: + speed = 0. + if self.interpolator: + self.interpolator.update(self._game.frame) + x, y = self.interpolator.values + if self.speed_interpolator: + self.speed_interpolator.update(self._game.frame) + speed, = self.speed_interpolator.values + else: + speed = self.speed + self.speed += self.acceleration + self.angle += self.rotation_speed + + dx, dy = cos(self.angle) * speed, sin(self.angle) * speed + if self._type & 2: + x -= dx + else: + x += dx + y += dy + + if self.movement_dependant_sprites is not None: + #TODO: is that really how it works? Almost. + # Sprite determination is done only once per changement, and is + # superseeded by ins_97. + end_left, end_right, left, right = self.movement_dependant_sprites + if x < self.x and self.direction != -1: + self.set_anim(left) + self.direction = -1 + elif x > self.x and self.direction != +1: + self.set_anim(right) + self.direction = +1 + elif x == self.x and self.direction != 0: + self.set_anim({-1: end_left, +1: end_right}[self.direction]) + self.direction = 0 + + + if self.screen_box is not None: + xmin, ymin, xmax, ymax = self.screen_box + x = max(xmin, min(x, xmax)) + y = max(ymin, min(y, ymax)) + + + self.x, self.y = x, y + + #TODO + if self.anmrunner is not None and not self.anmrunner.run_frame(): + self.anmrunner = None + + if self.sprite is not None and self.visible: + if self.sprite.removed: + self.sprite = None + else: + self.sprite.update_orientation(self.angle, + self.automatic_orientation) + + + if self.bullet_launch_interval != 0: + self.bullet_launch_timer += 1 + if self.bullet_launch_timer == self.bullet_launch_interval: + self.fire() + + # Check collisions + if self.touchable: + self.check_collisions() + + for anm in self.aux_anm: + if anm is not None: + anm.x, anm.y = self.x, self.y + anm.update() + + self.handle_callbacks() + + self.frame += 1 +
--- a/pytouhou/vm/eclrunner.py Fri Aug 30 14:16:08 2013 +0200 +++ b/pytouhou/vm/eclrunner.py Sat Aug 10 19:59:17 2013 +0200 @@ -815,8 +815,7 @@ @instruction(103) def set_hitbox(self, width, height, depth): - self._enemy.hitbox = (width, height) - self._enemy.hitbox_half_size = (width / 2., height / 2.) + self._enemy.set_hitbox(width, height) @instruction(104)
