5 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
Yonokid
7ef74a601b dancers can be removed 2025-12-31 14:45:08 -05:00
Yonokid
102d82001f Update README.md 2025-12-31 14:25:49 -05:00
Yonokid
f278868a83 better judgment improvements 2025-12-31 14:17:11 -05:00
10 changed files with 182 additions and 44 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

@@ -47,18 +47,30 @@ Just make sure to use `/` and not `\`!<br>
Q: I'm trying to play on Mac and it can't open!<br> Q: I'm trying to play on Mac and it can't open!<br>
A: Delete your installation and follow these commands in the terminal:<br> A: Delete your installation and follow these commands in the terminal:<br>
``` ```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" # Move to the place where you want to download Pytaiko, this example will be in the desktop
curl -LsSf https://astral.sh/uv/install.sh | sh # Make sure the directory is not saved in iCloud! This will create an issue on macOS where the executable cannot compile.
source $HOME/.local/bin/env cd ~/Desktop
# Installing dependencies
brew install python@3.12 uv git speexdsp libsndfile
# Downloading Pytaiko project
git clone https://github.com/Yonokid/PyTaiko git clone https://github.com/Yonokid/PyTaiko
cd PyTaiko cd PyTaiko
brew install libsndfile
brew install speexdsp # Compiling audio libraries
cd libs/audio cd libs/audio
make make
mv libaudio.dylib ../../ mv libaudio.dylib ...
cd ../../ cd ...
# Running game (for testing)
uv run PyTaiko.py uv run PyTaiko.py
# Creating executable
uv add nuitka
uv run nuitka --mode=app --noinclude-setuptools-mode=nofollow --noinclude-IPython-mode=nofollow --assume-yes-for-downloads PyTaiko.py
``` ```
## Installation ## Installation
@@ -71,7 +83,7 @@ Download the latest release for your operating system from the [releases page](h
2. Run `PyTaiko.exe` 2. Run `PyTaiko.exe`
#### macOS #### macOS
- Run with Python directly (see [Building from Source](#building-from-source)) - Run with Python directly or self compile (see [Building from Source](#building-from-source))
#### Linux #### Linux
- Try running the compiled `PyTaiko.bin` binary - Try running the compiled `PyTaiko.bin` binary

View File

@@ -130,10 +130,17 @@ class Background:
current_milestone = min(self.max_dancers - 1, int(gauge_1p.gauge_length / (clear_threshold / self.max_dancers))) current_milestone = min(self.max_dancers - 1, int(gauge_1p.gauge_length / (clear_threshold / self.max_dancers)))
else: else:
current_milestone = self.max_dancers current_milestone = self.max_dancers
if current_milestone > self.last_milestone and current_milestone <= self.max_dancers: if current_milestone > self.last_milestone and current_milestone <= self.max_dancers:
self.dancer.add_dancer() self.dancer.add_dancer()
self.last_milestone = current_milestone self.last_milestone = current_milestone
logger.info(f"Dancer milestone reached: {current_milestone}/{self.max_dancers}") logger.info(f"Dancer milestone reached: {current_milestone}/{self.max_dancers}")
elif current_milestone < self.last_milestone:
dancers_to_remove = self.last_milestone - current_milestone
for _ in range(dancers_to_remove):
self.dancer.remove_dancer()
self.last_milestone = current_milestone
logger.info(f"Dancer milestones lost: {current_milestone}/{self.max_dancers} (removed {dancers_to_remove})")
if self.bg_fever is not None and gauge_1p is not None: if self.bg_fever is not None and gauge_1p is not None:
if not self.is_clear and gauge_1p.is_clear: if not self.is_clear and gauge_1p.is_clear:
self.bg_fever.start() self.bg_fever.start()

View File

@@ -162,6 +162,12 @@ class BaseDancerGroup():
dancer.start() dancer.start()
self.active_count += 1 self.active_count += 1
def remove_dancer(self):
if self.active_count > 1:
self.active_count -= 1
position = self.spawn_positions[self.active_count]
self.active_dancers[position] = None
def update(self, current_time_ms: float, bpm: float): def update(self, current_time_ms: float, bpm: float):
for dancer in self.dancers: for dancer in self.dancers:
dancer.update(current_time_ms, bpm) dancer.update(current_time_ms, bpm)

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
@@ -13,7 +15,7 @@ from libs.chara_2d import Chara2D
from libs.global_data import Difficulty, Modifiers, PlayerNum, global_data from libs.global_data import Difficulty, Modifiers, PlayerNum, global_data
from libs.global_objects import Nameplate from libs.global_objects import Nameplate
from libs.texture import tex from libs.texture import tex
from libs.tja import Note, TJAParser from libs.tja import TJAParser
from libs.utils import get_current_ms, global_tex from libs.utils import get_current_ms, global_tex
from scenes.game import ( from scenes.game import (
DrumType, DrumType,
@@ -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,20 +102,36 @@ 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):
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:
self.player_2.good_percentage = self.player_1.good_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}")
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
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()
current_time = get_current_ms() current_time = get_current_ms()
@@ -114,18 +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)
section_notes = self.total_notes // 5 self.update_scoreboards()
if self.player_1.good_count + self.player_1.ok_count + self.player_1.bad_count == section_notes: 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.player_2.good_percentage = self.player_1.good_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}")
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.song_info.update(current_time) self.song_info.update(current_time)
self.result_transition.update(current_time) self.result_transition.update(current_time)
@@ -139,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()
@@ -149,17 +174,63 @@ class AIBattleGameScreen(GameScreen):
return self.global_keys() return self.global_keys()
def update_background(self, current_time): def update_background(self, current_time):
self.background.update(current_time, (self.player_1.good_count, self.player_1.ok_count), (self.player_2.good_count, self.player_2.ok_count)) if self.player_1.don_notes == self.player_2.don_notes and self.player_1.kat_notes == self.player_2.kat_notes:
self.background.update_values((self.player_1.good_count, self.player_1.ok_count), (self.player_2.good_count, self.player_2.ok_count))
self.background.update(current_time)
def draw(self): def draw(self):
if self.movie is not None: if self.movie is not None:
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)
@@ -211,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:
@@ -226,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)
@@ -235,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
@@ -339,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:
@@ -418,13 +488,18 @@ 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, player_judge: tuple[int, int], ai_judge: tuple[int, int]): 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]):
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])
ai_total = (ai_judge[0] * self.multipliers[self.difficulty][0]) + (ai_judge[1] * self.multipliers[self.difficulty][1]) ai_total = (ai_judge[0] * self.multipliers[self.difficulty][0]) + (ai_judge[1] * self.multipliers[self.difficulty][1])
self.contest_point = player_total - ai_total + 10 self.contest_point = ((player_total - ai_total) // 2) + 10
self.contest_point = min(max(1, self.contest_point), self.total_tiles - 1) self.contest_point = min(max(1, self.contest_point), self.total_tiles - 1)
def unload(self): def unload(self):
@@ -437,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

@@ -3,6 +3,7 @@ import logging
import pyray as ray import pyray as ray
from libs.audio import audio from libs.audio import audio
from libs.chara_2d import Chara2D
from libs.file_navigator import SongFile from libs.file_navigator import SongFile
from libs.global_data import Difficulty, PlayerNum, global_data from libs.global_data import Difficulty, PlayerNum, global_data
from libs.texture import tex from libs.texture import tex
@@ -27,9 +28,11 @@ class AISongSelectScreen(SongSelectScreen):
super().on_screen_start() super().on_screen_start()
self.player_1 = AISongSelectPlayer(global_data.player_num, self.text_fade_in) self.player_1 = AISongSelectPlayer(global_data.player_num, self.text_fade_in)
global_data.modifiers[global_data.player_num].subdiff = 0 global_data.modifiers[global_data.player_num].subdiff = 0
self.ai_chara = Chara2D(PlayerNum.AI-1)
def update_players(self, current_time) -> str: def update_players(self, current_time) -> str:
self.player_1.update(current_time) self.player_1.update(current_time)
self.ai_chara.update(current_time)
if self.text_fade_out.is_finished: if self.text_fade_out.is_finished:
self.player_1.selected_song = True self.player_1.selected_song = True
next_screen = "AI_GAME" next_screen = "AI_GAME"
@@ -69,6 +72,7 @@ class AISongSelectScreen(SongSelectScreen):
tex.draw_texture('global', 'song_select', fade=self.text_fade_out.attribute) tex.draw_texture('global', 'song_select', fade=self.text_fade_out.attribute)
self.draw_players() self.draw_players()
self.ai_chara.draw(x=tex.skin_config["song_select_chara_2p"].x, y=tex.skin_config["song_select_chara_2p"].y, mirror=True)
if self.state == State.BROWSING and self.navigator.items != []: if self.state == State.BROWSING and self.navigator.items != []:
curr_item = self.navigator.get_current_item() curr_item = self.navigator.get_current_item()
@@ -183,12 +187,8 @@ class AISongSelectPlayer(SongSelectPlayer):
offset = 0 offset = 0
if self.subdiff_selector is not None: if self.subdiff_selector is not None:
offset = -self.subdiff_selector.move.attribute*1.05 offset = -self.subdiff_selector.move.attribute*1.05
if self.player_num == PlayerNum.P1: self.nameplate.draw(tex.skin_config["song_select_nameplate_1p"].x, tex.skin_config["song_select_nameplate_1p"].y)
self.nameplate.draw(tex.skin_config["song_select_nameplate_1p"].x, tex.skin_config["song_select_nameplate_1p"].y) self.chara.draw(x=tex.skin_config["song_select_chara_1p"].x, y=tex.skin_config["song_select_chara_1p"].y + (offset*0.6))
self.chara.draw(x=tex.skin_config["song_select_chara_1p"].x, y=tex.skin_config["song_select_chara_1p"].y + (offset*0.6))
else:
self.nameplate.draw(tex.skin_config["song_select_nameplate_2p"].x, tex.skin_config["song_select_nameplate_2p"].y)
self.chara.draw(mirror=True, x=tex.skin_config["song_select_chara_2p"].x, y=tex.skin_config["song_select_chara_2p"].y + (offset*0.6))
if self.subdiff_selector is not None: if self.subdiff_selector is not None:
self.subdiff_selector.draw() self.subdiff_selector.draw()

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)