Add doc strings

This commit is contained in:
Anthony Samms
2025-10-22 18:34:40 -04:00
parent ba906fa6c4
commit 7e73ee24f1
18 changed files with 787 additions and 241 deletions

View File

@@ -111,6 +111,7 @@ except OSError as e:
raise
class AudioEngine:
"""Initialize an audio engine for playing sounds and music."""
def __init__(self, device_type: int, sample_rate: float, buffer_size: int, volume_presets: dict[str, float]):
self.device_type = device_type
if sample_rate == -1:

View File

@@ -12,17 +12,52 @@ from libs.bg_objects.renda import RendaController
from libs.texture import TextureWrapper
class Background:
"""The background class for the game."""
COLLABS = {
"A3": libs.bg_collabs.a3.Background,
"ANIMAL": libs.bg_collabs.animal.Background,
"BUTTOBURST": libs.bg_collabs.buttoburst.Background
}
def __init__(self, player_num: int, bpm: float, scene_preset: str = ''):
"""
Initialize the background class.
Args:
player_num (int): The player number.
bpm (float): The beats per minute.
scene_preset (str): The scene preset.
"""
self.tex_wrapper = TextureWrapper()
self.tex_wrapper.load_animations('background')
if scene_preset == '':
if player_num == 3:
if scene_preset == '':
self.max_dancers = 5
don_bg_num = random.randint(0, 5)
self.don_bg = DonBG.create(self.tex_wrapper, don_bg_num, 1)
self.don_bg_2 = DonBG.create(self.tex_wrapper, don_bg_num, 2)
self.renda = RendaController(self.tex_wrapper, random.randint(0, 2))
self.chibi = ChibiController(self.tex_wrapper, random.randint(0, 13), bpm)
self.bg_normal = None
self.bg_fever = None
self.footer = None
self.fever = None
self.dancer = None
else:
collab_bg = Background.COLLABS[scene_preset](self.tex_wrapper, 1, bpm)
self.max_dancers = 5
self.don_bg = collab_bg.don_bg
self.don_bg_2 = collab_bg.don_bg
self.bg_normal = None
self.bg_fever = None
self.footer = None
self.fever = None
self.dancer = None
self.renda = collab_bg.renda
self.chibi = collab_bg.chibi
elif scene_preset == '':
self.max_dancers = 5
self.don_bg = DonBG.create(self.tex_wrapper, random.randint(0, 5), player_num)
self.don_bg_2 = None
self.bg_normal = BGNormal.create(self.tex_wrapper, random.randint(0, 4))
self.bg_fever = BGFever.create(self.tex_wrapper, random.randint(0, 3))
self.footer = Footer(self.tex_wrapper, random.randint(0, 2))
@@ -34,6 +69,7 @@ class Background:
collab_bg = Background.COLLABS[scene_preset](self.tex_wrapper, player_num, bpm)
self.max_dancers = collab_bg.max_dancers
self.don_bg = collab_bg.don_bg
self.don_bg_2 = None
self.bg_normal = collab_bg.bg_normal
self.bg_fever = collab_bg.bg_fever
self.footer = collab_bg.footer
@@ -45,50 +81,87 @@ class Background:
self.is_rainbow = False
self.last_milestone = 0
def add_chibi(self, bad: bool):
self.chibi.add_chibi(bad)
def add_chibi(self, bad: bool, player_num: int):
"""
Add a chibi to the background.
Args:
player_num (int): The player number.
bad (bool): Whether the chibi is bad.
"""
self.chibi.add_chibi(player_num, bad)
def add_renda(self):
"""
Add a renda to the background.
"""
self.renda.add_renda()
def update(self, current_time_ms: float, bpm: float, gauge):
clear_threshold = gauge.clear_start[min(gauge.difficulty, 3)]
if gauge.gauge_length < clear_threshold:
current_milestone = min(self.max_dancers - 1, int(gauge.gauge_length / (clear_threshold / self.max_dancers)))
else:
current_milestone = self.max_dancers
if current_milestone > self.last_milestone and current_milestone < self.max_dancers:
self.dancer.add_dancer()
self.last_milestone = current_milestone
if not self.is_clear and gauge.is_clear:
self.bg_fever.start()
if not self.is_rainbow and gauge.is_rainbow and self.fever is not None:
self.fever.start()
"""
Update the background.
Args:
current_time_ms (float): The current time in milliseconds.
bpm (float): The beats per minute.
gauge (Gauge): The gauge object.
"""
if self.dancer is not None:
clear_threshold = gauge.clear_start[min(gauge.difficulty, 3)]
if gauge.gauge_length < clear_threshold:
current_milestone = min(self.max_dancers - 1, int(gauge.gauge_length / (clear_threshold / self.max_dancers)))
else:
current_milestone = self.max_dancers
if current_milestone > self.last_milestone and current_milestone < self.max_dancers:
self.dancer.add_dancer()
self.last_milestone = current_milestone
if self.bg_fever is not None:
if not self.is_clear and gauge.is_clear:
self.bg_fever.start()
if not self.is_rainbow and gauge.is_rainbow and self.fever is not None:
self.fever.start()
self.is_clear = gauge.is_clear
self.is_rainbow = gauge.is_rainbow
self.don_bg.update(current_time_ms, self.is_clear)
self.bg_normal.update(current_time_ms)
self.bg_fever.update(current_time_ms)
if self.don_bg_2 is not None:
self.don_bg_2.update(current_time_ms, self.is_clear)
if self.bg_normal is not None:
self.bg_normal.update(current_time_ms)
if self.bg_fever is not None:
self.bg_fever.update(current_time_ms)
if self.fever is not None:
self.fever.update(current_time_ms, bpm)
self.dancer.update(current_time_ms, bpm)
if self.dancer is not None:
self.dancer.update(current_time_ms, bpm)
self.renda.update(current_time_ms)
self.chibi.update(current_time_ms, bpm)
def draw(self):
if self.is_clear and not self.bg_fever.transitioned:
self.bg_normal.draw(self.tex_wrapper)
self.bg_fever.draw(self.tex_wrapper)
elif self.is_clear:
self.bg_fever.draw(self.tex_wrapper)
else:
self.bg_normal.draw(self.tex_wrapper)
"""
Draw the background.
"""
if self.bg_normal is not None:
if self.is_clear and not self.bg_fever.transitioned:
self.bg_normal.draw(self.tex_wrapper)
self.bg_fever.draw(self.tex_wrapper)
elif self.is_clear:
self.bg_fever.draw(self.tex_wrapper)
else:
self.bg_normal.draw(self.tex_wrapper)
self.don_bg.draw(self.tex_wrapper)
if self.don_bg_2 is not None:
self.don_bg_2.draw(self.tex_wrapper, y=536)
self.renda.draw()
self.dancer.draw(self.tex_wrapper)
if self.dancer is not None:
self.dancer.draw(self.tex_wrapper)
if self.footer is not None:
self.footer.draw(self.tex_wrapper)
if self.is_rainbow and self.fever is not None:
self.fever.draw(self.tex_wrapper)
self.chibi.draw()
def unload(self):
"""
Unload the background.
"""
self.tex_wrapper.unload_textures()

View File

@@ -7,18 +7,19 @@ import pyray as ray
class Chibi:
@staticmethod
def create(index: int, bpm: float, bad: bool, tex: TextureWrapper):
def create(index: int, bpm: float, bad: bool, tex: TextureWrapper, is_2p: bool):
if bad:
return ChibiBad(index, bpm, tex)
return ChibiBad(index, bpm, tex, is_2p)
map = [Chibi0, BaseChibi, Chibi2, BaseChibi, Chibi4, Chibi5, BaseChibi,
BaseChibi, Chibi8, BaseChibi, BaseChibi, BaseChibi, BaseChibi, Chibi13]
selected_obj = map[index]
return selected_obj(index, bpm, tex)
return selected_obj(index, bpm, tex, is_2p)
class BaseChibi:
def __init__(self, index: int, bpm: float, tex: TextureWrapper):
def __init__(self, index: int, bpm: float, tex: TextureWrapper, is_2p: bool):
self.name = 'chibi_' + str(index)
self.bpm = bpm
self.is_2p = is_2p
self.hori_move = Animation.create_move(60000 / self.bpm * 5, total_distance=1280)
self.hori_move.start()
self.vert_move = Animation.create_move(60000 / self.bpm / 2, total_distance=50, reverse_delay=0)
@@ -42,10 +43,11 @@ class BaseChibi:
self.texture_change.restart()
def draw(self, tex: TextureWrapper):
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute, y=-self.vert_move.attribute)
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute, y=-self.vert_move.attribute+(self.is_2p*535))
class ChibiBad(BaseChibi):
def __init__(self, index: int, bpm: float, tex: TextureWrapper):
def __init__(self, index: int, bpm: float, tex: TextureWrapper, is_2p: bool):
self.is_2p = is_2p
self.bpm = bpm
self.index = random.randint(0, 2)
self.keyframes = [3, 4]
@@ -72,17 +74,17 @@ class ChibiBad(BaseChibi):
def draw(self, tex: TextureWrapper):
if not self.s_texture_change.is_finished:
tex.draw_texture('chibi_bad', '0', frame=self.s_texture_change.attribute, x=self.hori_move.attribute, y=self.vert_move.attribute, fade=self.fade_in.attribute)
tex.draw_texture('chibi_bad', '0', frame=self.s_texture_change.attribute, x=self.hori_move.attribute, y=self.vert_move.attribute+(self.is_2p*535), fade=self.fade_in.attribute)
else:
tex.draw_texture('chibi_bad', '0', frame=self.texture_change.attribute, x=self.hori_move.attribute, y=self.vert_move.attribute)
tex.draw_texture('chibi_bad', '0', frame=self.texture_change.attribute, x=self.hori_move.attribute, y=self.vert_move.attribute+(self.is_2p*535))
class Chibi0(BaseChibi):
def draw(self, tex: TextureWrapper):
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute, y=self.vert_move.attribute)
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute, y=self.vert_move.attribute+(self.is_2p*535))
class Chibi2(BaseChibi):
def __init__(self, index: int, bpm: float, tex: TextureWrapper):
super().__init__(index, bpm, tex)
def __init__(self, index: int, bpm: float, tex: TextureWrapper, is_2p: bool):
super().__init__(index, bpm, tex, is_2p)
self.rotate = Animation.create_move(60000 / self.bpm / 2, total_distance=360, reverse_delay=0)
self.rotate.start()
@@ -94,23 +96,23 @@ class Chibi2(BaseChibi):
def draw(self, tex: TextureWrapper):
origin = ray.Vector2(64, 64)
tex.draw_texture(self.name, str(self.index), x=self.hori_move.attribute+origin.x, y=origin.y, origin=origin, rotation=self.rotate.attribute)
tex.draw_texture(self.name, str(self.index), x=self.hori_move.attribute+origin.x, y=origin.y+(self.is_2p*535), origin=origin, rotation=self.rotate.attribute)
class Chibi4(BaseChibi):
def draw(self, tex: TextureWrapper):
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute)
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute, y=(self.is_2p*535))
class Chibi5(BaseChibi):
def draw(self, tex: TextureWrapper):
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute)
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute, y=(self.is_2p*535))
class Chibi8(BaseChibi):
def draw(self, tex: TextureWrapper):
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute)
tex.draw_texture(self.name, str(self.index), frame=self.texture_change.attribute, x=self.hori_move.attribute, y=(self.is_2p*535))
class Chibi13(BaseChibi):
def __init__(self, index: int, bpm: float, tex: TextureWrapper):
super().__init__(index, bpm, tex)
def __init__(self, index: int, bpm: float, tex: TextureWrapper, is_2p: bool):
super().__init__(index, bpm, tex, is_2p)
duration = (60000 / self.bpm)
self.scale = Animation.create_fade(duration, initial_opacity=1.0, final_opacity=0.75, delay=duration, reverse_delay=duration)
self.scale.start()
@@ -129,9 +131,9 @@ class Chibi13(BaseChibi):
def draw(self, tex: TextureWrapper):
tex.draw_texture(self.name, 'tail', frame=self.frame, x=self.hori_move.attribute, y=-self.vert_move.attribute)
if self.scale.attribute == 0.75:
tex.draw_texture(self.name, str(self.index), frame=self.frame, x=self.hori_move.attribute, y=-self.vert_move.attribute)
tex.draw_texture(self.name, str(self.index), frame=self.frame, x=self.hori_move.attribute, y=-self.vert_move.attribute+(self.is_2p*535))
else:
tex.draw_texture(self.name, str(self.index), scale=self.scale.attribute, center=True, frame=self.frame, x=self.hori_move.attribute, y=-self.vert_move.attribute)
tex.draw_texture(self.name, str(self.index), scale=self.scale.attribute, center=True, frame=self.frame, x=self.hori_move.attribute, y=-self.vert_move.attribute+(self.is_2p*535))
class ChibiController:
@@ -144,8 +146,8 @@ class ChibiController:
tex.load_zip(path, f'chibi/{self.name}')
tex.load_zip('background', 'chibi/chibi_bad')
def add_chibi(self, bad=False):
self.chibis.append(Chibi.create(self.index, self.bpm, bad, self.tex))
def add_chibi(self, player_num: int, bad=False):
self.chibis.append(Chibi.create(self.index, self.bpm, bad, self.tex, player_num == 2))
def update(self, current_time_ms: float, bpm: float):
self.bpm = bpm

View File

@@ -31,17 +31,17 @@ class DonBG1(DonBGBase):
def update(self, current_time_ms: float, is_clear: bool):
super().update(current_time_ms, is_clear)
self.overlay_move.update(current_time_ms)
def draw(self, tex: TextureWrapper):
self._draw_textures(tex, 1.0)
def draw(self, tex: TextureWrapper, y: float=0):
self._draw_textures(tex, 1.0, y)
if self.is_clear:
self._draw_textures(tex, self.clear_fade.attribute)
def _draw_textures(self, tex: TextureWrapper, fade: float):
self._draw_textures(tex, self.clear_fade.attribute, y)
def _draw_textures(self, tex: TextureWrapper, fade: float, y: float):
for i in range(5):
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute)
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=y)
for i in range(6):
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*347)+self.move.attribute*(347/328), y=self.overlay_move.attribute)
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*347)+self.move.attribute*(347/328), y=self.overlay_move.attribute+y)
for i in range(30):
tex.draw_texture(self.name, 'footer', frame=self.is_clear, fade=fade, x=(i*56)+self.move.attribute*((56/328)*3), y=self.overlay_move.attribute)
tex.draw_texture(self.name, 'footer', frame=self.is_clear, fade=fade, x=(i*56)+self.move.attribute*((56/328)*3), y=self.overlay_move.attribute+y)
class DonBG2(DonBGBase):
def __init__(self, tex: TextureWrapper, index: int, player_num: int, path: str):
@@ -50,14 +50,14 @@ class DonBG2(DonBGBase):
def update(self, current_time_ms: float, is_clear: bool):
super().update(current_time_ms, is_clear)
self.overlay_move.update(current_time_ms)
def draw(self, tex: TextureWrapper):
self._draw_textures(tex, 1.0)
def draw(self, tex: TextureWrapper, y: float = 0):
self._draw_textures(tex, 1.0, y)
if self.is_clear:
self._draw_textures(tex, self.clear_fade.attribute)
def _draw_textures(self, tex: TextureWrapper, fade: float):
self._draw_textures(tex, self.clear_fade.attribute, y)
def _draw_textures(self, tex: TextureWrapper, fade: float, y: float):
for i in range(5):
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute)
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=self.overlay_move.attribute)
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=y)
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=self.overlay_move.attribute+y)
class DonBG3(DonBGBase):
def __init__(self, tex: TextureWrapper, index: int, player_num: int, path: str):
@@ -79,17 +79,17 @@ class DonBG3(DonBGBase):
self.overlay_move.update(current_time_ms)
self.overlay_move_2.update(current_time_ms)
def draw(self, tex: TextureWrapper):
self._draw_textures(tex, 1.0)
def draw(self, tex: TextureWrapper, y: float = 0):
self._draw_textures(tex, 1.0, y)
if self.is_clear:
self._draw_textures(tex, self.clear_fade.attribute)
self._draw_textures(tex, self.clear_fade.attribute, y)
def _draw_textures(self, tex: TextureWrapper, fade: float):
def _draw_textures(self, tex: TextureWrapper, fade: float, y: float):
for i in range(10):
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*164)+self.move.attribute)
y = self.bounce_up.attribute - self.bounce_down.attribute + self.overlay_move.attribute + self.overlay_move_2.attribute
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*164)+self.move.attribute, y=y)
y_pos = self.bounce_up.attribute - self.bounce_down.attribute + self.overlay_move.attribute + self.overlay_move_2.attribute
for i in range(6):
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*328)+(self.move.attribute*2), y=y)
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*328)+(self.move.attribute*2), y=y_pos+y)
class DonBG4(DonBGBase):
def __init__(self, tex: TextureWrapper, index: int, player_num: int, path: str):
@@ -98,15 +98,15 @@ class DonBG4(DonBGBase):
def update(self, current_time_ms: float, is_clear: bool):
super().update(current_time_ms, is_clear)
self.overlay_move.update(current_time_ms)
def draw(self, tex: TextureWrapper):
self._draw_textures(tex, 1.0)
def draw(self, tex: TextureWrapper, y: float = 0):
self._draw_textures(tex, 1.0, y)
if self.is_clear:
self._draw_textures(tex, self.clear_fade.attribute)
self._draw_textures(tex, self.clear_fade.attribute, y)
def _draw_textures(self, tex: TextureWrapper, fade: float):
def _draw_textures(self, tex: TextureWrapper, fade: float, y: float):
for i in range(5):
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute)
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=self.overlay_move.attribute)
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=y)
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=self.overlay_move.attribute+y)
class DonBG5(DonBGBase):
def __init__(self, tex: TextureWrapper, index: int, player_num: int, path: str):
@@ -126,16 +126,16 @@ class DonBG5(DonBGBase):
self.bounce_down.restart()
self.adjust.update(current_time_ms)
def draw(self, tex: TextureWrapper):
self._draw_textures(tex, 1.0)
def draw(self, tex: TextureWrapper, y: float = 0):
self._draw_textures(tex, 1.0, y)
if self.is_clear:
self._draw_textures(tex, self.clear_fade.attribute)
self._draw_textures(tex, self.clear_fade.attribute, y)
def _draw_textures(self, tex: TextureWrapper, fade: float):
def _draw_textures(self, tex: TextureWrapper, fade: float, y: float):
for i in range(5):
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute)
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=y)
for i in range(6):
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*368)+(self.move.attribute * ((184/328)*2)), y=self.bounce_up.attribute - self.bounce_down.attribute - self.adjust.attribute)
tex.draw_texture(self.name, 'overlay', frame=self.is_clear, fade=fade, x=(i*368)+(self.move.attribute * ((184/328)*2)), y=self.bounce_up.attribute - self.bounce_down.attribute - self.adjust.attribute + y)
class DonBG6(DonBGBase):
def __init__(self, tex: TextureWrapper, index: int, player_num: int, path: str):
@@ -144,15 +144,15 @@ class DonBG6(DonBGBase):
def update(self, current_time_ms: float, is_clear: bool):
super().update(current_time_ms, is_clear)
self.overlay_move.update(current_time_ms)
def draw(self, tex: TextureWrapper):
self._draw_textures(tex, 1.0)
def draw(self, tex: TextureWrapper, y: float = 0):
self._draw_textures(tex, 1.0, y)
if self.is_clear:
self._draw_textures(tex, self.clear_fade.attribute)
self._draw_textures(tex, self.clear_fade.attribute, y)
def _draw_textures(self, tex: TextureWrapper, fade: float):
def _draw_textures(self, tex: TextureWrapper, fade: float, y: float):
for i in range(5):
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute)
tex.draw_texture(self.name, 'background', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=y)
for i in range(0, 6, 2):
tex.draw_texture(self.name, 'overlay_1', frame=self.is_clear, fade=fade, x=(i*264) + self.move.attribute*3, y=-self.move.attribute*0.85)
tex.draw_texture(self.name, 'overlay_1', frame=self.is_clear, fade=fade, x=(i*264) + self.move.attribute*3, y=-self.move.attribute*0.85+y)
for i in range(5):
tex.draw_texture(self.name, 'overlay_2', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=self.overlay_move.attribute)
tex.draw_texture(self.name, 'overlay_2', frame=self.is_clear, fade=fade, x=(i*328)+self.move.attribute, y=self.overlay_move.attribute+y)

View File

@@ -3,6 +3,14 @@ from libs.utils import global_tex
class Chara2D:
def __init__(self, index: int, bpm: float, path: str = 'chara'):
"""
Initialize a Chara2D object.
Args:
index (int): The index of the character.
bpm (float): The beats per minute.
path (str, optional): The path to the character's textures. Defaults to 'chara'.
"""
self.name = "chara_" + str(index)
self.tex = global_tex
self.anims = dict()
@@ -27,6 +35,12 @@ class Chara2D:
self.anims[name].start()
def set_animation(self, name: str):
"""
Set the current animation for the character.
Args:
name (str): The name of the animation to set.
"""
if name == self.current_anim:
return
if self.current_anim in self.temp_anims:
@@ -49,6 +63,15 @@ class Chara2D:
self.current_anim = name
self.anims[name].start()
def update(self, current_time_ms: float, bpm: float, is_clear: bool, is_rainbow: bool):
"""
Update the character's animation state and appearance.
Args:
current_time_ms (float): The current time in milliseconds.
bpm (float): The beats per minute.
is_clear (bool): Whether the gauge is in clear mode.
is_rainbow (bool): Whether the gauge is in rainbow mode.
"""
if is_rainbow and not self.is_rainbow:
self.is_rainbow = True
self.set_animation('soul_in')
@@ -76,6 +99,14 @@ class Chara2D:
self.anims[self.current_anim].restart()
def draw(self, x: float = 0, y: float = 0, mirror=False):
"""
Draw the character on the screen.
Args:
x (float): The x-coordinate of the character's position.
y (float): The y-coordinate of the character's position.
mirror (bool): Whether to mirror the character horizontally.
"""
if self.is_rainbow and self.current_anim not in {'soul_in', 'balloon_pop', 'balloon_popping'}:
self.tex.draw_texture(self.name, self.current_anim + '_max', frame=self.anims[self.current_anim].attribute, x=x, y=y)
else:

View File

@@ -1,8 +1,12 @@
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
@dataclass
class Modifiers:
"""
Modifiers for the game.
"""
auto: bool = False
speed: float = 1.0
display: bool = False
@@ -11,9 +15,25 @@ class Modifiers:
@dataclass
class GlobalData:
"""
Global data for the game. Should be accessed via the global_data variable.
Attributes:
selected_song (Path): The currently selected song.
songs_played (int): The number of songs played.
config (dict): The configuration settings.
song_hashes (dict[str, list[dict]]): A dictionary mapping song hashes to their metadata.
song_paths (dict[Path, str]): A dictionary mapping song paths to their hashes.
song_progress (float): The progress of the loading bar.
total_songs (int): The total number of songs.
hit_sound (int): The index of the hit sound currently used.
player_num (int): The player number. Either 1 or 2.
input_locked (int): The input lock status. 0 means unlocked, 1 or greater means locked.
modifiers (Modifiers): The modifiers for the game.
"""
selected_song: Path = Path()
songs_played: int = 0
config: dict = field(default_factory=lambda: dict())
config: dict[str, Any] = field(default_factory=lambda: dict())
song_hashes: dict[str, list[dict]] = field(default_factory=lambda: dict()) #Hash to path
song_paths: dict[Path, str] = field(default_factory=lambda: dict()) #path to hash
song_progress: float = 0.0

View File

@@ -6,19 +6,43 @@ from libs.audio import audio
class Nameplate:
"""Nameplate for displaying player information."""
def __init__(self, name: str, title: str, player_num: int, dan: int, is_gold: bool):
"""Initialize a Nameplate object.
Args:
name (str): The player's name.
title (str): The player's title.
player_num (int): The player's number.
dan (int): The player's dan level.
is_gold (bool): Whether the player's dan is gold.
"""
self.name = OutlinedText(name, 22, ray.WHITE, ray.BLACK, outline_thickness=3.0)
self.title = OutlinedText(title, 20, ray.BLACK, ray.WHITE, outline_thickness=0)
self.dan_index = dan
self.player_num = player_num
self.is_gold = is_gold
def update(self, current_time_ms: float):
"""Update the Nameplate object.
Args:
current_time_ms (float): The current time in milliseconds.
Currently unused as rainbow nameplates are not implemented.
"""
pass
def unload(self):
"""Unload the Nameplate object."""
self.name.unload()
self.title.unload()
def draw(self, x: int, y: int, fade: float = 1.0):
"""Draw the Nameplate object.
Args:
x (int): The x-coordinate of the Nameplate object.
y (int): The y-coordinate of the Nameplate object.
fade (float): The fade value of the Nameplate object.
"""
tex = global_tex
tex.draw_texture('nameplate', 'shadow', x=x, y=y, fade=min(0.5, fade))
if self.player_num == -1:
@@ -46,23 +70,28 @@ class Nameplate:
self.title.draw(self.title.default_src, dest, ray.Vector2(0, 0), 0, ray.fade(ray.WHITE, fade))
class Indicator:
"""Indicator class for displaying drum navigation."""
class State(Enum):
"""Enum representing the different states of the indicator."""
SKIP = 0
SIDE = 1
SELECT = 2
WAIT = 3
def __init__(self, state: State):
"""Initialize the indicator with the given state."""
self.state = state
self.don_fade = global_tex.get_animation(6)
self.blue_arrow_move = global_tex.get_animation(7)
self.blue_arrow_fade = global_tex.get_animation(8)
def update(self, current_time_ms: float):
"""Update the indicator's animations."""
self.don_fade.update(current_time_ms)
self.blue_arrow_move.update(current_time_ms)
self.blue_arrow_fade.update(current_time_ms)
def draw(self, x: int, y: int, fade=1.0):
"""Draw the indicator at the given position with the given fade."""
tex = global_tex
tex.draw_texture('indicator', 'background', x=x, y=y, fade=fade)
tex.draw_texture('indicator', 'text', frame=self.state.value, x=x, y=y, fade=fade)
@@ -80,29 +109,42 @@ class Indicator:
tex.draw_texture('indicator', 'drum_don', fade=min(fade, self.don_fade.attribute), index=self.state.value, x=x, y=y)
class CoinOverlay:
"""Coin overlay for the game."""
def __init__(self):
"""Initialize the coin overlay."""
pass
def update(self, current_time_ms: float):
"""Update the coin overlay. Unimplemented"""
pass
def draw(self, x: int = 0, y: int = 0):
"""Draw the coin overlay.
Only draws free play for now."""
tex = global_tex
tex.draw_texture('overlay', 'free_play', x=x, y=y)
class AllNetIcon:
"""All.Net status icon for the game."""
def __init__(self):
"""Initialize the All.Net status icon."""
pass
def update(self, current_time_ms: float):
"""Update the All.Net status icon."""
pass
def draw(self, x: int = 0, y: int = 0):
"""Draw the All.Net status icon. Only drawn offline for now"""
tex = global_tex
tex.draw_texture('overlay', 'allnet_indicator', x=x, y=y, frame=0)
class EntryOverlay:
"""Banapass and Camera status icons"""
def __init__(self):
"""Initialize the Banapass and Camera status icons."""
self.online = False
def update(self, current_time_ms: float):
"""Update the Banapass and Camera status icons."""
pass
def draw(self, x: int = 0, y: int = 0):
"""Draw the Banapass and Camera status icons."""
tex = global_tex
tex.draw_texture('overlay', 'banapass_or', x=x, y=y, frame=self.online)
tex.draw_texture('overlay', 'banapass_card', x=x, y=y, frame=self.online)
@@ -113,7 +155,16 @@ class EntryOverlay:
tex.draw_texture('overlay', 'camera', x=x, y=y, frame=0)
class Timer:
"""Timer class for displaying countdown timers."""
def __init__(self, time: int, current_time_ms: float, confirm_func):
"""
Initialize a Timer object.
Args:
time (int): The value to start counting down from.
current_time_ms (float): The current time in milliseconds.
confirm_func (function): The function to call when the timer finishes.
"""
self.time = time
self.last_time = current_time_ms
self.counter = str(self.time)
@@ -124,6 +175,7 @@ class Timer:
self.is_finished = False
self.is_frozen = get_config()["general"]["timer_frozen"]
def update(self, current_time_ms: float):
"""Update the timer's state."""
if self.time == 0 and not self.is_finished and not audio.is_sound_playing('voice_timer_0'):
self.is_finished = True
self.confirm_func()
@@ -148,6 +200,7 @@ class Timer:
elif self.time == 0:
audio.play_sound('voice_timer_0', 'voice')
def draw(self, x: int = 0, y: int = 0):
"""Draw the timer on the screen."""
tex = global_tex
if self.time < 10:
tex.draw_texture('timer', 'bg_red')

View File

@@ -23,6 +23,7 @@ class DiffHashesDecoder(json.JSONDecoder):
super().__init__(object_hook=diff_hashes_object_hook, *args, **kwargs)
def read_tjap3_score(input_file: Path):
"""Read a TJAPlayer3 score.ini file and return the scores and clears."""
score_ini = configparser.ConfigParser()
score_ini.read(input_file)
scores = [int(score_ini['HiScore.Drums']['HiScore1']),
@@ -50,6 +51,7 @@ def read_tjap3_score(input_file: Path):
return scores, clears, None
def build_song_hashes(output_dir=Path("cache")):
"""Build a dictionary of song hashes and save it to a file."""
if not output_dir.exists():
output_dir.mkdir()
song_hashes: dict[str, list[dict]] = dict()

View File

@@ -14,6 +14,7 @@ SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
class Texture:
"""Texture class for managing textures and animations."""
def __init__(self, name: str, texture: Union[ray.Texture, list[ray.Texture]], init_vals: dict[str, int]):
self.name = name
self.texture = texture
@@ -33,12 +34,14 @@ class Texture:
self.controllable: list[bool] = [False]
class TextureWrapper:
"""Texture wrapper class for managing textures and animations."""
def __init__(self):
self.textures: dict[str, dict[str, Texture]] = dict()
self.animations: dict[int, BaseAnimation] = dict()
self.graphics_path = Path("Graphics")
def unload_textures(self):
"""Unload all textures and animations."""
for zip in self.textures:
for file in self.textures[zip]:
tex_object = self.textures[zip][file]
@@ -49,6 +52,8 @@ class TextureWrapper:
ray.unload_texture(tex_object.texture)
def get_animation(self, index: int, is_copy: bool = False):
"""Get an animation by ID and returns a reference.
Returns a copy of the animation if is_copy is True."""
if index not in self.animations:
raise Exception(f"Unable to find id {index} in loaded animations")
if is_copy:
@@ -83,12 +88,14 @@ class TextureWrapper:
tex_object.controllable = [tex_mapping.get("controllable", False)]
def load_animations(self, screen_name: str):
"""Load animations for a screen."""
screen_path = self.graphics_path / screen_name
if (screen_path / 'animation.json').exists():
with open(screen_path / 'animation.json') as json_file:
self.animations = parse_animations(json.loads(json_file.read()))
def load_zip(self, screen_name: str, subset: str):
"""Load textures from a zip file."""
zip = (self.graphics_path / screen_name / subset).with_suffix('.zip')
if screen_name in self.textures and subset in self.textures[screen_name]:
return
@@ -134,6 +141,7 @@ class TextureWrapper:
raise Exception(f"Texture {tex_name} was not found in {zip}")
def load_screen_textures(self, screen_name: str) -> None:
"""Load textures for a screen."""
screen_path = self.graphics_path / screen_name
if (screen_path / 'animation.json').exists():
with open(screen_path / 'animation.json') as json_file:
@@ -165,6 +173,26 @@ class TextureWrapper:
mirror: str = '', x: float = 0, y: float = 0, x2: float = 0, y2: float = 0,
origin: ray.Vector2 = ray.Vector2(0,0), rotation: float = 0, fade: float = 1.1,
index: int = 0, src: Optional[ray.Rectangle] = None) -> None:
"""
Wrapper function for raylib's draw_texture_pro().
Parameters:
subset (str): The subset of textures to use.
texture (str): The name of the texture to draw.
color (ray.Color): The color to tint the texture.
frame (int): The frame of the texture to draw. Only used if the texture is animated.
scale (float): The scale factor to apply to the texture.
center (bool): Whether to center the texture.
mirror (str): The direction to mirror the texture, either 'horizontal' or 'vertical'.
x (float): An x-value added to the top-left corner of the texture.
y (float): The y-value added to the top-left corner of the texture.
x2 (float): The x-value added to the bottom-right corner of the texture.
y2 (float): The y-value added to the bottom-right corner of the texture.
origin (ray.Vector2): The origin point of the texture.
rotation (float): The rotation angle of the texture.
fade (float): The fade factor to apply to the texture.
index (int): The index of the position data for the texture. Only used if the texture has multiple positions.
src (Optional[ray.Rectangle]): The source rectangle of the texture.
"""
mirror_x = -1 if mirror == 'horizontal' else 1
mirror_y = -1 if mirror == 'vertical' else 1
if fade != 1.1:

View File

@@ -11,18 +11,36 @@ from libs.utils import get_pixels_per_frame, global_data, strip_comments
@lru_cache(maxsize=64)
def get_ms_per_measure(bpm_val, time_sig):
def get_ms_per_measure(bpm_val: float, time_sig: float):
"""Calculate the number of milliseconds per measure."""
#https://gist.github.com/KatieFrogs/e000f406bbc70a12f3c34a07303eec8b#measure
if bpm_val == 0:
return 0
return 60000 * (time_sig * 4) / bpm_val
@lru_cache(maxsize=64)
def get_pixels_per_ms(pixels_per_frame):
def get_pixels_per_ms(pixels_per_frame: float):
"""Calculate the number of pixels per millisecond."""
return pixels_per_frame / (1000 / 60)
@dataclass()
class Note:
"""A note in a TJA file.
Attributes:
type (int): The type (color) of the note.
hit_ms (float): The time at which the note should be hit.
load_ms (float): The time at which the note should be loaded.
pixels_per_frame_x (float): The number of pixels per frame in the x direction.
pixels_per_frame_y (float): The number of pixels per frame in the y direction.
display (bool): Whether the note should be displayed.
index (int): The index of the note.
bpm (float): The beats per minute of the song.
gogo_time (bool): Whether the note is a gogo time note.
moji (int): The text drawn below the note.
is_branch_start (bool): Whether the note is the start of a branch.
branch_params (str): The parameters (requirements) of the branch.
"""
type: int = field(init=False)
hit_ms: float = field(init=False)
load_ms: float = field(init=False)
@@ -78,6 +96,12 @@ class Note:
@dataclass
class Drumroll(Note):
"""A drumroll note in a TJA file.
Attributes:
_source_note (Note): The source note.
color (int): The color of the drumroll. (0-255 where 255 is red)
"""
_source_note: Note
color: int = field(init=False)
@@ -94,6 +118,14 @@ class Drumroll(Note):
@dataclass
class Balloon(Note):
"""A balloon note in a TJA file.
Attributes:
_source_note (Note): The source note.
count (int): The number of hits it takes to pop.
popped (bool): Whether the balloon has been popped.
is_kusudama (bool): Whether the balloon is a kusudama.
"""
_source_note: Note
count: int = field(init=False)
popped: bool = False
@@ -125,6 +157,10 @@ class Balloon(Note):
@dataclass
class NoteList:
"""A collection of notes
play_notes: A list of notes, drumrolls, and balloons that are played by the player
draw_notes: A list of notes, drumrolls, and balloons that are drawn by the player
bars: A list of bars"""
play_notes: list[Note | Drumroll | Balloon] = field(default_factory=lambda: [])
draw_notes: list[Note | Drumroll | Balloon] = field(default_factory=lambda: [])
bars: list[Note] = field(default_factory=lambda: [])
@@ -144,6 +180,13 @@ class NoteList:
@dataclass
class CourseData:
"""A collection of course metadata
level: number of stars
balloon: list of balloon counts
scoreinit: Unused
scorediff: Unused
is_branching: whether the course has branches
"""
level: int = 0
balloon: list[int] = field(default_factory=lambda: [])
scoreinit: list[int] = field(default_factory=lambda: [])
@@ -152,6 +195,19 @@ class CourseData:
@dataclass
class TJAMetadata:
"""Metadata for a TJA file
title: dictionary for song titles, accessed by language code
subtitle: dictionary for song subtitles, accessed by language code
genre: genre of the song
wave: path to the song's audio file
demostart: start time of the preview
offset: offset of the song's audio file
bpm: beats per minute of the song
bgmovie: path to the song's background movie file
movieoffset: offset of the song's background movie file
scene_preset: background for the song
course_data: dictionary of course metadata, accessed by diff number
"""
title: dict[str, str] = field(default_factory= lambda: {'en': ''})
subtitle: dict[str, str] = field(default_factory= lambda: {'en': ''})
genre: str = ''
@@ -166,6 +222,11 @@ class TJAMetadata:
@dataclass
class TJAEXData:
"""Extra data for TJA files
new_audio: Contains the word "-New Audio-" in any song title
old_audio: Contains the word "-Old Audio-" in any song title
limited_time: Contains the word "限定" in any song title or subtitle
new: If the TJA file has been created or modified in the last week"""
new_audio: bool = False
old_audio: bool = False
limited_time: bool = False
@@ -173,6 +234,14 @@ class TJAEXData:
def calculate_base_score(notes: NoteList) -> int:
"""Calculate the base score for a song based on the number of notes, balloons, and drumrolls.
Args:
notes (NoteList): The list of notes in the song.
Returns:
int: The base score for the song.
"""
total_notes = 0
balloon_count = 0
drumroll_msec = 0
@@ -195,6 +264,14 @@ def calculate_base_score(notes: NoteList) -> int:
return math.ceil((1000000 - (balloon_count * 100) - (16.920079999994086 * drumroll_msec / 1000 * 100)) / total_notes / 10) * 10
def test_encodings(file_path):
"""Test the encoding of a file by trying different encodings.
Args:
file_path (Path): The path to the file to test.
Returns:
str: The encoding that successfully decoded the file.
"""
encodings = ['utf-8-sig', 'shift-jis', 'utf-8']
final_encoding = None
@@ -209,8 +286,28 @@ def test_encodings(file_path):
class TJAParser:
"""Parse a TJA file and extract metadata and data.
Args:
path (Path): The path to the TJA file.
start_delay (int): The delay in milliseconds before the first note.
distance (int): The distance between notes.
Attributes:
metadata (TJAMetadata): The metadata extracted from the TJA file.
ex_data (TJAEXData): The extended data extracted from the TJA file.
data (list): The data extracted from the TJA file.
"""
DIFFS = {0: "easy", 1: "normal", 2: "hard", 3: "oni", 4: "edit", 5: "tower", 6: "dan"}
def __init__(self, path: Path, start_delay: int = 0, distance: int = 866):
"""
Initialize a TJA object.
Args:
path (Path): The path to the TJA file.
start_delay (int): The delay in milliseconds before the first note.
distance (int): The distance between notes.
"""
self.file_path: Path = path
encoding = test_encodings(self.file_path)
@@ -226,6 +323,9 @@ class TJAParser:
self.current_ms: float = start_delay
def get_metadata(self):
"""
Extract metadata from the TJA file.
"""
current_diff = None # Track which difficulty we're currently processing
for item in self.data:
@@ -332,6 +432,15 @@ class TJAParser:
self.ex_data.limited_time = True
def data_to_notes(self, diff) -> list[list[str]]:
"""
Convert the data to notes.
Args:
diff (int): The difficulty level.
Returns:
list[list[str]]: The notes.
"""
diff_name = self.DIFFS.get(diff, "").lower()
# Use enumerate for single iteration
@@ -380,6 +489,16 @@ class TJAParser:
return notes
def get_moji(self, play_note_list: list[Note], ms_per_measure: float) -> None:
"""
Assign 口唱歌 (note phoneticization) to notes.
Args:
play_note_list (list[Note]): The list of notes to process.
ms_per_measure (float): The duration of a measure in milliseconds.
Returns:
None
"""
se_notes = {
1: [0, 1, 2], # Note '1' has three possible sound effects
2: [3, 4], # Note '2' has two possible sound effects
@@ -440,6 +559,7 @@ class TJAParser:
play_note_list[-3].moji = se_notes[1][2]
def notes_to_position(self, diff: int):
"""Parse a TJA's notes into a NoteList."""
master_notes = NoteList()
branch_m: list[NoteList] = []
branch_e: list[NoteList] = []
@@ -705,6 +825,7 @@ class TJAParser:
return master_notes, branch_m, branch_e, branch_n
def hash_note_data(self, notes: NoteList):
"""Hashes the note data for the given NoteList."""
n = hashlib.sha256()
list1 = notes.play_notes
list2 = notes.bars
@@ -726,6 +847,7 @@ class TJAParser:
return n.hexdigest()
def modifier_speed(notes: NoteList, value: float):
"""Modifies the speed of the notes in the given NoteList."""
modded_notes = notes.draw_notes.copy()
modded_bars = notes.bars.copy()
for note in modded_notes:
@@ -737,12 +859,14 @@ def modifier_speed(notes: NoteList, value: float):
return modded_notes, modded_bars
def modifier_display(notes: NoteList):
"""Modifies the display of the notes in the given NoteList."""
modded_notes = notes.draw_notes.copy()
for note in modded_notes:
note.display = False
return modded_notes
def modifier_inverse(notes: NoteList):
"""Inverts the type of the notes in the given NoteList."""
modded_notes = notes.play_notes.copy()
type_mapping = {1: 2, 2: 1, 3: 4, 4: 3}
for note in modded_notes:
@@ -751,6 +875,8 @@ def modifier_inverse(notes: NoteList):
return modded_notes
def modifier_random(notes: NoteList, value: int):
"""Randomly modifies the type of the notes in the given NoteList.
value: 1 == kimagure, 2 == detarame"""
#value: 1 == kimagure, 2 == detarame
modded_notes = notes.play_notes.copy()
percentage = int(len(modded_notes) / 5) * value
@@ -762,6 +888,7 @@ def modifier_random(notes: NoteList, value: int):
return modded_notes
def apply_modifiers(notes: NoteList):
"""Applies all selected modifiers from global_data to the given NoteList."""
if global_data.modifiers.display:
draw_notes = modifier_display(notes)
if global_data.modifiers.inverse:

View File

@@ -4,7 +4,12 @@ from libs.utils import OutlinedText, global_tex
class Transition:
"""Transition class for the game."""
def __init__(self, title: str, subtitle: str, is_second: bool = False) -> None:
"""Initialize the transition object.
title: str - The title of the chart.
subtitle: str - The subtitle of the chart.
is_second: bool - Whether this is the second half of the transition."""
self.is_finished = False
self.rainbow_up = global_tex.get_animation(0)
self.mini_up = global_tex.get_animation(1)
@@ -16,6 +21,7 @@ class Transition:
self.is_second = is_second
def start(self):
"""Start the transition effect."""
self.rainbow_up.start()
self.mini_up.start()
self.chara_down.start()
@@ -23,6 +29,7 @@ class Transition:
self.song_info_fade_out.start()
def update(self, current_time_ms: float):
"""Update the transition effect."""
self.rainbow_up.update(current_time_ms)
self.chara_down.update(current_time_ms)
self.mini_up.update(current_time_ms)
@@ -30,7 +37,7 @@ class Transition:
self.song_info_fade_out.update(current_time_ms)
self.is_finished = self.song_info_fade.is_finished
def draw_song_info(self):
def _draw_song_info(self):
color_1 = ray.fade(ray.WHITE, self.song_info_fade.attribute)
color_2 = ray.fade(ray.WHITE, min(0.70, self.song_info_fade.attribute))
offset = 0
@@ -50,6 +57,7 @@ class Transition:
self.subtitle.draw(self.subtitle.default_src, dest, ray.Vector2(0, 0), 0, color_1)
def draw(self):
"""Draw the transition effect."""
total_offset = 0
if self.is_second:
total_offset = 816
@@ -65,4 +73,4 @@ class Transition:
global_tex.draw_texture('rainbow_transition', 'chara_right', x=self.mini_up.attribute//2 + chara_offset, y=-self.mini_up.attribute + offset - total_offset)
global_tex.draw_texture('rainbow_transition', 'chara_center', y=-self.rainbow_up.attribute + offset - total_offset)
self.draw_song_info()
self._draw_song_info()

View File

@@ -39,6 +39,7 @@ def force_dedicated_gpu():
print(e)
def rounded(num: float) -> int:
"""Round a number to the nearest integer"""
sign = 1 if (num >= 0) else -1
num = abs(num)
result = int(num)
@@ -47,9 +48,11 @@ def rounded(num: float) -> int:
return sign * result
def get_current_ms() -> int:
"""Get the current time in milliseconds"""
return rounded(time.time() * 1000)
def strip_comments(code: str) -> str:
"""Strip comments from a string of code"""
result = ''
index = 0
for line in code.splitlines():
@@ -63,6 +66,7 @@ def strip_comments(code: str) -> str:
@lru_cache
def get_pixels_per_frame(bpm: float, time_signature: float, distance: float) -> float:
"""Calculate the number of pixels per frame"""
if bpm == 0:
return 0
beat_duration = 60 / bpm
@@ -71,6 +75,7 @@ def get_pixels_per_frame(bpm: float, time_signature: float, distance: float) ->
return (distance / total_frames)
def get_config() -> dict[str, Any]:
"""Get the configuration from the TOML file"""
config_path = Path('dev-config.toml') if Path('dev-config.toml').exists() else Path('config.toml')
with open(config_path, "r", encoding="utf-8") as f:
@@ -79,6 +84,7 @@ def get_config() -> dict[str, Any]:
return json.loads(json.dumps(config_file))
def save_config(config: dict[str, Any]) -> None:
"""Save the configuration to the TOML file"""
if Path('dev-config.toml').exists():
with open(Path('dev-config.toml'), "w", encoding="utf-8") as f:
tomlkit.dump(config, f)
@@ -87,6 +93,7 @@ def save_config(config: dict[str, Any]) -> None:
tomlkit.dump(config, f)
def is_l_don_pressed() -> bool:
"""Check if the left don button is pressed"""
if global_data.input_locked:
return False
keys = global_data.config["keys"]["left_don"]
@@ -111,6 +118,7 @@ def is_l_don_pressed() -> bool:
return False
def is_r_don_pressed() -> bool:
"""Check if the right don button is pressed"""
if global_data.input_locked:
return False
keys = global_data.config["keys"]["right_don"]
@@ -137,6 +145,7 @@ def is_r_don_pressed() -> bool:
return False
def is_l_kat_pressed() -> bool:
"""Check if the left kat button is pressed"""
if global_data.input_locked:
return False
keys = global_data.config["keys"]["left_kat"]
@@ -163,6 +172,7 @@ def is_l_kat_pressed() -> bool:
return False
def is_r_kat_pressed() -> bool:
"""Check if the right kat button is pressed"""
if global_data.input_locked:
return False
keys = global_data.config["keys"]["right_kat"]
@@ -190,6 +200,18 @@ def is_r_kat_pressed() -> bool:
@dataclass
class SessionData:
"""Data class for storing session data. Wiped after the result screen.
selected_difficulty: The difficulty level selected by the user.
song_title: The title of the song being played.
genre_index: The index of the genre being played.
result_score: The score achieved in the game.
result_good: The number of good notes achieved in the game.
result_ok: The number of ok notes achieved in the game.
result_bad: The number of bad notes achieved in the game.
result_max_combo: The maximum combo achieved in the game.
result_total_drumroll: The total drumroll achieved in the game.
result_gauge_length: The length of the gauge achieved in the game.
prev_score: The previous score pulled from the database."""
selected_difficulty: int = 0
song_title: str = ''
genre_index: int = 0
@@ -206,6 +228,7 @@ session_data = SessionData()
global_tex = TextureWrapper()
def reset_session():
"""Reset the session data."""
return SessionData()
text_cache = set()
@@ -218,7 +241,19 @@ for file in Path('cache/image').iterdir():
text_cache.add(file.stem)
class OutlinedText:
"""Create an outlined text object."""
def __init__(self, text: str, font_size: int, color: ray.Color, outline_color: ray.Color, outline_thickness=5.0, vertical=False):
"""
Create an outlined text object.
Args:
text (str): The text to be displayed.
font_size (int): The size of the font.
color (ray.Color): The color of the text.
outline_color (ray.Color): The color of the outline.
outline_thickness (float): The thickness of the outline.
vertical (bool): Whether the text is vertical or not.
"""
self.text = text
self.hash = self._hash_text(text, font_size, color, vertical)
self.outline_thickness = outline_thickness
@@ -463,6 +498,16 @@ class OutlinedText:
return texture
def draw(self, src: ray.Rectangle, dest: ray.Rectangle, origin: ray.Vector2, rotation: float, color: ray.Color):
"""
Draw the outlined text object.
Args:
src (ray.Rectangle): The source rectangle of the texture.
dest (ray.Rectangle): The destination rectangle of the texture.
origin (ray.Vector2): The origin of the texture.
rotation (float): The rotation of the texture.
color (ray.Color): The color of the text.
"""
if isinstance(color, tuple):
alpha_value = ray.ffi.new('float*', color[3] / 255.0)
else:
@@ -475,5 +520,11 @@ class OutlinedText:
ray.end_shader_mode()
def unload(self):
"""
Unload the outlined text object.
Args:
None
"""
ray.unload_shader(self.shader)
ray.unload_texture(self.texture)