Mercurial > touhou
view pytouhou/game/laser.py @ 316:f0be7ea62330
Fix a bug with ECL instruction 96, and fix overall ECL handling.
The issue with instruction 96 was about death callbacks,
being executed on the caller of instruction 96 instead of the dying enemies.
This was introduced by changeset 5930b33a0370.
Additionnaly, ECL processes are now an attribute of the Enemy,
and death/timeout conditions are checked right after the ECL frame,
even if the ECL script has already ended, just like in the original game.
author | Thibaut Girka <thib@sitedethib.com> |
---|---|
date | Thu, 29 Mar 2012 21:18:35 +0200 |
parents | 14c9aca8e274 |
children | 690b5faaa0e6 |
line wrap: on
line source
# -*- encoding: utf-8 -*- ## ## Copyright (C) 2012 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 math import cos, sin, pi from pytouhou.vm.anmrunner import ANMRunner from pytouhou.game.sprite import Sprite STARTING, STARTED, STOPPING = range(3) class LaserLaunchAnim(object): def __init__(self, laser, anm_wrapper, index): self._laser = laser self.sprite = Sprite() self.sprite.anm, self.sprite.texcoords = anm_wrapper.get_sprite(index) self.sprite.blendfunc = 1 self.removed = False self.x, self.y = 0, 0 def update(self): laser = self._laser length = min(laser.end_offset - laser.start_offset, laser.max_length) offset = laser.end_offset - length dx, dy = cos(laser.angle), sin(laser.angle) self.x = laser.base_pos[0] + offset * dx self.y = laser.base_pos[1] + offset * dy scale = laser.width / 10. - (offset - laser.start_offset) #TODO: check self.sprite.rescale = (scale, scale) self.sprite.changed = True if laser.removed or scale <= 0.: self.removed = True class Laser(object): def __init__(self, base_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 launch_anim = LaserLaunchAnim(self, laser_type.anm_wrapper, laser_type.launch_anim_offsets[sprite_idx_offset] + laser_type.launch_sprite_idx) self._game.effects.append(launch_anim) self._laser_type = laser_type self.state = STARTING self.sprite = None self.anmrunner = None self.removed = False #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.base_pos = base_pos self.x, self.y = 0, 0 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 _check_collision(self, point, border_size): x, y = point[0] - self.base_pos[0], point[1] - self.base_pos[1] dx, dy = cos(self.angle), sin(self.angle) dx2, dy2 = -dy, dx length = min(self.end_offset - self.start_offset, self.max_length) offset = self.end_offset - length - border_size / 2. end_offset = self.end_offset + border_size / 2. half_width = self.width / 4. + border_size / 2. c1 = dx * offset - dx2 * half_width, dy * offset - dy2 * half_width c2 = dx * offset + dx2 * half_width, dy * offset + dy2 * half_width c3 = dx * end_offset + dx2 * half_width, dy * end_offset + dy2 * half_width vx, vy = x - c2[0], y - c2[1] v1x, v1y = c1[0] - c2[0], c1[1] - c2[1] v2x, v2y = c3[0] - c2[0], c3[1] - c2[1] return (0 <= vx * v1x + vy * v1y <= v1x * v1x + v1y * v1y and 0 <= vx * v2x + vy * v2y <= v2x * v2x + v2y * v2y) def check_collision(self, point): if self.state != STARTED: return False return self._check_collision(point, 2.5) def check_grazing(self, point): #TODO: quadruple check! if self.state == STOPPING and self.frame >= self.grazing_extra_duration: return False if self.state == STARTING and self.frame <= self.grazing_delay: return False if self.frame % 12 != 0: return False return self._check_collision(point, 96 + 2.5) def get_bullets_pos(self): #TODO: check length = min(self.end_offset - self.start_offset, self.max_length) offset = self.end_offset - length dx, dy = cos(self.angle), sin(self.angle) while self.start_offset <= offset < self.end_offset: yield (self.base_pos[0] + offset * dx, self.base_pos[1] + offset * dy) offset += 48. def cancel(self): self.grazing_extra_duration = 0 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 offset = self.end_offset - length / 2. self.x, self.y = self.base_pos[0] + offset * cos(self.angle), self.base_pos[1] + offset * sin(self.angle) 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 class PlayerLaser(object): def __init__(self, laser_type, sprite_idx_offset, hitbox, damage, angle, offset, duration, origin): self.sprite = None self.anmrunner = None self.removed = False self._laser_type = laser_type self.origin = origin self.hitbox_half_size = hitbox[0] / 2., hitbox[1] / 2. self.frame = 0 self.duration = duration self.sprite_idx_offset = sprite_idx_offset self.angle = angle self.offset = offset self.damage = damage self.set_anim() @property def x(self): return self.origin.x + self.offset * cos(self.angle) @property def y(self): return self.origin.y / 2. + self.offset * sin(self.angle) 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.anmrunner = ANMRunner(lt.anm_wrapper, lt.anim_index, self.sprite, self.sprite_idx_offset) #self.sprite.blendfunc = 1 #XXX self.anmrunner.run_frame() def cancel(self): self.anmrunner.interrupt(1) def update(self): if self.anmrunner is not None and not self.anmrunner.run_frame(): self.anmrunner = None self.removed = True length = self.origin.y if self.frame == self.duration: self.cancel() self.sprite.height_override = length or 0.01 #TODO self.sprite.changed = True #TODO self.frame += 1