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,143 +1,159 @@
name: PyTaiko name: PyTaiko
on: on:
push: workflow_dispatch:
branches: ["main"]
pull_request:
branches: ["main"]
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
issues: write issues: write
repository-projects: write repository-projects: write
jobs: jobs:
build: build:
strategy: strategy:
matrix: matrix:
os: [ubuntu-22.04, windows-latest] os: [ubuntu-22.04, windows-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Check-out repository - name: Check-out repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
submodules: recursive
- name: Install libaudio Dependencies (macOS) - name: Install libaudio Dependencies (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
run: | run: |
brew update brew update
brew install portaudio libsndfile speexdsp ccache brew install portaudio libsndfile speexdsp ccache
- name: Install libaudio Dependencies (Windows) - name: Install libaudio Dependencies (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
with: with:
update: true update: true
install: >- install: >-
base-devel base-devel
mingw-w64-x86_64-gcc mingw-w64-x86_64-gcc
mingw-w64-x86_64-libsndfile mingw-w64-x86_64-libsndfile
mingw-w64-x86_64-speexdsp mingw-w64-x86_64-speexdsp
mingw-w64-x86_64-ccache mingw-w64-x86_64-ccache
- name: Install libaudio Dependencies (Linux) - name: Install libaudio Dependencies (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y \ sudo apt-get install -y \
build-essential \ build-essential \
pkg-config \ pkg-config \
libsndfile1-dev \ libsndfile1-dev \
libspeexdsp-dev \ libspeexdsp-dev \
portaudio19-dev \ portaudio19-dev \
libpulse-dev \ libpulse-dev \
ccache \ ccache \
- name: Build libaudio (Windows) - name: Build libaudio (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
shell: msys2 {0} shell: msys2 {0}
run: | run: |
cd libs/audio cd libs/audio
make clean make clean
make all make all
make verify make verify
- name: Build libaudio (Unix) - name: Build libaudio (Unix)
if: runner.os != 'Windows' if: runner.os != 'Windows'
shell: bash shell: bash
run: | run: |
cd libs/audio cd libs/audio
make clean make clean
make all make all
make verify make verify
- name: Upload libaudio Artifacts - name: Upload libaudio Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: libaudio-${{ runner.os }}-${{ runner.arch }} name: libaudio-${{ runner.os }}-${{ runner.arch }}
path: | path: |
libs/audio/libaudio.dll libs/audio/libaudio.dll
libs/audio/libaudio.so libs/audio/libaudio.so
libs/audio/libaudio.dylib libs/audio/libaudio.dylib
libs/audio/*.a libs/audio/*.a
if-no-files-found: ignore if-no-files-found: ignore
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@v4 uses: astral-sh/setup-uv@v4
- name: Setup Python - name: Setup Python
run: uv python install run: uv python install
- name: Build Executable - name: Build Executable
shell: bash shell: bash
run: | run: |
uv run nuitka \ uv run nuitka \
--lto=yes \ --lto=yes \
--mode=app \ --mode=app \
--noinclude-setuptools-mode=nofollow \ --noinclude-setuptools-mode=nofollow \
--noinclude-IPython-mode=nofollow \ --noinclude-IPython-mode=nofollow \
--assume-yes-for-downloads \ --assume-yes-for-downloads \
--windows-icon-from-ico=libs/icon.ico \ --windows-icon-from-ico=libs/icon.ico \
--macos-app-icon=libs/icon.icns \ --macos-app-icon=libs/icon.icns \
--linux-icon=libs/icon.png \ --linux-icon=libs/icon.png \
PyTaiko.py PyTaiko.py
- name: Create Release Directory - name: Create Release Directory
run: | run: |
mkdir -p release mkdir -p release
cp -r Skins Songs config.toml shader model release/ cp -r Skins Songs config.toml shader model release/
if [ "${{ runner.os }}" == "Windows" ]; then if [ "${{ runner.os }}" == "Windows" ]; then
cp libs/audio/*.dll release/ 2>/dev/null || echo "libaudio not found" cp libs/audio/*.dll release/ 2>/dev/null || echo "libaudio not found"
elif [ "${{ runner.os }}" == "macOS" ]; then elif [ "${{ runner.os }}" == "macOS" ]; then
cp libs/audio/libaudio.dylib release/ 2>/dev/null || echo "libaudio not found" cp libs/audio/libaudio.dylib release/ 2>/dev/null || echo "libaudio not found"
else else
cp libs/audio/libaudio.so release/ 2>/dev/null || echo "libaudio not found" cp libs/audio/libaudio.so release/ 2>/dev/null || echo "libaudio not found"
fi fi
if [ "${{ runner.os }}" == "Windows" ]; then if [ "${{ runner.os }}" == "Windows" ]; then
cp *.exe release/ 2>/dev/null || echo "No .exe files found" cp *.exe release/ 2>/dev/null || echo "No .exe files found"
elif [ "${{ runner.os }}" == "macOS" ]; then elif [ "${{ runner.os }}" == "macOS" ]; then
cp -r *.app release/ 2>/dev/null || echo "No .app bundles found" cp -r *.app release/ 2>/dev/null || echo "No .app bundles found"
else else
cp *.bin release/ 2>/dev/null || echo "No .bin files found" cp *.bin release/ 2>/dev/null || echo "No .bin files found"
fi fi
shell: bash shell: bash
- name: Create Zip Archive - name: Create Zip Archive
run: | run: |
cd release cd release
if [ "${{ runner.os }}" == "Windows" ]; then if [ "${{ runner.os }}" == "Windows" ]; then
7z a ../PyTaiko-${{ runner.os }}-${{ runner.arch }}.zip * 7z a ../PyTaiko-${{ runner.os }}-${{ runner.arch }}.zip *
else else
zip -r ../PyTaiko-${{ runner.os }}-${{ runner.arch }}.zip * zip -r ../PyTaiko-${{ runner.os }}-${{ runner.arch }}.zip *
fi fi
shell: bash shell: bash
- name: Upload Release - name: Upload Build Artifacts
uses: softprops/action-gh-release@v2 uses: actions/upload-artifact@v4
if: github.ref == 'refs/heads/main' && github.event_name == 'push' with:
with: name: pytaiko-${{ runner.os }}-${{ runner.arch }}
files: PyTaiko-${{ runner.os }}-${{ runner.arch }}.zip path: PyTaiko-${{ runner.os }}-${{ runner.arch }}.zip
name: "PyTaiko [Rolling Release]" retention-days: 1
tag_name: "latest"
make_latest: true release:
env: needs: build
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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
with:
files: PyTaiko-*.zip
name: "PyTaiko [Rolling Release]"
tag_name: "latest"
make_latest: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2
.gitignore vendored
View File

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

View File

@@ -1,12 +1,12 @@
import argparse
import logging import logging
import os import os
from pathlib import Path
import sys
import argparse
import sqlite3 import sqlite3
import sys
from pathlib import Path
import pyray as ray import pyray as ray
from pypresence.presence import Presence
from raylib.defines import ( from raylib.defines import (
RL_FUNC_ADD, RL_FUNC_ADD,
RL_ONE, RL_ONE,
@@ -15,6 +15,7 @@ from raylib.defines import (
) )
from libs.audio import audio from libs.audio import audio
from libs.config import get_config
from libs.global_data import PlayerNum, ScoreMethod from libs.global_data import PlayerNum, ScoreMethod
from libs.screen import Screen from libs.screen import Screen
from libs.song_hash import DB_VERSION from libs.song_hash import DB_VERSION
@@ -23,27 +24,24 @@ from libs.utils import (
force_dedicated_gpu, force_dedicated_gpu,
get_current_ms, get_current_ms,
global_data, 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.devtest import DevScreen
from scenes.entry import EntryScreen from scenes.entry import EntryScreen
from scenes.game import GameScreen 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.game import PracticeGameScreen
from scenes.practice.song_select import PracticeSongSelectScreen 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.result import ResultScreen
from scenes.settings import SettingsScreen from scenes.settings import SettingsScreen
from scenes.song_select import SongSelectScreen from scenes.song_select import SongSelectScreen
from scenes.title import TitleScreen 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.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__) logger = logging.getLogger(__name__)
DISCORD_APP_ID = '1451423960401973353' DISCORD_APP_ID = '1451423960401973353'

View File

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

View File

@@ -1,10 +1,10 @@
import cffi
import platform
import logging import logging
import platform
from pathlib import Path from pathlib import Path
from libs.config import VolumeConfig import cffi
from libs.config import get_config
from libs.config import VolumeConfig, get_config
ffi = cffi.FFI() ffi = cffi.FFI()
@@ -131,7 +131,7 @@ class AudioEngine:
self.audio_device_ready = False self.audio_device_ready = False
self.volume_presets = volume_presets 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): def set_log_level(self, level: int):
lib.set_log_level(level) # type: ignore lib.set_log_level(level) # type: ignore

View File

@@ -1,4 +1,5 @@
import random import random
from libs.animation import Animation from libs.animation import Animation
from libs.bg_objects.bg_fever import BGFever4 from libs.bg_objects.bg_fever import BGFever4
from libs.bg_objects.bg_normal import BGNormal2 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.global_data import PlayerNum
from libs.texture import TextureWrapper from libs.texture import TextureWrapper
class Background: class Background:
def __init__(self, tex: TextureWrapper, player_num: PlayerNum, bpm: float, path: str, max_dancers: int): def __init__(self, tex: TextureWrapper, player_num: PlayerNum, bpm: float, path: str, max_dancers: int):
self.tex_wrapper = tex self.tex_wrapper = tex

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,22 @@
from dataclasses import dataclass
from enum import IntEnum
import json import json
import logging import logging
from pathlib import Path
import random 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 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 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 BOX_CENTER = 594 * tex.screen_scale
@@ -109,18 +110,22 @@ class BaseBox():
else: else:
self.fore_color = ray.Color(101, 0, 82, 255) self.fore_color = ray.Color(101, 0, 82, 255)
self.position = float('inf') self.position = float('inf')
self.start_position = -1.0 self.start_position = float('inf')
self.target_position = -1.0 self.target_position = float('inf')
self.open_anim = Animation.create_move(233, total_distance=150*tex.screen_scale, delay=50) 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.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.shader = None
self.is_open = False self.is_open = False
self.text_loaded = False self.text_loaded = False
self.wait = 0 self.wait = 0
def load_text(self): 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: if self.back_color is not None:
self.shader = ray.load_shader('shader/dummy.vs', 'shader/colortransform.fs') self.shader = ray.load_shader('shader/dummy.vs', 'shader/colortransform.fs')
source_rgb = (142, 212, 30) 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, source_loc, source_color, SHADER_UNIFORM_VEC3)
ray.set_shader_value(self.shader, target_loc, target_color, SHADER_UNIFORM_VEC3) ray.set_shader_value(self.shader, target_loc, target_color, SHADER_UNIFORM_VEC3)
def move_box(self, current_time: float): def move_box(self, direction: int):
if self.position != self.target_position and self.move is None: if self.position != self.target_position:
if self.position < self.target_position: distance = abs(self.target_position - self.position)
direction = 1 self.move = Animation.create_move(133, total_distance=distance * tex.screen_scale * direction, ease_out='cubic')
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)
self.start_position = self.position self.start_position = self.position
if self.move is not None: self.move.start()
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
def update(self, current_time: float, is_diff_select: bool): def update(self, current_time: float, is_diff_select: bool):
self.is_diff_select = is_diff_select self.is_diff_select = is_diff_select
self.open_anim.update(current_time) self.open_anim.update(current_time)
self.open_fade.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): def _draw_closed(self, x: float, y: float, outer_fade_override: float):
if self.shader is not None and self.texture_index == TextureIndex.BLANK: 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): def update(self, current_time: float, is_diff_select: bool):
super().update(current_time, is_diff_select) super().update(current_time, is_diff_select)
is_open_prev = self.is_open is_open_prev = self.is_open
self.move_box(current_time)
self.is_open = self.position == BOX_CENTER self.is_open = self.position == BOX_CENTER
if self.yellow_box is not None: if self.yellow_box is not None:
@@ -253,7 +249,6 @@ class SongBox(BaseBox):
def update(self, current_time: float, is_diff_select: bool): def update(self, current_time: float, is_diff_select: bool):
super().update(current_time, is_diff_select) super().update(current_time, is_diff_select)
is_open_prev = self.is_open is_open_prev = self.is_open
self.move_box(current_time)
self.is_open = self.position == BOX_CENTER self.is_open = self.position == BOX_CENTER
if self.yellow_box is not None: if self.yellow_box is not None:
@@ -324,7 +319,6 @@ class FolderBox(BaseBox):
def update(self, current_time: float, is_diff_select: bool): def update(self, current_time: float, is_diff_select: bool):
super().update(current_time, is_diff_select) super().update(current_time, is_diff_select)
is_open_prev = self.is_open is_open_prev = self.is_open
self.move_box(current_time)
self.is_open = self.position == BOX_CENTER self.is_open = self.position == BOX_CENTER
if not is_open_prev and self.is_open: 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): def update(self, current_time: float, is_diff_select: bool):
super().update(current_time, is_diff_select) super().update(current_time, is_diff_select)
is_open_prev = self.is_open is_open_prev = self.is_open
self.move_box(current_time)
self.is_open = self.position == BOX_CENTER self.is_open = self.position == BOX_CENTER
if not is_open_prev and self.is_open: if not is_open_prev and self.is_open:
self.yellow_box = YellowBox(False, is_dan=True) 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: if self.shader is not None and self.end_box.texture_index == TextureIndex.BLANK:
ray.begin_shader_mode(self.shader) ray.begin_shader_mode(self.shader)
offset = (tex.skin_config["genre_bg_offset"].x * -1) if self.start_box.is_open else 0 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) 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) 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 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) 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) tex.draw_texture('leaderboard', 'shinuchi_ura', index=self.long)
else: else:
tex.draw_texture('leaderboard', 'shinuchi', index=self.long) tex.draw_texture('leaderboard', 'shinuchi', index=self.long)
tex.draw_texture('leaderboard', 'pts', color=ray.WHITE, index=self.long)
case ScoreMethod.GEN3: 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', 'normal', index=self.long)
tex.draw_texture('leaderboard', 'pts', color=ray.BLACK, index=self.long)
tex.draw_texture('leaderboard', 'pts', color=ray.WHITE, index=self.long)
tex.draw_texture('leaderboard', 'difficulty', frame=self.curr_difficulty, index=self.long) tex.draw_texture('leaderboard', 'difficulty', frame=self.curr_difficulty, index=self.long)
for i in range(4): for i in range(4):
@@ -887,7 +881,11 @@ class ScoreHistory:
margin = tex.skin_config["score_info_counter_margin"].x margin = tex.skin_config["score_info_counter_margin"].x
for i in range(len(counter)): for i in range(len(counter)):
if j == 0: if j == 0:
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) 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: 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) 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""" """Check if currently at the virtual root"""
return self.current_dir == Path() 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): def load_current_directory(self, selected_item: Optional[Directory] = None):
"""Load pre-generated items for the current directory (unified for root and subdirs)""" """Load pre-generated items for the current directory (unified for root and subdirs)"""
dir_key = str(self.current_dir) dir_key = str(self.current_dir)
@@ -1383,47 +1429,15 @@ class FileNavigator:
# Handle special collections (same logic as before) # Handle special collections (same logic as before)
if isinstance(selected_item, Directory): if isinstance(selected_item, Directory):
if selected_item.collection == Directory.COLLECTIONS[0]: 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]: elif selected_item.collection == Directory.COLLECTIONS[1]:
if self.recent_folder is None: content_items = self.load_recent_items(selected_item, dir_key)
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]
elif selected_item.collection == Directory.COLLECTIONS[2]: elif selected_item.collection == Directory.COLLECTIONS[2]:
if self.favorite_folder is None: content_items = self.load_favorite_items(selected_item, dir_key)
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
elif selected_item.collection == Directory.COLLECTIONS[3]: elif selected_item.collection == Directory.COLLECTIONS[3]:
content_items = [] content_items = self.load_diff_sort_items(selected_item, dir_key)
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)
elif selected_item.collection == Directory.COLLECTIONS[4]: elif selected_item.collection == Directory.COLLECTIONS[4]:
parent_dir = selected_item.path.parent content_items = self.load_recommended_items(selected_item, dir_key)
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)))
if content_items == []: if content_items == []:
self.go_back() self.go_back()
@@ -1494,7 +1508,7 @@ class FileNavigator:
# Save current state to history # Save current state to history
self.history.append((self.current_dir, self.selected_index)) self.history.append((self.current_dir, self.selected_index))
self.current_dir = selected_item.path 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) self.load_current_directory(selected_item=selected_item)
@@ -1744,19 +1758,19 @@ class FileNavigator:
def navigate_left(self): def navigate_left(self):
"""Move selection left with wrap-around""" """Move selection left with wrap-around"""
if self.items: 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.selected_index = (self.selected_index - 1) % len(self.items)
self.calculate_box_positions() 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}") logger.info(f"Moved Left to {self.items[self.selected_index].path}")
def navigate_right(self): def navigate_right(self):
"""Move selection right with wrap-around""" """Move selection right with wrap-around"""
if self.items: 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.selected_index = (self.selected_index + 1) % len(self.items)
self.calculate_box_positions() 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}") logger.info(f"Moved Right to {self.items[self.selected_index].path}")
def skip_left(self): def skip_left(self):

View File

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

View File

@@ -1,11 +1,12 @@
from enum import Enum from enum import Enum
from typing import Callable from typing import Callable
import pyray as ray 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.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: class Nameplate:
@@ -98,6 +99,7 @@ class Indicator:
self.don_fade = global_tex.get_animation(6) self.don_fade = global_tex.get_animation(6)
self.blue_arrow_move = global_tex.get_animation(7) self.blue_arrow_move = global_tex.get_animation(7)
self.blue_arrow_fade = global_tex.get_animation(8) 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): def update(self, current_time_ms: float):
"""Update the indicator's animations.""" """Update the indicator's animations."""
@@ -109,7 +111,8 @@ class Indicator:
"""Draw the indicator at the given position with the given fade.""" """Draw the indicator at the given position with the given fade."""
tex = global_tex tex = global_tex
tex.draw_texture('indicator', 'background', x=x, y=y, fade=fade) 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) tex.draw_texture('indicator', 'drum_face', index=self.state.value, x=x, y=y, fade=fade)
if self.state == Indicator.State.SELECT: if self.state == Indicator.State.SELECT:
tex.draw_texture('indicator', 'drum_kat', fade=min(fade, self.don_fade.attribute), x=x, y=y) 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.""" """Coin overlay for the game."""
def __init__(self): def __init__(self):
"""Initialize the coin overlay.""" """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): def update(self, current_time_ms: float):
"""Update the coin overlay. Unimplemented""" """Update the coin overlay. Unimplemented"""
pass pass
def draw(self, x: int = 0, y: int = 0): def draw(self, x: int = 0, y: int = 0):
"""Draw the coin overlay. """Draw the coin overlay.
Only draws free play for now.""" Only draws free play for now."""
tex = global_tex 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)
tex.draw_texture('overlay', 'free_play', x=x, y=y)
class AllNetIcon: class AllNetIcon:
"""All.Net status icon for the game.""" """All.Net status icon for the game."""

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import string
import ctypes import ctypes
import hashlib import hashlib
import sys import sys
@@ -135,7 +136,7 @@ for file in Path('cache/image').iterdir():
class OutlinedText: class OutlinedText:
"""Create an outlined text object.""" """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. Create an outlined text object.
@@ -158,7 +159,7 @@ class OutlinedText:
if vertical: if vertical:
self.texture = self._create_text_vertical(text, font_size, color, ray.BLANK, self.font) self.texture = self._create_text_vertical(text, font_size, color, ray.BLANK, self.font)
else: 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.gen_texture_mipmaps(self.texture)
ray.set_texture_filter(self.texture, ray.TextureFilter.TEXTURE_FILTER_TRILINEAR) ray.set_texture_filter(self.texture, ray.TextureFilter.TEXTURE_FILTER_TRILINEAR)
outline_size = ray.ffi.new('float*', self.outline_thickness) outline_size = ray.ffi.new('float*', self.outline_thickness)
@@ -200,7 +201,7 @@ class OutlinedText:
if reload_font: if reload_font:
codepoint_count = ray.ffi.new('int *', 0) codepoint_count = ray.ffi.new('int *', 0)
codepoints = ray.load_codepoints(''.join(global_data.font_codepoints), codepoint_count) 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") logger.info(f"Reloaded font with {len(global_data.font_codepoints)} codepoints")
return global_data.font return global_data.font
@@ -358,19 +359,25 @@ class OutlinedText:
ray.unload_image(image) ray.unload_image(image)
return texture 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: 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_width = text_size.x + (padding * 2)
total_height = text_size.y + (padding * 2) total_height = text_size.y + (padding * 2)
else: else:
total_width = ray.measure_text(text, font_size) + (padding * 2) total_width = ray.measure_text(text, font_size) + (padding * 2)
total_height = font_size + (padding * 2) total_height = font_size + (padding * 2)
image = ray.gen_image_color(int(total_width), int(total_height), bg_color) image = ray.gen_image_color(int(total_width), int(total_height), bg_color)
if font: 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: else:
text_image = ray.image_text(text, font_size, color) text_image = ray.image_text(text, font_size, color)
text_x = padding text_x = padding
text_y = padding text_y = padding
ray.image_draw(image, text_image, 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.Rectangle(text_x, text_y, text_image.width, text_image.height),
ray.WHITE) ray.WHITE)
ray.unload_image(text_image) ray.unload_image(text_image)
ray.export_image(image, f'cache/image/{self.hash}.png') ray.export_image(image, f'cache/image/{self.hash}.png')
texture = ray.load_texture_from_image(image) texture = ray.load_texture_from_image(image)
ray.unload_image(image) ray.unload_image(image)

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,28 @@
import logging import logging
import pyray as ray import pyray as ray
from libs.audio import audio 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.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.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.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 from scenes.song_select import State
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

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

View File

@@ -1,13 +1,21 @@
import logging import logging
from typing import Optional from typing import Optional
import pyray as ray import pyray as ray
from libs.audio import audio from libs.audio import audio
from libs.chara_2d import Chara2D from libs.chara_2d import Chara2D
from libs.global_data import PlayerNum from libs.global_data import PlayerNum
from libs.global_objects import AllNetIcon, CoinOverlay, Nameplate, Indicator, EntryOverlay, Timer from libs.global_objects import (
from libs.texture import tex AllNetIcon,
CoinOverlay,
EntryOverlay,
Indicator,
Nameplate,
Timer,
)
from libs.screen import Screen from libs.screen import Screen
from libs.texture import tex
from libs.utils import ( from libs.utils import (
OutlinedText, OutlinedText,
get_current_ms, get_current_ms,
@@ -439,9 +447,9 @@ class BoxManager:
"""BoxManager class for the entry screen""" """BoxManager class for the entry screen"""
def __init__(self): def __init__(self):
self.box_titles: list[OutlinedText] = [ 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_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_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_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.box_locations = ["SONG_SELECT", "PRACTICE_SELECT", "SETTINGS"]
self.num_boxes = len(self.box_titles) 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))] 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 import bisect
from enum import IntEnum
import math
import logging import logging
import math
import sqlite3 import sqlite3
from collections import deque from collections import deque
from enum import IntEnum
from itertools import chain
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from itertools import chain
import pyray as ray import pyray as ray
@@ -14,7 +14,13 @@ from libs.animation import Animation
from libs.audio import audio from libs.audio import audio
from libs.background import Background from libs.background import Background
from libs.chara_2d import Chara2D 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.global_objects import AllNetIcon, Nameplate
from libs.screen import Screen from libs.screen import Screen
from libs.texture import tex from libs.texture import tex
@@ -24,8 +30,8 @@ from libs.tja import (
Note, Note,
NoteList, NoteList,
NoteType, NoteType,
TJAParser,
TimelineObject, TimelineObject,
TJAParser,
apply_modifiers, apply_modifiers,
calculate_base_score, calculate_base_score,
) )
@@ -116,7 +122,7 @@ class GameScreen(Screen):
def load_hitsounds(self): def load_hitsounds(self):
"""Load the hit sounds""" """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: if global_data.hit_sound == -1:
audio.load_sound(Path('none.wav'), 'hitsound_don_1p') audio.load_sound(Path('none.wav'), 'hitsound_don_1p')
audio.load_sound(Path('none.wav'), 'hitsound_kat_1p') audio.load_sound(Path('none.wav'), 'hitsound_kat_1p')
@@ -448,7 +454,7 @@ class Player:
self.get_load_time(note) self.get_load_time(note)
if note.type == NoteType.TAIL: if note.type == NoteType.TAIL:
note.load_ms = last_note.load_ms note.load_ms = last_note.load_ms
note.unload_ms = last_note.unload_ms last_note.unload_ms = note.unload_ms
last_note = note last_note = note
self.draw_note_list = deque(sorted(self.draw_note_list, key=lambda n: n.load_ms)) self.draw_note_list = deque(sorted(self.draw_note_list, key=lambda n: n.load_ms))

View File

@@ -1,17 +1,16 @@
import logging import logging
from pathlib import Path
import threading import threading
from pathlib import Path
import pyray as ray import pyray as ray
from libs.animation import Animation from libs.animation import Animation
from libs.file_navigator import navigator
from libs.global_objects import AllNetIcon from libs.global_objects import AllNetIcon
from libs.screen import Screen from libs.screen import Screen
from libs.song_hash import build_song_hashes from libs.song_hash import build_song_hashes
from libs.texture import tex from libs.texture import tex
from libs.utils import get_current_ms, global_data from libs.utils import get_current_ms, global_data
from libs.file_navigator import navigator
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -58,7 +57,7 @@ class LoadScreen(Screen):
global_data.font_codepoints.add(character) global_data.font_codepoints.add(character)
codepoint_count = ray.ffi.new('int *', 0) codepoint_count = ray.ffi.new('int *', 0)
codepoints = ray.load_codepoints(''.join(global_data.font_codepoints), codepoint_count) 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): def _load_navigator(self):
"""Background thread function to load navigator""" """Background thread function to load navigator"""

View File

@@ -1,20 +1,41 @@
import copy
import logging
import math import math
from collections import deque from collections import deque
import logging
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import pyray as ray import pyray as ray
import copy
from libs.animation import Animation from libs.animation import Animation
from libs.audio import audio from libs.audio import audio
from libs.background import Background from libs.background import Background
from libs.global_data import Modifiers, PlayerNum, global_data 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 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__) logger = logging.getLogger(__name__)

View File

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

View File

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

View File

@@ -1,18 +1,31 @@
import logging
import random import random
from dataclasses import fields from dataclasses import fields
from pathlib import Path from pathlib import Path
import pyray as ray import pyray as ray
import logging
from raylib import SHADER_UNIFORM_VEC3 from raylib import SHADER_UNIFORM_VEC3
from libs.file_navigator import DEFAULT_COLORS, BackBox, DanCourse, GenreIndex, navigator
from libs.audio import audio from libs.audio import audio
from libs.chara_2d import Chara2D 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_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.screen import Screen
from libs.texture import tex from libs.texture import tex
from libs.transition import Transition from libs.transition import Transition
@@ -69,6 +82,8 @@ class SongSelectScreen(Screen):
self.dan_transition = DanTransition() self.dan_transition = DanTransition()
self.shader = ray.load_shader('shader/dummy.vs', 'shader/colortransform.fs') self.shader = ray.load_shader('shader/dummy.vs', 'shader/colortransform.fs')
self.color = None 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) self.load_shader_values(self.color)
session_data = global_data.session_data[global_data.player_num] 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) 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_bg', fade=0.75, x=-(self.song_num.texture.width-127), x2=(self.song_num.texture.width-127))
tex.draw_texture('global', 'song_num', frame=global_data.songs_played % 4) 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: if self.state == State.BROWSING or self.state == State.DIFF_SORTING:
self.timer_browsing.draw() self.timer_browsing.draw()
elif self.state == State.SONG_SELECTED: elif self.state == State.SONG_SELECTED:
@@ -478,13 +493,14 @@ class SongSelectPlayer:
audio.play_sound('skip', 'sound') audio.play_sound('skip', 'sound')
return "skip_right" return "skip_right"
wheel = ray.get_mouse_wheel_move()
# Navigate left # 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') audio.play_sound('kat', 'sound')
return "navigate_left" return "navigate_left"
# Navigate right # 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') audio.play_sound('kat', 'sound')
return "navigate_right" return "navigate_right"

View File

@@ -2,10 +2,14 @@ import logging
import random import random
from pathlib import Path from pathlib import Path
import pyray as ray
from libs.audio import audio from libs.audio import audio
from libs.global_objects import AllNetIcon, CoinOverlay, EntryOverlay from libs.global_objects import AllNetIcon, CoinOverlay, EntryOverlay
from libs.screen import Screen
from libs.texture import tex from libs.texture import tex
from libs.utils import ( from libs.utils import (
OutlinedText,
get_current_ms, get_current_ms,
global_data, global_data,
global_tex, global_tex,
@@ -13,7 +17,6 @@ from libs.utils import (
is_r_don_pressed, is_r_don_pressed,
) )
from libs.video import VideoPlayer from libs.video import VideoPlayer
from libs.screen import Screen
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -25,19 +28,11 @@ class State:
class TitleScreen(Screen): class TitleScreen(Screen):
def __init__(self, name: str): def __init__(self, name: str):
super().__init__(name) 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.op_video_list = []
self.attract_video_list = [] self.attract_video_list = []
for base in video_paths: base = Path(f"Skins/{global_data.config["paths"]["skin"]}/Videos")
base = Path(base) self.op_video_list += list((base/"op_videos").glob("**/*.mp4"))
self.op_video_list += list((base/"op_videos").glob("**/*.mp4")) self.attract_video_list += list((base/"attract_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): def on_screen_start(self):
super().on_screen_start() super().on_screen_start()
@@ -45,6 +40,10 @@ class TitleScreen(Screen):
self.op_video = None self.op_video = None
self.attract_video = None self.attract_video = None
self.warning_board = 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.fade_out = tex.get_animation(13)
self.text_overlay_fade = tex.get_animation(14) self.text_overlay_fade = tex.get_animation(14)
@@ -121,8 +120,8 @@ class TitleScreen(Screen):
self.allnet_indicator.draw() self.allnet_indicator.draw()
self.entry_overlay.draw(tex.skin_config["entry_overlay_title"].x, y=tex.skin_config["entry_overlay_title"].y) 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) 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)
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.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: class WarningScreen:
"""Warning screen for the game""" """Warning screen for the game"""

View File

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

View File

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

View File

@@ -1,10 +1,16 @@
import logging import logging
from libs.audio import audio
from libs.file_navigator import SongBox, SongFile from libs.file_navigator import SongBox, SongFile
from libs.global_data import PlayerNum from libs.global_data import PlayerNum
from libs.transition import Transition 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.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__) logger = logging.getLogger(__name__)