mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
make bootup faster
This commit is contained in:
19
PyTaiko.py
19
PyTaiko.py
@@ -2,7 +2,6 @@ import sqlite3
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pyray as ray
|
import pyray as ray
|
||||||
from dotenv import dotenv_values
|
|
||||||
from raylib.defines import (
|
from raylib.defines import (
|
||||||
RL_FUNC_ADD,
|
RL_FUNC_ADD,
|
||||||
RL_ONE,
|
RL_ONE,
|
||||||
@@ -59,21 +58,11 @@ def create_song_db():
|
|||||||
print("Scores database created successfully")
|
print("Scores database created successfully")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
env_config = dotenv_values(".env")
|
|
||||||
create_song_db()
|
create_song_db()
|
||||||
song_hash.song_hashes = song_hash.build_song_hashes()
|
global_data.song_hashes = song_hash.build_song_hashes()
|
||||||
global_data.config = get_config()
|
global_data.config = get_config()
|
||||||
screen_width: int = global_data.config["video"]["screen_width"]
|
screen_width: int = global_data.config["video"]["screen_width"]
|
||||||
screen_height: int = global_data.config["video"]["screen_height"]
|
screen_height: int = global_data.config["video"]["screen_height"]
|
||||||
'''
|
|
||||||
render_width, render_height = ray.get_render_width(), ray.get_render_height()
|
|
||||||
dpi_scale = ray.get_window_scale_dpi()
|
|
||||||
if dpi_scale.x == 0:
|
|
||||||
dpi_scale = (ray.get_render_width(), ray.get_render_height())
|
|
||||||
dpi_scale = screen_width, screen_height
|
|
||||||
else:
|
|
||||||
dpi_scale = int(render_width/dpi_scale.x), int(render_height/dpi_scale.y)
|
|
||||||
'''
|
|
||||||
|
|
||||||
if global_data.config["video"]["vsync"]:
|
if global_data.config["video"]["vsync"]:
|
||||||
ray.set_config_flags(ray.ConfigFlags.FLAG_VSYNC_HINT)
|
ray.set_config_flags(ray.ConfigFlags.FLAG_VSYNC_HINT)
|
||||||
@@ -113,12 +102,7 @@ def main():
|
|||||||
ray.rl_set_blend_factors_separate(RL_SRC_ALPHA, RL_ONE_MINUS_SRC_ALPHA, RL_ONE, RL_ONE_MINUS_SRC_ALPHA, RL_FUNC_ADD, RL_FUNC_ADD)
|
ray.rl_set_blend_factors_separate(RL_SRC_ALPHA, RL_ONE_MINUS_SRC_ALPHA, RL_ONE, RL_ONE_MINUS_SRC_ALPHA, RL_FUNC_ADD, RL_FUNC_ADD)
|
||||||
ray.set_exit_key(ray.KeyboardKey.KEY_A)
|
ray.set_exit_key(ray.KeyboardKey.KEY_A)
|
||||||
global_data.textures = load_all_textures_from_zip(Path('Graphics/lumendata/intermission.zip'))
|
global_data.textures = load_all_textures_from_zip(Path('Graphics/lumendata/intermission.zip'))
|
||||||
prev_ms = get_current_ms()
|
|
||||||
while not ray.window_should_close():
|
while not ray.window_should_close():
|
||||||
current_ms = get_current_ms()
|
|
||||||
if current_ms >= prev_ms + 100:
|
|
||||||
print("LAG SPIKE DETECTED")
|
|
||||||
prev_ms = current_ms
|
|
||||||
|
|
||||||
ray.begin_texture_mode(target)
|
ray.begin_texture_mode(target)
|
||||||
ray.begin_blend_mode(ray.BlendMode.BLEND_CUSTOM_SEPARATE)
|
ray.begin_blend_mode(ray.BlendMode.BLEND_CUSTOM_SEPARATE)
|
||||||
@@ -140,7 +124,6 @@ def main():
|
|||||||
ray.end_texture_mode()
|
ray.end_texture_mode()
|
||||||
ray.begin_drawing()
|
ray.begin_drawing()
|
||||||
ray.clear_background(ray.WHITE)
|
ray.clear_background(ray.WHITE)
|
||||||
#Thanks to rnoiz proper render height
|
|
||||||
ray.draw_texture_pro(
|
ray.draw_texture_pro(
|
||||||
target.texture,
|
target.texture,
|
||||||
ray.Rectangle(0, 0, target.texture.width, -target.texture.height),
|
ray.Rectangle(0, 0, target.texture.width, -target.texture.height),
|
||||||
|
|||||||
@@ -1,16 +1,138 @@
|
|||||||
import csv
|
import csv
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from git import Repo
|
from git import Repo
|
||||||
|
|
||||||
from libs.tja import TJAParser
|
from libs.tja import TJAParser
|
||||||
from libs.utils import get_config
|
from libs.utils import get_config, global_data
|
||||||
|
|
||||||
song_hashes: Optional[dict] = None
|
|
||||||
|
|
||||||
|
class DiffHashesDecoder(json.JSONDecoder):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(object_hook=self.object_hook, *args, **kwargs)
|
||||||
|
|
||||||
|
def object_hook(self, obj):
|
||||||
|
if "diff_hashes" in obj:
|
||||||
|
obj["diff_hashes"] = {
|
||||||
|
int(key): value
|
||||||
|
for key, value in obj["diff_hashes"].items()
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def build_song_hashes(output_dir=Path("cache")):
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Load existing data
|
||||||
|
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)
|
||||||
|
|
||||||
|
saved_timestamp = 0.0
|
||||||
|
current_timestamp = time.time()
|
||||||
|
if (output_dir / 'timestamp.txt').exists():
|
||||||
|
with open(output_dir / 'timestamp.txt', 'r') as f:
|
||||||
|
saved_timestamp = float(f.read())
|
||||||
|
|
||||||
|
tja_paths = get_config()["paths"]["tja_path"]
|
||||||
|
all_tja_files: list[Path] = []
|
||||||
|
for root_dir in tja_paths:
|
||||||
|
root_path = Path(root_dir)
|
||||||
|
if (root_path / '.git').exists():
|
||||||
|
repo = Repo(root_path)
|
||||||
|
origin = repo.remotes.origin
|
||||||
|
origin.pull()
|
||||||
|
print('Pulled latest from', root_path)
|
||||||
|
all_tja_files.extend(root_path.rglob("*.tja"))
|
||||||
|
|
||||||
|
files_to_process = []
|
||||||
|
|
||||||
|
# O(n) pass to identify which files need processing
|
||||||
|
for tja_path in all_tja_files:
|
||||||
|
tja_path_str = str(tja_path)
|
||||||
|
current_modified = tja_path.stat().st_mtime
|
||||||
|
|
||||||
|
# Skip files that haven't been modified since last run
|
||||||
|
if current_modified <= saved_timestamp:
|
||||||
|
# File hasn't changed, just restore to global_data if we have it
|
||||||
|
current_hash = path_to_hash.get(tja_path_str)
|
||||||
|
if current_hash is not None:
|
||||||
|
global_data.song_paths[tja_path] = current_hash
|
||||||
|
continue
|
||||||
|
|
||||||
|
# O(1) lookup instead of nested loops
|
||||||
|
current_hash = path_to_hash.get(tja_path_str)
|
||||||
|
|
||||||
|
if current_hash is None:
|
||||||
|
# New file (modified after saved_timestamp)
|
||||||
|
files_to_process.append(tja_path)
|
||||||
|
else:
|
||||||
|
# File was modified after saved_timestamp, need to reprocess
|
||||||
|
files_to_process.append(tja_path)
|
||||||
|
# Clean up old hash
|
||||||
|
if current_hash in song_hashes:
|
||||||
|
del song_hashes[current_hash]
|
||||||
|
del path_to_hash[tja_path_str]
|
||||||
|
|
||||||
|
# Process only files that need updating
|
||||||
|
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()
|
||||||
|
diff_hashes = dict()
|
||||||
|
|
||||||
|
for diff in tja.metadata.course_data:
|
||||||
|
diff_notes, _, diff_bars = TJAParser.notes_to_position(TJAParser(tja.file_path), diff)
|
||||||
|
diff_hashes[diff] = tja.hash_note_data(diff_notes, diff_bars)
|
||||||
|
all_notes.extend(diff_notes)
|
||||||
|
all_bars.extend(diff_bars)
|
||||||
|
|
||||||
|
if all_notes == []:
|
||||||
|
continue
|
||||||
|
|
||||||
|
hash_val = tja.hash_note_data(all_notes, all_bars)
|
||||||
|
|
||||||
|
if hash_val not in song_hashes:
|
||||||
|
song_hashes[hash_val] = []
|
||||||
|
|
||||||
|
song_hashes[hash_val].append({
|
||||||
|
"file_path": tja_path_str,
|
||||||
|
"last_modified": current_modified,
|
||||||
|
"title": tja.metadata.title,
|
||||||
|
"subtitle": tja.metadata.subtitle,
|
||||||
|
"diff_hashes": diff_hashes
|
||||||
|
})
|
||||||
|
|
||||||
|
# Update both indexes
|
||||||
|
path_to_hash[tja_path_str] = hash_val
|
||||||
|
global_data.song_paths[tja_path] = hash_val
|
||||||
|
|
||||||
|
# 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))
|
||||||
|
|
||||||
|
return song_hashes
|
||||||
|
|
||||||
def process_tja_file(tja_file):
|
def process_tja_file(tja_file):
|
||||||
"""Process a single TJA file and return hash or None if error"""
|
"""Process a single TJA file and return hash or None if error"""
|
||||||
@@ -25,78 +147,6 @@ def process_tja_file(tja_file):
|
|||||||
hash = tja.hash_note_data(all_notes[0], all_notes[2])
|
hash = tja.hash_note_data(all_notes[0], all_notes[2])
|
||||||
return hash
|
return hash
|
||||||
|
|
||||||
|
|
||||||
def build_song_hashes(output_file="cache/song_hashes.json"):
|
|
||||||
existing_hashes = {}
|
|
||||||
output_path = Path(output_file)
|
|
||||||
if output_path.exists():
|
|
||||||
try:
|
|
||||||
with open(output_file, "r", encoding="utf-8") as f:
|
|
||||||
existing_hashes = json.load(f)
|
|
||||||
except (json.JSONDecodeError, IOError) as e:
|
|
||||||
print(
|
|
||||||
f"Warning: Could not load existing hashes from {output_file}: {e}"
|
|
||||||
)
|
|
||||||
existing_hashes = {}
|
|
||||||
|
|
||||||
song_hashes = existing_hashes.copy()
|
|
||||||
tja_paths = get_config()["paths"]["tja_path"]
|
|
||||||
all_tja_files = []
|
|
||||||
|
|
||||||
for root_dir in tja_paths:
|
|
||||||
root_path = Path(root_dir)
|
|
||||||
if (root_path / '.git').exists():
|
|
||||||
repo = Repo(root_path)
|
|
||||||
origin = repo.remotes.origin
|
|
||||||
origin.pull()
|
|
||||||
print('Pulled latest from', root_path)
|
|
||||||
all_tja_files.extend(root_path.rglob("*.tja"))
|
|
||||||
|
|
||||||
updated_count = 0
|
|
||||||
for tja_file in all_tja_files:
|
|
||||||
current_modified = tja_file.stat().st_mtime
|
|
||||||
|
|
||||||
should_update = False
|
|
||||||
hash_val = None
|
|
||||||
|
|
||||||
existing_hash = None
|
|
||||||
for h, data in song_hashes.items():
|
|
||||||
if data["file_path"] == str(tja_file):
|
|
||||||
existing_hash = h
|
|
||||||
break
|
|
||||||
|
|
||||||
if existing_hash is None:
|
|
||||||
should_update = True
|
|
||||||
else:
|
|
||||||
stored_modified = song_hashes[existing_hash].get("last_modified", 0)
|
|
||||||
if current_modified > stored_modified:
|
|
||||||
should_update = True
|
|
||||||
del song_hashes[existing_hash]
|
|
||||||
|
|
||||||
if should_update:
|
|
||||||
tja = TJAParser(tja_file)
|
|
||||||
all_notes = []
|
|
||||||
for diff in tja.metadata.course_data:
|
|
||||||
all_notes.extend(
|
|
||||||
TJAParser.notes_to_position(TJAParser(tja.file_path), diff)
|
|
||||||
)
|
|
||||||
if all_notes == []:
|
|
||||||
continue
|
|
||||||
hash_val = tja.hash_note_data(all_notes[0], all_notes[2])
|
|
||||||
song_hashes[hash_val] = {
|
|
||||||
"file_path": str(tja_file),
|
|
||||||
"last_modified": current_modified,
|
|
||||||
"title": tja.metadata.title,
|
|
||||||
"subtitle": tja.metadata.subtitle,
|
|
||||||
}
|
|
||||||
updated_count += 1
|
|
||||||
|
|
||||||
with open(output_file, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(song_hashes, f, indent=2, ensure_ascii=False)
|
|
||||||
|
|
||||||
print(f"Song hashes saved to {output_file}. Updated {updated_count} files.")
|
|
||||||
return song_hashes
|
|
||||||
|
|
||||||
def get_japanese_songs_for_version(csv_file_path, version_column):
|
def get_japanese_songs_for_version(csv_file_path, version_column):
|
||||||
# Read CSV file and filter rows where the specified version column has 'YES'
|
# Read CSV file and filter rows where the specified version column has 'YES'
|
||||||
version_songs = []
|
version_songs = []
|
||||||
|
|||||||
@@ -232,9 +232,9 @@ class GlobalData:
|
|||||||
textures: dict[str, list[ray.Texture]] = field(default_factory=lambda: dict())
|
textures: dict[str, list[ray.Texture]] = field(default_factory=lambda: dict())
|
||||||
songs_played: int = 0
|
songs_played: int = 0
|
||||||
config: dict = field(default_factory=lambda: dict())
|
config: dict = field(default_factory=lambda: dict())
|
||||||
|
song_hashes: dict[str, list[dict]] = field(default_factory=lambda: dict()) #Hash to path
|
||||||
|
song_paths: dict[Path, str] = field(default_factory=lambda: dict()) #path to hash
|
||||||
global_data = GlobalData()
|
global_data = GlobalData()
|
||||||
shader = ray.load_shader('', 'shader/outline.fs')
|
|
||||||
|
|
||||||
class OutlinedText:
|
class OutlinedText:
|
||||||
def __init__(self, text: str, font_size: int, color: ray.Color, outline_color: ray.Color, outline_thickness=5.0, vertical=False):
|
def __init__(self, text: str, font_size: int, color: ray.Color, outline_color: ray.Color, outline_thickness=5.0, vertical=False):
|
||||||
@@ -260,7 +260,7 @@ class OutlinedText:
|
|||||||
])
|
])
|
||||||
texture_size = ray.ffi.new("float[2]", [self.texture.width, self.texture.height])
|
texture_size = ray.ffi.new("float[2]", [self.texture.width, self.texture.height])
|
||||||
|
|
||||||
self.shader = shader
|
self.shader = ray.load_shader('shader/outline.vs', 'shader/outline.fs')
|
||||||
outline_size_loc = ray.get_shader_location(self.shader, "outlineSize")
|
outline_size_loc = ray.get_shader_location(self.shader, "outlineSize")
|
||||||
outline_color_loc = ray.get_shader_location(self.shader, "outlineColor")
|
outline_color_loc = ray.get_shader_location(self.shader, "outlineColor")
|
||||||
texture_size_loc = ray.get_shader_location(self.shader, "textureSize")
|
texture_size_loc = ray.get_shader_location(self.shader, "textureSize")
|
||||||
@@ -279,7 +279,7 @@ class OutlinedText:
|
|||||||
rotate_chars = {'-', '‐', '|', '/', '\\', 'ー', '~', '~', '(', ')', '(', ')',
|
rotate_chars = {'-', '‐', '|', '/', '\\', 'ー', '~', '~', '(', ')', '(', ')',
|
||||||
'「', '」', '[', ']', '[', ']', '【', '】', '…', '→', '→', ':', ':'}
|
'「', '」', '[', ']', '[', ']', '【', '】', '…', '→', '→', ':', ':'}
|
||||||
max_char_width = 0
|
max_char_width = 0
|
||||||
total_height = padding * 2 # Top and bottom padding
|
total_height = padding * 2
|
||||||
|
|
||||||
for char in text:
|
for char in text:
|
||||||
if font:
|
if font:
|
||||||
@@ -288,22 +288,20 @@ class OutlinedText:
|
|||||||
char_width = ray.measure_text(char, font_size)
|
char_width = ray.measure_text(char, font_size)
|
||||||
char_size = ray.Vector2(char_width, font_size)
|
char_size = ray.Vector2(char_width, font_size)
|
||||||
|
|
||||||
# If character should be rotated, swap width and height for measurements
|
|
||||||
if char in rotate_chars:
|
if char in rotate_chars:
|
||||||
effective_width = char_size.y # Height becomes width when rotated 90°
|
effective_width = char_size.y
|
||||||
else:
|
else:
|
||||||
effective_width = char_size.x
|
effective_width = char_size.x
|
||||||
|
|
||||||
max_char_width = max(max_char_width, effective_width)
|
max_char_width = max(max_char_width, effective_width)
|
||||||
|
|
||||||
total_height += len(text) * font_size
|
total_height += len(text) * font_size
|
||||||
width = int(max_char_width + (padding * 2)) # Add left and right padding
|
width = int(max_char_width + (padding * 2))
|
||||||
height = total_height
|
height = total_height
|
||||||
image = ray.gen_image_color(width, height, bg_color)
|
image = ray.gen_image_color(width, height, bg_color)
|
||||||
|
|
||||||
for i, char in enumerate(text):
|
for i, char in enumerate(text):
|
||||||
char_y = i * ray.measure_text_ex(self.font, char, font_size, 0).y
|
char_y = i * font_size + padding
|
||||||
char_y += padding
|
|
||||||
|
|
||||||
if font:
|
if font:
|
||||||
char_size = ray.measure_text_ex(font, char, font_size, 0)
|
char_size = ray.measure_text_ex(font, char, font_size, 0)
|
||||||
@@ -313,26 +311,22 @@ class OutlinedText:
|
|||||||
char_size = ray.Vector2(char_width, font_size)
|
char_size = ray.Vector2(char_width, font_size)
|
||||||
char_image = ray.image_text(char, font_size, color)
|
char_image = ray.image_text(char, font_size, color)
|
||||||
|
|
||||||
# Rotate character if it's in the rotate_chars set
|
|
||||||
if char in rotate_chars:
|
if char in rotate_chars:
|
||||||
rotated_image = ray.gen_image_color(char_image.height, char_image.width, ray.BLANK)
|
rotated_image = ray.gen_image_color(char_image.height, char_image.width, ray.BLANK)
|
||||||
|
|
||||||
# Manual 90-degree clockwise rotation
|
|
||||||
for y in range(char_image.height):
|
for y in range(char_image.height):
|
||||||
for x in range(char_image.width):
|
for x in range(char_image.width):
|
||||||
src_color = ray.get_image_color(char_image, x, y)
|
src_color = ray.get_image_color(char_image, x, y)
|
||||||
# 90° clockwise: new_x = old_y, new_y = width - 1 - old_x
|
new_x = char_image.height - 1 - y
|
||||||
new_x = y
|
new_y = x
|
||||||
new_y = char_image.width - 1 - x
|
|
||||||
ray.image_draw_pixel(rotated_image, new_x, new_y, src_color)
|
ray.image_draw_pixel(rotated_image, new_x, new_y, src_color)
|
||||||
|
|
||||||
ray.unload_image(char_image)
|
ray.unload_image(char_image)
|
||||||
char_image = rotated_image
|
char_image = rotated_image
|
||||||
effective_width = char_size.y # Height becomes width when rotated
|
effective_width = char_size.y
|
||||||
else:
|
else:
|
||||||
effective_width = char_size.x
|
effective_width = char_size.x
|
||||||
|
|
||||||
# Center the character horizontally
|
|
||||||
char_x = width // 2 - effective_width // 2
|
char_x = width // 2 - effective_width // 2
|
||||||
|
|
||||||
ray.image_draw(image, char_image,
|
ray.image_draw(image, char_image,
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class EntryScreen:
|
|||||||
self.selected_box = max(0, self.selected_box - 1)
|
self.selected_box = max(0, self.selected_box - 1)
|
||||||
if is_r_kat_pressed():
|
if is_r_kat_pressed():
|
||||||
audio.play_sound(self.sound_kat)
|
audio.play_sound(self.sound_kat)
|
||||||
self.selected_box = min(self.num_boxes, self.selected_box + 1)
|
self.selected_box = min(self.num_boxes - 1, self.selected_box + 1)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.on_screen_start()
|
self.on_screen_start()
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ class ResultScreen:
|
|||||||
self.load_textures()
|
self.load_textures()
|
||||||
self.load_sounds()
|
self.load_sounds()
|
||||||
self.screen_init = True
|
self.screen_init = True
|
||||||
self.song_info = FontText(session_data.song_title, 40).texture
|
self.song_info = OutlinedText(session_data.song_title, 40, ray.Color(255, 255, 255, 255), ray.Color(0, 0, 0, 255), outline_thickness=5)
|
||||||
audio.play_sound(self.bgm)
|
audio.play_sound(self.bgm)
|
||||||
self.fade_in = FadeIn(get_current_ms())
|
self.fade_in = FadeIn()
|
||||||
self.fade_out = None
|
self.fade_out = None
|
||||||
self.gauge = None
|
self.gauge = None
|
||||||
self.score_delay = None
|
self.score_delay = None
|
||||||
@@ -165,7 +165,9 @@ class ResultScreen:
|
|||||||
|
|
||||||
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)
|
||||||
ray.draw_texture(self.song_info, 1252 - self.song_info.width, int(35 - self.song_info.height / 2), ray.WHITE)
|
src = ray.Rectangle(0, 0, self.song_info.texture.width, self.song_info.texture.height)
|
||||||
|
dest = ray.Rectangle(1252 - self.song_info.texture.width, 35 - self.song_info.texture.height / 2, self.song_info.texture.width, self.song_info.texture.height)
|
||||||
|
self.song_info.draw(src, dest, ray.Vector2(0, 0), 0, ray.WHITE)
|
||||||
|
|
||||||
ray.draw_texture(self.textures['result'][175], 532, 98, ray.fade(ray.WHITE, 0.75))
|
ray.draw_texture(self.textures['result'][175], 532, 98, ray.fade(ray.WHITE, 0.75))
|
||||||
|
|
||||||
@@ -189,7 +191,7 @@ class ResultScreen:
|
|||||||
|
|
||||||
|
|
||||||
class FadeIn:
|
class FadeIn:
|
||||||
def __init__(self, current_ms: float):
|
def __init__(self):
|
||||||
self.fadein = Animation.create_fade(450, initial_opacity=1.0, final_opacity=0.0, delay=100)
|
self.fadein = Animation.create_fade(450, initial_opacity=1.0, final_opacity=0.0, delay=100)
|
||||||
self.fade = ray.fade(ray.WHITE, self.fadein.attribute)
|
self.fade = ray.fade(ray.WHITE, self.fadein.attribute)
|
||||||
|
|
||||||
@@ -212,12 +214,6 @@ class FadeIn:
|
|||||||
ray.draw_texture(texture_2, x, screen_height - texture_2.height + 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
|
x += texture_2.width
|
||||||
|
|
||||||
class FontText:
|
|
||||||
def __init__(self, text, font_size):
|
|
||||||
self.text = OutlinedText(str(text), font_size, ray.Color(255, 255, 255, 255), ray.Color(0, 0, 0, 255), outline_thickness=5)
|
|
||||||
|
|
||||||
self.texture = self.text.texture
|
|
||||||
|
|
||||||
class ScoreAnimator:
|
class ScoreAnimator:
|
||||||
def __init__(self, target_score):
|
def __init__(self, target_score):
|
||||||
self.target_score = str(target_score)
|
self.target_score = str(target_score)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from typing import Optional, Union
|
|||||||
|
|
||||||
import pyray as ray
|
import pyray as ray
|
||||||
|
|
||||||
from libs import song_hash
|
|
||||||
from libs.animation import Animation
|
from libs.animation import Animation
|
||||||
from libs.audio import audio
|
from libs.audio import audio
|
||||||
from libs.tja import TJAParser
|
from libs.tja import TJAParser
|
||||||
@@ -418,20 +417,8 @@ class SongBox:
|
|||||||
def get_scores(self):
|
def get_scores(self):
|
||||||
if self.tja is None:
|
if self.tja is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
with sqlite3.connect('scores.db') as con:
|
with sqlite3.connect('scores.db') as con:
|
||||||
cursor = con.cursor()
|
cursor = con.cursor()
|
||||||
|
|
||||||
diffs_to_compute = []
|
|
||||||
for diff in self.tja.metadata.course_data:
|
|
||||||
if diff not in self.hash:
|
|
||||||
diffs_to_compute.append(diff)
|
|
||||||
|
|
||||||
if diffs_to_compute:
|
|
||||||
for diff in diffs_to_compute:
|
|
||||||
notes, _, bars = TJAParser.notes_to_position(TJAParser(self.tja.file_path), diff)
|
|
||||||
self.hash[diff] = self.tja.hash_note_data(notes, bars)
|
|
||||||
|
|
||||||
# Batch database query for all diffs at once
|
# Batch database query for all diffs at once
|
||||||
if self.tja.metadata.course_data:
|
if self.tja.metadata.course_data:
|
||||||
hash_values = [self.hash[diff] for diff in self.tja.metadata.course_data]
|
hash_values = [self.hash[diff] for diff in self.tja.metadata.course_data]
|
||||||
@@ -1004,17 +991,15 @@ class SongFile(FileSystemItem):
|
|||||||
if self.is_recent:
|
if self.is_recent:
|
||||||
self.tja.ex_data.new = True
|
self.tja.ex_data.new = True
|
||||||
title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
|
title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
|
||||||
|
self.hash = global_data.song_paths[path]
|
||||||
self.box = SongBox(title, texture_index, False, tja=self.tja, name_texture_index=name_texture_index if name_texture_index is not None else texture_index)
|
self.box = SongBox(title, texture_index, False, tja=self.tja, name_texture_index=name_texture_index if name_texture_index is not None else texture_index)
|
||||||
|
self.box.hash = global_data.song_hashes[self.hash][0]["diff_hashes"]
|
||||||
self.box.get_scores()
|
self.box.get_scores()
|
||||||
|
|
||||||
class FileNavigator:
|
class FileNavigator:
|
||||||
"""Manages navigation through pre-generated Directory and SongFile objects"""
|
"""Manages navigation through pre-generated Directory and SongFile objects"""
|
||||||
def __init__(self, root_dirs: list[str]):
|
def __init__(self, root_dirs: list[str]):
|
||||||
# Handle both single path and list of paths
|
|
||||||
if isinstance(root_dirs, (list, tuple)):
|
|
||||||
self.root_dirs = [Path(p) if not isinstance(p, Path) else p for p in root_dirs]
|
self.root_dirs = [Path(p) if not isinstance(p, Path) else p for p in root_dirs]
|
||||||
else:
|
|
||||||
self.root_dirs = [Path(root_dirs) if not isinstance(root_dirs, Path) else root_dirs]
|
|
||||||
|
|
||||||
# Pre-generated objects storage
|
# Pre-generated objects storage
|
||||||
self.all_directories: dict[str, Directory] = {} # path -> Directory
|
self.all_directories: dict[str, Directory] = {} # path -> Directory
|
||||||
@@ -1118,14 +1103,10 @@ class FileNavigator:
|
|||||||
for i, tja_path in enumerate(sorted(tja_files)):
|
for i, tja_path in enumerate(sorted(tja_files)):
|
||||||
song_key = str(tja_path)
|
song_key = str(tja_path)
|
||||||
if song_key not in self.all_song_files:
|
if song_key not in self.all_song_files:
|
||||||
try:
|
|
||||||
song_obj = SongFile(tja_path, tja_path.name, texture_index)
|
song_obj = SongFile(tja_path, tja_path.name, texture_index)
|
||||||
if song_obj.is_recent:
|
if song_obj.is_recent:
|
||||||
self.new_items.append(SongFile(tja_path, tja_path.name, 620, name_texture_index=texture_index))
|
self.new_items.append(SongFile(tja_path, tja_path.name, 620, name_texture_index=texture_index))
|
||||||
self.all_song_files[song_key] = song_obj
|
self.all_song_files[song_key] = song_obj
|
||||||
except Exception as e:
|
|
||||||
print(f"Error creating SongFile for {tja_path}: {e}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
content_items.append(self.all_song_files[song_key])
|
content_items.append(self.all_song_files[song_key])
|
||||||
|
|
||||||
@@ -1302,19 +1283,20 @@ class FileNavigator:
|
|||||||
hash_val, title, subtitle = parts[0], parts[1], parts[2]
|
hash_val, title, subtitle = parts[0], parts[1], parts[2]
|
||||||
original_hash = hash_val
|
original_hash = hash_val
|
||||||
|
|
||||||
if song_hash.song_hashes is not None:
|
if hash_val in global_data.song_hashes:
|
||||||
if hash_val in song_hash.song_hashes:
|
file_path = Path(global_data.song_hashes[hash_val][0]["file_path"])
|
||||||
file_path = Path(song_hash.song_hashes[hash_val]["file_path"])
|
|
||||||
if file_path.exists():
|
if file_path.exists():
|
||||||
tja_files.append(file_path)
|
tja_files.append(file_path)
|
||||||
else:
|
else:
|
||||||
# Try to find by title and subtitle
|
# Try to find by title and subtitle
|
||||||
for key, value in song_hash.song_hashes.items():
|
for key, value in global_data.song_hashes.items():
|
||||||
if (value["title"]["en"] == title and
|
for i in range(len(value)):
|
||||||
value["subtitle"]["en"][2:] == subtitle and
|
song = value[i]
|
||||||
Path(value["file_path"]).exists()):
|
if (song["title"]["en"] == title and
|
||||||
|
song["subtitle"]["en"][2:] == subtitle and
|
||||||
|
Path(song["file_path"]).exists()):
|
||||||
hash_val = key
|
hash_val = key
|
||||||
tja_files.append(Path(song_hash.song_hashes[hash_val]["file_path"]))
|
tja_files.append(Path(global_data.song_hashes[hash_val][i]["file_path"]))
|
||||||
break
|
break
|
||||||
|
|
||||||
if hash_val != original_hash:
|
if hash_val != original_hash:
|
||||||
@@ -1523,17 +1505,3 @@ class FileNavigator:
|
|||||||
self.current_root_dir = Path()
|
self.current_root_dir = Path()
|
||||||
self.history.clear()
|
self.history.clear()
|
||||||
self.load_root_directories()
|
self.load_root_directories()
|
||||||
|
|
||||||
def get_stats(self):
|
|
||||||
"""Get statistics about the pre-generated objects"""
|
|
||||||
song_count_by_dir = {}
|
|
||||||
for dir_path, items in self.directory_contents.items():
|
|
||||||
song_count_by_dir[dir_path] = len([item for item in items if isinstance(item, SongFile)])
|
|
||||||
|
|
||||||
return {
|
|
||||||
'total_directories': len(self.all_directories),
|
|
||||||
'total_songs': len(self.all_song_files),
|
|
||||||
'root_items': len(self.root_items),
|
|
||||||
'directories_with_content': len(self.directory_contents),
|
|
||||||
'songs_by_directory': song_count_by_dir
|
|
||||||
}
|
|
||||||
|
|||||||
27
shader/outline.vs
Normal file
27
shader/outline.vs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#version 330
|
||||||
|
|
||||||
|
// Input vertex attributes
|
||||||
|
in vec3 vertexPosition;
|
||||||
|
in vec2 vertexTexCoord;
|
||||||
|
in vec3 vertexNormal;
|
||||||
|
in vec4 vertexColor;
|
||||||
|
|
||||||
|
// Input uniform values
|
||||||
|
uniform mat4 mvp;
|
||||||
|
uniform mat4 matModel;
|
||||||
|
uniform mat4 matView;
|
||||||
|
uniform mat4 matProjection;
|
||||||
|
|
||||||
|
// Output vertex attributes (to fragment shader)
|
||||||
|
out vec2 fragTexCoord;
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// Calculate final vertex position
|
||||||
|
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||||
|
|
||||||
|
// Send vertex attributes to fragment shader
|
||||||
|
fragTexCoord = vertexTexCoord;
|
||||||
|
fragColor = vertexColor;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user