changeset 494:6be9c99a3a24

Merge PlayerState into Player, fix player respawn position.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Mon, 14 Oct 2013 12:11:01 +0200
parents 26c082870dcf
children b32cef75df59
files eosd pytouhou/game/enemy.pyx pytouhou/game/game.pyx pytouhou/game/item.pyx pytouhou/game/orb.pxd pytouhou/game/orb.py pytouhou/game/player.pxd pytouhou/game/player.pyx pytouhou/games/eosd.py pytouhou/ui/gamerunner.pyx pytouhou/vm/eclrunner.py pytouhou/vm/msgrunner.py
diffstat 12 files changed, 176 insertions(+), 180 deletions(-) [+]
line wrap: on
line diff
--- a/eosd
+++ b/eosd
@@ -60,7 +60,7 @@ from pytouhou.lib.sdl import SDL
 from pytouhou.ui.window import Window
 from pytouhou.resource.loader import Loader
 from pytouhou.ui.gamerunner import GameRunner
-from pytouhou.game.player import PlayerState, GameOver
+from pytouhou.game.player import GameOver
 from pytouhou.formats.t6rp import T6RP, Level
 from pytouhou.utils.random import Random
 from pytouhou.vm.msgrunner import NextStage
@@ -82,8 +82,8 @@ class GameBossRush(Game):
             Game.run_iter(self, keystate | 256 if i == 0 else 0)
             if not self.enemies and self.frame % 90 == 0:
                 for player in self.players:
-                    if player.state.power < 128:
-                        player.state.power += 1
+                    if player.power < 128:
+                        player.power += 1
             if not skip:
                 break
 
@@ -141,7 +141,6 @@ def main(window, path, data, stage_num, 
         save_replay.character = character
 
     difficulty = 16
-    default_power = [0, 64, 128, 128, 128, 128, 0][stage_num - 1]
 
     if port != 0:
         if remote:
@@ -154,12 +153,11 @@ def main(window, path, data, stage_num, 
 
         prng = Random(0)
         con = Network(port, addr, selected_player)
-        states = [PlayerState(0, character=1, power=default_power, continues=continues),
-                  PlayerState(1, character=3, power=default_power, continues=continues)]
+        characters = [1, 3]
     else:
         con = None
         selected_player = 0
-        states = [PlayerState(0, character=character, power=default_power, continues=continues)]
+        characters = [character]
 
     if hints:
         with open(hints, 'rb') as file:
@@ -167,9 +165,11 @@ def main(window, path, data, stage_num, 
 
     game_class = GameBossRush if boss_rush else Game
 
-    common = Common(resource_loader, states[selected_player])
+    common = Common(resource_loader, characters, continues, stage_num - 1)
     runner = GameRunner(window, resource_loader, skip=skip_replay, con=con)
     while True:
+        first_player = common.players[0]
+
         if replay:
             level = replay.levels[stage_num - 1]
             if not level:
@@ -181,12 +181,12 @@ def main(window, path, data, stage_num, 
             #TODO: see if the stored score is used or if it’s the one from the previous stage.
             if stage_num != 1 and stage_num - 2 in replay.levels:
                 previous_level = replay.levels[stage_num - 1]
-                states[0].score = previous_level.score
-                states[0].effective_score = previous_level.score
-            states[0].points = level.point_items
-            states[0].power = level.power
-            states[0].lives = level.lives
-            states[0].bombs = level.bombs
+                first_player.score = previous_level.score
+                first_player.effective_score = previous_level.score
+            first_player.points = level.point_items
+            first_player.power = level.power
+            first_player.lives = level.lives
+            first_player.bombs = level.bombs
             difficulty = level.difficulty
         elif port == 0:
             prng = Random()
@@ -194,18 +194,18 @@ def main(window, path, data, stage_num, 
         if save_filename:
             if not replay:
                 save_replay.levels[stage_num - 1] = level = Level()
-                level.score = states[0].score
                 level.random_seed = prng.seed
-                level.point_items = states[0].points
-                level.power = states[0].power
-                level.lives = states[0].lives
-                level.bombs = states[0].bombs
+                level.score = first_player.score
+                level.point_items = first_player.points
+                level.power = first_player.power
+                level.lives = first_player.lives
+                level.bombs = first_player.bombs
                 level.difficulty = difficulty
             save_keystates = []
 
         hints_stage = hints.stages[stage_num - 1] if hints else None
 
-        game = game_class(resource_loader, states, stage_num, rank, difficulty,
+        game = game_class(resource_loader, stage_num, rank, difficulty,
                           common, prng=prng, hints=hints_stage,
                           friendly_fire=friendly_fire)
 
@@ -227,7 +227,6 @@ def main(window, path, data, stage_num, 
             if not story or stage_num == (7 if boss_rush else 6 if rank > 0 else 5):
                 break
             stage_num += 1
-            states = [player.state for player in game.players]
         except GameOver:
             print('Game over')
             break
--- a/pytouhou/game/enemy.pyx
+++ b/pytouhou/game/enemy.pyx
@@ -221,7 +221,7 @@ cdef class Enemy(Element):
         if player is None:
             player = self.select_player()
         x, y = pos or (self.x, self.y)
-        return atan2(player.state.y - y, player.state.x - x)
+        return atan2(player.y - y, player.x - x)
 
 
     cpdef set_anim(self, index):
@@ -350,7 +350,7 @@ cdef class Enemy(Element):
         ey1, ey2 = ey - ehalf_size_y * 2. / 3., ey + ehalf_size_y * 2. / 3.
         if self.collidable:
             for player in self._game.players:
-                px, py = player.state.x, player.state.y
+                px, py = player.x, player.y
                 phalf_size = player.sht.hitbox
                 px1, px2 = px - phalf_size, px + phalf_size
                 py1, py2 = py - phalf_size, py + phalf_size
@@ -364,7 +364,7 @@ cdef class Enemy(Element):
         # Adjust damages
         damages = min(70, damages)
         score = (damages // 5) * 10
-        self._game.players[0].state.score += score #TODO: better distribution amongst the players.
+        self._game.players[0].score += score #TODO: better distribution amongst the players.
 
         if self.damageable:
             if self._game.spellcard is not None:
@@ -388,7 +388,7 @@ cdef class Enemy(Element):
             self.die_anim()
 
             #TODO: verify if the score is added with all the different flags.
-            self._game.players[0].state.score += self.die_score #TODO: better distribution amongst the players.
+            self._game.players[0].score += self.die_score #TODO: better distribution amongst the players.
 
             #TODO: verify if that should really be there.
             if self.boss:
@@ -540,5 +540,5 @@ cdef class Enemy(Element):
         self.frame += 1
 
 
-    def select_player_key(self, p):
-        return ((p.x - self.x) ** 2 + (p.y - self.y) ** 2, p.state.character)
+    def select_player_key(self, player):
+        return ((player.x - self.x) ** 2 + (player.y - self.y) ** 2, player.character)
--- a/pytouhou/game/game.pyx
+++ b/pytouhou/game/game.pyx
@@ -192,7 +192,7 @@ cdef class Game:
 
         #TODO: do we really want to give it to each player?
         for player in self.players:
-            player.state.score += score
+            player.score += score
 
 
     cpdef kill_enemies(self):
@@ -358,8 +358,8 @@ cdef class Game:
             player.update(keystate) #TODO: differentiate keystates (multiplayer mode)
 
             #XXX: Why 78910? Is it really the right value?
-            player.state.effective_score = min(player.state.effective_score + 78910,
-                                               player.state.score)
+            player.effective_score = min(player.effective_score + 78910,
+                                         player.score)
             #TODO: give extra lives to the player
 
 
@@ -408,12 +408,10 @@ cdef class Game:
             item.update()
 
         for player in self.players:
-            player_state = player.state
-
-            if not player_state.touchable:
+            if not player.touchable:
                 continue
 
-            px, py = player_state.x, player_state.y
+            px, py = player.x, player.y
             player_pos[:] = [px, py]
             phalf_size = <double>player.sht.hitbox
             px1, px2 = px - phalf_size, px + phalf_size
@@ -425,11 +423,11 @@ cdef class Game:
 
             for laser in self.lasers:
                 if laser.check_collision(player_pos):
-                    if player_state.invulnerable_time == 0:
+                    if player.invulnerable_time == 0:
                         player.collide()
                 elif laser.check_grazing(player_pos):
-                    player_state.graze += 1 #TODO
-                    player_state.score += 500 #TODO
+                    player.graze += 1 #TODO
+                    player.score += 500 #TODO
                     player.play_sound('graze')
                     self.modify_difficulty(+6) #TODO
                     self.new_particle((px, py), 9, 192) #TODO
@@ -447,14 +445,14 @@ cdef class Game:
                 if not (bx2 < px1 or bx1 > px2
                         or by2 < py1 or by1 > py2):
                     bullet.collide()
-                    if player_state.invulnerable_time == 0:
+                    if player.invulnerable_time == 0:
                         player.collide()
 
                 elif not bullet.grazed and not (bx2 < gx1 or bx1 > gx2
                         or by2 < gy1 or by1 > gy2):
                     bullet.grazed = True
-                    player_state.graze += 1
-                    player_state.score += 500 # found experimentally
+                    player.graze += 1
+                    player.score += 500 # found experimentally
                     player.play_sound('graze')
                     self.modify_difficulty(+6)
                     self.new_particle((px, py), 9, 192) #TODO: find the real size and range.
@@ -467,7 +465,7 @@ cdef class Game:
                     if bullet.state != LAUNCHED:
                         continue
 
-                    if bullet.player == player_state.number:
+                    if bullet.player == player.number:
                         continue
 
                     bhalf_width = bullet.hitbox[0]
@@ -479,7 +477,7 @@ cdef class Game:
                     if not (bx2 < px1 or bx1 > px2
                             or by2 < py1 or by1 > py2):
                         bullet.collide()
-                        if player_state.invulnerable_time == 0:
+                        if player.invulnerable_time == 0:
                             player.collide()
 
                 for plaser in self.players_lasers:
@@ -492,11 +490,11 @@ cdef class Game:
 
                     if not (lx2 < px1 or lx1 > px2
                             or ly < py1):
-                        if player_state.invulnerable_time == 0:
+                        if player.invulnerable_time == 0:
                             player.collide()
 
             #TODO: is it the right place?
-            if py < 128 and player_state.power >= 128: #TODO: check py.
+            if py < 128 and player.power >= 128: #TODO: check py.
                 self.autocollect(player)
 
             ihalf_size = <double>player.sht.item_hitbox
@@ -594,4 +592,4 @@ cdef list filter_removed(list elements):
 
 
 def select_player_key(player):
-    return (player.state.score, player.state.character)
+    return (player.score, player.character)
--- a/pytouhou/game/item.pyx
+++ b/pytouhou/game/item.pyx
@@ -78,8 +78,7 @@ cdef class Item(Element):
     cdef void on_collect(self, Player player):
         cdef long level, poc
 
-        player_state = player.state
-        old_power = player_state.power
+        old_power = player.power
         score = 0
         label = None
         color = 'white'
@@ -87,19 +86,19 @@ cdef class Item(Element):
 
         if self._type == 0 or self._type == 2: # power or big power
             if old_power < 128:
-                player_state.power_bonus = 0
+                player.power_bonus = 0
                 score = 10
-                player_state.power += (1 if self._type == 0 else 8)
-                if player_state.power > 128:
-                    player_state.power = 128
+                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_state.power >= level:
+                    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_state.power_bonus + (1 if self._type == 0 else 8)
+                bonus = player.power_bonus + (1 if self._type == 0 else 8)
                 if bonus > 30:
                     bonus = 30
                 if bonus < 9:
@@ -111,13 +110,13 @@ cdef class Item(Element):
                 elif bonus == 30:
                     score = 51200
                     color = 'yellow'
-                player_state.power_bonus = bonus
+                player.power_bonus = bonus
             self._game.modify_difficulty(+1)
 
         elif self._type == 1: # point
-            player_state.points += 1
+            player.points += 1
             poc = player.sht.point_of_collection
-            if player_state.y < poc:
+            if player.y < poc:
                 score = 100000
                 self._game.modify_difficulty(+30)
                 color = 'yellow'
@@ -126,29 +125,29 @@ cdef class Item(Element):
                 self._game.modify_difficulty(+3)
 
         elif self._type == 3: # bomb
-            if player_state.bombs < 8:
-                player_state.bombs += 1
+            if player.bombs < 8:
+                player.bombs += 1
             self._game.modify_difficulty(+5)
 
         elif self._type == 4: # full power
             score = 1000
-            player_state.power = 128
+            player.power = 128
 
         elif self._type == 5: # 1up
-            if player_state.lives < 8:
-                player_state.lives += 1
+            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_state.power == 128:
+        if old_power < 128 and player.power == 128:
             #TODO: display “full power”.
             self._game.change_bullets_into_star_items()
 
         if score > 0:
-            player_state.score += score
+            player.score += score
             if label is None:
                 label = self._game.new_label((self.x, self.y), str(score))
                 if color != 'white':
@@ -165,8 +164,7 @@ cdef class Item(Element):
                                                    (3.,), 180)
 
         if self.player is not None:
-            player_state = self.player.state
-            self.angle = atan2(player_state.y - self.y, player_state.x - self.x)
+            self.angle = atan2(self.player.y - self.y, self.player.x - self.x)
             self.x += cos(self.angle) * self.speed
             self.y += sin(self.angle) * self.speed
         elif self.speed_interpolator is None:
--- a/pytouhou/game/orb.pxd
+++ b/pytouhou/game/orb.pxd
@@ -1,10 +1,10 @@
 from pytouhou.game.element cimport Element
 from pytouhou.game.sprite cimport Sprite
-from pytouhou.game.player cimport PlayerState
+from pytouhou.game.player cimport Player
 
 cdef class Orb(Element):
     cdef public double offset_x, offset_y
-    cdef PlayerState player_state
+    cdef Player player
     cdef object fire
 
     cpdef update(self)
--- a/pytouhou/game/orb.py
+++ b/pytouhou/game/orb.py
@@ -16,7 +16,7 @@ from pytouhou.vm.anmrunner import ANMRun
 
 
 class Orb(Element):
-    def __init__(self, anm, index, player_state):
+    def __init__(self, anm, index, player):
         Element.__init__(self)
 
         self.sprite = Sprite()
@@ -25,10 +25,10 @@ class Orb(Element):
         self.offset_x = 0
         self.offset_y = 0
 
-        self.player_state = player_state
+        self.player = player
 
 
     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
+        self.x = self.player.x + self.offset_x
+        self.y = self.player.y + self.offset_y
--- a/pytouhou/game/player.pxd
+++ b/pytouhou/game/player.pxd
@@ -1,8 +1,9 @@
 from pytouhou.game.element cimport Element
 from pytouhou.game.game cimport Game
 
-cdef class PlayerState:
-    cdef public double x, y
+cdef class Player(Element):
+    cdef public Game _game
+    cdef public long death_time
     cdef public bint touchable, focused
     cdef public long character, score, effective_score, lives, bombs, power
     cdef public long graze, points
@@ -11,12 +12,6 @@ cdef class PlayerState:
     cdef long invulnerable_time, power_bonus, continues, continues_used, miss,
     cdef long bombs_used
 
-
-cdef class Player(Element):
-    cdef public PlayerState state
-    cdef public long death_time
-    cdef public Game _game
-
     cdef object anm
     cdef tuple speeds
     cdef long fire_time, bomb_time, direction
--- a/pytouhou/game/player.pyx
+++ b/pytouhou/game/player.pyx
@@ -26,9 +26,11 @@ class GameOver(Exception):
     pass
 
 
-cdef class PlayerState:
-    def __init__(self, long number, long character=0, long score=0,
-                 long power=0, long lives=2, long bombs=3, long continues=0):
+cdef class Player(Element):
+    def __init__(self, long number, anm, long character=0, long power=0,
+                 long continues=0, long lives=2, long bombs=3, long score=0):
+        Element.__init__(self)
+
         self.number = number
         self.character = character # ReimuA/ReimuB/MarisaA/MarisaB/...
 
@@ -36,8 +38,8 @@ cdef class PlayerState:
         self.effective_score = score
         self.lives = lives
         self.bombs = bombs
+        self.continues = continues
         self.power = power
-        self.continues = continues
 
         self.continues_used = 0
         self.miss = 0
@@ -55,12 +57,7 @@ cdef class PlayerState:
 
         self.power_bonus = 0 # Never goes over 30.
 
-
-cdef class Player(Element):
-    def __init__(self, PlayerState state, Game game, anm):
-        Element.__init__(self)
-
-        self._game = game
+        self._game = None
         self.anm = anm
 
         self.speeds = (self.sht.horizontal_vertical_speed,
@@ -70,7 +67,6 @@ cdef class Player(Element):
 
         self.fire_time = 0
 
-        self.state = state
         self.direction = 0
 
         self.set_anim(0)
@@ -88,33 +84,32 @@ cdef class Player(Element):
 
 
     cdef void collide(self):
-        if not self.state.invulnerable_time and not self.death_time and self.state.touchable: # Border Between Life and Death
+        if not self.invulnerable_time and not self.death_time and self.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.new_effect((self.x, self.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.
+                self._game.new_particle((self.x, self.y), 11, 256) #TODO: find the real size and range.
 
 
     def start_focusing(self):
-        self.state.focused = True
+        self.focused = True
 
 
     def stop_focusing(self):
-        self.state.focused = False
+        self.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
+        sht = self.focused_sht if self.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:
+            if self.power < shot_power:
                 power = power if power < shot_power else shot_power
 
         bullets = self._game.players_bullets
@@ -125,7 +120,7 @@ cdef class Player(Element):
             self.play_sound('plst00')
 
         for shot in sht.shots[power]:
-            origin = self.orbs[shot.orb - 1] if shot.orb else self.state
+            origin = <Element>(self.orbs[shot.orb - 1] if shot.orb else self)
             shot_type = <unsigned char>shot.type
 
             if shot_type == 3:
@@ -149,8 +144,8 @@ cdef class Player(Element):
             if nb_bullets_max != 0 and len(bullets) == nb_bullets_max:
                 break
 
-            x = origin.x + shot.pos[0]
-            y = origin.y + shot.pos[1]
+            x = origin.x + <double>shot.pos[0]
+            y = origin.y + <double>shot.pos[1]
 
             #TODO: find a better way to do that.
             bullet_type = BulletType(self.anm, shot.sprite % 256,
@@ -162,13 +157,13 @@ cdef class Player(Element):
                 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=self.state.number,
+                                      16, self, self._game, player=self.number,
                                       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=self.state.number,
+                                      0, self, self._game, player=self.number,
                                       damage=shot.damage, hitbox=shot.hitbox))
 
 
@@ -176,7 +171,7 @@ cdef class Player(Element):
         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]
+            speed, diag_speed = self.speeds[2:] if self.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),
@@ -194,32 +189,28 @@ cdef class Player(Element):
                 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
+            self.x += dx
+            self.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 self.x < 8.:
+                self.x = 8.
+            if self.x > self._game.width - 8:
+                self.x = self._game.width - 8.
+            if self.y < 16.:
+                self.y = 16.
+            if self.y > self._game.height - 16:
+                self.y = self._game.height -16.
 
-            if not self.state.focused and keystate & 4:
+            if not self.focused and keystate & 4:
                 self.start_focusing()
-            elif self.state.focused and not keystate & 4:
+            elif self.focused and not keystate & 4:
                 self.stop_focusing()
 
-            if self.state.invulnerable_time > 0:
-                self.state.invulnerable_time -= 1
+            if self.invulnerable_time > 0:
+                self.invulnerable_time -= 1
 
-                m = self.state.invulnerable_time % 8
-                if m == 7 or self.state.invulnerable_time == 0:
+                m = self.invulnerable_time % 8
+                if m == 7 or self.invulnerable_time == 0:
                     self.sprite.color = (255, 255, 255)
                     self.sprite.changed = True
                 elif m == 1:
@@ -243,43 +234,43 @@ cdef class Player(Element):
         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
+                self.touchable = False
+                if self.power > 16:
+                    self.power -= 16
                 else:
-                    self.state.power = 0
+                    self.power = 0
                 self._game.cancel_player_lasers()
 
-                self.state.miss += 1
-                self.state.lives -= 1
-                if self.state.lives < 0:
+                self.miss += 1
+                self.lives -= 1
+                if self.lives < 0:
                     #TODO: display a menu to ask the players if they want to continue.
-                    if self.state.continues == 0:
+                    if self.continues == 0:
                         raise GameOver
 
                     # Don’t decrement if it’s infinite.
-                    if self.state.continues >= 0:
-                        self.state.continues -= 1
-                    self.state.continues_used += 1
+                    if self.continues >= 0:
+                        self.continues -= 1
+                    self.continues_used += 1
 
                     for i in xrange(5):
-                        self._game.drop_bonus(self.state.x, self.state.y, 4,
+                        self._game.drop_bonus(self.x, self.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.score = 0
+                    self.effective_score = 0
+                    self.lives = 2 #TODO: use the right default.
+                    self.bombs = 3 #TODO: use the right default.
+                    self.power = 0
 
-                    self.state.graze = 0
-                    self.state.points = 0
+                    self.graze = 0
+                    self.points = 0
                 else:
-                    self._game.drop_bonus(self.state.x, self.state.y, 2,
+                    self._game.drop_bonus(self.x, self.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,
+                        self._game.drop_bonus(self.x, self.y, 0,
                                               end_pos=(self._game.prng.rand_double() * 288 + 48,
                                                        self._game.prng.rand_double() * 192 - 64))
 
@@ -295,8 +286,8 @@ cdef class Player(Element):
                 self._game.cancel_bullets()
 
             elif time == 32:
-                self.state.x = float(self._game.width) / 2. #TODO
-                self.state.y = float(self._game.width) #TODO
+                self.x = float(self._game.width) / 2. #TODO
+                self.y = float(self._game.width) #TODO
                 self.direction = 0
 
                 self.sprite = Sprite()
@@ -308,8 +299,8 @@ cdef class Player(Element):
                 self.sprite.scale_in(30, 1., 1.)
 
             elif time == 61: # respawned
-                self.state.touchable = True
-                self.state.invulnerable_time = 240
+                self.touchable = True
+                self.invulnerable_time = 240
                 self.sprite.blendfunc = 0
                 self.sprite.changed = True
 
--- a/pytouhou/games/eosd.py
+++ b/pytouhou/games/eosd.py
@@ -28,7 +28,7 @@ from pytouhou.vm.eclrunner import ECLMai
 
 
 class EoSDCommon(object):
-    def __init__(self, resource_loader, player_state):
+    def __init__(self, resource_loader, player_characters, continues, stage):
         self.etama = resource_loader.get_multi_anm(('etama3.anm', 'etama4.anm'))
         self.bullet_types = [BulletType(self.etama[0], 0, 11, 14, 15, 16, hitbox_size=2,
                                         type_id=0),
@@ -74,13 +74,31 @@ class EoSDCommon(object):
                            ('face09b.anm', 'face10a.anm', 'face10b.anm'),
                            ('face08a.anm', 'face12a.anm', 'face12b.anm', 'face12c.anm')]
 
-        self.characters = resource_loader.get_eosd_characters()
-        self.interface = EoSDInterface(resource_loader, player_state)
+        default_power = [0, 64, 128, 128, 128, 128, 0][stage]
+
+        eosd_characters = resource_loader.get_eosd_characters()
+        self.first_character = player_characters[0] // 2
+        self.player_anms = {}
+        self.players = [None] * len(player_characters)
+        for i, player_character in enumerate(player_characters):
+            character = player_character // 2
+            if character not in self.player_anms:
+                face = resource_loader.get_multi_anm(('face0%da.anm' % character,
+                                                      'face0%db.anm' % character,
+                                                      'face0%dc.anm' % character))
+                anm = resource_loader.get_single_anm('player0%d.anm' % character)
+                self.player_anms[character] = (anm, face)
+
+            self.players[i] = EoSDPlayer(i, self.player_anms[character][0],
+                                         eosd_characters[player_character],
+                                         character, default_power, continues)
+
+        self.interface = EoSDInterface(resource_loader, self.players[0]) #XXX
 
 
 
 class EoSDGame(Game):
-    def __init__(self, resource_loader, player_states, stage, rank, difficulty,
+    def __init__(self, resource_loader, stage, rank, difficulty,
                  common, nb_bullets_max=640, width=384, height=448, prng=None,
                  hints=None, friendly_fire=True):
 
@@ -95,11 +113,8 @@ class EoSDGame(Game):
 
         self.spellcard_effect_anm = resource_loader.get_single_anm('eff0%d.anm' % stage)
 
-        player_face = player_states[0].character // 2
         self.msg = resource_loader.get_msg('msg%d.dat' % stage)
-        msg_anm = [resource_loader.get_multi_anm(('face0%da.anm' % player_face,
-                                                  'face0%db.anm' % player_face,
-                                                  'face0%dc.anm' % player_face)),
+        msg_anm = [common.player_anms[common.first_character][1], #TODO: does it break bomb face of non-first player?
                    resource_loader.get_multi_anm(common.enemy_face[stage - 1])]
 
         self.msg_anm = [[], []]
@@ -108,7 +123,8 @@ class EoSDGame(Game):
                 for sprite in anm.sprites.values():
                     self.msg_anm[i].append((anm, sprite))
 
-        players = [EoSDPlayer(state, self, resource_loader, common.characters[state.character]) for state in player_states]
+        for player in common.players:
+            player._game = self
 
         # Load stage data
         self.std = resource_loader.get_stage('stage%d.std' % stage)
@@ -121,7 +137,7 @@ class EoSDGame(Game):
 
         self.resource_loader = resource_loader #XXX: currently used for texture preload in pytouhou.ui.gamerunner. Wipe it!
 
-        Game.__init__(self, players, stage, rank, difficulty,
+        Game.__init__(self, common.players, stage, rank, difficulty,
                       common.bullet_types, common.laser_types,
                       common.item_types, nb_bullets_max, width, height, prng,
                       common.interface, hints, friendly_fire)
@@ -144,8 +160,8 @@ class EoSDInterface(object):
                       [Effect((416 + 32 * i, 32 * j), 6, front) for i in range(7) for j in range(15)] +
                       [Effect((32 + 32 * i, 0), 7, front) for i in range(12)] +
                       [Effect((32 + 32 * i, 464), 8, front) for i in range(12)] +
-                      [Effect((0, 0), 5, front)] +
-                      [Effect((0, 0), i, front) for i in range(5) + range(9, 16)])
+                      [Effect((0, 0), i, front) for i in reversed(range(6))] +
+                      [Effect((0, 0), i, front) for i in range(9, 16)])
         for item in self.items:
             item.sprite.allow_dest_offset = True #XXX
 
@@ -269,15 +285,14 @@ class EoSDInterface(object):
 
 
 class EoSDPlayer(Player):
-    def __init__(self, state, game, resource_loader, character):
-        self.sht = character[0]
-        self.focused_sht = character[1]
-        self.anm = resource_loader.get_single_anm('player0%d.anm' % (state.character // 2))
+    def __init__(self, number, anm, shts, character, power, continues):
+        self.sht = shts[0]
+        self.focused_sht = shts[1]
 
-        Player.__init__(self, state, game, self.anm)
+        Player.__init__(self, number, anm, character, power, continues)
 
-        self.orbs = [Orb(self.anm, 128, self.state),
-                     Orb(self.anm, 129, self.state)]
+        self.orbs = [Orb(anm, 128, self),
+                     Orb(anm, 129, self)]
 
         self.orbs[0].offset_x = -24
         self.orbs[1].offset_x = 24
@@ -292,7 +307,7 @@ class EoSDPlayer(Player):
                                                 lambda x: x ** 2)
         self.orb_dy_interpolator = Interpolator((0,), self._game.frame,
                                                 (-32,), self._game.frame + 8)
-        self.state.focused = True
+        self.focused = True
 
 
     def stop_focusing(self):
@@ -301,12 +316,12 @@ class EoSDPlayer(Player):
                                                 lambda x: x ** 2)
         self.orb_dy_interpolator = Interpolator((-32,), self._game.frame,
                                                 (0,), self._game.frame + 8)
-        self.state.focused = False
+        self.focused = False
 
 
     @property
     def objects(self):
-        return [self] + (self.orbs if self.state.power >= 8 else [])
+        return [self] + (self.orbs if self.power >= 8 else [])
 
 
     def update(self, keystate):
--- a/pytouhou/ui/gamerunner.pyx
+++ b/pytouhou/ui/gamerunner.pyx
@@ -48,9 +48,9 @@ cdef class GameRunner(Runner):
 
         self.set_input(replay)
         if replay and replay.levels[game.stage - 1]:
-            game.players[0].state.lives = self.replay_level.lives
-            game.players[0].state.power = self.replay_level.power
-            game.players[0].state.bombs = self.replay_level.bombs
+            game.players[0].lives = self.replay_level.lives
+            game.players[0].power = self.replay_level.power
+            game.players[0].bombs = self.replay_level.bombs
             game.difficulty = self.replay_level.difficulty
 
         self.save_keystates = save_keystates
--- a/pytouhou/vm/eclrunner.py
+++ b/pytouhou/vm/eclrunner.py
@@ -203,10 +203,10 @@ class ECLRunner(object):
                 return self._enemy.z
             elif value == -10018:
                 player = self._enemy.select_player()
-                return player.state.x
+                return player.x
             elif value == -10019:
                 player = self._enemy.select_player()
-                return player.state.y
+                return player.y
             elif value == -10021:
                 return self._enemy.get_player_angle()
             elif value == -10022:
@@ -214,7 +214,7 @@ class ECLRunner(object):
             elif value == -10024:
                 return self._enemy.life
             elif value == -10025:
-                return self._enemy.select_player().state.character #TODO
+                return self._enemy.select_player().character #TODO
             raise NotImplementedError(value) #TODO
         else:
             return value
@@ -932,7 +932,7 @@ class ECLRunner(object):
 
     @instruction(119)
     def drop_some_bonus(self, number):
-        if self._enemy.select_player().state.power < 128:
+        if self._enemy.select_player().power < 128:
             if number > 0:
                 #TODO: find the real formula in the binary.
                 self._game.drop_bonus(self._enemy.x - 64 + self._game.prng.rand_double() * 128,
@@ -981,7 +981,7 @@ class ECLRunner(object):
                       [2, 3, 4],
                       [1, 4, 0],
                       [4, 2, 3]]
-            character = self._enemy.select_player().state.character
+            character = self._enemy.select_player().character
             self.variables[1:4] = values[character]
         elif function == 4:
             if arg == 1:
--- a/pytouhou/vm/msgrunner.py
+++ b/pytouhou/vm/msgrunner.py
@@ -31,7 +31,7 @@ class MSGRunner(object):
                  'handlers')
 
     def __init__(self, msg, script, game):
-        self._msg = msg.msgs[script + 10 * (game.players[0].state.character // 2)]
+        self._msg = msg.msgs[script + 10 * (game.players[0].character // 2)]
         self._game = game
         self.handlers = self._handlers[6]
         self.frame = 0