view pytouhou/game/item.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 3168e5ff22dc
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.
##

from libc.math cimport cos, sin, atan2, M_PI as pi


cdef class Indicator(Element):
    def __init__(self, Item item):
        Element.__init__(self)

        self._item = item
        self.sprite = item._item_type.indicator_sprite.copy()

        self.x = self._item.x
        self.y = self.sprite._texcoords[3] / 2.


    cdef void update(self) nogil:
        #TODO: alpha
        self.x = self._item.x



cdef class Item(Element):
    def __init__(self, start_pos, long _type, ItemType item_type, Game game,
                 double angle=pi/2, player=None, end_pos=None,
                 target=None):
        Element.__init__(self, start_pos)

        self._game = game
        self._type = _type
        self._item_type = item_type
        self.sprite = item_type.sprite

        self.frame = 0
        self.angle = angle
        self.indicator = None

        # The only player allowed to collect that item. If not None,
        # autocollection is disabled too.
        self.player = player

        # The player who has autocollected that item.
        # TODO: do we allow stealing in case another player is in the way?
        self.target = target

        #TODO: find the formulae in the binary.
        self.speed_interpolator = None
        if end_pos:
            self.pos_interpolator = Interpolator(start_pos, 0,
                                                 end_pos, 60)
        else:
            self.speed_interpolator = Interpolator((-2.,), 0,
                                                   (0.,), 60)

        self.sprite.angle = angle


    property objects:
        def __get__(self):
            if self.indicator is not None:
                return [self.indicator]
            return [self]


    cdef bint autocollect(self, Player player) except True:
        if self.target is None and self.player is None:
            self.target = player
            self.speed = player.sht.autocollection_speed


    cdef bint on_collect(self, Player player) except True:
        if not (self.player is None or self.player is player):
            return False

        old_power = player.power
        score = 0
        label = None
        color = 'white'
        player.play_sound('item00')

        if self._type == 0 or self._type == 2: # power or big power
            if old_power < 128:
                player.power_bonus = 0
                score = 10
                player.power += (1 if self._type == 0 else 8)
                if player.power > 128:
                    player.power = 128
                for level in (8, 16, 32, 48, 64, 96):
                    if old_power < level and player.power >= level:
                        label = self._game.new_label((self.x, self.y), b':') # Actually a “PowerUp” character.
                        color = 'blue'
                        label.set_color(color)
                        labeled = True
            else:
                bonus = player.power_bonus + (1 if self._type == 0 else 8)
                if bonus > 30:
                    bonus = 30
                if bonus < 9:
                    score = (bonus + 1) * 10
                elif bonus < 18:
                    score = (bonus - 8) * 100
                elif bonus < 30:
                    score = (bonus - 17) * 1000
                elif bonus == 30:
                    score = 51200
                    color = 'yellow'
                player.power_bonus = bonus
            self._game.modify_difficulty(+1)

        elif self._type == 1: # point
            player.points += 1
            poc = <long>player.sht.point_of_collection
            if player.y < poc:
                score = 100000
                self._game.modify_difficulty(+30)
                color = 'yellow'
            else:
                score = (728 - <int>(self.y)) * 100 #TODO: check the formula some more.
                self._game.modify_difficulty(+3)

        elif self._type == 3: # bomb
            if player.bombs < 8:
                player.bombs += 1
            self._game.modify_difficulty(+5)

        elif self._type == 4: # full power
            score = 1000
            player.power = 128

        elif self._type == 5: # 1up
            if player.lives < 8:
                player.lives += 1
            self._game.modify_difficulty(+200)
            player.play_sound('extend')

        elif self._type == 6: # star
            score = 500

        if old_power < 128 and player.power == 128:
            #TODO: display “full power”.
            self._game.change_bullets_into_star_items()

        if score > 0:
            player.score += score
            if label is None:
                label = self._game.new_label((self.x, self.y), str(score).encode())
                if color != 'white':
                    label.set_color(color)

        self.removed = True


    cpdef update(self):
        cdef bint offscreen

        if self.frame == 60:
            self.speed_interpolator = Interpolator((0.,), 60,
                                                   (3.,), 180)

        if self.target is not None:
            self.angle = atan2(self.target.y - self.y, self.target.x - self.x)
            self.x += cos(self.angle) * self.speed
            self.y += sin(self.angle) * self.speed
        elif self.speed_interpolator is None:
            self.pos_interpolator.update(self.frame)
            self.x, self.y = self.pos_interpolator.values
        else:
            self.speed_interpolator.update(self.frame)
            self.speed, = self.speed_interpolator.values
            dx, dy = cos(self.angle) * self.speed, sin(self.angle) * self.speed
            self.x += dx
            self.y += dy

        offscreen = self.y < -self.sprite._texcoords[3] / 2.
        if offscreen:
            if self.indicator is None:
                self.indicator = Indicator(self)
            self.indicator.update()
        else:
            self.indicator = None

        self.frame += 1