improve results screen, fix many bugs

This commit is contained in:
Yonokid
2025-04-25 17:56:35 -04:00
parent ac927114cb
commit e2552c0e86
11 changed files with 381 additions and 172 deletions

View File

@@ -11,20 +11,15 @@ Download for OS of choice on releases page
How to run:
Windows:
```bash
PyTaiko.exe {"Song Name"} {difficulty number 0-4}
PyTaiko.exe
```
MacOS/Linux:
```bash
PyTaiko.bin {"Song Name"} {difficulty number 0-4}
```
PyTaiko.bin
## Roadmap
- Add Kusudama notes
- add basic song select
- add basic results screen
https://linear.app/pytaiko
## Known Issues
@@ -64,7 +59,7 @@ Start the game
#### Keybinds?
EFJI
see config.toml
#### Why does it look like Gen 3?
@@ -74,4 +69,3 @@ I like it
## Contributing
I would highly suggest not contributing to this until sizeable progress has been made. The fabric of this code is ever changing and anything you write could disappear at any time

View File

@@ -13,7 +13,7 @@ right_don = ['J','D']
right_kat = ['I','U']
[audio]
device_type = 'WASAPI'
device_type = 'ASIO'
asio_buffer = 6
[video]

View File

@@ -65,8 +65,8 @@ class TJAParser:
def get_metadata(self):
self._file_to_data()
diff_index = 1
highest_diff = -1
current_diff = None # Track which difficulty we're currently processing
for item in self.data:
if item[0] == '#':
continue
@@ -87,58 +87,88 @@ class TJAParser:
elif 'DEMOSTART' in item:
self.demo_start = float(item.split(':')[1])
elif 'COURSE' in item:
course = str(item.split(':')[1]).lower()
# Determine which difficulty we're now processing
course = str(item.split(':')[1]).lower().strip()
# Map the course string to its corresponding index
if course == 'dan' or course == '6':
current_diff = 6
self.course_data[6] = []
if course == 'tower' or course == '5':
elif course == 'tower' or course == '5':
current_diff = 5
self.course_data[5] = []
elif course == 'edit' or course == '4':
current_diff = 4
self.course_data[4] = []
elif course == 'oni' or course == '3':
current_diff = 3
self.course_data[3] = []
elif course == 'hard' or course == '2':
current_diff = 2
self.course_data[2] = []
elif course == 'normal' or course == '1':
current_diff = 1
self.course_data[1] = []
elif course == 'easy' or course == '0':
current_diff = 0
self.course_data[0] = []
highest_diff = max(self.course_data)
diff_index -= 1
elif 'LEVEL' in item:
item = int(item.split(':')[1])
self.course_data[diff_index+highest_diff].append(item)
elif 'BALLOON' in item:
item = item.split(':')[1]
if item == '':
continue
self.course_data[diff_index+highest_diff].append([int(x) for x in item.split(',')])
elif 'SCOREINIT' in item:
if item.split(':')[1] == '':
continue
item = item.split(':')[1]
self.course_data[diff_index+highest_diff].append([int(x) for x in item.split(',')])
elif 'SCOREDIFF' in item:
if item.split(':')[1] == '':
continue
item = int(item.split(':')[1])
self.course_data[diff_index+highest_diff].append(item)
# Only process these items if we have a current difficulty
elif current_diff is not None:
if 'LEVEL' in item:
level = int(item.split(':')[1])
self.course_data[current_diff].append(level)
elif 'BALLOON' in item:
balloon_data = item.split(':')[1]
if balloon_data == '':
continue
self.course_data[current_diff].append([int(x) for x in balloon_data.split(',')])
elif 'SCOREINIT' in item:
score_init = item.split(':')[1]
if score_init == '':
continue
self.course_data[current_diff].append([int(x) for x in score_init.split(',')])
elif 'SCOREDIFF' in item:
score_diff = item.split(':')[1]
if score_diff == '':
continue
self.course_data[current_diff].append(int(score_diff))
return [self.title, self.title_ja, self.subtitle, self.subtitle_ja,
self.bpm, self.wave, self.offset, self.demo_start, self.course_data]
self.bpm, self.wave, self.offset, self.demo_start, self.course_data]
def data_to_notes(self, diff):
self._file_to_data()
#Get notes start and end
note_start = -1
note_end = -1
diff_count = 0
for i in range(len(self.data)):
if self.data[i] == '#START':
note_start = i+1
elif self.data[i] == '#END':
note_end = i
diff_count += 1
if diff_count == len(self.course_data) - diff:
break
target_found = False
diffs = {0: "easy", 1: "normal", 2: "hard", 3: "oni", 4: "edit", 5: "tower", 6: "dan"}
# Get the name corresponding to this difficulty number
diff_name = diffs.get(diff, "").lower()
i = 0
while i < len(self.data):
line = self.data[i]
# Check if this is the start of a difficulty section
if line.startswith("COURSE:"):
course_value = line[7:].strip().lower()
# Match either the exact number or the name
if (course_value.isdigit() and int(course_value) == diff) or course_value == diff_name:
target_found = True
else:
target_found = False
# If we found our target section, look for START and END markers
if target_found:
if line == "#START":
note_start = i + 1
elif line == "#END" and note_start != -1:
note_end = i
break # We found our complete section
i += 1
notes = []
bar = []
@@ -148,19 +178,18 @@ class TJAParser:
if line.startswith("#"):
bar.append(line)
else:
item = line.strip(',')
if item == '':
if bar == []:
bar.append(item)
else:
notes.append(bar)
bar = []
continue
if line == ',':
if len(bar) == 0 or all(item.startswith('#') for item in bar):
bar.append('')
notes.append(bar)
bar = []
else:
item = line.strip(',')
bar.append(item)
if item != line:
notes.append(bar)
bar = []
print(self.course_data)
if len(self.course_data[diff]) < 2:
return notes, None
return notes, self.course_data[diff][1]

View File

@@ -97,7 +97,6 @@ def get_config() -> dict[str, Any]:
@dataclass
class GlobalData:
videos_cleared = False
start_song: bool = False
selected_song: str = '' #Path
selected_difficulty: int = -1
song_title: str = ''

View File

@@ -1,3 +1,8 @@
raylib==5.0.0.3
raylib_dynamic==5.0.0.3
opencv-python==4.10.0.84
numpy==2.2.5
opencv_python==4.11.0.86
pydub==0.25.1
pyray==0.1.0
raylib==5.5.0.2
raylib_dynamic==5.5.0.2
scipy==1.15.2
sounddevice==0.5.1

3
requirements.txt.bak Normal file
View File

@@ -0,0 +1,3 @@
raylib==5.0.0.3
raylib_dynamic==5.0.0.3
opencv-python==4.10.0.84

View File

@@ -10,9 +10,20 @@ class EntryScreen:
self.texture_footer = load_texture_from_zip('Graphics\\lumendata\\entry.zip', 'entry_img00375.png')
self.screen_init = False
def on_screen_start(self):
if not self.screen_init:
self.screen_init = True
def on_screen_end(self):
self.screen_init = False
return "SONG_SELECT"
def update(self):
self.on_screen_start()
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
return "SONG_SELECT"
return self.on_screen_end()
def draw(self):
ray.draw_texture(self.texture_footer, 0, self.height - 151, ray.WHITE)

View File

@@ -24,7 +24,12 @@ class GameScreen:
self.height = height
self.judge_x = 414
self.current_ms = 0
self.song_is_started = False
self.result_transition = None
self.song_info = None
self.screen_init = False
def load_textures(self):
self.textures = load_all_textures_from_zip('Graphics\\lumendata\\enso_system\\common.zip')
@@ -130,11 +135,16 @@ class GameScreen:
self.textures.update(load_all_textures_from_zip('Graphics\\lumendata\\enso_system\\base1p.zip'))
self.textures.update(load_all_textures_from_zip('Graphics\\lumendata\\enso_system\\don1p.zip'))
self.result_transition_1 = load_texture_from_zip('Graphics\\lumendata\\enso_result.zip', 'retry_game_img00125.png')
self.result_transition_2 = load_texture_from_zip('Graphics\\lumendata\\enso_result.zip', 'retry_game_img00126.png')
def load_sounds(self):
self.sound_don = audio.load_sound('Sounds\\inst_00_don.wav')
self.sound_kat = audio.load_sound('Sounds\\inst_00_katsu.wav')
self.sound_balloon_pop = audio.load_sound('Sounds\\balloon_pop.wav')
self.sound_result_transition = audio.load_sound('Sounds\\result\\VO_RESULT [1].ogg')
def init_tja(self, song: str, difficulty: int):
self.load_textures()
self.load_sounds()
@@ -165,24 +175,46 @@ class GameScreen:
audio.play_sound(self.song_music)
def on_screen_start(self):
if not self.screen_init:
self.screen_init = True
self.init_tja(global_data.selected_song, global_data.selected_difficulty)
self.current_ms = get_current_ms() - self.start_ms
self.song_info = SongInfo(self.current_ms, self.tja.title, 'TEST')
self.result_transition = None
def on_screen_end(self):
self.screen_init = False
for zip in self.textures:
for texture in self.textures[zip]:
ray.unload_texture(texture)
return 'RESULT'
def update(self):
if global_data.start_song and not self.song_is_started:
self.init_tja(global_data.selected_song, global_data.selected_difficulty)
self.song_is_started = True
self.on_screen_start()
self.current_ms = get_current_ms() - self.start_ms
self.player_1.update(self)
if len(self.player_1.play_note_list) == 0 and not audio.is_sound_playing(self.song_music):
self.player_1.update(self)
if self.song_info is not None:
self.song_info.update(self.current_ms)
if self.result_transition is not None:
self.result_transition.update(self.current_ms)
if self.result_transition.is_finished:
return self.on_screen_end()
elif len(self.player_1.play_note_list) == 0 and (len(self.player_1.current_notes) == 0) and not audio.is_sound_playing(self.song_music):
global_data.result_good, global_data.result_ok, global_data.result_bad, global_data.result_score = self.player_1.get_result_score()
global_data.start_song = False
self.song_is_started = False
return 'RESULT'
self.result_transition = ResultTransition(self.current_ms, self.height)
audio.play_sound(self.sound_result_transition)
def draw(self):
self.player_1.draw(self)
if self.song_info is not None:
self.song_info.draw(self)
if self.result_transition is not None:
self.result_transition.draw(self.width, self.height, self.result_transition_1, self.result_transition_2)
class Player:
def __init__(self, game_screen: GameScreen, player_number: int, difficulty: int, judge_offset: int):
@@ -218,6 +250,7 @@ class Player:
self.combo = 0
self.score = 0
self.max_combo = 0
self.total_drumroll = 0
self.arc_points = 25
@@ -225,12 +258,11 @@ class Player:
self.draw_effect_list: list[LaneHitEffect] = []
self.draw_arc_list: list[NoteArc] = []
self.draw_drum_hit_list: list[DrumHitEffect] = []
self.drumroll_counter: list[DrumrollCounter] = []
self.drumroll_counter: DrumrollCounter | None = None
self.balloon_list: list[BalloonAnimation] = []
self.base_score_list: list[ScoreCounterAnimation] = []
self.combo_display = Combo(self.combo, game_screen.current_ms)
self.score_counter = ScoreCounter(self.score, game_screen.current_ms)
self.song_info = SongInfo(game_screen.current_ms, game_screen.tja.title, 'TEST')
self.input_log: dict[float, str] = dict()
@@ -304,7 +336,6 @@ class Player:
else:
bisect.insort_left(self.current_notes_draw, self.draw_note_list.popleft(), key=lambda x: x['index'])
#If a note is off screen, remove it
if len(self.current_notes_draw) == 0:
return
@@ -316,8 +347,6 @@ class Player:
note = self.current_notes_draw[1]
position = self.get_position(game_screen, note['ms'], note['ppf'])
if position < game_screen.judge_x + 650:
if note['note'] == '8':
self.current_notes_draw.pop(0)
self.current_notes_draw.pop(0)
def note_manager(self, game_screen: GameScreen):
@@ -347,6 +376,7 @@ class Player:
note_type = game_screen.note_type_dict[str(int(drum_type)+self.drumroll_big)]
self.draw_arc_list.append(NoteArc(note_type, game_screen.current_ms, self.player_number))
self.curr_drumroll_count += 1
self.total_drumroll += 1
self.score += 100
self.base_score_list.append(ScoreCounterAnimation(game_screen.current_ms, 100))
color = max(0, 255 - (self.curr_drumroll_count * 10))
@@ -359,6 +389,7 @@ class Player:
if len(self.balloon_list) < 1:
self.balloon_list.append(BalloonAnimation(game_screen.current_ms, current_note['balloon']))
self.curr_balloon_count += 1
self.total_drumroll += 1
self.score += 100
self.base_score_list.append(ScoreCounterAnimation(game_screen.current_ms, 100))
self.current_notes_draw[0]['popped'] = False
@@ -367,8 +398,6 @@ class Player:
self.current_notes_draw[0]['popped'] = True
audio.play_sound(game_screen.sound_balloon_pop)
self.note_correct(game_screen, self.current_notes[0])
if len(self.current_notes) > 1 and self.current_notes[0]['note'] == '8':
self.note_correct(game_screen, self.current_notes[0])
def check_note(self, game_screen: GameScreen, drum_type: str):
if self.is_drumroll:
@@ -421,17 +450,14 @@ class Player:
self.current_notes.popleft()
def drumroll_counter_manager(self, game_screen: GameScreen):
if self.is_drumroll and self.curr_drumroll_count > 0:
if len(self.drumroll_counter) == 0 or self.drumroll_counter[-1].is_finished:
self.drumroll_counter.append(DrumrollCounter(game_screen.current_ms))
if self.is_drumroll and self.curr_drumroll_count > 0 and self.drumroll_counter is None:
self.drumroll_counter = DrumrollCounter(game_screen.current_ms)
if len(self.drumroll_counter) <= 0:
return
if self.drumroll_counter[0].is_finished and not self.is_drumroll:
self.drumroll_counter.pop(0)
else:
self.drumroll_counter[0].update(game_screen, game_screen.current_ms, self.curr_drumroll_count)
if self.drumroll_counter is not None:
if self.drumroll_counter.is_finished and not self.is_drumroll:
self.drumroll_counter = None
else:
self.drumroll_counter.update(game_screen, game_screen.current_ms, self.curr_drumroll_count)
def balloon_animation_manager(self, game_screen: GameScreen):
if len(self.balloon_list) <= 0:
@@ -489,7 +515,6 @@ class Player:
self.animation_manager(game_screen, self.draw_drum_hit_list)
self.animation_manager(game_screen, self.draw_arc_list)
self.animation_manager(game_screen, self.base_score_list)
self.song_info.update(game_screen.current_ms)
self.score_counter.update(game_screen.current_ms, self.score)
self.key_manager(game_screen)
@@ -499,10 +524,9 @@ class Player:
def draw_drumroll(self, game_screen: GameScreen, big: bool, position: int, index: int, color: int):
drumroll_start_position = position
i = 1
while self.current_notes_draw[index+i]['note'] != '8':
i += 1
tail = self.current_notes_draw[index+i]
tail = next((note for note in self.current_notes_draw[index+1:] if note['note'] == '8'), None)
if tail is None:
return
if big:
drumroll_body = 'dai_drumroll_body'
drumroll_tail = 'dai_drumroll_tail'
@@ -515,17 +539,16 @@ class Player:
self.draw_note(game_screen, drumroll_tail, drumroll_end_position, color, 10, drumroll_length=None)
def draw_balloon(self, game_screen: GameScreen, note: dict, position: int, index: int):
if self.current_notes_draw[0].get('popped', None):
tail = next((note for note in self.current_notes_draw[index+1:] if note['note'] == '8'), None)
if tail is None:
return
i = 1
while self.current_notes_draw[index+i]['note'] != '8':
i += 1
end_time = self.current_notes_draw[index+i]
end_time_position = self.get_position(game_screen, end_time['load_ms'], end_time['ppf'])
if game_screen.current_ms >= end_time['ms']:
position = end_time_position
tail_position = self.get_position(game_screen, tail['load_ms'], tail['ppf'])
if game_screen.current_ms >= tail['ms']:
position = tail_position
elif game_screen.current_ms >= note['ms']:
position = 349
if note.get('popped', None):
return
self.draw_note(game_screen, '7', position, 255, 9)
def draw_note(self, game_screen: GameScreen, note: str, position: int, color: int, se_note: int | None, drumroll_length: int | None=None):
@@ -587,8 +610,6 @@ class Player:
note = self.current_notes_draw[i]
note_type, load_ms, pixels_per_frame = note['note'], note['load_ms'], note['ppf']
position = self.get_position(game_screen, load_ms, pixels_per_frame)
if 'popped' in note:
continue
if note_type == '5':
self.draw_drumroll(game_screen, False, position, i, note['color'])
elif note_type == '6':
@@ -597,7 +618,7 @@ class Player:
self.draw_balloon(game_screen, note, position, i)
else:
self.draw_note(game_screen, note_type, position, 255, note['se_note'])
#ray.draw_text(str(i), position+64, 192, 25, ray.GREEN)
ray.draw_text(str(i), position+64, 192, 25, ray.GREEN)
def draw_gauge(self, game_screen: GameScreen):
ray.draw_texture(game_screen.textures['gage_don_1p_hard'][0], 327, 128, ray.WHITE)
@@ -621,8 +642,8 @@ class Player:
ray.draw_texture(game_screen.textures['lane_obi'][3], 0, 184, ray.WHITE)
ray.draw_texture(game_screen.textures['lane_obi'][19], 0, 225, ray.WHITE)
ray.draw_texture(game_screen.texture_difficulty[self.difficulty], 50, 222, ray.WHITE)
self.song_info.draw(game_screen)
self.draw_animation_list(game_screen, self.drumroll_counter)
if self.drumroll_counter is not None:
self.drumroll_counter.draw(game_screen)
self.draw_animation_list(game_screen, self.draw_arc_list)
self.draw_animation_list(game_screen, self.balloon_list)
self.score_counter.draw(game_screen)
@@ -787,6 +808,7 @@ class DrumrollCounter:
def update_count(self, current_ms: float, count: int, elapsed_time: float):
self.total_duration = elapsed_time + 1349
self.fade_animation.params['delay'] = self.total_duration - 166
if self.drumroll_count != count:
self.drumroll_count = count
self.stretch_animation = Animation(current_ms, 50, 'text_stretch')
@@ -1028,40 +1050,93 @@ class ScoreCounterAnimation:
ray.draw_texture_pro(game_screen.textures['score_add_1p'][int(counter[i])], source_rect, dest_rect, ray.Vector2(0,0), 0, self.color)
class SongInfo:
FADE_DURATION = 366
DISPLAY_DURATION = 1666
def __init__(self, current_ms: float, song_name: str, genre: str):
self.fade_in = Animation(current_ms, 366, 'fade')
self.fade_in.params['initial_opacity'] = 0.0
self.fade_in.params['final_opacity'] = 1.0
self.fade_in = self._create_fade_in_animation(current_ms)
self.fade_out = self._create_fade_out_animation(current_ms)
self.fade_out = Animation(current_ms, 366, 'fade')
self.fade_out.params['initial_opacity'] = 1.0
self.fade_out.params['final_opacity'] = 0.0
self.fade_out.params['delay'] = 1666 + self.fade_in.duration
self.song_name = song_name
self.genre = genre
self.is_finished = False
self.song_num_fade = ray.WHITE
self.song_name_fade = ray.WHITE
self.font = self._load_font_for_text(song_name)
self.song_title = OutlinedText(
self.font, song_name, 40, ray.WHITE, ray.BLACK, outline_thickness=5
)
def _create_fade_in_animation(self, start_ms: float) -> Animation:
fade_in = Animation(start_ms, self.FADE_DURATION, 'fade')
fade_in.params['initial_opacity'] = 0.0
fade_in.params['final_opacity'] = 1.0
return fade_in
def _create_fade_out_animation(self, start_ms: float) -> Animation:
fade_out = Animation(start_ms, self.FADE_DURATION, 'fade')
fade_out.params['initial_opacity'] = 1.0
fade_out.params['final_opacity'] = 0.0
fade_out.params['delay'] = self.DISPLAY_DURATION + self.FADE_DURATION
return fade_out
def _load_font_for_text(self, text: str) -> ray.Font:
codepoint_count = ray.ffi.new('int *', 0)
codepoints_no_dup = set()
codepoints_no_dup.update(song_name)
codepoints = ray.load_codepoints(''.join(codepoints_no_dup), codepoint_count)
self.font = ray.load_font_ex('Graphics\\Modified-DFPKanteiryu-XB.ttf', 32, codepoints, 0)
self.song_title = OutlinedText(self.font, song_name, 40, ray.WHITE, ray.BLACK, outline_thickness=5)
unique_codepoints = set(text)
codepoints = ray.load_codepoints(''.join(unique_codepoints), codepoint_count)
return ray.load_font_ex('Graphics\\Modified-DFPKanteiryu-XB.ttf', 32, codepoints, 0)
def update(self, current_ms: float):
self.fade_in.update(current_ms)
self.fade_out.update(current_ms)
self.song_num_fade = ray.fade(ray.WHITE, self.fade_in.attribute)
self.song_name_fade = ray.fade(ray.WHITE, 1 - self.fade_in.attribute)
if self.fade_in.is_finished:
if not self.fade_in.is_finished:
self.song_num_fade = ray.fade(ray.WHITE, self.fade_in.attribute)
self.song_name_fade = ray.fade(ray.WHITE, 1 - self.fade_in.attribute)
else:
self.song_num_fade = ray.fade(ray.WHITE, self.fade_out.attribute)
self.song_name_fade = ray.fade(ray.WHITE, 1 - self.fade_out.attribute)
if self.fade_out.is_finished:
self.fade_in.start_ms = current_ms + 1666
self.fade_out.start_ms = current_ms + 1666
self.fade_in.is_finished = False
self.fade_out.is_finished = False
self._reset_animations(current_ms)
def _reset_animations(self, current_ms: float):
next_cycle_start = current_ms + self.DISPLAY_DURATION
self.fade_in = self._create_fade_in_animation(next_cycle_start)
self.fade_out = self._create_fade_out_animation(next_cycle_start)
def draw(self, game_screen: GameScreen):
ray.draw_texture(game_screen.textures['song_info'][(global_data.songs_played % 4) + 8], 1132, 25, self.song_num_fade)
self.song_title.draw(1252 - self.song_title.texture.width, int(50 - self.song_title.texture.height / 2), self.song_name_fade)
song_texture_index = (global_data.songs_played % 4) + 8
ray.draw_texture(
game_screen.textures['song_info'][song_texture_index],
1132, 25,
self.song_num_fade
)
text_x = 1252 - self.song_title.texture.width
text_y = int(50 - self.song_title.texture.height / 2)
self.song_title.draw(text_x, text_y, self.song_name_fade)
class ResultTransition:
def __init__(self, current_ms: float, screen_height: int):
self.move = Animation(current_ms, 983.33, 'move')
self.move.params['start_position'] = 0.0
self.move.params['total_distance'] = screen_height//2
self.is_finished = False
def update(self, current_ms: float):
self.move.update(current_ms)
if self.move.is_finished:
self.is_finished = True
def draw(self, screen_width: int, screen_height: int, texture_1: ray.Texture, texture_2: ray.Texture):
x = 0
while x < screen_width:
ray.draw_texture(texture_1, x, (0 - texture_1.height) + int(self.move.attribute), ray.WHITE)
ray.draw_texture(texture_1, x, (screen_height) - int(self.move.attribute), ray.WHITE)
x += texture_1.width
x = 0
while x < screen_width:
ray.draw_texture(texture_2, x, (0 - texture_2.height) + int(self.move.attribute / ((screen_height//2) / (texture_2.height//2))), ray.WHITE)
ray.draw_texture(texture_2, x, (screen_height) - int(self.move.attribute / ((screen_height//2) / (texture_2.height//2))), ray.WHITE)
x += texture_2.width

View File

@@ -1,11 +1,12 @@
import pyray as ray
from libs.animation import Animation
from libs.audio import audio
from libs.utils import (
OutlinedText,
get_current_ms,
global_data,
load_all_textures_from_zip,
load_image_from_zip,
)
@@ -17,31 +18,61 @@ def draw_scaled_texture(texture, x: int, y: int, scale: float, color: ray.Color)
ray.draw_texture_pro(texture, src_rect, dst_rect, ray.Vector2(0, 0), 0, color)
class ResultScreen:
def __init__(self, width, height):
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.sound_don = audio.load_sound('Sounds\\inst_00_don.wav')
self.bgm = audio.load_sound('Sounds\\result\\JINGLE_SEISEKI [1].ogg')
zip_file = 'Graphics\\lumendata\\enso_result.zip'
self.textures = load_all_textures_from_zip(zip_file)
ray.unload_texture(self.textures['result'][327])
image = load_image_from_zip(zip_file, 'result_img00327.png')
ray.image_resize(image, 1280, 144)
self.textures['result'][327] = ray.load_texture_from_image(image)
self.text_generated = False
self.song_info = FontText(global_data.song_title, 40).texture
self.screen_init = False
self.fade_in = None
self.bgm_volume = 1.0
def on_screen_start(self):
if not self.screen_init:
self.textures = load_all_textures_from_zip('Graphics\\lumendata\\enso_result.zip')
self.screen_init = True
self.song_info = FontText(global_data.song_title, 40).texture
self.bgm_volume = 1.0
audio.play_sound(self.bgm)
self.fade_in = FadeIn(get_current_ms())
def on_screen_end(self):
self.screen_init = False
global_data.songs_played += 1
audio.play_sound(self.sound_don)
for zip in self.textures:
for texture in self.textures[zip]:
ray.unload_texture(texture)
audio.stop_sound(self.bgm)
return "SONG_SELECT"
def update(self):
self.on_screen_start()
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
global_data.songs_played += 1
audio.play_sound(self.sound_don)
return "SONG_SELECT"
return self.on_screen_end()
if not self.text_generated and global_data.song_title != '':
self.song_info = FontText(global_data.song_title, 40).texture
self.text_generated = True
if self.fade_in is not None:
self.fade_in.update(get_current_ms())
def draw_score_info(self):
for i in range(len(str(global_data.result_good))):
ray.draw_texture(self.textures['result'][int(str(global_data.result_good)[::-1][i]) + 136], 943-(i*24), 186, ray.WHITE)
for i in range(len(str(global_data.result_ok))):
ray.draw_texture(self.textures['result'][int(str(global_data.result_ok)[::-1][i]) + 136], 943-(i*24), 227, ray.WHITE)
for i in range(len(str(global_data.result_bad))):
ray.draw_texture(self.textures['result'][int(str(global_data.result_bad)[::-1][i]) + 136], 943-(i*24), 267, ray.WHITE)
def draw_total_score(self):
ray.draw_texture(self.textures['result'][167], 554, 236, ray.WHITE)
for i in range(len(str(global_data.result_score))):
ray.draw_texture(self.textures['result'][int(str(global_data.result_score)[::-1][i]) + 156], 723-(i*21), 252, ray.WHITE)
def draw(self):
x = 0
@@ -49,15 +80,11 @@ class ResultScreen:
ray.draw_texture(self.textures['result'][326], x, 0 - self.textures['result'][326].height//2, ray.WHITE)
ray.draw_texture(self.textures['result'][326], x, self.height - self.textures['result'][326].height//2, ray.WHITE)
x += self.textures['result'][326].width
ray.draw_texture(self.textures['result'][327], 0, 0 - self.textures['result'][327].height//2, ray.WHITE)
ray.draw_texture(self.textures['result'][327], 0, self.height - self.textures['result'][327].height + self.textures['result'][327].height//2, ray.WHITE)
ray.draw_text(f"{global_data.selected_song}", 100, 60, 20, ray.BLACK)
ray.draw_text(f"SCORE: {global_data.result_score}", 100, 80, 20, ray.BLACK)
ray.draw_text(f"GOOD: {global_data.result_good}", 100, 100, 20, ray.BLACK)
ray.draw_text(f"OK: {global_data.result_ok}", 100, 120, 20, ray.BLACK)
ray.draw_text(f"BAD: {global_data.result_bad}", 100, 140, 20, ray.BLACK)
x = 0
while x < self.width:
ray.draw_texture(self.textures['result'][327], x, 0 - self.textures['result'][327].height//2, ray.WHITE)
ray.draw_texture(self.textures['result'][327], x, self.height - self.textures['result'][327].height + self.textures['result'][327].height//2, ray.WHITE)
x += self.textures['result'][327].width
ray.draw_texture(self.textures['result'][330], -5, 3, ray.WHITE)
ray.draw_texture(self.textures['result'][(global_data.songs_played % 4) + 331], 232, 4, ray.WHITE)
@@ -71,6 +98,43 @@ class ResultScreen:
draw_scaled_texture(self.textures['result'][187], 1058, 124, (10/11), ray.WHITE)
draw_scaled_texture(self.textures['result'][188], 1182, 115, (10/11), ray.WHITE)
ray.draw_texture(self.textures['result'][170], 817, 186, ray.WHITE)
ray.draw_texture(self.textures['result'][171], 817, 227, ray.WHITE)
ray.draw_texture(self.textures['result'][172], 817, 267, ray.WHITE)
ray.draw_texture(self.textures['result'][173], 987, 186, ray.WHITE)
ray.draw_texture(self.textures['result'][174], 981, 227, ray.WHITE)
self.draw_score_info()
self.draw_total_score()
if self.fade_in is not None:
self.fade_in.draw(self.width, self.height, self.textures['result'][326], self.textures['result'][327])
class FadeIn:
def __init__(self, current_ms: float):
self.fadein = Animation(current_ms, 450, 'fade')
self.fadein.params['initial_opacity'] = 1.0
self.fadein.params['final_opacity'] = 0.0
self.fadein.params['delay'] = 100
self.fade = ray.fade(ray.WHITE, self.fadein.attribute)
def update(self, current_ms: float):
self.fadein.update(current_ms)
self.fade = ray.fade(ray.WHITE, self.fadein.attribute)
def draw(self, screen_width: int, screen_height: int, texture_1: ray.Texture, texture_2: ray.Texture):
x = 0
while x < screen_width:
ray.draw_texture(texture_1, x, 0 - texture_1.height//2, self.fade)
ray.draw_texture(texture_1, x, screen_height - texture_1.height//2, self.fade)
x += texture_1.width
x = 0
while x < screen_width:
ray.draw_texture(texture_2, x, 0 - texture_2.height//2, self.fade)
ray.draw_texture(texture_2, x, screen_height - texture_2.height + texture_2.height//2, self.fade)
x += texture_2.width
class FontText:
def __init__(self, text, font_size):
codepoint_count = ray.ffi.new('int *', 0)

View File

@@ -3,6 +3,7 @@ import os
import pyray as ray
from libs.audio import audio
from libs.tja import TJAParser
from libs.utils import get_config, global_data
@@ -12,7 +13,7 @@ class SongSelectScreen:
self.height = height
self.is_song_select = True
self.is_difficulty_select = False
self.song_list: list[str] = []
self.song_list: dict[str, list] = dict()
self.selected_song = 0
self.selected_difficulty = 0
self.selected_index = 0
@@ -21,7 +22,23 @@ class SongSelectScreen:
for dirpath, dirnames, filenames in os.walk(f'{get_config()["paths"]["tja_path"]}'):
for filename in filenames:
if filename.endswith(".tja"):
self.song_list.append(dirpath)
self.song_list[dirpath] = TJAParser(dirpath).get_metadata()
self.screen_init = False
def on_screen_start(self):
if not self.screen_init:
self.screen_init = True
self.is_song_select = True
self.is_difficulty_select = False
def on_screen_end(self):
self.screen_init = False
audio.play_sound(self.sound_don)
global_data.selected_song = list(self.song_list.keys())[self.selected_song]
global_data.selected_difficulty = self.selected_difficulty
return "GAME"
def update_song_select(self):
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
@@ -37,13 +54,7 @@ class SongSelectScreen:
def update_difficulty_select(self):
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
audio.play_sound(self.sound_don)
global_data.selected_song = self.song_list[self.selected_song]
global_data.selected_difficulty = self.selected_difficulty
global_data.start_song = True
self.is_song_select = True
self.is_difficulty_select = False
return "GAME"
return self.on_screen_end()
elif ray.is_key_pressed(ray.KeyboardKey.KEY_BACKSPACE):
self.is_song_select = True
self.is_difficulty_select = False
@@ -55,6 +66,7 @@ class SongSelectScreen:
self.selected_difficulty = (self.selected_difficulty + 1) % 5
def update(self):
self.on_screen_start()
if self.is_song_select:
self.update_song_select()
elif self.is_difficulty_select:
@@ -62,21 +74,23 @@ class SongSelectScreen:
def draw_song_select(self):
visible_songs = 36
total_songs = len(self.song_list)
song_paths = list(self.song_list.keys()) # Get all paths as a list
total_songs = len(song_paths)
start_index = max(0, self.selected_song - visible_songs // 2)
if start_index + visible_songs > total_songs:
start_index = max(0, total_songs - visible_songs)
for i in range(visible_songs):
song_index = (start_index + i) % total_songs
if start_index + i < total_songs: # Ensure we don't go out of bounds
song_index = start_index + i
current_path = song_paths[song_index]
# Get display text from metadata, or use the path as fallback
display_text = self.song_list[current_path][0]
if song_index == self.selected_song:
color = ray.GREEN
else:
color = ray.BLACK
ray.draw_text(self.song_list[song_index], 20, (20*i), 20, color)
if song_index == self.selected_song:
color = ray.GREEN
else:
color = ray.BLACK
ray.draw_text(display_text, 20, (20*i), 20, color)
def draw_difficulty_select(self):
difficulties = ["Easy", "Normal", "Hard", "Oni", "Ura"]

View File

@@ -28,6 +28,8 @@ class TitleScreen:
self.load_textures()
self.warning_board = WarningScreen(get_current_ms(), self)
self.screen_init = False
def get_videos(self):
return self.op_video, self.attract_video
@@ -41,6 +43,17 @@ class TitleScreen:
self.texture_black = load_texture_from_zip('Graphics\\lumendata\\attract\\movie.zip', 'movie_img00000.png')
def on_screen_start(self):
if not self.screen_init:
self.screen_init = True
def on_screen_end(self) -> str:
for zip in self.textures:
for texture in self.textures[zip]:
ray.unload_texture(texture)
self.screen_init = False
return "ENTRY"
def scene_manager(self):
if self.scene == 'Opening Video':
self.op_video.update()
@@ -59,9 +72,11 @@ class TitleScreen:
self.op_video = VideoPlayer(random.choice(self.op_video_list))
def update(self):
self.on_screen_start()
self.scene_manager()
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
return "ENTRY"
return self.on_screen_end()
def draw(self):
if self.scene == 'Opening Video':