mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
add fancy backgrounds (one of them)
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
Songs
|
||||
__pycache__
|
||||
.venv
|
||||
.ruff_cache
|
||||
scores.db
|
||||
|
||||
24
PyTaiko.py
24
PyTaiko.py
@@ -1,3 +1,5 @@
|
||||
import sqlite3
|
||||
|
||||
import pyray as ray
|
||||
|
||||
from libs.audio import audio
|
||||
@@ -16,7 +18,29 @@ class Screens:
|
||||
GAME = "GAME"
|
||||
RESULT = "RESULT"
|
||||
|
||||
def create_song_db():
|
||||
with sqlite3.connect('scores.db') as con:
|
||||
cursor = con.cursor()
|
||||
create_table_query = '''
|
||||
CREATE TABLE IF NOT EXISTS Scores (
|
||||
hash TEXT PRIMARY KEY,
|
||||
en_name TEXT NOT NULL,
|
||||
jp_name TEXT NOT NULL,
|
||||
diff INTEGER,
|
||||
score INTEGER,
|
||||
good INTEGER,
|
||||
ok INTEGER,
|
||||
bad INTEGER,
|
||||
drumroll INTEGER,
|
||||
combo INTEGER
|
||||
);
|
||||
'''
|
||||
cursor.execute(create_table_query)
|
||||
con.commit()
|
||||
print("Scores database created successfully")
|
||||
|
||||
def main():
|
||||
create_song_db()
|
||||
screen_width: int = get_config()["video"]["screen_width"]
|
||||
screen_height: int = get_config()["video"]["screen_height"]
|
||||
render_width, render_height = ray.get_render_width(), ray.get_render_height()
|
||||
|
||||
@@ -14,7 +14,7 @@ right_don = ['J']
|
||||
right_kat = ['K']
|
||||
|
||||
[audio]
|
||||
device_type = 'ASIO'
|
||||
device_type = 'WASAPI'
|
||||
asio_buffer = 6
|
||||
|
||||
[video]
|
||||
|
||||
@@ -86,8 +86,10 @@ class FadeAnimation(BaseAnimation):
|
||||
class MoveAnimation(BaseAnimation):
|
||||
def __init__(self, duration: float, total_distance: int = 0,
|
||||
start_position: int = 0, delay: float = 0.0,
|
||||
reverse_delay: Optional[float] = None,
|
||||
ease_in: Optional[str] = None, ease_out: Optional[str] = None):
|
||||
super().__init__(duration, delay)
|
||||
self.reverse_delay = reverse_delay
|
||||
self.total_distance = total_distance
|
||||
self.start_position = start_position
|
||||
self.ease_in = ease_in
|
||||
@@ -100,6 +102,13 @@ class MoveAnimation(BaseAnimation):
|
||||
|
||||
elif elapsed_time >= self.delay + self.duration:
|
||||
self.attribute = self.start_position + self.total_distance
|
||||
if self.reverse_delay is not None:
|
||||
self.start_ms = current_time_ms
|
||||
self.delay = self.reverse_delay
|
||||
self.start_position = self.start_position + self.total_distance
|
||||
self.total_distance = -(self.total_distance)
|
||||
self.reverse_delay = None
|
||||
else:
|
||||
self.is_finished = True
|
||||
else:
|
||||
progress = (elapsed_time - self.delay) / self.duration
|
||||
@@ -194,6 +203,7 @@ class Animation:
|
||||
duration: Length of the move in milliseconds
|
||||
start_position: The coordinates of the object before the move
|
||||
total_distance: The distance travelled from the start to end position
|
||||
reverse_delay: If provided, move will play in reverse after this delay
|
||||
delay: Time to wait before starting the move
|
||||
ease_in: Control ease into the move
|
||||
ease_out: Control ease out of the move
|
||||
|
||||
12
libs/tja.py
12
libs/tja.py
@@ -1,3 +1,4 @@
|
||||
import hashlib
|
||||
import math
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field, fields
|
||||
@@ -375,6 +376,10 @@ class TJAParser:
|
||||
note = Balloon(note)
|
||||
note.count = int(balloon[balloon_index])
|
||||
balloon_index += 1
|
||||
elif item == '8':
|
||||
new_pixels_per_ms = play_note_list[-1].pixels_per_frame / (1000 / 60)
|
||||
note.load_ms = note.hit_ms - (self.distance / new_pixels_per_ms)
|
||||
note.pixels_per_frame = play_note_list[-1].pixels_per_frame
|
||||
self.current_ms += increment
|
||||
play_note_list.append(note)
|
||||
self.get_moji(play_note_list, ms_per_measure)
|
||||
@@ -389,3 +394,10 @@ class TJAParser:
|
||||
draw_note_list = deque(sorted(play_note_list, key=lambda n: n.load_ms))
|
||||
bar_list = deque(sorted(bar_list, key=lambda b: b.load_ms))
|
||||
return play_note_list, draw_note_list, bar_list
|
||||
|
||||
def hash_note_data(self, notes: list):
|
||||
n = hashlib.sha256()
|
||||
for bar in notes:
|
||||
for part in bar:
|
||||
n.update(part.encode('utf-8'))
|
||||
return n.hexdigest()
|
||||
|
||||
146
scenes/game.py
146
scenes/game.py
@@ -1,6 +1,6 @@
|
||||
import bisect
|
||||
import math
|
||||
import random
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -8,9 +8,11 @@ import pyray as ray
|
||||
|
||||
from libs.animation import Animation
|
||||
from libs.audio import audio
|
||||
from libs.backgrounds import Background
|
||||
from libs.tja import Balloon, Drumroll, Note, TJAParser, calculate_base_score
|
||||
from libs.utils import (
|
||||
OutlinedText,
|
||||
draw_scaled_texture,
|
||||
get_config,
|
||||
get_current_ms,
|
||||
global_data,
|
||||
@@ -36,6 +38,8 @@ class GameScreen:
|
||||
self.start_delay = 1000
|
||||
self.song_started = False
|
||||
|
||||
self.background = Background(width, height)
|
||||
|
||||
def load_textures(self):
|
||||
self.textures = load_all_textures_from_zip(Path('Graphics/lumendata/enso_system/common.zip'))
|
||||
zip_file = Path('Graphics/lumendata/enso_system/common.zip')
|
||||
@@ -77,25 +81,6 @@ class GameScreen:
|
||||
self.textures.update(load_all_textures_from_zip(Path('Graphics/lumendata/enso_system/base1p.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path('Graphics/lumendata/enso_system/don1p.zip')))
|
||||
|
||||
self.bg_fever_name = 'bg_fever_a_' + str(random.randint(1, 4)).zfill(2)
|
||||
self.bg_normal_name = 'bg_nomal_a_' + str(random.randint(1, 5)).zfill(2)
|
||||
self.chibi_name = 'chibi_a_' + str(random.randint(1, 14)).zfill(2)
|
||||
self.dance_name = 'dance_a_' + str(random.randint(1, 22)).zfill(2)
|
||||
self.footer_name = 'dodai_a_' + str(random.randint(1, 3)).zfill(2)
|
||||
self.donbg_name = 'donbg_a_' + str(random.randint(1, 6)).zfill(2)
|
||||
self.fever_name = 'fever_a_' + str(random.randint(1, 4)).zfill(2)
|
||||
self.renda_name = 'renda_a_' + str(random.randint(1, 3)).zfill(2)
|
||||
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.bg_fever_name}.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.bg_normal_name}.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.chibi_name}.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.dance_name}.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.footer_name}.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.donbg_name}_1p.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.donbg_name}_2p.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.fever_name}.zip')))
|
||||
self.textures.update(load_all_textures_from_zip(Path(f'Graphics/lumendata/enso_original/{self.renda_name}.zip')))
|
||||
|
||||
|
||||
self.result_transition_1 = load_texture_from_zip(Path('Graphics/lumendata/enso_result.zip'), 'retry_game_img00125.png')
|
||||
self.result_transition_2 = load_texture_from_zip(Path('Graphics/lumendata/enso_result.zip'), 'retry_game_img00126.png')
|
||||
@@ -154,6 +139,28 @@ class GameScreen:
|
||||
self.end_ms = 0
|
||||
return 'RESULT'
|
||||
|
||||
def write_score(self):
|
||||
if get_config()['general']['autoplay']:
|
||||
return
|
||||
with sqlite3.connect('scores.db') as con:
|
||||
cursor = con.cursor()
|
||||
hash = self.tja.hash_note_data(self.tja.data_to_notes(self.player_1.difficulty)[0])
|
||||
check_query = "SELECT score FROM Scores WHERE hash = ? LIMIT 1"
|
||||
cursor.execute(check_query, (hash,))
|
||||
result = cursor.fetchone()
|
||||
if result is None or session_data.result_score > result[0]:
|
||||
insert_query = '''
|
||||
INSERT OR REPLACE INTO Scores (hash, en_name, jp_name, diff, score, good, ok, bad, drumroll, combo)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
'''
|
||||
data = (hash, self.tja.title,
|
||||
self.tja.title_ja, self.player_1.difficulty,
|
||||
session_data.result_score, session_data.result_good,
|
||||
session_data.result_ok, session_data.result_bad,
|
||||
session_data.result_total_drumroll, session_data.result_max_combo)
|
||||
cursor.execute(insert_query, data)
|
||||
con.commit()
|
||||
|
||||
def update(self):
|
||||
self.on_screen_start()
|
||||
self.current_ms = get_current_ms() - self.start_ms
|
||||
@@ -165,6 +172,8 @@ class GameScreen:
|
||||
self.song_started = True
|
||||
if self.movie is not None:
|
||||
self.movie.update()
|
||||
else:
|
||||
self.background.update(get_current_ms(), self.player_1.gauge.gauge_length > self.player_1.gauge.clear_start[min(self.player_1.difficulty, 3)])
|
||||
|
||||
self.player_1.update(self)
|
||||
if self.song_info is not None:
|
||||
@@ -176,6 +185,7 @@ class GameScreen:
|
||||
return self.on_screen_end()
|
||||
elif len(self.player_1.play_notes) == 0:
|
||||
session_data.result_score, session_data.result_good, session_data.result_ok, session_data.result_bad, session_data.result_max_combo, session_data.result_total_drumroll = self.player_1.get_result_score()
|
||||
self.write_score()
|
||||
session_data.result_gauge_length = self.player_1.gauge.gauge_length
|
||||
if self.end_ms != 0:
|
||||
if get_current_ms() >= self.end_ms + 8533.34:
|
||||
@@ -184,25 +194,17 @@ class GameScreen:
|
||||
else:
|
||||
self.end_ms = get_current_ms()
|
||||
|
||||
def draw_background(self):
|
||||
for i in range(0, self.width, self.textures[self.donbg_name + '_1p'][0].width):
|
||||
ray.draw_texture(self.textures[self.donbg_name + '_1p'][0], i, 0, ray.WHITE)
|
||||
ray.draw_texture(self.textures[self.bg_normal_name][0], 0, 360, ray.WHITE)
|
||||
ray.draw_texture(self.textures[self.bg_normal_name][1], 0, 360, ray.fade(ray.WHITE, 0.25))
|
||||
ray.draw_texture(self.textures[self.footer_name][0], 0, self.height - self.textures[self.footer_name][0].height + 20, ray.WHITE)
|
||||
|
||||
def draw(self):
|
||||
if self.movie is not None:
|
||||
self.movie.draw()
|
||||
else:
|
||||
self.draw_background()
|
||||
self.background.draw()
|
||||
self.player_1.draw(self)
|
||||
if self.song_info is not None:
|
||||
self.song_info.draw(self)
|
||||
if self.result_transition is not None:
|
||||
self.result_transition.draw(self.width, self.height, self.result_transition_1, self.result_transition_2)
|
||||
|
||||
|
||||
class Player:
|
||||
TIMING_GOOD = 25.0250015258789
|
||||
TIMING_OK = 75.0750045776367
|
||||
@@ -252,6 +254,7 @@ class Player:
|
||||
self.input_log: dict[float, tuple] = dict()
|
||||
|
||||
self.gauge = Gauge(self.difficulty, metadata[-1][self.difficulty][0])
|
||||
self.gauge_hit_effect: list[GaugeHitEffect] = []
|
||||
|
||||
self.autoplay_hit_side = 'L'
|
||||
|
||||
@@ -318,7 +321,7 @@ class Player:
|
||||
if 5 <= current_note.type <= 7:
|
||||
bisect.insort_left(self.current_notes_draw, current_note, key=lambda x: x.index)
|
||||
try:
|
||||
tail_note = next(note for note in self.draw_note_list if note.index == current_note.index+1)
|
||||
tail_note = next(note for note in self.draw_note_list if note.type == 8)
|
||||
bisect.insort_left(self.current_notes_draw, tail_note, key=lambda x: x.index)
|
||||
self.draw_note_list.remove(tail_note)
|
||||
except Exception as e:
|
||||
@@ -357,7 +360,8 @@ class Player:
|
||||
if self.combo > self.max_combo:
|
||||
self.max_combo = self.combo
|
||||
|
||||
self.draw_arc_list.append(NoteArc(note_type, get_current_ms(), self.player_number))
|
||||
self.draw_arc_list.append(NoteArc(note_type, get_current_ms(), self.player_number, note.type == 3 or note.type == 4) or note.type == 7)
|
||||
#game_screen.background.chibis.append(game_screen.background.Chibi())
|
||||
|
||||
if note in self.current_notes_draw:
|
||||
index = self.current_notes_draw.index(note)
|
||||
@@ -365,7 +369,7 @@ class Player:
|
||||
|
||||
def check_drumroll(self, game_screen: GameScreen, drum_type: int):
|
||||
note_type = game_screen.note_type_list[drum_type][0]
|
||||
self.draw_arc_list.append(NoteArc(note_type, get_current_ms(), self.player_number))
|
||||
self.draw_arc_list.append(NoteArc(note_type, get_current_ms(), self.player_number, drum_type == 3 or drum_type == 4))
|
||||
self.curr_drumroll_count += 1
|
||||
self.total_drumroll += 1
|
||||
self.score += 100
|
||||
@@ -511,7 +515,12 @@ class Player:
|
||||
if self.lane_hit_effect is not None:
|
||||
self.lane_hit_effect.update(get_current_ms())
|
||||
self.animation_manager(self.draw_drum_hit_list)
|
||||
self.animation_manager(self.draw_arc_list)
|
||||
for anim in self.draw_arc_list:
|
||||
anim.update(get_current_ms())
|
||||
if anim.is_finished:
|
||||
self.gauge_hit_effect.append(GaugeHitEffect(anim.texture, anim.is_big))
|
||||
self.draw_arc_list.remove(anim)
|
||||
self.animation_manager(self.gauge_hit_effect)
|
||||
self.animation_manager(self.base_score_list)
|
||||
self.score_counter.update(get_current_ms(), self.score)
|
||||
self.autoplay_manager(game_screen)
|
||||
@@ -625,6 +634,8 @@ class Player:
|
||||
self.drumroll_counter.draw(game_screen)
|
||||
for anim in self.draw_arc_list:
|
||||
anim.draw(game_screen)
|
||||
for anim in self.gauge_hit_effect:
|
||||
anim.draw(game_screen)
|
||||
if self.balloon_anim is not None:
|
||||
self.balloon_anim.draw(game_screen)
|
||||
self.score_counter.draw(game_screen)
|
||||
@@ -725,9 +736,58 @@ class DrumHitEffect:
|
||||
elif self.side == 'R':
|
||||
ray.draw_texture(game_screen.textures['lane_obi'][17], x, y, self.color)
|
||||
|
||||
class GaugeHitEffect:
|
||||
def __init__(self, note_texture: ray.Texture, big: bool):
|
||||
self.note_texture = note_texture
|
||||
self.is_big = big
|
||||
self.texture_change = Animation.create_texture_change(116.67, textures=[(0, 33.33, 1), (33.33, 66.66, 2), (66.66, float('inf'), 3)])
|
||||
self.circle_fadein = Animation.create_fade(133, initial_opacity=0.0, final_opacity=1.0, delay=16.67)
|
||||
self.resize = Animation.create_texture_resize(233, delay=self.texture_change.duration, initial_size=0.75, final_size=1.15)
|
||||
self.fade_out = Animation.create_fade(66, delay=233)
|
||||
self.test = Animation.create_fade(300, delay=116.67, initial_opacity=0.0, final_opacity=1.0)
|
||||
self.color = ray.fade(ray.YELLOW, self.circle_fadein.attribute)
|
||||
self.is_finished = False
|
||||
def update(self, current_ms):
|
||||
self.texture_change.update(current_ms)
|
||||
self.circle_fadein.update(current_ms)
|
||||
self.fade_out.update(current_ms)
|
||||
self.resize.update(current_ms)
|
||||
self.test.update(current_ms)
|
||||
color = ray.YELLOW
|
||||
if self.circle_fadein.is_finished:
|
||||
color = ray.WHITE
|
||||
self.color = ray.fade(color, min(self.fade_out.attribute, self.circle_fadein.attribute))
|
||||
if self.fade_out.is_finished:
|
||||
self.is_finished = True
|
||||
def draw(self, game_screen):
|
||||
texture = game_screen.textures['onp_kiseki_don_1p'][self.texture_change.attribute]
|
||||
color_map = {0.70: ray.WHITE, 0.80: ray.YELLOW, 0.90: ray.ORANGE, 1.00: ray.RED}
|
||||
texture_color = ray.WHITE
|
||||
for upper_bound, color in color_map.items():
|
||||
lower_bound = list(color_map.keys())[list(color_map.keys()).index(upper_bound) - 1] if list(color_map.keys()).index(upper_bound) > 0 else 0.70
|
||||
|
||||
if lower_bound <= self.resize.attribute <= upper_bound:
|
||||
texture_color = color
|
||||
elif self.resize.attribute >= upper_bound:
|
||||
texture_color = ray.RED
|
||||
original_x = 1223
|
||||
original_y = 164
|
||||
source = ray.Rectangle(0, 0, texture.width, texture.height)
|
||||
dest_width = texture.width * self.resize.attribute
|
||||
dest_height = texture.height * self.resize.attribute
|
||||
dest = ray.Rectangle(original_x, original_y, dest_width, dest_height)
|
||||
origin = ray.Vector2(dest_width / 2, dest_height / 2)
|
||||
ray.draw_texture_pro(texture, source, dest, origin, self.test.attribute*100, ray.fade(texture_color, self.fade_out.attribute))
|
||||
ray.draw_texture(self.note_texture, 1187 - 29, 130 - 29, ray.fade(ray.WHITE, self.fade_out.attribute))
|
||||
if self.is_big:
|
||||
ray.draw_texture(game_screen.textures['onp_kiseki_don_1p'][20], 1187 - 29, 130 - 29, self.color)
|
||||
else:
|
||||
ray.draw_texture(game_screen.textures['onp_kiseki_don_1p'][4], 1187 - 29, 130 - 29, self.color)
|
||||
|
||||
class NoteArc:
|
||||
def __init__(self, note_texture: ray.Texture, current_ms: float, player_number: int):
|
||||
def __init__(self, note_texture: ray.Texture, current_ms: float, player_number: int, big: bool):
|
||||
self.texture = note_texture
|
||||
self.is_big = big
|
||||
self.arc_points = 22
|
||||
self.create_ms = current_ms
|
||||
self.player_number = player_number
|
||||
@@ -886,7 +946,7 @@ class BalloonAnimation:
|
||||
if self.is_popped:
|
||||
ray.draw_texture(game_screen.textures['action_fusen_1p'][18], 460, 130, self.color)
|
||||
elif self.balloon_count >= 1:
|
||||
balloon_index = (self.balloon_count - 1) * 7 // self.balloon_total
|
||||
balloon_index = min(7, (self.balloon_count - 1) * 7 // self.balloon_total)
|
||||
ray.draw_texture(game_screen.textures['action_fusen_1p'][balloon_index+11], 460, 130, self.color)
|
||||
if self.balloon_count > 0:
|
||||
ray.draw_texture(game_screen.textures['action_fusen_1p'][0], 414, 40, ray.WHITE)
|
||||
@@ -1050,9 +1110,6 @@ class SongInfo:
|
||||
DISPLAY_DURATION = 1666
|
||||
|
||||
def __init__(self, song_name: str, genre: str):
|
||||
self.fade_in = Animation.create_fade(self.FADE_DURATION, initial_opacity=0.0, final_opacity=1.0)
|
||||
self.fade_out = Animation.create_fade(self.FADE_DURATION, delay=self.FADE_DURATION + self.DISPLAY_DURATION)
|
||||
|
||||
self.song_name = song_name
|
||||
self.genre = genre
|
||||
|
||||
@@ -1060,6 +1117,9 @@ class SongInfo:
|
||||
self.song_title = OutlinedText(
|
||||
self.font, song_name, 40, ray.WHITE, ray.BLACK, outline_thickness=5
|
||||
)
|
||||
self.fade_in = Animation.create_fade(self.FADE_DURATION, initial_opacity=0.0, final_opacity=1.0)
|
||||
self.fade_out = Animation.create_fade(self.FADE_DURATION, delay=self.DISPLAY_DURATION)
|
||||
self.fade_fake = Animation.create_fade(0, delay=self.DISPLAY_DURATION*2 + self.FADE_DURATION)
|
||||
|
||||
def _load_font_for_text(self, text: str) -> ray.Font:
|
||||
codepoint_count = ray.ffi.new('int *', 0)
|
||||
@@ -1070,6 +1130,7 @@ class SongInfo:
|
||||
def update(self, current_ms: float):
|
||||
self.fade_in.update(current_ms)
|
||||
self.fade_out.update(current_ms)
|
||||
self.fade_fake.update(current_ms)
|
||||
|
||||
if not self.fade_in.is_finished:
|
||||
self.song_num_fade = ray.fade(ray.WHITE, self.fade_in.attribute)
|
||||
@@ -1078,12 +1139,13 @@ class SongInfo:
|
||||
self.song_num_fade = ray.fade(ray.WHITE, self.fade_out.attribute)
|
||||
self.song_name_fade = ray.fade(ray.WHITE, 1 - self.fade_out.attribute)
|
||||
|
||||
if self.fade_out.is_finished:
|
||||
self._reset_animations()
|
||||
if self.fade_fake.is_finished:
|
||||
self._reset_animations(current_ms)
|
||||
|
||||
def _reset_animations(self):
|
||||
def _reset_animations(self, current_ms: float):
|
||||
self.fade_in = Animation.create_fade(self.FADE_DURATION, initial_opacity=0.0, final_opacity=1.0)
|
||||
self.fade_out = Animation.create_fade(self.FADE_DURATION, delay=self.FADE_DURATION + self.DISPLAY_DURATION)
|
||||
self.fade_out = Animation.create_fade(self.FADE_DURATION, delay=self.DISPLAY_DURATION)
|
||||
self.fade_fake = Animation.create_fade(0, delay=self.DISPLAY_DURATION*2 + self.FADE_DURATION)
|
||||
|
||||
def draw(self, game_screen: GameScreen):
|
||||
song_texture_index = (global_data.songs_played % 4) + 8
|
||||
|
||||
Reference in New Issue
Block a user