changeset 445:b0abb05811f7

Make pytouhou.game.player an extension type, and move the GameOver exception there since it makes more sense.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sat, 17 Aug 2013 04:44:28 +0200
parents f26c8ab57257
children 3a33ed7f3b85
files eosd pytouhou/game/game.py pytouhou/game/item.py pytouhou/game/player.pxd pytouhou/game/player.py pytouhou/game/player.pyx pytouhou/vm/eclrunner.py
diffstat 7 files changed, 335 insertions(+), 314 deletions(-) [+]
line wrap: on
line diff
--- a/eosd	Fri Aug 30 14:16:08 2013 +0200
+++ b/eosd	Sat Aug 17 04:44:28 2013 +0200
@@ -56,8 +56,7 @@
 from pytouhou.resource.loader import Loader
 from pytouhou.ui.gamerunner import GameRunner
 from pytouhou.games.eosd import EoSDCommon, EoSDGame
-from pytouhou.game.game import GameOver
-from pytouhou.game.player import PlayerState
+from pytouhou.game.player import PlayerState, GameOver
 from pytouhou.formats.t6rp import T6RP, Level
 from pytouhou.utils.random import Random
 from pytouhou.vm.msgrunner import NextStage
@@ -161,7 +160,7 @@
                 previous_level = replay.levels[stage_num - 1]
                 states[0].score = previous_level.score
                 states[0].effective_score = previous_level.score
-            states[0].point_items = level.point_items
+            states[0].points = level.point_items
             states[0].power = level.power
             states[0].lives = level.lives
             states[0].bombs = level.bombs
--- a/pytouhou/game/game.py	Fri Aug 30 14:16:08 2013 +0200
+++ b/pytouhou/game/game.py	Sat Aug 17 04:44:28 2013 +0200
@@ -24,11 +24,6 @@
 from pytouhou.game.face import Face
 
 
-
-class GameOver(Exception):
-    pass
-
-
 class Game(object):
     def __init__(self, players, stage, rank, difficulty, bullet_types,
                  laser_types, item_types, nb_bullets_max=None, width=384,
@@ -342,7 +337,7 @@
             if not player.state.touchable:
                 continue
 
-            px, py = player.x, player.y
+            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
--- a/pytouhou/game/item.py	Fri Aug 30 14:16:08 2013 +0200
+++ b/pytouhou/game/item.py	Sat Aug 17 04:44:28 2013 +0200
@@ -168,7 +168,8 @@
                                                    (3.,), 180)
 
         if self.player is not None:
-            self.angle = atan2(self.player.y - self.y, self.player.x - self.x)
+            player_state = self.player.state
+            self.angle = atan2(player_state.y - self.y, player_state.x - self.x)
             self.x += cos(self.angle) * self.speed
             self.y += sin(self.angle) * self.speed
         elif self.speed_interpolator is None:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/game/player.pxd	Sat Aug 17 04:44:28 2013 +0200
@@ -0,0 +1,22 @@
+from pytouhou.game.element cimport Element
+
+cdef class PlayerState:
+    cdef public double x, y
+    cdef public bint touchable, focused
+    cdef public long character, score, effective_score, lives, bombs, power, graze, points, invulnerable_time, power_bonus
+
+
+cdef class Player(Element):
+    cdef public PlayerState state
+    cdef public object _game
+    cdef public long death_time
+
+    cdef object anm
+    cdef tuple speeds
+    cdef long fire_time, direction
+
+    cdef void set_anim(self, index)
+    cpdef play_sound(self, str name)
+    cpdef collide(self)
+    cdef void fire(self)
+    cpdef update(self, long keystate)
--- a/pytouhou/game/player.py	Fri Aug 30 14:16:08 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,302 +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.game.element import Element
-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 pytouhou.game.game import GameOver
-
-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.
-
-
-    def copy(self):
-        return PlayerState(self.character, self.score,
-                           self.power, self.lives, self.bombs)
-
-
-class Player(Element):
-    def __init__(self, state, game, anm):
-        Element.__init__(self)
-
-        self._game = game
-        self.anm = anm
-
-        self.speeds = (self.sht.horizontal_vertical_speed,
-                       self.sht.diagonal_speed,
-                       self.sht.horizontal_vertical_focused_speed,
-                       self.sht.diagonal_focused_speed)
-
-        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 set_anim(self, index):
-        self.sprite = Sprite()
-        self.anmrunner = ANMRunner(self.anm, index, self.sprite)
-
-
-    def play_sound(self, name):
-        self._game.sfx_player.play('%s.wav' % name)
-
-
-    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)
-            self.play_sound('pldead00')
-            for i in range(16):
-                self._game.new_particle((self.state.x, self.state.y), 11, 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
-
-        # Don’t use min() since sht.shots could be an empty dict.
-        power = 999
-        for shot_power in sht.shots:
-            if self.state.power < shot_power:
-                power = power if power < shot_power else shot_power
-
-        bullets = self._game.players_bullets
-        lasers = self._game.players_lasers
-        nb_bullets_max = self._game.nb_bullets_max
-
-        if self.fire_time % 5 == 0:
-            self.play_sound('plst00')
-
-        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, 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, 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 self.state.x < 8.:
-                self.state.x = 8.
-            if self.state.x > self._game.width - 8:
-                self.state.x = self._game.width - 8.
-            if self.state.y < 16.:
-                self.state.y = 16.
-            if self.state.y > self._game.height - 16:
-                self.state.y = self._game.height -16.
-
-            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 == 7 or self.state.invulnerable_time == 0:
-                    self.sprite.color = (255, 255, 255)
-                    self.sprite.changed = True
-                elif m == 1:
-                    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
-                if self.state.power > 16:
-                    self.state.power -= 16
-                else:
-                    self.state.power = 0
-                for laser in self._game.players_lasers:
-                    if laser:
-                        laser.cancel()
-
-                self.state.lives -= 1
-                if self.state.lives < 0:
-                    #TODO: display a menu to ask the players if they want to continue.
-                    self._game.continues -= 1
-                    if self._game.continues < 0:
-                        raise GameOver
-
-                    for i in range(5):
-                        self._game.drop_bonus(self.state.x, self.state.y, 4,
-                                              end_pos=(self._game.prng.rand_double() * 288 + 48,
-                                                       self._game.prng.rand_double() * 192 - 64))
-                    self.state.score = 0
-                    self.state.effective_score = 0
-                    self.state.lives = 2 #TODO: use the right default.
-                    self.state.bombs = 3 #TODO: use the right default.
-                    self.state.power = 0
-
-                    self.state.graze = 0
-                    self.state.points = 0
-                else:
-                    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, 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)
-
-            elif time == 61: # respawned
-                self.state.touchable = True
-                self.state.invulnerable_time = 240
-                self.sprite.blendfunc = 0
-                self.sprite.changed = True
-
-            if time > 30:
-                self._game.cancel_bullets()
-
-            if time > 90: # start the bullet hell again
-                self.death_time = 0
-
-        self.anmrunner.run_frame()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/game/player.pyx	Sat Aug 17 04:44:28 2013 +0200
@@ -0,0 +1,306 @@
+# -*- 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 M_PI as pi
+
+from pytouhou.game.sprite cimport Sprite
+from pytouhou.vm.anmrunner import ANMRunner
+from pytouhou.game.bullettype import BulletType
+from pytouhou.game.bullet cimport Bullet
+from pytouhou.game.lasertype import LaserType
+from pytouhou.game.laser import PlayerLaser
+
+
+class GameOver(Exception):
+    pass
+
+
+cdef class PlayerState:
+    def __init__(self, long character=0, long score=0, long power=0, long lives=2, long 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.
+
+
+    def copy(self):
+        return PlayerState(self.character, self.score,
+                           self.power, self.lives, self.bombs)
+
+
+cdef class Player(Element):
+    def __init__(self, PlayerState state, game, anm):
+        Element.__init__(self)
+
+        self._game = game
+        self.anm = anm
+
+        self.speeds = (self.sht.horizontal_vertical_speed,
+                       self.sht.diagonal_speed,
+                       self.sht.horizontal_vertical_focused_speed,
+                       self.sht.diagonal_focused_speed)
+
+        self.fire_time = 0
+
+        self.state = state
+        self.direction = 0
+
+        self.set_anim(0)
+
+        self.death_time = 0
+
+
+    cdef void set_anim(self, index):
+        self.sprite = Sprite()
+        self.anmrunner = ANMRunner(self.anm, index, self.sprite)
+
+
+    cpdef play_sound(self, str name):
+        self._game.sfx_player.play('%s.wav' % name)
+
+
+    cpdef 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)
+            self.play_sound('pldead00')
+            for i in xrange(16):
+                self._game.new_particle((self.state.x, self.state.y), 11, 256) #TODO: find the real size and range.
+
+
+    def start_focusing(self):
+        self.state.focused = True
+
+
+    def stop_focusing(self):
+        self.state.focused = False
+
+
+    cdef void fire(self):
+        cdef double x, y
+        cdef long shot_power
+
+        sht = self.focused_sht if self.state.focused else self.sht
+
+        # Don’t use min() since sht.shots could be an empty dict.
+        power = 999
+        for shot_power in sht.shots:
+            if self.state.power < shot_power:
+                power = power if power < shot_power else shot_power
+
+        bullets = self._game.players_bullets
+        lasers = self._game.players_lasers
+        nb_bullets_max = <long>self._game.nb_bullets_max
+
+        if self.fire_time % 5 == 0:
+            self.play_sound('plst00')
+
+        for shot in sht.shots[power]:
+            origin = self.orbs[shot.orb - 1] if shot.orb else self.state
+            shot_type = <unsigned char>shot.type
+
+            if shot_type == 3:
+                if self.fire_time != 30:
+                    continue
+
+                #TODO: number can do very surprising things, like removing any
+                # bullet creation from enemies with 3. For now, crash when not
+                # an actual laser number.
+                number = <long>shot.delay
+                if lasers[number] is not None:
+                    continue
+
+                laser_type = LaserType(self.anm, 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 != 0 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, 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))
+
+
+    cpdef update(self, long keystate):
+        cdef double dx, dy
+
+        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., -speed), 32: (0., speed), 64: (-speed, 0.), 128: (speed, 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.
+
+            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 != 0:
+                self.set_anim({-1: 2, +1: 4}[self.direction])
+                self.direction = 0
+
+            self.state.x += dx
+            self.state.y += dy
+
+            #XXX
+            self.x = self.state.x
+            self.y = self.state.y
+
+            if self.state.x < 8.:
+                self.state.x = 8.
+            if self.state.x > self._game.width - 8:
+                self.state.x = self._game.width - 8.
+            if self.state.y < 16.:
+                self.state.y = 16.
+            if self.state.y > self._game.height - 16:
+                self.state.y = self._game.height -16.
+
+            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 == 7 or self.state.invulnerable_time == 0:
+                    self.sprite.color = (255, 255, 255)
+                    self.sprite.changed = True
+                elif m == 1:
+                    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 = <long>self._game.frame - self.death_time
+            if time == 6: # too late, you are dead :(
+                self.state.touchable = False
+                if self.state.power > 16:
+                    self.state.power -= 16
+                else:
+                    self.state.power = 0
+                for laser in self._game.players_lasers:
+                    if laser is not None:
+                        laser.cancel()
+
+                self.state.lives -= 1
+                if self.state.lives < 0:
+                    #TODO: display a menu to ask the players if they want to continue.
+                    self._game.continues -= 1
+                    if self._game.continues < 0:
+                        raise GameOver
+
+                    for i in xrange(5):
+                        self._game.drop_bonus(self.state.x, self.state.y, 4,
+                                              end_pos=(self._game.prng.rand_double() * 288 + 48,
+                                                       self._game.prng.rand_double() * 192 - 64))
+                    self.state.score = 0
+                    self.state.effective_score = 0
+                    self.state.lives = 2 #TODO: use the right default.
+                    self.state.bombs = 3 #TODO: use the right default.
+                    self.state.power = 0
+
+                    self.state.graze = 0
+                    self.state.points = 0
+                else:
+                    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 xrange(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)
+                self.sprite.scale_in(26, 0., 2.5)
+
+            elif time == 32:
+                self.state.x = float(self._game.width) / 2. #TODO
+                self.state.y = float(self._game.width) #TODO
+                self.direction = 0
+
+                self.sprite = Sprite()
+                self.anmrunner = ANMRunner(self.anm, 0, self.sprite)
+                self.sprite.alpha = 128
+                self.sprite.rescale = 0., 2.5
+                self.sprite.fade(30, 255)
+                self.sprite.blendfunc = 1
+                self.sprite.scale_in(30, 1., 1.)
+
+            elif time == 61: # respawned
+                self.state.touchable = True
+                self.state.invulnerable_time = 240
+                self.sprite.blendfunc = 0
+                self.sprite.changed = True
+
+            if time > 30:
+                self._game.cancel_bullets()
+
+            if time > 90: # start the bullet hell again
+                self.death_time = 0
+
+        self.anmrunner.run_frame()
+
--- a/pytouhou/vm/eclrunner.py	Fri Aug 30 14:16:08 2013 +0200
+++ b/pytouhou/vm/eclrunner.py	Sat Aug 17 04:44:28 2013 +0200
@@ -203,10 +203,10 @@
                 return self._enemy.z
             elif value == -10018:
                 player = self._enemy.select_player()
-                return player.x
+                return player.state.x
             elif value == -10019:
                 player = self._enemy.select_player()
-                return player.y
+                return player.state.y
             elif value == -10021:
                 return self._enemy.get_player_angle()
             elif value == -10022: