Files
PyTaiko/scenes/song_select.py
2025-05-15 20:06:07 -04:00

479 lines
22 KiB
Python

import os
import sqlite3
from pathlib import Path
import pyray as ray
from libs.animation import Animation
from libs.audio import audio
from libs.tja import TJAParser
from libs.utils import (
OutlinedText,
get_config,
get_current_ms,
global_data,
load_all_textures_from_zip,
session_data,
)
class SongSelectScreen:
BOX_CENTER = 444
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.song_name_textures: list[OutlinedText] = []
self.selected_song = 0
self.selected_difficulty = 0
self.song_boxes: list[SongBox] = []
self.screen_init = False
i = 0
for dirpath, dirnames, filenames in os.walk(f'{get_config()["paths"]["tja_path"]}'):
for filename in filenames:
if filename.endswith(".tja"):
position = -56 + (100*i)
if position == SongSelectScreen.BOX_CENTER:
position += 150
elif position > SongSelectScreen.BOX_CENTER:
position += 300
self.song_boxes.append(SongBox(dirpath, position))
i += 1
def load_textures(self):
self.textures = load_all_textures_from_zip(Path('Graphics/lumendata/song_select.zip'))
def load_sounds(self):
sounds_dir = Path("Sounds")
self.sound_don = audio.load_sound(str(sounds_dir / "inst_00_don.wav"))
self.sound_kat = audio.load_sound(str(sounds_dir / "inst_00_katsu.wav"))
def on_screen_start(self):
if not self.screen_init:
self.load_textures()
self.load_sounds()
self.game_transition = None
self.move_away = Animation.create_move(float('inf'))
self.diff_fade_out = Animation.create_fade(0, final_opacity=1.0)
self.text_fade_out = None
self.text_fade_in = None
self.screen_init = True
self.is_difficulty_select = False
self.background_move = Animation.create_move(15000, start_position=0, total_distance=1280)
def on_screen_end(self):
self.screen_init = False
curr_box = self.song_boxes[0]
for box in self.song_boxes:
if box.is_open:
curr_box = box
box.reset()
global_data.selected_song = curr_box.tja_path
session_data.selected_difficulty = self.selected_difficulty
for zip in self.textures:
for texture in self.textures[zip]:
ray.unload_texture(texture)
return "GAME"
def update_song_select(self):
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
audio.play_sound(self.sound_don)
self.move_away = Animation.create_move(233, total_distance=500)
self.diff_fade_out = Animation.create_fade(83)
elif ray.is_key_pressed(ray.KeyboardKey.KEY_LEFT):
audio.play_sound(self.sound_kat)
for box in self.song_boxes:
box.move_left()
elif ray.is_key_pressed(ray.KeyboardKey.KEY_RIGHT):
audio.play_sound(self.sound_kat)
for box in self.song_boxes:
box.move_right()
def update_difficulty_select(self):
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
if self.selected_difficulty == -1:
self.is_difficulty_select = False
self.move_away = Animation.create_move(float('inf'))
self.diff_fade_out = Animation.create_fade(0, final_opacity=1.0)
self.text_fade_out = None
self.text_fade_in = None
for box in self.song_boxes:
if box.yellow_box is not None:
box.yellow_box.reset_animations()
else:
audio.play_sound(self.sound_don)
self.game_transition = Transition(self.height)
elif ray.is_key_pressed(ray.KeyboardKey.KEY_LEFT):
audio.play_sound(self.sound_kat)
if self.selected_difficulty >= 0:
self.selected_difficulty = (self.selected_difficulty - 1)
elif ray.is_key_pressed(ray.KeyboardKey.KEY_RIGHT):
audio.play_sound(self.sound_kat)
if self.selected_difficulty < 4:
self.selected_difficulty = (self.selected_difficulty + 1)
def update(self):
self.on_screen_start()
if self.background_move.is_finished:
self.background_move = Animation.create_move(15000, start_position=0, total_distance=1280)
self.background_move.update(get_current_ms())
if self.move_away.is_finished and self.text_fade_out is None:
self.text_fade_out = Animation.create_fade(33)
self.text_fade_in = Animation.create_fade(33, initial_opacity=0.0, final_opacity=1.0, delay=self.text_fade_out.duration)
self.move_away.update(get_current_ms())
if self.text_fade_out is not None:
self.text_fade_out.update(get_current_ms())
if self.text_fade_out.is_finished:
self.is_difficulty_select = True
if self.text_fade_in is not None:
self.text_fade_in.update(get_current_ms())
self.diff_fade_out.update(get_current_ms())
if self.is_difficulty_select:
self.update_difficulty_select()
else:
self.update_song_select()
for box in self.song_boxes:
box.update(self.is_difficulty_select)
if self.game_transition is not None:
self.game_transition.update(get_current_ms())
if self.game_transition.is_finished:
return self.on_screen_end()
def draw_song_select(self):
for box in self.song_boxes:
if box.position <= 500:
box.draw(box.position - int(self.move_away.attribute), 95, 620, self.textures, int(self.diff_fade_out.attribute))
else:
box.draw(box.position + int(self.move_away.attribute), 95, 620, self.textures, int(self.diff_fade_out.attribute))
def draw_selector(self):
if self.selected_difficulty == -1:
ray.draw_texture(self.textures['song_select'][133], 314, 110, ray.WHITE)
else:
ray.draw_texture(self.textures['song_select'][140], 450 + (self.selected_difficulty * 115), 7, ray.WHITE)
ray.draw_texture(self.textures['song_select'][131], 461 + (self.selected_difficulty * 115), 132, ray.WHITE)
def draw_difficulty_select(self):
for box in self.song_boxes:
if box.is_open:
box.draw(box.position, 95, 620, self.textures, int(self.diff_fade_out.attribute))
self.draw_selector()
def draw(self):
texture = self.textures['song_select'][784]
for i in range(0, texture.width * 4, texture.width):
ray.draw_texture(self.textures['song_select'][784], i - int(self.background_move.attribute), 0, ray.WHITE)
if self.is_difficulty_select:
self.draw_difficulty_select()
fade = ray.WHITE
if self.text_fade_in is not None:
fade = ray.fade(ray.WHITE, self.text_fade_in.attribute)
ray.draw_texture(self.textures['song_select'][192], 5, 5, fade)
else:
self.draw_song_select()
fade = ray.WHITE
if self.text_fade_out is not None:
fade = ray.fade(ray.WHITE, self.text_fade_out.attribute)
ray.draw_texture(self.textures['song_select'][244], 5, 5, fade)
ray.draw_texture(self.textures['song_select'][394], 0, self.height - self.textures['song_select'][394].height, ray.WHITE)
if self.game_transition is not None:
self.game_transition.draw(self.height)
class SongBox:
def __init__(self, tja_path: str, position: int):
self.tja_path = tja_path
self.scores = dict()
self.position = position
self.start_position = position
tja = TJAParser(tja_path)
self.course_data = tja.get_metadata()
for diff in self.course_data[8].keys():
self.scores[diff] = self._get_scores(tja, diff)
self.is_open = False
self.name = None
self.yellow_box = None
self.move = Animation.create_move(0)
self.wait = 0
self.update(False)
def reset(self):
self.yellow_box = YellowBox(self.name)
def _load_font_for_text(self, text: str) -> ray.Font:
codepoint_count = ray.ffi.new('int *', 0)
unique_codepoints = set(text)
codepoints = ray.load_codepoints(''.join(unique_codepoints), codepoint_count)
return ray.load_font_ex(str(Path('Graphics/Modified-DFPKanteiryu-XB.ttf')), 40, codepoints, 0)
def _get_scores(self, tja: TJAParser, difficulty: int):
with sqlite3.connect('scores.db') as con:
cursor = con.cursor()
hash = tja.hash_note_data(tja.data_to_notes(difficulty)[0])
check_query = "SELECT score, good, ok, bad FROM Scores WHERE hash = ? LIMIT 1"
cursor.execute(check_query, (hash,))
result = cursor.fetchone()
return result
def move_left(self):
if not self.move.is_finished:
self.position = self.start_position
self.move = Animation.create_move(0)
return
self.start_position = self.position
self.move = Animation.create_move(66.67, start_position=0, total_distance=100)
if self.is_open:
self.move.total_distance = 250
elif self.position + self.move.total_distance == SongSelectScreen.BOX_CENTER:
self.move.total_distance = 250
elif SongSelectScreen.BOX_CENTER < self.position + self.move.total_distance < SongSelectScreen.BOX_CENTER + 300:
self.move.total_distance = 400
def move_right(self):
if not self.move.is_finished:
self.position = self.start_position
self.move = Animation.create_move(0)
return
self.start_position = self.position
self.move = Animation.create_move(66.67, start_position=0, total_distance=-100)
if self.is_open:
self.move.total_distance = -250
elif self.position + (self.move.total_distance - 300) == SongSelectScreen.BOX_CENTER:
self.move.total_distance = -250
elif SongSelectScreen.BOX_CENTER < self.position + self.move.total_distance < SongSelectScreen.BOX_CENTER + 300:
self.move.total_distance = -400
def update(self, is_diff_select):
self.is_diff_select = is_diff_select
if self.yellow_box is not None:
self.yellow_box.update(is_diff_select)
self.move.update(get_current_ms())
self.position = self.start_position + int(self.move.attribute)
is_open_prev = self.is_open
self.is_open = self.position == SongSelectScreen.BOX_CENTER + 150
if not is_open_prev and self.is_open:
if self.yellow_box is not None:
self.wait = get_current_ms()
self.yellow_box.create_anim()
if self.name is None and 0 <= self.position <= 1280:
name = self.course_data[0]
font = self._load_font_for_text(name)
self.name = OutlinedText(font, name, 40, ray.WHITE, ray.BLACK, outline_thickness=4, vertical=True)
self.yellow_box = YellowBox(self.name)
def _draw_closed(self, x: int, y: int, texture_index: int, textures):
ray.draw_texture(textures['song_select'][texture_index+1], x, y, ray.WHITE)
for i in range(0, textures['song_select'][texture_index].width * 4, textures['song_select'][texture_index].width):
ray.draw_texture(textures['song_select'][texture_index], (x+32)+i, y, ray.WHITE)
ray.draw_texture(textures['song_select'][texture_index+2], x+64, y, ray.WHITE)
ray.draw_texture(textures['song_select'][texture_index+3], x+12, y+16, ray.WHITE)
if self.name is not None:
src = ray.Rectangle(0, 0, self.name.texture.width, self.name.texture.height)
dest = ray.Rectangle(x + 47 - int(self.name.texture.width / 2), y+35, self.name.texture.width, min(self.name.texture.height, 417))
self.name.draw(src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
def draw(self, x: int, y: int, texture_index: int, textures, fade_override=None):
if self.is_open and get_current_ms() >= self.wait + 83.33:
if self.yellow_box is not None:
self.yellow_box.draw(textures, self, fade_override)
else:
self._draw_closed(x, y, texture_index, textures)
class YellowBox:
def __init__(self, name):
self.is_diff_select = False
self.right_x = 803
self.left_x = 443
self.top_y = 96
self.bottom_y = 543
self.center_width = 332
self.center_height = 422
self.edge_height = 32
self.name = name
self.anim_created = False
self.left_out = Animation.create_move(83.33, total_distance=-152, delay=83.33)
self.right_out = Animation.create_move(83.33, total_distance=145, delay=83.33)
self.center_out = Animation.create_move(83.33, total_distance=300, delay=83.33)
self.fade = Animation.create_fade(83.33, initial_opacity=1.0, final_opacity=1.0, delay=83.33)
self.reset_animations()
def reset_animations(self):
self.fade_in = Animation.create_fade(float('inf'), initial_opacity=0.0, final_opacity=1.0, delay=83.33)
self.left_out_2 = Animation.create_move(float('inf'), total_distance=-213)
self.right_out_2 = Animation.create_move(float('inf'), total_distance=0)
self.center_out_2 = Animation.create_move(float('inf'), total_distance=423)
self.top_y_out = Animation.create_move(float('inf'), total_distance=-62)
self.center_h_out = Animation.create_move(float('inf'), total_distance=60)
def create_anim(self):
self.left_out = Animation.create_move(83.33, total_distance=-152, delay=83.33)
self.right_out = Animation.create_move(83.33, total_distance=145, delay=83.33)
self.center_out = Animation.create_move(83.33, total_distance=300, delay=83.33)
self.fade = Animation.create_fade(83.33, initial_opacity=0.0, final_opacity=1.0, delay=83.33)
def create_anim_2(self):
self.left_out_2 = Animation.create_move(116.67, total_distance=-213)
self.right_out_2 = Animation.create_move(116.67, total_distance=211)
self.center_out_2 = Animation.create_move(116.67, total_distance=423)
self.top_y_out = Animation.create_move(133.33, total_distance=-62, delay=self.left_out_2.duration)
self.center_h_out = Animation.create_move(133.33, total_distance=60, delay=self.left_out_2.duration)
self.fade_in = Animation.create_fade(116.67, initial_opacity=0.0, final_opacity=1.0, delay=self.left_out_2.duration + self.top_y_out.duration + 16.67)
def update(self, is_diff_select):
self.left_out.update(get_current_ms())
self.right_out.update(get_current_ms())
self.center_out.update(get_current_ms())
self.fade.update(get_current_ms())
self.fade_in.update(get_current_ms())
self.left_out_2.update(get_current_ms())
self.right_out_2.update(get_current_ms())
self.center_out_2.update(get_current_ms())
self.top_y_out.update(get_current_ms())
self.center_h_out.update(get_current_ms())
self.is_diff_select = is_diff_select
if self.is_diff_select:
if not self.anim_created:
self.anim_created = True
self.create_anim_2()
self.right_x = 803 + int(self.right_out_2.attribute)
self.left_x = 443 + int(self.left_out_2.attribute)
self.top_y = 96 + int(self.top_y_out.attribute)
self.center_width = 332 + int(self.center_out_2.attribute)
self.center_height = 422 + int(self.center_h_out.attribute)
else:
self.anim_created = False
self.right_x = 658 + int(self.right_out.attribute)
self.left_x = 595 + int(self.left_out.attribute)
self.top_y = 96
self.center_width = 32 + int(self.center_out.attribute)
self.center_height = 422
def draw(self, textures: dict[str, list[ray.Texture]], song_box: SongBox, fade_override):
# Draw corners
ray.draw_texture(textures['song_select'][235], self.right_x, self.bottom_y, ray.WHITE) # Bottom right
ray.draw_texture(textures['song_select'][236], self.left_x, self.bottom_y, ray.WHITE) # Bottom left
ray.draw_texture(textures['song_select'][237], self.right_x, self.top_y, ray.WHITE) # Top right
ray.draw_texture(textures['song_select'][238], self.left_x, self.top_y, ray.WHITE) # Top left
# Edges
# Bottom edge
texture = textures['song_select'][231]
src = ray.Rectangle(0, 0, texture.width, texture.height)
dest = ray.Rectangle(self.left_x + self.edge_height, self.bottom_y, self.center_width, texture.height)
ray.draw_texture_pro(texture, src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
# Right edge
texture = textures['song_select'][232]
src = ray.Rectangle(0, 0, texture.width, texture.height)
dest = ray.Rectangle(self.right_x, self.top_y + self.edge_height, texture.width, self.center_height)
ray.draw_texture_pro(texture, src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
# Left edge
texture = textures['song_select'][233]
src = ray.Rectangle(0, 0, texture.width, texture.height)
dest = ray.Rectangle(self.left_x, self.top_y + self.edge_height, texture.width, self.center_height)
ray.draw_texture_pro(texture, src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
# Top edge
texture = textures['song_select'][234]
src = ray.Rectangle(0, 0, texture.width, texture.height)
dest = ray.Rectangle(self.left_x + self.edge_height, self.top_y, self.center_width, texture.height)
ray.draw_texture_pro(texture, src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
# Center
texture = textures['song_select'][230]
src = ray.Rectangle(0, 0, texture.width, texture.height)
dest = ray.Rectangle(self.left_x + self.edge_height, self.top_y + self.edge_height, self.center_width, self.center_height)
ray.draw_texture_pro(texture, src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
if self.is_diff_select:
#Back Button
color = ray.fade(ray.WHITE, self.fade_in.attribute)
ray.draw_texture(textures['song_select'][153], 314, 110, color)
#Difficulties
ray.draw_texture(textures['song_select'][154], 450, 90, color)
ray.draw_texture(textures['song_select'][182], 565, 90, color)
ray.draw_texture(textures['song_select'][185], 680, 90, color)
ray.draw_texture(textures['song_select'][188], 795, 90, color)
for i in range(4):
try:
for j in range(song_box.course_data[8][i][0]):
ray.draw_texture(textures['song_select'][155], 482+(i*115), 471+(j*-20), color)
except:
pass
else:
#Crowns
fade = self.fade.attribute
if fade_override is not None:
fade = min(self.fade.attribute, fade_override)
color = ray.fade(ray.WHITE, fade)
for i in range(4):
if i in song_box.scores and song_box.scores[i] is not None and song_box.scores[i][3] == 0:
ray.draw_texture(textures['song_select'][160], 473 + (i*60), 175, color)
ray.draw_texture(textures['song_select'][158], 473 + (i*60), 175, ray.fade(color, min(fade, 0.25)))
#Difficulties
ray.draw_texture(textures['song_select'][395], 458, 210, color)
ray.draw_texture(textures['song_select'][401], 518, 210, color)
ray.draw_texture(textures['song_select'][403], 578, 210, color)
ray.draw_texture(textures['song_select'][406], 638, 210, color)
#Stars
for i in range(4):
try:
for j in range(song_box.course_data[8][i][0]):
ray.draw_texture(textures['song_select'][396], 474+(i*60), 490+(j*-17), color)
except:
pass
if self.name is not None:
texture = self.name.texture
src = ray.Rectangle(0, 0, texture.width, texture.height)
dest = ray.Rectangle(((song_box.position + 47) - texture.width / 2) + (int(self.right_out.attribute)*0.85) + (int(self.right_out_2.attribute)), self.top_y+35, texture.width, min(texture.height, 417))
self.name.draw(src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
class Transition:
def __init__(self, screen_height) -> None:
self.is_finished = False
self.rainbow_up = Animation.create_move(266, start_position=0, total_distance=screen_height + global_data.textures['scene_change_rainbow'][2].height, ease_in='cubic')
self.chara_down = None
def update(self, current_time_ms: float):
self.rainbow_up.update(current_time_ms)
if self.rainbow_up.is_finished and self.chara_down is None:
self.chara_down = Animation.create_move(33, start_position=0, total_distance=30)
if self.chara_down is not None:
self.chara_down.update(current_time_ms)
self.is_finished = self.chara_down.is_finished
def draw(self, screen_height):
ray.draw_texture(global_data.textures['scene_change_rainbow'][2], 0, screen_height - int(self.rainbow_up.attribute), ray.WHITE)
texture = global_data.textures['scene_change_rainbow'][0]
src = ray.Rectangle(0, 0, texture.width, texture.height)
dest = ray.Rectangle(0, screen_height - int(self.rainbow_up.attribute) + global_data.textures['scene_change_rainbow'][2].height, texture.width, screen_height)
ray.draw_texture_pro(texture, src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
texture = global_data.textures['scene_change_rainbow'][3]
offset = 0
if self.chara_down is not None:
offset = int(self.chara_down.attribute)
ray.draw_texture(texture, 76, 816 - int(self.rainbow_up.attribute) + offset, ray.WHITE)