Mercurial > touhou
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 |