changeset 471:06f0eeb519bb

Make Laser and Orb extension types, and use that where possible.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Mon, 16 Sep 2013 18:42:04 +0200
parents 98995d8ac744
children 8038f1957b71
files pytouhou/game/enemy.pyx pytouhou/game/game.pxd pytouhou/game/game.pyx pytouhou/game/laser.pxd pytouhou/game/laser.py pytouhou/game/laser.pyx pytouhou/game/orb.pxd pytouhou/game/orb.py pytouhou/game/player.pyx pytouhou/games/eosd.py pytouhou/vm/eclrunner.py
diffstat 11 files changed, 339 insertions(+), 289 deletions(-) [+]
line wrap: on
line diff
--- a/pytouhou/game/enemy.pyx	Mon Sep 16 18:41:51 2013 +0200
+++ b/pytouhou/game/enemy.pyx	Mon Sep 16 18:42:04 2013 +0200
@@ -17,7 +17,7 @@
 from pytouhou.vm.anmrunner import ANMRunner
 from pytouhou.game.sprite import Sprite
 from pytouhou.game.bullet cimport Bullet, LAUNCHED
-from pytouhou.game.laser import Laser
+from pytouhou.game.laser cimport Laser, PlayerLaser
 from pytouhou.game.effect cimport Effect
 
 
@@ -301,8 +301,9 @@
     cdef void check_collisions(self):
         cdef Bullet bullet
         cdef Player player
+        cdef PlayerLaser laser
         cdef long damages
-        cdef double half_size[2], lx, ly, phalf_size
+        cdef double half_size[2], phalf_size
 
         # Check for collisions
         ex, ey = self.x, self.y
--- a/pytouhou/game/game.pxd	Mon Sep 16 18:41:51 2013 +0200
+++ b/pytouhou/game/game.pxd	Mon Sep 16 18:42:04 2013 +0200
@@ -21,6 +21,7 @@
     cpdef drop_bonus(self, double x, double y, long _type, end_pos=*)
     cdef void autocollect(self, Player player) except *
     cdef void cancel_bullets(self) except *
+    cdef void cancel_player_lasers(self) except *
     cpdef change_bullets_into_star_items(self)
     cpdef change_bullets_into_bonus(self)
     cpdef kill_enemies(self)
--- a/pytouhou/game/game.pyx	Mon Sep 16 18:41:51 2013 +0200
+++ b/pytouhou/game/game.pyx	Mon Sep 16 18:42:04 2013 +0200
@@ -19,6 +19,7 @@
 from pytouhou.game.enemy cimport Enemy
 from pytouhou.game.item cimport Item
 from pytouhou.game.effect cimport Particle
+from pytouhou.game.laser cimport Laser, PlayerLaser
 from pytouhou.game.text import Text, NativeText
 from pytouhou.game.face import Face
 
@@ -145,17 +146,24 @@
 
     cdef void cancel_bullets(self):
         cdef Bullet bullet
-        #TODO: cdef Laser laser
+        cdef Laser laser
 
         for bullet in self.bullets:
             bullet.cancel()
         for laser in self.lasers:
             laser.cancel()
 
+    cdef void cancel_player_lasers(self):
+        cdef PlayerLaser laser
+        for laser in self.players_lasers:
+            if laser is not None:
+                laser.cancel()
+
 
     cpdef change_bullets_into_star_items(self):
         cdef Player player
         cdef Bullet bullet
+        cdef Laser laser
 
         player = self.players[0] #TODO
         item_type = self.item_types[6]
@@ -253,6 +261,7 @@
 
 
     cpdef run_iter(self, long keystate):
+        cdef Laser laser
         # 1. VMs.
         for runner in self.ecl_runners:
             runner.run_iter()
@@ -371,6 +380,9 @@
         cdef Player player
         cdef Bullet bullet
         cdef Item item
+        cdef PlayerLaser player_laser
+        cdef Laser laser
+        cdef double player_pos[2]
 
         if self.time_stop:
             return
@@ -381,9 +393,9 @@
         for bullet in self.bullets:
             bullet.update()
 
-        for laser in self.players_lasers:
-            if laser is not None:
-                laser.update()
+        for player_laser in self.players_lasers:
+            if player_laser is not None:
+                player_laser.update()
 
         for item in self.items:
             item.update()
@@ -395,6 +407,7 @@
                 continue
 
             px, py = player_state.x, player_state.y
+            player_pos[:] = [px, py]
             phalf_size = <double>player.sht.hitbox
             px1, px2 = px - phalf_size, px + phalf_size
             py1, py2 = py - phalf_size, py + phalf_size
@@ -404,10 +417,10 @@
             gy1, gy2 = py - ghalf_size, py + ghalf_size
 
             for laser in self.lasers:
-                if laser.check_collision((px, py)):
+                if laser.check_collision(player_pos):
                     if player_state.invulnerable_time == 0:
                         player.collide()
-                elif laser.check_grazing((px, py)):
+                elif laser.check_grazing(player_pos):
                     player_state.graze += 1 #TODO
                     player_state.score += 500 #TODO
                     player.play_sound('graze')
@@ -460,6 +473,7 @@
         cdef Enemy enemy
         cdef Bullet bullet
         cdef Item item
+        cdef PlayerLaser laser
         cdef long i
 
         # Filter out non-visible enemies
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/game/laser.pxd	Mon Sep 16 18:42:04 2013 +0200
@@ -0,0 +1,44 @@
+from pytouhou.game.element cimport Element
+from pytouhou.game.sprite cimport Sprite
+from pytouhou.game.game cimport Game
+
+cdef enum State:
+    STARTING, STARTED, STOPPING
+
+
+cdef class LaserLaunchAnim(Element):
+    cdef Laser _laser
+
+    cpdef update(self)
+
+
+cdef class Laser(Element):
+    cdef public unsigned long frame
+    cdef public double angle
+
+    cdef unsigned long start_duration, duration, stop_duration, grazing_delay,
+    cdef unsigned long grazing_extra_duration, sprite_idx_offset
+    cdef double base_pos[2], speed, start_offset, end_offset, max_length, width
+    cdef State state
+    cdef Game _game
+    cdef object _laser_type
+
+    cdef void set_anim(self, long sprite_idx_offset=*) except *
+    cpdef set_base_pos(self, double x, double y)
+    cdef bint _check_collision(self, double point[2], double border_size)
+    cdef bint check_collision(self, double point[2])
+    cdef bint check_grazing(self, double point[2])
+    #def get_bullets_pos(self)
+    cpdef cancel(self)
+    cpdef update(self)
+
+
+cdef class PlayerLaser(Element):
+    cdef double hitbox[2], angle, offset
+    cdef unsigned long frame, duration, sprite_idx_offset, damage
+    cdef Element origin
+    cdef object _laser_type
+
+    cdef void set_anim(self, long sprite_idx_offset=*) except *
+    cdef void cancel(self) except *
+    cdef void update(self) except *
--- a/pytouhou/game/laser.py	Mon Sep 16 18:41:51 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-# -*- 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.game.element import Element
-from pytouhou.vm.anmrunner import ANMRunner
-from pytouhou.game.sprite import Sprite
-
-
-STARTING, STARTED, STOPPING = range(3)
-
-
-class LaserLaunchAnim(Element):
-    def __init__(self, laser, anm, index):
-        Element.__init__(self, (0, 0))
-
-        self._laser = laser
-        self.sprite = Sprite()
-        self.sprite.anm = anm
-        self.sprite.texcoords = anm.sprites[index]
-        self.sprite.blendfunc = 1
-
-
-    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(Element):
-    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):
-        Element.__init__(self, (0, 0))
-
-        self._game = game
-        launch_anim = LaserLaunchAnim(self, laser_type.anm,
-                                      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
-
-        #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.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, lt.anim_index,
-                                   self.sprite, self.sprite_idx_offset)
-
-
-    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.visible = (width > 0 and length > 0)
-        self.sprite.width_override = width
-        self.sprite.height_override = length
-
-        self.sprite.update_orientation(pi/2. - self.angle, True)
-        self.sprite.changed = True #TODO
-
-        self.frame += 1
-
-
-class PlayerLaser(Element):
-    def __init__(self, laser_type, sprite_idx_offset, hitbox, damage,
-                 angle, offset, duration, origin):
-        Element.__init__(self)
-
-        self._laser_type = laser_type
-        self.origin = origin
-
-        self.hitbox = hitbox[0], hitbox[1]
-
-        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, lt.anim_index,
-                                   self.sprite, self.sprite_idx_offset)
-        #self.sprite.blendfunc = 1 #XXX
-
-
-    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.visible = (length > 0)
-        self.sprite.height_override = length
-        self.sprite.changed = True #TODO
-
-        self.frame += 1
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/game/laser.pyx	Mon Sep 16 18:42:04 2013 +0200
@@ -0,0 +1,253 @@
+# -*- 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 libc.math cimport cos, sin, M_PI as pi
+
+from pytouhou.vm.anmrunner import ANMRunner
+
+
+cdef class LaserLaunchAnim(Element):
+    def __init__(self, Laser laser, anm, unsigned long index):
+        Element.__init__(self, (0, 0))
+
+        self._laser = laser
+        self.sprite = Sprite()
+        self.sprite.anm = anm
+        self.sprite.texcoords = anm.sprites[index]
+        self.sprite.blendfunc = 1
+
+
+    cpdef update(self):
+        laser = self._laser
+        length = <double>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
+
+
+
+cdef class Laser(Element):
+    def __init__(self, tuple base_pos, laser_type,
+                 unsigned long sprite_idx_offset, double angle, double speed,
+                 double start_offset, double end_offset, double max_length,
+                 double width, unsigned long start_duration,
+                 unsigned long duration, unsigned long stop_duration,
+                 unsigned long grazing_delay,
+                 unsigned long grazing_extra_duration, Game game):
+        Element.__init__(self, (0, 0))
+
+        self._game = game
+        launch_anim = LaserLaunchAnim(self, laser_type.anm,
+                                      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
+
+        #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.set_base_pos(base_pos[0], base_pos[1])
+        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()
+
+
+    cdef void set_anim(self, long sprite_idx_offset=-1):
+        if sprite_idx_offset >= 0:
+            self.sprite_idx_offset = sprite_idx_offset
+
+        lt = self._laser_type
+        self.sprite = Sprite()
+        self.sprite.angle = self.angle
+        self.anmrunner = ANMRunner(lt.anm, lt.anim_index,
+                                   self.sprite, self.sprite_idx_offset)
+
+
+    cpdef set_base_pos(self, double x, double y):
+        self.base_pos[:] = [x, y]
+
+
+    cdef bint _check_collision(self, double point[2], double border_size):
+        cdef double c1[2], c2[2], c3[2]
+
+        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 = <double>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)
+
+
+    cdef bint check_collision(self, double point[2]):
+        if self.state != STARTED:
+            return False
+
+        return self._check_collision(point, 2.5)
+
+
+    cdef bint check_grazing(self, double point[2]):
+        #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 = <double>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.
+
+
+    cpdef cancel(self):
+        self.grazing_extra_duration = 0
+        if self.state != STOPPING:
+            self.frame = 0
+            self.state = STOPPING
+
+
+    cpdef update(self):
+        if self.anmrunner is not None and not self.anmrunner.run_frame():
+            self.anmrunner = None
+
+        self.end_offset += self.speed
+
+        length = <double>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.base_pos[0] + offset * cos(self.angle)
+        self.y = self.base_pos[1] + offset * sin(self.angle)
+        self.sprite.visible = (width > 0 and length > 0)
+        self.sprite.width_override = width
+        self.sprite.height_override = length
+
+        self.sprite.update_orientation(pi/2. - self.angle, True)
+        self.sprite.changed = True #TODO
+
+        self.frame += 1
+
+
+cdef class PlayerLaser(Element):
+    def __init__(self, laser_type, unsigned long sprite_idx_offset,
+                 tuple hitbox, unsigned long damage, double angle,
+                 double offset, unsigned long duration, Element origin):
+        Element.__init__(self)
+
+        self._laser_type = laser_type
+        self.origin = origin
+
+        self.hitbox[:] = [hitbox[0], hitbox[1]]
+
+        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()
+
+
+    cdef void set_anim(self, long sprite_idx_offset=-1):
+        if sprite_idx_offset >= 0:
+            self.sprite_idx_offset = sprite_idx_offset
+
+        lt = self._laser_type
+        self.sprite = Sprite()
+        self.anmrunner = ANMRunner(lt.anm, lt.anim_index,
+                                   self.sprite, self.sprite_idx_offset)
+        #self.sprite.blendfunc = 1 #XXX
+
+
+    cdef void cancel(self):
+        self.anmrunner.interrupt(1)
+
+
+    cdef void 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.visible = (length > 0)
+        self.sprite.height_override = length
+        self.sprite.changed = True #TODO
+
+        self.x = self.origin.x + self.offset * cos(self.angle)
+        self.y = self.origin.y / 2. + self.offset * sin(self.angle)
+
+        self.frame += 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pytouhou/game/orb.pxd	Mon Sep 16 18:42:04 2013 +0200
@@ -0,0 +1,10 @@
+from pytouhou.game.element cimport Element
+from pytouhou.game.sprite cimport Sprite
+from pytouhou.game.player cimport PlayerState
+
+cdef class Orb(Element):
+    cdef public double offset_x, offset_y
+    cdef PlayerState player_state
+    cdef object fire
+
+    cpdef update(self)
--- a/pytouhou/game/orb.py	Mon Sep 16 18:41:51 2013 +0200
+++ b/pytouhou/game/orb.py	Mon Sep 16 18:42:04 2013 +0200
@@ -12,16 +12,11 @@
 ## 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
 
 
 class Orb(Element):
-    __slots__ = ('offset_x', 'offset_y', 'player_state', 'fire')
-
-    def __init__(self, anm, index, player_state, fire_func):
+    def __init__(self, anm, index, player_state):
         Element.__init__(self)
 
         self.sprite = Sprite()
@@ -31,19 +26,9 @@
         self.offset_y = 0
 
         self.player_state = player_state
-        self.fire = fire_func
-
-
-    @property
-    def x(self):
-        return self.player_state.x + self.offset_x
-
-
-    @property
-    def y(self):
-        return self.player_state.y + self.offset_y
 
 
     def update(self):
         self.anmrunner.run_frame()
-
+        self.x = self.player_state.x + self.offset_x
+        self.y = self.player_state.y + self.offset_y
--- a/pytouhou/game/player.pyx	Mon Sep 16 18:41:51 2013 +0200
+++ b/pytouhou/game/player.pyx	Mon Sep 16 18:42:04 2013 +0200
@@ -19,7 +19,7 @@
 from pytouhou.game.bullettype import BulletType
 from pytouhou.game.bullet cimport Bullet
 from pytouhou.game.lasertype import LaserType
-from pytouhou.game.laser import PlayerLaser
+from pytouhou.game.laser cimport PlayerLaser
 
 
 class GameOver(Exception):
@@ -247,9 +247,7 @@
                     self.state.power -= 16
                 else:
                     self.state.power = 0
-                for laser in self._game.players_lasers:
-                    if laser is not None:
-                        laser.cancel()
+                self._game.cancel_player_lasers()
 
                 self.state.miss += 1
                 self.state.lives -= 1
--- a/pytouhou/games/eosd.py	Mon Sep 16 18:41:51 2013 +0200
+++ b/pytouhou/games/eosd.py	Mon Sep 16 18:42:04 2013 +0200
@@ -275,8 +275,8 @@
 
         Player.__init__(self, state, game, self.anm)
 
-        self.orbs = [Orb(self.anm, 128, self.state, None),
-                     Orb(self.anm, 129, self.state, None)]
+        self.orbs = [Orb(self.anm, 128, self.state),
+                     Orb(self.anm, 129, self.state)]
 
         self.orbs[0].offset_x = -24
         self.orbs[1].offset_x = 24
--- a/pytouhou/vm/eclrunner.py	Mon Sep 16 18:41:51 2013 +0200
+++ b/pytouhou/vm/eclrunner.py	Mon Sep 16 18:42:04 2013 +0200
@@ -728,7 +728,7 @@
         except KeyError:
             pass #TODO
         else:
-            laser.base_pos = self._enemy.x + ox, self._enemy.y + oy
+            laser.set_base_pos(self._enemy.x + ox, self._enemy.y + oy)
 
 
     @instruction(92)