fix scrobbling

This commit is contained in:
Anthony Samms
2025-11-10 15:37:47 -05:00
parent 66b88ba480
commit a1b1471bd5
3 changed files with 246 additions and 31 deletions

View File

@@ -202,6 +202,19 @@ class GameScreen(Screen):
self.movie.start(current_time)
self.song_started = True
def pause_song(self):
self.paused = not self.paused
if self.paused:
if self.song_music is not None:
self.audio_time = audio.get_music_time_played(self.song_music)
audio.stop_music_stream(self.song_music)
self.pause_time = get_current_ms() - self.start_ms
else:
if self.song_music is not None:
audio.play_music_stream(self.song_music, 'music')
audio.seek_music_stream(self.song_music, self.audio_time)
self.start_ms = get_current_ms() - self.pause_time
def global_keys(self):
if ray.is_key_pressed(get_key_code(global_data.config["keys"]["restart_key"])):
if self.song_music is not None:
@@ -216,17 +229,7 @@ class GameScreen(Screen):
return self.on_screen_end('SONG_SELECT')
if ray.is_key_pressed(ray.KeyboardKey.KEY_SPACE):
self.paused = not self.paused
if self.paused:
if self.song_music is not None:
self.audio_time = audio.get_music_time_played(self.song_music)
audio.stop_music_stream(self.song_music)
self.pause_time = get_current_ms() - self.start_ms
else:
if self.song_music is not None:
audio.play_music_stream(self.song_music, 'music')
audio.seek_music_stream(self.song_music, self.audio_time)
self.start_ms = get_current_ms() - self.pause_time
self.pause_song()
def spawn_ending_anims(self):
if global_data.session_data[global_data.player_num-1].result_data.bad == 0:
@@ -266,7 +269,8 @@ class GameScreen(Screen):
elif self.current_ms >= self.player_1.end_time:
session_data = global_data.session_data[global_data.player_num-1]
session_data.result_data.score, session_data.result_data.good, session_data.result_data.ok, session_data.result_data.bad, session_data.result_data.max_combo, session_data.result_data.total_drumroll = self.player_1.get_result_score()
session_data.result_data.gauge_length = self.player_1.gauge.gauge_length
if self.player_1.gauge is not None:
session_data.result_data.gauge_length = self.player_1.gauge.gauge_length
if self.end_ms != 0:
if current_time >= self.end_ms + 1000:
if self.player_1.ending_anim is None:
@@ -524,7 +528,8 @@ class Player:
else:
background.add_chibi(True, 1)
self.bad_count += 1
self.gauge.add_bad()
if self.gauge is not None:
self.gauge.add_bad()
self.don_notes.popleft()
if self.is_branch and self.branch_condition == 'p':
self.branch_condition_count -= 1
@@ -537,7 +542,8 @@ class Player:
else:
background.add_chibi(True, 1)
self.bad_count += 1
self.gauge.add_bad()
if self.gauge is not None:
self.gauge.add_bad()
self.kat_notes.popleft()
if self.is_branch and self.branch_condition == 'p':
self.branch_condition_count -= 1
@@ -718,7 +724,8 @@ class Player:
self.score += self.base_score
self.base_score_list.append(ScoreCounterAnimation(self.player_number, self.base_score, self.is_2p))
self.note_correct(curr_note, current_time)
self.gauge.add_good()
if self.gauge is not None:
self.gauge.add_good()
if self.is_branch and self.branch_condition == 'p':
self.branch_condition_count += 1
if background is not None:
@@ -733,7 +740,8 @@ class Player:
self.score += 10 * math.floor(self.base_score / 2 / 10)
self.base_score_list.append(ScoreCounterAnimation(self.player_number, 10 * math.floor(self.base_score / 2 / 10), self.is_2p))
self.note_correct(curr_note, current_time)
self.gauge.add_ok()
if self.gauge is not None:
self.gauge.add_ok()
if self.is_branch and self.branch_condition == 'p':
self.branch_condition_count += 0.5
if background is not None:
@@ -751,7 +759,8 @@ class Player:
self.don_notes.popleft()
else:
self.kat_notes.popleft()
self.gauge.add_bad()
if self.gauge is not None:
self.gauge.add_bad()
if background is not None:
if self.is_2p:
background.add_chibi(True, 2)
@@ -905,7 +914,8 @@ class Player:
self.autoplay_manager(ms_from_start, current_time, background)
self.handle_input(ms_from_start, current_time, background)
self.nameplate.update(current_time)
self.gauge.update(current_time)
if self.gauge is not None:
self.gauge.update(current_time)
if self.judge_counter is not None:
self.judge_counter.update(self.good_count, self.ok_count, self.bad_count, self.total_drumroll)
if self.branch_indicator is not None:
@@ -939,7 +949,10 @@ class Player:
self.is_gogo_time = False
self.gogo_time = None
self.chara.set_animation('gogo_stop')
self.chara.update(current_time, self.bpm, self.gauge.is_clear, self.gauge.is_rainbow)
if self.gauge is None:
self.chara.update(current_time, self.bpm, False, False)
else:
self.chara.update(current_time, self.bpm, self.gauge.is_clear, self.gauge.is_rainbow)
def draw_drumroll(self, current_ms: float, head: Drumroll, current_eighth: int):
"""Draws a drumroll in the player's lane"""
@@ -1058,12 +1071,17 @@ class Player:
for modifier in modifiers_to_draw:
tex.draw_texture('lane', modifier, index=self.is_2p)
def draw_note_types(self, ms_from_start: float, start_ms: float):
self.draw_bars(ms_from_start)
self.draw_notes(ms_from_start, start_ms)
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:
self.branch_indicator.draw()
self.gauge.draw()
if self.gauge is not None:
self.gauge.draw()
if self.lane_hit_effect is not None:
self.lane_hit_effect.draw()
tex.draw_texture('lane', 'lane_hit_circle', index=self.is_2p)
@@ -1075,8 +1093,7 @@ class Player:
anim.draw()
# Group 3: Notes and bars (game content)
self.draw_bars(ms_from_start)
self.draw_notes(ms_from_start, start_ms)
self.draw_note_types(ms_from_start, start_ms)
if dan_transition is not None:
dan_transition.draw()

View File

@@ -1,8 +1,18 @@
from collections import deque
import logging
from pathlib import Path
import pyray as ray
import copy
from libs.animation import Animation
from libs.audio import audio
from libs.background import Background
from scenes.game import GameScreen, JudgeCounter
from libs.global_data import Modifiers, global_data
from libs.tja import Balloon, Drumroll, TJAParser, apply_modifiers
from libs.utils import get_current_ms
from libs.texture import tex
from scenes.game import GameScreen, JudgeCounter, Player, SCREEN_WIDTH
logger = logging.getLogger(__name__)
@@ -12,5 +22,191 @@ class PracticeGameScreen(GameScreen):
self.background = Background(1, self.bpm, scene_preset='PRACTICE')
def init_tja(self, song: Path):
super().init_tja(song)
self.player_1.judge_counter = JudgeCounter()
"""Initialize the TJA file"""
self.tja = TJAParser(song, start_delay=self.start_delay, distance=SCREEN_WIDTH - GameScreen.JUDGE_X)
self.scrolling_tja = TJAParser(song, start_delay=self.start_delay, distance=SCREEN_WIDTH - GameScreen.JUDGE_X)
global_data.session_data[0].song_title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
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 = PracticePlayer(self.tja, global_data.player_num, global_data.session_data[global_data.player_num-1].selected_difficulty, False, global_data.modifiers[0])
notes, branch_m, branch_e, branch_n = self.tja.notes_to_position(self.player_1.difficulty)
_, self.scroll_note_list, self.bars = apply_modifiers(notes, self.player_1.modifiers)
self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000)
self.scrobble_index = 0
self.scrobble_time = self.bars[self.scrobble_index].hit_ms
self.scrobble_move = Animation.create_move(200, total_distance=0)
def pause_song(self):
self.paused = not self.paused
self.player_1.paused = self.paused
if self.paused:
if self.song_music is not None:
audio.stop_music_stream(self.song_music)
self.pause_time = get_current_ms() - self.start_ms
first_bar_time = self.bars[0].hit_ms
nearest_bar_index = 0
min_distance = float('inf')
for i, bar in enumerate(self.bars):
bar_relative_time = bar.hit_ms - first_bar_time
distance = abs(bar_relative_time - self.current_ms)
if distance < min_distance:
min_distance = distance
nearest_bar_index = i
self.scrobble_index = nearest_bar_index - 1
self.scrobble_time = self.bars[self.scrobble_index].hit_ms
else:
resume_bar_index = max(0, self.scrobble_index)
previous_bar_index = max(0, self.scrobble_index - 1)
first_bar_time = self.bars[0].hit_ms
resume_time = self.bars[resume_bar_index].hit_ms - first_bar_time + self.start_delay
start_time = self.bars[previous_bar_index].hit_ms - first_bar_time
tja_copy = copy.deepcopy(self.scrolling_tja)
self.player_1.tja = tja_copy
self.player_1.reset_chart()
self.player_1.don_notes = deque([note for note in self.player_1.don_notes if note.hit_ms >= resume_time])
self.player_1.kat_notes = deque([note for note in self.player_1.kat_notes if note.hit_ms >= resume_time])
self.player_1.other_notes = deque([note for note in self.player_1.other_notes if note.hit_ms >= resume_time])
self.player_1.draw_note_list = deque([note for note in self.player_1.draw_note_list if note.hit_ms >= resume_time])
self.player_1.draw_bar_list = deque([note for note in self.player_1.draw_bar_list if note.hit_ms >= resume_time])
self.player_1.total_notes = len([note for note in self.player_1.play_notes if 0 < note.type < 5])
self.pause_time = start_time
if self.song_music is not None:
audio.play_music_stream(self.song_music, 'music')
audio.seek_music_stream(self.song_music, (self.pause_time - self.start_delay)/1000 - self.tja.metadata.offset)
self.start_ms = get_current_ms() - self.pause_time
def global_keys(self):
super().global_keys()
if ray.is_key_pressed(ray.KeyboardKey.KEY_LEFT) or ray.is_key_pressed(ray.KeyboardKey.KEY_RIGHT):
audio.play_sound('kat', 'sound')
old_index = self.scrobble_index
if ray.is_key_pressed(ray.KeyboardKey.KEY_LEFT):
self.scrobble_index = (self.scrobble_index - 1) if self.scrobble_index > 0 else len(self.bars) - 1
elif ray.is_key_pressed(ray.KeyboardKey.KEY_RIGHT):
self.scrobble_index = (self.scrobble_index + 1) % len(self.bars)
time_difference = self.bars[self.scrobble_index].load_ms - self.bars[old_index].load_ms
self.scrobble_move = Animation.create_move(400, total_distance=time_difference, ease_out='quadratic')
self.scrobble_move.start()
def update(self):
super().update()
self.scrobble_move.update(get_current_ms())
if self.scrobble_move.is_finished:
self.scrobble_time = self.bars[self.scrobble_index].hit_ms
self.scrobble_move.reset()
def get_position_x(self, width: int, current_ms: float, load_ms: float, pixels_per_frame: float) -> int:
"""Calculates the x-coordinate of a note based on its load time and current time"""
if self.paused:
time_diff = load_ms - self.scrobble_time - self.scrobble_move.attribute
else:
time_diff = load_ms - current_ms
return int(width + pixels_per_frame * 0.06 * time_diff - 64)
def get_position_y(self, current_ms: float, load_ms: float, pixels_per_frame: float, pixels_per_frame_x) -> int:
"""Calculates the y-coordinate of a note based on its load time and current time"""
time_diff = load_ms - current_ms
return int((pixels_per_frame * 0.06 * time_diff) + ((866 * pixels_per_frame) / pixels_per_frame_x))
def draw_drumroll(self, current_ms: float, head: Drumroll, current_eighth: int, index: int):
"""Draws a drumroll in the player's lane"""
start_position = self.get_position_x(SCREEN_WIDTH, current_ms, head.load_ms, head.pixels_per_frame_x)
tail = self.scroll_note_list[index + 1]
is_big = int(head.type == 6)
end_position = self.get_position_x(SCREEN_WIDTH, current_ms, tail.load_ms, tail.pixels_per_frame_x)
length = end_position - start_position
color = ray.Color(255, head.color, head.color, 255)
if head.display:
if length > 0:
tex.draw_texture('notes', "8", frame=is_big, x=start_position+64, y=192, x2=length-47, color=color)
if is_big:
tex.draw_texture('notes', "drumroll_big_tail", x=end_position+64, y=192, color=color)
else:
tex.draw_texture('notes', "drumroll_tail", x=end_position+64, y=192, color=color)
tex.draw_texture('notes', str(head.type), frame=current_eighth % 2, x=start_position, y=192, color=color)
tex.draw_texture('notes', 'moji_drumroll_mid', x=start_position + 60, y=323, x2=length)
tex.draw_texture('notes', 'moji', frame=head.moji, x=(start_position - (168//2)) + 64, y=323)
tex.draw_texture('notes', 'moji', frame=tail.moji, x=(end_position - (168//2)) + 32, y=323)
def draw_balloon(self, current_ms: float, head: Balloon, current_eighth: int, index: int):
"""Draws a balloon in the player's lane"""
offset = 12
start_position = self.get_position_x(SCREEN_WIDTH, current_ms, head.load_ms, head.pixels_per_frame_x)
tail = self.scroll_note_list[index + 1]
end_position = self.get_position_x(SCREEN_WIDTH, current_ms, tail.load_ms, tail.pixels_per_frame_x)
pause_position = 349
if current_ms >= tail.hit_ms:
position = end_position
elif current_ms >= head.hit_ms:
position = pause_position
else:
position = start_position
if head.display:
tex.draw_texture('notes', str(head.type), frame=current_eighth % 2, x=position-offset, y=192)
tex.draw_texture('notes', '10', frame=current_eighth % 2, x=position-offset+128, y=192)
def draw(self):
super().draw()
if self.paused:
# Batch bar draws by pre-calculating positions
bar_draws = []
for bar in reversed(self.bars):
if not bar.display:
continue
x_position = self.get_position_x(SCREEN_WIDTH, self.current_ms, bar.load_ms, bar.pixels_per_frame_x)
y_position = self.get_position_y(self.current_ms, bar.load_ms, bar.pixels_per_frame_y, bar.pixels_per_frame_x)
if x_position < 236 or x_position > SCREEN_WIDTH:
continue
if hasattr(bar, 'is_branch_start'):
frame = 1
else:
frame = 0
bar_draws.append((str(bar.type), frame, x_position+60, y_position+190))
# Draw all bars in one batch
for bar_type, frame, x, y in bar_draws:
tex.draw_texture('notes', bar_type, frame=frame, x=x, y=y)
for note in reversed(self.scroll_note_list):
if note.type == 8:
continue
if isinstance(note, Drumroll):
self.draw_drumroll(self.current_ms, note, 0, note.index)
elif isinstance(note, Balloon) and not note.is_kusudama:
x_position = self.get_position_x(SCREEN_WIDTH, self.current_ms, note.load_ms, note.pixels_per_frame_x)
y_position = self.get_position_y(self.current_ms, note.load_ms, note.pixels_per_frame_y, note.pixels_per_frame_x)
if x_position < 236 or x_position > SCREEN_WIDTH:
continue
self.draw_balloon(self.current_ms, note, 0, note.index)
tex.draw_texture('notes', 'moji', frame=note.moji, x=x_position - (168//2) + 64, y=323 + y_position)
else:
x_position = self.get_position_x(SCREEN_WIDTH, self.current_ms, note.load_ms, note.pixels_per_frame_x)
y_position = self.get_position_y(self.current_ms, note.load_ms, note.pixels_per_frame_y, note.pixels_per_frame_x)
if x_position < 236 or x_position > SCREEN_WIDTH:
continue
if note.display:
tex.draw_texture('notes', str(note.type), x=x_position, y=y_position+192, center=True)
tex.draw_texture('notes', 'moji', frame=note.moji, x=x_position - (168//2) + 64, y=323 + y_position)
class PracticePlayer(Player):
def __init__(self, tja: TJAParser, player_number: int, difficulty: int, is_2p: bool, modifiers: Modifiers):
super().__init__(tja, player_number, difficulty, is_2p, modifiers)
self.judge_counter = JudgeCounter()
self.gauge = None
self.paused = False
def draw_note_types(self, ms_from_start: float, start_ms: float):
if not self.paused:
self.draw_bars(ms_from_start)
self.draw_notes(ms_from_start, start_ms)