Mercurial > touhou
view pytouhou/game/bullet.pyx @ 792:11bc22bad1bf
python: Replace the image crate with png
We weren’t using any of its features anyway, so the png crate is exactly what
we need, without the many heavy dependencies of image.
https://github.com/image-rs/image-png/pull/670 will eventually make it even
faster to build.
| author | Link Mauve <linkmauve@linkmauve.fr> |
|---|---|
| date | Sat, 17 Jan 2026 22:22:25 +0100 |
| parents | a6af3ff86612 |
| children |
line wrap: on
line source
# -*- 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. ## cimport cython from libc.math cimport cos, sin, atan2, M_PI as pi from pytouhou.vm import ANMRunner from pytouhou.game.sprite cimport Sprite cdef class Bullet(Element): def __init__(self, pos, BulletType bullet_type, unsigned long sprite_idx_offset, double angle, double speed, attributes, unsigned long flags, target, Game game, long player=-1, unsigned long damage=0, tuple hitbox=None): cdef double launch_mult Element.__init__(self, pos) self._game = game self._bullet_type = bullet_type self.state = LAUNCHING self.was_visible = True if hitbox is not None: self.hitbox[:] = [hitbox[0], hitbox[1]] else: self.hitbox[:] = [bullet_type.hitbox_size, bullet_type.hitbox_size] self.speed_interpolator = None self.frame = 0 self.grazed = False self.target = target self.sprite_idx_offset = sprite_idx_offset self.flags = flags self.attributes = list(attributes) self.angle = angle self.speed = speed self.dx, self.dy = cos(angle) * speed, sin(angle) * speed self.player = player self.damage = damage #TODO if flags & 14: if flags & 2: index = bullet_type.launch_anim2_index launch_mult = bullet_type.launch_anim_penalties[0] elif flags & 4: index = bullet_type.launch_anim4_index launch_mult = bullet_type.launch_anim_penalties[1] else: index = bullet_type.launch_anim8_index launch_mult = bullet_type.launch_anim_penalties[2] self.dx, self.dy = self.dx * launch_mult, self.dy * launch_mult self.sprite = Sprite() self.anmrunner = ANMRunner(bullet_type.anm, index, self.sprite, bullet_type.launch_anim_offsets[sprite_idx_offset]) else: self.launch() if self.player >= 0: self.sprite.angle = angle - pi else: self.sprite.angle = angle cdef bint is_visible(self, unsigned int screen_width, unsigned int screen_height) nogil: tw, th = self.sprite._texcoords[2], self.sprite._texcoords[3] 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 cpdef set_anim(self, sprite_idx_offset=None): if sprite_idx_offset is not None: self.sprite_idx_offset = sprite_idx_offset bt = self._bullet_type self.sprite = Sprite() if self.player >= 0: self.sprite.angle = self.angle - pi else: self.sprite.angle = self.angle self.anmrunner = ANMRunner(bt.anm, bt.anim_index, self.sprite, self.sprite_idx_offset) cdef bint launch(self) except True: self.state = LAUNCHED self.frame = 0 self.set_anim() self.dx, self.dy = cos(self.angle) * self.speed, sin(self.angle) * self.speed if self.flags & 1: self.speed_interpolator = Interpolator((self.speed + 5.,), 0, (self.speed,), 16) cdef bint collide(self) except True: self.cancel() self._game.new_particle((self.x, self.y), 10, 256) #TODO: find the real size. @cython.cdivision(True) cdef bint cancel(self) except True: # Cancel animation bt = self._bullet_type self.sprite = Sprite() if self.player >= 0: self.sprite.angle = self.angle - pi divisor = 8. else: self.sprite.angle = self.angle divisor = 2. self.anmrunner = ANMRunner(bt.anm, bt.cancel_anim_index, self.sprite, bt.launch_anim_offsets[self.sprite_idx_offset]) self.dx /= divisor self.dy /= divisor self.state = CANCELLED cdef bint update(self) except True: cdef int frame, count, game_width, game_height cdef double length, angle, speed, acceleration, angular_speed if self.anmrunner is not None and not self.anmrunner.run_frame(): if self.state == LAUNCHING: #TODO: check if it doesn't skip a frame self.launch() elif self.state == CANCELLED: self.removed = True else: self.anmrunner = None if self.state == LAUNCHING: pass elif self.state == CANCELLED: pass elif self.flags & 1: # Initial speed burst #TODO: use frame instead of interpolator? if not self.speed_interpolator: self.flags &= ~1 elif self.flags & 16: # Each frame, add a vector to the speed vector length, angle = self.attributes[4:6] angle = self.angle if angle < -900.0 else angle #TODO: is that right? self.dx += cos(angle) * length self.dy += sin(angle) * length self.speed = (self.dx ** 2 + self.dy ** 2) ** 0.5 self.angle = self.sprite.angle = atan2(self.dy, self.dx) if self.sprite.automatic_orientation: self.sprite.changed = True if self.frame == self.attributes[0]: #TODO: include last frame, or not? self.flags &= ~16 elif self.flags & 32: # Each frame, accelerate and rotate #TODO: check acceleration, angular_speed = self.attributes[4:6] self.speed += acceleration self.angle += angular_speed self.dx = cos(self.angle) * self.speed self.dy = sin(self.angle) * self.speed self.sprite.angle = self.angle if self.sprite.automatic_orientation: self.sprite.changed = True if self.frame == self.attributes[0]: self.flags &= ~32 elif self.flags & 448: #TODO: check frame, count = self.attributes[0:2] angle, speed = self.attributes[4:6] if self.frame % frame == 0: count -= 1 if self.frame != 0: self.speed = self.speed if speed < -900 else speed if self.flags & 64: self.angle += angle elif self.flags & 128: self.angle = atan2(self.target.y - self.y, self.target.x - self.x) + angle elif self.flags & 256: self.angle = angle self.dx = cos(self.angle) * self.speed self.dy = sin(self.angle) * self.speed self.sprite.angle = self.angle if self.sprite.automatic_orientation: self.sprite.changed = True if count >= 0: self.speed_interpolator = Interpolator((self.speed,), self.frame, (0.,), self.frame + frame - 1) else: self.flags &= ~448 self.attributes[1] = count # Common updates if self.speed_interpolator: self.speed_interpolator.update(self.frame) speed, = self.speed_interpolator.values self.dx = cos(self.angle) * speed self.dy = sin(self.angle) * speed self.x += self.dx self.y += self.dy self.frame += 1 game_width, game_height = self._game.width, self._game.height # Filter out-of-screen bullets and handle special flags if self.flags & 448: self.was_visible = False elif self.is_visible(game_width, game_height): self.was_visible = True elif self.was_visible: self.removed = True if self.flags & (1024 | 2048) and self.attributes[0] > 0: # Bounce! if self.x < 0 or self.x > game_width: self.angle = pi - self.angle self.removed = False if self.y < 0 or ((self.flags & 1024) and self.y > game_height): self.angle = -self.angle self.removed = False self.sprite.angle = self.angle if self.sprite.automatic_orientation: self.sprite.changed = True self.dx = cos(self.angle) * self.speed self.dy = sin(self.angle) * self.speed self.attributes[0] -= 1
