mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
add difficulty sorting menu
This commit is contained in:
@@ -828,7 +828,7 @@ class AudioEngine:
|
||||
|
||||
def set_sound_volume(self, sound: str, volume: float) -> None:
|
||||
if sound in self.sounds:
|
||||
self.sounds[sound].volume = max(0.0, min(1.0, volume))
|
||||
self.sounds[sound].volume = max(0.0, volume)
|
||||
|
||||
def set_sound_pan(self, sound: str, pan: float) -> None:
|
||||
if sound in self.sounds:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import csv
|
||||
import json
|
||||
import sqlite3
|
||||
import sys
|
||||
import time
|
||||
from collections import deque
|
||||
@@ -28,7 +29,6 @@ def build_song_hashes(output_dir=Path("cache")):
|
||||
output_dir.mkdir()
|
||||
song_hashes: dict[str, list[dict]] = dict()
|
||||
path_to_hash: dict[str, str] = dict() # New index for O(1) path lookups
|
||||
|
||||
output_path = Path(output_dir / "song_hashes.json")
|
||||
index_path = Path(output_dir / "path_to_hash.json")
|
||||
|
||||
@@ -36,7 +36,6 @@ def build_song_hashes(output_dir=Path("cache")):
|
||||
if output_path.exists():
|
||||
with open(output_path, "r", encoding="utf-8") as f:
|
||||
song_hashes = json.load(f, cls=DiffHashesDecoder)
|
||||
|
||||
if index_path.exists():
|
||||
with open(index_path, "r", encoding="utf-8") as f:
|
||||
path_to_hash = json.load(f)
|
||||
@@ -59,21 +58,17 @@ def build_song_hashes(output_dir=Path("cache")):
|
||||
all_tja_files.extend(root_path.rglob("*.tja"))
|
||||
|
||||
global_data.total_songs = len(all_tja_files)
|
||||
|
||||
files_to_process = []
|
||||
|
||||
for tja_path in all_tja_files:
|
||||
tja_path_str = str(tja_path)
|
||||
current_modified = tja_path.stat().st_mtime
|
||||
|
||||
if current_modified <= saved_timestamp:
|
||||
current_hash = path_to_hash.get(tja_path_str)
|
||||
if current_hash is not None:
|
||||
global_data.song_paths[tja_path] = current_hash
|
||||
continue
|
||||
|
||||
current_hash = path_to_hash.get(tja_path_str)
|
||||
|
||||
if current_hash is None:
|
||||
files_to_process.append(tja_path)
|
||||
else:
|
||||
@@ -82,15 +77,19 @@ def build_song_hashes(output_dir=Path("cache")):
|
||||
del song_hashes[current_hash]
|
||||
del path_to_hash[tja_path_str]
|
||||
|
||||
# Prepare database connection for updates
|
||||
db_path = Path("scores.db")
|
||||
db_updates = [] # Store updates to batch process later
|
||||
|
||||
# Process only files that need updating
|
||||
song_count = 0
|
||||
total_songs = len(files_to_process)
|
||||
if total_songs > 0:
|
||||
global_data.total_songs = total_songs
|
||||
|
||||
for tja_path in files_to_process:
|
||||
tja_path_str = str(tja_path)
|
||||
current_modified = tja_path.stat().st_mtime
|
||||
|
||||
tja = TJAParser(tja_path)
|
||||
all_notes = deque()
|
||||
all_bars = deque()
|
||||
@@ -106,7 +105,6 @@ def build_song_hashes(output_dir=Path("cache")):
|
||||
continue
|
||||
|
||||
hash_val = tja.hash_note_data(all_notes, all_bars)
|
||||
|
||||
if hash_val not in song_hashes:
|
||||
song_hashes[hash_val] = []
|
||||
|
||||
@@ -121,16 +119,48 @@ def build_song_hashes(output_dir=Path("cache")):
|
||||
# Update both indexes
|
||||
path_to_hash[tja_path_str] = hash_val
|
||||
global_data.song_paths[tja_path] = hash_val
|
||||
|
||||
# Prepare database updates for each difficulty
|
||||
en_name = tja.metadata.title.get('en', '') if isinstance(tja.metadata.title, dict) else str(tja.metadata.title)
|
||||
jp_name = tja.metadata.title.get('jp', '') if isinstance(tja.metadata.title, dict) else ''
|
||||
|
||||
for diff, diff_hash in diff_hashes.items():
|
||||
db_updates.append((diff_hash, en_name, jp_name, diff))
|
||||
|
||||
song_count += 1
|
||||
global_data.song_progress = song_count / total_songs
|
||||
|
||||
# Update database with new difficulty hashes
|
||||
if db_updates and db_path.exists():
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
for diff_hash, en_name, jp_name, diff in db_updates:
|
||||
# Update existing entries that match by name and difficulty
|
||||
cursor.execute("""
|
||||
UPDATE scores
|
||||
SET hash = ?
|
||||
WHERE (en_name = ? AND jp_name = ?) AND diff = ?
|
||||
""", (diff_hash, en_name, jp_name, diff))
|
||||
if cursor.rowcount > 0:
|
||||
print(f"Updated {cursor.rowcount} entries for {en_name} ({diff})")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(f"Database update completed. Processed {len(db_updates)} difficulty hash updates.")
|
||||
|
||||
except sqlite3.Error as e:
|
||||
print(f"Database error: {e}")
|
||||
except Exception as e:
|
||||
print(f"Error updating database: {e}")
|
||||
elif db_updates:
|
||||
print(f"Warning: scores.db not found, skipping {len(db_updates)} database updates")
|
||||
|
||||
# Save both files
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(song_hashes, f, indent=2, ensure_ascii=False)
|
||||
|
||||
with open(index_path, "w", encoding="utf-8") as f:
|
||||
json.dump(path_to_hash, f, indent=2, ensure_ascii=False)
|
||||
|
||||
with open(output_dir / 'timestamp.txt', 'w') as f:
|
||||
f.write(str(current_timestamp))
|
||||
|
||||
|
||||
10
libs/tja.py
10
libs/tja.py
@@ -40,11 +40,13 @@ class Note:
|
||||
return self.hit_ms == other.hit_ms
|
||||
|
||||
def _get_hash_data(self) -> bytes:
|
||||
"""Get deterministic byte representation for hashing"""
|
||||
hash_fields = ['type', 'hit_ms', 'load_ms']
|
||||
field_values = []
|
||||
for f in sorted([f.name for f in fields(self)]):
|
||||
value = getattr(self, f, None)
|
||||
field_values.append((f, value))
|
||||
|
||||
for field_name in sorted(hash_fields):
|
||||
value = getattr(self, field_name, None)
|
||||
field_values.append((field_name, value))
|
||||
|
||||
field_values.append(('__class__', self.__class__.__name__))
|
||||
hash_string = str(field_values)
|
||||
return hash_string.encode('utf-8')
|
||||
|
||||
@@ -240,6 +240,7 @@ for file in Path('cache/image').iterdir():
|
||||
|
||||
class OutlinedText:
|
||||
def __init__(self, text: str, font_size: int, color: ray.Color, outline_color: ray.Color, outline_thickness=5.0, vertical=False):
|
||||
self.text = text
|
||||
self.hash = self._hash_text(text, font_size, color, vertical)
|
||||
if self.hash in text_cache:
|
||||
self.texture = ray.load_texture(f'cache/image/{self.hash}.png')
|
||||
|
||||
@@ -60,8 +60,8 @@ class GameScreen:
|
||||
session_data.song_title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
|
||||
if not hasattr(self, 'song_music'):
|
||||
if self.tja.metadata.wave.exists() and self.tja.metadata.wave.is_file():
|
||||
self.song_music = audio.load_sound(self.tja.metadata.wave)
|
||||
audio.normalize_sound(self.song_music, 0.1935)
|
||||
self.song_music = audio.load_music_stream(self.tja.metadata.wave)
|
||||
audio.normalize_music_stream(self.song_music, 0.1935)
|
||||
else:
|
||||
self.song_music = None
|
||||
self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000)
|
||||
@@ -88,7 +88,7 @@ class GameScreen:
|
||||
self.screen_init = False
|
||||
tex.unload_textures()
|
||||
if self.song_music is not None:
|
||||
audio.unload_sound(self.song_music)
|
||||
audio.unload_music_stream(self.song_music)
|
||||
del self.song_music
|
||||
self.song_started = False
|
||||
self.end_ms = 0
|
||||
@@ -132,8 +132,8 @@ class GameScreen:
|
||||
if self.tja is not None:
|
||||
if (self.current_ms >= self.tja.metadata.offset*1000 + self.start_delay - global_data.config["general"]["judge_offset"]) and not self.song_started:
|
||||
if self.song_music is not None:
|
||||
if not audio.is_sound_playing(self.song_music):
|
||||
audio.play_sound(self.song_music)
|
||||
if not audio.is_music_stream_playing(self.song_music):
|
||||
audio.play_music_stream(self.song_music)
|
||||
print(f"Song started at {self.current_ms}")
|
||||
if self.movie is not None:
|
||||
self.movie.start(get_current_ms())
|
||||
@@ -146,6 +146,9 @@ class GameScreen:
|
||||
self.player_1.update(self)
|
||||
self.song_info.update(get_current_ms())
|
||||
|
||||
if self.song_music is not None:
|
||||
audio.update_music_stream(self.song_music)
|
||||
|
||||
self.result_transition.update(get_current_ms())
|
||||
if self.result_transition.is_finished:
|
||||
return self.on_screen_end('RESULT')
|
||||
@@ -162,13 +165,15 @@ class GameScreen:
|
||||
self.end_ms = get_current_ms()
|
||||
|
||||
if ray.is_key_pressed(ray.KeyboardKey.KEY_F1):
|
||||
audio.stop_sound(self.song_music)
|
||||
if self.song_music is not None:
|
||||
audio.stop_music_stream(self.song_music)
|
||||
self.init_tja(global_data.selected_song, session_data.selected_difficulty)
|
||||
audio.play_sound(self.sound_restart)
|
||||
self.song_started = False
|
||||
|
||||
if ray.is_key_pressed(ray.KeyboardKey.KEY_ESCAPE):
|
||||
audio.stop_sound(self.song_music)
|
||||
if self.song_music is not None:
|
||||
audio.stop_music_stream(self.song_music)
|
||||
return self.on_screen_end('SONG_SELECT')
|
||||
|
||||
def draw(self):
|
||||
@@ -613,7 +618,7 @@ class Player:
|
||||
self.draw_balloon(game_screen, note, current_eighth)
|
||||
tex.draw_texture('notes', 'moji', frame=note.moji, x=x_position - (168//2) + 64, y=323 + y_position)
|
||||
else:
|
||||
tex.draw_texture('notes', str(note.type), frame=current_eighth % 2, x=x_position, y=y_position+192)
|
||||
tex.draw_texture('notes', str(note.type), frame=current_eighth % 2, x=x_position, y=y_position+192, center=True)
|
||||
tex.draw_texture('notes', 'moji', frame=note.moji, x=x_position - (168//2) + 64, y=323 + y_position)
|
||||
|
||||
def draw(self, game_screen: GameScreen):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import random
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
@@ -25,6 +26,7 @@ from libs.utils import (
|
||||
class State:
|
||||
BROWSING = 0
|
||||
SONG_SELECTED = 1
|
||||
DIFF_SORTING = 2
|
||||
|
||||
class SongSelectScreen:
|
||||
BOX_CENTER = 444
|
||||
@@ -43,37 +45,43 @@ class SongSelectScreen:
|
||||
self.sound_kat = audio.load_sound(sounds_dir / "inst_00_katsu.wav")
|
||||
self.sound_skip = audio.load_sound(sounds_dir / 'song_select' / 'Skip.ogg')
|
||||
self.sound_ura_switch = audio.load_sound(sounds_dir / 'song_select' / 'SE_SELECT [4].ogg')
|
||||
self.sound_add_favorite = audio.load_sound(sounds_dir / 'song_select' / 'add_favorite.ogg')
|
||||
audio.set_sound_volume(self.sound_ura_switch, 0.25)
|
||||
audio.set_sound_volume(self.sound_add_favorite, 3.0)
|
||||
self.sound_bgm = audio.load_sound(sounds_dir / "song_select" / "JINGLE_GENRE [1].ogg")
|
||||
|
||||
def on_screen_start(self):
|
||||
if not self.screen_init:
|
||||
tex.load_screen_textures('song_select')
|
||||
self.load_sounds()
|
||||
self.selected_song = None
|
||||
self.selected_difficulty = -1
|
||||
self.background_move = tex.get_animation(0)
|
||||
self.background_move.start()
|
||||
self.move_away = tex.get_animation(1)
|
||||
self.diff_fade_out = tex.get_animation(2)
|
||||
self.state = State.BROWSING
|
||||
self.text_fade_out = tex.get_animation(3)
|
||||
self.text_fade_in = tex.get_animation(4)
|
||||
self.background_fade_change = tex.get_animation(5)
|
||||
self.background_move.start()
|
||||
self.state = State.BROWSING
|
||||
self.selected_difficulty = -1
|
||||
self.selected_song = None
|
||||
self.game_transition = None
|
||||
self.demo_song = None
|
||||
self.diff_sort_selector = None
|
||||
self.texture_index = 9
|
||||
self.last_texture_index = 9
|
||||
self.demo_song = None
|
||||
self.navigator.reset_items()
|
||||
self.navigator.get_current_item().box.get_scores()
|
||||
self.screen_init = True
|
||||
self.last_moved = get_current_ms()
|
||||
self.ura_toggle = 0
|
||||
self.ura_switch_animation = UraSwitchAnimation()
|
||||
self.is_ura = False
|
||||
self.screen_init = True
|
||||
self.ura_switch_animation = UraSwitchAnimation()
|
||||
|
||||
if str(global_data.selected_song) in self.navigator.all_song_files:
|
||||
self.navigator.mark_crowns_dirty_for_song(self.navigator.all_song_files[str(global_data.selected_song)])
|
||||
|
||||
self.navigator.reset_items()
|
||||
self.navigator.get_current_item().box.get_scores()
|
||||
self.navigator.add_recent()
|
||||
|
||||
def on_screen_end(self, next_screen):
|
||||
self.screen_init = False
|
||||
global_data.selected_song = self.navigator.get_current_item().path
|
||||
@@ -124,6 +132,9 @@ class SongSelectScreen:
|
||||
if selected_item is not None and selected_item.box.is_back:
|
||||
self.navigator.go_back()
|
||||
#audio.play_sound(self.sound_cancel)
|
||||
elif isinstance(selected_item, Directory) and selected_item.collection == Directory.COLLECTIONS[3]:
|
||||
self.state = State.DIFF_SORTING
|
||||
self.diff_sort_selector = DiffSortSelect()
|
||||
else:
|
||||
selected_song = self.navigator.select_current_item()
|
||||
if selected_song:
|
||||
@@ -136,6 +147,11 @@ class SongSelectScreen:
|
||||
self.text_fade_out.start()
|
||||
self.text_fade_in.start()
|
||||
|
||||
if ray.is_key_pressed(ray.KeyboardKey.KEY_SPACE):
|
||||
success = self.navigator.add_favorite()
|
||||
if success:
|
||||
audio.play_sound(self.sound_add_favorite)
|
||||
|
||||
def handle_input_selected(self):
|
||||
# Handle song selection confirmation or cancel
|
||||
if is_l_don_pressed() or is_r_don_pressed():
|
||||
@@ -164,6 +180,34 @@ class SongSelectScreen:
|
||||
self.selected_difficulty in [3, 4]):
|
||||
self._toggle_ura_mode()
|
||||
|
||||
def handle_input_diff_sort(self):
|
||||
if self.diff_sort_selector is None:
|
||||
raise Exception("Diff sort selector was not able to be created")
|
||||
if is_l_kat_pressed():
|
||||
self.diff_sort_selector.input_left()
|
||||
audio.play_sound(self.sound_kat)
|
||||
if is_r_kat_pressed():
|
||||
self.diff_sort_selector.input_right()
|
||||
audio.play_sound(self.sound_kat)
|
||||
if is_l_don_pressed() or is_r_don_pressed():
|
||||
diff, level = self.diff_sort_selector.input_select()
|
||||
if diff == -2:
|
||||
audio.play_sound(self.sound_don)
|
||||
return
|
||||
elif diff == -1:
|
||||
self.diff_sort_selector = None
|
||||
self.state = State.BROWSING
|
||||
if level == 0:
|
||||
audio.play_sound(self.sound_don)
|
||||
self.navigator.select_current_item()
|
||||
else:
|
||||
self.diff_sort_selector = None
|
||||
self.state = State.BROWSING
|
||||
audio.play_sound(self.sound_don)
|
||||
self.navigator.diff_sort_diff = diff
|
||||
self.navigator.diff_sort_level = level
|
||||
self.navigator.select_current_item()
|
||||
|
||||
def _cancel_selection(self):
|
||||
"""Reset to browsing state"""
|
||||
self.selected_song = None
|
||||
@@ -229,6 +273,8 @@ class SongSelectScreen:
|
||||
self.handle_input_browsing()
|
||||
elif self.state == State.SONG_SELECTED:
|
||||
self.handle_input_selected()
|
||||
elif self.state == State.DIFF_SORTING:
|
||||
self.handle_input_diff_sort()
|
||||
|
||||
def update(self):
|
||||
self.on_screen_start()
|
||||
@@ -266,6 +312,9 @@ class SongSelectScreen:
|
||||
if self.navigator.genre_bg is not None:
|
||||
self.navigator.genre_bg.update(get_current_ms())
|
||||
|
||||
if self.diff_sort_selector is not None:
|
||||
self.diff_sort_selector.update(get_current_ms())
|
||||
|
||||
for song in self.navigator.items:
|
||||
song.box.update(self.state == State.SONG_SELECTED)
|
||||
song.box.is_open = song.box.position == SongSelectScreen.BOX_CENTER + 150
|
||||
@@ -311,13 +360,19 @@ class SongSelectScreen:
|
||||
|
||||
self.ura_switch_animation.draw()
|
||||
|
||||
if self.selected_song and self.state == State.SONG_SELECTED:
|
||||
tex.draw_texture('global', 'footer')
|
||||
|
||||
if self.diff_sort_selector is not None:
|
||||
self.diff_sort_selector.draw()
|
||||
|
||||
if (self.selected_song and self.state == State.SONG_SELECTED):
|
||||
self.draw_selector()
|
||||
tex.draw_texture('global', 'difficulty_select', fade=self.text_fade_in.attribute)
|
||||
elif self.state == State.DIFF_SORTING:
|
||||
tex.draw_texture('global', 'difficulty_select', fade=self.text_fade_in.attribute)
|
||||
else:
|
||||
tex.draw_texture('global', 'song_select', fade=self.text_fade_out.attribute)
|
||||
|
||||
tex.draw_texture('global', 'footer')
|
||||
|
||||
if self.game_transition is not None:
|
||||
self.game_transition.draw()
|
||||
@@ -335,6 +390,11 @@ class SongBox:
|
||||
6: ray.Color(134, 88, 0, 255),
|
||||
7: ray.Color(79, 40, 134, 255),
|
||||
8: ray.Color(148, 24, 0, 255),
|
||||
9: ray.Color(101, 0, 82, 255),
|
||||
10: ray.Color(140, 39, 92, 255),
|
||||
11: ray.Color(151, 57, 30, 255),
|
||||
12: ray.Color(35, 123, 103, 255),
|
||||
13: ray.Color(25, 68, 137, 255),
|
||||
14: ray.Color(157, 13, 31, 255)
|
||||
}
|
||||
def __init__(self, name: str, texture_index: int, is_dir: bool, tja: Optional[TJAParser] = None,
|
||||
@@ -461,11 +521,13 @@ class SongBox:
|
||||
|
||||
def _draw_closed(self, x: int, y: int):
|
||||
tex.draw_texture('box', 'folder_texture_left', frame=self.texture_index, x=x)
|
||||
offset = 1 if self.texture_index == 3 or self.texture_index >= 9 else 0
|
||||
offset = 1 if self.texture_index == 3 or self.texture_index >= 9 and self.texture_index not in {10,11,12} else 0
|
||||
tex.draw_texture('box', 'folder_texture', frame=self.texture_index, x=x, x2=32, y=offset)
|
||||
tex.draw_texture('box', 'folder_texture_right', frame=self.texture_index, x=x)
|
||||
if self.texture_index == 9:
|
||||
tex.draw_texture('box', 'genre_overlay', x=x, y=y)
|
||||
elif self.texture_index == 14:
|
||||
tex.draw_texture('box', 'diff_overlay', x=x, y=y)
|
||||
if not self.is_back and self.is_dir:
|
||||
tex.draw_texture('box', 'folder_clip', frame=self.texture_index, x=x - (1 - offset), y=y)
|
||||
|
||||
@@ -505,12 +567,14 @@ class SongBox:
|
||||
self.hori_name.draw(self.hori_name.default_src, dest, ray.Vector2(0, 0), 0, color)
|
||||
|
||||
tex.draw_texture('box', 'folder_texture_left', frame=self.texture_index, x=x - self.open_anim.attribute)
|
||||
offset = 1 if self.texture_index == 3 or self.texture_index >= 9 else 0
|
||||
offset = 1 if self.texture_index == 3 or self.texture_index >= 9 and self.texture_index not in {10,11,12} else 0
|
||||
tex.draw_texture('box', 'folder_texture', frame=self.texture_index, x=x - self.open_anim.attribute, y=offset, x2=324)
|
||||
tex.draw_texture('box', 'folder_texture_right', frame=self.texture_index, x=x + self.open_anim.attribute)
|
||||
|
||||
if self.texture_index == 9:
|
||||
tex.draw_texture('box', 'genre_overlay_large', x=x, y=y, color=color)
|
||||
elif self.texture_index == 14:
|
||||
tex.draw_texture('box', 'diff_overlay_large', x=x, y=y, color=color)
|
||||
color = ray.WHITE
|
||||
if fade_override is not None:
|
||||
color = ray.fade(ray.WHITE, min(0.5, fade_override))
|
||||
@@ -780,6 +844,122 @@ class UraSwitchAnimation:
|
||||
def draw(self):
|
||||
tex.draw_texture('diff_select', 'ura_switch', frame=self.texture_change.attribute, fade=self.fade_out.attribute)
|
||||
|
||||
class DiffSortSelect:
|
||||
def __init__(self):
|
||||
self.selected_box = -1
|
||||
self.selected_level = 1
|
||||
self.in_level_select = False
|
||||
self.confirmation = False
|
||||
self.confirm_index = 1
|
||||
self.num_boxes = 6
|
||||
self.limits = [5, 7, 8, 10]
|
||||
|
||||
def update(self, current_ms):
|
||||
pass
|
||||
|
||||
def get_random_sort(self):
|
||||
diff = random.randint(0, 4)
|
||||
if diff == 0:
|
||||
level = random.randint(1, 5)
|
||||
elif diff == 1:
|
||||
level = random.randint(1, 7)
|
||||
elif diff == 2:
|
||||
level = random.randint(1, 8)
|
||||
elif diff == 3:
|
||||
level = random.randint(1, 10)
|
||||
else:
|
||||
level = random.choice([1, 5, 6, 7, 8, 9, 10])
|
||||
return diff, level
|
||||
|
||||
def input_select(self):
|
||||
if self.confirmation:
|
||||
if self.confirm_index == 0:
|
||||
self.confirmation = False
|
||||
return (-2, -1)
|
||||
elif self.confirm_index == 1:
|
||||
return self.selected_box, self.selected_level
|
||||
elif self.confirm_index == 2:
|
||||
self.confirmation = False
|
||||
self.in_level_select = False
|
||||
return (-2, -1)
|
||||
elif self.in_level_select:
|
||||
self.confirmation = True
|
||||
self.confirm_index = 1
|
||||
return (-2, -1)
|
||||
if self.selected_box == -1:
|
||||
return (-1, -1)
|
||||
elif self.selected_box == 5:
|
||||
return (-1, 0)
|
||||
elif self.selected_box == 4:
|
||||
return self.get_random_sort()
|
||||
self.in_level_select = True
|
||||
self.selected_level = min(self.selected_level, self.limits[self.selected_box])
|
||||
return (-2, -1)
|
||||
|
||||
def input_left(self):
|
||||
if self.confirmation:
|
||||
self.confirm_index = max(self.confirm_index - 1, 0)
|
||||
elif self.in_level_select:
|
||||
self.selected_level = max(self.selected_level - 1, 1)
|
||||
else:
|
||||
self.selected_box = max(self.selected_box - 1, -1)
|
||||
|
||||
def input_right(self):
|
||||
if self.confirmation:
|
||||
self.confirm_index = min(self.confirm_index + 1, 2)
|
||||
elif self.in_level_select:
|
||||
self.selected_level = min(self.selected_level + 1, self.limits[self.selected_box])
|
||||
else:
|
||||
self.selected_box = min(self.selected_box + 1, self.num_boxes - 1)
|
||||
|
||||
def draw_diff_select(self):
|
||||
tex.draw_texture('diff_sort', 'background')
|
||||
|
||||
tex.draw_texture('diff_sort', 'back')
|
||||
for i in range(self.num_boxes):
|
||||
if i == self.selected_box:
|
||||
tex.draw_texture('diff_sort', 'box_highlight', x=(100*i))
|
||||
tex.draw_texture('diff_sort', 'box_text_highlight', x=(100*i), frame=i)
|
||||
else:
|
||||
tex.draw_texture('diff_sort', 'box', x=(100*i))
|
||||
tex.draw_texture('diff_sort', 'box_text', x=(100*i), frame=i)
|
||||
if self.selected_box == -1:
|
||||
tex.draw_texture('diff_sort', 'back_outline')
|
||||
else:
|
||||
tex.draw_texture('diff_sort', 'box_outline', x=(100*self.selected_box))
|
||||
|
||||
for i in range(self.num_boxes):
|
||||
if i < 4:
|
||||
tex.draw_texture('diff_sort', 'box_diff', x=(100*i), frame=i)
|
||||
|
||||
def draw_level_select(self):
|
||||
tex.draw_texture('diff_sort', 'background')
|
||||
tex.draw_texture('diff_sort', 'star_select_text')
|
||||
tex.draw_texture('diff_sort', 'star_limit', frame=self.selected_box)
|
||||
tex.draw_texture('diff_sort', 'level_box')
|
||||
tex.draw_texture('diff_sort', 'diff', frame=self.selected_box)
|
||||
tex.draw_texture('diff_sort', 'star_num', frame=self.selected_level)
|
||||
for i in range(self.selected_level):
|
||||
tex.draw_texture('diff_sort', 'star', x=(i*40.5))
|
||||
|
||||
if self.confirmation:
|
||||
for i in range(3):
|
||||
if i == self.confirm_index:
|
||||
tex.draw_texture('diff_sort', 'small_box_highlight', x=(i*245))
|
||||
tex.draw_texture('diff_sort', 'small_box_text_highlight', x=(i*245), frame=i)
|
||||
else:
|
||||
tex.draw_texture('diff_sort', 'small_box', x=(i*245))
|
||||
tex.draw_texture('diff_sort', 'small_box_text', x=(i*245), frame=i)
|
||||
else:
|
||||
tex.draw_texture('diff_sort', 'pongos')
|
||||
|
||||
def draw(self):
|
||||
ray.draw_rectangle(0, 0, 1280, 720, ray.fade(ray.BLACK, 0.6))
|
||||
if self.in_level_select:
|
||||
self.draw_level_select()
|
||||
else:
|
||||
self.draw_diff_select()
|
||||
|
||||
class FileSystemItem:
|
||||
GENRE_MAP = {
|
||||
'J-POP': 1,
|
||||
@@ -790,11 +970,15 @@ class FileSystemItem:
|
||||
'クラシック': 6,
|
||||
'ゲームミュージック': 7,
|
||||
'ナムコオリジナル': 8,
|
||||
'RECOMMENDED': 10,
|
||||
'FAVORITE': 11,
|
||||
'RECENT': 12,
|
||||
'DIFFICULTY': 14
|
||||
}
|
||||
"""Base class for files and directories in the navigation system"""
|
||||
def __init__(self, path: Path, name: str):
|
||||
self.path = path
|
||||
self.name = name
|
||||
|
||||
class Directory(FileSystemItem):
|
||||
"""Represents a directory in the navigation system"""
|
||||
@@ -856,7 +1040,12 @@ class FileNavigator:
|
||||
self.current_root_dir = Path()
|
||||
self.items: list[Directory | SongFile] = []
|
||||
self.new_items: list[Directory | SongFile] = []
|
||||
self.favorite_folder: Optional[Directory] = None
|
||||
self.in_favorites = False
|
||||
self.recent_folder: Optional[Directory] = None
|
||||
self.selected_index = 0
|
||||
self.diff_sort_diff = 4
|
||||
self.diff_sort_level = 10
|
||||
self.history = []
|
||||
self.box_open = False
|
||||
self.genre_bg = None
|
||||
@@ -915,6 +1104,10 @@ class FileNavigator:
|
||||
box_texture=box_texture,
|
||||
collection=collection
|
||||
)
|
||||
if directory_obj.collection == Directory.COLLECTIONS[2]:
|
||||
self.favorite_folder = directory_obj
|
||||
elif directory_obj.collection == Directory.COLLECTIONS[1]:
|
||||
self.recent_folder = directory_obj
|
||||
self.all_directories[dir_key] = directory_obj
|
||||
|
||||
# Generate content list for this directory
|
||||
@@ -979,7 +1172,7 @@ class FileNavigator:
|
||||
self.root_items.append(self.all_song_files[song_key])
|
||||
|
||||
def _count_tja_files(self, folder_path: Path):
|
||||
"""Count TJA files in directory (matching original logic)"""
|
||||
"""Count TJA files in directory"""
|
||||
tja_count = 0
|
||||
|
||||
# Find all song_list.txt files recursively
|
||||
@@ -988,16 +1181,10 @@ class FileNavigator:
|
||||
if song_list_files:
|
||||
# Process all song_list.txt files found
|
||||
for song_list_path in song_list_files:
|
||||
try:
|
||||
with open(song_list_path, 'r', encoding='utf-8-sig') as song_list_file:
|
||||
tja_count += len([line for line in song_list_file.readlines() if line.strip()])
|
||||
except (IOError, UnicodeDecodeError) as e:
|
||||
# Handle potential file reading errors
|
||||
print(f"Warning: Could not read {song_list_path}: {e}")
|
||||
continue
|
||||
else:
|
||||
# Fallback: Use recursive counting of .tja files
|
||||
tja_count = sum(1 for _ in folder_path.rglob("*.tja"))
|
||||
with open(song_list_path, 'r', encoding='utf-8-sig') as song_list_file:
|
||||
tja_count += len([line for line in song_list_file.readlines() if line.strip()])
|
||||
# Fallback: Use recursive counting of .tja files
|
||||
tja_count += sum(1 for _ in folder_path.rglob("*.tja"))
|
||||
|
||||
return tja_count
|
||||
|
||||
@@ -1046,41 +1233,35 @@ class FileNavigator:
|
||||
"""Find TJA files only in the specified directory, not recursively in subdirectories with box.def"""
|
||||
tja_files: list[Path] = []
|
||||
|
||||
try:
|
||||
for path in directory.iterdir():
|
||||
if path.is_file() and path.suffix.lower() == ".tja":
|
||||
tja_files.append(path)
|
||||
elif path.is_dir():
|
||||
# Only recurse into subdirectories that don't have box.def
|
||||
sub_dir_has_box_def = (path / "box.def").exists()
|
||||
if not sub_dir_has_box_def:
|
||||
tja_files.extend(self._find_tja_files_in_directory_only(path))
|
||||
except (PermissionError, OSError):
|
||||
pass
|
||||
for path in directory.iterdir():
|
||||
if path.is_file() and path.suffix.lower() == ".tja":
|
||||
tja_files.append(path)
|
||||
elif path.is_dir():
|
||||
# Only recurse into subdirectories that don't have box.def
|
||||
sub_dir_has_box_def = (path / "box.def").exists()
|
||||
if not sub_dir_has_box_def:
|
||||
tja_files.extend(self._find_tja_files_in_directory_only(path))
|
||||
|
||||
return tja_files
|
||||
|
||||
def _find_tja_files_recursive(self, directory: Path, box_def_dirs_only=True):
|
||||
tja_files: list[Path] = []
|
||||
|
||||
try:
|
||||
has_box_def = (directory / "box.def").exists()
|
||||
# Fixed: Only skip if box_def_dirs_only is True AND has_box_def AND it's not the directory we're currently processing
|
||||
# During object generation, we want to get files from directories with box.def
|
||||
if box_def_dirs_only and has_box_def and directory != self.current_dir:
|
||||
# This logic should only apply during navigation, not during object generation
|
||||
# During object generation, we want to collect all TJA files
|
||||
return []
|
||||
has_box_def = (directory / "box.def").exists()
|
||||
# Fixed: Only skip if box_def_dirs_only is True AND has_box_def AND it's not the directory we're currently processing
|
||||
# During object generation, we want to get files from directories with box.def
|
||||
if box_def_dirs_only and has_box_def and directory != self.current_dir:
|
||||
# This logic should only apply during navigation, not during object generation
|
||||
# During object generation, we want to collect all TJA files
|
||||
return []
|
||||
|
||||
for path in directory.iterdir():
|
||||
if path.is_file() and path.suffix.lower() == ".tja":
|
||||
tja_files.append(path)
|
||||
elif path.is_dir():
|
||||
sub_dir_has_box_def = (path / "box.def").exists()
|
||||
if not sub_dir_has_box_def:
|
||||
tja_files.extend(self._find_tja_files_recursive(path, box_def_dirs_only))
|
||||
except (PermissionError, OSError):
|
||||
pass
|
||||
for path in directory.iterdir():
|
||||
if path.is_file() and path.suffix.lower() == ".tja":
|
||||
tja_files.append(path)
|
||||
elif path.is_dir():
|
||||
sub_dir_has_box_def = (path / "box.def").exists()
|
||||
if not sub_dir_has_box_def:
|
||||
tja_files.extend(self._find_tja_files_recursive(path, box_def_dirs_only))
|
||||
|
||||
return tja_files
|
||||
|
||||
@@ -1137,7 +1318,7 @@ class FileNavigator:
|
||||
for i in range(len(value)):
|
||||
song = value[i]
|
||||
if (song["title"]["en"] == title and
|
||||
song["subtitle"]["en"][2:] == subtitle and
|
||||
song["subtitle"]["en"] == subtitle and
|
||||
Path(song["file_path"]).exists()):
|
||||
hash_val = key
|
||||
tja_files.append(Path(global_data.song_hashes[hash_val][i]["file_path"]))
|
||||
@@ -1151,6 +1332,7 @@ class FileNavigator:
|
||||
if file_updated:
|
||||
with open(path / 'song_list.txt', 'w', encoding='utf-8-sig') as song_list:
|
||||
for line in updated_lines:
|
||||
print("updated", line)
|
||||
song_list.write(line + '\n')
|
||||
|
||||
return tja_files
|
||||
@@ -1197,6 +1379,7 @@ class FileNavigator:
|
||||
"""Load pre-generated items for the current directory"""
|
||||
has_children = any(item.is_dir() and (item / "box.def").exists() for item in self.current_dir.iterdir())
|
||||
self.genre_bg = None
|
||||
self.in_favorites = False
|
||||
if has_children:
|
||||
self.items = []
|
||||
if not self.box_open:
|
||||
@@ -1222,6 +1405,21 @@ class FileNavigator:
|
||||
if isinstance(selected_item, Directory):
|
||||
if selected_item.collection == Directory.COLLECTIONS[0]:
|
||||
content_items = self.new_items
|
||||
elif selected_item.collection == Directory.COLLECTIONS[1]:
|
||||
if self.recent_folder is None:
|
||||
raise Exception("tried to enter recent folder without recents")
|
||||
self._generate_objects_recursive(self.recent_folder.path)
|
||||
selected_item.box.tja_count_text = None
|
||||
selected_item.box.tja_count = self._count_tja_files(self.recent_folder.path)
|
||||
content_items = self.directory_contents[dir_key]
|
||||
elif selected_item.collection == Directory.COLLECTIONS[2]:
|
||||
if self.favorite_folder is None:
|
||||
raise Exception("tried to enter favorite folder without favorites")
|
||||
self._generate_objects_recursive(self.favorite_folder.path)
|
||||
selected_item.box.tja_count_text = None
|
||||
selected_item.box.tja_count = self._count_tja_files(self.favorite_folder.path)
|
||||
content_items = self.directory_contents[dir_key]
|
||||
self.in_favorites = True
|
||||
elif selected_item.collection == Directory.COLLECTIONS[3]:
|
||||
content_items = []
|
||||
parent_dir = selected_item.path.parent
|
||||
@@ -1230,11 +1428,20 @@ class FileNavigator:
|
||||
sibling_key = str(sibling_path)
|
||||
if sibling_key in self.directory_contents:
|
||||
for item in self.directory_contents[sibling_key]:
|
||||
if isinstance(item, SongFile):
|
||||
if 3 in item.tja.metadata.course_data and item.tja.metadata.course_data[3].level == 10:
|
||||
item.box.texture_index = 14
|
||||
content_items.append(item)
|
||||
|
||||
if isinstance(item, SongFile) and item:
|
||||
if self.diff_sort_diff in item.tja.metadata.course_data and item.tja.metadata.course_data[self.diff_sort_diff].level == self.diff_sort_level:
|
||||
if item not in content_items:
|
||||
content_items.append(item)
|
||||
elif selected_item.collection == Directory.COLLECTIONS[4]:
|
||||
parent_dir = selected_item.path.parent
|
||||
temp_items = []
|
||||
for sibling_path in parent_dir.iterdir():
|
||||
if sibling_path.is_dir() and sibling_path != selected_item.path:
|
||||
sibling_key = str(sibling_path)
|
||||
if sibling_key in self.directory_contents:
|
||||
for item in self.directory_contents[sibling_key]:
|
||||
temp_items.append(item)
|
||||
content_items = random.sample(temp_items, 10)
|
||||
i = 1
|
||||
for item in content_items:
|
||||
if isinstance(item, SongFile):
|
||||
@@ -1242,8 +1449,9 @@ class FileNavigator:
|
||||
back_dir = Directory(self.current_dir.parent, "", 17, back=True)
|
||||
self.items.insert(self.selected_index+i, back_dir)
|
||||
i += 1
|
||||
|
||||
if not has_children:
|
||||
if selected_item is not None:
|
||||
item.box.texture_index = selected_item.box.texture_index
|
||||
self.items.insert(self.selected_index+i, item)
|
||||
else:
|
||||
self.items.append(item)
|
||||
@@ -1341,3 +1549,54 @@ class FileNavigator:
|
||||
def reset_items(self):
|
||||
for item in self.items:
|
||||
item.box.reset()
|
||||
|
||||
def add_recent(self):
|
||||
song = self.get_current_item()
|
||||
if isinstance(song, Directory):
|
||||
return
|
||||
if self.recent_folder is None:
|
||||
return
|
||||
|
||||
recents_path = self.recent_folder.path / 'song_list.txt'
|
||||
new_entry = f'{song.hash}|{song.tja.metadata.title["en"]}|{song.tja.metadata.subtitle["en"]}\n'
|
||||
existing_entries = []
|
||||
if recents_path.exists():
|
||||
with open(recents_path, 'r', encoding='utf-8-sig') as song_list:
|
||||
existing_entries = song_list.readlines()
|
||||
existing_entries = [entry for entry in existing_entries if not entry.startswith(f'{song.hash}|')]
|
||||
all_entries = [new_entry] + existing_entries
|
||||
recent_entries = all_entries[:25]
|
||||
with open(recents_path, 'w', encoding='utf-8-sig') as song_list:
|
||||
song_list.writelines(recent_entries)
|
||||
|
||||
print("Added recent: ", song.hash, song.tja.metadata.title['en'], song.tja.metadata.subtitle['en'])
|
||||
|
||||
def add_favorite(self) -> bool:
|
||||
song = self.get_current_item()
|
||||
if isinstance(song, Directory):
|
||||
return False
|
||||
if self.favorite_folder is None:
|
||||
return False
|
||||
favorites_path = self.favorite_folder.path / 'song_list.txt'
|
||||
lines = []
|
||||
with open(favorites_path, 'r', encoding='utf-8-sig') as song_list:
|
||||
for line in song_list:
|
||||
line = line.strip()
|
||||
if not line: # Skip empty lines
|
||||
continue
|
||||
hash, title, subtitle = line.split('|')
|
||||
if song.hash == hash or (song.tja.metadata.title['en'] == title and song.tja.metadata.subtitle['en'] == subtitle):
|
||||
if not self.in_favorites:
|
||||
return False
|
||||
else:
|
||||
lines.append(line)
|
||||
if self.in_favorites:
|
||||
with open(favorites_path, 'w', encoding='utf-8-sig') as song_list:
|
||||
for line in lines:
|
||||
song_list.write(line + '\n')
|
||||
print("Removed favorite:", song.hash, song.tja.metadata.title['en'], song.tja.metadata.subtitle['en'])
|
||||
else:
|
||||
with open(favorites_path, 'a', encoding='utf-8-sig') as song_list:
|
||||
song_list.write(f'{song.hash}|{song.tja.metadata.title['en']}|{song.tja.metadata.subtitle['en']}\n')
|
||||
print("Added favorite: ", song.hash, song.tja.metadata.title['en'], song.tja.metadata.subtitle['en'])
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user