from pathlib import Path import pyray as ray from raylib import SHADER_UNIFORM_FLOAT from libs import utils from libs.audio import audio from libs.texture import tex from libs.utils import ( OutlinedText, get_current_ms, global_data, is_l_don_pressed, is_r_don_pressed, session_data, ) class State: FAIL = 0 CLEAR = 1 RAINBOW = 2 class ResultScreen: def __init__(self, width: int, height: int): self.width = width self.height = height self.screen_init = False self.alpha_shader = ray.load_shader('', 'shader/grayscale_alpha.fs') def load_textures(self): tex.load_screen_textures('result') def load_sounds(self): sounds_dir = Path("Sounds") self.sound_don = audio.load_sound(sounds_dir / "hit_sounds" / "0" / "don.wav") self.sound_kat = audio.load_sound(sounds_dir / "hit_sounds" / "0" / "ka.wav") self.sound_num_up = audio.load_sound(sounds_dir / "result" / "SE_RESULT [4].ogg") self.bgm = audio.load_sound(sounds_dir / "result" / "JINGLE_SEISEKI [1].ogg") def on_screen_start(self): if not self.screen_init: self.load_textures() self.load_sounds() self.screen_init = True self.song_info = OutlinedText(session_data.song_title, 40, ray.WHITE, ray.BLACK, outline_thickness=5) audio.play_sound(self.bgm) self.fade_in = FadeIn() self.fade_out = tex.get_animation(0) self.fade_in_bottom = tex.get_animation(1) self.gauge = None self.score_delay = None self.bottom_characters = BottomCharacters() self.crown = None self.state = None self.high_score_indicator = None self.score_animator = ScoreAnimator(session_data.result_score) self.score = '' self.good = '' self.ok = '' self.bad = '' self.max_combo = '' self.total_drumroll = '' self.update_list = [['score', session_data.result_score], ['good', session_data.result_good], ['ok', session_data.result_ok], ['bad', session_data.result_bad], ['max_combo', session_data.result_max_combo], ['total_drumroll', session_data.result_total_drumroll]] self.update_index = 0 self.is_skipped = False self.start_ms = get_current_ms() if session_data.result_bad == 0: self.crown_type = 'crown_fc' else: self.crown_type = 'crown_clear' def on_screen_end(self): self.screen_init = False global_data.songs_played += 1 tex.unload_textures() audio.stop_sound(self.bgm) utils.session_data = utils.reset_session() return "SONG_SELECT" def update_score_animation(self): if self.is_skipped: if self.update_index == len(self.update_list) - 1: return setattr(self, self.update_list[self.update_index][0], self.update_list[self.update_index][1]) self.update_index += 1 elif self.score_delay is not None: if get_current_ms() > self.score_delay: if self.score_animator is not None and not self.score_animator.is_finished: curr_num = self.update_list[self.update_index][0] setattr(self, self.update_list[self.update_index][0], self.score_animator.next_score()) if self.update_list[self.update_index] != curr_num: audio.play_sound(self.sound_num_up) if self.score_animator.is_finished: audio.play_sound(self.sound_don) self.score_delay += 750 if self.update_index == len(self.update_list) - 1: self.is_skipped = True return self.update_index += 1 self.score_animator = ScoreAnimator(self.update_list[self.update_index][1]) self.score_delay += 16.67 * 3 if self.update_index > 0 and self.high_score_indicator is None: if session_data.result_score > session_data.prev_score: self.high_score_indicator = HighScoreIndicator(session_data.prev_score, session_data.result_score) def handle_input(self): if is_r_don_pressed() or is_l_don_pressed(): if not self.is_skipped: self.is_skipped = True else: self.fade_out.start() audio.play_sound(self.sound_don) def update(self): self.on_screen_start() self.fade_in.update(get_current_ms()) if self.fade_in.is_finished and self.gauge is None: self.gauge = Gauge(str(global_data.player_num), session_data.result_gauge_length) self.bottom_characters.start() self.bottom_characters.update(self.state) if self.bottom_characters.is_finished and self.crown is None: if self.gauge is not None and self.gauge.gauge_length > 69: self.crown = Crown() if self.gauge is not None: self.gauge.update(get_current_ms()) if self.gauge.is_finished and self.score_delay is None: self.score_delay = get_current_ms() + 1883 if self.score_delay is not None: if get_current_ms() > self.score_delay and not self.fade_in_bottom.is_started: self.fade_in_bottom.start() if self.gauge is not None: self.state = self.gauge.state if self.high_score_indicator is not None: self.high_score_indicator.update(get_current_ms()) self.fade_in_bottom.update(get_current_ms()) alpha_loc = ray.get_shader_location(self.alpha_shader, "ext_alpha") alpha_value = ray.ffi.new('float*', self.fade_in_bottom.attribute) ray.set_shader_value(self.alpha_shader, alpha_loc, alpha_value, SHADER_UNIFORM_FLOAT) if get_current_ms() >= self.start_ms + 5000: self.handle_input() self.update_score_animation() self.fade_out.update(get_current_ms()) if self.fade_out.is_finished: return self.on_screen_end() if self.crown is not None: self.crown.update(get_current_ms()) def draw_score_info(self): if self.good: for i in range(len(str(self.good))): tex.draw_texture('score', 'judge_num', frame=int(str(self.good)[::-1][i]), x=943-(i*24), y=186) if self.ok: for i in range(len(str(self.ok))): tex.draw_texture('score', 'judge_num', frame=int(str(self.ok)[::-1][i]), x=943-(i*24), y=227) if self.bad: for i in range(len(str(self.bad))): tex.draw_texture('score', 'judge_num', frame=int(str(self.bad)[::-1][i]), x=943-(i*24), y=267) if self.max_combo: for i in range(len(str(self.max_combo))): tex.draw_texture('score', 'judge_num', frame=int(str(self.max_combo)[::-1][i]), x=1217-(i*24), y=186) if self.total_drumroll: for i in range(len(str(self.total_drumroll))): tex.draw_texture('score', 'judge_num', frame=int(str(self.total_drumroll)[::-1][i]), x=1217-(i*24), y=227) def draw_total_score(self): if not self.fade_in.is_finished: return tex.draw_texture('score', 'score_shinuchi') if self.score: for i in range(len(str(self.score))): tex.draw_texture('score', 'score_num', x=-(i*21), frame=int(str(self.score)[::-1][i])) def draw_bottom_textures(self): if self.state == State.FAIL: tex.draw_texture('background', 'gradient_fail', fade=min(0.4, self.fade_in_bottom.attribute)) else: ray.begin_shader_mode(self.alpha_shader) tex.draw_texture('background', 'gradient_clear', fade=self.fade_in_bottom.attribute) ray.end_shader_mode() self.bottom_characters.draw() def draw(self): x = 0 while x < self.width: tex.draw_texture('background', f'background_{str(global_data.player_num)}p', x=x, y=-360) tex.draw_texture('background', f'background_{str(global_data.player_num)}p', x=x, y=360) tex.draw_texture('background', f'footer_{str(global_data.player_num)}p', x=x, y=-77) tex.draw_texture('background', f'footer_{str(global_data.player_num)}p', x=x, y=653) x += 256 tex.draw_texture('background', 'result_text') tex.draw_texture('song_info', 'song_num', frame=global_data.songs_played%4) dest = ray.Rectangle(1252 - self.song_info.texture.width, 35 - self.song_info.texture.height / 2, self.song_info.texture.width, self.song_info.texture.height) self.song_info.draw(self.song_info.default_src, dest, ray.Vector2(0, 0), 0, ray.WHITE) tex.draw_texture('score', 'overlay', color=ray.fade(ray.WHITE, 0.75)) tex.draw_texture('score', 'difficulty', frame=session_data.selected_difficulty) self.draw_bottom_textures() if self.gauge is not None: self.gauge.draw() tex.draw_texture('score', 'judge_good') tex.draw_texture('score', 'judge_ok') tex.draw_texture('score', 'judge_bad') tex.draw_texture('score', 'max_combo') tex.draw_texture('score', 'drumroll') self.draw_score_info() self.draw_total_score() if self.crown is not None: self.crown.draw(self.crown_type) if self.high_score_indicator is not None: self.high_score_indicator.draw() self.fade_in.draw() ray.draw_rectangle(0, 0, self.width, self.height, ray.fade(ray.BLACK, self.fade_out.attribute)) def draw_3d(self): pass class Crown: def __init__(self): self.resize = tex.get_animation(2) self.resize_fix = tex.get_animation(3) self.white_fadein = tex.get_animation(4) self.gleam = tex.get_animation(5) self.fadein = tex.get_animation(6) self.resize.start() self.resize_fix.start() self.white_fadein.start() self.gleam.start() self.fadein.start() self.sound = audio.load_sound(Path('Sounds/result/SE_RESULT [1].ogg')) self.sound_played = False def update(self, current_ms: float): self.fadein.update(current_ms) self.resize.update(current_ms) self.resize_fix.update(current_ms) self.white_fadein.update(current_ms) self.gleam.update(current_ms) if self.resize_fix.is_finished and not self.sound_played: audio.play_sound(self.sound) self.sound_played = True def draw(self, crown_name: str): scale = self.resize.attribute if self.resize.is_finished: scale = self.resize_fix.attribute tex.draw_texture('crown', crown_name, scale=scale, center=True) tex.draw_texture('crown', 'crown_fade', fade=self.white_fadein.attribute) if self.gleam.attribute >= 0: tex.draw_texture('crown', 'gleam', frame=self.gleam.attribute) class BottomCharacters: def __init__(self): self.move_up = tex.get_animation(7) self.move_down = tex.get_animation(8) self.bounce_up = tex.get_animation(9) self.bounce_down = tex.get_animation(10) self.move_center = tex.get_animation(11) self.c_bounce_up = tex.get_animation(12) self.c_bounce_down = tex.get_animation(13) self.flower_up = tex.get_animation(14) self.state = None self.flower_index = 0 self.flower_start = None self.chara_0_index = 0 self.chara_1_index = 0 self.is_finished = False def start(self): self.move_up.start() self.move_down.start() self.c_bounce_up.start() self.c_bounce_down.start() def update(self, state): self.state = state if self.state == State.CLEAR or self.state == State.RAINBOW: self.chara_0_index = 1 self.chara_1_index = 1 if not self.bounce_up.is_started: self.bounce_up.start() self.bounce_down.start() self.move_center.start() if self.flower_start is None: self.flower_up.start() self.flower_start = get_current_ms() elif self.state == State.FAIL: self.chara_0_index = 2 self.chara_1_index = 2 self.move_up.update(get_current_ms()) self.move_down.update(get_current_ms()) self.is_finished = self.move_down.is_finished self.bounce_up.update(get_current_ms()) self.bounce_down.update(get_current_ms()) if self.bounce_down.is_finished: self.bounce_up.restart() self.bounce_down.restart() self.move_center.update(get_current_ms()) self.flower_up.update(get_current_ms()) if self.flower_start is not None: if get_current_ms() > self.flower_start + 116*2 + 333: self.flower_index = 2 elif get_current_ms() > self.flower_start + 116 + 333: self.flower_index = 1 self.c_bounce_up.update(get_current_ms()) self.c_bounce_down.update(get_current_ms()) if self.c_bounce_down.is_finished: self.c_bounce_up.restart() self.c_bounce_down.restart() def draw_flowers(self): tex.draw_texture('bottom','flowers', y=-self.flower_up.attribute, frame=self.flower_index) tex.draw_texture('bottom','flowers', y=-self.flower_up.attribute, frame=self.flower_index, x=792, mirror='horizontal') def draw(self): self.draw_flowers() y = -self.move_up.attribute + self.move_down.attribute + self.bounce_up.attribute - self.bounce_down.attribute if self.state == State.RAINBOW: center_y = self.c_bounce_up.attribute - self.c_bounce_down.attribute tex.draw_texture('bottom', 'chara_center', y=-self.move_center.attribute + center_y) tex.draw_texture('bottom', 'chara_0', frame=self.chara_0_index, y=y) tex.draw_texture('bottom', 'chara_1', frame=self.chara_1_index, y=y) class FadeIn: def __init__(self): self.fadein = tex.get_animation(15) self.fadein.start() self.is_finished = False def update(self, current_ms: float): self.fadein.update(current_ms) self.is_finished = self.fadein.is_finished def draw(self): x = 0 while x < 1280: tex.draw_texture('background', f'background_{str(global_data.player_num)}p', x=x, y=-360, fade=self.fadein.attribute) tex.draw_texture('background', f'background_{str(global_data.player_num)}p', x=x, y=360, fade=self.fadein.attribute) tex.draw_texture('background', f'footer_{str(global_data.player_num)}p', x=x, y=-77, fade=self.fadein.attribute) tex.draw_texture('background', f'footer_{str(global_data.player_num)}p', x=x, y=653, fade=self.fadein.attribute) x += 256 class ScoreAnimator: def __init__(self, target_score): self.target_score = str(target_score) self.current_score_list = [[0,0] for _ in range(len(self.target_score))] self.digit_index = len(self.target_score) - 1 self.is_finished = False def next_score(self) -> str: if self.digit_index == -1: self.is_finished = True if self.target_score == '0': return '0' return str(int(''.join([str(item[0]) for item in self.current_score_list]))) curr_digit, counter = self.current_score_list[self.digit_index] if counter < 9: self.current_score_list[self.digit_index][1] += 1 self.current_score_list[self.digit_index][0] = (curr_digit + 1) % 10 else: self.current_score_list[self.digit_index][0] = int(self.target_score[self.digit_index]) self.digit_index -= 1 ret_val = ''.join([str(item[0]) for item in self.current_score_list]) if int(ret_val) == 0: if not (len(self.target_score) - self.digit_index) > (len(self.target_score)): return '0' * (len(self.target_score) - self.digit_index) return '0' return str(int(ret_val)) class HighScoreIndicator: def __init__(self, old_score: int, new_score: int): self.score_diff = new_score - old_score self.move = tex.get_animation(18) self.fade = tex.get_animation(19) self.move.start() self.fade.start() def update(self, current_ms): self.move.update(current_ms) self.fade.update(current_ms) def draw(self): tex.draw_texture('score', 'high_score', y=self.move.attribute, fade=self.fade.attribute) for i in range(len(str(self.score_diff))): tex.draw_texture('score', 'high_score_num', x=-(i*14), frame=int(str(self.score_diff)[::-1][i]), y=self.move.attribute, fade=self.fade.attribute) class Gauge: def __init__(self, player_num: str, gauge_length: int): self.player_num = player_num self.gauge_length = gauge_length self.rainbow_animation = tex.get_animation(16) self.gauge_fade_in = tex.get_animation(17) self.rainbow_animation.start() self.gauge_fade_in.start() self.is_finished = self.gauge_fade_in.is_finished if self.gauge_length == 87: self.state = State.RAINBOW elif self.gauge_length > 69: self.state = State.CLEAR else: self.state = State.FAIL def update(self, current_ms: float): self.rainbow_animation.update(current_ms) if self.rainbow_animation.is_finished: self.rainbow_animation.restart() self.gauge_fade_in.update(current_ms) self.is_finished = self.gauge_fade_in.is_finished def draw(self): scale = 10/11 tex.draw_texture('gauge', f'{self.player_num}p_unfilled', scale=scale, fade=self.gauge_fade_in.attribute) gauge_length = int(self.gauge_length) if gauge_length == 87: if 0 < self.rainbow_animation.attribute < 8: tex.draw_texture('gauge', 'rainbow', frame=self.rainbow_animation.attribute-1, scale=scale, fade=self.gauge_fade_in.attribute) tex.draw_texture('gauge', 'rainbow', frame=self.rainbow_animation.attribute, scale=scale, fade=self.gauge_fade_in.attribute) else: for i in range(gauge_length+1): width = int(i * 7.2) if i == 69: tex.draw_texture('gauge', 'bar_clear_transition', x=width, scale=scale, fade=self.gauge_fade_in.attribute) elif i > 69: if i % 5 == 0: tex.draw_texture('gauge', 'bar_clear_top', x=width, scale=scale, fade=self.gauge_fade_in.attribute) tex.draw_texture('gauge', 'bar_clear_bottom', x=width, scale=scale, fade=self.gauge_fade_in.attribute) tex.draw_texture('gauge', 'bar_clear_top', x=width+1, scale=scale, fade=self.gauge_fade_in.attribute) tex.draw_texture('gauge', 'bar_clear_bottom', x=width+1, scale=scale, fade=self.gauge_fade_in.attribute) else: if i % 5 == 0: tex.draw_texture('gauge', f'{self.player_num}p_bar', x=width, scale=scale, fade=self.gauge_fade_in.attribute) tex.draw_texture('gauge', f'{self.player_num}p_bar', x=width+1, scale=scale, fade=self.gauge_fade_in.attribute) tex.draw_texture('gauge', 'overlay', scale=scale, fade=min(0.15, self.gauge_fade_in.attribute)) tex.draw_texture('gauge', 'footer', scale=scale, fade=self.gauge_fade_in.attribute) if gauge_length >= 69: tex.draw_texture('gauge', 'clear', scale=scale, fade=self.gauge_fade_in.attribute) tex.draw_texture('gauge', 'tamashii', scale=scale, fade=self.gauge_fade_in.attribute) else: tex.draw_texture('gauge', 'clear_dark', scale=scale, fade=self.gauge_fade_in.attribute) tex.draw_texture('gauge', 'tamashii_dark', scale=scale, fade=self.gauge_fade_in.attribute)