mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 03:30:13 +01:00
Compare commits
5 Commits
ac7c7abf82
...
keyboard-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8a219243c | ||
|
|
e0b7f0a863 | ||
|
|
7ef74a601b | ||
|
|
102d82001f | ||
|
|
f278868a83 |
@@ -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()
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -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
|
||||||
|
|||||||
Submodule Skins/PyTaikoGreen updated: b62da05eda...ccd048aa2d
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user