comparison pytouhou/game/player.pyx @ 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 pytouhou/game/player.py@f26c8ab57257
children 78e1c3864e73
comparison
equal deleted inserted replaced
444:f26c8ab57257 445:b0abb05811f7
1 # -*- encoding: utf-8 -*-
2 ##
3 ## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
4 ##
5 ## This program is free software; you can redistribute it and/or modify
6 ## it under the terms of the GNU General Public License as published
7 ## by the Free Software Foundation; version 3 only.
8 ##
9 ## This program is distributed in the hope that it will be useful,
10 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 ## GNU General Public License for more details.
13 ##
14
15 from libc.math cimport M_PI as pi
16
17 from pytouhou.game.sprite cimport Sprite
18 from pytouhou.vm.anmrunner import ANMRunner
19 from pytouhou.game.bullettype import BulletType
20 from pytouhou.game.bullet cimport Bullet
21 from pytouhou.game.lasertype import LaserType
22 from pytouhou.game.laser import PlayerLaser
23
24
25 class GameOver(Exception):
26 pass
27
28
29 cdef class PlayerState:
30 def __init__(self, long character=0, long score=0, long power=0, long lives=2, long bombs=3):
31 self.character = character # ReimuA/ReimuB/MarisaA/MarisaB/...
32
33 self.score = score
34 self.effective_score = score
35 self.lives = lives
36 self.bombs = bombs
37 self.power = power
38
39 self.graze = 0
40 self.points = 0
41
42 self.x = 192.0
43 self.y = 384.0
44
45 self.invulnerable_time = 240
46 self.touchable = True
47 self.focused = False
48
49 self.power_bonus = 0 # Never goes over 30.
50
51
52 def copy(self):
53 return PlayerState(self.character, self.score,
54 self.power, self.lives, self.bombs)
55
56
57 cdef class Player(Element):
58 def __init__(self, PlayerState state, game, anm):
59 Element.__init__(self)
60
61 self._game = game
62 self.anm = anm
63
64 self.speeds = (self.sht.horizontal_vertical_speed,
65 self.sht.diagonal_speed,
66 self.sht.horizontal_vertical_focused_speed,
67 self.sht.diagonal_focused_speed)
68
69 self.fire_time = 0
70
71 self.state = state
72 self.direction = 0
73
74 self.set_anim(0)
75
76 self.death_time = 0
77
78
79 cdef void set_anim(self, index):
80 self.sprite = Sprite()
81 self.anmrunner = ANMRunner(self.anm, index, self.sprite)
82
83
84 cpdef play_sound(self, str name):
85 self._game.sfx_player.play('%s.wav' % name)
86
87
88 cpdef collide(self):
89 if not self.state.invulnerable_time and not self.death_time and self.state.touchable: # Border Between Life and Death
90 self.death_time = self._game.frame
91 self._game.new_effect((self.state.x, self.state.y), 17)
92 self._game.modify_difficulty(-1600)
93 self.play_sound('pldead00')
94 for i in xrange(16):
95 self._game.new_particle((self.state.x, self.state.y), 11, 256) #TODO: find the real size and range.
96
97
98 def start_focusing(self):
99 self.state.focused = True
100
101
102 def stop_focusing(self):
103 self.state.focused = False
104
105
106 cdef void fire(self):
107 cdef double x, y
108 cdef long shot_power
109
110 sht = self.focused_sht if self.state.focused else self.sht
111
112 # Don’t use min() since sht.shots could be an empty dict.
113 power = 999
114 for shot_power in sht.shots:
115 if self.state.power < shot_power:
116 power = power if power < shot_power else shot_power
117
118 bullets = self._game.players_bullets
119 lasers = self._game.players_lasers
120 nb_bullets_max = <long>self._game.nb_bullets_max
121
122 if self.fire_time % 5 == 0:
123 self.play_sound('plst00')
124
125 for shot in sht.shots[power]:
126 origin = self.orbs[shot.orb - 1] if shot.orb else self.state
127 shot_type = <unsigned char>shot.type
128
129 if shot_type == 3:
130 if self.fire_time != 30:
131 continue
132
133 #TODO: number can do very surprising things, like removing any
134 # bullet creation from enemies with 3. For now, crash when not
135 # an actual laser number.
136 number = <long>shot.delay
137 if lasers[number] is not None:
138 continue
139
140 laser_type = LaserType(self.anm, shot.sprite % 256, 68)
141 lasers[number] = PlayerLaser(laser_type, 0, shot.hitbox, shot.damage, shot.angle, shot.speed, shot.interval, origin)
142 continue
143
144 if (self.fire_time + shot.delay) % shot.interval != 0:
145 continue
146
147 if nb_bullets_max != 0 and len(bullets) == nb_bullets_max:
148 break
149
150 x = origin.x + shot.pos[0]
151 y = origin.y + shot.pos[1]
152
153 #TODO: find a better way to do that.
154 bullet_type = BulletType(self.anm, shot.sprite % 256,
155 shot.sprite % 256 + 32, #TODO: find the real cancel anim
156 0, 0, 0, 0.)
157 #TODO: Type 1 (homing bullets)
158 if shot_type == 2:
159 #TODO: triple-check acceleration!
160 bullets.append(Bullet((x, y), bullet_type, 0,
161 shot.angle, shot.speed,
162 (-1, 0, 0, 0, 0.15, -pi/2., 0., 0.),
163 16, self, self._game, player_bullet=True,
164 damage=shot.damage, hitbox=shot.hitbox))
165 else:
166 bullets.append(Bullet((x, y), bullet_type, 0,
167 shot.angle, shot.speed,
168 (0, 0, 0, 0, 0., 0., 0., 0.),
169 0, self, self._game, player_bullet=True,
170 damage=shot.damage, hitbox=shot.hitbox))
171
172
173 cpdef update(self, long keystate):
174 cdef double dx, dy
175
176 if self.death_time == 0 or self._game.frame - self.death_time > 60:
177 speed, diag_speed = self.speeds[2:] if self.state.focused else self.speeds[:2]
178 try:
179 dx, dy = {16: (0., -speed), 32: (0., speed), 64: (-speed, 0.), 128: (speed, 0.),
180 16|64: (-diag_speed, -diag_speed), 16|128: (diag_speed, -diag_speed),
181 32|64: (-diag_speed, diag_speed), 32|128: (diag_speed, diag_speed)}[keystate & (16|32|64|128)]
182 except KeyError:
183 dx, dy = 0., 0.
184
185 if dx < 0 and self.direction != -1:
186 self.set_anim(1)
187 self.direction = -1
188 elif dx > 0 and self.direction != +1:
189 self.set_anim(3)
190 self.direction = +1
191 elif dx == 0 and self.direction != 0:
192 self.set_anim({-1: 2, +1: 4}[self.direction])
193 self.direction = 0
194
195 self.state.x += dx
196 self.state.y += dy
197
198 #XXX
199 self.x = self.state.x
200 self.y = self.state.y
201
202 if self.state.x < 8.:
203 self.state.x = 8.
204 if self.state.x > self._game.width - 8:
205 self.state.x = self._game.width - 8.
206 if self.state.y < 16.:
207 self.state.y = 16.
208 if self.state.y > self._game.height - 16:
209 self.state.y = self._game.height -16.
210
211 if not self.state.focused and keystate & 4:
212 self.start_focusing()
213 elif self.state.focused and not keystate & 4:
214 self.stop_focusing()
215
216 if self.state.invulnerable_time > 0:
217 self.state.invulnerable_time -= 1
218
219 m = self.state.invulnerable_time % 8
220 if m == 7 or self.state.invulnerable_time == 0:
221 self.sprite.color = (255, 255, 255)
222 self.sprite.changed = True
223 elif m == 1:
224 self.sprite.color = (64, 64, 64)
225 self.sprite.changed = True
226
227 if keystate & 1 and self.fire_time == 0:
228 self.fire_time = 30
229 if self.fire_time > 0:
230 self.fire()
231 self.fire_time -= 1
232
233 if self.death_time:
234 time = <long>self._game.frame - self.death_time
235 if time == 6: # too late, you are dead :(
236 self.state.touchable = False
237 if self.state.power > 16:
238 self.state.power -= 16
239 else:
240 self.state.power = 0
241 for laser in self._game.players_lasers:
242 if laser is not None:
243 laser.cancel()
244
245 self.state.lives -= 1
246 if self.state.lives < 0:
247 #TODO: display a menu to ask the players if they want to continue.
248 self._game.continues -= 1
249 if self._game.continues < 0:
250 raise GameOver
251
252 for i in xrange(5):
253 self._game.drop_bonus(self.state.x, self.state.y, 4,
254 end_pos=(self._game.prng.rand_double() * 288 + 48,
255 self._game.prng.rand_double() * 192 - 64))
256 self.state.score = 0
257 self.state.effective_score = 0
258 self.state.lives = 2 #TODO: use the right default.
259 self.state.bombs = 3 #TODO: use the right default.
260 self.state.power = 0
261
262 self.state.graze = 0
263 self.state.points = 0
264 else:
265 self._game.drop_bonus(self.state.x, self.state.y, 2,
266 end_pos=(self._game.prng.rand_double() * 288 + 48, # 102h.exe@0x41f3dc
267 self._game.prng.rand_double() * 192 - 64)) # @0x41f3
268 for i in xrange(5):
269 self._game.drop_bonus(self.state.x, self.state.y, 0,
270 end_pos=(self._game.prng.rand_double() * 288 + 48,
271 self._game.prng.rand_double() * 192 - 64))
272
273 elif time == 7:
274 self.sprite.mirrored = False
275 self.sprite.blendfunc = 0
276 self.sprite.rescale = 0.75, 1.5
277 self.sprite.fade(26, 96)
278 self.sprite.scale_in(26, 0., 2.5)
279
280 elif time == 32:
281 self.state.x = float(self._game.width) / 2. #TODO
282 self.state.y = float(self._game.width) #TODO
283 self.direction = 0
284
285 self.sprite = Sprite()
286 self.anmrunner = ANMRunner(self.anm, 0, self.sprite)
287 self.sprite.alpha = 128
288 self.sprite.rescale = 0., 2.5
289 self.sprite.fade(30, 255)
290 self.sprite.blendfunc = 1
291 self.sprite.scale_in(30, 1., 1.)
292
293 elif time == 61: # respawned
294 self.state.touchable = True
295 self.state.invulnerable_time = 240
296 self.sprite.blendfunc = 0
297 self.sprite.changed = True
298
299 if time > 30:
300 self._game.cancel_bullets()
301
302 if time > 90: # start the bullet hell again
303 self.death_time = 0
304
305 self.anmrunner.run_frame()
306