- new level prototype: asterism
[cake] / Cakewalk.py
1 # -*- coding: utf-8 -*-
2
3 from random import randint, choice
4 import math, os, random
5
6 import pygame
7 from pygame.locals import *
8 from pygame import gfxdraw
9
10 from lib.pgfw.pgfw.Game import Game
11 from lib.pgfw.pgfw.GameChild import GameChild
12 from lib.pgfw.pgfw.Sprite import Sprite, RainbowSprite
13 from lib.pgfw.pgfw.Vector import Vector
14 from lib.pgfw.pgfw.Audio import SoundEffect
15 from lib.pgfw.pgfw.Animation import Animation
16 from lib.pgfw.pgfw.extension import *
17
18 LEVEL_POINTS = (
19
20 # (
21 # (106.000000, 451.000000),
22 # (265.000000, 374.000000),
23 # (885.000000, -416.000000),
24 # (739.000000, 357.000000),
25 # (-364.000000, -36.333333),
26 # (96.000000, 38.333333),
27 # (725.000000, 113.000000),
28 # (891.000000, 133.666667),
29 # (1208.000000, 255.333333),
30 # (-594.000000, 444.000000),
31 # ),
32
33 (
34 (53.000000, 413.000000),
35 (150.000000, 400.000000),
36 (31.000000, 196.000000),
37 (420.000000, 200.000000),
38 (495.333333, 197.000000),
39 (566.666667, 198.000000),
40 (563.000000, 79.000000),
41 (572.000000, -19.333333),
42 (716.000000, 62.333333),
43 (700.000000, 359.000000),
44 (688.333333, 462.000000),
45 (486.666667, 257.000000),
46 (475.000000, 310.000000),
47 (431.333333, 560.000000),
48 (267.666667, 262.000000),
49 (194.000000, 457.000000),
50 ),
51
52 (
53 (83.000000, 231.000000),
54 (83.000000, 37.000000),
55 (55.000000, 90.000000),
56 (220.000000, 226.000000),
57 (499.000000, -77.333333),
58 (782.000000, 148.333333),
59 (783.000000, 229.000000),
60 (779.333333, 454.666667),
61 (320.666667, 423.333333),
62 (220.000000, 294.000000),
63 (82.666667, 358.333333),
64 (68.333333, 478.666667),
65 (83.000000, 275.000000),
66 ),
67
68 (
69 (385.000000, 151.000000),
70 (-363.000000, 234.000000),
71 (287.000000, -241.000000),
72 (288.000000, 258.000000),
73 (252.000000, 668.000000),
74 (-331.000000, 259.000000),
75 (396.000000, 299.000000),
76 (1232.666667, 266.000000),
77 (542.333333, 715.000000),
78 (498.000000, 258.000000),
79 (494.333333, -205.000000),
80 (1222.666667, 244.000000),
81 (434.000000, 148.000000),
82 ),
83
84 (
85 (298.000000, 207.000000),
86 (296.000000, 94.000000),
87 (478.000000, 239.000000),
88 (478.000000, 80.000000),
89 (473.333333, 17.333333),
90 (606.666667, 97.666667),
91 (607.000000, 15.000000),
92 (598.666667, -165.333333),
93 (706.333333, -40.666667),
94 (706.000000, -131.000000),
95 (694.000000, -279.000000),
96 (819.000000, -138.000000),
97 (819.000000, -269.000000),
98 (801.666667, -415.666667),
99 (1026.333333, -186.333333),
100 (1016.000000, -538.000000),
101 (1015.333333, -658.000000),
102 (1150.666667, -519.000000),
103 (1151.000000, -671.000000),
104 (1140.333333, -749.333333),
105 (1316.666667, -628.666667),
106 (1316.000000, -756.000000),
107 ),
108
109 (
110 (83.000000, 386.000000),
111 (150.000000, 400.000000),
112 (260.000000, 287.000000),
113 (234.000000, 89.000000),
114 (199.000000, 9.000000),
115 (184.000000, 91.000000),
116 (159.000000, 92.000000),
117 (101.333333, 94.666667),
118 (95.666667, 251.333333),
119 (67.000000, 67.000000),
120 (36.666667, 20.000000),
121 (88.333333, 31.000000),
122 (99.000000, 32.000000),
123 (151.000000, 15.666667),
124 (139.000000, 38.333333),
125 (159.000000, 51.000000),
126 (221.666667, 115.666667),
127 (107.333333, 216.333333),
128 (170.000000, 245.000000),
129 (255.333333, 257.666667),
130 (252.666667, 188.333333),
131 (294.000000, 160.000000),
132 (336.333333, 116.000000),
133 (314.666667, 184.000000),
134 (325.000000, 196.000000),
135 (344.333333, 256.666667),
136 (351.666667, 221.333333),
137 (365.000000, 234.000000),
138 (366.000000, 259.666667),
139 (367.000000, 285.333333),
140 (296.000000, 339.000000),
141 (223.666667, 387.666667),
142 (220.333333, 383.333333),
143 (244.000000, 311.000000),
144 (260.333333, 240.000000),
145 (276.666667, 169.000000),
146 (306.000000, 46.000000),
147 (363.333333, -190.000000),
148 (425.666667, -96.000000),
149 (393.000000, -39.000000),
150 (362.666667, 21.000000),
151 (487.333333, -66.000000),
152 (517.000000, -36.000000),
153 (608.000000, 54.000000),
154 (605.000000, 772.000000),
155 (687.000000, 38.000000),
156 (685.666667, -24.666667),
157 (664.333333, 40.666667),
158 (648.000000, 93.000000),
159 (623.666667, 185.666667),
160 (616.333333, 250.333333),
161 (702.000000, 163.000000),
162 (716.666667, 140.333333),
163 (717.333333, 121.666667),
164 (618.000000, 89.000000),
165 (538.000000, 61.000000),
166 (495.000000, 146.000000),
167 (468.000000, 86.000000),
168 (439.000000, 17.000000),
169 (410.000000, 64.000000),
170 (381.000000, 53.000000),
171 (312.666667, 32.666667),
172 (376.333333, 126.333333),
173 (374.000000, 163.000000),
174 (379.000000, 321.333333),
175 (488.000000, 313.666667),
176 (545.000000, 389.000000),
177 (582.000000, 463.666667),
178 (726.000000, 360.333333),
179 (812.000000, 460.000000),
180 (834.333333, 489.000000),
181 (836.666667, 452.000000),
182 (849.000000, 448.000000),
183 (883.666667, 394.666667),
184 (931.333333, 457.333333),
185 (940.000000, 429.000000),
186 ),
187
188 (
189 (485.000000, 259.000000),
190 (-1191.000000, -246.000000),
191 (303.000000, 711.000000),
192 (452.000000, 375.000000),
193 ),
194
195 (
196 (47.000000, 162.000000),
197 (186.000000, 687.000000),
198 (159.000000, -656.000000),
199 (274.000000, -152.000000),
200 (295.666667, -43.666667),
201 (390.333333, -147.333333),
202 (416.000000, 97.000000),
203 (456.666667, 450.333333),
204 (551.333333, 454.666667),
205 (600.000000, 86.000000),
206 (604.000000, 66.333333),
207 (608.000000, 46.666667),
208 (782.000000, -204.000000),
209 (838.666667, -278.333333),
210 (699.333333, -328.666667),
211 (698.000000, -392.000000),
212 (704.333333, -425.333333),
213 (760.666667, -490.666667),
214 (933.000000, -383.000000),
215 ),
216
217 (
218 (100.000000, 100.000000),
219 (269.000000, 2051.000000),
220 (409.000000, 451.000000),
221 (450.000000, 131.000000),
222 (653.666667, -1520.666667),
223 (661.333333, 156.666667),
224 (943.000000, 173.000000),
225 ),
226
227 (
228 (106.000000, 451.000000),
229 (265.000000, 374.000000),
230 (885.000000, -416.000000),
231 (739.000000, 357.000000),
232 (-338.000000, -36.333333),
233 (96.000000, 38.333333),
234 (725.000000, 113.000000),
235 (891.000000, 133.666667),
236 (1208.000000, 255.333333),
237 (-594.000000, 444.000000),
238 ),
239
240 (
241 (67.000000, 312.000000),
242 (191.000000, 302.000000),
243 (311.000000, 208.000000),
244 (233.000000, 33.000000),
245 (199.000000, -18.000000),
246 (161.000000, 59.000000),
247 (126.000000, 234.000000),
248 (110.666667, 284.666667),
249 (147.333333, 331.333333),
250 (183.000000, 343.000000),
251 (226.666667, 348.333333),
252 (267.333333, 326.666667),
253 (299.000000, 300.000000),
254 (330.000000, 261.333333),
255 (289.000000, 231.666667),
256 (239.000000, 251.000000),
257 (217.333333, 280.333333),
258 (261.666667, 281.666667),
259 (364.000000, 243.000000),
260 (357.666667, 393.000000),
261 (478.333333, 393.000000),
262 (479.000000, 235.000000),
263 (486.333333, 435.333333),
264 (442.666667, 516.666667),
265 (416.000000, 416.000000),
266 (455.333333, 312.000000),
267 (509.666667, 353.000000),
268 (568.000000, 316.000000),
269 (595.666667, 300.666667),
270 (624.333333, 279.333333),
271 (649.000000, 249.000000),
272 (647.000000, 184.333333),
273 (587.000000, 185.666667),
274 (538.000000, 256.000000),
275 (512.000000, 316.666667),
276 (596.000000, 391.333333),
277 (674.000000, 334.000000),
278 (723.333333, 295.000000),
279 (772.666667, 346.000000),
280 (893.000000, 329.000000),
281 ),
282 )
283
284 TITLE_POINTS = (
285 Vector(129.000000, 190.000000),
286 Vector(97.000000, 87.000000),
287 Vector(20.000000, 306.000000),
288 Vector(150.000000, 261.000000),
289 Vector(174.000000, 252.000000),
290 Vector(177.000000, 190.000000),
291 Vector(225.000000, 198.000000),
292 Vector(195.000000, 190.666667),
293 Vector(133.000000, 260.333333),
294 Vector(218.000000, 254.000000),
295 Vector(234.000000, 254.666667),
296 Vector(243.000000, 209.333333),
297 Vector(230.000000, 204.000000),
298 Vector(239.333333, 222.666667),
299 Vector(248.666667, 241.333333),
300 Vector(258.000000, 260.000000),
301 Vector(271.333333, 212.333333),
302 Vector(337.666667, 111.666667),
303 Vector(298.000000, 117.000000),
304 Vector(276.000000, 127.666667),
305 Vector(284.000000, 210.333333),
306 Vector(277.000000, 265.000000),
307 Vector(271.000000, 165.333333),
308 Vector(348.000000, 164.666667),
309 Vector(298.000000, 238.000000),
310 Vector(336.666667, 299.666667),
311 Vector(404.333333, 169.333333),
312 Vector(337.000000, 219.000000),
313 Vector(282.666667, 280.333333),
314 Vector(376.333333, 276.666667),
315 Vector(413.000000, 208.000000),
316 Vector(412.666667, 206.333333),
317 Vector(412.333333, 204.666667),
318 Vector(413.000000, 205.000000),
319 Vector(432.333333, 284.666667),
320 Vector(450.666667, 289.333333),
321 Vector(484.000000, 194.000000),
322 Vector(440.333333, 339.666667),
323 Vector(517.666667, 227.333333),
324 Vector(527.000000, 199.000000),
325 Vector(551.333333, 193.333333),
326 Vector(575.666667, 192.666667),
327 Vector(600.000000, 218.000000),
328 Vector(496.000000, 140.666667),
329 Vector(485.000000, 333.333333),
330 Vector(594.000000, 222.000000),
331 Vector(611.666667, 358.000000),
332 Vector(706.333333, 157.000000),
333 Vector(683.000000, 135.000000),
334 Vector(631.000000, 199.333333),
335 Vector(633.000000, 300.666667),
336 Vector(710.000000, 259.000000),
337 Vector(758.333333, 215.666667),
338 Vector(765.666667, 108.333333),
339 Vector(741.000000, 129.000000),
340 Vector(737.000000, 175.666667),
341 Vector(733.000000, 222.333333),
342 Vector(729.000000, 269.000000),
343 Vector(743.333333, 172.000000),
344 Vector(818.666667, 176.000000),
345 Vector(763.000000, 237.000000),
346 Vector(752.000000, 260.666667),
347 Vector(775.000000, 294.333333),
348 Vector(805.000000, 272.000000),
349 )
350
351
352 class Cakewalk(Game, Animation):
353
354 def __init__(self):
355 pygame.display.set_icon(pygame.image.load("resource/icon/32x32.png"))
356 Game.__init__(self)
357 Animation.__init__(self, self)
358 self.subscribe(self.respond, MOUSEBUTTONDOWN)
359 self.subscribe(self.respond, MOUSEBUTTONUP)
360 self.subscribe(self.respond, MOUSEMOTION)
361 self.subscribe(self.respond, KEYDOWN)
362 self.subscribe(self.respond, KEYUP)
363 self.subscribe(self.respond, JOYBUTTONDOWN)
364 self.subscribe(self.respond, JOYBUTTONUP)
365 self.subscribe(self.respond)
366 self.selected = None
367 self.title_active = True
368 self.end_active = False
369 self.curve_color = pygame.Color(255, 128, 128)
370 self.curve_color_offset = 0
371 self.levels = []
372 for ii, group in enumerate(LEVEL_POINTS):
373 names = ["close circuit", "fishy", "heraldic", "climb",
374 "writing on the wall", "peek", "radar", "wood", "asterism",
375 "farewell"]
376 self.levels.append(Level(self, group, names[ii]))
377 self.current_level_index = 0
378 dsr = self.get_display_surface().get_rect()
379 self.background_layers = []
380 self.background_color_conversion = {(79, 79, 79, 255): (255, 255, 255, 255)}
381 for ii, name in enumerate(("1-far-stars.png", "2-close-stars.png", "3-planets.png")):
382 tile = pygame.image.load(self.get_resource(os.path.join("background", name))).convert()
383 pixels = pygame.PixelArray(tile)
384 for row in pixels:
385 for pixel in row:
386 color = tuple(tile.unmap_rgb(pixel))
387 if color != (255, 255, 255, 255) and color != (79, 79, 79, 255):
388 self.background_color_conversion[color] = get_inverted_color(color)
389 pixels.close()
390 del pixels
391 width = dsr.w + tile.get_width() * 2 - dsr.w % tile.get_width()
392 height = dsr.h + tile.get_height() * 2 - dsr.h % tile.get_height()
393 layer = Sprite(self)
394 surface = pygame.Surface((width, height))
395 if ii != 0:
396 surface.set_colorkey((255, 255, 255))
397 fill_tile(surface, tile)
398 layer.add_frame(surface)
399 self.background_layers.append(layer)
400 self.background_tile_size = Vector(*tile.get_size())
401 self.mouse = Vector(*self.get_display_surface().get_rect().center)
402 self.character = Sprite(self)
403 self.character.load_from_path(self.get_resource("cake-frames"), True)
404 self.character.add_frameset([0], name="standing")
405 self.character.add_frameset(range(1, 9), name="walking", switch=True, framerate=200)
406 self.return_character_to_beginning()
407 self.character_speed = 0
408 self.character_accelerating = False
409 self.editing = False
410 self.bezier_resolution = 60
411 self.previous_curve_hue = 0
412 self.set_curve()
413 self.set_enemies()
414 self.enemy_types = Slicer, Fish, Projector
415 self.enemy_type_index = 0
416 self.set_edit_mode_menu()
417 self.title_music = pygame.mixer.Sound(self.get_resource("azu menu music.ogg"))
418 self.level_music = pygame.mixer.Sound(self.get_resource("azu main theme.ogg"))
419 self.title_music.play(-1, 0, 3000)
420 self.walk_sfx = SoundEffect(self, self.get_resource("azu walk.ogg"), .7)
421 self.collide_sfx = SoundEffect(self, self.get_resource("ow2.ogg"), .6)
422 self.teleport_sfx = SoundEffect(self, self.get_resource("azu teleport 3.ogg"), 1.0)
423 self.ow_sfx = []
424 for path in "ow2.ogg", "ouch.ogg", "ow3.ogg":
425 self.ow_sfx.append(SoundEffect(self, self.get_resource(path), .9))
426 self.controls_diagram = Sprite(self)
427 self.controls_diagram.load_from_path(self.get_resource("controls.png"), True)
428 self.controls_diagram.location.midbottom = dsr.centerx, dsr.bottom - 100
429 self.ow = RainbowSprite(self, pygame.image.load(self.get_resource("ow.png")), 30)
430 self.warp = Sprite(self)
431 base = pygame.Surface((dsr.w * 2, dsr.h * 2), SRCALPHA)
432 scale = 1
433 angle = 0
434 while scale < 150:
435 surface = pygame.transform.rotozoom(self.character.frames[0], angle, scale)
436 frame = base.copy()
437 rect = surface.get_rect()
438 rect.center = frame.get_rect().center
439 frame.blit(surface, rect)
440 self.warp.add_frame(frame)
441 scale += 3
442 angle += 30
443 self.wipe = Sprite(self)
444 painting = get_blinds_frames(pygame.Surface(dsr.size, SRCALPHA), fill=(0, 0, 0))
445 full = pygame.Surface(dsr.size, SRCALPHA)
446 full.fill((0, 0, 0))
447 erasing = get_blinds_frames(full)
448 for frame in painting + erasing:
449 self.wipe.add_frame(frame)
450 self.wipe = Sprite(self)
451 blank = pygame.Surface(dsr.size, SRCALPHA)
452 blank.fill((255, 255, 255, 255))
453 for frame in get_blinds_frames(blank, .04, 5):
454 self.wipe.add_frame(frame)
455 self.dialog_box = Sprite(self)
456 self.dialog_box.load_from_path(self.get_resource("dialog_box.png"), True)
457 self.dialog_box.location.midbottom = dsr.centerx, 449
458 self.register(self.explode, self.continue_dialog, self.blow_up, self.reveal_dialog_text,
459 self.cancel_grab)
460 self.get_configuration().type_declarations.add("bool", "effects", "crumbs")
461 for point in TITLE_POINTS:
462 point.marker = Marker(self, point)
463 self.cursor = Cursor(self)
464 self.reset()
465 pygame.event.clear()
466
467 def cancel_grab(self):
468 # print(pygame.mouse.get_pos())
469 pygame.event.set_grab(False)
470 # print(pygame.mouse.get_pos())
471
472 def set_edit_mode_menu(self):
473 enemy_type = self.enemy_types[self.enemy_type_index]
474 if enemy_type == Slicer:
475 self.edit_mode_selected_enemy = Slicer(self, 0)
476 elif enemy_type == Fish:
477 self.edit_mode_selected_enemy = Fish(self, 0)
478 elif enemy_type == Projector:
479 self.edit_mode_selected_enemy = Projector(self, self.mouse.copy())
480 font = pygame.font.Font(self.get_resource("BPmono.ttf"), 12)
481 text = {
482 "CTRL+T": "active enemy",
483 "SHIFT+CLICK": "add points",
484 "SHIFT+ALT+CLICK": "remove points",
485 "CTRL+CLICK": "add enemy",
486 "CTRL+ALT+CLICK": "remove enemy",
487 "CTRL+P": "print",
488 "CTRL+LEFT/RIGHT": "level",
489 "CTRL+E": "toggle edit",
490 "CTRL+UP/DOWN": "enemy speed",
491 "CTRL+SHIFT+UP/DOWN": "enemy distance"
492 }
493 self.edit_menu = Sprite(self)
494 frame = pygame.Surface((self.get_display_surface().get_width(), 24))
495 self.edit_menu.add_frame(frame)
496 frame.fill(pygame.Color("black"))
497 x = self.edit_mode_selected_enemy.location.w
498 y = 0
499 ii = 0
500 for combo, action in text.items():
501 fg = (pygame.Color("black"), pygame.Color("white"))[ii % 2]
502 bg = (pygame.Color("white"), pygame.Color("black"))[ii % 2]
503 combo_surface = render_box(font, combo, True, fg, bg, padding=(3, 0))
504 action_surface = render_box(font, action, True, fg, bg, padding=(3, 0))
505 if x + combo_surface.get_width() + action_surface.get_width() > frame.get_width():
506 x = 0
507 y += 12
508 for surface in combo_surface, action_surface:
509 frame.blit(surface, (x, y))
510 x += surface.get_width()
511 ii += 1
512 frame.set_alpha(128)
513
514 def reset(self):
515 self.title_active = True
516 self.end_active = False
517 self.ow.halt(self.ow.toggle_hidden)
518 self.ow.hide()
519 self.crumbs = []
520 if self.level_music.get_num_channels() > 0:
521 self.level_music.fadeout(3000)
522 if self.title_music.get_num_channels() == 0:
523 self.title_music.play(-1, 0, 1000)
524 else:
525 self.title_music.stop()
526 self.title_music.play(-1)
527 self.set_level(0)
528 self.cancel_grab()
529 self.character_speed = 1
530 self.time_elapsed = 0
531 self.is_dialog = False
532 path = "resource/high-scores"
533 self.high_scores = []
534 if os.path.exists(path):
535 results = sorted(list(map(int, open(path, "r"))))[:5]
536 dsr = self.get_display_surface().get_rect()
537 locations = get_points_on_line((0, 0), dsr.topright, len(results) + 3)[1:-1]
538 size = 18
539 font = pygame.font.Font(self.get_resource("BPmono.ttf"), size)
540 top = Sprite(self)
541 top.add_frame(render_box(font, " BEST ", True, (0, 0, 0), (200, 200, 200), (0, 0, 0)))
542 top.location.midtop = locations[0].x, locations[0].y - 2
543 self.high_scores.append(top)
544 for ii, time in enumerate(results):
545 result = self.get_time_sprite(time, size, True)
546 result.location.midtop = locations[ii + 1].x, locations[ii + 1].y - 2
547 self.high_scores.append(result)
548
549 def return_character_to_beginning(self):
550 self.next_point_index = 0
551 if not self.end_active:
552 self.character_pos = self.get_control_points()[0].copy()
553 self.character_speed = 0
554 self.character_accelerating = False
555
556 def get_control_points(self):
557 if self.title_active:
558 return TITLE_POINTS
559 elif self.end_active or self.is_dialog:
560 return []
561 else:
562 return self.get_current_level().control_points
563
564 def set_enemies(self):
565 self.enemies = []
566 if not self.title_active and not self.end_active:
567 self.get_current_level().set_enemies()
568
569 def reset_enemies(self):
570 for enemy in self.enemies:
571 enemy.reset()
572
573 def get_current_level(self):
574 return self.levels[self.current_level_index]
575
576 def respond(self, event):
577 if event.type == MOUSEBUTTONDOWN and event.button == 1 and (self.editing or self.title_active):
578 found = self.find_grabbable_control_point()
579 if found:
580 self.selected = found
581 self.cursor.set_frameset("closed")
582 if self.editing:
583 self.selected.marker.blink()
584 else:
585 self.selected.marker.grab()
586 if not found and pygame.key.get_mods() & KMOD_SHIFT and self.editing:
587 if pygame.key.get_mods() & KMOD_ALT:
588 for _ in range(3):
589 if len(self.get_control_points()) > 4:
590 self.get_control_points().pop()
591 else:
592 points = get_points_on_line(self.get_control_points()[-1], self.mouse, 4)
593 for point in points:
594 point.marker = Marker(self, point)
595 point.marker.grab()
596 self.get_control_points().extend(points[1:])
597 self.set_curve()
598 self.reset_enemies()
599 if not found and pygame.key.get_mods() & KMOD_CTRL and self.editing:
600 if pygame.key.get_mods() & KMOD_ALT:
601 closest = self.get_closest_enemy()
602 if closest is not None:
603 self.enemies.remove(closest)
604 else:
605 min_distance = None
606 for ii, point in enumerate(self.curve):
607 translated = self.translate_point_to_screen(point)
608 distance = get_distance(translated, self.mouse)
609 if min_distance is None or distance < min_distance:
610 min_distance = distance
611 closest_ii = ii
612 offset = float(closest_ii) / len(self.curve)
613 enemy_type = self.enemy_types[self.enemy_type_index]
614 if enemy_type == Slicer:
615 self.enemies.append(Slicer(self, offset))
616 elif enemy_type == Fish:
617 self.enemies.append(Fish(self, offset))
618 elif enemy_type == Projector:
619 self.enemies.append(Projector(self, self.mouse.copy()))
620 self.reset_enemies()
621 elif event.type == JOYBUTTONDOWN:
622 self.handle_action_button_press()
623 elif event.type == JOYBUTTONUP:
624 self.handle_action_button_release()
625 elif event.type == KEYDOWN:
626 if event.key == K_e and pygame.key.get_mods() & KMOD_CTRL:
627 self.editing = not self.editing
628 self.set_curve()
629 for point in self.get_control_points():
630 if self.editing:
631 point.marker.grab()
632 else:
633 point.marker.hide()
634 # if self.editing:
635 # pygame.event.set_grab(True)
636 # else:
637 # pygame.event.set_grab(False)
638 elif event.key == K_SPACE and not self.is_playing(self.blow_up):
639 self.handle_action_button_press()
640 elif event.key == K_p and pygame.key.get_mods() & KMOD_CTRL and self.editing:
641 for point in self.get_control_points():
642 print(point)
643 for enemy in self.enemies:
644 if not isinstance(enemy, Projectile) and not isinstance(enemy, Fire):
645 print(enemy)
646 elif event.key == K_RIGHT and pygame.key.get_mods() & KMOD_CTRL:
647 self.increment_level(1)
648 elif event.key == K_LEFT and pygame.key.get_mods() & KMOD_CTRL:
649 self.increment_level(-1)
650 elif (event.key == K_UP or event.key == K_DOWN) and pygame.key.get_mods() & KMOD_CTRL and self.editing:
651 closest = self.get_closest_enemy()
652 if closest is not None:
653 mod = 1 if event.key == K_UP else -1
654 if pygame.key.get_mods() & KMOD_SHIFT:
655 if isinstance(closest, Slicer):
656 closest.increase_stray(2.5 * mod)
657 elif isinstance(closest, Fish):
658 closest.increase_radius(2 * mod)
659 elif isinstance(closest, Projector):
660 closest.increase_frequency(250 * -mod)
661 else:
662 if isinstance(closest, Slicer):
663 closest.increase_speed(.25 * mod)
664 elif isinstance(closest, Fish):
665 closest.increase_speed(math.pi / 256.0 * mod)
666 elif isinstance(closest, Projector):
667 closest.increase_speed(.25 * mod)
668 self.reset_enemies()
669 elif event.key == K_t and pygame.key.get_mods() & KMOD_CTRL and self.editing:
670 self.enemy_type_index = (self.enemy_type_index + 1) % len(self.enemy_types)
671 self.set_edit_mode_menu()
672 elif event.type == KEYUP and event.key == K_SPACE and not self.is_playing(self.blow_up):
673 self.handle_action_button_release()
674 elif event.type == MOUSEBUTTONUP and event.button == 1:
675 if self.selected is not None:
676 if self.editing:
677 self.selected.marker.grab()
678 else:
679 self.selected.marker.hide()
680 self.selected = None
681 if not self.editing:
682 self.set_curve(True)
683 elif event.type == MOUSEMOTION:
684 self.halt(self.cancel_grab)
685 mouse_motion = Vector(*event.rel)
686 if abs(mouse_motion.x) + abs(mouse_motion.y) < 25:
687 dsr = self.get_display_surface().get_rect()
688 self.mouse += mouse_motion
689 if self.mouse.x > dsr.w:
690 self.mouse.x -= dsr.w
691 elif self.mouse.x < 0:
692 self.mouse.x += dsr.w
693 if self.mouse.y > dsr.h:
694 self.mouse.y -= dsr.h
695 elif self.mouse.y < 0:
696 self.mouse.y += dsr.h
697 if self.selected is not None:
698 self.selected += mouse_motion
699 pygame.event.set_grab(True)
700 self.play(self.cancel_grab, delay=800, play_once=True)
701 elif self.get_delegate().compare(event, "reset-game"):
702 self.reset()
703
704 def find_grabbable_control_point(self):
705 for p in self.get_control_points():
706 translated = self.translate_point_to_screen(p)
707 if get_distance(translated, self.mouse) < 10:
708 return p
709 return None
710
711 def handle_action_button_press(self):
712 if self.title_active:
713 self.title_active = False
714 self.increment_level(0)
715 self.title_music.fadeout(3000)
716 self.level_music.play(-1, 0, 3000)
717 self.teleport_sfx.play()
718 elif self.end_active:
719 self.end_active = False
720 self.reset()
721 elif self.is_dialog:
722 if self.is_playing(self.reveal_dialog_text):
723 self.reveal_all_text()
724 elif self.dialog_line_index == len(self.dialog) - 1:
725 self.play(self.blow_up, increment=1)
726 else:
727 self.dialog_line_index += 1
728 self.dialog_text = self.dialog[self.dialog_line_index]
729 self.dialog_text_index = 0
730 self.play(self.reveal_dialog_text)
731 else:
732 self.character_accelerating = True
733
734 def handle_action_button_release(self):
735 self.character_accelerating = False
736
737 def get_closest_enemy(self):
738 min_distance = None
739 closest = None
740 for enemy in self.enemies:
741 if not isinstance(enemy, Projectile) and not isinstance(enemy, Fire):
742 center = self.translate_point_to_screen(enemy.center)
743 distance = get_distance(center, self.mouse)
744 if min_distance is None or distance < min_distance:
745 min_distance = distance
746 closest = enemy
747 return closest
748
749 def increment_level(self, increment):
750 self.set_level((self.current_level_index + increment) % len(self.levels))
751
752 def set_level(self, index):
753 self.current_level_index = index
754 self.crumbs = []
755 start = self.set_curve()
756 self.set_enemies()
757 self.return_character_to_beginning()
758 if not self.end_active:
759 self.set_goal(start)
760
761 def set_curve(self, use_previous_color=False):
762 self.curve = []
763 points = self.get_control_points()
764 for ii in range(0, len(points) - 3, 3):
765 self.curve.extend(compute_bezier_points(points[ii:ii + 4], self.bezier_resolution))
766 self.curve_plate = Sprite(self)
767 light_colors = []
768 if self.editing or self.selected is not None or use_previous_color:
769 start = self.previous_curve_hue
770 else:
771 start = randint(0, 359)
772 self.previous_curve_hue = start
773 for hue in range(start, start + 60, 4):
774 light_colors.append(get_hsla_color(hue, 100, 50, 100))
775 groups = self.translate_points_to_screen(self.curve)
776 if self.editing or self.selected is not None:
777 frame_count = 1
778 else:
779 frame_count = 30
780 for offset in range(frame_count):
781 frame = pygame.Surface(self.get_display_surface().get_size(), SRCALPHA)
782 self.curve_plate.add_frame(frame)
783 for group in groups:
784 if group:
785 # pygame.draw.lines(frame, self.curve_color, False, group, 7)
786 # for ii in range(2, len(group) - 1, 2):
787 # pygame.draw.aaline(frame, (255, 255, 255), group[ii], group[ii - 2], 0)
788 for ii, point in enumerate(group[:-1]):
789 # angle = get_angle(point, group[ii + 1]) + math.pi * .5
790 # left_start = [int(round(n)) for n in get_endpoint(point, angle - math.pi * .5, 3, False)]
791 # left_end = [int(round(n)) for n in get_endpoint(group[ii + 1], angle - math.pi * .5, 3, False)]
792 if (offset - ii) // 15 % 2:
793 color = light_colors[(offset - ii) % 15]
794 # color = self.curve_color
795 else:
796 color = pygame.Color("white")
797 # pygame.draw.line(frame, color, left_start, left_end, 2)
798 # right_start = [int(round(n)) for n in get_endpoint(point, angle + math.pi * .5, 3, False)]
799 # right_end = [int(round(n)) for n in get_endpoint(group[ii + 1], angle + math.pi * .5, 3, False)]
800 # pygame.draw.line(frame, color, right_start, right_end, 2)
801 pygame.draw.aaline(frame, color, point, group[ii + 1], 1)
802 pygame.draw.line(frame, color, point, group[ii + 1], 1)
803 # right = get_endpoint(point, angle + math.pi * .75, 5, False)
804 # pygame.draw.line(ds, [self.curve_color, pygame.Color("white")][(ii + self.curve_color_offset) % 2],
805 # point, group[ii + 1], 3)
806 # pygame.draw.lines(ds, colors[ii % 10], False, (left, point, right), 2)
807 # for ii, point in enumerate(group):
808 # pygame.draw.circle(ds, colors[ii % 10], (point.x, point.y), 5)
809 # gfxdraw.aacircle(ds, point.x, point.y, 5, colors[ii % 10])
810 self.curve_plate.location.center = self.get_display_surface().get_rect().center
811 return start
812
813 def get_curve_index_from_offset(self, offset):
814 return int(round(len(self.curve) * offset))
815
816 def is_final_level(self):
817 return self.current_level_index == len(self.levels) - 1
818
819 def get_minutes_and_seconds(self, time):
820 minutes = time // 1000 // 60
821 seconds = time // 1000 - minutes * 60
822 return minutes, seconds
823
824 def get_time_sprite(self, time, size, box=False):
825 minutes, seconds = self.get_minutes_and_seconds(time)
826 font = pygame.font.Font(self.get_resource("BPmono.ttf"), size)
827 text = " %im %is " % (minutes, seconds)
828 if box:
829 surface = render_box(font, text, True, (0, 0, 0), (200, 200, 200), (0, 0, 0))
830 else:
831 surface = font.render(text, True, pygame.Color(255, 255, 255))
832 sprite = Sprite(self)
833 sprite.add_frame(surface)
834 return sprite
835
836 def explode(self, center, radius=1):
837 max_r = 40
838 ring = Sprite(self)
839 frame = pygame.Surface([radius * 2 + 2] * 2, SRCALPHA)
840 ring.add_frame(frame)
841 for ii, sub_radius in enumerate(range(radius, 0, -5)):
842 if ii <= 3:
843 alpha = int((1 - float(sub_radius) / max_r) * 255)
844 color = (
845 pygame.Color(255, 192, 18, alpha), pygame.Color(255, 128, 128, alpha),
846 pygame.Color(255, 255, 128, alpha))[ii % 3]
847 pygame.draw.circle(
848 frame,
849 color,
850 ring.location.center, sub_radius, 1)
851 ring.location.center = center
852 ring.update()
853 if radius < max_r:
854 self.play(self.explode, play_once=True, center=center, radius=radius + 1)
855
856 def set_goal(self, start):
857 self.goal = Sprite(self)
858 location = self.translate_point_to_screen(self.curve[-1])
859 start = randint(60, 359)
860 for offset in range(6):
861 frame = pygame.Surface((26, 48), SRCALPHA)
862 for ii, y in enumerate(range(40, 7, -1)):
863 hue = range(start, start - 60, -10)[(ii - offset) % 6]
864 alpha = int(round(y / 40.0 * 100))
865 color = get_hsla_color(hue, 100, 50, alpha)
866 gfxdraw.filled_ellipse(frame, 13, y, 13, 8, color)
867 # gfxdraw.aaellipse(frame, 13, y, 13, 8, color)
868 self.goal.add_frame(frame)
869 self.goal.location.midbottom = location.x, location.y + 8
870
871 def blow_up(self, increment):
872 if not self.warp.is_playing() and not self.wipe.is_playing():
873 self.warp.unhide()
874 self.warp.get_current_frameset().reset()
875 self.warp.play()
876 elif self.warp.is_playing():
877 self.warp.location.center = self.character.location.center
878 self.warp.update()
879 frameset = self.warp.get_current_frameset()
880 if frameset.current_index == frameset.length() - 1:
881 self.warp.halt()
882 self.warp.hide()
883 self.wipe.hide()
884 self.wipe.get_current_frameset().reset()
885 self.wipe.play()
886 if not self.end_active and self.is_final_level():
887 self.end_active = True
888 fp = open("resource/high-scores", "a")
889 fp.write("%i\n" % self.time_elapsed)
890 fp.close()
891 dsr = self.get_display_surface().get_rect()
892 self.character_pos = Vector(*dsr.center)
893 self.time_result = self.get_time_sprite(self.time_elapsed, 40)
894 self.time_result.location.midtop = dsr.centerx, 287
895 self.level_music.fadeout(2000)
896 self.title_music.play(-1, 0, 500)
897 # if increment and self.current_level_index == 0 and not self.is_dialog and not self.title_active:
898 # self.start_dialog()
899 # else:
900 # self.is_dialog = False
901 # self.halt(self.continue_dialog)
902 self.increment_level(increment)
903 else:
904 intermediate = pygame.Surface(self.get_display_surface().get_size(), SRCALPHA)
905 intermediate.blit(self.warp.get_current_frame(), self.warp.location)
906 intermediate.blit(self.wipe.get_current_frame(), (0, 0), None, BLEND_RGBA_MIN)
907 self.get_display_surface().blit(intermediate, (0, 0))
908 self.warp.update()
909 self.wipe.update()
910 frameset = self.wipe.get_current_frameset()
911 if frameset.current_index == frameset.length() - 1:
912 self.wipe.halt()
913 self.halt(self.blow_up)
914
915 def start_dialog(self):
916 self.return_character_to_beginning()
917 self.is_dialog = True
918 self.character_pos.place(290, 250)
919 self.dialog_enemy = Fish(self, 0)
920 for ii in range(len(self.dialog_enemy.frames)):
921 for _ in range(4):
922 self.dialog_enemy.frames[ii] = pygame.transform.scale2x(
923 self.dialog_enemy.frames[ii])
924 self.dialog_enemy.location.center = 430, 90
925 self.dialog = (
926 # "heh heh heh",
927 # "I need these roads for my transactions"
928 # "I am a sovereign merchant so I am above the law",
929 # "take the long way around or go through the sun lol"
930 "what are you transporting by the way",
931 "oh living bodies",
932 "heh heh heh",
933 "I actually get a good price on skeletons rofl"
934 )
935 self.dialog_text = self.dialog[0]
936 self.dialog_line_index = 0
937 self.dialog_text_index = 0
938 self.dialog_enemy.mirror()
939 self.set_curve()
940 if self.character.mirrored:
941 self.character.mirror()
942 self.play(self.continue_dialog)
943 self.play(self.reveal_dialog_text)
944
945 def continue_dialog(self):
946 self.dialog_enemy.update(act=False)
947 frame = self.dialog_box.get_current_frame().copy()
948 subsurface = self.get_display_surface().subsurface(self.dialog_box.location)
949 frame.blit(subsurface, (0, 0), None, BLEND_RGBA_MIN)
950 frame_pixels = pygame.PixelArray(frame)
951 for color, replacement in self.background_color_conversion.items():
952 frame_pixels.replace(color, replacement)
953 frame_pixels.close()
954 del frame_pixels
955 # if not self.is_playing(self.blow_up):
956 # dialog_box_pixels = pygame.PixelArray(frame)
957 # bg_pixels = pygame.PixelArray(
958 # self.get_display_surface().subsurface(self.dialog_box.location))
959 # for x in range(len(dialog_box_pixels)):
960 # for y in range(len(dialog_box_pixels[0])):
961 # if bg_pixels[x][y] != 0x707070:
962 # bg_hsva = frame.unmap_rgb(bg_pixels[x][y]).hsva
963 # dialog_box_hsva = frame.unmap_rgb(dialog_box_pixels[x][y]).hsva
964 # color = get_hsva_color(
965 # (bg_hsva[0] + 180) % 360, bg_hsva[1], dialog_box_hsva[2], dialog_box_hsva[3])
966 # dialog_box_pixels[x][y] = color
967 # del dialog_box_pixels
968 # del bg_pixels
969 self.get_display_surface().blit(frame, self.dialog_box.location)
970 font = pygame.font.Font(self.get_resource("BPmono.ttf"), 39)
971 text = self.dialog_text[:self.dialog_text_index + 1]
972 offset = 1
973 for color in pygame.Color(203, 107, 19), pygame.Color(255, 104, 104):
974 layer = Sprite(self)
975 surface = get_wrapped_text_surface(font, text, frame.get_width() - 50,
976 True, color)
977 layer.add_frame(surface)
978 layer.location.center = self.dialog_box.location.center
979 layer.move(offset, offset)
980 layer.update()
981 offset -= 1
982
983 def reveal_dialog_text(self):
984 self.dialog_text_index += 1
985 if self.dialog_text_index >= len(self.dialog_text):
986 self.reveal_all_text()
987
988 def reveal_all_text(self):
989 self.dialog_text_index = len(self.dialog_text) - 1
990 self.halt(self.reveal_dialog_text)
991
992 def update(self):
993 ds = self.get_display_surface()
994 dsr = ds.get_rect()
995 # for tile in self.background:
996 # tile.move(-2, -2)
997 # if tile.location.right <= 0:
998 # tile.move(self.background_size.x, 0)
999 # if tile.location.bottom <= 0:
1000 # tile.move(0, self.background_size.y)
1001 # tile.update()
1002 for ii, layer in enumerate(self.background_layers):
1003 layer.move(*[-[.1, .5, 2.5][ii]] * 2)
1004 if layer.location.right <= dsr.right:
1005 layer.move(self.background_tile_size.x, 0)
1006 if layer.location.bottom <= dsr.bottom:
1007 layer.move(0, self.background_tile_size.y)
1008 layer.update()
1009 if self.editing or self.title_active:
1010 # mouse_motion = Vector(*pygame.mouse.get_rel())
1011 # self.mouse += mouse_motion
1012 # if self.mouse.x > dsr.w:
1013 # self.mouse.x -= dsr.w
1014 # elif self.mouse.x < 0:
1015 # self.mouse.x += dsr.w
1016 # if self.mouse.y > dsr.h:
1017 # self.mouse.y -= dsr.h
1018 # elif self.mouse.y < 0:
1019 # self.mouse.y += dsr.h
1020 if self.selected is not None:
1021 # self.selected += mouse_motion
1022 # pygame.draw.circle(ds, pygame.Color("green"), self.translate_point_to_screen(self.selected), 10)
1023 self.set_curve()
1024 self.reset_enemies()
1025 for p in self.get_control_points():
1026 # pygame.draw.circle(ds, pygame.Color("blue"), self.translate_point_to_screen(p), 4)
1027 if random.random() < .001 and not self.editing:
1028 p.marker.shine()
1029 p.marker.update()
1030 if self.editing:
1031 for group in self.translate_points_to_screen(self.get_control_points()):
1032 pygame.draw.lines(ds, pygame.Color(200, 200, 200), False, group)
1033 if self.mouse.y < dsr.centery:
1034 self.edit_menu.location.bottom = dsr.bottom
1035 else:
1036 self.edit_menu.location.top = dsr.top
1037 self.edit_mode_selected_enemy.location.topleft = self.edit_menu.location.topleft
1038 self.edit_menu.update()
1039 self.edit_mode_selected_enemy.update(act=False)
1040 if not self.title_active and not self.is_playing(self.blow_up) and not self.is_dialog:
1041 for enemy in self.enemies:
1042 if self.character.collide_mask(enemy) and self.next_point_index > 0:
1043 self.play(self.explode, play_once=True, center=self.character.location.center)
1044 self.return_character_to_beginning()
1045 self.ow.location.bottomleft = self.translate_point_to_screen(
1046 Vector(*self.character.location.topright))
1047 self.ow.unhide()
1048 self.ow.play(self.ow.toggle_hidden, play_once=True, delay=1800)
1049 pile = Sprite(self)
1050 self.crumbs.append(pile)
1051 frame = pygame.Surface((20, 20), SRCALPHA)
1052 for _ in range(randint(3, 6)):
1053 color = get_hsla_color(randint(50, 70), randint(70, 100), randint(50, 90))
1054 frame.fill(
1055 color, Rect((randint(0, 17), randint(0, 17)), [randint(1, 3)] * 2))
1056 pile.add_frame(frame)
1057 pile.location.center = self.character.location.midbottom
1058 choice(self.ow_sfx).play(x=self.character.location.centerx)
1059 if isinstance(enemy, Projectile):
1060 self.enemies.remove(enemy)
1061 break
1062 self.curve_plate.update()
1063 if self.get_configuration("effects", "crumbs"):
1064 for pile in self.crumbs:
1065 pile.update()
1066 if self.editing or self.title_active:
1067 self.cursor.location.center = self.mouse
1068 if not self.selected:
1069 if self.find_grabbable_control_point():
1070 self.cursor.set_frameset("flashing")
1071 else:
1072 self.cursor.set_frameset("open")
1073 self.cursor.update()
1074 if not self.title_active and not self.is_playing(self.blow_up):
1075 if self.character_accelerating:
1076 self.character_speed += .5 + abs(self.character_speed) * .125
1077 else:
1078 self.character_speed -= .25 + abs(self.character_speed) * .1
1079 if self.character_speed > 7:
1080 self.character_speed = 7
1081 elif self.character_speed < -4:
1082 self.character_speed = -4
1083 else:
1084 self.character_speed = 1
1085 distance_remaining = abs(self.character_speed)
1086 while distance_remaining and not self.is_playing(self.blow_up) and not self.end_active and not self.is_dialog:
1087 if self.character_speed < 0 and self.next_point_index == 0:
1088 self.character_speed = 0
1089 break
1090 elif self.character_speed > 0 and self.next_point_index > len(self.curve) - 1:
1091 self.character_speed = 0
1092 if self.title_active or self.is_final_level() or self.editing:
1093 increment = 0
1094 else:
1095 increment = 1
1096 self.play(self.blow_up, increment=increment)
1097 self.teleport_sfx.play()
1098 break
1099 if self.character_speed > 0:
1100 next_point = self.curve[self.next_point_index]
1101 else:
1102 next_point = self.curve[self.next_point_index - 1]
1103 distance = get_distance(self.character_pos, next_point)
1104 if distance <= distance_remaining:
1105 distance_remaining -= distance
1106 self.character_pos = next_point.copy()
1107 if self.character_speed < 0:
1108 self.next_point_index -= 1
1109 else:
1110 self.next_point_index += 1
1111 else:
1112 step = get_step(self.character_pos, next_point, distance_remaining)
1113 if step.x <= -2 and not self.character.mirrored:
1114 self.character.mirror()
1115 elif step.x > 2 and self.character.mirrored:
1116 self.character.mirror()
1117 self.character_pos.move(*step)
1118 distance_remaining = 0
1119 if self.next_point_index == 0 and not self.title_active:
1120 self.character.set_frameset("standing")
1121 else:
1122 self.character.set_frameset("walking")
1123 if not self.title_active:
1124 framerate = int((1 - self.character_speed / 7.0) * 200) + 50
1125 else:
1126 framerate = 200
1127 self.character.set_framerate(framerate)
1128 self.character.location.midbottom = self.translate_point_to_screen(self.character_pos)
1129 if not self.end_active and not self.is_dialog:
1130 self.goal.update()
1131 if not self.title_active and not self.is_dialog:
1132 if not self.is_playing(self.blow_up):
1133 self.time_elapsed += self.time_filter.get_last_frame_duration()
1134 outgoing = []
1135 for enemy in self.enemies:
1136 if self.editing:
1137 pygame.draw.circle(ds, pygame.Color("black"),
1138 list(map(int, self.translate_point_to_screen(enemy.center))), 3)
1139 if self.is_playing(self.blow_up) and (
1140 isinstance(enemy, Projectile) or isinstance(enemy, Projector)):
1141 act = False
1142 else:
1143 act = True
1144 enemy.update(act=act)
1145 if isinstance(enemy, Projectile) and enemy.marked_to_delete:
1146 outgoing.append(enemy)
1147 for enemy in outgoing:
1148 self.enemies.remove(enemy)
1149 elif self.title_active:
1150 self.controls_diagram.update()
1151 for score in self.high_scores:
1152 score.update()
1153 if self.end_active:
1154 self.time_result.update()
1155 self.character.update()
1156 self.ow.update()
1157 Animation.update(self)
1158
1159 def translate_points_to_screen(self, points):
1160 groups = [[]]
1161 previous_x_sector, previous_y_sector, previous_point = None, None, None
1162 dsr = self.get_display_surface().get_rect()
1163 for point in points:
1164 x_sector = point.x // dsr.w
1165 y_sector = point.y // dsr.h
1166 if previous_x_sector is not None and (
1167 x_sector != previous_x_sector or y_sector != previous_y_sector):
1168 copy = point.copy()
1169 previous_copy = previous_point.copy()
1170 groups[-1].append(copy)
1171 if x_sector != previous_x_sector or y_sector != previous_y_sector:
1172 copy.x = copy.x % dsr.w + (x_sector - previous_x_sector) * dsr.w
1173 previous_copy.x = previous_copy.x % dsr.w + (previous_x_sector - x_sector) * dsr.w
1174 copy.y = copy.y % dsr.h + (y_sector - previous_y_sector) * dsr.h
1175 previous_copy.y = previous_copy.y % dsr.h + (previous_y_sector - y_sector) * dsr.h
1176 groups.append([previous_copy])
1177 previous_x_sector = x_sector
1178 previous_y_sector = y_sector
1179 previous_point = point
1180 copy = self.translate_point_to_screen(point)
1181 groups[-1].append(copy)
1182 return groups
1183
1184 def translate_point_to_screen(self, point):
1185 dsr = self.get_display_surface().get_rect()
1186 copy = point.copy()
1187 copy.x = int(round(copy.x % dsr.w))
1188 copy.y = int(round(copy.y % dsr.h))
1189 return copy
1190
1191
1192 class Marker(Sprite):
1193
1194 def __init__(self, parent, point):
1195 Sprite.__init__(self, parent)
1196 for start in mirrored(range(2, 16, 2)):
1197 frame = pygame.Surface((33, 33), SRCALPHA)
1198 for radius, alpha in get_percent_way(range(16, 2, -2)):
1199 if radius <= start:
1200 pygame.draw.circle(frame, (255, 255, 255, alpha * 100), (16, 16), radius)
1201 self.add_frame(frame)
1202 self.add_frameset([len(self.frames) // 2], name="grabbed")
1203 self.hide()
1204 self.point = point
1205 self.shining = False
1206
1207 def shine(self):
1208 if not self.shining:
1209 self.unhide()
1210 self.set_frameset(0)
1211 self.get_current_frameset().reset()
1212 self.shining = True
1213
1214 def grab(self):
1215 self.shining = False
1216 self.set_frameset("grabbed")
1217 self.unhide()
1218
1219 def blink(self):
1220 self.shining = False
1221 self.set_frameset(0)
1222 self.unhide()
1223
1224 def update(self):
1225 self.location.center = self.get_game().translate_point_to_screen(self.point)
1226 Sprite.update(self)
1227 if self.shining and self.get_current_frameset().current_index == 0:
1228 self.hide()
1229
1230
1231 class Cursor(RainbowSprite):
1232
1233 def __init__(self, parent):
1234 RainbowSprite.__init__(self, parent, hue_shift=60)
1235 self.load_from_path(self.get_resource("cursor"), True)
1236 self.add_frameset([0], name="open")
1237 self.add_frameset([1], name="closed")
1238 self.set_frames(get_color_swapped_surface(self.frames[0], (255, 255, 255), (255, 180, 180)))
1239 self.add_frameset(range(2, len(self.frames)), name="flashing")
1240 self.set_frameset("open")
1241
1242
1243 class Enemy(Sprite):
1244
1245 def __init__(self, parent, framerate=None):
1246 Sprite.__init__(self, parent, framerate)
1247 self.shadow_frame = None
1248
1249 def reset(self):
1250 pass
1251
1252 def act(self):
1253 pass
1254
1255 def update(self, act=True):
1256 if act:
1257 self.act()
1258 if self.shadow_frame is not None:
1259 mask = pygame.Surface(self.shadow_frame.get_size(), SRCALPHA)
1260 mask.fill((0, 0, 0))
1261 mask.blit(self.shadow_frame, (0, 0), None, BLEND_RGBA_MIN)
1262 shadow = pygame.Surface(mask.get_size())
1263 shadow.fill((255, 0, 0))
1264 shadow.set_colorkey((255, 0, 0))
1265 shadow.blit(mask, (0, 0))
1266 shadow.set_alpha(80)
1267 self.get_display_surface().blit(shadow, self.location.move(3, 3))
1268 Sprite.update(self)
1269 self.shadow_frame = self.get_current_frame()
1270
1271
1272 class Slicer(Enemy):
1273
1274 def __init__(self, parent, offset, speed=5, stray=60):
1275 Enemy.__init__(self, parent, 500)
1276 self.offset = offset
1277 self.speed = speed
1278 self.stray = stray
1279 self.load_from_path(self.get_resource("slicer"), True)
1280 angle = self.get_angle()
1281 self.end = get_endpoint(self.center, angle, self.stray, False)
1282 self.start = get_endpoint(self.center, angle + math.pi, self.stray, False)
1283 self.reset()
1284 self.toward_end = True
1285
1286 def reset(self):
1287 self.pos = Vector(*self.start)
1288
1289 def increase_speed(self, increase):
1290 self.speed += increase
1291
1292 def increase_stray(self, stray):
1293 self.stray += stray
1294
1295 def __repr__(self):
1296 return "<Slicer %.5f %.5f %.5f>" % (self.offset, self.speed, self.stray)
1297
1298 def get_angle(self):
1299 ii = self.get_game().get_curve_index_from_offset(self.offset)
1300 curve = self.get_game().curve
1301 self.center = curve[ii]
1302 return get_angle(curve[max(0, ii - 1)], curve[min(len(curve) - 1, ii + 1)], True) - math.pi / 2
1303
1304 def act(self):
1305 angle = self.get_angle()
1306 self.end = get_endpoint(self.center, angle, self.stray, False)
1307 self.start = get_endpoint(self.center, angle + math.pi, self.stray, False)
1308 speed = self.speed
1309 if self.toward_end:
1310 distance = get_distance(self.pos, self.end)
1311 if distance < speed:
1312 self.pos = self.end.copy()
1313 speed -= distance
1314 self.toward_end = False
1315 else:
1316 distance = get_distance(self.pos, self.start)
1317 if distance < speed:
1318 self.pos = self.start.copy()
1319 speed -= distance
1320 self.toward_end = True
1321 if self.toward_end:
1322 step = get_step(self.pos, self.end, speed)
1323 self.pos.move(*step)
1324 else:
1325 step = get_step(self.pos, self.start, speed)
1326 self.pos.move(*step)
1327 self.location.center = self.get_game().translate_point_to_screen(self.pos)
1328
1329
1330 class Fish(Enemy):
1331
1332 def __init__(self, parent, offset, speed=math.pi / 32.0, radius=30):
1333 Enemy.__init__(self, parent, 300)
1334 self.offset = offset
1335 self.speed = speed
1336 self.radius = radius
1337 self.load_from_path(self.get_resource("fish"), True)
1338 ii = self.get_game().get_curve_index_from_offset(self.offset)
1339 self.center = self.get_game().curve[ii]
1340 self.reset()
1341
1342 def reset(self):
1343 self.angle = 0
1344
1345 def __repr__(self):
1346 return "<Fish %.5f %.6f %.5f>" % (self.offset, self.speed, self.radius)
1347
1348 def increase_speed(self, increase):
1349 self.speed += increase
1350
1351 def increase_radius(self, increase):
1352 self.radius += increase
1353
1354 def act(self):
1355 ii = self.get_game().get_curve_index_from_offset(self.offset)
1356 self.center = self.get_game().curve[ii]
1357 self.angle += self.speed
1358 self.location.center = self.get_game().translate_point_to_screen(
1359 get_point_on_circle(self.center, self.radius, self.angle, False))
1360
1361
1362 class Projector(Enemy):
1363
1364 def __init__(self, parent, pos, speed=5.0, frequency=3000):
1365 Enemy.__init__(self, parent)
1366 self.pos = pos
1367 self.speed = speed
1368 self.frequency = frequency
1369 self.center = pos
1370 self.load_from_path(self.get_resource("projector"), True)
1371 self.add_frameset([0], name="released", switch=True)
1372 self.add_frameset([1], name="charging")
1373 self.register(self.charge, interval=self.frequency)
1374 self.register(self.release)
1375 self.play(self.charge)
1376
1377 def reset(self):
1378 self.reset_timer()
1379
1380 def charge(self):
1381 self.set_frameset("charging")
1382 self.play(self.release, play_once=True, delay=300)
1383
1384 def release(self):
1385 self.set_frameset("released")
1386 self.get_game().enemies.append(Projectile(self))
1387
1388 def __repr__(self):
1389 return "<Projector %s %.5f %i>" % (self.pos, self.speed, self.frequency)
1390
1391 def increase_speed(self, increase):
1392 self.speed += increase
1393
1394 def increase_frequency(self, increase):
1395 self.frequency += increase
1396
1397 def act(self):
1398 self.location.center = self.get_game().translate_point_to_screen(self.pos)
1399 self.register(self.charge, interval=self.frequency)
1400
1401
1402 class Projectile(Enemy):
1403
1404 def __init__(self, parent):
1405 Enemy.__init__(self, parent)
1406 self.pos = parent.pos.copy()
1407 self.speed = parent.speed
1408 self.angle = get_angle(self.pos, self.get_game().character.location.center, True) + math.pi / 2 - math.pi / 2
1409 self.center = self.pos
1410 self.load_from_path(self.get_resource("projectile"), True)
1411 self.marked_to_delete = False
1412
1413 def reset(self):
1414 self.get_game().enemies.remove(self)
1415
1416 def update(self, act=True):
1417 dsr = self.get_display_surface().get_rect()
1418 if act:
1419 self.pos.move(*get_delta(self.angle, self.speed, False))
1420 self.location.center = self.get_game().translate_point_to_screen(self.pos)
1421 self.center = self.pos
1422 if self.pos.y < 0 or self.pos.y > dsr.bottom or \
1423 self.pos.x < 0 or self.pos.x > dsr.right:
1424 self.marked_to_delete = True
1425 else:
1426 Enemy.update(self, act)
1427
1428
1429 class Fire(Enemy):
1430
1431 def __init__(self, parent, pos, angle, speed, mirroring=False):
1432 Enemy.__init__(self, parent, 300)
1433 self.pos = pos
1434 self.angle = angle
1435 self.speed = speed
1436 self.center = self.pos
1437 self.mirroring = mirroring
1438 self.load_from_path(self.get_resource("fire"), True)
1439 self.register(self.mirror, interval=4000)
1440 if self.mirroring:
1441 self.play(self.mirror)
1442
1443 def mirror(self):
1444 self.angle += math.pi
1445
1446 def act(self):
1447 self.pos.move(*get_delta(self.angle, self.speed, False))
1448 self.location.center = self.get_game().translate_point_to_screen(self.pos)
1449
1450
1451 class Level(GameChild):
1452
1453 def __init__(self, parent, points, name):
1454 GameChild.__init__(self, parent)
1455 self.control_points = [Vector(*point) for point in points]
1456 for point in self.control_points:
1457 point.marker = Marker(self, point)
1458 self.name = name
1459
1460 def set_enemies(self):
1461 self.get_game().enemies = []
1462 if self.name == "close circuit":
1463 self.get_game().enemies = [
1464 Slicer(self, 0.18, 11.00, 142.50),
1465 Slicer(self, 0.12, 3.00, 65.00),
1466 Slicer(self, 0.52, 3.00, 50.00),
1467 Slicer(self, 0.34, 7.25, 212.50),
1468 Slicer(self, 0.72, 5.00, 72.50),
1469 Slicer(self, 0.69, 5.00, 72.50),
1470 Slicer(self, 0.97, 2.75, 35.00),
1471 Slicer(self, 0.94, 2.25, 60.00),
1472 Slicer(self, 0.92, 5.00, 72.50),
1473 ]
1474 elif self.name == "fishy":
1475 self.get_game().enemies = [
1476 Fish(self, 0.11, 0.06, 40.000000),
1477 Fish(self, 0.25, 0.10, 86.000000),
1478 Fish(self, 0.31, 0.10, 30.000000),
1479 Fish(self, 0.33, 0.10, 30.000000),
1480 Fish(self, 0.42, 0.17, 106.000000),
1481 Fish(self, 0.56, 0.13, 112.000000),
1482 Fish(self, 0.75, 0.12, 32.000000),
1483 Fish(self, 0.90, 0.10, 106.000000),
1484 Fish(self, 0.90, 0.10, 30.000000),
1485 Fish(self, 0.675, 0.11, 40.000000),
1486 Fish(self, 0.67, 0.11, 38.000000),
1487 Fish(self, 0.665, 0.11, 36.000000),
1488 Fish(self, 0.6625, 0.11, 34.000000),
1489 ]
1490 elif self.name == "heraldic":
1491 self.get_game().enemies = [
1492 Projector(self, Vector(139.000000, 95.000000), 5.00000, 4000),
1493 Projector(self, Vector(154.000000, 373.000000), 5.00000, 3000),
1494 Projector(self, Vector(660.000000, 386.000000), 5.00000, 2000),
1495 Projector(self, Vector(671.000000, 106.000000), 5.00000, 1000),
1496 Projector(self, Vector(668.000000, 239.000000), 7.00000, 2000),
1497 ]
1498 elif self.name == "climb":
1499 self.get_game().enemies = [
1500 Fish(self, 0.00952, 0.122718, 106.00000),
1501 Slicer(self, 0.07619, 5.00000, 60.00000),
1502 Fish(self, 0.24048, 0.098175, 30.00000),
1503 Fish(self, 0.22143, 0.098175, 30.00000),
1504 Fish(self, 0.26190, 0.098175, 30.00000),
1505 Fish(self, 0.28333, 0.098175, 30.00000),
1506 Slicer(self, 0.46429, 5.00000, 87.50000),
1507 Slicer(self, 0.43095, 5.00000, 87.50000),
1508 Slicer(self, 0.50000, 5.00000, 87.50000),
1509 Fish(self, 0.44286, 0.110447, 30.00000),
1510 Fish(self, 0.48333, 0.110447, 30.00000),
1511 Fish(self, 0.67381, 0.098175, 30.00000),
1512 Slicer(self, 0.66905, 5.00000, 60.00000),
1513 Slicer(self, 0.67857, 5.00000, 60.00000),
1514 Fish(self, 0.70238, 0.098175, 58.00000),
1515 Slicer(self, 0.77143, 0.75000, 22.50000),
1516 Slicer(self, 0.79524, 5.00000, 60.00000),
1517 Fish(self, 0.86667, 0.098175, 30.00000),
1518 Slicer(self, 0.97381, 11.00000, 290.00000),
1519 ]
1520 elif self.name == "peek":
1521 self.get_game().enemies = [
1522 Projector(self, Vector(136.000000, 299.000000), 15.50000, 2000),
1523 Slicer(self, 0.66667, 7.75000, 57.50000),
1524 Projector(self, Vector(400.000000, 56.000000), 11.25000, 2250),
1525 Slicer(self, 0.03333, 5.00000, 60.00000),
1526 Slicer(self, 0.05000, 5.00000, 60.00000),
1527 Slicer(self, 0.06667, 5.00000, 60.00000),
1528 Slicer(self, 0.08333, 5.00000, 60.00000),
1529 Slicer(self, 0.33333, 5.00000, 60.00000),
1530 Slicer(self, 0.28333, 5.00000, 60.00000),
1531 Slicer(self, 0.38333, 5.00000, 60.00000),
1532 Slicer(self, 0.55000, 5.00000, 60.00000),
1533 Slicer(self, 0.51667, 1.50000, 60.00000),
1534 Slicer(self, 0.95000, 5.00000, 72.50000),
1535 Slicer(self, 0.90000, 3.00000, 42.50000),
1536 Slicer(self, 0.85000, 1.25000, 32.50000),
1537 ]
1538 elif self.name == "writing on the wall":
1539 margin = Vector(144.0, 121.5)
1540 y = 0
1541 shift = False
1542 dsr = self.get_display_surface().get_rect()
1543 while y < dsr.bottom:
1544 x = shift * margin.x / 2.0
1545 while x < dsr.right:
1546 self.get_game().enemies.append(Fire(self, Vector(x, y), math.pi / 4.0, 4))
1547 x += margin.x
1548 shift = not shift
1549 y += margin.y
1550 elif self.name == "radar":
1551 self.get_game().enemies = [
1552 Fish(self, 0.02778, 0.245437, 24.00000),
1553 Fish(self, 0.08333, 0.098175, 54.00000),
1554 Fish(self, 0.13611, 0.134990, 26.00000),
1555 Fish(self, 0.11667, 0.134990, 26.00000),
1556 Fish(self, 0.15556, 0.134990, 26.00000),
1557 Fish(self, 0.26111, 0.085903, 148.00000),
1558 Fish(self, 0.29167, 0.134990, 60.00000),
1559 Fish(self, 0.34444, 0.159534, 40.00000),
1560 Fish(self, 0.34167, 0.122718, 42.00000),
1561 Fish(self, 0.34722, 0.159534, 40.00000),
1562 Fish(self, 0.70278, 0.233165, 60.00000),
1563 Fish(self, 0.82222, 0.245437, 72.00000),
1564 Fish(self, 0.61944, 0.184078, 34.00000),
1565 Fish(self, 0.64167, 0.122718, 84.00000),
1566 Fish(self, 0.47778, 0.098175, 98.00000),
1567 Fish(self, 0.47778, 0.098175, 30.00000),
1568 Fish(self, 0.82222, 0.098175, 30.00000),
1569 ]
1570 elif self.name == "wood":
1571 self.get_game().enemies = [
1572 Slicer(self, 0.44167, 5.00000, 72.50000),
1573 Slicer(self, 0.54167, 5.00000, 72.50000),
1574 Slicer(self, 0.31667, 5.00000, 72.50000),
1575 Slicer(self, 0.28333, 5.00000, 60.00000),
1576 Slicer(self, 0.52500, 5.00000, 60.00000),
1577 Slicer(self, 0.40833, 5.00000, 60.00000),
1578 Slicer(self, 0.50833, 5.00000, 60.00000),
1579 Fish(self, 0.49167, 0.098175, 66.00000),
1580 Fish(self, 0.06667, -0.012272, 328.00000),
1581 Slicer(self, 0.57500, 5.00000, 60.00000),
1582 Slicer(self, 0.36667, 5.00000, 60.00000),
1583 Fish(self, 0.05833, 0.098175, 50.00000),
1584 Fish(self, 0.07500, -0.098175, 60.00000),
1585 Fish(self, 0.09167, 0.098175, 58.00000),
1586 Slicer(self, 0.18333, 5.00000, 60.00000),
1587 Slicer(self, 0.17500, 5.00000, 60.00000),
1588 Slicer(self, 0.77500, 9.00000, 147.50000),
1589 Fish(self, 0.65000, 0.098175, 30.00000),
1590 Fish(self, 0.67500, -0.085903, 56.00000),
1591 Slicer(self, 0.80833, 13.00000, 202.50000),
1592 Fish(self, 0.84167, 0.061359, 26.00000),
1593 Fish(self, 0.85833, -0.061359, 26.00000),
1594 ]
1595 elif self.name == "farewell":
1596 self.get_game().enemies = [Projector(self, Vector(704.000000, 426.000000), 1.50000, 2500)]
1597 margin = Vector(144.0, 121.5)
1598 y = 0
1599 shift = False
1600 dsr = self.get_display_surface().get_rect()
1601 while y < dsr.bottom:
1602 x = shift * margin.x / 2.0
1603 while x < dsr.right:
1604 self.get_game().enemies.append(Fire(self, Vector(x, y), math.pi / 4.0, 3, True))
1605 x += margin.x
1606 shift = not shift
1607 y += margin.y
1608 elif self.name == "asterism":
1609 self.get_game().enemies = [
1610 Fish(self, 0.99444, 0.073631, 30.00000),
1611 Fish(self, 0.99444, 0.098175, 30.00000),
1612 Fish(self, 0.35556, 0.196350, 22.00000),
1613 Fish(self, 0.32778, -0.233165, 40.00000),
1614 Fish(self, 0.29444, 0.110447, 72.00000),
1615 Fish(self, 0.37778, -0.220893, 50.00000),
1616 Fish(self, 0.16111, 0.098175, 18.00000),
1617 Slicer(self, 0.61667, 5.00000, 60.00000),
1618 Slicer(self, 0.14444, 5.00000, 60.00000),
1619 Slicer(self, 0.17778, 5.00000, 60.00000),
1620 Slicer(self, 0.63889, 5.00000, 60.00000),
1621 Fish(self, 0.94444, 0.134990, 30.00000),
1622 Fish(self, 0.93889, 0.159534, 26.00000),
1623 Fish(self, 0.95000, 0.147262, 26.00000),
1624 Slicer(self, 0.43889, 5.50000, 102.50000),
1625 Slicer(self, 0.42778, 5.50000, 102.50000),
1626 Slicer(self, 0.41667, 5.50000, 102.50000),
1627 Slicer(self, 0.42222, 5.50000, 102.50000),
1628 Slicer(self, 0.43333, 5.50000, 102.50000),
1629 Slicer(self, 0.41111, 5.50000, 102.50000),
1630 Slicer(self, 0.40556, 5.50000, 102.50000),
1631 Slicer(self, 0.44444, 5.50000, 102.50000),
1632 Slicer(self, 0.45556, 5.50000, 102.50000),
1633 Slicer(self, 0.45000, 5.50000, 102.50000),
1634 Slicer(self, 0.46667, 5.50000, 102.50000),
1635 Slicer(self, 0.97778, 4.25000, 37.50000),
1636 Slicer(self, 0.98333, 4.25000, 37.50000),
1637 Slicer(self, 0.97222, 4.25000, 37.50000),
1638 Fish(self, 0.98333, 0.098175, 30.00000),
1639 Fish(self, 0.97778, 0.098175, 30.00000),
1640 Fish(self, 0.97222, 0.098175, 30.00000),
1641 ]
1642
1643 if __name__ == '__main__':
1644 LD45().run()