|
|
|
@ -102,7 +102,7 @@ class NS(Game, Animation):
|
|
|
|
|
|
|
|
|
|
def formatted(self):
|
|
|
|
|
if self.milliseconds is None:
|
|
|
|
|
return "--:--.-"
|
|
|
|
|
return "-:--.-"
|
|
|
|
|
else:
|
|
|
|
|
minutes, remainder = divmod(int(self.milliseconds), 60000)
|
|
|
|
|
seconds, fraction = divmod(remainder, 1000)
|
|
|
|
@ -194,7 +194,8 @@ class NS(Game, Animation):
|
|
|
|
|
"display":
|
|
|
|
|
{
|
|
|
|
|
"float": "attract-gif-alpha",
|
|
|
|
|
"bool": ["effects", "alpha-effect-title"]
|
|
|
|
|
"bool": ["effects", "alpha-effect-title"],
|
|
|
|
|
"path": "scores-font"
|
|
|
|
|
},
|
|
|
|
|
"system":
|
|
|
|
|
{
|
|
|
|
@ -305,13 +306,12 @@ class NS(Game, Animation):
|
|
|
|
|
self.register(self.close_pop_up)
|
|
|
|
|
self.reset()
|
|
|
|
|
self.most_recent_score = None
|
|
|
|
|
self.pop_up_font = pygame.font.Font(
|
|
|
|
|
self.get_resource(Dialogue.FONT_PATH), 12)
|
|
|
|
|
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
|
|
|
|
|
blank_count = 25
|
|
|
|
|
for level_index in range(3):
|
|
|
|
|
for _ in range(blank_count):
|
|
|
|
|
self.scores.append(NS.Score.blank_level(level_index))
|
|
|
|
@ -1066,13 +1066,18 @@ class Title(Animation):
|
|
|
|
|
@param parent GameChild object that will connect this GameChild object to the overall tree and root Game object
|
|
|
|
|
"""
|
|
|
|
|
Animation.__init__(self, parent)
|
|
|
|
|
ds = self.get_display_surface()
|
|
|
|
|
dsr = ds.get_rect()
|
|
|
|
|
|
|
|
|
|
# Set up attract mode pop-up
|
|
|
|
|
self.angle = pi / 8
|
|
|
|
|
self.video = Video(self, 320)
|
|
|
|
|
self.video.location.center = 329, 182
|
|
|
|
|
self.register(self.show_video, self.hide_video)
|
|
|
|
|
self.show_video()
|
|
|
|
|
|
|
|
|
|
# Set up scores
|
|
|
|
|
font_path = self.get_resource(self.get_configuration("display", "scores-font"))
|
|
|
|
|
self.heading_font = pygame.font.Font(font_path, 22)
|
|
|
|
|
self.score_font = pygame.font.Font(font_path, 16)
|
|
|
|
|
self.score_sprites = []
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
@ -1112,57 +1117,117 @@ class Title(Animation):
|
|
|
|
|
else:
|
|
|
|
|
self.get_game().level_select.launch(0)
|
|
|
|
|
|
|
|
|
|
def draw_scores(self):
|
|
|
|
|
def draw_score_to_column(self, score, column, screen_pos, rank):
|
|
|
|
|
"""
|
|
|
|
|
Draw frames for a sprite object for each score and store the sprite in a list to be drawn each frame.
|
|
|
|
|
Blit `score` onto `column`, taking positioning and rank into account
|
|
|
|
|
|
|
|
|
|
@param score Score to display in top scores
|
|
|
|
|
@param column Surface for displaying score
|
|
|
|
|
@param screen_pos absolute screen (x, y) of score topleft
|
|
|
|
|
@param rank rank of score
|
|
|
|
|
@return height of the drawn score
|
|
|
|
|
"""
|
|
|
|
|
# Create a list of strings in order of which to draw
|
|
|
|
|
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]
|
|
|
|
|
# Parse both strings and score objects
|
|
|
|
|
if isinstance(score, NS.Score):
|
|
|
|
|
text = score.formatted()
|
|
|
|
|
else:
|
|
|
|
|
text = score
|
|
|
|
|
|
|
|
|
|
# The background is based on rank
|
|
|
|
|
if rank == 0:
|
|
|
|
|
bg = 255, 215, 0
|
|
|
|
|
elif rank == 1:
|
|
|
|
|
bg = 192, 192, 192
|
|
|
|
|
elif rank == 2:
|
|
|
|
|
bg = 205, 127, 50
|
|
|
|
|
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]
|
|
|
|
|
bg = 255, 255, 255
|
|
|
|
|
|
|
|
|
|
# Create a sprite object for each score and place on the screen in two columns on the left and right edges the screen
|
|
|
|
|
step = 56
|
|
|
|
|
ds = self.get_display_surface()
|
|
|
|
|
self.score_sprites = []
|
|
|
|
|
for ii, entry in enumerate(entries):
|
|
|
|
|
# Draw the score
|
|
|
|
|
score_surface = render_box(self.score_font, text, width=column.get_width(), background=bg)
|
|
|
|
|
column.blit(score_surface, (0, screen_pos[1]))
|
|
|
|
|
|
|
|
|
|
# Reset y counter
|
|
|
|
|
if ii == 0 or ii == 8:
|
|
|
|
|
y = 20
|
|
|
|
|
# Create a blink effect for the most recent score
|
|
|
|
|
if score == self.get_game().most_recent_score:
|
|
|
|
|
self.score_blanker = BlinkingSprite(self, 500)
|
|
|
|
|
blank = pygame.surface.Surface(score_surface.get_size())
|
|
|
|
|
blank.fill(bg)
|
|
|
|
|
self.score_blanker.add_frame(blank)
|
|
|
|
|
self.score_blanker.location = screen_pos
|
|
|
|
|
|
|
|
|
|
font = pygame.font.Font(self.get_resource(Dialogue.FONT_PATH), 18)
|
|
|
|
|
# The height is used to move the draw location
|
|
|
|
|
return score_surface.get_height()
|
|
|
|
|
|
|
|
|
|
# Parse both strings and score objects
|
|
|
|
|
if isinstance(entry, NS.Score):
|
|
|
|
|
text = entry.formatted()
|
|
|
|
|
else:
|
|
|
|
|
text = entry
|
|
|
|
|
def draw_heading_to_column(self, text, column, y):
|
|
|
|
|
"""
|
|
|
|
|
Blit `text` on `column` in the heading style
|
|
|
|
|
|
|
|
|
|
# Create a surface as a box around the score
|
|
|
|
|
message = render_box(font, text, True, Color(255, 255, 255), Color(128, 128, 128), Color(0, 0, 0), padding=2)
|
|
|
|
|
message.set_alpha(230)
|
|
|
|
|
@param text heading text to blit
|
|
|
|
|
@param column Surface where heading will be blit
|
|
|
|
|
@param y top position of text
|
|
|
|
|
@return height of drawn heading
|
|
|
|
|
"""
|
|
|
|
|
heading = render_box(self.heading_font, text, color=(255, 255, 255), background=(0, 0, 0), width=column.get_width())
|
|
|
|
|
column.blit(heading, (0, y))
|
|
|
|
|
return heading.get_height()
|
|
|
|
|
|
|
|
|
|
# Store it in a sprite, use a blinking sprite for the most recent score
|
|
|
|
|
if not entry == self.get_game().most_recent_score:
|
|
|
|
|
sprite = Sprite(self)
|
|
|
|
|
else:
|
|
|
|
|
sprite = BlinkingSprite(self, 500)
|
|
|
|
|
sprite.add_frame(message)
|
|
|
|
|
self.score_sprites.append(sprite)
|
|
|
|
|
|
|
|
|
|
# Place the sprite along the column
|
|
|
|
|
sprite.location.top = y
|
|
|
|
|
if ii < 8:
|
|
|
|
|
sprite.location.left = -1
|
|
|
|
|
def draw_scores(self):
|
|
|
|
|
"""
|
|
|
|
|
Create two columns, one for each side of the screen. Draw as many scores as can fit along each column, in order from best to worst, separating
|
|
|
|
|
them evenly into categories: normal, advanced, and expert. Draw the two columns to the display surface, with the expectation that they will be
|
|
|
|
|
removed from the clip and will not be drawn over. Note that this doesn't support non-level select mode anymore.
|
|
|
|
|
"""
|
|
|
|
|
ds = self.get_display_surface()
|
|
|
|
|
self.score_blanker = None
|
|
|
|
|
heading_width, heading_height = self.heading_font.size("ADVANCED")
|
|
|
|
|
heading_width += 10
|
|
|
|
|
score_height = self.score_font.size("0")[1]
|
|
|
|
|
column_width, column_height = heading_width, ds.get_height()
|
|
|
|
|
left_score_count = (column_height - heading_height * 2) // score_height
|
|
|
|
|
right_score_count = (column_height - heading_height) // score_height
|
|
|
|
|
total_score_count = left_score_count + right_score_count
|
|
|
|
|
per_category_count, remainder = divmod(total_score_count, 3)
|
|
|
|
|
left_column_sprite = Sprite(self)
|
|
|
|
|
left_column = pygame.surface.Surface((column_width, column_height))
|
|
|
|
|
left_column.fill((255, 255, 255))
|
|
|
|
|
x, y = 0, 0
|
|
|
|
|
y += self.draw_heading_to_column("NORMAL", left_column, y)
|
|
|
|
|
count = per_category_count
|
|
|
|
|
if remainder:
|
|
|
|
|
count += 1
|
|
|
|
|
remainder -= 1
|
|
|
|
|
for rank, score in enumerate(sorted([score for score in self.get_game().scores if score.level_index == 0])[:count]):
|
|
|
|
|
y += self.draw_score_to_column(score, left_column, (x, y), rank)
|
|
|
|
|
left_score_count -= 1
|
|
|
|
|
y += self.draw_heading_to_column("ADVANCED", left_column, y)
|
|
|
|
|
count = per_category_count
|
|
|
|
|
if remainder:
|
|
|
|
|
count += 1
|
|
|
|
|
remainder -= 1
|
|
|
|
|
right_column_sprite = Sprite(self)
|
|
|
|
|
right_column = pygame.surface.Surface((column_width, column_height))
|
|
|
|
|
right_column.fill((255, 255, 255))
|
|
|
|
|
column = left_column
|
|
|
|
|
for rank, score in enumerate(sorted([score for score in self.get_game().scores if score.level_index == 1])[:count]):
|
|
|
|
|
y += self.draw_score_to_column(score, column, (x, y), rank)
|
|
|
|
|
if left_score_count == 1:
|
|
|
|
|
y = 0
|
|
|
|
|
x = ds.get_width() - column_width
|
|
|
|
|
column = right_column
|
|
|
|
|
left_column_sprite.add_frame(left_column)
|
|
|
|
|
if left_score_count == 0:
|
|
|
|
|
right_score_count -= 1
|
|
|
|
|
else:
|
|
|
|
|
sprite.location.right = ds.get_width() + 1
|
|
|
|
|
|
|
|
|
|
# Move to the next sprite location
|
|
|
|
|
y += step
|
|
|
|
|
left_score_count -= 1
|
|
|
|
|
y += self.draw_heading_to_column("EXPERT", right_column, y)
|
|
|
|
|
count = per_category_count
|
|
|
|
|
for rank, score in enumerate(sorted([score for score in self.get_game().scores if score.level_index == 2])[:count]):
|
|
|
|
|
y += self.draw_score_to_column(score, right_column, (x, y), rank)
|
|
|
|
|
right_column_sprite.add_frame(right_column)
|
|
|
|
|
right_column_sprite.location.topleft = x, 0
|
|
|
|
|
self.score_sprites = [left_column_sprite, right_column_sprite]
|
|
|
|
|
for sprite in self.score_sprites:
|
|
|
|
|
sprite.update()
|
|
|
|
|
|
|
|
|
|
def show_video(self):
|
|
|
|
|
self.video.unhide()
|
|
|
|
@ -1185,7 +1250,13 @@ class Title(Animation):
|
|
|
|
|
if self.active:
|
|
|
|
|
ds = self.get_display_surface()
|
|
|
|
|
dsr = ds.get_rect()
|
|
|
|
|
|
|
|
|
|
# Optimize by setting a clip that excludes the area where the scores are drawn
|
|
|
|
|
ds.set_clip((self.score_sprites[0].location.right, 0, self.score_sprites[1].location.left - self.score_sprites[0].location.right, dsr.height))
|
|
|
|
|
|
|
|
|
|
# Draw the background
|
|
|
|
|
self.get_game().logo.update()
|
|
|
|
|
|
|
|
|
|
# Advance through the unlock pattern
|
|
|
|
|
platform = self.get_game().platform
|
|
|
|
|
if not self.get_game().wipe.is_playing() and platform.get_edge_pressed() == self.UNLOCK_MOVES[self.unlock_index]:
|
|
|
|
@ -1198,6 +1269,7 @@ class Title(Animation):
|
|
|
|
|
platform.set_glowing(platform.get_buttons_from_edges([self.UNLOCK_MOVES[self.unlock_index]]))
|
|
|
|
|
self.get_audio().play_sfx("land_0")
|
|
|
|
|
self.get_game().tony.update()
|
|
|
|
|
|
|
|
|
|
# Bounce the GIF around the screen
|
|
|
|
|
if self.video.location.right > dsr.right or self.video.location.left < dsr.left:
|
|
|
|
|
self.angle = reflect_angle(self.angle, 0)
|
|
|
|
@ -1213,6 +1285,7 @@ class Title(Animation):
|
|
|
|
|
self.video.move(dy=dsr.top - self.video.location.top)
|
|
|
|
|
dx, dy = get_delta(self.angle, 5, False)
|
|
|
|
|
self.video.move(dx, dy)
|
|
|
|
|
|
|
|
|
|
# Hide GIFs/attract mode (or keep them hidden) if input is detected. Set a countdown that will turn
|
|
|
|
|
# attract mode back on if no input is detected before the countdown expires. As long as input keeps
|
|
|
|
|
# being detected, this block will keep running and restarting the countdown.
|
|
|
|
@ -1221,10 +1294,11 @@ class Title(Animation):
|
|
|
|
|
self.get_game().tony.set_frameset("static")
|
|
|
|
|
self.halt()
|
|
|
|
|
self.play(self.show_video, delay=self.get_configuration("time", "attract-reset-countdown"), play_once=True)
|
|
|
|
|
# self.video.update()
|
|
|
|
|
# self.draw_scores()
|
|
|
|
|
for score in self.score_sprites:
|
|
|
|
|
score.update()
|
|
|
|
|
|
|
|
|
|
# Disable clip and draw blanker which creates a blinking affect for a single score
|
|
|
|
|
ds.set_clip(None)
|
|
|
|
|
if self.score_blanker:
|
|
|
|
|
self.score_blanker.update()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Dialogue(Animation):
|
|
|
|
|