endings for level select mode
This commit is contained in:
parent
5e941dbd5b
commit
4265c13dbb
481
NS.py
481
NS.py
|
@ -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
5
config
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue