1 Commits

Author SHA1 Message Date
Yonokid
d8a219243c unlikely fix 2026-01-01 10:56:55 -05:00
14 changed files with 277 additions and 486 deletions

View File

@@ -20,7 +20,7 @@ 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
from libs.parsers.tja import TJAParser from libs.tja import TJAParser
from libs.utils import ( from libs.utils import (
force_dedicated_gpu, force_dedicated_gpu,
get_current_ms, get_current_ms,
@@ -265,7 +265,7 @@ def check_args():
selected_difficulty = args.difficulty selected_difficulty = args.difficulty
else: else:
selected_difficulty = max(tja.metadata.course_data.keys()) selected_difficulty = max(tja.metadata.course_data.keys())
current_screen = Screens.GAME_PRACTICE if args.practice else Screens.GAME current_screen = Screens.GAME_PRACTICE if args.practice else Screens.AI_GAME
global_data.session_data[PlayerNum.P1].selected_song = path global_data.session_data[PlayerNum.P1].selected_song = path
global_data.session_data[PlayerNum.P1].selected_difficulty = selected_difficulty global_data.session_data[PlayerNum.P1].selected_difficulty = selected_difficulty
global_data.modifiers[PlayerNum.P1].auto = args.auto global_data.modifiers[PlayerNum.P1].auto = args.auto
@@ -286,15 +286,14 @@ def check_discord_heartbeat(current_screen):
def draw_fps(last_fps: int): def draw_fps(last_fps: int):
curr_fps = ray.GetFPS() curr_fps = ray.GetFPS()
pos = 20 * global_tex.screen_scale
if curr_fps != 0 and curr_fps != last_fps: if curr_fps != 0 and curr_fps != last_fps:
last_fps = curr_fps last_fps = curr_fps
if last_fps < 30: if last_fps < 30:
pyray.draw_text_ex(global_data.font, f'{last_fps} FPS', (pos, pos), pos, 1, pyray.RED) ray.DrawText(f'{last_fps} FPS'.encode('utf-8'), 20, 20, 20, ray.RED)
elif last_fps < 60: elif last_fps < 60:
pyray.draw_text_ex(global_data.font, f'{last_fps} FPS', (pos, pos), pos, 1, pyray.YELLOW) ray.DrawText(f'{last_fps} FPS'.encode('utf-8'), 20, 20, 20, ray.YELLOW)
else: else:
pyray.draw_text_ex(global_data.font, f'{last_fps} FPS', (pos, pos), pos, 1, pyray.LIME) ray.DrawText(f'{last_fps} FPS'.encode('utf-8'), 20, 20, 20, ray.LIME)
def draw_outer_border(screen_width: int, screen_height: int, last_color: pyray.Color): def draw_outer_border(screen_width: int, screen_height: int, last_color: pyray.Color):
pyray.draw_rectangle(-screen_width, 0, screen_width, screen_height, last_color) pyray.draw_rectangle(-screen_width, 0, screen_width, screen_height, last_color)

View File

@@ -2,7 +2,7 @@
fps_counter = false fps_counter = false
audio_offset = 0 audio_offset = 0
visual_offset = 0 visual_offset = 0
language = "en" language = "ja"
timer_frozen = true timer_frozen = true
judge_counter = false judge_counter = false
nijiiro_notes = false nijiiro_notes = false

View File

@@ -15,7 +15,7 @@ from libs.animation import Animation, MoveAnimation
from libs.audio import audio from libs.audio import audio
from libs.global_data import Crown, Difficulty, ScoreMethod from libs.global_data import Crown, Difficulty, ScoreMethod
from libs.texture import tex from libs.texture import tex
from libs.parsers.tja import TJAParser, test_encodings from libs.tja import TJAParser, test_encodings
from libs.utils import OutlinedText, get_current_ms, global_data from libs.utils import OutlinedText, get_current_ms, global_data
BOX_CENTER = 594 * tex.screen_scale BOX_CENTER = 594 * tex.screen_scale

View File

@@ -3,6 +3,7 @@ from typing import Any
from libs.audio import audio from libs.audio import audio
from libs.texture import tex from libs.texture import tex
from libs.utils import input_state
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -34,6 +35,7 @@ class Screen:
return next_screen return next_screen
def update(self) -> Any: def update(self) -> Any:
input_state.update()
ret_val = self._do_screen_start() ret_val = self._do_screen_start()
if ret_val: if ret_val:
return ret_val return ret_val

View File

@@ -8,7 +8,7 @@ from pathlib import Path
from libs.config import get_config from libs.config import get_config
from libs.global_data import Crown from libs.global_data import Crown
from libs.parsers.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
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -2,6 +2,7 @@ import hashlib
import logging import logging
import math import math
import random import random
from collections import deque
from dataclasses import dataclass, field, fields from dataclasses import dataclass, field, fields
from enum import IntEnum from enum import IntEnum
from functools import lru_cache from functools import lru_cache

View File

@@ -1,10 +1,9 @@
import string
import ctypes import ctypes
import hashlib import hashlib
import sys
import logging import logging
import string
import sys
import time import time
from libs.global_data import PlayerNum, global_data
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
@@ -16,6 +15,7 @@ from raylib import (
SHADER_UNIFORM_VEC4, SHADER_UNIFORM_VEC4,
) )
from libs.global_data import PlayerNum, global_data
from libs.texture import TextureWrapper from libs.texture import TextureWrapper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -57,11 +57,25 @@ def strip_comments(code: str) -> str:
index += 1 index += 1
return result return result
class InputState:
def __init__(self):
self.pressed_keys_this_frame = set()
def update(self):
"""Call this once per frame to drain the key queue"""
self.pressed_keys_this_frame.clear()
key = rl.GetKeyPressed()
while key > 0:
self.pressed_keys_this_frame.add(key)
key = rl.GetKeyPressed()
input_state = InputState()
def is_input_key_pressed(keys: list[int], gamepad_buttons: list[int]): def is_input_key_pressed(keys: list[int], gamepad_buttons: list[int]):
if global_data.input_locked: if global_data.input_locked:
return False return False
for key in keys: for key in keys:
if rl.IsKeyPressed(key): if key in input_state.pressed_keys_this_frame:
return True return True
if rl.IsGamepadAvailable(0): if rl.IsGamepadAvailable(0):

View File

@@ -15,7 +15,7 @@ from libs.chara_2d import Chara2D
from libs.global_data import Difficulty, Modifiers, PlayerNum, global_data from libs.global_data import Difficulty, Modifiers, PlayerNum, global_data
from libs.global_objects import Nameplate from libs.global_objects import Nameplate
from libs.texture import tex from libs.texture import tex
from libs.parsers.tja import TJAParser from libs.tja import TJAParser
from libs.utils import get_current_ms, global_tex from libs.utils import get_current_ms, global_tex
from scenes.game import ( from scenes.game import (
DrumType, DrumType,

View File

@@ -16,7 +16,7 @@ from libs.global_data import (
) )
from libs.global_objects import AllNetIcon from libs.global_objects import AllNetIcon
from libs.texture import tex from libs.texture import tex
from libs.parsers.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 scenes.game import ( from scenes.game import (

View File

@@ -25,7 +25,7 @@ from libs.global_data import (
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
from libs.parsers.tja import ( from libs.tja import (
Balloon, Balloon,
Drumroll, Drumroll,
Note, Note,
@@ -171,7 +171,7 @@ class GameScreen(Screen):
existing_score = result[0] if result is not None else None existing_score = result[0] if result is not None else None
existing_crown = result[1] if result is not None and len(result) > 1 and result[1] is not None else 0 existing_crown = result[1] if result is not None and len(result) > 1 and result[1] is not None else 0
crown = Crown.NONE crown = Crown.NONE
if session_data.result_data.bad == 0 and session_data.result_data.ok == 0: if session_data.result_data.bad and session_data.result_data.ok == 0:
crown = Crown.DFC crown = Crown.DFC
elif session_data.result_data.bad == 0: elif session_data.result_data.bad == 0:
crown = Crown.FC crown = Crown.FC

View File

@@ -12,7 +12,7 @@ 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.texture import tex from libs.texture import tex
from libs.parsers.tja import ( from libs.tja import (
Balloon, Balloon,
Drumroll, Drumroll,
NoteType, NoteType,

View File

@@ -1,17 +1,12 @@
import json
import logging import logging
import pyray as ray import pyray as ray
from libs.animation import Animation
from libs.audio import audio from libs.audio import audio
from libs.config import get_key_string, save_config from libs.config import save_config
from libs.global_objects import AllNetIcon, CoinOverlay, Indicator
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 (
OutlinedText,
get_current_ms,
global_data, global_data,
is_l_don_pressed, is_l_don_pressed,
is_l_kat_pressed, is_l_kat_pressed,
@@ -21,482 +16,262 @@ from libs.utils import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class BaseOptionBox:
def __init__(self, name: str, description: str, path: str, values: dict):
self.name = OutlinedText(name, 30, ray.WHITE)
self.setting_header, self.setting_name = path.split('/')
self.description = description
self.is_highlighted = False
self.value = global_data.config[self.setting_header][self.setting_name]
def update(self, current_time):
pass
def move_left(self):
pass
def move_right(self):
pass
def confirm(self):
global_data.config[self.setting_header][self.setting_name] = self.value
def __repr__(self):
return str(self.__dict__)
def draw(self):
tex.draw_texture('background', 'overlay', scale=0.70)
if self.is_highlighted:
tex.draw_texture('background', 'title_highlight')
else:
tex.draw_texture('background', 'title')
text_x = tex.textures['background']['title'].x[0] + (tex.textures['background']['title'].width//2) - (self.name.texture.width//2)
text_y = tex.textures['background']['title'].y[0] + self.name.texture.height//4
self.name.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
ray.draw_text_ex(global_data.font, self.description, (450 * tex.screen_scale, 270 * tex.screen_scale), 25 * tex.screen_scale, 1, ray.BLACK)
class BoolOptionBox(BaseOptionBox):
def __init__(self, name: str, description: str, path: str, values: dict):
super().__init__(name, description, path, values)
language = global_data.config["general"]["language"]
self.on_value = OutlinedText(values["true"].get(language, values["true"]["en"]), int(30 * tex.screen_scale), ray.WHITE)
self.off_value = OutlinedText(values["false"].get(language, values["false"]["en"]), int(30 * tex.screen_scale), ray.WHITE)
def move_left(self):
self.value = False
def move_right(self):
self.value = True
def draw(self):
super().draw()
if not self.value:
tex.draw_texture('option', 'button_on', index=0)
else:
tex.draw_texture('option', 'button_off', index=0)
text_x = tex.textures["option"]["button_on"].x[0] + (tex.textures["option"]["button_on"].width//2) - (self.off_value.texture.width//2)
text_y = tex.textures["option"]["button_on"].y[0] + (tex.textures["option"]["button_on"].height//2) - (self.off_value.texture.height//2)
self.off_value.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
if self.value:
tex.draw_texture('option', 'button_on', index=1)
else:
tex.draw_texture('option', 'button_off', index=1)
text_x = tex.textures["option"]["button_on"].x[1] + (tex.textures["option"]["button_on"].width//2) - (self.on_value.texture.width//2)
text_y = tex.textures["option"]["button_on"].y[1] + (tex.textures["option"]["button_on"].height//2) - (self.on_value.texture.height//2)
self.on_value.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
class IntOptionBox(BaseOptionBox):
def __init__(self, name: str, description: str, path: str, values: dict):
super().__init__(name, description, path, values)
self.value_text = OutlinedText(str(self.value), int(30 * tex.screen_scale), ray.WHITE)
self.flicker_fade = Animation.create_fade(400, initial_opacity=0.0, final_opacity=1.0, reverse_delay=0, loop=True)
self.flicker_fade.start()
language = global_data.config["general"]["language"]
self.value_list = []
if values != dict():
self.value_list = list(values.keys())
self.value_index = 0
self.values = values
self.value_text = OutlinedText(self.values[str(self.value)].get(language, self.values[str(self.value)]["en"]), int(30 * tex.screen_scale), ray.WHITE)
def update(self, current_time):
self.flicker_fade.update(current_time)
def move_left(self):
if self.value_list:
self.value_index = max(self.value_index - 1, 0)
self.value = int(self.value_list[self.value_index])
self.value_text = OutlinedText(self.values[str(self.value)].get(global_data.config["general"]["language"], self.values[str(self.value)]["en"]), int(30 * tex.screen_scale), ray.WHITE)
else:
self.value -= 1
self.value_text = OutlinedText(str(self.value), int(30 * tex.screen_scale), ray.WHITE)
def move_right(self):
if self.value_list:
self.value_index = min(self.value_index + 1, len(self.value_list) - 1)
self.value = int(self.value_list[self.value_index])
self.value_text = OutlinedText(self.values[str(self.value)].get(global_data.config["general"]["language"], self.values[str(self.value)]["en"]), int(30 * tex.screen_scale), ray.WHITE)
else:
self.value += 1
self.value_text = OutlinedText(str(self.value), int(30 * tex.screen_scale), ray.WHITE)
def draw(self):
super().draw()
tex.draw_texture('option', 'button_off', index=2)
if self.is_highlighted:
tex.draw_texture('option', 'button_on', index=2, fade=self.flicker_fade.attribute)
text_x = tex.textures["option"]["button_on"].x[2] + (tex.textures["option"]["button_on"].width//2) - (self.value_text.texture.width//2)
text_y = tex.textures["option"]["button_on"].y[2] + (tex.textures["option"]["button_on"].height//2) - (self.value_text.texture.height//2)
self.value_text.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
class StrOptionBox(BaseOptionBox):
def __init__(self, name: str, description: str, path: str, values: dict):
super().__init__(name, description, path, values)
self.flicker_fade = Animation.create_fade(400, initial_opacity=0.0, final_opacity=1.0, reverse_delay=0, loop=True)
self.flicker_fade.start()
self.value_list = []
if values != dict():
self.value_list = list(values.keys())
self.value_index = 0
self.values = values
language = global_data.config["general"]["language"]
self.value_text = OutlinedText(self.values[self.value].get(language, self.values[self.value]["en"]), int(30 * tex.screen_scale), ray.WHITE)
else:
self.string = self.value
self.value_text = OutlinedText(self.value, int(30 * tex.screen_scale), ray.WHITE)
def update(self, current_time):
self.flicker_fade.update(current_time)
if self.is_highlighted and self.value_list == []:
if ray.is_key_pressed(ray.KeyboardKey.KEY_BACKSPACE):
self.string = self.string[:-1]
self.value_text = OutlinedText(self.string, int(30 * tex.screen_scale), ray.WHITE)
elif ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
self.value = self.string
self.is_highlighted = False
key = ray.get_char_pressed()
if key > 0:
self.string += chr(key)
self.value_text = OutlinedText(self.string, int(30 * tex.screen_scale), ray.WHITE)
def move_left(self):
if self.value_list:
self.value_index = max(self.value_index - 1, 0)
self.value = self.value_list[self.value_index]
self.value_text = OutlinedText(self.values[self.value].get(global_data.config["general"]["language"], self.values[self.value]["en"]), int(30 * tex.screen_scale), ray.WHITE)
def move_right(self):
if self.value_list:
self.value_index = min(self.value_index + 1, len(self.value_list) - 1)
self.value = self.value_list[self.value_index]
self.value_text = OutlinedText(self.values[self.value].get(global_data.config["general"]["language"], self.values[self.value]["en"]), int(30 * tex.screen_scale), ray.WHITE)
def draw(self):
super().draw()
tex.draw_texture('option', 'button_off', index=2)
if self.is_highlighted:
tex.draw_texture('option', 'button_on', index=2, fade=self.flicker_fade.attribute)
text_x = tex.textures["option"]["button_on"].x[2] + (tex.textures["option"]["button_on"].width//2) - (self.value_text.texture.width//2)
text_y = tex.textures["option"]["button_on"].y[2] + (tex.textures["option"]["button_on"].height//2) - (self.value_text.texture.height//2)
self.value_text.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
class KeybindOptionBox(BaseOptionBox):
def __init__(self, name: str, description: str, path: str, values: dict):
super().__init__(name, description, path, values)
if isinstance(self.value, list):
text = ', '.join([get_key_string(key) for key in self.value])
else:
text = get_key_string(self.value)
self.value_text = OutlinedText(text, int(30 * tex.screen_scale), ray.WHITE)
self.flicker_fade = Animation.create_fade(400, initial_opacity=0.0, final_opacity=1.0, reverse_delay=0, loop=True)
self.flicker_fade.start()
def update(self, current_time):
self.flicker_fade.update(current_time)
if self.is_highlighted:
key = ray.get_key_pressed()
if key > 0:
self.value = key
audio.play_sound('don', 'sound')
self.value_text = OutlinedText(get_key_string(self.value), int(30 * tex.screen_scale), ray.WHITE)
self.is_highlighted = False
def draw(self):
super().draw()
tex.draw_texture('option', 'button_off', index=2)
if self.is_highlighted:
tex.draw_texture('option', 'button_on', index=2, fade=self.flicker_fade.attribute)
text_x = tex.textures["option"]["button_on"].x[2] + (tex.textures["option"]["button_on"].width//2) - (self.value_text.texture.width//2)
text_y = tex.textures["option"]["button_on"].y[2] + (tex.textures["option"]["button_on"].height//2) - (self.value_text.texture.height//2)
self.value_text.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
class KeyBindControllerOptionBox(BaseOptionBox):
def __init__(self, name: str, description: str, path: str, values: dict):
super().__init__(name, description, path, values)
if isinstance(self.value, list):
text = ', '.join([str(key) for key in self.value])
else:
text = str(self.value)
self.value_text = OutlinedText(text, int(30 * tex.screen_scale), ray.WHITE)
self.flicker_fade = Animation.create_fade(400, initial_opacity=0.0, final_opacity=1.0, reverse_delay=0, loop=True)
self.flicker_fade.start()
def update(self, current_time):
self.flicker_fade.update(current_time)
if self.is_highlighted:
key = ray.get_gamepad_button_pressed()
if key > 0:
self.value = key
audio.play_sound('don', 'sound')
self.value_text = OutlinedText(str(self.value), int(30 * tex.screen_scale), ray.WHITE)
self.is_highlighted = False
def draw(self):
super().draw()
tex.draw_texture('option', 'button_off', index=2)
if self.is_highlighted:
tex.draw_texture('option', 'button_on', index=2, fade=self.flicker_fade.attribute)
text_x = tex.textures["option"]["button_on"].x[2] + (tex.textures["option"]["button_on"].width//2) - (self.value_text.texture.width//2)
text_y = tex.textures["option"]["button_on"].y[2] + (tex.textures["option"]["button_on"].height//2) - (self.value_text.texture.height//2)
self.value_text.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
class FloatOptionBox(BaseOptionBox):
def __init__(self, name: str, description: str, path: str, values: dict):
super().__init__(name, description, path, values)
self.value_text = OutlinedText(str(int(self.value*100)) + "%", int(30 * tex.screen_scale), ray.WHITE)
self.flicker_fade = Animation.create_fade(400, initial_opacity=0.0, final_opacity=1.0, reverse_delay=0, loop=True)
self.flicker_fade.start()
def update(self, current_time):
self.flicker_fade.update(current_time)
def move_left(self):
self.value = ((self.value*100) - 1) / 100
self.value_text = OutlinedText(str(int(self.value*100))+"%", int(30 * tex.screen_scale), ray.WHITE)
def move_right(self):
self.value = ((self.value*100) + 1) / 100
self.value_text = OutlinedText(str(int(self.value*100))+"%", int(30 * tex.screen_scale), ray.WHITE)
def draw(self):
super().draw()
tex.draw_texture('option', 'button_off', index=2)
if self.is_highlighted:
tex.draw_texture('option', 'button_on', index=2, fade=self.flicker_fade.attribute)
text_x = tex.textures["option"]["button_on"].x[2] + (tex.textures["option"]["button_on"].width//2) - (self.value_text.texture.width//2)
text_y = tex.textures["option"]["button_on"].y[2] + (tex.textures["option"]["button_on"].height//2) - (self.value_text.texture.height//2)
self.value_text.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
class Box:
"""Box class for the entry screen"""
OPTION_BOX_MAP = {
"int": IntOptionBox,
"bool": BoolOptionBox,
"string": StrOptionBox,
"keybind": KeybindOptionBox,
"keybind_controller": KeyBindControllerOptionBox,
"float": FloatOptionBox
}
def __init__(self, name: str, text: str, box_options: dict):
self.name = name
self.text = OutlinedText(text, tex.skin_config["entry_box_text"].font_size - int(5*tex.screen_scale), ray.WHITE, outline_thickness=5)
self.x = 10 * tex.screen_scale
self.y = -50 * tex.screen_scale
self.move = tex.get_animation(0)
self.blue_arrow_fade = tex.get_animation(1)
self.blue_arrow_move = tex.get_animation(2)
self.is_selected = False
self.in_box = False
self.outline_color = ray.Color(109, 68, 24, 255)
self.direction = 1
self.target_position = float('inf')
self.start_position = self.y
self.option_index = 0
language = global_data.config["general"]["language"]
self.options = [Box.OPTION_BOX_MAP[
box_options[option]["type"]](box_options[option]["name"].get(language, box_options[option]["name"]["en"]),
box_options[option]["description"].get(language, box_options[option]["description"]["en"]), box_options[option]["path"],
box_options[option]["values"]) for option in box_options]
def __repr__(self):
return str(self.__dict__)
def move_left(self):
"""Move the box left"""
if self.y != self.target_position and self.target_position != float('inf'):
return False
self.move.start()
self.direction = 1
self.start_position = self.y
self.target_position = self.y + (100 * tex.screen_scale * self.direction)
if self.target_position >= 650:
self.target_position = -50 + (self.target_position - 650)
return True
def move_right(self):
"""Move the box right"""
if self.y != self.target_position and self.target_position != float('inf'):
return False
self.move.start()
self.start_position = self.y
self.direction = -1
self.target_position = self.y + (100 * tex.screen_scale * self.direction)
if self.target_position < -50:
self.target_position = 650 + (self.target_position + 50)
return True
def move_option_left(self):
if self.options[self.option_index].is_highlighted:
self.options[self.option_index].move_left()
return True
else:
if self.option_index == 0:
self.in_box = False
return False
self.option_index -= 1
return True
def move_option_right(self):
if self.options[self.option_index].is_highlighted:
self.options[self.option_index].move_right()
else:
self.option_index = min(self.option_index + 1, len(self.options) - 1)
def select_option(self):
self.options[self.option_index].is_highlighted = not self.options[self.option_index].is_highlighted
self.options[self.option_index].confirm()
def select(self):
self.in_box = True
def update(self, current_time_ms: float, is_selected: bool):
self.move.update(current_time_ms)
self.blue_arrow_fade.update(current_time_ms)
self.blue_arrow_move.update(current_time_ms)
self.is_selected = is_selected
if self.move.is_finished:
self.y = self.target_position
else:
self.y = self.start_position + (self.move.attribute * self.direction)
for option in self.options:
option.update(current_time_ms)
def _draw_highlighted(self):
tex.draw_texture('box', 'box_highlight', x=self.x, y=self.y)
def _draw_text(self):
text_x = self.x + (tex.textures['box']['box'].width//2) - (self.text.texture.width//2)
text_y = self.y + (tex.textures['box']['box'].height//2) - (self.text.texture.height//2)
if self.is_selected:
self.text.draw(outline_color=ray.BLACK, x=text_x, y=text_y)
else:
if self.name == 'exit':
self.text.draw(outline_color=ray.RED, x=text_x, y=text_y)
else:
self.text.draw(outline_color=self.outline_color, x=text_x, y=text_y)
def draw(self):
tex.draw_texture('box', 'box', x=self.x, y=self.y)
if self.is_selected:
self._draw_highlighted()
if self.in_box:
self.options[self.option_index].draw()
if not self.options[self.option_index].is_highlighted:
tex.draw_texture('background', 'blue_arrow', index=0, x=-self.blue_arrow_move.attribute, fade=self.blue_arrow_fade.attribute)
if self.option_index != len(self.options) - 1:
tex.draw_texture('background', 'blue_arrow', index=1, x=self.blue_arrow_move.attribute, fade=self.blue_arrow_fade.attribute, mirror='horizontal')
self._draw_text()
class BoxManager:
"""BoxManager class for the entry screen"""
def __init__(self, settings_template: dict):
language = global_data.config["general"]["language"]
self.boxes = [Box(config_name, settings_template[config_name]["name"].get(language, settings_template[config_name]["name"]["en"]), settings_template[config_name]["options"]) for config_name in settings_template]
self.num_boxes = len(self.boxes)
self.selected_box_index = 3
self.box_selected = False
for i, box in enumerate(self.boxes):
box.y += 100*i
box.start_position += 100*i
def move_left(self):
"""Move the cursor to the left"""
if self.box_selected:
box = self.boxes[self.selected_box_index]
self.box_selected = box.move_option_left()
else:
moved = True
for box in self.boxes:
if not box.move_left():
moved = False
if moved:
self.selected_box_index = (self.selected_box_index - 1) % self.num_boxes
def move_right(self):
"""Move the cursor to the right"""
if self.box_selected:
box = self.boxes[self.selected_box_index]
box.move_option_right()
else:
moved = True
for box in self.boxes:
if not box.move_right():
moved = False
if moved:
self.selected_box_index = (self.selected_box_index + 1) % self.num_boxes
def select_box(self):
if self.boxes[self.selected_box_index].name == "exit":
return "exit"
if self.box_selected:
box = self.boxes[self.selected_box_index]
box.select_option()
else:
self.box_selected = True
self.boxes[self.selected_box_index].in_box = True
def update(self, current_time_ms: float):
for i, box in enumerate(self.boxes):
is_selected = i == self.selected_box_index and not self.box_selected
box.update(current_time_ms, is_selected)
def draw(self):
for box in self.boxes:
box.draw()
class SettingsScreen(Screen): class SettingsScreen(Screen):
def on_screen_start(self): def on_screen_start(self):
super().on_screen_start() super().on_screen_start()
self.indicator = Indicator(Indicator.State.SELECT) self.config = global_data.config
self.template = json.loads((tex.graphics_path / "settings_template.json").read_text(encoding='utf-8')) self.headers = list(self.config.keys())
self.box_manager = BoxManager(self.template) self.headers.append('Exit')
self.coin_overlay = CoinOverlay() self.header_index = 0
self.allnet_indicator = AllNetIcon() self.setting_index = 0
audio.play_sound('bgm', 'music') self.in_setting_edit = False
self.editing_key = False
self.editing_gamepad = False
def on_screen_end(self, next_screen: str): def on_screen_end(self, next_screen: str):
save_config(global_data.config) save_config(self.config)
global_data.config = self.config
audio.close_audio_device() audio.close_audio_device()
audio.device_type = global_data.config["audio"]["device_type"] audio.device_type = global_data.config["audio"]["device_type"]
audio.target_sample_rate = global_data.config["audio"]["sample_rate"] sample_rate = global_data.config["audio"]["sample_rate"]
if sample_rate < 0:
sample_rate = 44100
audio.target_sample_rate = sample_rate
audio.buffer_size = global_data.config["audio"]["buffer_size"] audio.buffer_size = global_data.config["audio"]["buffer_size"]
audio.volume_presets = global_data.config["volume"] audio.volume_presets = global_data.config["volume"]
audio.init_audio_device() audio.init_audio_device()
logger.info("Settings saved and audio device re-initialized") logger.info("Settings saved and audio device re-initialized")
return super().on_screen_end(next_screen) return next_screen
def handle_input(self): def get_current_settings(self):
if is_l_kat_pressed(): """Get the current section's settings as a list"""
audio.play_sound('kat', 'sound') current_header = self.headers[self.header_index]
self.box_manager.move_left() if current_header == 'Exit' or current_header not in self.config:
elif is_r_kat_pressed(): return []
audio.play_sound('kat', 'sound') return list(self.config[current_header].items())
self.box_manager.move_right()
elif is_l_don_pressed() or is_r_don_pressed(): def handle_boolean_toggle(self, section, key):
audio.play_sound('don', 'sound') """Toggle boolean values"""
box_name = self.box_manager.select_box() self.config[section][key] = not self.config[section][key]
if box_name == 'exit': logger.info(f"Toggled boolean setting: {section}.{key} -> {self.config[section][key]}")
return self.on_screen_end("ENTRY")
def handle_numeric_change(self, section, key, increment):
"""Handle numeric value changes"""
current_value = self.config[section][key]
# Define step sizes for different settings
step_sizes = {
'judge_offset': 1,
'visual_offset': 1,
'sample_rate': 1000,
}
step = step_sizes.get(key, 1)
new_value = current_value + (step * increment)
if key == 'sample_rate':
valid_rates = [-1, 22050, 44100, 48000, 88200, 96000]
current_idx = valid_rates.index(current_value) if current_value in valid_rates else 2
new_idx = max(0, min(len(valid_rates) - 1, current_idx + increment))
new_value = valid_rates[new_idx]
if key == 'buffer_size':
valid_sizes = [-1, 32, 64, 128, 256, 512, 1024]
current_idx = valid_sizes.index(current_value) if current_value in valid_sizes else 2
new_idx = max(0, min(len(valid_sizes) - 1, current_idx + increment))
new_value = valid_sizes[new_idx]
self.config[section][key] = new_value
logger.info(f"Changed numeric setting: {section}.{key} -> {new_value}")
def handle_string_cycle(self, section, key):
"""Cycle through predefined string values"""
current_value = self.config[section][key]
options = {
'language': ['ja', 'en'],
}
if key in options:
values = options[key]
try:
current_idx = values.index(current_value)
new_idx = (current_idx + 1) % len(values)
self.config[section][key] = values[new_idx]
except ValueError:
self.config[section][key] = values[0]
logger.info(f"Cycled string setting: {section}.{key} -> {self.config[section][key]}")
def handle_key_binding(self, section, key):
"""Handle key binding changes"""
self.editing_key = True
logger.info(f"Started key binding edit for: {section}.{key}")
def update_key_binding(self):
"""Update key binding based on input"""
key_pressed = ray.get_key_pressed()
if key_pressed != 0:
# Convert keycode to character
if 65 <= key_pressed <= 90: # A-Z
new_key = chr(key_pressed)
current_header = self.headers[self.header_index]
settings = self.get_current_settings()
if settings:
setting_key, _ = settings[self.setting_index]
self.config[current_header][setting_key] = [new_key]
self.editing_key = False
logger.info(f"Key binding updated: {current_header}.{setting_key} -> {new_key}")
elif key_pressed == global_data.config["keys"]["back_key"]:
self.editing_key = False
logger.info("Key binding edit cancelled")
def handle_gamepad_binding(self, section, key):
self.editing_gamepad = True
logger.info(f"Started gamepad binding edit for: {section}.{key}")
def update_gamepad_binding(self):
"""Update gamepad binding based on input"""
button_pressed = ray.get_gamepad_button_pressed()
if button_pressed != 0:
current_header = self.headers[self.header_index]
settings = self.get_current_settings()
if settings:
setting_key, _ = settings[self.setting_index]
self.config[current_header][setting_key] = [button_pressed]
self.editing_gamepad = False
logger.info(f"Gamepad binding updated: {current_header}.{setting_key} -> {button_pressed}")
if ray.is_key_pressed(global_data.config["keys"]["back_key"]):
self.editing_gamepad = False
logger.info("Gamepad binding edit cancelled")
def update(self): def update(self):
super().update() super().update()
current_time = get_current_ms() # Handle key binding editing
self.indicator.update(current_time) if self.editing_key:
self.box_manager.update(current_time) self.update_key_binding()
return self.handle_input() return
if self.editing_gamepad:
self.update_gamepad_binding()
return
current_header = self.headers[self.header_index]
# Exit handling
if current_header == 'Exit' and (is_l_don_pressed() or is_r_don_pressed()):
logger.info("Exiting settings screen")
return self.on_screen_end("ENTRY")
# Navigation between sections
if not self.in_setting_edit:
if is_r_kat_pressed():
self.header_index = (self.header_index + 1) % len(self.headers)
self.setting_index = 0
logger.info(f"Navigated to next section: {self.headers[self.header_index]}")
elif is_l_kat_pressed():
self.header_index = (self.header_index - 1) % len(self.headers)
self.setting_index = 0
logger.info(f"Navigated to previous section: {self.headers[self.header_index]}")
elif (is_l_don_pressed() or is_r_don_pressed()) and current_header != 'Exit':
self.in_setting_edit = True
logger.info(f"Entered section edit: {current_header}")
else:
# Navigation within settings
settings = self.get_current_settings()
if not settings:
self.in_setting_edit = False
return
if is_r_kat_pressed():
self.setting_index = (self.setting_index + 1) % len(settings)
logger.info(f"Navigated to next setting: {settings[self.setting_index][0]}")
elif is_l_kat_pressed():
self.setting_index = (self.setting_index - 1) % len(settings)
logger.info(f"Navigated to previous setting: {settings[self.setting_index][0]}")
elif is_r_don_pressed():
# Modify setting value
setting_key, setting_value = settings[self.setting_index]
if isinstance(setting_value, bool):
self.handle_boolean_toggle(current_header, setting_key)
elif isinstance(setting_value, (int, float)):
self.handle_numeric_change(current_header, setting_key, 1)
elif isinstance(setting_value, str):
if 'keys' in current_header:
self.handle_key_binding(current_header, setting_key)
elif 'gamepad' in current_header:
self.handle_gamepad_binding(current_header, setting_key)
else:
self.handle_string_cycle(current_header, setting_key)
elif isinstance(setting_value, list) and len(setting_value) > 0:
if isinstance(setting_value[0], str) and len(setting_value[0]) == 1:
# Key binding
self.handle_key_binding(current_header, setting_key)
elif isinstance(setting_value[0], int):
self.handle_gamepad_binding(current_header, setting_key)
elif is_l_don_pressed():
# Modify setting value (reverse direction for numeric)
setting_key, setting_value = settings[self.setting_index]
if isinstance(setting_value, bool):
self.handle_boolean_toggle(current_header, setting_key)
elif isinstance(setting_value, (int, float)):
self.handle_numeric_change(current_header, setting_key, -1)
elif isinstance(setting_value, str):
if ('keys' not in current_header) and ('gamepad' not in current_header):
self.handle_string_cycle(current_header, setting_key)
elif ray.is_key_pressed(global_data.config["keys"]["back_key"]):
self.in_setting_edit = False
logger.info("Exited section edit")
def draw(self): def draw(self):
tex.draw_texture('background', 'background') ray.draw_rectangle(0, 0, tex.screen_width, tex.screen_height, ray.BLACK)
self.box_manager.draw() # Draw title
tex.draw_texture('background', 'footer') ray.draw_rectangle(0, 0, tex.screen_width, tex.screen_height, ray.BLACK)
self.indicator.draw(tex.skin_config['song_select_indicator'].x, tex.skin_config['song_select_indicator'].y) ray.draw_text("SETTINGS", 20, 20, 30, ray.WHITE)
self.coin_overlay.draw()
self.allnet_indicator.draw() # Draw section headers
current_header = self.headers[self.header_index]
for i, key in enumerate(self.headers):
color = ray.GREEN
if key == current_header:
color = ray.YELLOW if not self.in_setting_edit else ray.ORANGE
ray.draw_text(f'{key}', 20, i*25 + 70, 20, color)
# Draw current section settings
if current_header != 'Exit' and current_header in self.config:
settings = self.get_current_settings()
# Draw settings list
for i, (key, value) in enumerate(settings):
color = ray.GREEN
if self.in_setting_edit and i == self.setting_index:
color = ray.YELLOW
# Format value display
if isinstance(value, list):
display_value = ', '.join(map(str, value))
else:
display_value = str(value)
if key == 'device_type' and not isinstance(value, list):
display_value = f'{display_value} ({audio.get_host_api_name(value)})'
ray.draw_text(f'{key}: {display_value}', 250, i*25 + 70, 20, color)
# Draw instructions
y_offset = len(settings) * 25 + 150
if not self.in_setting_edit:
ray.draw_text("Don/Kat: Navigate sections", 20, y_offset, 16, ray.GRAY)
ray.draw_text("L/R Don: Enter section", 20, y_offset + 20, 16, ray.GRAY)
else:
ray.draw_text("Don/Kat: Navigate settings", 20, y_offset, 16, ray.GRAY)
ray.draw_text("L/R Don: Modify value", 20, y_offset + 20, 16, ray.GRAY)
ray.draw_text("ESC: Back to sections", 20, y_offset + 40, 16, ray.GRAY)
if self.editing_key:
ray.draw_text("Press a key to bind (ESC to cancel)", 20, y_offset + 60, 16, ray.RED)
else:
# Draw exit instruction
ray.draw_text("Press Don to exit settings", 250, 100, 20, ray.GREEN)

View File

@@ -6,7 +6,7 @@ import pyray as ray
from libs.audio import audio from libs.audio import audio
from libs.global_data import PlayerNum from libs.global_data import PlayerNum
from libs.parsers.tja import TJAParser from libs.tja import TJAParser
from libs.utils import get_current_ms, global_data from libs.utils import get_current_ms, global_data
from libs.video import VideoPlayer from libs.video import VideoPlayer
from scenes.game import ( from scenes.game import (