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: How to run:
Windows: Windows:
```bash ```bash
PyTaiko.exe {"Song Name"} {difficulty number 0-4} PyTaiko.exe
``` ```
MacOS/Linux: MacOS/Linux:
```bash ```bash
PyTaiko.bin {"Song Name"} {difficulty number 0-4} PyTaiko.bin
```
## Roadmap ## Roadmap
- Add Kusudama notes https://linear.app/pytaiko
- add basic song select
- add basic results screen
## Known Issues ## Known Issues
@@ -64,7 +59,7 @@ Start the game
#### Keybinds? #### Keybinds?
EFJI see config.toml
#### Why does it look like Gen 3? #### Why does it look like Gen 3?
@@ -74,4 +69,3 @@ I like it
## Contributing ## 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 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'] right_kat = ['I','U']
[audio] [audio]
device_type = 'WASAPI' device_type = 'ASIO'
asio_buffer = 6 asio_buffer = 6
[video] [video]

View File

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

View File

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

View File

@@ -1,3 +1,8 @@
raylib==5.0.0.3 numpy==2.2.5
raylib_dynamic==5.0.0.3 opencv_python==4.11.0.86
opencv-python==4.10.0.84 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.texture_footer = load_texture_from_zip('Graphics\\lumendata\\entry.zip', 'entry_img00375.png')
def update(self): self.screen_init = False
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
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" return "SONG_SELECT"
def update(self):
self.on_screen_start()
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
return self.on_screen_end()
def draw(self): def draw(self):
ray.draw_texture(self.texture_footer, 0, self.height - 151, ray.WHITE) ray.draw_texture(self.texture_footer, 0, self.height - 151, ray.WHITE)

View File

@@ -24,7 +24,12 @@ class GameScreen:
self.height = height self.height = height
self.judge_x = 414 self.judge_x = 414
self.current_ms = 0 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): def load_textures(self):
self.textures = load_all_textures_from_zip('Graphics\\lumendata\\enso_system\\common.zip') 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\\base1p.zip'))
self.textures.update(load_all_textures_from_zip('Graphics\\lumendata\\enso_system\\don1p.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): def load_sounds(self):
self.sound_don = audio.load_sound('Sounds\\inst_00_don.wav') self.sound_don = audio.load_sound('Sounds\\inst_00_don.wav')
self.sound_kat = audio.load_sound('Sounds\\inst_00_katsu.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_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): def init_tja(self, song: str, difficulty: int):
self.load_textures() self.load_textures()
self.load_sounds() self.load_sounds()
@@ -165,24 +175,46 @@ class GameScreen:
audio.play_sound(self.song_music) 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): def update(self):
if global_data.start_song and not self.song_is_started: self.on_screen_start()
self.init_tja(global_data.selected_song, global_data.selected_difficulty)
self.song_is_started = True
self.current_ms = get_current_ms() - self.start_ms 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.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.result_transition = ResultTransition(self.current_ms, self.height)
self.song_is_started = False audio.play_sound(self.sound_result_transition)
return 'RESULT'
def draw(self): def draw(self):
self.player_1.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: class Player:
def __init__(self, game_screen: GameScreen, player_number: int, difficulty: int, judge_offset: int): def __init__(self, game_screen: GameScreen, player_number: int, difficulty: int, judge_offset: int):
@@ -218,6 +250,7 @@ class Player:
self.combo = 0 self.combo = 0
self.score = 0 self.score = 0
self.max_combo = 0 self.max_combo = 0
self.total_drumroll = 0
self.arc_points = 25 self.arc_points = 25
@@ -225,12 +258,11 @@ class Player:
self.draw_effect_list: list[LaneHitEffect] = [] self.draw_effect_list: list[LaneHitEffect] = []
self.draw_arc_list: list[NoteArc] = [] self.draw_arc_list: list[NoteArc] = []
self.draw_drum_hit_list: list[DrumHitEffect] = [] self.draw_drum_hit_list: list[DrumHitEffect] = []
self.drumroll_counter: list[DrumrollCounter] = [] self.drumroll_counter: DrumrollCounter | None = None
self.balloon_list: list[BalloonAnimation] = [] self.balloon_list: list[BalloonAnimation] = []
self.base_score_list: list[ScoreCounterAnimation] = [] self.base_score_list: list[ScoreCounterAnimation] = []
self.combo_display = Combo(self.combo, game_screen.current_ms) self.combo_display = Combo(self.combo, game_screen.current_ms)
self.score_counter = ScoreCounter(self.score, 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() self.input_log: dict[float, str] = dict()
@@ -304,7 +336,6 @@ class Player:
else: else:
bisect.insort_left(self.current_notes_draw, self.draw_note_list.popleft(), key=lambda x: x['index']) 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: if len(self.current_notes_draw) == 0:
return return
@@ -316,8 +347,6 @@ class Player:
note = self.current_notes_draw[1] note = self.current_notes_draw[1]
position = self.get_position(game_screen, note['ms'], note['ppf']) position = self.get_position(game_screen, note['ms'], note['ppf'])
if position < game_screen.judge_x + 650: if position < game_screen.judge_x + 650:
if note['note'] == '8':
self.current_notes_draw.pop(0)
self.current_notes_draw.pop(0) self.current_notes_draw.pop(0)
def note_manager(self, game_screen: GameScreen): 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)] 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.draw_arc_list.append(NoteArc(note_type, game_screen.current_ms, self.player_number))
self.curr_drumroll_count += 1 self.curr_drumroll_count += 1
self.total_drumroll += 1
self.score += 100 self.score += 100
self.base_score_list.append(ScoreCounterAnimation(game_screen.current_ms, 100)) self.base_score_list.append(ScoreCounterAnimation(game_screen.current_ms, 100))
color = max(0, 255 - (self.curr_drumroll_count * 10)) color = max(0, 255 - (self.curr_drumroll_count * 10))
@@ -359,6 +389,7 @@ class Player:
if len(self.balloon_list) < 1: if len(self.balloon_list) < 1:
self.balloon_list.append(BalloonAnimation(game_screen.current_ms, current_note['balloon'])) self.balloon_list.append(BalloonAnimation(game_screen.current_ms, current_note['balloon']))
self.curr_balloon_count += 1 self.curr_balloon_count += 1
self.total_drumroll += 1
self.score += 100 self.score += 100
self.base_score_list.append(ScoreCounterAnimation(game_screen.current_ms, 100)) self.base_score_list.append(ScoreCounterAnimation(game_screen.current_ms, 100))
self.current_notes_draw[0]['popped'] = False self.current_notes_draw[0]['popped'] = False
@@ -367,8 +398,6 @@ class Player:
self.current_notes_draw[0]['popped'] = True self.current_notes_draw[0]['popped'] = True
audio.play_sound(game_screen.sound_balloon_pop) audio.play_sound(game_screen.sound_balloon_pop)
self.note_correct(game_screen, self.current_notes[0]) 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): def check_note(self, game_screen: GameScreen, drum_type: str):
if self.is_drumroll: if self.is_drumroll:
@@ -421,17 +450,14 @@ class Player:
self.current_notes.popleft() self.current_notes.popleft()
def drumroll_counter_manager(self, game_screen: GameScreen): def drumroll_counter_manager(self, game_screen: GameScreen):
if self.is_drumroll and self.curr_drumroll_count > 0: if self.is_drumroll and self.curr_drumroll_count > 0 and self.drumroll_counter is None:
if len(self.drumroll_counter) == 0 or self.drumroll_counter[-1].is_finished: self.drumroll_counter = DrumrollCounter(game_screen.current_ms)
self.drumroll_counter.append(DrumrollCounter(game_screen.current_ms))
if len(self.drumroll_counter) <= 0: if self.drumroll_counter is not None:
return if self.drumroll_counter.is_finished and not self.is_drumroll:
self.drumroll_counter = None
if self.drumroll_counter[0].is_finished and not self.is_drumroll:
self.drumroll_counter.pop(0)
else: else:
self.drumroll_counter[0].update(game_screen, game_screen.current_ms, self.curr_drumroll_count) self.drumroll_counter.update(game_screen, game_screen.current_ms, self.curr_drumroll_count)
def balloon_animation_manager(self, game_screen: GameScreen): def balloon_animation_manager(self, game_screen: GameScreen):
if len(self.balloon_list) <= 0: 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_drum_hit_list)
self.animation_manager(game_screen, self.draw_arc_list) self.animation_manager(game_screen, self.draw_arc_list)
self.animation_manager(game_screen, self.base_score_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.score_counter.update(game_screen.current_ms, self.score)
self.key_manager(game_screen) 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): def draw_drumroll(self, game_screen: GameScreen, big: bool, position: int, index: int, color: int):
drumroll_start_position = position drumroll_start_position = position
i = 1 tail = next((note for note in self.current_notes_draw[index+1:] if note['note'] == '8'), None)
while self.current_notes_draw[index+i]['note'] != '8': if tail is None:
i += 1 return
tail = self.current_notes_draw[index+i]
if big: if big:
drumroll_body = 'dai_drumroll_body' drumroll_body = 'dai_drumroll_body'
drumroll_tail = 'dai_drumroll_tail' 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) 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): 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 return
i = 1 tail_position = self.get_position(game_screen, tail['load_ms'], tail['ppf'])
while self.current_notes_draw[index+i]['note'] != '8': if game_screen.current_ms >= tail['ms']:
i += 1 position = tail_position
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
elif game_screen.current_ms >= note['ms']: elif game_screen.current_ms >= note['ms']:
position = 349 position = 349
if note.get('popped', None):
return
self.draw_note(game_screen, '7', position, 255, 9) 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): 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 = self.current_notes_draw[i]
note_type, load_ms, pixels_per_frame = note['note'], note['load_ms'], note['ppf'] 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) position = self.get_position(game_screen, load_ms, pixels_per_frame)
if 'popped' in note:
continue
if note_type == '5': if note_type == '5':
self.draw_drumroll(game_screen, False, position, i, note['color']) self.draw_drumroll(game_screen, False, position, i, note['color'])
elif note_type == '6': elif note_type == '6':
@@ -597,7 +618,7 @@ class Player:
self.draw_balloon(game_screen, note, position, i) self.draw_balloon(game_screen, note, position, i)
else: else:
self.draw_note(game_screen, note_type, position, 255, note['se_note']) 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): def draw_gauge(self, game_screen: GameScreen):
ray.draw_texture(game_screen.textures['gage_don_1p_hard'][0], 327, 128, ray.WHITE) 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'][3], 0, 184, ray.WHITE)
ray.draw_texture(game_screen.textures['lane_obi'][19], 0, 225, 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) ray.draw_texture(game_screen.texture_difficulty[self.difficulty], 50, 222, ray.WHITE)
self.song_info.draw(game_screen) if self.drumroll_counter is not None:
self.draw_animation_list(game_screen, self.drumroll_counter) self.drumroll_counter.draw(game_screen)
self.draw_animation_list(game_screen, self.draw_arc_list) self.draw_animation_list(game_screen, self.draw_arc_list)
self.draw_animation_list(game_screen, self.balloon_list) self.draw_animation_list(game_screen, self.balloon_list)
self.score_counter.draw(game_screen) self.score_counter.draw(game_screen)
@@ -787,6 +808,7 @@ class DrumrollCounter:
def update_count(self, current_ms: float, count: int, elapsed_time: float): def update_count(self, current_ms: float, count: int, elapsed_time: float):
self.total_duration = elapsed_time + 1349 self.total_duration = elapsed_time + 1349
self.fade_animation.params['delay'] = self.total_duration - 166
if self.drumroll_count != count: if self.drumroll_count != count:
self.drumroll_count = count self.drumroll_count = count
self.stretch_animation = Animation(current_ms, 50, 'text_stretch') 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) 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: class SongInfo:
FADE_DURATION = 366
DISPLAY_DURATION = 1666
def __init__(self, current_ms: float, song_name: str, genre: str): def __init__(self, current_ms: float, song_name: str, genre: str):
self.fade_in = Animation(current_ms, 366, 'fade') self.fade_in = self._create_fade_in_animation(current_ms)
self.fade_in.params['initial_opacity'] = 0.0 self.fade_out = self._create_fade_out_animation(current_ms)
self.fade_in.params['final_opacity'] = 1.0
self.fade_out = Animation(current_ms, 366, 'fade') self.song_name = song_name
self.fade_out.params['initial_opacity'] = 1.0 self.genre = genre
self.fade_out.params['final_opacity'] = 0.0
self.fade_out.params['delay'] = 1666 + self.fade_in.duration
self.is_finished = False self.font = self._load_font_for_text(song_name)
self.song_num_fade = ray.WHITE self.song_title = OutlinedText(
self.song_name_fade = ray.WHITE 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) codepoint_count = ray.ffi.new('int *', 0)
codepoints_no_dup = set() unique_codepoints = set(text)
codepoints_no_dup.update(song_name) codepoints = ray.load_codepoints(''.join(unique_codepoints), codepoint_count)
codepoints = ray.load_codepoints(''.join(codepoints_no_dup), codepoint_count) return ray.load_font_ex('Graphics\\Modified-DFPKanteiryu-XB.ttf', 32, codepoints, 0)
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)
def update(self, current_ms: float): def update(self, current_ms: float):
self.fade_in.update(current_ms) self.fade_in.update(current_ms)
self.fade_out.update(current_ms) self.fade_out.update(current_ms)
if not self.fade_in.is_finished:
self.song_num_fade = ray.fade(ray.WHITE, self.fade_in.attribute) 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) self.song_name_fade = ray.fade(ray.WHITE, 1 - self.fade_in.attribute)
if self.fade_in.is_finished: else:
self.song_num_fade = ray.fade(ray.WHITE, self.fade_out.attribute) 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) self.song_name_fade = ray.fade(ray.WHITE, 1 - self.fade_out.attribute)
if self.fade_out.is_finished: if self.fade_out.is_finished:
self.fade_in.start_ms = current_ms + 1666 self._reset_animations(current_ms)
self.fade_out.start_ms = current_ms + 1666
self.fade_in.is_finished = False def _reset_animations(self, current_ms: float):
self.fade_out.is_finished = False 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): 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) song_texture_index = (global_data.songs_played % 4) + 8
self.song_title.draw(1252 - self.song_title.texture.width, int(50 - self.song_title.texture.height / 2), self.song_name_fade) 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 import pyray as ray
from libs.animation import Animation
from libs.audio import audio from libs.audio import audio
from libs.utils import ( from libs.utils import (
OutlinedText, OutlinedText,
get_current_ms,
global_data, global_data,
load_all_textures_from_zip, 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) ray.draw_texture_pro(texture, src_rect, dst_rect, ray.Vector2(0, 0), 0, color)
class ResultScreen: class ResultScreen:
def __init__(self, width, height): def __init__(self, width: int, height: int):
self.width = width self.width = width
self.height = height self.height = height
self.sound_don = audio.load_sound('Sounds\\inst_00_don.wav') 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' zip_file = 'Graphics\\lumendata\\enso_result.zip'
self.textures = load_all_textures_from_zip(zip_file) 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.song_info = FontText(global_data.song_title, 40).texture
self.screen_init = False
def update(self): self.fade_in = None
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
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 global_data.songs_played += 1
audio.play_sound(self.sound_don) 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" return "SONG_SELECT"
if not self.text_generated and global_data.song_title != '': def update(self):
self.song_info = FontText(global_data.song_title, 40).texture self.on_screen_start()
self.text_generated = True if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
return self.on_screen_end()
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): def draw(self):
x = 0 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, 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) ray.draw_texture(self.textures['result'][326], x, self.height - self.textures['result'][326].height//2, ray.WHITE)
x += self.textures['result'][326].width x += self.textures['result'][326].width
ray.draw_texture(self.textures['result'][327], 0, 0 - self.textures['result'][327].height//2, ray.WHITE) x = 0
ray.draw_texture(self.textures['result'][327], 0, self.height - self.textures['result'][327].height + self.textures['result'][327].height//2, ray.WHITE) 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)
ray.draw_text(f"{global_data.selected_song}", 100, 60, 20, ray.BLACK) x += self.textures['result'][327].width
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)
ray.draw_texture(self.textures['result'][330], -5, 3, ray.WHITE) 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) 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'][187], 1058, 124, (10/11), ray.WHITE)
draw_scaled_texture(self.textures['result'][188], 1182, 115, (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: class FontText:
def __init__(self, text, font_size): def __init__(self, text, font_size):
codepoint_count = ray.ffi.new('int *', 0) codepoint_count = ray.ffi.new('int *', 0)

View File

@@ -3,6 +3,7 @@ import os
import pyray as ray import pyray as ray
from libs.audio import audio from libs.audio import audio
from libs.tja import TJAParser
from libs.utils import get_config, global_data from libs.utils import get_config, global_data
@@ -12,7 +13,7 @@ class SongSelectScreen:
self.height = height self.height = height
self.is_song_select = True self.is_song_select = True
self.is_difficulty_select = False self.is_difficulty_select = False
self.song_list: list[str] = [] self.song_list: dict[str, list] = dict()
self.selected_song = 0 self.selected_song = 0
self.selected_difficulty = 0 self.selected_difficulty = 0
self.selected_index = 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 dirpath, dirnames, filenames in os.walk(f'{get_config()["paths"]["tja_path"]}'):
for filename in filenames: for filename in filenames:
if filename.endswith(".tja"): 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): def update_song_select(self):
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER): if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
@@ -37,13 +54,7 @@ class SongSelectScreen:
def update_difficulty_select(self): def update_difficulty_select(self):
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER): if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
audio.play_sound(self.sound_don) return self.on_screen_end()
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"
elif ray.is_key_pressed(ray.KeyboardKey.KEY_BACKSPACE): elif ray.is_key_pressed(ray.KeyboardKey.KEY_BACKSPACE):
self.is_song_select = True self.is_song_select = True
self.is_difficulty_select = False self.is_difficulty_select = False
@@ -55,6 +66,7 @@ class SongSelectScreen:
self.selected_difficulty = (self.selected_difficulty + 1) % 5 self.selected_difficulty = (self.selected_difficulty + 1) % 5
def update(self): def update(self):
self.on_screen_start()
if self.is_song_select: if self.is_song_select:
self.update_song_select() self.update_song_select()
elif self.is_difficulty_select: elif self.is_difficulty_select:
@@ -62,21 +74,23 @@ class SongSelectScreen:
def draw_song_select(self): def draw_song_select(self):
visible_songs = 36 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) start_index = max(0, self.selected_song - visible_songs // 2)
if start_index + visible_songs > total_songs: if start_index + visible_songs > total_songs:
start_index = max(0, total_songs - visible_songs) start_index = max(0, total_songs - visible_songs)
for i in range(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: if song_index == self.selected_song:
color = ray.GREEN color = ray.GREEN
else: else:
color = ray.BLACK color = ray.BLACK
ray.draw_text(display_text, 20, (20*i), 20, color)
ray.draw_text(self.song_list[song_index], 20, (20*i), 20, color)
def draw_difficulty_select(self): def draw_difficulty_select(self):
difficulties = ["Easy", "Normal", "Hard", "Oni", "Ura"] difficulties = ["Easy", "Normal", "Hard", "Oni", "Ura"]

View File

@@ -28,6 +28,8 @@ class TitleScreen:
self.load_textures() self.load_textures()
self.warning_board = WarningScreen(get_current_ms(), self) self.warning_board = WarningScreen(get_current_ms(), self)
self.screen_init = False
def get_videos(self): def get_videos(self):
return self.op_video, self.attract_video 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') 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): def scene_manager(self):
if self.scene == 'Opening Video': if self.scene == 'Opening Video':
self.op_video.update() self.op_video.update()
@@ -59,9 +72,11 @@ class TitleScreen:
self.op_video = VideoPlayer(random.choice(self.op_video_list)) self.op_video = VideoPlayer(random.choice(self.op_video_list))
def update(self): def update(self):
self.on_screen_start()
self.scene_manager() self.scene_manager()
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER): if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
return "ENTRY" return self.on_screen_end()
def draw(self): def draw(self):
if self.scene == 'Opening Video': if self.scene == 'Opening Video':