mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
327c48aa1a | ||
|
|
3a18a507c0 | ||
|
|
2769503899 | ||
|
|
e719119764 | ||
|
|
174f322696 | ||
|
|
33125c6322 | ||
|
|
e97bd5bd4c | ||
|
|
88acfe5e5b | ||
|
|
73abcddf44 | ||
|
|
7ca8ff8c38 | ||
|
|
22778dbd3d | ||
|
|
d70e734661 | ||
|
|
2c09360bfd | ||
|
|
58d7043a50 | ||
|
|
27c58cc97e | ||
|
|
20c1f1141e | ||
|
|
4844792aaa | ||
|
|
201b37dda0 | ||
|
|
77886d34cb | ||
|
|
74080634de | ||
|
|
f7ab62ab1d | ||
|
|
5c7e759385 |
28
.github/workflows/python-app.yml
vendored
28
.github/workflows/python-app.yml
vendored
@@ -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
2
.gitignore
vendored
@@ -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
|
||||
|
||||
26
PyTaiko.py
26
PyTaiko.py
@@ -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'
|
||||
|
||||
Submodule Skins/PyTaikoGreen updated: cc253a534d...b9dbd95dc7
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import random
|
||||
from libs.animation import Animation
|
||||
from libs.texture import TextureWrapper
|
||||
|
||||
|
||||
class Dancer:
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from libs.animation import Animation
|
||||
from libs.texture import TextureWrapper
|
||||
|
||||
|
||||
class Fever:
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from libs.texture import TextureWrapper
|
||||
|
||||
|
||||
class Footer:
|
||||
def __init__(self, tex: TextureWrapper, index: int, path: str = 'background'):
|
||||
self.index = index
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
|
||||
from libs.animation import Animation
|
||||
from libs.utils import global_tex
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -7,6 +7,7 @@ import pyray as ray
|
||||
|
||||
from libs.config import Config
|
||||
|
||||
|
||||
class PlayerNum(IntEnum):
|
||||
ALL = 0
|
||||
P1 = 1
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from libs.audio import audio
|
||||
from libs.texture import tex
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
|
||||
from libs.global_data import PlayerNum
|
||||
from libs.texture import tex
|
||||
from libs.utils import get_current_ms
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user