22 Commits
latest ... v1.1

Author SHA1 Message Date
Yonokid
327c48aa1a Update python-app.yml 2025-12-27 13:45:41 -05:00
Yonokid
3a18a507c0 Update pyproject.toml 2025-12-27 13:29:48 -05:00
Yonokid
2769503899 Update python-app.yml 2025-12-27 13:27:45 -05:00
Yonokid
e719119764 chore: organize imports 2025-12-27 13:26:24 -05:00
Yonokid
174f322696 made navigation smooth 2025-12-27 13:19:13 -05:00
Anthony Samms
33125c6322 Merge pull request #186 from magickale/gen3-scoring
updated leaderboard logic
2025-12-26 21:04:07 -05:00
Anthony Samms
e97bd5bd4c Merge pull request #185 from magickale/scrollwheel
Added the ability to use the scroll wheel in the song select
2025-12-26 21:03:34 -05:00
Valerio
88acfe5e5b Changed gen3 leaderboard logic to not need any new textures 2025-12-26 01:23:27 -05:00
Valerio
73abcddf44 Added logic so gen3 scores can be drawn with a border around them. 2025-12-26 01:13:22 -05:00
Valerio
7ca8ff8c38 Added the ability to use the scroll wheel in the song select 2025-12-26 00:27:27 -05:00
Anthony Samms
22778dbd3d Update texture.py 2025-12-24 23:54:43 -05:00
Anthony Samms
d70e734661 update skin 2025-12-24 23:48:06 -05:00
Anthony Samms
2c09360bfd add more english 2025-12-24 15:06:59 -05:00
Anthony Samms
58d7043a50 Update PyTaikoGreen 2025-12-24 11:05:37 -05:00
Anthony Samms
27c58cc97e Update utils.py 2025-12-24 11:05:24 -05:00
Anthony Samms
20c1f1141e Update python-app.yml 2025-12-23 23:00:44 -05:00
Anthony Samms
4844792aaa Update python-app.yml 2025-12-23 22:45:16 -05:00
Anthony Samms
201b37dda0 Update python-app.yml 2025-12-23 22:32:51 -05:00
Anthony Samms
77886d34cb Update python-app.yml 2025-12-23 22:21:37 -05:00
Anthony Samms
74080634de Update PyTaikoGreen 2025-12-23 21:19:45 -05:00
Anthony Samms
f7ab62ab1d update skin 2025-12-23 21:03:48 -05:00
Anthony Samms
5c7e759385 fix skin loading 2025-12-23 21:02:56 -05:00
45 changed files with 498 additions and 355 deletions

View File

@@ -1,9 +1,6 @@
name: PyTaiko
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
@@ -18,6 +15,8 @@ jobs:
steps:
- name: Check-out repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install libaudio Dependencies (macOS)
if: runner.os == 'macOS'
@@ -131,11 +130,28 @@ jobs:
fi
shell: bash
- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: pytaiko-${{ runner.os }}-${{ runner.arch }}
path: PyTaiko-${{ runner.os }}-${{ runner.arch }}.zip
retention-days: 1
release:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch'
steps:
- name: Download All Artifacts
uses: actions/download-artifact@v4
with:
pattern: pytaiko-*
merge-multiple: true
- name: Upload Release
uses: softprops/action-gh-release@v2
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
with:
files: PyTaiko-${{ runner.os }}-${{ runner.arch }}.zip
files: PyTaiko-*.zip
name: "PyTaiko [Rolling Release]"
tag_name: "latest"
make_latest: true

2
.gitignore vendored
View File

@@ -5,8 +5,8 @@ cache
dev-config.toml
libaudio.so
latest.log
libaudio.dll
libaudio.dylib
scores.db
scores_gen3.db
./libs/audio/audio.o
*.dll

View File

@@ -1,12 +1,12 @@
import argparse
import logging
import os
from pathlib import Path
import sys
import argparse
import sqlite3
import sys
from pathlib import Path
import pyray as ray
from pypresence.presence import Presence
from raylib.defines import (
RL_FUNC_ADD,
RL_ONE,
@@ -15,6 +15,7 @@ from raylib.defines import (
)
from libs.audio import audio
from libs.config import get_config
from libs.global_data import PlayerNum, ScoreMethod
from libs.screen import Screen
from libs.song_hash import DB_VERSION
@@ -23,27 +24,24 @@ from libs.utils import (
force_dedicated_gpu,
get_current_ms,
global_data,
global_tex
global_tex,
)
from libs.config import get_config
from scenes.dan.dan_result import DanResultScreen
from scenes.dan.dan_select import DanSelectScreen
from scenes.dan.game_dan import DanGameScreen
from scenes.devtest import DevScreen
from scenes.entry import EntryScreen
from scenes.game import GameScreen
from scenes.dan.game_dan import DanGameScreen
from scenes.loading import LoadScreen
from scenes.practice.game import PracticeGameScreen
from scenes.practice.song_select import PracticeSongSelectScreen
from scenes.two_player.game import TwoPlayerGameScreen
from scenes.two_player.result import TwoPlayerResultScreen
from scenes.loading import LoadScreen
from scenes.result import ResultScreen
from scenes.settings import SettingsScreen
from scenes.song_select import SongSelectScreen
from scenes.title import TitleScreen
from scenes.two_player.game import TwoPlayerGameScreen
from scenes.two_player.result import TwoPlayerResultScreen
from scenes.two_player.song_select import TwoPlayerSongSelectScreen
from scenes.dan.dan_select import DanSelectScreen
from scenes.dan.dan_result import DanResultScreen
from pypresence.presence import Presence
logger = logging.getLogger(__name__)
DISCORD_APP_ID = '1451423960401973353'

View File

@@ -29,9 +29,7 @@ rainbow = false
[paths]
tja_path = ['Songs']
video_path = ['Videos']
#You can change this path to Graphics/GreenVer1080 for the 1080p skin
graphics_path = 'Graphics/GreenVer'
skin = 'PyTaikoGreen'
[keys]
exit_key = 'Q'

View File

@@ -1,10 +1,10 @@
import cffi
import platform
import logging
import platform
from pathlib import Path
from libs.config import VolumeConfig
from libs.config import get_config
import cffi
from libs.config import VolumeConfig, get_config
ffi = cffi.FFI()
@@ -131,7 +131,7 @@ class AudioEngine:
self.audio_device_ready = False
self.volume_presets = volume_presets
self.sounds_path = Path("Sounds")
self.sounds_path = Path(f"Skins/{get_config()["paths"]["skin"]}/Sounds")
def set_log_level(self, level: int):
lib.set_log_level(level) # type: ignore

View File

@@ -1,4 +1,5 @@
import random
from libs.animation import Animation
from libs.bg_objects.bg_fever import BGFever4
from libs.bg_objects.bg_normal import BGNormal2
@@ -9,6 +10,7 @@ from libs.bg_objects.renda import RendaController
from libs.global_data import PlayerNum
from libs.texture import TextureWrapper
class Background:
def __init__(self, tex: TextureWrapper, player_num: PlayerNum, bpm: float, path: str, max_dancers: int):
self.tex_wrapper = tex

View File

@@ -1,14 +1,15 @@
import random
from libs.bg_objects.bg_fever import BGFeverBase
from libs.bg_objects.bg_normal import BGNormalBase
from libs.bg_objects.chibi import ChibiController
from libs.bg_objects.dancer import BaseDancer, BaseDancerGroup
from libs.bg_objects.don_bg import DonBG4
from libs.bg_objects.fever import Fever3
from libs.bg_objects.footer import Footer
from libs.bg_objects.renda import RendaController
from libs.global_data import PlayerNum
from libs.texture import TextureWrapper
from libs.bg_objects.don_bg import DonBG4
class Background:

View File

@@ -1,13 +1,14 @@
import random
from libs.bg_objects.bg_fever import BGFeverBase
from libs.bg_objects.bg_normal import BGNormalBase
from libs.bg_objects.chibi import ChibiController
from libs.bg_objects.dancer import BaseDancer, BaseDancerGroup
from libs.bg_objects.don_bg import DonBG4
from libs.bg_objects.fever import Fever3
from libs.bg_objects.renda import RendaController
from libs.global_data import PlayerNum
from libs.texture import TextureWrapper
from libs.bg_objects.don_bg import DonBG4
class Background:

View File

@@ -6,6 +6,7 @@ from libs.bg_objects.footer import Footer
from libs.global_data import PlayerNum
from libs.texture import TextureWrapper
class Background:
def __init__(self, tex: TextureWrapper, player_num: PlayerNum, bpm: float, path: str, max_dancers: int):
self.tex_wrapper = tex

View File

@@ -1,16 +1,16 @@
import pyray as ray
from libs.animation import Animation
from libs.bg_objects.bg_fever import BGFeverBase
from libs.bg_objects.bg_normal import BGNormalBase
from libs.bg_objects.chibi import ChibiController
from libs.bg_objects.dancer import BaseDancerGroup
from libs.bg_objects.don_bg import DonBGBase
from libs.bg_objects.fever import BaseFever
from libs.bg_objects.footer import Footer
from libs.bg_objects.renda import RendaController
from libs.global_data import PlayerNum
from libs.texture import TextureWrapper
from libs.bg_objects.don_bg import DonBGBase
import pyray as ray
class Background:

View File

@@ -4,11 +4,11 @@ from libs.bg_objects.bg_fever import BGFeverBase
from libs.bg_objects.bg_normal import BGNormalBase
from libs.bg_objects.chibi import ChibiController
from libs.bg_objects.dancer import BaseDancerGroup
from libs.bg_objects.don_bg import DonBGBase
from libs.bg_objects.footer import Footer
from libs.bg_objects.renda import RendaController
from libs.global_data import PlayerNum
from libs.texture import TextureWrapper
from libs.bg_objects.don_bg import DonBGBase
class Background:

View File

@@ -1,15 +1,16 @@
from libs.animation import Animation
from libs.bg_objects.fever import Fever3
from libs.bg_objects.bg_fever import BGFeverBase
from libs.bg_objects.bg_normal import BGNormalBase
from libs.bg_objects.chibi import ChibiController
from libs.bg_objects.dancer import BaseDancer, BaseDancerGroup
from libs.bg_objects.don_bg import DonBGBase
from libs.bg_objects.fever import Fever3
from libs.bg_objects.footer import Footer
from libs.bg_objects.renda import RendaController
from libs.global_data import PlayerNum
from libs.texture import TextureWrapper
class Background:
def __init__(self, tex: TextureWrapper, player_num: PlayerNum, bpm: float, path: str, max_dancers: int):
self.tex_wrapper = tex

View File

@@ -1,8 +1,10 @@
import random
import pyray as ray
from libs.animation import Animation
from libs.texture import TextureWrapper
import pyray as ray
class Chibi:

View File

@@ -3,6 +3,7 @@ import random
from libs.animation import Animation
from libs.texture import TextureWrapper
class Dancer:
@staticmethod

View File

@@ -1,6 +1,7 @@
from libs.animation import Animation
from libs.texture import TextureWrapper
class Fever:
@staticmethod

View File

@@ -1,5 +1,6 @@
from libs.texture import TextureWrapper
class Footer:
def __init__(self, tex: TextureWrapper, index: int, path: str = 'background'):
self.index = index

View File

@@ -1,8 +1,10 @@
import random
import pyray as ray
from libs.animation import Animation
from libs.texture import TextureWrapper
import pyray as ray
class Renda:

View File

@@ -1,4 +1,5 @@
import logging
from libs.animation import Animation
from libs.utils import global_tex

View File

@@ -1,9 +1,10 @@
from pathlib import Path
import tomlkit
import json
from pathlib import Path
from typing import TypedDict
import pyray as ray
import tomlkit
class GeneralConfig(TypedDict):
fps_counter: bool
@@ -30,8 +31,7 @@ class NameplateConfig(TypedDict):
class PathsConfig(TypedDict):
tja_path: list[Path]
video_path: list[Path]
graphics_path: Path
skin: Path
class KeysConfig(TypedDict):
exit_key: int

View File

@@ -1,21 +1,22 @@
from dataclasses import dataclass
from enum import IntEnum
import json
import logging
from pathlib import Path
import random
import sqlite3
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import IntEnum
from pathlib import Path
from typing import Optional, Union
from raylib import SHADER_UNIFORM_VEC3
from libs.audio import audio
from libs.animation import Animation, MoveAnimation
from libs.global_data import Crown, Difficulty, ScoreMethod
from libs.tja import TJAParser, test_encodings
from libs.texture import tex
from libs.utils import OutlinedText, get_current_ms, global_data
from datetime import datetime, timedelta
import sqlite3
import pyray as ray
from raylib import SHADER_UNIFORM_VEC3
from libs.animation import Animation, MoveAnimation
from libs.audio import audio
from libs.global_data import Crown, Difficulty, ScoreMethod
from libs.texture import tex
from libs.tja import TJAParser, test_encodings
from libs.utils import OutlinedText, get_current_ms, global_data
BOX_CENTER = 594 * tex.screen_scale
@@ -109,18 +110,22 @@ class BaseBox():
else:
self.fore_color = ray.Color(101, 0, 82, 255)
self.position = float('inf')
self.start_position = -1.0
self.target_position = -1.0
self.start_position = float('inf')
self.target_position = float('inf')
self.open_anim = Animation.create_move(233, total_distance=150*tex.screen_scale, delay=50)
self.open_fade = Animation.create_fade(200, initial_opacity=0, final_opacity=1.0)
self.move = None
self.move = Animation.create_move(133, total_distance=100 * tex.screen_scale, ease_out='cubic')
self.move.start()
self.shader = None
self.is_open = False
self.text_loaded = False
self.wait = 0
def load_text(self):
self.name = OutlinedText(self.text_name, tex.skin_config["song_box_name"].font_size, ray.WHITE, outline_thickness=5, vertical=True)
font_size = tex.skin_config["song_box_name"].font_size
if len(self.text_name) >= 30:
font_size -= int(10 * tex.screen_scale)
self.name = OutlinedText(self.text_name, font_size, ray.WHITE, outline_thickness=5, vertical=True)
if self.back_color is not None:
self.shader = ray.load_shader('shader/dummy.vs', 'shader/colortransform.fs')
source_rgb = (142, 212, 30)
@@ -132,30 +137,22 @@ class BaseBox():
ray.set_shader_value(self.shader, source_loc, source_color, SHADER_UNIFORM_VEC3)
ray.set_shader_value(self.shader, target_loc, target_color, SHADER_UNIFORM_VEC3)
def move_box(self, current_time: float):
if self.position != self.target_position and self.move is None:
if self.position < self.target_position:
direction = 1
else:
direction = -1
if abs(self.target_position - self.position) > 250 * tex.screen_scale:
direction *= -1
self.move = Animation.create_move(133, total_distance=100 * direction * tex.screen_scale, ease_out='cubic')
self.move.start()
if self.is_open or self.target_position == BOX_CENTER:
self.move.total_distance = int(250 * direction * tex.screen_scale)
def move_box(self, direction: int):
if self.position != self.target_position:
distance = abs(self.target_position - self.position)
self.move = Animation.create_move(133, total_distance=distance * tex.screen_scale * direction, ease_out='cubic')
self.start_position = self.position
if self.move is not None:
self.move.update(current_time)
self.position = self.start_position + int(self.move.attribute)
if self.move.is_finished:
self.position = self.target_position
self.move = None
self.move.start()
def update(self, current_time: float, is_diff_select: bool):
self.is_diff_select = is_diff_select
self.open_anim.update(current_time)
self.open_fade.update(current_time)
self.move.update(current_time)
if not self.move.is_finished:
self.position = self.start_position + int(self.move.attribute)
else:
self.position = self.target_position
def _draw_closed(self, x: float, y: float, outer_fade_override: float):
if self.shader is not None and self.texture_index == TextureIndex.BLANK:
@@ -192,7 +189,6 @@ class BackBox(BaseBox):
def update(self, current_time: float, is_diff_select: bool):
super().update(current_time, is_diff_select)
is_open_prev = self.is_open
self.move_box(current_time)
self.is_open = self.position == BOX_CENTER
if self.yellow_box is not None:
@@ -253,7 +249,6 @@ class SongBox(BaseBox):
def update(self, current_time: float, is_diff_select: bool):
super().update(current_time, is_diff_select)
is_open_prev = self.is_open
self.move_box(current_time)
self.is_open = self.position == BOX_CENTER
if self.yellow_box is not None:
@@ -324,7 +319,6 @@ class FolderBox(BaseBox):
def update(self, current_time: float, is_diff_select: bool):
super().update(current_time, is_diff_select)
is_open_prev = self.is_open
self.move_box(current_time)
self.is_open = self.position == BOX_CENTER
if not is_open_prev and self.is_open:
@@ -652,7 +646,6 @@ class DanBox(BaseBox):
def update(self, current_time: float, is_diff_select: bool):
super().update(current_time, is_diff_select)
is_open_prev = self.is_open
self.move_box(current_time)
self.is_open = self.position == BOX_CENTER
if not is_open_prev and self.is_open:
self.yellow_box = YellowBox(False, is_dan=True)
@@ -781,7 +774,8 @@ class GenreBG:
if self.shader is not None and self.end_box.texture_index == TextureIndex.BLANK:
ray.begin_shader_mode(self.shader)
offset = (tex.skin_config["genre_bg_offset"].x * -1) if self.start_box.is_open else 0
if (344 * tex.screen_scale < self.start_box.position < 594 * tex.screen_scale):
offset = -self.start_position + 444 * tex.screen_scale
tex.draw_texture('box', 'folder_background_edge', frame=self.end_box.texture_index, x=self.start_position+offset, y=y, mirror="horizontal", fade=self.fade_in.attribute)
@@ -803,6 +797,8 @@ class GenreBG:
tex.draw_texture('box', 'folder_background', x=tex.skin_config["genre_bg_folder_background"].x, y=y, x2=x2, frame=self.end_box.texture_index)
if (594 * tex.screen_scale < self.end_box.position < 844 * tex.screen_scale):
offset = -self.end_position + 674 * tex.screen_scale
offset = tex.skin_config["genre_bg_offset"].x if self.end_box.is_open else 0
tex.draw_texture('box', 'folder_background_edge', x=self.end_position+tex.skin_config["genre_bg_folder_edge"].x+offset, y=y, fade=self.fade_in.attribute, frame=self.end_box.texture_index)
@@ -861,13 +857,11 @@ class ScoreHistory:
tex.draw_texture('leaderboard', 'shinuchi_ura', index=self.long)
else:
tex.draw_texture('leaderboard', 'shinuchi', index=self.long)
case ScoreMethod.GEN3:
if self.curr_difficulty == Difficulty.URA:
tex.draw_texture('leaderboard', 'normal', index=self.long)
else:
tex.draw_texture('leaderboard', 'normal', index=self.long)
tex.draw_texture('leaderboard', 'pts', color=ray.WHITE, index=self.long)
case ScoreMethod.GEN3:
tex.draw_texture('leaderboard', 'normal', index=self.long)
tex.draw_texture('leaderboard', 'pts', color=ray.BLACK, index=self.long)
tex.draw_texture('leaderboard', 'difficulty', frame=self.curr_difficulty, index=self.long)
for i in range(4):
@@ -887,7 +881,11 @@ class ScoreHistory:
margin = tex.skin_config["score_info_counter_margin"].x
for i in range(len(counter)):
if j == 0:
match global_data.config["general"]["score_method"]:
case ScoreMethod.SHINUCHI:
tex.draw_texture('leaderboard', 'counter', frame=int(counter[i]), x=-((len(counter) * tex.skin_config["score_info_counter_margin"].width) // 2) + (i * tex.skin_config["score_info_counter_margin"].width), color=ray.WHITE, index=self.long)
case ScoreMethod.GEN3:
tex.draw_texture('leaderboard', 'counter', frame=int(counter[i]), x=-((len(counter) * tex.skin_config["score_info_counter_margin"].width) // 2) + (i * tex.skin_config["score_info_counter_margin"].width), color=ray.BLACK, index=self.long)
else:
tex.draw_texture('leaderboard', 'judge_num', frame=int(counter[i]), x=-(len(counter) - i) * margin, y=j*tex.skin_config["score_info_bg_offset"].y)
@@ -1346,6 +1344,54 @@ class FileNavigator:
"""Check if currently at the virtual root"""
return self.current_dir == Path()
def load_new_items(self, selected_item, dir_key: str):
return self.new_items
def load_recent_items(self, selected_item, dir_key: str):
if self.recent_folder is None:
raise Exception("tried to enter recent folder without recents")
self._generate_objects_recursive(self.recent_folder.path)
if not isinstance(selected_item.box, BackBox):
selected_item.box.tja_count = self._count_tja_files(self.recent_folder.path)
return self.directory_contents[dir_key]
def load_favorite_items(self, selected_item, dir_key: str):
if self.favorite_folder is None:
raise Exception("tried to enter favorite folder without favorites")
self._generate_objects_recursive(self.favorite_folder.path)
tja_files = self._get_tja_files_for_directory(self.favorite_folder.path)
self._calculate_directory_crowns(dir_key, tja_files)
if not isinstance(selected_item.box, BackBox):
selected_item.box.tja_count = self._count_tja_files(self.favorite_folder.path)
self.in_favorites = True
return self.directory_contents[dir_key]
def load_diff_sort_items(self, selected_item, dir_key: str):
content_items = []
parent_dir = selected_item.path.parent
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]:
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)
return content_items
def load_recommended_items(self, selected_item, dir_key: str):
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]:
if not isinstance(item, Directory) and isinstance(item, SongFile):
temp_items.append(item)
return random.sample(temp_items, min(10, len(temp_items)))
def load_current_directory(self, selected_item: Optional[Directory] = None):
"""Load pre-generated items for the current directory (unified for root and subdirs)"""
dir_key = str(self.current_dir)
@@ -1383,47 +1429,15 @@ class FileNavigator:
# Handle special collections (same logic as before)
if isinstance(selected_item, Directory):
if selected_item.collection == Directory.COLLECTIONS[0]:
content_items = self.new_items
content_items = self.load_new_items(selected_item, dir_key)
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)
if not isinstance(selected_item.box, BackBox):
selected_item.box.tja_count = self._count_tja_files(self.recent_folder.path)
content_items = self.directory_contents[dir_key]
content_items = self.load_recent_items(selected_item, 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)
tja_files = self._get_tja_files_for_directory(self.favorite_folder.path)
self._calculate_directory_crowns(dir_key, tja_files)
if not isinstance(selected_item.box, BackBox):
selected_item.box.tja_count = self._count_tja_files(self.favorite_folder.path)
content_items = self.directory_contents[dir_key]
self.in_favorites = True
content_items = self.load_favorite_items(selected_item, dir_key)
elif selected_item.collection == Directory.COLLECTIONS[3]:
content_items = []
parent_dir = selected_item.path.parent
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]:
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)
content_items = self.load_diff_sort_items(selected_item, dir_key)
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]:
if not isinstance(item, Directory) and isinstance(item, SongFile):
temp_items.append(item)
content_items = random.sample(temp_items, min(10, len(temp_items)))
content_items = self.load_recommended_items(selected_item, dir_key)
if content_items == []:
self.go_back()
@@ -1494,7 +1508,7 @@ class FileNavigator:
# Save current state to history
self.history.append((self.current_dir, self.selected_index))
self.current_dir = selected_item.path
logger.info(f"Entered Directory {selected_item.path}")
logger.info(f"Entered Directory {selected_item.path} at index {self.selected_index}")
self.load_current_directory(selected_item=selected_item)
@@ -1744,19 +1758,19 @@ class FileNavigator:
def navigate_left(self):
"""Move selection left with wrap-around"""
if self.items:
if self.items[0].box.move is not None and not self.items[0].box.move.is_finished:
return
self.selected_index = (self.selected_index - 1) % len(self.items)
self.calculate_box_positions()
for item in self.items:
item.box.move_box(1)
logger.info(f"Moved Left to {self.items[self.selected_index].path}")
def navigate_right(self):
"""Move selection right with wrap-around"""
if self.items:
if self.items[0].box.move is not None and not self.items[0].box.move.is_finished:
return
self.selected_index = (self.selected_index + 1) % len(self.items)
self.calculate_box_positions()
for item in self.items:
item.box.move_box(-1)
logger.info(f"Moved Right to {self.items[self.selected_index].path}")
def skip_left(self):

View File

@@ -7,6 +7,7 @@ import pyray as ray
from libs.config import Config
class PlayerNum(IntEnum):
ALL = 0
P1 = 1

View File

@@ -1,11 +1,12 @@
from enum import Enum
from typing import Callable
import pyray as ray
from libs.global_data import PlayerNum
from libs.utils import OutlinedText, global_tex
from libs.config import get_config
from libs.audio import audio
from libs.config import get_config
from libs.global_data import PlayerNum, global_data
from libs.utils import OutlinedText, global_tex
class Nameplate:
@@ -98,6 +99,7 @@ class Indicator:
self.don_fade = global_tex.get_animation(6)
self.blue_arrow_move = global_tex.get_animation(7)
self.blue_arrow_fade = global_tex.get_animation(8)
self.select_text = OutlinedText(global_tex.skin_config["indicator_text"].text[global_data.config["general"]["language"]], global_tex.skin_config["indicator_text"].font_size, ray.WHITE, spacing=-3)
def update(self, current_time_ms: float):
"""Update the indicator's animations."""
@@ -109,7 +111,8 @@ class Indicator:
"""Draw the indicator at the given position with the given fade."""
tex = global_tex
tex.draw_texture('indicator', 'background', x=x, y=y, fade=fade)
tex.draw_texture('indicator', 'text', frame=self.state.value, x=x, y=y, fade=fade)
tex.draw_texture('indicator', 'text', frame=self.state.value, x=x, y=y, fade=fade, color=ray.BLACK)
self.select_text.draw(ray.BLANK, x=x+global_tex.skin_config["indicator_text"].x, y=y, fade=fade)
tex.draw_texture('indicator', 'drum_face', index=self.state.value, x=x, y=y, fade=fade)
if self.state == Indicator.State.SELECT:
tex.draw_texture('indicator', 'drum_kat', fade=min(fade, self.don_fade.attribute), x=x, y=y)
@@ -127,15 +130,14 @@ class CoinOverlay:
"""Coin overlay for the game."""
def __init__(self):
"""Initialize the coin overlay."""
pass
self.free_play = OutlinedText(global_tex.skin_config["free_play"].text[global_data.config["general"]["language"]], global_tex.skin_config["free_play"].font_size, ray.WHITE, spacing=5, outline_thickness=4)
def update(self, current_time_ms: float):
"""Update the coin overlay. Unimplemented"""
pass
def draw(self, x: int = 0, y: int = 0):
"""Draw the coin overlay.
Only draws free play for now."""
tex = global_tex
tex.draw_texture('overlay', 'free_play', x=x, y=y)
self.free_play.draw(ray.BLACK, x=global_tex.screen_width//2 - self.free_play.texture.width//2, y=global_tex.skin_config["free_play"].y)
class AllNetIcon:
"""All.Net status icon for the game."""

View File

@@ -1,5 +1,6 @@
import logging
from typing import Any
from libs.audio import audio
from libs.texture import tex

View File

@@ -1,15 +1,15 @@
import configparser
import logging
import csv
import json
import logging
import sqlite3
import time
import csv
from pathlib import Path
from libs.config import get_config
from libs.global_data import Crown
from libs.tja import NoteList, TJAParser, test_encodings
from libs.utils import global_data
from libs.config import get_config
logger = logging.getLogger(__name__)
DB_VERSION = 1

View File

@@ -1,28 +1,26 @@
import copy
import json
import os
import logging
import sys
import tempfile
from pathlib import Path
from typing import Any, Optional
import raylib as ray
from pyray import Vector2, Rectangle, Color
from pyray import Color, Rectangle, Vector2
from libs.animation import BaseAnimation, parse_animations
from libs.config import get_config
logger = logging.getLogger(__name__)
class SkinInfo:
def __init__(self, x: float, y: float, font_size: int, width: float, height: float):
def __init__(self, x: float, y: float, font_size: int, width: float, height: float, text: dict[str, str]):
self.x = x
self.y = y
self.width = width
self.height = height
self.font_size = font_size
self.text = text
def __repr__(self):
return f"{self.__dict__}"
@@ -73,24 +71,24 @@ class TextureWrapper:
self.textures: dict[str, dict[str, Texture | FramedTexture]] = dict()
self.animations: dict[int, BaseAnimation] = dict()
self.skin_config: dict[str, SkinInfo] = dict()
self.graphics_path = Path(get_config()['paths']['graphics_path'])
self.parent_graphics_path = Path(get_config()['paths']['graphics_path'])
self.graphics_path = Path(f'Skins/{get_config()['paths']['skin']}/Graphics')
self.parent_graphics_path = Path(f'Skins/{get_config()['paths']['skin']}/Graphics')
if not (self.graphics_path / "skin_config.json").exists():
raise Exception("skin is missing a skin_config.json")
data = json.loads((self.graphics_path / "skin_config.json").read_text())
data = json.loads((self.graphics_path / "skin_config.json").read_text(encoding='utf-8'))
self.skin_config: dict[str, SkinInfo] = {
k: SkinInfo(v.get('x', 0), v.get('y', 0), v.get('font_size', 0), v.get('width', 0), v.get('height', 0)) for k, v in data.items()
k: SkinInfo(v.get('x', 0), v.get('y', 0), v.get('font_size', 0), v.get('width', 0), v.get('height', 0), v.get('text', dict())) for k, v in data.items()
}
self.screen_width = int(self.skin_config["screen"].width)
self.screen_height = int(self.skin_config["screen"].height)
self.screen_scale = self.screen_width / 1280
if "parent" in data["screen"]:
parent = data["screen"]["parent"]
self.parent_graphics_path = Path("Graphics") / parent
parent_data = json.loads((self.parent_graphics_path / "skin_config.json").read_text())
self.parent_graphics_path = Path("Skins") / parent
parent_data = json.loads((self.parent_graphics_path / "skin_config.json").read_text(encoding='utf-8'))
for k, v in parent_data.items():
self.skin_config[k] = SkinInfo(v.get('x', 0) * self.screen_scale, v.get('y', 0) * self.screen_scale, v.get('font_size', 0) * self.screen_scale, v.get('width', 0) * self.screen_scale, v.get('height', 0) * self.screen_scale)
self.skin_config[k] = SkinInfo(v.get('x', 0) * self.screen_scale, v.get('y', 0) * self.screen_scale, v.get('font_size', 0) * self.screen_scale, v.get('width', 0) * self.screen_scale, v.get('height', 0) * self.screen_scale, v.get('text', dict()))
def unload_textures(self):
"""Unload all textures and animations."""
@@ -189,7 +187,7 @@ class TextureWrapper:
if screen_name in self.textures and subset in self.textures[screen_name]:
return
try:
if not os.path.isfile(folder / 'texture.json'):
if not (folder / 'texture.json').exists():
raise Exception(f"texture.json file missing from {folder}")
with open(folder / 'texture.json') as json_file:
@@ -205,7 +203,7 @@ class TextureWrapper:
if tex_dir.is_dir():
frames = [ray.LoadTexture(str(frame).encode(encoding)) for frame in sorted(tex_dir.iterdir(),
key=lambda x: int(x.stem)) if frame.is_file()]
self.textures[folder.stem][tex_name] = Texture(tex_name, frames, tex_mapping)
self.textures[folder.stem][tex_name] = FramedTexture(tex_name, frames, tex_mapping)
self._read_tex_obj_data(tex_mapping, self.textures[folder.stem][tex_name])
elif tex_file.is_file():
tex = ray.LoadTexture(str(tex_file).encode(encoding))
@@ -230,7 +228,7 @@ class TextureWrapper:
# Load zip files from child screen path only
for zip_file in screen_path.iterdir():
if zip_file.is_file() and zip_file.suffix == ".zip":
if zip_file.is_dir():
self.load_zip(screen_name, zip_file.stem)
logger.info(f"Screen textures loaded for: {screen_name}")

View File

@@ -1,10 +1,10 @@
from enum import IntEnum
import hashlib
import math
import logging
import math
import random
from collections import deque
from dataclasses import dataclass, field, fields
from enum import IntEnum
from functools import lru_cache
from pathlib import Path
from typing import Optional

View File

@@ -1,3 +1,4 @@
import string
import ctypes
import hashlib
import sys
@@ -135,7 +136,7 @@ for file in Path('cache/image').iterdir():
class OutlinedText:
"""Create an outlined text object."""
def __init__(self, text: str, font_size: int, color: ray.Color, outline_thickness=5.0, vertical=False):
def __init__(self, text: str, font_size: int, color: ray.Color, outline_thickness=5.0, vertical=False, spacing=1):
"""
Create an outlined text object.
@@ -158,7 +159,7 @@ class OutlinedText:
if vertical:
self.texture = self._create_text_vertical(text, font_size, color, ray.BLANK, self.font)
else:
self.texture = self._create_text_horizontal(text, font_size, color, ray.BLANK, self.font)
self.texture = self._create_text_horizontal(text, font_size, color, ray.BLANK, self.font, spacing=spacing)
ray.gen_texture_mipmaps(self.texture)
ray.set_texture_filter(self.texture, ray.TextureFilter.TEXTURE_FILTER_TRILINEAR)
outline_size = ray.ffi.new('float*', self.outline_thickness)
@@ -200,7 +201,7 @@ class OutlinedText:
if reload_font:
codepoint_count = ray.ffi.new('int *', 0)
codepoints = ray.load_codepoints(''.join(global_data.font_codepoints), codepoint_count)
global_data.font = ray.load_font_ex(str(Path('Graphics/Modified-DFPKanteiryu-XB.ttf')), 40, codepoints, len(global_data.font_codepoints))
global_data.font = ray.load_font_ex(str(Path(f'Skins/{global_data.config["paths"]["skin"]}/Graphics/Modified-DFPKanteiryu-XB.ttf')), 40, codepoints, len(global_data.font_codepoints))
logger.info(f"Reloaded font with {len(global_data.font_codepoints)} codepoints")
return global_data.font
@@ -358,19 +359,25 @@ class OutlinedText:
ray.unload_image(image)
return texture
def _create_text_horizontal(self, text: str, font_size: int, color: ray.Color, bg_color: ray.Color, font: Optional[ray.Font]=None, padding: int=10):
def _create_text_horizontal(self, text: str, font_size: int, color: ray.Color, bg_color: ray.Color, font: Optional[ray.Font]=None, padding: int=10, spacing: int=1):
if font:
text_size = ray.measure_text_ex(font, text, font_size, 0)
text_size = ray.measure_text_ex(font, text, font_size, spacing)
for char in text:
if char in string.whitespace:
text_size.x += 2
total_width = text_size.x + (padding * 2)
total_height = text_size.y + (padding * 2)
else:
total_width = ray.measure_text(text, font_size) + (padding * 2)
total_height = font_size + (padding * 2)
image = ray.gen_image_color(int(total_width), int(total_height), bg_color)
if font:
text_image = ray.image_text_ex(font, text, font_size, 0, color)
text_image = ray.image_text_ex(font, text, font_size, spacing, color)
else:
text_image = ray.image_text(text, font_size, color)
text_x = padding
text_y = padding
ray.image_draw(image, text_image,
@@ -378,7 +385,6 @@ class OutlinedText:
ray.Rectangle(text_x, text_y, text_image.width, text_image.height),
ray.WHITE)
ray.unload_image(text_image)
ray.export_image(image, f'cache/image/{self.hash}.png')
texture = ray.load_texture_from_image(image)
ray.unload_image(image)

View File

@@ -1,12 +1,12 @@
from pathlib import Path
import logging
from pathlib import Path
import raylib as ray
import av
import raylib as ray
from libs.audio import audio
from libs.utils import get_current_ms
from libs.texture import tex
from libs.utils import get_current_ms
logger = logging.getLogger(__name__)

View File

@@ -1,6 +1,6 @@
[project]
name = "pytaiko"
version = "1.0"
version = "1.1"
description = "Taiko no Tatsujin simulator written in python and raylib"
readme = "README.md"
requires-python = ">=3.13"

View File

@@ -1,10 +1,11 @@
import logging
import pyray as ray
from libs.animation import Animation
from libs.audio import audio
from libs.chara_2d import Chara2D
from libs.global_data import PlayerNum, reset_session
from libs.audio import audio
from libs.global_objects import AllNetIcon, CoinOverlay, Nameplate
from libs.screen import Screen
from libs.texture import tex
@@ -13,7 +14,7 @@ from libs.utils import (
get_current_ms,
global_data,
is_l_don_pressed,
is_r_don_pressed
is_r_don_pressed,
)
from scenes.game import Gauge
from scenes.result import Background

View File

@@ -1,16 +1,28 @@
import logging
import pyray as ray
from libs.audio import audio
from libs.global_data import PlayerNum, global_data
from libs.texture import tex
from libs.chara_2d import Chara2D
from libs.global_objects import AllNetIcon, CoinOverlay, Indicator, Nameplate, Timer
from libs.screen import Screen
from libs.file_navigator import BackBox, DanCourse, navigator
from libs.global_data import PlayerNum, global_data
from libs.global_objects import (
AllNetIcon,
CoinOverlay,
Indicator,
Nameplate,
Timer,
)
from libs.screen import Screen
from libs.texture import tex
from libs.transition import Transition
from libs.utils import get_current_ms, is_l_don_pressed, is_l_kat_pressed, is_r_don_pressed, is_r_kat_pressed
from libs.utils import (
get_current_ms,
is_l_don_pressed,
is_l_kat_pressed,
is_r_don_pressed,
is_r_kat_pressed,
)
from scenes.song_select import State
logger = logging.getLogger(__name__)

View File

@@ -1,18 +1,33 @@
import copy
from typing import Optional, override
import pyray as ray
import logging
from typing import Optional, override
import pyray as ray
from libs.animation import Animation
from libs.audio import audio
from libs.background import Background
from libs.file_navigator import Exam
from libs.global_data import DanResultExam, DanResultSong, PlayerNum, global_data
from libs.global_data import (
DanResultExam,
DanResultSong,
PlayerNum,
global_data,
)
from libs.global_objects import AllNetIcon
from libs.texture import tex
from libs.tja import TJAParser
from libs.transition import Transition
from libs.utils import OutlinedText, get_current_ms
from libs.texture import tex
from scenes.game import ClearAnimation, FCAnimation, FailAnimation, GameScreen, Gauge, ResultTransition, SongInfo
from scenes.game import (
ClearAnimation,
FailAnimation,
FCAnimation,
GameScreen,
Gauge,
ResultTransition,
SongInfo,
)
logger = logging.getLogger(__name__)

View File

@@ -1,13 +1,21 @@
import logging
from typing import Optional
import pyray as ray
from libs.audio import audio
from libs.chara_2d import Chara2D
from libs.global_data import PlayerNum
from libs.global_objects import AllNetIcon, CoinOverlay, Nameplate, Indicator, EntryOverlay, Timer
from libs.texture import tex
from libs.global_objects import (
AllNetIcon,
CoinOverlay,
EntryOverlay,
Indicator,
Nameplate,
Timer,
)
from libs.screen import Screen
from libs.texture import tex
from libs.utils import (
OutlinedText,
get_current_ms,
@@ -439,9 +447,9 @@ class BoxManager:
"""BoxManager class for the entry screen"""
def __init__(self):
self.box_titles: list[OutlinedText] = [
OutlinedText('演奏ゲーム', tex.skin_config["entry_box_text"].font_size, ray.WHITE, outline_thickness=5, vertical=True),
OutlinedText('特訓モード', tex.skin_config["entry_box_text"].font_size, ray.WHITE, outline_thickness=5, vertical=True),
OutlinedText('ゲーム設定', tex.skin_config["entry_box_text"].font_size, ray.WHITE, outline_thickness=5, vertical=True)]
OutlinedText(tex.skin_config["entry_game"].text[global_data.config["general"]["language"]], tex.skin_config["entry_box_text"].font_size, ray.WHITE, outline_thickness=5, vertical=True),
OutlinedText(tex.skin_config["entry_practice"].text[global_data.config["general"]["language"]], tex.skin_config["entry_box_text"].font_size, ray.WHITE, outline_thickness=5, vertical=True),
OutlinedText(tex.skin_config["entry_settings"].text[global_data.config["general"]["language"]], tex.skin_config["entry_box_text"].font_size, ray.WHITE, outline_thickness=5, vertical=True)]
self.box_locations = ["SONG_SELECT", "PRACTICE_SELECT", "SETTINGS"]
self.num_boxes = len(self.box_titles)
self.boxes = [Box(self.box_titles[i], self.box_locations[i]) for i in range(len(self.box_titles))]

View File

@@ -1,12 +1,12 @@
import bisect
from enum import IntEnum
import math
import logging
import math
import sqlite3
from collections import deque
from enum import IntEnum
from itertools import chain
from pathlib import Path
from typing import Optional
from itertools import chain
import pyray as ray
@@ -14,7 +14,13 @@ from libs.animation import Animation
from libs.audio import audio
from libs.background import Background
from libs.chara_2d import Chara2D
from libs.global_data import Crown, Difficulty, Modifiers, PlayerNum, ScoreMethod
from libs.global_data import (
Crown,
Difficulty,
Modifiers,
PlayerNum,
ScoreMethod,
)
from libs.global_objects import AllNetIcon, Nameplate
from libs.screen import Screen
from libs.texture import tex
@@ -24,8 +30,8 @@ from libs.tja import (
Note,
NoteList,
NoteType,
TJAParser,
TimelineObject,
TJAParser,
apply_modifiers,
calculate_base_score,
)
@@ -116,7 +122,7 @@ class GameScreen(Screen):
def load_hitsounds(self):
"""Load the hit sounds"""
sounds_dir = Path("Sounds")
sounds_dir = Path(f"Skins/{global_data.config["paths"]["skin"]}/Sounds")
if global_data.hit_sound == -1:
audio.load_sound(Path('none.wav'), 'hitsound_don_1p')
audio.load_sound(Path('none.wav'), 'hitsound_kat_1p')
@@ -448,7 +454,7 @@ class Player:
self.get_load_time(note)
if note.type == NoteType.TAIL:
note.load_ms = last_note.load_ms
note.unload_ms = last_note.unload_ms
last_note.unload_ms = note.unload_ms
last_note = note
self.draw_note_list = deque(sorted(self.draw_note_list, key=lambda n: n.load_ms))

View File

@@ -1,17 +1,16 @@
import logging
from pathlib import Path
import threading
from pathlib import Path
import pyray as ray
from libs.animation import Animation
from libs.file_navigator import navigator
from libs.global_objects import AllNetIcon
from libs.screen import Screen
from libs.song_hash import build_song_hashes
from libs.texture import tex
from libs.utils import get_current_ms, global_data
from libs.file_navigator import navigator
logger = logging.getLogger(__name__)
@@ -58,7 +57,7 @@ class LoadScreen(Screen):
global_data.font_codepoints.add(character)
codepoint_count = ray.ffi.new('int *', 0)
codepoints = ray.load_codepoints(''.join(global_data.font_codepoints), codepoint_count)
global_data.font = ray.load_font_ex(str(Path('Graphics/Modified-DFPKanteiryu-XB.ttf')), 40, codepoints, len(global_data.font_codepoints))
global_data.font = ray.load_font_ex(str(Path(f'Skins/{global_data.config["paths"]["skin"]}/Graphics/Modified-DFPKanteiryu-XB.ttf')), 40, codepoints, len(global_data.font_codepoints))
def _load_navigator(self):
"""Background thread function to load navigator"""

View File

@@ -1,20 +1,41 @@
import copy
import logging
import math
from collections import deque
import logging
from pathlib import Path
from typing import Optional
import pyray as ray
import copy
from libs.animation import Animation
from libs.audio import audio
from libs.background import Background
from libs.global_data import Modifiers, PlayerNum, global_data
from libs.tja import Balloon, Drumroll, NoteType, TJAParser, TimelineObject, apply_modifiers
from libs.utils import get_current_ms, is_l_don_pressed, is_l_kat_pressed, is_r_don_pressed, is_r_kat_pressed
from libs.texture import tex
from scenes.game import DrumHitEffect, DrumType, GameScreen, JudgeCounter, LaneHitEffect, Player, Side
from libs.tja import (
Balloon,
Drumroll,
NoteType,
TimelineObject,
TJAParser,
apply_modifiers,
)
from libs.utils import (
get_current_ms,
is_l_don_pressed,
is_l_kat_pressed,
is_r_don_pressed,
is_r_kat_pressed,
)
from scenes.game import (
DrumHitEffect,
DrumType,
GameScreen,
JudgeCounter,
LaneHitEffect,
Player,
Side,
)
logger = logging.getLogger(__name__)

View File

@@ -1,9 +1,10 @@
import logging
import pyray as ray
from libs.global_data import Difficulty, PlayerNum, reset_session
from libs.audio import audio
from libs.chara_2d import Chara2D
from libs.global_data import Difficulty, PlayerNum, reset_session
from libs.global_objects import AllNetIcon, CoinOverlay, Nameplate
from libs.screen import Screen
from libs.texture import tex
@@ -12,7 +13,7 @@ from libs.utils import (
get_current_ms,
global_data,
is_l_don_pressed,
is_r_don_pressed
is_r_don_pressed,
)
from scenes.game import ScoreMethod

View File

@@ -1,7 +1,9 @@
import logging
import pyray as ray
from libs.audio import audio
from libs.config import save_config
from libs.screen import Screen
from libs.texture import tex
from libs.utils import (
@@ -11,7 +13,6 @@ from libs.utils import (
is_r_don_pressed,
is_r_kat_pressed,
)
from libs.config import save_config
logger = logging.getLogger(__name__)

View File

@@ -1,18 +1,31 @@
import logging
import random
from dataclasses import fields
from pathlib import Path
import pyray as ray
import logging
from raylib import SHADER_UNIFORM_VEC3
from libs.file_navigator import DEFAULT_COLORS, BackBox, DanCourse, GenreIndex, navigator
from libs.audio import audio
from libs.chara_2d import Chara2D
from libs.file_navigator import Directory, SongBox, SongFile
from libs.file_navigator import (
DEFAULT_COLORS,
BackBox,
DanCourse,
Directory,
GenreIndex,
SongBox,
SongFile,
navigator,
)
from libs.global_data import Difficulty, Modifiers, PlayerNum
from libs.global_objects import AllNetIcon, CoinOverlay, Nameplate, Indicator, Timer
from libs.global_objects import (
AllNetIcon,
CoinOverlay,
Indicator,
Nameplate,
Timer,
)
from libs.screen import Screen
from libs.texture import tex
from libs.transition import Transition
@@ -69,6 +82,8 @@ class SongSelectScreen(Screen):
self.dan_transition = DanTransition()
self.shader = ray.load_shader('shader/dummy.vs', 'shader/colortransform.fs')
self.color = None
song_format = tex.skin_config["song_num"].text[global_data.config["general"]["language"]]
self.song_num = OutlinedText(song_format.format(global_data.songs_played+1), tex.skin_config["song_num"].font_size, ray.WHITE)
self.load_shader_values(self.color)
session_data = global_data.session_data[global_data.player_num]
@@ -387,8 +402,8 @@ class SongSelectScreen(Screen):
self.indicator.draw(tex.skin_config['song_select_indicator'].x, tex.skin_config['song_select_indicator'].y)
tex.draw_texture('global', 'song_num_bg', fade=0.75)
tex.draw_texture('global', 'song_num', frame=global_data.songs_played % 4)
tex.draw_texture('global', 'song_num_bg', fade=0.75, x=-(self.song_num.texture.width-127), x2=(self.song_num.texture.width-127))
self.song_num.draw(ray.BLACK, x=tex.skin_config["song_num"].x-self.song_num.texture.width, y=tex.skin_config["song_num"].y)
if self.state == State.BROWSING or self.state == State.DIFF_SORTING:
self.timer_browsing.draw()
elif self.state == State.SONG_SELECTED:
@@ -478,13 +493,14 @@ class SongSelectPlayer:
audio.play_sound('skip', 'sound')
return "skip_right"
wheel = ray.get_mouse_wheel_move()
# Navigate left
if is_l_kat_pressed(self.player_num):
if is_l_kat_pressed(self.player_num) or wheel > 0:
audio.play_sound('kat', 'sound')
return "navigate_left"
# Navigate right
if is_r_kat_pressed(self.player_num):
if is_r_kat_pressed(self.player_num) or wheel < 0:
audio.play_sound('kat', 'sound')
return "navigate_right"

View File

@@ -2,10 +2,14 @@ import logging
import random
from pathlib import Path
import pyray as ray
from libs.audio import audio
from libs.global_objects import AllNetIcon, CoinOverlay, EntryOverlay
from libs.screen import Screen
from libs.texture import tex
from libs.utils import (
OutlinedText,
get_current_ms,
global_data,
global_tex,
@@ -13,7 +17,6 @@ from libs.utils import (
is_r_don_pressed,
)
from libs.video import VideoPlayer
from libs.screen import Screen
logger = logging.getLogger(__name__)
@@ -25,19 +28,11 @@ class State:
class TitleScreen(Screen):
def __init__(self, name: str):
super().__init__(name)
#normalize to accept both stings and lists in toml
#maybe normalize centrally? but it's used only here
vp = global_data.config["paths"]["video_path"]
video_paths = [vp] if isinstance(vp, str) else vp
self.op_video_list = []
self.attract_video_list = []
for base in video_paths:
base = Path(base)
base = Path(f"Skins/{global_data.config["paths"]["skin"]}/Videos")
self.op_video_list += list((base/"op_videos").glob("**/*.mp4"))
self.attract_video_list += list((base/"attract_videos").glob("**/*.mp4"))
self.coin_overlay = CoinOverlay()
self.allnet_indicator = AllNetIcon()
self.entry_overlay = EntryOverlay()
def on_screen_start(self):
super().on_screen_start()
@@ -45,6 +40,10 @@ class TitleScreen(Screen):
self.op_video = None
self.attract_video = None
self.warning_board = None
self.coin_overlay = CoinOverlay()
self.allnet_indicator = AllNetIcon()
self.entry_overlay = EntryOverlay()
self.hit_taiko_text = OutlinedText(global_tex.skin_config["hit_taiko_to_start"].text[global_data.config["general"]["language"]], tex.skin_config["hit_taiko_to_start"].font_size, ray.WHITE, spacing=5)
self.fade_out = tex.get_animation(13)
self.text_overlay_fade = tex.get_animation(14)
@@ -121,8 +120,8 @@ class TitleScreen(Screen):
self.allnet_indicator.draw()
self.entry_overlay.draw(tex.skin_config["entry_overlay_title"].x, y=tex.skin_config["entry_overlay_title"].y)
global_tex.draw_texture('overlay', 'hit_taiko_to_start', index=0, fade=self.text_overlay_fade.attribute)
global_tex.draw_texture('overlay', 'hit_taiko_to_start', index=1, fade=self.text_overlay_fade.attribute)
self.hit_taiko_text.draw(ray.BLACK, x=tex.screen_width*0.25 - self.hit_taiko_text.texture.width//2, y=tex.skin_config["hit_taiko_to_start"].y, fade=self.text_overlay_fade.attribute)
self.hit_taiko_text.draw(ray.BLACK, x=tex.screen_width*0.75 - self.hit_taiko_text.texture.width//2, y=tex.skin_config["hit_taiko_to_start"].y, fade=self.text_overlay_fade.attribute)
class WarningScreen:
"""Warning screen for the game"""

View File

@@ -1,14 +1,23 @@
import logging
import copy
import logging
from pathlib import Path
import pyray as ray
from libs.audio import audio
from libs.global_data import PlayerNum
from libs.tja import TJAParser
from libs.utils import get_current_ms
from libs.audio import audio
from libs.utils import global_data
from libs.utils import get_current_ms, global_data
from libs.video import VideoPlayer
import pyray as ray
from scenes.game import ClearAnimation, FCAnimation, FailAnimation, GameScreen, Player, Background, ResultTransition
from scenes.game import (
Background,
ClearAnimation,
FailAnimation,
FCAnimation,
GameScreen,
Player,
ResultTransition,
)
logger = logging.getLogger(__name__)
@@ -23,7 +32,7 @@ class TwoPlayerGameScreen(GameScreen):
def load_hitsounds(self):
"""Load the hit sounds"""
sounds_dir = Path("Sounds")
sounds_dir = Path(f"Skins/{global_data.config["paths"]["skin"]}/Sounds")
# Load hitsounds for 1P
if global_data.hit_sound[PlayerNum.P1] == -1:

View File

@@ -1,4 +1,5 @@
import logging
from libs.global_data import PlayerNum
from libs.texture import tex
from libs.utils import get_current_ms

View File

@@ -1,10 +1,16 @@
import logging
from libs.audio import audio
from libs.file_navigator import SongBox, SongFile
from libs.global_data import PlayerNum
from libs.transition import Transition
from scenes.song_select import DiffSortSelect, SongSelectPlayer, SongSelectScreen, State
from libs.utils import get_current_ms, global_data
from libs.audio import audio
from scenes.song_select import (
DiffSortSelect,
SongSelectPlayer,
SongSelectScreen,
State,
)
logger = logging.getLogger(__name__)