view pytouhou/game/player.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 f3099ebf4f61
children 56523a16db1d
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 pytouhou.game.sprite import Sprite
from pytouhou.vm.anmrunner import ANMRunner
from pytouhou.game.bullettype import BulletType
from pytouhou.game.bullet import Bullet
from pytouhou.game.lasertype import LaserType
from pytouhou.game.laser import PlayerLaser

from math import pi


class PlayerState(object):
    def __init__(self, character=0, score=0, power=0, lives=2, bombs=3):
        self.character = character # ReimuA/ReimuB/MarisaA/MarisaB/...

        self.score = score
        self.effective_score = score
        self.lives = lives
        self.bombs = bombs
        self.power = power

        self.graze = 0
        self.points = 0

        self.x = 192.0
        self.y = 384.0

        self.invulnerable_time = 240
        self.touchable = True
        self.focused = False

        self.power_bonus = 0 # Never goes over 30.


class Player(object):
    def __init__(self, state, game, anm_wrapper):
        self._game = game
        self.sprite = None
        self.anmrunner = None
        self.anm_wrapper = anm_wrapper

        self.speeds = (self.sht.horizontal_vertical_speed,
                       self.sht.diagonal_speed,
                       self.sht.horizontal_vertical_focused_speed,
                       self.sht.diagonal_focused_speed)

        self.hitbox_half_size = self.sht.hitbox / 2.
        self.graze_hitbox_half_size = self.sht.graze_hitbox / 2.

        self.fire_time = 0

        self.state = state
        self.direction = None

        self.set_anim(0)

        self.death_time = 0


    @property
    def x(self):
        return self.state.x


    @property
    def y(self):
        return self.state.y


    def objects(self):
        return []


    def set_anim(self, index):
        self.sprite = Sprite()
        self.anmrunner = ANMRunner(self.anm_wrapper, index, self.sprite)
        self.anmrunner.run_frame()


    def collide(self):
        if not self.state.invulnerable_time and not self.death_time and self.state.touchable: # Border Between Life and Death
            self.death_time = self._game.frame
            self._game.new_effect((self.state.x, self.state.y), 17)
            self._game.modify_difficulty(-1600)
            for i in range(16):
                self._game.new_particle((self.state.x, self.state.y), 2, 4., 256) #TODO: find the real size and range.


    def start_focusing(self):
        self.state.focused = True


    def stop_focusing(self):
        self.state.focused = False


    def fire(self):
        sht = self.focused_sht if self.state.focused else self.sht
        power = min(power for power in sht.shots if self.state.power < power)

        bullets = self._game.players_bullets
        lasers = self._game.players_lasers
        nb_bullets_max = self._game.nb_bullets_max

        for shot in sht.shots[power]:
            origin = self.orbs[shot.orb - 1] if shot.orb else self.state

            if shot.type == 3:
                if self.fire_time != 30:
                    continue

                number = shot.delay #TODO: number can do very surprising things, like removing any bullet creation from enemies with 3. For now, crash when not 0 or 1.
                if lasers[number]:
                    continue

                laser_type = LaserType(self.anm_wrapper, shot.sprite % 256, 68)
                lasers[number] = PlayerLaser(laser_type, 0, shot.hitbox, shot.damage, shot.angle, shot.speed, shot.interval, origin)
                continue

            if (self.fire_time + shot.delay) % shot.interval != 0:
                continue

            if nb_bullets_max is not None and len(bullets) == nb_bullets_max:
                break

            x = origin.x + shot.pos[0]
            y = origin.y + shot.pos[1]

            #TODO: find a better way to do that.
            bullet_type = BulletType(self.anm_wrapper, shot.sprite % 256,
                                     shot.sprite % 256 + 32, #TODO: find the real cancel anim
                                     0, 0, 0, 0.)
            #TODO: Type 1 (homing bullets)
            if shot.type == 2:
                #TODO: triple-check acceleration!
                bullets.append(Bullet((x, y), bullet_type, 0,
                                      shot.angle, shot.speed,
                                      (-1, 0, 0, 0, 0.15, -pi/2., 0., 0.),
                                      16, self, self._game, player_bullet=True,
                                      damage=shot.damage, hitbox=shot.hitbox))
            else:
                bullets.append(Bullet((x, y), bullet_type, 0,
                                      shot.angle, shot.speed,
                                      (0, 0, 0, 0, 0., 0., 0., 0.),
                                      0, self, self._game, player_bullet=True,
                                      damage=shot.damage, hitbox=shot.hitbox))


    def update(self, keystate):
        if self.death_time == 0 or self._game.frame - self.death_time > 60:
            speed, diag_speed = self.speeds[2:] if self.state.focused else self.speeds[:2]
            try:
                dx, dy = {16: (0.0, -speed), 32: (0.0, speed), 64: (-speed, 0.0), 128: (speed, 0.0),
                          16|64: (-diag_speed, -diag_speed), 16|128: (diag_speed, -diag_speed),
                          32|64: (-diag_speed, diag_speed), 32|128:  (diag_speed, diag_speed)}[keystate & (16|32|64|128)]
            except KeyError:
                dx, dy = 0.0, 0.0

            if dx < 0 and self.direction != -1:
                self.set_anim(1)
                self.direction = -1
            elif dx > 0 and self.direction != +1:
                self.set_anim(3)
                self.direction = +1
            elif dx == 0 and self.direction is not None:
                self.set_anim({-1: 2, +1: 4}[self.direction])
                self.direction = None

            self.state.x += dx
            self.state.y += dy

            if not self.state.focused and keystate & 4:
                self.start_focusing()
            elif self.state.focused and not keystate & 4:
                self.stop_focusing()

            if self.state.invulnerable_time > 0:
                self.state.invulnerable_time -= 1

                m = self.state.invulnerable_time % 8
                if m == 0:
                    self.sprite.color = (255, 255, 255)
                    self.sprite.changed = True
                elif m == 2:
                    self.sprite.color = (64, 64, 64)
                    self.sprite.changed = True

            if keystate & 1 and self.fire_time == 0:
                self.fire_time = 30
            if self.fire_time > 0:
                self.fire()
                self.fire_time -= 1

        if self.death_time:
            time = self._game.frame - self.death_time
            if time == 6: # too late, you are dead :(
                self.state.touchable = False
                self.state.lives -= 1
                if self.state.power > 16:
                    self.state.power -= 16
                else:
                    self.state.power = 0

                self._game.drop_bonus(self.state.x, self.state.y, 2,
                                      end_pos=(self._game.prng.rand_double() * 288 + 48, # 102h.exe@0x41f3dc
                                               self._game.prng.rand_double() * 192 - 64))        # @0x41f3
                for i in range(5):
                    self._game.drop_bonus(self.state.x, self.state.y, 0,
                                          end_pos=(self._game.prng.rand_double() * 288 + 48,
                                                   self._game.prng.rand_double() * 192 - 64))

            elif time == 7:
                self.sprite.mirrored = False
                self.sprite.blendfunc = 0
                self.sprite.rescale = 0.75, 1.5
                self.sprite.fade(26, 96, lambda x: x)
                self.sprite.scale_in(26, 0.00, 2.5, lambda x: x)

            elif time == 32:
                self.state.x = float(self._game.width) / 2. #TODO
                self.state.y = float(self._game.width) #TODO
                self.direction = None

                self.sprite = Sprite()
                self.anmrunner = ANMRunner(self.anm_wrapper, 0, self.sprite)
                self.sprite.alpha = 128
                self.sprite.rescale = 0.0, 2.5
                self.sprite.fade(30, 255, lambda x: x)
                self.sprite.blendfunc = 1
                self.sprite.scale_in(30, 1., 1., lambda x: x)
                self.anmrunner.run_frame()

            elif time == 61: # respawned
                self.state.touchable = True
                self.state.invulnerable_time = 240
                self.sprite.blendfunc = 0
                self.sprite.changed = True

            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

        self.anmrunner.run_frame()