mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
add failure noise and some more TJA metadata handling
This commit is contained in:
BIN
libs/icon.png
BIN
libs/icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 7.2 KiB |
59
libs/tja.py
59
libs/tja.py
@@ -350,17 +350,47 @@ class TJAParser:
|
|||||||
region_code = (item[len('TITLE'):len('TITLE')+2]).lower()
|
region_code = (item[len('TITLE'):len('TITLE')+2]).lower()
|
||||||
self.metadata.title[region_code] = ''.join(item.split(':')[1:])
|
self.metadata.title[region_code] = ''.join(item.split(':')[1:])
|
||||||
elif item.startswith('BPM'):
|
elif item.startswith('BPM'):
|
||||||
self.metadata.bpm = float(item.split(':')[1])
|
data = item.split(':')[1]
|
||||||
|
if not data:
|
||||||
|
logger.warning(f"Invalid BPM value: {data} in TJA file {self.file_path}")
|
||||||
|
self.metadata.bpm = 0.0
|
||||||
|
else:
|
||||||
|
self.metadata.bpm = float(data)
|
||||||
elif item.startswith('WAVE'):
|
elif item.startswith('WAVE'):
|
||||||
self.metadata.wave = self.file_path.parent / item.split(':')[1].strip()
|
data = item.split(':')[1]
|
||||||
|
if not data:
|
||||||
|
logger.warning(f"Invalid WAVE value: {data} in TJA file {self.file_path}")
|
||||||
|
self.metadata.wave = ''
|
||||||
|
else:
|
||||||
|
self.metadata.wave = self.file_path.parent / data.strip()
|
||||||
elif item.startswith('OFFSET'):
|
elif item.startswith('OFFSET'):
|
||||||
self.metadata.offset = float(item.split(':')[1])
|
data = item.split(':')[1]
|
||||||
|
if not data:
|
||||||
|
logger.warning(f"Invalid OFFSET value: {data} in TJA file {self.file_path}")
|
||||||
|
self.metadata.offset = 0.0
|
||||||
|
else:
|
||||||
|
self.metadata.offset = float(data)
|
||||||
elif item.startswith('DEMOSTART'):
|
elif item.startswith('DEMOSTART'):
|
||||||
self.metadata.demostart = float(item.split(':')[1]) if item.split(':')[1] != '' else 0
|
data = item.split(':')[1]
|
||||||
|
if not data:
|
||||||
|
logger.warning(f"Invalid DEMOSTART value: {data} in TJA file {self.file_path}")
|
||||||
|
self.metadata.demostart = 0.0
|
||||||
|
else:
|
||||||
|
self.metadata.demostart = float(data)
|
||||||
elif item.startswith('BGMOVIE'):
|
elif item.startswith('BGMOVIE'):
|
||||||
self.metadata.bgmovie = self.file_path.parent / item.split(':')[1].strip()
|
data = item.split(':')[1]
|
||||||
|
if not data:
|
||||||
|
logger.warning(f"Invalid BGMOVIE value: {data} in TJA file {self.file_path}")
|
||||||
|
self.metadata.bgmovie = None
|
||||||
|
else:
|
||||||
|
self.metadata.bgmovie = self.file_path.parent / data.strip()
|
||||||
elif item.startswith('MOVIEOFFSET'):
|
elif item.startswith('MOVIEOFFSET'):
|
||||||
self.metadata.movieoffset = float(item.split(':')[1])
|
data = item.split(':')[1]
|
||||||
|
if not data:
|
||||||
|
logger.warning(f"Invalid MOVIEOFFSET value: {data} in TJA file {self.file_path}")
|
||||||
|
self.metadata.movieoffset = 0.0
|
||||||
|
else:
|
||||||
|
self.metadata.movieoffset = float(data)
|
||||||
elif item.startswith('SCENEPRESET'):
|
elif item.startswith('SCENEPRESET'):
|
||||||
self.metadata.scene_preset = item.split(':')[1]
|
self.metadata.scene_preset = item.split(':')[1]
|
||||||
elif item.startswith('COURSE'):
|
elif item.startswith('COURSE'):
|
||||||
@@ -381,24 +411,32 @@ class TJAParser:
|
|||||||
elif course == '0' or course == 'easy':
|
elif course == '0' or course == 'easy':
|
||||||
current_diff = 0
|
current_diff = 0
|
||||||
else:
|
else:
|
||||||
raise Exception("course level empty")
|
logger.error(f"Course level empty in {self.file_path}")
|
||||||
self.metadata.course_data[current_diff] = CourseData()
|
self.metadata.course_data[current_diff] = CourseData()
|
||||||
elif current_diff is not None:
|
elif current_diff is not None:
|
||||||
if item.startswith('LEVEL'):
|
if item.startswith('LEVEL'):
|
||||||
self.metadata.course_data[current_diff].level = int(float(item.split(':')[1]))
|
data = item.split(':')[1]
|
||||||
|
if not data:
|
||||||
|
self.metadata.course_data[current_diff].level = 0
|
||||||
|
logger.warning(f"Invalid LEVEL value: {data} in TJA file {self.file_path}")
|
||||||
|
else:
|
||||||
|
self.metadata.course_data[current_diff].level = int(float(data))
|
||||||
elif item.startswith('BALLOONNOR'):
|
elif item.startswith('BALLOONNOR'):
|
||||||
balloon_data = item.split(':')[1]
|
balloon_data = item.split(':')[1]
|
||||||
if balloon_data == '':
|
if balloon_data == '':
|
||||||
|
logger.warning(f"Invalid BALLOONNOR value: {balloon_data} in TJA file {self.file_path}")
|
||||||
continue
|
continue
|
||||||
self.metadata.course_data[current_diff].balloon.extend([int(x) for x in balloon_data.split(',') if x != ''])
|
self.metadata.course_data[current_diff].balloon.extend([int(x) for x in balloon_data.split(',') if x != ''])
|
||||||
elif item.startswith('BALLOONEXP'):
|
elif item.startswith('BALLOONEXP'):
|
||||||
balloon_data = item.split(':')[1]
|
balloon_data = item.split(':')[1]
|
||||||
if balloon_data == '':
|
if balloon_data == '':
|
||||||
|
logger.warning(f"Invalid BALLOONEXP value: {balloon_data} in TJA file {self.file_path}")
|
||||||
continue
|
continue
|
||||||
self.metadata.course_data[current_diff].balloon.extend([int(x) for x in balloon_data.split(',') if x != ''])
|
self.metadata.course_data[current_diff].balloon.extend([int(x) for x in balloon_data.split(',') if x != ''])
|
||||||
elif item.startswith('BALLOONMAS'):
|
elif item.startswith('BALLOONMAS'):
|
||||||
balloon_data = item.split(':')[1]
|
balloon_data = item.split(':')[1]
|
||||||
if balloon_data == '':
|
if balloon_data == '':
|
||||||
|
logger.warning(f"Invalid BALLOONMAS value: {balloon_data} in TJA file {self.file_path}")
|
||||||
continue
|
continue
|
||||||
self.metadata.course_data[current_diff].balloon = [int(x) for x in balloon_data.split(',') if x != '']
|
self.metadata.course_data[current_diff].balloon = [int(x) for x in balloon_data.split(',') if x != '']
|
||||||
elif item.startswith('BALLOON'):
|
elif item.startswith('BALLOON'):
|
||||||
@@ -407,20 +445,23 @@ class TJAParser:
|
|||||||
continue
|
continue
|
||||||
balloon_data = item.split(':')[1]
|
balloon_data = item.split(':')[1]
|
||||||
if balloon_data == '':
|
if balloon_data == '':
|
||||||
|
logger.warning(f"Invalid BALLOON value: {balloon_data} in TJA file {self.file_path}")
|
||||||
continue
|
continue
|
||||||
self.metadata.course_data[current_diff].balloon = [int(x) for x in balloon_data.split(',') if x != '']
|
self.metadata.course_data[current_diff].balloon = [int(x) for x in balloon_data.split(',') if x != '']
|
||||||
elif item.startswith('SCOREINIT'):
|
elif item.startswith('SCOREINIT'):
|
||||||
score_init = item.split(':')[1]
|
score_init = item.split(':')[1]
|
||||||
if score_init == '':
|
if score_init == '':
|
||||||
|
logger.warning(f"Invalid SCOREINIT value: {score_init} in TJA file {self.file_path}")
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
self.metadata.course_data[current_diff].scoreinit = [int(x) for x in score_init.split(',') if x != '']
|
self.metadata.course_data[current_diff].scoreinit = [int(x) for x in score_init.split(',') if x != '']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Failed to parse SCOREINIT: ", e)
|
logger.error(f"Failed to parse SCOREINIT: {e} in TJA file {self.file_path}")
|
||||||
self.metadata.course_data[current_diff].scoreinit = [0, 0]
|
self.metadata.course_data[current_diff].scoreinit = [0, 0]
|
||||||
elif item.startswith('SCOREDIFF'):
|
elif item.startswith('SCOREDIFF'):
|
||||||
score_diff = item.split(':')[1]
|
score_diff = item.split(':')[1]
|
||||||
if score_diff == '':
|
if score_diff == '':
|
||||||
|
logger.error(f"Invalid SCOREDIFF value: {score_diff} in TJA file {self.file_path}")
|
||||||
continue
|
continue
|
||||||
self.metadata.course_data[current_diff].scorediff = int(float(score_diff))
|
self.metadata.course_data[current_diff].scorediff = int(float(score_diff))
|
||||||
for region_code in self.metadata.title:
|
for region_code in self.metadata.title:
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ class DanGameScreen(GameScreen):
|
|||||||
self.dan_transition.start()
|
self.dan_transition.start()
|
||||||
self.allnet_indicator = AllNetIcon()
|
self.allnet_indicator = AllNetIcon()
|
||||||
|
|
||||||
|
self.dan_info_cache = None
|
||||||
|
self.exam_failed = [False] * len(self.exams)
|
||||||
|
|
||||||
def change_song(self):
|
def change_song(self):
|
||||||
session_data = global_data.session_data[global_data.player_num-1]
|
session_data = global_data.session_data[global_data.player_num-1]
|
||||||
songs = session_data.selected_dan
|
songs = session_data.selected_dan
|
||||||
@@ -94,6 +97,59 @@ class DanGameScreen(GameScreen):
|
|||||||
self.dan_transition.start()
|
self.dan_transition.start()
|
||||||
self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000)
|
self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000)
|
||||||
|
|
||||||
|
def _calculate_dan_info(self):
|
||||||
|
"""Calculate all dan info data for drawing"""
|
||||||
|
remaining_notes = self.total_notes - self.player_1.good_count - self.player_1.ok_count - self.player_1.bad_count
|
||||||
|
|
||||||
|
exam_data = []
|
||||||
|
for exam in self.exams:
|
||||||
|
progress_value = self._get_exam_progress(exam)
|
||||||
|
progress = progress_value / exam.red
|
||||||
|
|
||||||
|
if exam.range == 'less':
|
||||||
|
progress = 1 - progress
|
||||||
|
counter_value = max(0, exam.red - progress_value)
|
||||||
|
elif exam.range == 'more':
|
||||||
|
counter_value = max(0, progress_value)
|
||||||
|
else:
|
||||||
|
counter_value = max(0, progress_value)
|
||||||
|
|
||||||
|
# Clamp progress
|
||||||
|
progress = max(0, min(progress, 1))
|
||||||
|
|
||||||
|
# Determine progress bar texture
|
||||||
|
if progress == 1:
|
||||||
|
bar_texture = 'exam_max'
|
||||||
|
elif progress >= 0.5:
|
||||||
|
bar_texture = 'exam_gold'
|
||||||
|
else:
|
||||||
|
bar_texture = 'exam_red'
|
||||||
|
|
||||||
|
exam_data.append({
|
||||||
|
'exam': exam,
|
||||||
|
'progress': progress,
|
||||||
|
'bar_texture': bar_texture,
|
||||||
|
'counter_value': counter_value,
|
||||||
|
'red_value': exam.red
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'remaining_notes': remaining_notes,
|
||||||
|
'exam_data': exam_data
|
||||||
|
}
|
||||||
|
|
||||||
|
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 + self.player_1.bad_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 update(self):
|
def update(self):
|
||||||
super(GameScreen, self).update()
|
super(GameScreen, self).update()
|
||||||
current_time = get_current_ms()
|
current_time = get_current_ms()
|
||||||
@@ -109,6 +165,10 @@ class DanGameScreen(GameScreen):
|
|||||||
self.player_1.update(self.current_ms, current_time, self.background)
|
self.player_1.update(self.current_ms, current_time, self.background)
|
||||||
self.song_info.update(current_time)
|
self.song_info.update(current_time)
|
||||||
self.result_transition.update(current_time)
|
self.result_transition.update(current_time)
|
||||||
|
|
||||||
|
self.dan_info_cache = self._calculate_dan_info()
|
||||||
|
self._check_exam_failures()
|
||||||
|
|
||||||
if self.result_transition.is_finished and not audio.is_sound_playing('result_transition'):
|
if self.result_transition.is_finished and not audio.is_sound_playing('result_transition'):
|
||||||
logger.info("Result transition finished, moving to RESULT screen")
|
logger.info("Result transition finished, moving to RESULT screen")
|
||||||
return self.on_screen_end('RESULT')
|
return self.on_screen_end('RESULT')
|
||||||
@@ -133,40 +193,71 @@ class DanGameScreen(GameScreen):
|
|||||||
|
|
||||||
return self.global_keys()
|
return self.global_keys()
|
||||||
|
|
||||||
|
def _check_exam_failures(self):
|
||||||
|
for i, exam in enumerate(self.exams):
|
||||||
|
progress_value = self._get_exam_progress(exam)
|
||||||
|
|
||||||
|
if self.exam_failed[i]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if exam.range == 'more':
|
||||||
|
if progress_value < exam.red and self.end_ms != 0:
|
||||||
|
self.exam_failed[i] = True
|
||||||
|
audio.play_sound('exam_failed', 'sound')
|
||||||
|
logger.info(f"Exam {i} ({exam.type}) failed: {progress_value} < {exam.red}")
|
||||||
|
elif exam.range == 'less':
|
||||||
|
counter_value = max(0, exam.red - progress_value)
|
||||||
|
if counter_value == 0:
|
||||||
|
self.exam_failed[i] = True
|
||||||
|
audio.play_sound('dan_failed', 'sound')
|
||||||
|
logger.info(f"Exam {i} ({exam.type}) failed: counter reached 0")
|
||||||
|
|
||||||
def draw_dan_info(self):
|
def draw_dan_info(self):
|
||||||
|
if self.dan_info_cache is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
cache = self.dan_info_cache
|
||||||
|
|
||||||
|
# Draw total notes counter
|
||||||
tex.draw_texture('dan_info', 'total_notes')
|
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)
|
counter = str(cache['remaining_notes'])
|
||||||
self._draw_counter(counter, margin=45, texture='total_notes_counter')
|
self._draw_counter(counter, margin=45, texture='total_notes_counter')
|
||||||
|
|
||||||
for i, exam in enumerate(self.exams):
|
# Draw exam info
|
||||||
|
for i, exam_info in enumerate(cache['exam_data']):
|
||||||
y_offset = i * 94
|
y_offset = i * 94
|
||||||
|
exam = exam_info['exam']
|
||||||
|
|
||||||
tex.draw_texture('dan_info', 'exam_bg', y=y_offset)
|
tex.draw_texture('dan_info', 'exam_bg', y=y_offset)
|
||||||
tex.draw_texture('dan_info', 'exam_overlay_1', y=y_offset)
|
tex.draw_texture('dan_info', 'exam_overlay_1', y=y_offset)
|
||||||
|
|
||||||
# Get progress based on exam type
|
# Draw progress bar
|
||||||
progress = self._get_exam_progress(exam) / exam.red
|
tex.draw_texture('dan_info', exam_info['bar_texture'], x2=940*exam_info['progress'], y=y_offset)
|
||||||
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)
|
|
||||||
|
|
||||||
|
# Draw exam type and red value counter
|
||||||
|
red_counter = str(exam_info['red_value'])
|
||||||
|
self._draw_counter(red_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(red_counter)*20)
|
||||||
|
|
||||||
|
# Draw range indicator
|
||||||
if exam.range == 'less':
|
if exam.range == 'less':
|
||||||
tex.draw_texture('dan_info', 'exam_less', y=y_offset)
|
tex.draw_texture('dan_info', 'exam_less', y=y_offset)
|
||||||
elif exam.range == 'more':
|
elif exam.range == 'more':
|
||||||
tex.draw_texture('dan_info', 'exam_more', y=y_offset)
|
tex.draw_texture('dan_info', 'exam_more', y=y_offset)
|
||||||
|
|
||||||
|
# Draw current value counter
|
||||||
tex.draw_texture('dan_info', 'exam_overlay_2', y=y_offset)
|
tex.draw_texture('dan_info', 'exam_overlay_2', y=y_offset)
|
||||||
if exam.range == 'less':
|
value_counter = str(exam_info['counter_value'])
|
||||||
counter = str(max(0, exam.red - self._get_exam_progress(exam)))
|
self._draw_counter(value_counter, margin=22, texture='value_counter', index=1, y=y_offset)
|
||||||
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':
|
if exam.type == 'gauge':
|
||||||
tex.draw_texture('dan_info', 'exam_percent', y=y_offset, index=1)
|
tex.draw_texture('dan_info', 'exam_percent', y=y_offset, index=1)
|
||||||
|
|
||||||
|
if self.exam_failed[i]:
|
||||||
|
tex.draw_texture('dan_info', 'exam_bg', fade=0.5, y=y_offset)
|
||||||
|
tex.draw_texture('dan_info', 'exam_failed', y=y_offset)
|
||||||
|
|
||||||
|
# Draw frame and title
|
||||||
tex.draw_texture('dan_info', 'frame', frame=self.color)
|
tex.draw_texture('dan_info', 'frame', frame=self.color)
|
||||||
if self.hori_name is not None:
|
if self.hori_name is not None:
|
||||||
self.hori_name.draw(outline_color=ray.BLACK, x=154 - (self.hori_name.texture.width//2),
|
self.hori_name.draw(outline_color=ray.BLACK, x=154 - (self.hori_name.texture.width//2),
|
||||||
@@ -180,32 +271,6 @@ class DanGameScreen(GameScreen):
|
|||||||
kwargs['index'] = index
|
kwargs['index'] = index
|
||||||
tex.draw_texture('dan_info', texture, **kwargs)
|
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
|
@override
|
||||||
def draw(self):
|
def draw(self):
|
||||||
self.background.draw()
|
self.background.draw()
|
||||||
|
|||||||
Reference in New Issue
Block a user