fade
[hs] / HEAVENSlime.py
index ba0feb1..0038098 100644 (file)
@@ -1,4 +1,4 @@
-from pygame import Rect, Surface, Color, mixer
+from pygame import Rect, Surface, Color, mixer, PixelArray
 from pygame.image import load
 from pygame.mixer import Sound
 from pygame.transform import scale
@@ -8,6 +8,7 @@ from lib.pgfw.pgfw.GameChild import GameChild
 from lib.pgfw.pgfw.Sprite import Sprite
 from lib.pgfw.pgfw.Animation import Animation
 from lib.pgfw.pgfw.extension import get_hsla_color, get_value_in_range
+from lib.pgfw.pgfw.Note import Note, Chord
 
 class SoundEffect(GameChild, Sound):
 
@@ -42,11 +43,14 @@ class HEAVENSlime(Game):
         self.get_input().register_any_press_ignore("left", "right", "pause")
         self.subscribe(self.respond)
 
+    def reset(self):
+        if not self.title.active:
+            self.arena.deactivate()
+            self.title.activate()
+
     def respond(self, event):
         if self.get_delegate().compare(event, "reset-game"):
-            if not self.title.active:
-                self.arena.deactivate()
-                self.title.activate()
+            self.reset()
 
     def update(self):
         self.title.update()
@@ -56,7 +60,7 @@ class HEAVENSlime(Game):
 class Title(GameChild):
 
     CHARGE_INCREASE = 1
-    CHARGE_THRESHOLD = 30
+    CHARGE_THRESHOLD = 0
 
     def __init__(self, parent):
         GameChild.__init__(self, parent)
@@ -103,28 +107,53 @@ class Arena(Animation):
     GRADIENT_SPAN = 100
     GRADIENT_INTERVAL = 400
     GRADIENT_HUE_STEP = 1
+    INITIAL_BOOST = 3
 
     def __init__(self, parent):
         Animation.__init__(self, parent)
-        self.level_index = 0
+        self.boost_sound = SoundEffect(self, self.get_resource("use-boost-0.wav"), .7)
+        self.no_gas_sound = SoundEffect(self, self.get_resource("no-gas-1.wav"), .7)
+        self.thud_sound = SoundEffect(self, self.get_resource("thud-1.wav"), .7)
+        self.safe_sound = SoundEffect(self, self.get_resource("safe-2.wav"), .7)
         ground = self.ground = Sprite(self)
         ground.load_from_path(self.get_resource("Ground.png"), True)
+        end = self.end = Sprite(self)
+        end.load_from_path(self.get_resource("End.png"), True)
+        end.location.center = self.get_display_surface().get_rect().center
         self.music = Sound(self.get_resource("Climb.ogg"))
+        self.saved_music_volume = self.music.get_volume()
         self.meter = Meter(self)
         self.slime = Slime(self)
-        self.platforms = [Platform(self, 300), Platform(self, 800), Platform(self, 1200)]
-        self.register(self.set_gradient)
+        platforms = self.platforms = []
+        y = 0
+        for difference in (150, 120, 100, 200, 180, 250, 100, 300, 350, 400):
+            y += difference
+            platforms.append(Platform(self, y))
+        platforms[-1].final = True
+        self.register(self.set_gradient, self.restart_game, self.restore_volume)
         self.subscribe(self.respond)
         self.reset()
         self.deactivate()
         self.play(self.set_gradient, interval=self.GRADIENT_INTERVAL)
 
     def reset(self):
+        self.halt(self.restart_game)
         self.suppressing_input = False
         self.set_active_on_update = False
-        self.ground.location.bottom = self.get_display_surface().get_rect().bottom
+        self.boost_count = self.INITIAL_BOOST
+        boosts = self.boosts = Sprite(self, 333)
+        ds = self.get_display_surface()
+        boosts.load_from_path(self.get_resource("boost"), True)
+        boosts.location.top = 10
+        boosts.location.right = ds.get_rect().right - 10
+        boosts.add_location(offset=(-8, 0))
+        boosts.add_location(offset=(-16, 0))
+        self.end.hide()
+        self.ground.location.bottom = ds.get_rect().bottom
         self.slime.reset()
         self.meter.reset()
+        for platform in self.platforms:
+            platform.reset()
 
     def respond(self, event):
         if self.active and not self.suppressing_input:
@@ -138,10 +167,22 @@ class Arena(Animation):
                 if delegate.compare(event, "any"):
                     self.meter.hold()
                 elif delegate.compare(event, "any", cancel=True):
-                    if not self.slime.travelling:
-                        self.slime.launch()
+                    if not self.slime.travelling or self.boost_count:
+                        if self.slime.travelling:
+                            self.discard_boost()
+                        self.slime.launch(not self.slime.travelling)
+                    elif not self.boost_count:
+                        self.no_gas_sound.play()    
                     self.meter.release()
 
+    def discard_boost(self):
+        self.boost_count -= 1
+        if self.boost_count >= 1:
+            self.boosts.remove_locations(self.boosts.locations[-1])
+        else:
+            self.boosts.hide()
+        self.boost_sound.play()
+
     def pause(self):
         self.paused = True
         mixer.pause()
@@ -182,6 +223,13 @@ class Arena(Animation):
             platform.move(dy=dy)
         self.ground.move(dy=dy)
         
+    def restore_volume(self):
+        new_volume = self.music.get_volume() + .17
+        if new_volume > self.saved_music_volume:
+            self.halt(self.restore_volume)
+        self.music.set_volume(new_volume)
+        self.get_audio().original_volumes[self.music] = new_volume
+
     def update(self):
         if self.active:
             Animation.update(self)
@@ -198,19 +246,36 @@ class Arena(Animation):
                             self.shift_world(self.slime.location.bottom - platform.location.top + 1)
                             if abs(self.slime.speed) < Platform.SAFETY_THRESHOLD:
                                 platform.set_safe()
+                                self.safe_sound.play()
+                                if platform.final:
+                                    self.end.unhide()
+                                    self.play(self.restart_game, delay=5000, play_once=True)
+                                    self.suppressing_input = True
                             else:
                                 platform.set_miss()
+                                self.saved_music_volume = self.music.get_volume()
+                                self.music.set_volume(0)
+                                self.get_audio().original_volumes[self.music] = 0
+                                self.play(self.restore_volume, delay=10000)
+                                self.thud_sound.play()
+                                self.suppressing_input = True
+                                self.meter.reset()
                             break
             ds.blit(self.background, (0, 0))
             self.ground.update()
             for platform in self.platforms:
                 platform.update()
             self.meter.update()
+            self.boosts.update()
             self.slime.update()
+            self.end.update()
         elif self.set_active_on_update:
             self.active = True
             self.set_active_on_update = False
 
+    def restart_game(self):
+        self.get_game().reset()
+
 
 class Meter(Animation):
 
@@ -263,14 +328,22 @@ class Slime(Sprite):
     COLORS = Color(28, 187, 183), Color(235, 42, 128), Color(220, 240, 13)
     GRAVITY = .125
     MAX_SPEED = 12
+    DROP_SPEED = 8
+    DROP_DELAY = 1000
 
     def __init__(self, parent):
         Sprite.__init__(self, parent)
+        self.launch_sound = SoundEffect(self, self.get_resource("jump-0.wav"), .7)
+        self.drop_sound = SoundEffect(self, self.get_resource("end-1.wav"), 1)
         self.face = load(self.get_resource("Face.png")).convert_alpha()
+        self.register(self.set_dropping)
 
     def reset(self):
         self.segments = [self.COLORS[0]]
         self.travelling = False
+        self.dropping = False
+        self.place()
+        self.halt()
 
     def place(self):
         ds = self.get_display_surface()
@@ -282,7 +355,7 @@ class Slime(Sprite):
         charge = self.parent.meter.charge
         width = self.SEGMENT_WIDTH
         height = self.HEIGHT
-        if not self.travelling and charge:
+        if charge:
             width_add = self.BULGE * charge
             width = int((self.SEGMENT_WIDTH + width_add) * len(self.segments))
             height_subtract = self.COMPRESS * charge
@@ -309,7 +382,7 @@ class Slime(Sprite):
         frame.set_at((width - 2, height - 2), border)
         frame.fill(border, (2, height - 1, width - 4, 1))
         face = self.face
-        if not self.travelling and charge:
+        if charge:
             face = scale(face, (face.get_width() + int(width_add / 4),
                                 face.get_height() - int(height_subtract / 4)))
         fr = face.get_rect()
@@ -318,17 +391,36 @@ class Slime(Sprite):
         fr.centery = sr.centery + sr.h / 8
         frame.blit(face, fr)
         self.add_frame(frame)
-        self.place()
 
-    def launch(self):
+    def launch(self, play_sound=True):
         self.travelling = True
         self.speed = self.parent.meter.charge * self.MAX_SPEED
+        if play_sound:
+            self.launch_sound.play()
+        if self.parent.music.get_volume() == 0:
+            self.parent.music.set_volume(self.parent.saved_music_volume)
+            self.get_audio().original_volumes[self.parent.music] = self.parent.saved_music_volume
+    
 
     def stop(self):
         self.travelling = False
 
+    def drop(self):
+        self.drop_sound.play()
+        self.play(self.set_dropping, delay=self.DROP_DELAY, play_once=True)
+
+    def set_dropping(self):
+        self.dropping = True
+
     def update(self):
         self.set_frame()
+        if not self.dropping:
+            self.place()
+        else:
+            self.move(dy=self.DROP_SPEED)
+            if self.location.top > self.get_display_surface().get_height() + 300:
+                self.dropping = False
+                self.parent.reset()
         if self.travelling:
             self.speed -= self.GRAVITY
         Sprite.update(self)
@@ -340,6 +432,7 @@ class Slime(Sprite):
 # - springing up
 # - arcing swing
 # - blinking
+# - defended
 #
 # COLORS
 # - ff1c66 (rose)
@@ -350,23 +443,62 @@ class Platform(Sprite):
 
     SAFETY_THRESHOLD = 3.75
 
-    def __init__(self, parent, y):
+    def __init__(self, parent, y, final=False):
         Sprite.__init__(self, parent)
-        self.passed = False
-        self.load_from_path(self.get_resource("Platform.png"), True)
-        self.iris = load(self.get_resource("Iris.png")).convert_alpha()
         self.y = y
+        self.final = final
+        self.register(self.drop, interval=700)
+
+    def reset(self):
         ds = self.get_display_surface()
         self.location.centerx = ds.get_rect().centerx
-        self.location.centery = ds.get_height() - y
+        self.location.centery = ds.get_height() - self.y
+        self.set_unused()
+        self.unhide()
+        self.halt()
+
+    def set_unused(self):
+        self.clear_frames()
+        self.passed = False
+        if not self.final:
+            self.load_from_path(self.get_resource("Platform.png"), True)
+        else:
+            self.add_frame_with_iris("Platform.png")
+
+    def add_frame_with_iris(self, path):
+        base = load(self.get_resource(path)).convert_alpha()
+        br = base.get_rect()
+        iris = load(self.get_resource("Iris.png")).convert_alpha()
+        pixels = PixelArray(iris)
+        pixels.replace((255, 255, 255), Color("#ff1c66"))
+        del pixels
+        ir = iris.get_rect()
+        ir.center = br.center
+        base.blit(iris, ir)
+        self.add_frame(base)
 
     def set_safe(self):
         self.clear_frames()
-        self.load_from_path(self.get_resource("Safe.png"), True)
+        if not self.final:
+            self.load_from_path(self.get_resource("Safe.png"), True)
+        else:
+            self.add_frame_with_iris("Safe.png")
 
     def set_miss(self):
         self.clear_frames()
-        self.load_from_path(self.get_resource("Miss.png"), True)
+        self.load_from_path(self.get_resource("miss/miss-0.png"), True)
+        self.blink_index = 0
+        self.play(self.drop, delay=1000)
+
+    def drop(self):
+        self.blink_index += 1
+        if self.blink_index > 2:
+            self.hide()
+            self.halt()
+            self.parent.slime.drop()
+        else:
+            self.clear_frames()
+            self.load_from_path(self.get_resource("miss/miss-" + str(self.blink_index) + ".png"), True)
 
     def update(self):
         if self.location.top >= self.parent.slime.location.bottom: