endings for level select mode

This commit is contained in:
frank 2022-03-05 15:59:12 -05:00
parent 5e941dbd5b
commit 4265c13dbb
3 changed files with 383 additions and 129 deletions

481
NS.py
View File

@ -8,7 +8,7 @@
# general, visit https://scrape.nugget.fun/
#
import argparse, pathlib
import argparse, pathlib, operator
from random import randint, choice, random
from math import pi
from copy import copy
@ -36,7 +36,7 @@ from lib.pgfw.pgfw.Sprite import Sprite, RainbowSprite
from lib.pgfw.pgfw.Animation import Animation
from lib.pgfw.pgfw.extension import (
get_step, get_step_relative, get_delta, reflect_angle, get_distance, render_box, get_hsla_color, get_hue_shifted_surface,
get_color_swapped_surface, load_frames, fill_colorkey
get_color_swapped_surface, load_frames, fill_colorkey, get_segments
)
from lib.pgfw.pgfw.gfx_extension import aa_filled_polygon
@ -64,7 +64,80 @@ class NS(Game, Animation):
CHANNEL_COUNT = 8
NO_RESET_TIMEOUT = 3000
class Score:
def __init__(self, milliseconds=None, level_index=None):
self.milliseconds = milliseconds
self.level_index = level_index
@classmethod
def from_string(cls, line: str):
milliseconds, level_index = (int(field) for field in line.strip().split())
if level_index == -1:
level_index = None
return cls(milliseconds, level_index)
@classmethod
def level(cls, milliseconds: int, level_index: int):
return cls(milliseconds, level_index)
@classmethod
def full(cls, milliseconds: int):
return cls(milliseconds)
@classmethod
def blank_level(cls, level_index: int):
return cls(level_index=level_index)
@classmethod
def blank_full(cls):
return cls()
def is_full(self):
return self.level_index is None
def formatted(self):
if self.milliseconds is None:
return "--:--.-"
else:
minutes, remainder = divmod(int(self.milliseconds), 60000)
seconds, fraction = divmod(remainder, 1000)
return f"{int(minutes)}:{int(seconds):02}.{fraction // 100}"
def blank(self):
return self.milliseconds is None
def serialize(self):
if self.level_index is None:
serialized_level_index = -1
else:
serialized_level_index = self.level_index
return f"{self.milliseconds} {serialized_level_index}"
def __str__(self):
return self.formatted()
def __repr__(self):
return f"<Score: {self.formatted()}, level: {self.level_index}>"
def __lt__(self, other):
if self.level_index == other.level_index:
if self.milliseconds == other.milliseconds:
return False
elif self.blank() or other.blank():
return other.blank()
else:
return self.milliseconds < other.milliseconds
else:
if self.is_full() or other.is_full():
return other.is_full()
else:
return self.level_index < other.level_index
def __init__(self):
"""
Parse the command line, set config types, initialize the serial reader, subscribe to events, and initialize child objects
"""
# Specify possible arguments and parse the command line. If the -h flag is passed, the argparse library will
# print a help message and end the program.
parser = argparse.ArgumentParser()
@ -100,7 +173,8 @@ class NS(Game, Animation):
},
"system":
{
"bool": "minimize-load-time"
"bool": ["minimize-load-time", "enable-level-select"],
"int": ["lives-boss-rush-mode", "lives-level-select-mode"]
},
"pads":
{
@ -147,14 +221,13 @@ class NS(Game, Animation):
self.serial_thread = Thread(target=self.read_serial)
self.serial_thread.start()
Animation.__init__(self, self)
# All events will pass through self.respond
self.subscribe(self.respond, KEYDOWN)
self.subscribe(self.respond, KEYUP)
self.subscribe(self.respond)
ds = self.get_display_surface()
self.background = Surface(ds.get_size())
self.background.fill((0, 0, 0))
self.level_select = LevelSelect(self)
self.platform = Platform(self)
# Child objects for managing more specific parts of the game
self.platform = Platform(self, self.get_configuration("pads", "center"))
self.tony = Tony(self)
self.logo = Logo(self)
self.title = Title(self)
@ -163,11 +236,28 @@ class NS(Game, Animation):
self.dialogue = Dialogue(self)
self.chemtrails = Chemtrails(self)
self.boss = Boss(self)
self.level_select = LevelSelect(self)
self.last_press = get_ticks()
self.register(self.blink_score, interval=500)
self.register(self.close_pop_up)
self.play(self.blink_score)
self.reset()
self.most_recent_time = None
self.most_recent_score = None
self.pop_up_font = pygame.font.Font(self.get_resource(Dialogue.FONT_PATH), 12)
self.pop_up_text = ""
# Start the score list with all blank scores
self.scores = []
blank_count = 14
for level_index in range(3):
for _ in range(blank_count):
self.scores.append(NS.Score.blank_level(level_index))
for _ in range(blank_count):
self.scores.append(NS.Score.blank_full())
# Add existing scores to the list from file
with open(self.get_resource("scores"), "rt") as score_file:
for line in score_file:
if line.strip():
self.scores.append(NS.Score.from_string(line))
clear()
def serial_enabled(self):
@ -238,9 +328,6 @@ class NS(Game, Animation):
self.no_reset_elapsed = 0
self.title.activate()
def set_most_recent_time(self, score):
self.most_recent_time = score
def blink_score(self):
self.score_hidden = not self.score_hidden
@ -286,6 +373,43 @@ class NS(Game, Animation):
if self.get_delegate().compare(event, "reset-game"):
self.reset()
def pop_up(self, text):
"""
Trigger a pop up message that displays for a certain amount of time before being closed automatically. Adds a line of
text to a variable that contains all pop up messages in case there is a previously sent message that needs to continue
being displayed.
@param text message to display
"""
self.pop_up_text += f"{text}\n"
self.halt(self.close_pop_up)
self.play(self.close_pop_up, play_once=True, delay=3000)
def close_pop_up(self):
"""
Close the pop up message by removing all text from the pop up text variable. This will cause the pop up to stop being
drawn each frame.
"""
self.pop_up_text = ""
def add_time_to_scores(self, milliseconds: int, level_index=None):
"""
Add a time to the list of scores. This method will build a score object, add it to the list, and write to the scores file.
@param milliseconds player's time in milliseconds
@param level_index the level this time corresponds to or None for a full game
"""
if level_index is None:
score = NS.Score.full(milliseconds)
else:
score = NS.Score.level(milliseconds, level_index)
self.scores.append(score)
self.most_recent_score = score
with open(self.get_resource("scores"), "wt") as score_file:
for score in sorted(self.scores):
if not score.blank():
score_file.write(f"{score.serialize()}\n")
def update(self):
Animation.update(self)
last_frame_duration = self.time_filter.get_last_frame_duration()
@ -293,7 +417,7 @@ class NS(Game, Animation):
self.apply_serial()
if self.title.active or self.ending.active or self.dialogue.active:
self.no_reset_elapsed += last_frame_duration
# if we received good input, reset the auto reset timer
# If we received good input, reset the auto reset timer
if 0b11 <= self.serial_data <= 0b1100:
self.no_reset_elapsed = 0
if self.no_reset_elapsed >= self.NO_RESET_TIMEOUT:
@ -309,6 +433,13 @@ class NS(Game, Animation):
self.chemtrails.update()
self.boss.update_dialogue()
self.wipe.update()
# Draw the pop up text line by line if there is any
pop_up_y = 0
for line in self.pop_up_text.split("\n"):
if line:
surface = self.pop_up_font.render(line, False, (0, 0, 0), (255, 255, 255))
self.get_display_surface().blit(surface, (0, pop_up_y))
pop_up_y += surface.get_height()
self.idle_elapsed += self.time_filter.get_last_frame_duration()
if self.idle_elapsed >= self.IDLE_TIMEOUT:
self.reset()
@ -324,32 +455,119 @@ class LevelSelect(GameChild):
def __init__(self, parent):
GameChild.__init__(self, parent)
self.subscribe(self.respond, KEYDOWN)
y = 250
indent = 10
dsr = self.get_display_surface().get_rect()
self.platforms = [Platform(self, (dsr.centerx, y)), Platform(self, (0, y)), Platform(self, (0, y))]
self.platforms[1].view.location.left = dsr.left + indent
self.platforms[2].view.location.right = dsr.right - indent
self.platforms[0].set_glowing((NS.LNW, NS.LSE))
self.platforms[1].set_glowing((NS.LNW, NS.LSW))
self.platforms[2].set_glowing((NS.LNW, NS.LNE))
preview_rect = pygame.Rect(0, 0, dsr.w / 3 - 40, 160)
self.previews = []
font = pygame.font.Font(self.get_resource(Dialogue.FONT_PATH), 18)
padding = 4
for level_index, text in enumerate(("NORMAL", "ADVANCED", "EXPERT")):
self.previews.append(Sprite(self, 100))
text = font.render(text, True, (255, 255, 255))
text = pygame.transform.rotate(text, 90)
text_rect = text.get_rect()
text_rect.midleft = preview_rect.midleft
environment = pygame.transform.smoothscale(
self.get_game().boss.backgrounds[level_index].frames[0],
(preview_rect.w - text_rect.w - padding, preview_rect.h - padding * 2))
environment_rect = environment.get_rect()
environment_rect.midright = preview_rect.right - padding, preview_rect.centery
boss = pygame.transform.smoothscale(self.get_game().boss.level_sprite(level_index).frames[0],
environment_rect.inflate(-64, -28).size)
boss_rect = boss.get_rect()
boss_rect.center = environment_rect.center
for hue in range(0, 360, 8):
frame = pygame.Surface(preview_rect.size)
color = Color(0, 0, 0)
color.hsla = hue, 100, 50, 100
frame.fill(color)
frame.blit(text, text_rect)
frame.blit(environment, environment_rect)
frame.blit(boss, boss_rect)
self.previews[-1].add_frame(frame)
self.previews[-1].location.midbottom = self.platforms[level_index].view.location.centerx, \
self.platforms[level_index].view.location.top - 12
def activate(self):
self.active = True
for platform in self.platforms:
platform.activate()
def deactivate(self):
self.active = False
for platform in self.platforms:
platform.deactivate()
def reset(self):
self.deactivate()
self.level_index_selected = None
self.zoom = 1.0
for level_index in range(3):
self.platforms[level_index].view.unhide()
self.previews[level_index].unhide()
def respond(self, event):
if self.active:
launch_level = None
if event.key == K_1:
launch_level = 0
elif event.key == K_2:
launch_level = 1
elif event.key == K_3:
launch_level = 2
if launch_level is not None:
self.deactivate()
self.get_game().boss.start_level(launch_level)
"""
Respond to CTRL + key presses to launch a level or toggle level select mode
"""
level_index = None
if pygame.key.get_mods() & pygame.KMOD_CTRL:
if event.key in (pygame.K_1, pygame.K_2, pygame.K_3):
self.launch(event.key - pygame.K_1)
elif event.key == pygame.K_l:
level_select_enabled = not self.get_configuration("system", "enable-level-select")
self.get_configuration().set("system", "enable-level-select", level_select_enabled)
self.get_game().pop_up(f"Level select mode set to {level_select_enabled}")
def launch(self, index):
"""
Start a level through the boss object
"""
self.get_game().boss.start_level(index)
self.deactivate()
def launch_selected_index(self):
"""
Launch level index stored in the member variable
"""
self.launch(self.level_index_selected)
def update(self):
if self.active:
self.get_display_surface().fill((255, 255, 0))
self.get_game().logo.update()
if self.level_index_selected is None:
for level_index, platform in enumerate(self.platforms):
if platform.get_glowing_edge() == self.get_game().platform.get_edge_pressed():
self.level_index_selected = level_index
break
if self.level_index_selected is not None:
for level_index in range(3):
if level_index != self.level_index_selected:
self.platforms[level_index].view.play(self.platforms[level_index].view.wipe_out)
self.previews[level_index].play(self.previews[level_index].wipe_out)
self.get_audio().play_sfx("complete_pattern_3")
elif not self.get_game().wipe.is_playing() and any(preview.is_hidden() for preview in self.previews):
self.get_game().wipe.start(self.launch_selected_index)
for platform in self.platforms:
platform.update()
for ii, preview in enumerate(self.previews):
if ii == self.level_index_selected:
self.zoom += 0.1
frame = pygame.transform.scale(
preview.get_current_frame(), (int(preview.location.w * self.zoom), int(preview.location.h * self.zoom)))
rect = frame.get_rect()
rect.center = preview.location.center
preview.update()
self.get_display_surface().blit(frame, rect)
else:
preview.update()
class Button(Sprite):
@ -600,15 +818,12 @@ class Title(Animation):
Handles displaying and drawing the title screen.
"""
UNLOCK_MOVES = NS.NW, NS.N, NS.NE, NS.NW
UNLOCK_MOVES = NS.NW, NS.N, NS.NE, NS.S
def __init__(self, parent):
Animation.__init__(self, parent)
self.plank = Sprite(self)
self.plank.load_from_path(self.get_resource("Title_plank.png"), True)
ds = self.get_display_surface()
dsr = ds.get_rect()
self.plank.location.center = dsr.center
self.angle = pi / 8
self.video = Video(self, 320)
self.video.location.center = 329, 182
@ -636,47 +851,45 @@ class Title(Animation):
def start_game(self):
"""
Turn off the title screen and display the level select. Set the most recent time to None so the most
recent high score stops blinking.
Turn off the title screen and either display the level select or start level one if level select is disabled. Set the
most recent time to None so the most recent high score stops blinking.
"""
self.deactivate()
self.get_game().set_most_recent_time(None)
self.get_game().level_select.activate()
self.get_game().most_recent_score = None
if self.get_configuration("system", "enable-level-select"):
self.get_game().level_select.activate()
else:
self.get_game().level_select.launch(0)
def draw_scores(self):
step = 75
step = 56
ds = self.get_display_surface()
lines = map(int, open(self.get_resource("scores")).readlines())
entries = ["BEST"] + sorted(lines)[:9]
if not self.get_configuration("system", "enable-level-select"):
entries = ["BEST"] + sorted([score for score in self.get_game().scores if score.is_full()])[:15]
else:
entries = ["NORMAL"] + sorted([score for score in self.get_game().scores if score.level_index == 0])[:3] + \
["ADVANCED"] + sorted([score for score in self.get_game().scores if score.level_index == 1])[:3] + \
["EXPERT"] + sorted([score for score in self.get_game().scores if score.level_index == 2])[:7]
for ii, entry in enumerate(entries):
if ii == 0 or ii == 5:
y = 30
if ii == 0 or ii == 8:
y = 20
font = Font(self.get_resource(Dialogue.FONT_PATH), 18)
if ii > 0:
text = self.get_formatted_time(entry)
if isinstance(entry, NS.Score):
text = entry.formatted()
else:
text = entry
message = render_box(font, text, True, Color(255, 255, 255),
Color(128, 128, 128), Color(0, 0, 0), padding=2)
message = render_box(font, text, True, Color(255, 255, 255), Color(128, 128, 128), Color(0, 0, 0), padding=2)
message.set_alpha(230)
rect = message.get_rect()
rect.top = y
if ii < 5:
if ii < 8:
rect.left = -1
else:
rect.right = ds.get_width() + 1
if not entry == self.get_game().most_recent_time or not self.get_game().score_hidden:
if not entry == self.get_game().most_recent_score or not self.get_game().score_hidden:
ds.blit(message, rect)
y += step
def get_formatted_time(self, entry):
if int(entry) == 5999999:
return "--:--.-"
else:
minutes, milliseconds = divmod(int(entry), 60000)
seconds, fraction = divmod(milliseconds, 1000)
return "%i:%02i.%i" % (minutes, seconds, fraction / 100)
def show_video(self):
self.video.unhide()
self.play(self.hide_video, delay=self.get_configuration("time", "attract-gif-length"), play_once=True)
@ -1051,10 +1264,13 @@ class Platform(GameChild):
on-screen representation.
"""
def __init__(self, parent):
def __init__(self, parent, center):
"""
Initialize four lights, one for each pad on the platform. Initialize a Sprite for the pad graphics with one
frameset per six possible combinations of lights. Initialize masks for creating a glow effect on the pads.
@param parent PGFW game object that initialized this object
@param center tuple that gives the (x, y) screen coordinates of this platform
"""
GameChild.__init__(self, parent)
self.lights = [
@ -1072,7 +1288,7 @@ class Platform(GameChild):
self.view.add_frameset([4], name=str(NS.NE))
self.view.add_frameset([5], name=str(NS.W))
self.view.add_frameset([6], name=str(NS.S))
self.view.location.center = self.get_configuration("pads", "center")
self.view.location.center = center
self.glow_masks = []
base_images = load_frames(self.get_resource("pad_mask"), True)
for image in base_images:
@ -1299,9 +1515,11 @@ class Platform(GameChild):
else:
self.view.set_frameset(str(glowing))
self.view.update()
for light in self.lights:
if light.glowing():
self.get_display_surface().blit(self.glow_masks[light.position][light.glow_index], self.view.location, None, BLEND_RGBA_ADD)
if not self.view.is_hidden() and not self.view.is_playing(self.view.wipe_out):
for light in self.lights:
if light.glowing():
self.get_display_surface().blit(
self.glow_masks[light.position][light.glow_index], self.view.location, None, BLEND_RGBA_ADD)
class Light(Animation):
@ -1370,12 +1588,11 @@ class Light(Animation):
Checks the attack state to determine whether to start or stop glowing
"""
Animation.update(self)
if not self.get_game().title.active:
if not self.get_game().title.active and not self.get_game().level_select.active:
boss = self.get_game().boss
chemtrails = self.get_game().chemtrails
# checks the boss attack queue and chameleon queue index to see if the glow should be started now
if boss.queue and not self.is_playing(self.glow) \
and self.in_orientation(boss.queue[chemtrails.queue_index]):
if boss.queue and not self.is_playing(self.glow) and self.in_orientation(boss.queue[chemtrails.queue_index]):
self.play(self.glow)
# turns off the glow
elif self.is_playing(self.glow) and (not boss.queue or not self.in_orientation(boss.queue[chemtrails.queue_index])):
@ -1403,13 +1620,7 @@ class Light(Animation):
lightness = 0
else:
lightness = 40
lines(
self.get_display_surface(),
get_hsla_color(
int(self.color.hsla[0]), saturation, lightness
),
True, shifted, 3
)
lines(self.get_display_surface(), get_hsla_color(int(self.color.hsla[0]), saturation, lightness), True, shifted, 3)
def in_orientation(self, orientation):
"""
@ -1483,7 +1694,7 @@ class Chemtrails(Sprite):
if self.active:
self.orient()
Sprite.update(self)
if not self.get_game().title.active:
if not self.get_game().title.active and not self.get_game().level_select.active:
boss = self.get_game().boss
if boss.queue:
self.timer.tick()
@ -1621,7 +1832,12 @@ class Boys(Meter):
background = load(self.get_resource("HUD_lives.png")).convert()
rect = background.get_rect()
rect.bottomleft = 6, dsr.bottom - 4
self.setup(background, rect, 60, (0, 255, 0), 3, "scrapeIcons/scrapeIcons_01.png")
# The amount of lives depends on whether it's boss rush or level select mode.
if self.get_configuration("system", "enable-level-select"):
lives = self.get_configuration("system", "lives-level-select-mode")
else:
lives = self.get_configuration("system", "lives-boss-rush-mode")
self.setup(background, rect, 60, (0, 255, 0), lives, "scrapeIcons/scrapeIcons_01.png")
class BossSprite(Sprite):
@ -1661,12 +1877,12 @@ class Boss(Animation):
self.spoopy.load_from_path("Spoopy.png", True)
# Set up alien sprite with boil and hurt animations
self.visitor = BossSprite(self, 42)
self.visitor.add_frameset(name="normal", switch=True)
self.visitor.load_from_path("alienAnimations/alienBoil", True)
self.visitor.add_frameset(name="hurt", switch=True)
self.visitor.load_from_path("alienAnimations/alienHit", True)
self.visitor.add_frameset(name="entrance", switch=True)
self.visitor.load_from_path("alienAnimations/alienIntro", True)
self.visitor.add_frameset(name="normal", switch=True)
self.visitor.load_from_path("alienAnimations/alienBoil", True)
for sprite in self.kool_man, self.visitor, self.spoopy:
sprite.location.topleft = 207, 10
self.health = Health(self)
@ -2042,13 +2258,16 @@ class Boss(Animation):
self.queue = []
self.brandish_complete = True
if win:
if self.get_configuration("system", "enable-level-select"):
self.get_game().add_time_to_scores(self.time_elapsed, self.level_index)
if self.level_index == 0:
self.kool_man.set_frameset(0)
elif self.level_index == 1:
self.visitor.set_frameset("hurt")
elif self.level_index == 2:
self.spoopy.set_frameset(0)
self.add_score()
if not self.get_configuration("system", "enable-level-select"):
self.get_game().add_time_to_scores(self.time_elapsed)
self.backgrounds[self.level_index].set_frameset("shining")
else:
self.play(self.flash_player_damage)
@ -2056,10 +2275,6 @@ class Boss(Animation):
self.kills += not win
self.play(self.show_end_dialogue, delay=3000, play_once=True)
def add_score(self):
self.get_game().set_most_recent_time(self.time_elapsed)
open(self.get_resource("scores"), "a").write(str(self.time_elapsed) + "\n")
def show_end_dialogue(self):
dialogue = self.get_game().dialogue
dialogue.activate()
@ -2073,30 +2288,53 @@ class Boss(Animation):
if self.player_defeated:
dialogue.show_text("Wiped out!")
else:
dialogue.show_text("Well done! But it's not over yet!")
if self.get_configuration("system", "enable-level-select"):
dialogue.show_text("Ouch! Oof!")
else:
dialogue.show_text("Well done! But it's not over yet!")
elif self.level_index == 2:
if self.player_defeated:
dialogue.show_text("Just like I thought!")
else:
dialogue.show_text("H-how? But you're only a lizard! How could you" +
" manage to defeat all of us?")
if self.get_configuration("system", "enable-level-select"):
dialogue.show_text("H-how? But you're only a lizard!")
else:
dialogue.show_text("H-how? But you're only a lizard! How could you" +
" manage to defeat all of us?")
if self.player_defeated:
self.countdown.activate()
else:
self.play(self.end_dialogue, delay=5000, play_once=True)
def transition_to_battle(self):
index = self.level_index + (not self.player_defeated)
if self.kills >= 3:
self.get_game().reset(True)
elif index < 3:
self.start_level(index)
def end_dialogue(self):
self.get_game().dialogue.deactivate()
if not self.battle_finished:
self.combo(delay=1300)
else:
self.get_game().wipe.start(self.transition_after_battle)
def transition_after_battle(self):
"""
Determine whether to reset to title screen, relaunch the current level, launch the next level, or activate the ending object and
call the appropriate method.
"""
# If the player is out of lives, reset the game.
if self.get_game().chemtrails.boys.amount <= 0:
self.get_game().reset(True)
# Check if the ending screen should be activated.
elif not self.player_defeated and self.get_configuration("system", "enable-level-select") or self.level_index == 2:
defeated_level_index = self.level_index
game = self.get_game()
game.boss.reset()
game.chemtrails.reset()
game.platform.reset()
game.ending.activate()
game.ending.activate(defeated_level_index)
else:
# Level index to launch is the current level if the player was defeated, otherwise it's the next level. If the player wasn't
# defeated, level select mode would have launched the game ending, so the next level will only be launched when boss rush
# mode is active.
index = self.level_index + (not self.player_defeated)
self.start_level(index)
def transition_to_title(self):
self.get_game().reset(True)
@ -2109,13 +2347,6 @@ class Boss(Animation):
elif self.level_index == 2:
self.spoopy.set_frameset(0)
def end_dialogue(self):
self.get_game().dialogue.deactivate()
if not self.battle_finished:
self.combo(delay=1300)
else:
self.get_game().wipe.start(self.transition_to_battle)
def start_player_damage(self):
"""
Launch the flash player damage effect and queue it to end after a certain amount of time
@ -2172,6 +2403,19 @@ class Boss(Animation):
self.visitor.get_current_frameset().reset()
self.visitor.set_frameset("entrance")
def level_sprite(self, level_index):
"""
Return the boss sprite associated with this the given level index.
@param level_index index of the level of the requested sprite
"""
if level_index == 0:
return self.kool_man
elif level_index == 1:
return self.visitor
else:
return self.spoopy
def update(self):
"""
Update graphics
@ -2192,7 +2436,7 @@ class Boss(Animation):
if not self.battle_finished:
self.combo()
else:
self.get_game().wipe.start(self.transition_to_battle)
self.get_game().wipe.start(self.transition_after_battle)
self.advance_prompt.cancel_first_press()
else:
self.time_elapsed += self.get_game().time_filter.get_last_frame_duration()
@ -2230,7 +2474,7 @@ class Boss(Animation):
dialogue = self.get_game().dialogue
if dialogue.active:
self.get_game().dialogue.update()
if self.countdown.active:
if self.countdown.active and self.get_game().chemtrails.boys.amount > 0:
self.advance_prompt.update()
@ -2277,8 +2521,6 @@ class Countdown(GameChild):
if self.get_game().chemtrails.boys.amount > 0:
self.heading.update()
self.glyphs[int(self.remaining / 1000)].update()
else:
self.game_over.update()
if not self.get_game().wipe.is_playing():
if self.remaining <= 0:
self.get_game().wipe.start(self.end_game)
@ -2558,9 +2800,13 @@ class Health(Meter):
class Ending(Animation):
"""
Scene for the end of a successful play session. The Tony and slime bag sprites will be displayed, and Tony will say something
to the player. The player's time will be displayed as a sprite that bounces around the screen.
"""
TEXT = "Wow! You vanquished all the goons and skated like a pro, slime bag." + \
" You made your\nfather proud today. I love you, child.",
BOSS_RUSH_TEXT = "Wow! You vanquished all the goons and skated like a pro, slime bag." + \
" You made your\nfather proud today. I love you, child."
def __init__(self, parent):
Animation.__init__(self, parent)
@ -2568,7 +2814,7 @@ class Ending(Animation):
self.slime_bag.load_from_path(self.get_resource("Introduction_slime_bag.png"), True)
self.slime_bag.location.center = self.get_display_surface().get_rect().centerx, 300
self.tony_avatar = load(self.get_resource("Introduction_tony_avatar.png")).convert()
self.advance_prompt = AdvancePrompt(self)
self.font = Font(self.get_resource("rounded-mplus-1m-bold.ttf"), 64)
self.register(self.start, self.start_wipe)
def reset(self):
@ -2576,18 +2822,16 @@ class Ending(Animation):
self.slime_bag.unhide()
self.halt()
self.text_index = 0
self.advance_prompt.reset()
self.angle = choice((pi / 4, 3 * pi / 4, 5 * pi / 4, 7 * pi / 4))
def deactivate(self):
self.active = False
def activate(self):
def activate(self, level_index):
self.defeated_level_index = level_index
self.active = True
self.play(self.start, delay=3000, play_once=True)
font = Font(self.get_resource("rounded-mplus-1m-bold.ttf"), 64)
time = self.get_game().title.get_formatted_time(self.get_game().most_recent_time)
foreground = font.render(time, False, (180, 150, 20), (255, 255, 255)).convert_alpha()
foreground = self.font.render(str(self.get_game().most_recent_score), False, (180, 150, 20), (255, 255, 255)).convert_alpha()
dsr = self.get_display_surface().get_rect()
self.text = RainbowSprite(self, foreground, 180, 200)
self.text.location.midtop = dsr.centerx, 80
@ -2601,10 +2845,25 @@ class Ending(Animation):
self.get_audio().play_bgm("end")
def start(self):
self.advance_prompt.cancel_first_press()
dialogue = self.get_game().dialogue
dialogue.set_name("Tony")
dialogue.show_text(self.TEXT[0])
if self.get_configuration("system", "enable-level-select"):
rank = 0
for score in sorted([score for score in self.get_game().scores if score.level_index == self.defeated_level_index]):
rank += 1
if score == self.get_game().most_recent_score:
break
text = f"You vanquished my goon and got the #{rank} rank! Well done, slime bag.\n"
if self.defeated_level_index == 2:
dialogue.set_name("Tony")
text += "You made your father proud today. I love you child."
elif self.defeated_level_index == 1:
text += "Give expert mode a try next."
else:
text += "Give advanced mode a try next."
else:
text = self.BOSS_RUSH_TEXT
dialogue.set_name("Tony")
dialogue.show_text(text)
self.text_index = 0
def end_game(self):
@ -2623,21 +2882,19 @@ class Ending(Animation):
self.get_game().tony.update()
self.slime_bag.update()
dsr = self.get_display_surface().get_rect()
# Bounce the time sprite around the screen
if self.text.location.right > dsr.right or self.text.location.left < dsr.left:
self.angle = reflect_angle(self.angle, 0)
if self.text.location.right > dsr.right:
self.text.move(dsr.right - self.text.location.right)
else:
self.text.move(dsr.left - self.text.location.left)
if self.text.location.bottom > self.get_game().dialogue.avatar_box.location.top \
or self.text.location.top < dsr.top:
if self.text.location.bottom > self.get_game().dialogue.avatar_box.location.top or self.text.location.top < dsr.top:
self.angle = reflect_angle(self.angle, pi)
if self.text.location.top < dsr.top:
self.text.move(dy=dsr.top - self.text.location.top)
else:
self.text.move(
dy=self.get_game().dialogue.avatar_box.location.top - \
self.text.location.bottom)
self.text.move(dy=self.get_game().dialogue.avatar_box.location.top - self.text.location.bottom)
dx, dy = get_delta(self.angle, 5, False)
self.text.move(dx, dy)
self.text.update()

5
config
View File

@ -30,7 +30,10 @@ effects = yes
[system]
# will force set display->effects to off
minimize-load-time = no
minimize-load-time = yes
enable-level-select = yes
lives-boss-rush-mode = 3
lives-level-select-mode = 1
[mouse]
visible = no

View File

@ -1,16 +1,10 @@
5999999
5999999
5999999
5999999
5999999
5999999
5999999
5999999
5999999
5999999
52586
54614
52434
168209
141407
139786
51849 0
35340 1
37954 1
39171 1
42320 1
50057 1
64604 1
38638 2
100000 -1
1000000 -1