2 Commits

Author SHA1 Message Date
Yonokid
d8a219243c unlikely fix 2026-01-01 10:56:55 -05:00
Yonokid
e0b7f0a863 add sections to ai battle 2026-01-01 10:40:46 -05:00
6 changed files with 126 additions and 20 deletions

View File

@@ -265,7 +265,7 @@ def check_args():
selected_difficulty = args.difficulty selected_difficulty = args.difficulty
else: else:
selected_difficulty = max(tja.metadata.course_data.keys()) selected_difficulty = max(tja.metadata.course_data.keys())
current_screen = Screens.GAME_PRACTICE if args.practice else Screens.GAME current_screen = Screens.GAME_PRACTICE if args.practice else Screens.AI_GAME
global_data.session_data[PlayerNum.P1].selected_song = path global_data.session_data[PlayerNum.P1].selected_song = path
global_data.session_data[PlayerNum.P1].selected_difficulty = selected_difficulty global_data.session_data[PlayerNum.P1].selected_difficulty = selected_difficulty
global_data.modifiers[PlayerNum.P1].auto = args.auto global_data.modifiers[PlayerNum.P1].auto = args.auto
@@ -392,10 +392,13 @@ def main():
logger.info("Cursor hidden") logger.info("Cursor hidden")
last_fps = 1 last_fps = 1
last_color = pyray.BLACK last_color = pyray.BLACK
last_discord_check = 0
while not ray.WindowShouldClose(): while not ray.WindowShouldClose():
if discord_connected: current_time = get_current_ms()
if discord_connected and current_time > last_discord_check + 1000:
check_discord_heartbeat(current_screen) check_discord_heartbeat(current_screen)
last_discord_check = current_time
if ray.IsKeyPressed(global_data.config["keys"]["fullscreen_key"]): if ray.IsKeyPressed(global_data.config["keys"]["fullscreen_key"]):
ray.ToggleFullscreen() ray.ToggleFullscreen()

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
import copy import copy
import logging import logging
import random import random
import time
from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
@@ -26,12 +28,23 @@ from scenes.game import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class AIDifficulty(Enum):
LVL_1 = (0.90, 0.10)
LVL_2 = (0.92, 0.08)
LVL_3 = (0.94, 0.06)
LVL_4 = (0.96, 0.04)
LVL_5 = (0.98, 0.02)
def __iter__(self):
return iter(self.value)
class AIBattleGameScreen(GameScreen): class AIBattleGameScreen(GameScreen):
def on_screen_start(self): def on_screen_start(self):
super().on_screen_start() super().on_screen_start()
session_data = global_data.session_data[global_data.player_num] session_data = global_data.session_data[global_data.player_num]
self.song_info = SongInfoAI(session_data.song_title, session_data.genre_index) self.song_info = SongInfoAI(session_data.song_title, session_data.genre_index)
self.background = AIBackground(session_data.selected_difficulty) self.background = AIBackground(session_data.selected_difficulty)
self.section_board = SectionBoard()
def global_keys(self): def global_keys(self):
if ray.is_key_pressed(global_data.config["keys"]["restart_key"]): if ray.is_key_pressed(global_data.config["keys"]["restart_key"]):
@@ -89,28 +102,35 @@ class AIBattleGameScreen(GameScreen):
"""Initialize the TJA file""" """Initialize the TJA file"""
self.tja = TJAParser(song, start_delay=self.start_delay) self.tja = TJAParser(song, start_delay=self.start_delay)
self.movie = None self.movie = None
global_data.session_data[global_data.player_num].song_title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en']) session_data = global_data.session_data[global_data.player_num]
session_data.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: 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.song_music = audio.load_music_stream(self.tja.metadata.wave, 'song')
tja_copy = copy.deepcopy(self.tja) tja_copy = copy.deepcopy(self.tja)
self.player_1 = PlayerNoChara(self.tja, global_data.player_num, global_data.session_data[global_data.player_num].selected_difficulty, False, global_data.modifiers[global_data.player_num]) self.player_1 = PlayerNoChara(self.tja, global_data.player_num, session_data.selected_difficulty, False, global_data.modifiers[global_data.player_num])
self.player_1.gauge = AIGauge(self.player_1.player_num, self.player_1.difficulty, self.tja.metadata.course_data[self.player_1.difficulty].level, self.player_1.total_notes, self.player_1.is_2p) self.player_1.gauge = AIGauge(self.player_1.player_num, self.player_1.difficulty, self.tja.metadata.course_data[self.player_1.difficulty].level, self.player_1.total_notes, self.player_1.is_2p)
ai_modifiers = copy.deepcopy(global_data.modifiers[global_data.player_num]) ai_modifiers = copy.deepcopy(global_data.modifiers[global_data.player_num])
ai_modifiers.auto = True ai_modifiers.auto = True
self.player_2 = AIPlayer(tja_copy, PlayerNum.AI, global_data.session_data[global_data.player_num].selected_difficulty, True, ai_modifiers) self.player_2 = AIPlayer(tja_copy, PlayerNum.AI, session_data.selected_difficulty, True, ai_modifiers, AIDifficulty.LVL_2)
self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000) self.start_ms = (get_current_ms() - self.tja.metadata.offset*1000)
self.precise_start = time.time() - self.tja.metadata.offset
self.total_notes = len(self.player_1.don_notes) + len(self.player_1.kat_notes) self.total_notes = len(self.player_1.don_notes) + len(self.player_1.kat_notes)
logger.info(f"TJA initialized for two-player song: {song}") logger.info(f"TJA initialized for two-player song: {song}")
def update_scoreboards(self): def update_scoreboards(self):
section_notes = self.total_notes // 5 section_notes = (self.total_notes // 5) if self.section_board.num < 3 else (self.total_notes // 5) + (self.total_notes % 5) - 1
if self.player_1.good_count + self.player_1.ok_count + self.player_1.bad_count == section_notes: if self.player_1.good_count + self.player_1.ok_count + self.player_1.bad_count == section_notes:
self.player_2.good_percentage = self.player_1.good_count / section_notes self.player_2.good_percentage = self.player_1.good_count / section_notes
self.player_2.ok_percentage = self.player_1.ok_count / section_notes self.player_2.ok_percentage = self.player_1.ok_count / section_notes
logger.info(f"AI Good Percentage: {self.player_2.good_percentage}, AI OK Percentage: {self.player_2.ok_percentage}") logger.info(f"AI Good Percentage: {self.player_2.good_percentage}, AI OK Percentage: {self.player_2.ok_percentage}")
self.player_1.good_count, self.player_1.ok_count, self.player_1.bad_count = 0, 0, 0 self.player_1.good_count, self.player_1.ok_count, self.player_1.bad_count = 0, 0, 0
self.player_2.good_count, self.player_2.ok_count, self.player_2.bad_count = 0, 0, 0 self.player_2.good_count, self.player_2.ok_count, self.player_2.bad_count = 0, 0, 0
if self.background.contest_point >= 10:
self.section_board.wins[self.section_board.num] = True
else:
self.section_board.wins[self.section_board.num] = False
self.section_board.num += 1
def update(self): def update(self):
super(GameScreen, self).update() super(GameScreen, self).update()
@@ -123,12 +143,12 @@ class AIBattleGameScreen(GameScreen):
self.start_ms = current_time - self.tja.metadata.offset*1000 self.start_ms = current_time - self.tja.metadata.offset*1000
self.update_background(current_time) self.update_background(current_time)
if self.song_music is not None: self.update_audio(self.current_ms)
audio.update_music_stream(self.song_music)
self.player_1.update(self.current_ms, current_time, None) self.player_1.update(self.current_ms, current_time, None)
self.player_2.update(self.current_ms, current_time, None) self.player_2.update(self.current_ms, current_time, None)
self.update_scoreboards() self.update_scoreboards()
self.section_board.update(current_time, self.player_1.good_count + self.player_1.ok_count + self.player_1.bad_count, self.total_notes)
self.song_info.update(current_time) self.song_info.update(current_time)
self.result_transition.update(current_time) self.result_transition.update(current_time)
@@ -142,6 +162,8 @@ class AIBattleGameScreen(GameScreen):
if current_time >= self.end_ms + 1000: if current_time >= self.end_ms + 1000:
if self.player_1.ending_anim is None: if self.player_1.ending_anim is None:
self.spawn_ending_anims() self.spawn_ending_anims()
if self.player_1.modifiers.subdiff in [0, 1, 2, 3, 4, 5, 9, 13]:
self.write_score()
if current_time >= self.end_ms + 8533.34: if current_time >= self.end_ms + 8533.34:
if not self.result_transition.is_started: if not self.result_transition.is_started:
self.result_transition.start() self.result_transition.start()
@@ -161,10 +183,54 @@ class AIBattleGameScreen(GameScreen):
self.movie.draw() self.movie.draw()
elif self.background is not None: elif self.background is not None:
self.background.draw(self.player_1.chara, self.player_2.chara) self.background.draw(self.player_1.chara, self.player_2.chara)
self.section_board.draw()
self.player_1.draw(self.current_ms, self.start_ms, self.mask_shader) self.player_1.draw(self.current_ms, self.start_ms, self.mask_shader)
self.player_2.draw(self.current_ms, self.start_ms, self.mask_shader) self.player_2.draw(self.current_ms, self.start_ms, self.mask_shader)
self.draw_overlay() self.draw_overlay()
class SectionBoard:
def __init__(self):
self.num = 0
self.wins: list[Optional[bool]] = [None] * 5
self.current_progress = 0
self.progress_bar_flash = Animation.create_fade(133, loop=True, reverse_delay=0)
self.section_highlight_flash = Animation.create_fade(350, loop=True, reverse_delay=0)
self.section_highlight_flash.start()
self.progress_bar_flash.start()
def update(self, current_time, player_notes, total_notes):
self.current_progress = player_notes / (total_notes // 5) if self.num < 3 else player_notes / ((total_notes // 5) + (total_notes % 5))
self.progress_bar_flash.update(current_time)
self.section_highlight_flash.update(current_time)
def draw(self):
if self.current_progress < 0.75:
color = ray.GREEN
fade = 1.0
else:
color = ray.YELLOW
fade = self.progress_bar_flash.attribute
ray.draw_rectangle(int(177 * tex.screen_scale), int(160 * tex.screen_scale), int(148 * tex.screen_scale), int(20 * tex.screen_scale), ray.GRAY)
ray.draw_rectangle(int(177 * tex.screen_scale), int(160 * tex.screen_scale), int(self.current_progress * (148 * tex.screen_scale)), int(20 * tex.screen_scale), ray.fade(color, fade))
tex.draw_texture('ai_battle', 'progress_bar')
if self.num < len(self.wins):
tex.draw_texture('ai_battle', 'section_text', index=0, frame=self.num)
if self.num < len(self.wins) - 1:
tex.draw_texture('ai_battle', 'section_text', index=1, frame=self.num+1)
tex.draw_texture('ai_battle', 'sections')
if self.num < len(self.wins):
tex.draw_texture('ai_battle', 'section_highlight_green', index=self.num)
tex.draw_texture('ai_battle', 'section_highlight_white', index=self.num, fade=self.section_highlight_flash.attribute)
for i in range(len(self.wins)):
if self.wins[i] is not None:
if self.wins[i]:
tex.draw_texture('ai_battle', 'section_win', index=i)
else:
tex.draw_texture('ai_battle', 'section_lose', index=i)
class PlayerNoChara(Player): class PlayerNoChara(Player):
def __init__(self, tja: TJAParser, player_num: PlayerNum, difficulty: int, is_2p: bool, modifiers: Modifiers): def __init__(self, tja: TJAParser, player_num: PlayerNum, difficulty: int, is_2p: bool, modifiers: Modifiers):
super().__init__(tja, player_num, difficulty, is_2p, modifiers) super().__init__(tja, player_num, difficulty, is_2p, modifiers)
@@ -216,7 +282,7 @@ class PlayerNoChara(Player):
margin = tex.textures["ai_battle"]["scoreboard_num"].width//2 margin = tex.textures["ai_battle"]["scoreboard_num"].width//2
total_width = len(str(counter)) * margin total_width = len(str(counter)) * margin
for i, digit in enumerate(str(counter)): for i, digit in enumerate(str(counter)):
tex.draw_texture('ai_battle', 'scoreboard_num', frame=int(digit), x=-(total_width // 2) + (i * margin), y=-self.stretch_animation[j].attribute, y2=self.stretch_animation[j].attribute, index=j, controllable=True) tex.draw_texture('ai_battle', 'scoreboard_num', frame=int(digit), x=-(total_width // 2) + (i * margin), y=-self.stretch_animation[j].attribute, y2=self.stretch_animation[j].attribute, index=j)
# Group 8: Special animations and counters # Group 8: Special animations and counters
if self.drumroll_counter is not None: if self.drumroll_counter is not None:
@@ -231,7 +297,7 @@ class PlayerNoChara(Player):
class AIPlayer(Player): class AIPlayer(Player):
def __init__(self, tja: TJAParser, player_num: PlayerNum, difficulty: int, is_2p: bool, modifiers: Modifiers): def __init__(self, tja: TJAParser, player_num: PlayerNum, difficulty: int, is_2p: bool, modifiers: Modifiers, ai_difficulty: AIDifficulty):
super().__init__(tja, player_num, difficulty, is_2p, modifiers) super().__init__(tja, player_num, difficulty, is_2p, modifiers)
self.stretch_animation = [tex.get_animation(5, is_copy=True) for _ in range(4)] self.stretch_animation = [tex.get_animation(5, is_copy=True) for _ in range(4)]
self.chara = Chara2D(player_num - 1, self.bpm) self.chara = Chara2D(player_num - 1, self.bpm)
@@ -240,8 +306,7 @@ class AIPlayer(Player):
self.gauge_hit_effect = [] self.gauge_hit_effect = []
plate_info = global_data.config[f'nameplate_{self.is_2p+1}p'] plate_info = global_data.config[f'nameplate_{self.is_2p+1}p']
self.nameplate = Nameplate(plate_info['name'], plate_info['title'], PlayerNum.AI, plate_info['dan'], plate_info['gold'], plate_info['rainbow'], plate_info['title_bg']) self.nameplate = Nameplate(plate_info['name'], plate_info['title'], PlayerNum.AI, plate_info['dan'], plate_info['gold'], plate_info['rainbow'], plate_info['title_bg'])
self.good_percentage = 0.90 self.good_percentage, self.ok_percentage = ai_difficulty
self.ok_percentage = 0.07
def update(self, ms_from_start: float, current_time: float, background: Optional[Background]): def update(self, ms_from_start: float, current_time: float, background: Optional[Background]):
good_count, ok_count, bad_count, total_drumroll = self.good_count, self.ok_count, self.bad_count, self.total_drumroll good_count, ok_count, bad_count, total_drumroll = self.good_count, self.ok_count, self.bad_count, self.total_drumroll
@@ -344,7 +409,7 @@ class AIPlayer(Player):
margin = tex.textures["ai_battle"]["scoreboard_num"].width//2 margin = tex.textures["ai_battle"]["scoreboard_num"].width//2
total_width = len(str(counter)) * margin total_width = len(str(counter)) * margin
for i, digit in enumerate(str(counter)): for i, digit in enumerate(str(counter)):
tex.draw_texture('ai_battle', 'scoreboard_num', frame=int(digit), x=-(total_width // 2) + (i * margin), y=-self.stretch_animation[j].attribute, y2=self.stretch_animation[j].attribute, index=j+4, controllable=True) tex.draw_texture('ai_battle', 'scoreboard_num', frame=int(digit), x=-(total_width // 2) + (i * margin), y=-self.stretch_animation[j].attribute, y2=self.stretch_animation[j].attribute, index=j+4)
# Group 8: Special animations and counters # Group 8: Special animations and counters
if self.drumroll_counter is not None: if self.drumroll_counter is not None:
@@ -423,10 +488,13 @@ class AIBackground:
] ]
self.contest_point_fade = Animation.create_fade(166, initial_opacity=0.0, final_opacity=1.0, reverse_delay=166, delay=166, loop=True) self.contest_point_fade = Animation.create_fade(166, initial_opacity=0.0, final_opacity=1.0, reverse_delay=166, delay=166, loop=True)
self.triangles_down = Animation.create_move(8500, total_distance=1152, loop=True)
self.contest_point_fade.start() self.contest_point_fade.start()
self.triangles_down.start()
def update(self, current_ms: float): def update(self, current_ms: float):
self.contest_point_fade.update(current_ms) self.contest_point_fade.update(current_ms)
self.triangles_down.update(current_ms)
def update_values(self, player_judge: tuple[int, int], ai_judge: tuple[int, int]): def update_values(self, player_judge: tuple[int, int], ai_judge: tuple[int, int]):
player_total = (player_judge[0] * self.multipliers[self.difficulty][0]) + (player_judge[1] * self.multipliers[self.difficulty][1]) player_total = (player_judge[0] * self.multipliers[self.difficulty][0]) + (player_judge[1] * self.multipliers[self.difficulty][1])
@@ -444,6 +512,9 @@ class AIBackground:
tex.draw_texture('ai_battle', 'red_tile_lower', frame=i, x=(i*tile_width)) tex.draw_texture('ai_battle', 'red_tile_lower', frame=i, x=(i*tile_width))
for i in range(self.total_tiles - self.contest_point): for i in range(self.total_tiles - self.contest_point):
tex.draw_texture('ai_battle', 'blue_tile_lower', frame=i, x=(((self.total_tiles - 1) - i)*tile_width)) tex.draw_texture('ai_battle', 'blue_tile_lower', frame=i, x=(((self.total_tiles - 1) - i)*tile_width))
tex.draw_texture('ai_battle', 'lower_triangles_1', y=self.triangles_down.attribute, fade=0.5)
tex.draw_texture('ai_battle', 'lower_triangles_2', y=self.triangles_down.attribute, fade=0.5)
tex.draw_texture('ai_battle', 'highlight_tile_lower', x=self.contest_point * tile_width, fade=self.contest_point_fade.attribute) tex.draw_texture('ai_battle', 'highlight_tile_lower', x=self.contest_point * tile_width, fade=self.contest_point_fade.attribute)
def draw_upper(self, chara_1: Chara2D, chara_2: Chara2D): def draw_upper(self, chara_1: Chara2D, chara_2: Chara2D):

View File

@@ -2,6 +2,7 @@ import bisect
import logging import logging
import math import math
import sqlite3 import sqlite3
import time
from collections import deque from collections import deque
from enum import IntEnum from enum import IntEnum
from itertools import chain from itertools import chain
@@ -77,6 +78,7 @@ class GameScreen(Screen):
self.paused = False self.paused = False
self.pause_time = 0 self.pause_time = 0
self.audio_time = 0 self.audio_time = 0
self.last_resync = 0
self.movie = None self.movie = None
self.song_music = None self.song_music = None
if global_data.config["general"]["nijiiro_notes"]: if global_data.config["general"]["nijiiro_notes"]:
@@ -153,6 +155,7 @@ class GameScreen(Screen):
self.player_1 = Player(self.tja, global_data.player_num, global_data.session_data[global_data.player_num].selected_difficulty, False, global_data.modifiers[global_data.player_num]) self.player_1 = Player(self.tja, global_data.player_num, global_data.session_data[global_data.player_num].selected_difficulty, False, global_data.modifiers[global_data.player_num])
self.start_ms = get_current_ms() - self.tja.metadata.offset*1000 self.start_ms = get_current_ms() - self.tja.metadata.offset*1000
self.precise_start = time.time() - self.tja.metadata.offset
def write_score(self): def write_score(self):
"""Write the score to the database""" """Write the score to the database"""
@@ -249,6 +252,20 @@ class GameScreen(Screen):
if self.background is not None: if self.background is not None:
self.background.update(current_time, self.bpm, self.player_1.gauge, None) self.background.update(current_time, self.bpm, self.player_1.gauge, None)
def update_audio(self, ms_from_start: float):
if not self.song_started:
return
if self.song_music is not None:
audio.update_music_stream(self.song_music)
'''
raw_audio_time = audio.get_music_time_played(self.song_music)
current_time = time.time() - self.precise_start
if abs(raw_audio_time - current_time) >= 0.006 and current_time > self.last_resync + 1000:
audio.seek_music_stream(self.song_music, current_time)
logger.info(f"Resyncing due to difference: {raw_audio_time} - {current_time} = {raw_audio_time - current_time}")
self.last_resync = current_time
'''
def update(self): def update(self):
super().update() super().update()
current_time = get_current_ms() current_time = get_current_ms()
@@ -261,8 +278,7 @@ class GameScreen(Screen):
self.start_ms = current_time - self.tja.metadata.offset*1000 self.start_ms = current_time - self.tja.metadata.offset*1000
self.update_background(current_time) self.update_background(current_time)
if self.song_music is not None: self.update_audio(self.current_ms)
audio.update_music_stream(self.song_music)
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)