mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
add the majority of dan mode
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
|
||||
import logging
|
||||
import pyray as ray
|
||||
|
||||
from libs.audio import audio
|
||||
@@ -6,10 +8,12 @@ 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 navigator
|
||||
from libs.file_navigator import DanCourse, navigator
|
||||
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 scenes.song_select import SongSelectScreen, State
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DanSelectScreen(Screen):
|
||||
def on_screen_start(self):
|
||||
@@ -23,9 +27,21 @@ class DanSelectScreen(Screen):
|
||||
self.indicator = Indicator(Indicator.State.SELECT)
|
||||
self.player = DanSelectPlayer(str(global_data.player_num))
|
||||
self.state = State.BROWSING
|
||||
self.transition = Transition('', '')
|
||||
self.last_moved = 0
|
||||
|
||||
def on_screen_end(self, next_screen: str):
|
||||
session_data = global_data.session_data[global_data.player_num-1]
|
||||
current_item = self.navigator.get_current_item()
|
||||
if isinstance(current_item, DanCourse):
|
||||
session_data.selected_song = current_item.charts[0]
|
||||
session_data.selected_dan = current_item.charts
|
||||
session_data.selected_dan_exam = current_item.exams
|
||||
session_data.song_title = current_item.title
|
||||
session_data.dan_color = current_item.color
|
||||
else:
|
||||
self.navigator.in_dan_select = False
|
||||
self.navigator.go_back()
|
||||
return super().on_screen_end(next_screen)
|
||||
|
||||
def handle_input_browsing(self):
|
||||
@@ -47,27 +63,36 @@ class DanSelectScreen(Screen):
|
||||
self.navigator.navigate_right()
|
||||
self.last_moved = current_time
|
||||
elif action == "go_back":
|
||||
self.navigator.go_back()
|
||||
return action
|
||||
elif action == "select_song":
|
||||
pass
|
||||
self.state = State.SONG_SELECTED
|
||||
|
||||
def handle_input(self, state, screen):
|
||||
"""Main input dispatcher. Delegates to state-specific handlers."""
|
||||
if state == State.BROWSING:
|
||||
screen.handle_input_browsing()
|
||||
return screen.handle_input_browsing()
|
||||
elif state == State.SONG_SELECTED:
|
||||
screen.handle_input_selected()
|
||||
res = self.player.handle_input_selected()
|
||||
if res == 'confirm':
|
||||
self.transition.start()
|
||||
elif res == 'cancel':
|
||||
self.state = State.BROWSING
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
current_time = get_current_ms()
|
||||
self.indicator.update(current_time)
|
||||
self.timer.update(current_time)
|
||||
self.transition.update(current_time)
|
||||
if self.transition.is_finished:
|
||||
return self.on_screen_end("GAME_DAN")
|
||||
for song in self.navigator.items:
|
||||
song.box.update(False)
|
||||
song.box.is_open = song.box.position == SongSelectScreen.BOX_CENTER + 150
|
||||
self.player.update(current_time)
|
||||
self.handle_input(self.state, self)
|
||||
res = self.handle_input(self.state, self)
|
||||
if res == 'go_back':
|
||||
return self.on_screen_end("SONG_SELECT")
|
||||
|
||||
def draw(self):
|
||||
tex.draw_texture('global', 'bg')
|
||||
@@ -81,11 +106,14 @@ class DanSelectScreen(Screen):
|
||||
box.draw(box.position, 95, False)
|
||||
else:
|
||||
box.draw(box.position, 95, False)
|
||||
if self.state == State.SONG_SELECTED:
|
||||
ray.draw_rectangle(0, 0, 1280, 720, ray.fade(ray.BLACK, min(0.5, self.player.confirmation_window.fade_in.attribute)))
|
||||
self.player.draw()
|
||||
self.indicator.draw(410, 575)
|
||||
self.timer.draw()
|
||||
self.coin_overlay.draw()
|
||||
tex.draw_texture('global', 'dan_select')
|
||||
self.transition.draw()
|
||||
self.allnet_indicator.draw()
|
||||
|
||||
class DanSelectPlayer:
|
||||
@@ -95,10 +123,12 @@ class DanSelectPlayer:
|
||||
self.prev_diff = -3
|
||||
self.selected_song = False
|
||||
self.is_ura = False
|
||||
self.is_confirmed = False
|
||||
self.ura_toggle = 0
|
||||
self.diff_select_move_right = False
|
||||
self.neiro_selector = None
|
||||
self.modifier_selector = None
|
||||
self.confirmation_window = ConfirmationWindow()
|
||||
|
||||
# Player-specific objects
|
||||
self.chara = Chara2D(int(self.player_num) - 1, 100)
|
||||
@@ -110,6 +140,7 @@ class DanSelectPlayer:
|
||||
"""Update player state"""
|
||||
self.nameplate.update(current_time)
|
||||
self.chara.update(current_time, 100, False, False)
|
||||
self.confirmation_window.update(current_time, self.is_confirmed)
|
||||
|
||||
def handle_input_browsing(self, last_moved, selected_item):
|
||||
"""Handle input for browsing songs. Returns action string or None."""
|
||||
@@ -141,6 +172,7 @@ class DanSelectPlayer:
|
||||
audio.play_sound('cancel', 'sound')
|
||||
return "go_back"
|
||||
else:
|
||||
self.confirmation_window.start()
|
||||
return "select_song"
|
||||
|
||||
return None
|
||||
@@ -153,12 +185,24 @@ class DanSelectPlayer:
|
||||
if state == State.BROWSING:
|
||||
screen.handle_input_browsing()
|
||||
elif state == State.SONG_SELECTED:
|
||||
screen.handle_input_selected()
|
||||
res = screen.handle_input_selected()
|
||||
|
||||
def handle_input_selected(self, current_item):
|
||||
if res:
|
||||
return res
|
||||
|
||||
def handle_input_selected(self):
|
||||
"""Handle input for selecting difficulty. Returns 'cancel', 'confirm', or None"""
|
||||
if is_l_kat_pressed(self.player_num):
|
||||
self.is_confirmed = False
|
||||
if is_r_kat_pressed(self.player_num):
|
||||
self.is_confirmed = True
|
||||
|
||||
if is_l_don_pressed(self.player_num) or is_r_don_pressed(self.player_num):
|
||||
return "confirm"
|
||||
if self.is_confirmed:
|
||||
return "confirm"
|
||||
else:
|
||||
self.confirmation_window = ConfirmationWindow()
|
||||
return "cancel"
|
||||
return None
|
||||
|
||||
def draw(self):
|
||||
@@ -168,3 +212,28 @@ class DanSelectPlayer:
|
||||
else:
|
||||
self.nameplate.draw(950, 640)
|
||||
self.chara.draw(mirror=True, x=950, y=410)
|
||||
|
||||
self.confirmation_window.draw()
|
||||
|
||||
class ConfirmationWindow:
|
||||
def __init__(self):
|
||||
self.fade_in = tex.get_animation(8, is_copy=True)
|
||||
self.side = 0
|
||||
|
||||
def start(self):
|
||||
self.fade_in.start()
|
||||
|
||||
def update(self, current_time_ms: float, is_confirmed: bool):
|
||||
self.fade_in.update(current_time_ms)
|
||||
self.side = is_confirmed
|
||||
|
||||
def draw(self):
|
||||
tex.draw_texture('confirm_box', 'bg', fade=self.fade_in.attribute)
|
||||
tex.draw_texture('confirm_box', 'confirmation_text', fade=self.fade_in.attribute)
|
||||
for i in range(2):
|
||||
tex.draw_texture('confirm_box', 'selection_box', index=i, fade=self.fade_in.attribute)
|
||||
|
||||
tex.draw_texture('confirm_box', 'selection_box_highlight', index=self.side, fade=self.fade_in.attribute)
|
||||
tex.draw_texture('confirm_box', 'selection_box_outline', index=self.side, fade=self.fade_in.attribute)
|
||||
tex.draw_texture('confirm_box', 'yes', fade=self.fade_in.attribute)
|
||||
tex.draw_texture('confirm_box', 'no', fade=self.fade_in.attribute)
|
||||
|
||||
104
scenes/game.py
104
scenes/game.py
@@ -66,8 +66,8 @@ class GameScreen(Screen):
|
||||
ray.set_shader_value_texture(self.mask_shader, ray.get_shader_location(self.mask_shader, "texture0"), tex.textures['balloon']['rainbow_mask'].texture)
|
||||
ray.set_shader_value_texture(self.mask_shader, ray.get_shader_location(self.mask_shader, "texture1"), tex.textures['balloon']['rainbow'].texture)
|
||||
session_data = global_data.session_data[global_data.player_num-1]
|
||||
self.init_tja(global_data.selected_song)
|
||||
logger.info(f"TJA initialized for song: {global_data.selected_song}")
|
||||
self.init_tja(session_data.selected_song)
|
||||
logger.info(f"TJA initialized for song: {session_data.selected_song}")
|
||||
self.load_hitsounds()
|
||||
self.song_info = SongInfo(session_data.song_title, session_data.genre_index)
|
||||
self.result_transition = ResultTransition(global_data.player_num)
|
||||
@@ -292,53 +292,14 @@ class Player:
|
||||
|
||||
def __init__(self, tja: TJAParser, player_number: int, difficulty: int, is_2p: bool, modifiers: Modifiers):
|
||||
self.is_2p = is_2p
|
||||
self.is_dan = False
|
||||
self.player_number = str(player_number)
|
||||
self.difficulty = difficulty
|
||||
self.visual_offset = global_data.config["general"]["visual_offset"]
|
||||
self.modifiers = modifiers
|
||||
self.tja = tja
|
||||
|
||||
notes, self.branch_m, self.branch_e, self.branch_n = tja.notes_to_position(self.difficulty)
|
||||
self.play_notes, self.draw_note_list, self.draw_bar_list = apply_modifiers(notes, self.modifiers)
|
||||
self.end_time = 0
|
||||
if self.play_notes:
|
||||
self.end_time = self.play_notes[-1].hit_ms
|
||||
if self.branch_m:
|
||||
for section in self.branch_m:
|
||||
if section.play_notes:
|
||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
||||
if self.branch_e:
|
||||
for section in self.branch_e:
|
||||
if section.play_notes:
|
||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
||||
if self.branch_n:
|
||||
for section in self.branch_n:
|
||||
if section.play_notes:
|
||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
||||
|
||||
self.don_notes = deque([note for note in self.play_notes if note.type in {1, 3}])
|
||||
self.kat_notes = deque([note for note in self.play_notes if note.type in {2, 4}])
|
||||
self.other_notes = deque([note for note in self.play_notes if note.type not in {1, 2, 3, 4}])
|
||||
self.total_notes = len([note for note in self.play_notes if 0 < note.type < 5])
|
||||
total_notes = notes
|
||||
if self.branch_m:
|
||||
for section in self.branch_m:
|
||||
self.total_notes += len([note for note in section.play_notes if 0 < note.type < 5])
|
||||
total_notes += section
|
||||
self.base_score = calculate_base_score(total_notes)
|
||||
|
||||
#Note management
|
||||
self.current_bars: list[Note] = []
|
||||
self.current_notes_draw: list[Note | Drumroll | Balloon] = []
|
||||
self.is_drumroll = False
|
||||
self.curr_drumroll_count = 0
|
||||
self.is_balloon = False
|
||||
self.curr_balloon_count = 0
|
||||
self.is_branch = False
|
||||
self.curr_branch_reqs = []
|
||||
self.branch_condition_count = 0
|
||||
self.branch_condition = ''
|
||||
self.balloon_index = 0
|
||||
self.bpm = self.play_notes[0].bpm if self.play_notes else 120
|
||||
self.reset_chart()
|
||||
|
||||
#Score management
|
||||
self.good_count = 0
|
||||
@@ -382,6 +343,50 @@ class Player:
|
||||
self.autoplay_hit_side = 'L'
|
||||
self.last_subdivision = -1
|
||||
|
||||
def reset_chart(self):
|
||||
notes, self.branch_m, self.branch_e, self.branch_n = self.tja.notes_to_position(self.difficulty)
|
||||
self.play_notes, self.draw_note_list, self.draw_bar_list = apply_modifiers(notes, self.modifiers)
|
||||
self.end_time = 0
|
||||
if self.play_notes:
|
||||
self.end_time = self.play_notes[-1].hit_ms
|
||||
if self.branch_m:
|
||||
for section in self.branch_m:
|
||||
if section.play_notes:
|
||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
||||
if self.branch_e:
|
||||
for section in self.branch_e:
|
||||
if section.play_notes:
|
||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
||||
if self.branch_n:
|
||||
for section in self.branch_n:
|
||||
if section.play_notes:
|
||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
||||
|
||||
self.don_notes = deque([note for note in self.play_notes if note.type in {1, 3}])
|
||||
self.kat_notes = deque([note for note in self.play_notes if note.type in {2, 4}])
|
||||
self.other_notes = deque([note for note in self.play_notes if note.type not in {1, 2, 3, 4}])
|
||||
self.total_notes = len([note for note in self.play_notes if 0 < note.type < 5])
|
||||
total_notes = notes
|
||||
if self.branch_m:
|
||||
for section in self.branch_m:
|
||||
self.total_notes += len([note for note in section.play_notes if 0 < note.type < 5])
|
||||
total_notes += section
|
||||
self.base_score = calculate_base_score(total_notes)
|
||||
|
||||
#Note management
|
||||
self.current_bars: list[Note] = []
|
||||
self.current_notes_draw: list[Note | Drumroll | Balloon] = []
|
||||
self.is_drumroll = False
|
||||
self.curr_drumroll_count = 0
|
||||
self.is_balloon = False
|
||||
self.curr_balloon_count = 0
|
||||
self.is_branch = False
|
||||
self.curr_branch_reqs = []
|
||||
self.branch_condition_count = 0
|
||||
self.branch_condition = ''
|
||||
self.balloon_index = 0
|
||||
self.bpm = self.play_notes[0].bpm if self.play_notes else 120
|
||||
|
||||
def merge_branch_section(self, branch_section: NoteList, current_ms: float):
|
||||
"""Merges the branch notes into the current notes"""
|
||||
self.play_notes.extend(branch_section.play_notes)
|
||||
@@ -1043,7 +1048,7 @@ class Player:
|
||||
for modifier in modifiers_to_draw:
|
||||
tex.draw_texture('lane', modifier, index=self.is_2p)
|
||||
|
||||
def draw(self, ms_from_start: float, start_ms: float, mask_shader: ray.Shader):
|
||||
def draw(self, ms_from_start: float, start_ms: float, mask_shader: ray.Shader, dan_transition = None):
|
||||
# Group 1: Background and lane elements
|
||||
tex.draw_texture('lane', 'lane_background', index=self.is_2p)
|
||||
if self.branch_indicator is not None:
|
||||
@@ -1062,9 +1067,13 @@ class Player:
|
||||
# Group 3: Notes and bars (game content)
|
||||
self.draw_bars(ms_from_start)
|
||||
self.draw_notes(ms_from_start, start_ms)
|
||||
if dan_transition is not None:
|
||||
dan_transition.draw()
|
||||
|
||||
# Group 4: Lane covers and UI elements (batch similar textures)
|
||||
tex.draw_texture('lane', f'{self.player_number}p_lane_cover', index=self.is_2p)
|
||||
if self.is_dan:
|
||||
tex.draw_texture('lane', 'dan_lane_cover')
|
||||
tex.draw_texture('lane', 'drum', index=self.is_2p)
|
||||
if self.ending_anim is not None:
|
||||
self.ending_anim.draw()
|
||||
@@ -1085,7 +1094,10 @@ class Player:
|
||||
else:
|
||||
tex.draw_texture('lane', 'lane_score_cover', index=self.is_2p)
|
||||
tex.draw_texture('lane', f'{self.player_number}p_icon', index=self.is_2p)
|
||||
tex.draw_texture('lane', 'lane_difficulty', frame=self.difficulty, index=self.is_2p)
|
||||
if self.is_dan:
|
||||
tex.draw_texture('lane', 'lane_difficulty', frame=6)
|
||||
else:
|
||||
tex.draw_texture('lane', 'lane_difficulty', frame=self.difficulty, index=self.is_2p)
|
||||
if self.judge_counter is not None:
|
||||
self.judge_counter.draw()
|
||||
|
||||
|
||||
@@ -1,28 +1,105 @@
|
||||
from typing import override
|
||||
import pyray as ray
|
||||
import logging
|
||||
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 global_data
|
||||
from libs.global_objects import AllNetIcon
|
||||
from libs.tja import TJAParser
|
||||
from libs.transition import Transition
|
||||
from scenes.game import GameScreen, SongInfo
|
||||
from libs.utils import OutlinedText, get_current_ms
|
||||
from libs.texture import tex
|
||||
from scenes.game import GameScreen, ResultTransition, SongInfo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SCREEN_WIDTH = 1280
|
||||
SCREEN_HEIGHT = 720
|
||||
|
||||
class DanGameScreen(GameScreen):
|
||||
JUDGE_X = 414
|
||||
|
||||
@override
|
||||
def on_screen_start(self):
|
||||
super().on_screen_start()
|
||||
self.init_tja(global_data.selected_song)
|
||||
logger.info(f"TJA initialized for song: {global_data.selected_song}")
|
||||
self.song_info = SongInfo(session_data.song_title, session_data.genre_index)
|
||||
self.mask_shader = ray.load_shader("shader/outline.vs", "shader/mask.fs")
|
||||
self.current_ms = 0
|
||||
self.end_ms = 0
|
||||
self.start_delay = 4000
|
||||
self.song_started = False
|
||||
self.song_music = None
|
||||
self.song_index = 0
|
||||
tex.unload_textures()
|
||||
tex.load_screen_textures('game')
|
||||
audio.load_screen_sounds('game')
|
||||
if global_data.config["general"]["nijiiro_notes"]:
|
||||
# drop original
|
||||
if "notes" in tex.textures:
|
||||
del tex.textures["notes"]
|
||||
# load nijiiro, rename "notes"
|
||||
# to leave hardcoded 'notes' in calls below
|
||||
tex.load_zip("game", "notes_nijiiro")
|
||||
tex.textures["notes"] = tex.textures.pop("notes_nijiiro")
|
||||
logger.info("Loaded nijiiro notes textures")
|
||||
ray.set_shader_value_texture(self.mask_shader, ray.get_shader_location(self.mask_shader, "texture0"), tex.textures['balloon']['rainbow_mask'].texture)
|
||||
ray.set_shader_value_texture(self.mask_shader, ray.get_shader_location(self.mask_shader, "texture1"), tex.textures['balloon']['rainbow'].texture)
|
||||
session_data = global_data.session_data[global_data.player_num-1]
|
||||
songs = session_data.selected_dan
|
||||
self.exams = session_data.selected_dan_exam
|
||||
self.total_notes = 0
|
||||
for song, genre_index, difficulty in songs:
|
||||
notes, branch_m, branch_e, branch_n = song.notes_to_position(difficulty)
|
||||
self.total_notes += sum(1 for note in notes.play_notes if note.type < 5)
|
||||
for branch in branch_m:
|
||||
self.total_notes += sum(1 for note in branch.play_notes if note.type < 5)
|
||||
for branch in branch_e:
|
||||
self.total_notes += sum(1 for note in branch.play_notes if note.type < 5)
|
||||
for branch in branch_n:
|
||||
self.total_notes += sum(1 for note in branch.play_notes if note.type < 5)
|
||||
song, genre_index, difficulty = songs[self.song_index]
|
||||
session_data.selected_difficulty = difficulty
|
||||
self.hori_name = OutlinedText(session_data.song_title, 40, ray.WHITE)
|
||||
self.init_tja(song.file_path)
|
||||
self.color = session_data.dan_color
|
||||
self.player_1.is_dan = True
|
||||
self.player_1.gauge = DanGauge(str(global_data.player_num), self.total_notes)
|
||||
logger.info(f"TJA initialized for song: {song.file_path}")
|
||||
self.load_hitsounds()
|
||||
self.song_info = SongInfo(song.metadata.title.get(global_data.config["general"]["language"], "en"), genre_index)
|
||||
self.result_transition = ResultTransition(4)
|
||||
self.bpm = self.tja.metadata.bpm
|
||||
self.background = Background(global_data.player_num, self.bpm, scene_preset='DAN')
|
||||
self.transition = Transition('', '', is_second=True)
|
||||
self.transition.start()
|
||||
self.dan_transition = DanTransition()
|
||||
self.dan_transition.start()
|
||||
self.allnet_indicator = AllNetIcon()
|
||||
|
||||
def change_song(self):
|
||||
session_data = global_data.session_data[global_data.player_num-1]
|
||||
songs = session_data.selected_dan
|
||||
song, genre_index, difficulty = songs[self.song_index]
|
||||
session_data.selected_difficulty = difficulty
|
||||
self.player_1.difficulty = difficulty
|
||||
self.tja = TJAParser(song.file_path, start_delay=self.start_delay, distance=SCREEN_WIDTH - GameScreen.JUDGE_X)
|
||||
audio.unload_music_stream(self.song_music)
|
||||
self.song_music = None
|
||||
self.song_started = False
|
||||
|
||||
if self.tja.metadata.wave.exists() and self.tja.metadata.wave.is_file() and self.song_music is None:
|
||||
self.song_music = audio.load_music_stream(self.tja.metadata.wave, 'song')
|
||||
self.player_1.tja = self.tja
|
||||
self.player_1.reset_chart()
|
||||
self.dan_transition.start()
|
||||
self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000)
|
||||
|
||||
def update(self):
|
||||
super().update()
|
||||
super(GameScreen, self).update()
|
||||
current_time = get_current_ms()
|
||||
self.transition.update(current_time)
|
||||
self.current_ms = current_time - self.start_ms
|
||||
self.dan_transition.update(current_time)
|
||||
self.start_song(current_time)
|
||||
self.update_background(current_time)
|
||||
|
||||
@@ -31,4 +108,218 @@ class DanGameScreen(GameScreen):
|
||||
|
||||
self.player_1.update(self.current_ms, current_time, self.background)
|
||||
self.song_info.update(current_time)
|
||||
self.result_transition.update(current_time)
|
||||
if self.result_transition.is_finished and not audio.is_sound_playing('result_transition'):
|
||||
logger.info("Result transition finished, moving to RESULT screen")
|
||||
return self.on_screen_end('RESULT')
|
||||
elif self.current_ms >= self.player_1.end_time + 1000:
|
||||
session_data = global_data.session_data[global_data.player_num-1]
|
||||
if self.song_index == len(session_data.selected_dan) - 1:
|
||||
if self.end_ms != 0:
|
||||
if current_time >= self.end_ms + 1000:
|
||||
if self.player_1.ending_anim is None:
|
||||
self.spawn_ending_anims()
|
||||
if current_time >= self.end_ms + 8533.34:
|
||||
if not self.result_transition.is_started:
|
||||
self.result_transition.start()
|
||||
audio.play_sound('result_transition', 'voice')
|
||||
logger.info("Result transition started and voice played")
|
||||
else:
|
||||
self.end_ms = current_time
|
||||
else:
|
||||
self.song_index += 1
|
||||
self.dan_transition.start()
|
||||
self.change_song()
|
||||
|
||||
return self.global_keys()
|
||||
|
||||
def draw_dan_info(self):
|
||||
tex.draw_texture('dan_info', 'total_notes')
|
||||
counter = str(self.total_notes - self.player_1.good_count - self.player_1.ok_count - self.player_1.bad_count)
|
||||
self._draw_counter(counter, margin=45, texture='total_notes_counter')
|
||||
|
||||
for i, exam in enumerate(self.exams):
|
||||
y_offset = i * 94
|
||||
tex.draw_texture('dan_info', 'exam_bg', y=y_offset)
|
||||
tex.draw_texture('dan_info', 'exam_overlay_1', y=y_offset)
|
||||
|
||||
# Get progress based on exam type
|
||||
progress = self._get_exam_progress(exam) / exam.red
|
||||
if exam.range == 'less':
|
||||
progress = 1 - progress
|
||||
self._draw_progress_bar(progress, y_offset)
|
||||
# Draw exam type and counter
|
||||
counter = str(exam.red)
|
||||
self._draw_counter(counter, margin=22, texture='value_counter', index=0, y=y_offset)
|
||||
tex.draw_texture('dan_info', f'exam_{exam.type}', y=y_offset, x=-len(counter)*20)
|
||||
|
||||
if exam.range == 'less':
|
||||
tex.draw_texture('dan_info', 'exam_less', y=y_offset)
|
||||
elif exam.range == 'more':
|
||||
tex.draw_texture('dan_info', 'exam_more', y=y_offset)
|
||||
|
||||
tex.draw_texture('dan_info', 'exam_overlay_2', y=y_offset)
|
||||
if exam.range == 'less':
|
||||
counter = str(max(0, exam.red - self._get_exam_progress(exam)))
|
||||
elif exam.range == 'more':
|
||||
counter = str(max(0, self._get_exam_progress(exam)))
|
||||
self._draw_counter(counter, margin=22, texture='value_counter', index=1, y=y_offset)
|
||||
if exam.type == 'gauge':
|
||||
tex.draw_texture('dan_info', 'exam_percent', y=y_offset, index=1)
|
||||
|
||||
tex.draw_texture('dan_info', 'frame', frame=self.color)
|
||||
if self.hori_name is not None:
|
||||
self.hori_name.draw(outline_color=ray.BLACK, x=154 - (self.hori_name.texture.width//2),
|
||||
y=392, x2=min(self.hori_name.texture.width, 275)-self.hori_name.texture.width)
|
||||
|
||||
def _draw_counter(self, counter, margin, texture, index=None, y=0):
|
||||
"""Helper to draw digit counters"""
|
||||
for j in range(len(counter)):
|
||||
kwargs = {'frame': int(counter[j]), 'x': -(len(counter) - j) * margin, 'y': y}
|
||||
if index is not None:
|
||||
kwargs['index'] = index
|
||||
tex.draw_texture('dan_info', texture, **kwargs)
|
||||
|
||||
def _get_exam_progress(self, exam: Exam) -> int:
|
||||
"""Get progress value based on exam type"""
|
||||
type_mapping = {
|
||||
'gauge': (self.player_1.gauge.gauge_length / self.player_1.gauge.gauge_max) * 100,
|
||||
'judgeperfect': self.player_1.good_count,
|
||||
'judgegood': self.player_1.ok_count,
|
||||
'judgebad': self.player_1.bad_count,
|
||||
'score': self.player_1.score,
|
||||
'combo': self.player_1.max_combo
|
||||
}
|
||||
return int(type_mapping.get(exam.type, 0))
|
||||
|
||||
def _draw_progress_bar(self, progress, y_offset):
|
||||
"""Draw the progress bar with appropriate color"""
|
||||
progress = max(0, progress) # Clamp to 0 minimum
|
||||
progress = min(progress, 1) # Clamp to 1 maximum
|
||||
|
||||
if progress == 1:
|
||||
texture = 'exam_max'
|
||||
elif progress >= 0.5:
|
||||
texture = 'exam_gold'
|
||||
else:
|
||||
texture = 'exam_red'
|
||||
|
||||
tex.draw_texture('dan_info', texture, x2=940*progress, y=y_offset)
|
||||
|
||||
@override
|
||||
def draw(self):
|
||||
self.background.draw()
|
||||
self.draw_dan_info()
|
||||
self.player_1.draw(self.current_ms, self.start_ms, self.mask_shader, dan_transition=self.dan_transition)
|
||||
self.draw_overlay()
|
||||
|
||||
|
||||
class DanTransition:
|
||||
def __init__(self):
|
||||
self.move = tex.get_animation(26)
|
||||
self.is_finished = False
|
||||
|
||||
def start(self):
|
||||
self.move.start()
|
||||
self.is_finished = False
|
||||
|
||||
def update(self, current_time):
|
||||
self.move.update(current_time)
|
||||
self.is_finished = self.move.is_finished
|
||||
|
||||
def draw(self):
|
||||
tex.draw_texture('dan', 'transition', index=0, x=self.move.attribute, mirror='horizontal')
|
||||
tex.draw_texture('dan', 'transition', index=1, x=-self.move.attribute)
|
||||
|
||||
|
||||
class DanGauge:
|
||||
"""The player's gauge"""
|
||||
def __init__(self, player_num: str, total_notes: int):
|
||||
self.player_num = player_num
|
||||
self.string_diff = "_hard"
|
||||
self.gauge_length = 0
|
||||
self.previous_length = 0
|
||||
self.visual_length = 0
|
||||
self.total_notes = total_notes
|
||||
self.gauge_max = 89
|
||||
self.tamashii_fire_change = tex.get_animation(25)
|
||||
self.is_clear = False
|
||||
self.is_rainbow = False
|
||||
self.gauge_update_anim = tex.get_animation(10)
|
||||
self.rainbow_fade_in = None
|
||||
self.rainbow_animation = None
|
||||
|
||||
def add_good(self):
|
||||
"""Adds a good note to the gauge"""
|
||||
self.gauge_update_anim.start()
|
||||
self.previous_length = int(self.gauge_length)
|
||||
self.gauge_length += (1 / (self.total_notes * (self.gauge_max / 100))) * 100
|
||||
if self.gauge_length > self.gauge_max:
|
||||
self.gauge_length = self.gauge_max
|
||||
|
||||
if int(self.gauge_length * 8) % 8 == 0:
|
||||
self.visual_length = int(self.gauge_length * 8)
|
||||
|
||||
def add_ok(self):
|
||||
"""Adds an ok note to the gauge"""
|
||||
self.gauge_update_anim.start()
|
||||
self.previous_length = int(self.gauge_length)
|
||||
self.gauge_length += (0.5 / (self.total_notes * (self.gauge_max / 100))) * 100
|
||||
if self.gauge_length > self.gauge_max:
|
||||
self.gauge_length = self.gauge_max
|
||||
|
||||
if int(self.gauge_length * 8) % 8 == 0:
|
||||
self.visual_length = int(self.gauge_length * 8)
|
||||
|
||||
def add_bad(self):
|
||||
"""Adds a bad note to the gauge"""
|
||||
self.previous_length = int(self.gauge_length)
|
||||
self.gauge_length -= (2 / (self.total_notes * (self.gauge_max / 100))) * 100
|
||||
if self.gauge_length < 0:
|
||||
self.gauge_length = 0
|
||||
|
||||
if int(self.gauge_length * 8) % 8 == 0:
|
||||
self.visual_length = int(self.gauge_length * 8)
|
||||
|
||||
def update(self, current_ms: float):
|
||||
self.is_rainbow = self.gauge_length == self.gauge_max
|
||||
self.is_clear = self.is_rainbow
|
||||
if self.gauge_length == self.gauge_max and self.rainbow_fade_in is None:
|
||||
self.rainbow_fade_in = Animation.create_fade(450, initial_opacity=0.0, final_opacity=1.0)
|
||||
self.rainbow_fade_in.start()
|
||||
self.gauge_update_anim.update(current_ms)
|
||||
self.tamashii_fire_change.update(current_ms)
|
||||
|
||||
if self.rainbow_fade_in is not None:
|
||||
self.rainbow_fade_in.update(current_ms)
|
||||
|
||||
if self.rainbow_animation is None:
|
||||
self.rainbow_animation = Animation.create_texture_change((16.67*8) * 3, textures=[((16.67 * 3) * i, (16.67 * 3) * (i + 1), i) for i in range(8)])
|
||||
self.rainbow_animation.start()
|
||||
else:
|
||||
self.rainbow_animation.update(current_ms)
|
||||
if self.rainbow_animation.is_finished or self.gauge_length < 87:
|
||||
self.rainbow_animation = None
|
||||
|
||||
def draw(self):
|
||||
tex.draw_texture('gauge_dan', 'border')
|
||||
tex.draw_texture('gauge_dan', f'{self.player_num}p_unfilled')
|
||||
tex.draw_texture('gauge_dan', f'{self.player_num}p_bar', x2=self.visual_length-8)
|
||||
|
||||
# Rainbow effect for full gauge
|
||||
if self.gauge_length == self.gauge_max and self.rainbow_fade_in is not None and self.rainbow_animation is not None:
|
||||
if 0 < self.rainbow_animation.attribute < 8:
|
||||
tex.draw_texture('gauge_dan', 'rainbow', frame=self.rainbow_animation.attribute-1, fade=self.rainbow_fade_in.attribute)
|
||||
tex.draw_texture('gauge_dan', 'rainbow', frame=self.rainbow_animation.attribute, fade=self.rainbow_fade_in.attribute)
|
||||
if self.gauge_update_anim is not None and self.visual_length <= self.gauge_max and self.visual_length > self.previous_length:
|
||||
tex.draw_texture('gauge_dan', f'{self.player_num}p_bar_fade', x=self.visual_length-8, fade=self.gauge_update_anim.attribute)
|
||||
tex.draw_texture('gauge_dan', 'overlay', fade=0.15)
|
||||
|
||||
# Draw clear status indicators
|
||||
if self.is_rainbow:
|
||||
tex.draw_texture('gauge_dan', 'tamashii_fire', scale=0.75, center=True, frame=self.tamashii_fire_change.attribute)
|
||||
tex.draw_texture('gauge_dan', 'tamashii')
|
||||
if self.is_rainbow and self.tamashii_fire_change.attribute in (0, 1, 4, 5):
|
||||
tex.draw_texture('gauge_dan', 'tamashii_overlay', fade=0.5)
|
||||
else:
|
||||
tex.draw_texture('gauge_dan', 'tamashii_dark')
|
||||
|
||||
@@ -66,21 +66,22 @@ class SongSelectScreen(Screen):
|
||||
self.ura_switch_animation = UraSwitchAnimation()
|
||||
self.dan_transition = DanTransition()
|
||||
|
||||
session_data = global_data.session_data[global_data.player_num-1]
|
||||
self.player_1 = SongSelectPlayer(str(global_data.player_num), self.text_fade_in)
|
||||
|
||||
if self.navigator.items == []:
|
||||
logger.warning("No navigator items found, returning to ENTRY screen")
|
||||
return self.on_screen_end("ENTRY")
|
||||
|
||||
if str(global_data.selected_song) in self.navigator.all_song_files:
|
||||
self.navigator.mark_crowns_dirty_for_song(self.navigator.all_song_files[str(global_data.selected_song)])
|
||||
if str(session_data.selected_song) in self.navigator.all_song_files:
|
||||
self.navigator.mark_crowns_dirty_for_song(self.navigator.all_song_files[str(session_data.selected_song)])
|
||||
|
||||
curr_item = self.navigator.get_current_item()
|
||||
curr_item.box.get_scores()
|
||||
self.navigator.add_recent()
|
||||
|
||||
def finalize_song(self):
|
||||
global_data.selected_song = self.navigator.get_current_item().path
|
||||
global_data.session_data[global_data.player_num-1].selected_song = self.navigator.get_current_item().path
|
||||
global_data.session_data[global_data.player_num-1].selected_difficulty = self.player_1.selected_difficulty
|
||||
global_data.session_data[global_data.player_num-1].genre_index = self.navigator.get_current_item().box.name_texture_index
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ class TwoPlayerSongSelectScreen(SongSelectScreen):
|
||||
self.player_2 = SongSelectPlayer('2', self.text_fade_in)
|
||||
|
||||
def finalize_song(self):
|
||||
global_data.selected_song = self.navigator.get_current_item().path
|
||||
global_data.session_data[0].selected_song = self.navigator.get_current_item().path
|
||||
global_data.session_data[0].genre_index = self.navigator.get_current_item().box.name_texture_index
|
||||
logger.info(f"Finalized song selection: {global_data.selected_song}")
|
||||
logger.info(f"Finalized song selection: {global_data.session_data[0].selected_song}")
|
||||
|
||||
def handle_input_browsing(self):
|
||||
"""Handle input for browsing songs."""
|
||||
|
||||
Reference in New Issue
Block a user