Random track group are now supported in ct_config.json, cup icons can now be named by the cup name, custom track cup now start at 001
|
@ -1805,6 +1805,37 @@
|
|||
}
|
||||
],
|
||||
"tracks_list":[
|
||||
{
|
||||
"name":"4IT Clown's Road",
|
||||
"author":[
|
||||
"4IT★Lecce",
|
||||
"4IT★Clown"
|
||||
],
|
||||
"special":"T82",
|
||||
"music":"T82",
|
||||
"score":2,
|
||||
"since_version":"0.8",
|
||||
"sha1":"f9dbbaca67b7a26c7986a6e99c2a56cf9fdfa14f",
|
||||
"version":"v1.2",
|
||||
"family":2700,
|
||||
"tags":[
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"8'n Infinite Race",
|
||||
"author":"Bulzeeb",
|
||||
"special":"T11",
|
||||
"music":"T21",
|
||||
"score":2,
|
||||
"since_version":"0.7",
|
||||
"sha1":"3566c3453af382ac13b9db813d8b44286d1e281f",
|
||||
"version":"v1.1",
|
||||
"family":3020,
|
||||
"tags":[
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"4IT Clown's Road",
|
||||
"author":[
|
||||
|
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
@ -155,6 +155,7 @@
|
|||
"bmg_patch_dir": "/generated/",
|
||||
"cup_icon_dir": "/cup_icon/",
|
||||
"lecode_bin_dir": "/essentials/",
|
||||
"lpar_dir": "/essentials/"
|
||||
"lpar_dir": "/essentials/",
|
||||
"track_dir": "/Track-WU8/"
|
||||
}
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
from PIL import Image, ImageDraw, ImageFont
|
||||
from PIL import Image
|
||||
import math
|
||||
import json
|
||||
import os
|
||||
|
||||
from source.Cup import Cup
|
||||
from source.Track import Track, HiddenTrackAttr
|
||||
from source.Track import Track, get_trackdata_from_json
|
||||
|
||||
|
||||
class CT_Config:
|
||||
def __init__(self, version: str = None, name: str = None, nickname: str = None,
|
||||
game_variant: str = "01", gui=None, region: int = None, cheat_region: int = None,
|
||||
tags_color: dict = {}, prefix_list: list = [], suffix_list: list = [],
|
||||
tags_color: dict = None, prefix_list: list = None, suffix_list: list = None,
|
||||
tag_retro: str = "Retro", default_track: Track = None, pack_path: str = "",
|
||||
file_process: dict = None, file_structure: dict = None):
|
||||
|
||||
|
@ -23,12 +23,11 @@ class CT_Config:
|
|||
|
||||
self.ordered_cups = []
|
||||
self.unordered_tracks = []
|
||||
self.all_tracks = []
|
||||
self.all_version = {version}
|
||||
self.gui = gui
|
||||
self.tags_color = tags_color
|
||||
self.prefix_list = prefix_list
|
||||
self.suffix_list = suffix_list
|
||||
|
||||
self.tags_color = tags_color if tags_color else {}
|
||||
self.prefix_list = prefix_list if tags_color else []
|
||||
self.suffix_list = suffix_list if tags_color else []
|
||||
self.tag_retro = tag_retro
|
||||
self.default_track = default_track
|
||||
|
||||
|
@ -43,9 +42,6 @@ class CT_Config:
|
|||
:param cup: a Cup object to add as an ordered cup
|
||||
"""
|
||||
self.ordered_cups.append(cup)
|
||||
for track in cup.tracks:
|
||||
self.all_version.add(track.since_version)
|
||||
self.all_tracks.append(track)
|
||||
|
||||
def add_unordered_track(self, track: Track) -> None:
|
||||
"""
|
||||
|
@ -53,13 +49,19 @@ class CT_Config:
|
|||
:param track: a Track object to add as an unordered tracks
|
||||
"""
|
||||
self.unordered_tracks.append(track)
|
||||
self.all_version.add(track.since_version)
|
||||
self.all_tracks.append(track)
|
||||
|
||||
def create_ctfile(self, directory: str = "./file/", highlight_version: str = None, sort_track_by: str = None) -> None:
|
||||
def unordered_tracks_to_cup(self):
|
||||
track_in_cup: int = 4
|
||||
|
||||
for cup_id, track_id in enumerate(range(0, len(self.unordered_tracks), track_in_cup), start=1):
|
||||
cup = Cup(id=cup_id, name=f"CT{cup_id}")
|
||||
for index, track in enumerate(self.unordered_tracks[track_id:track_id + track_in_cup]):
|
||||
cup.tracks[index] = track
|
||||
yield cup
|
||||
|
||||
def create_ctfile(self, directory: str = "./file/", highlight_version: str = None) -> None:
|
||||
"""
|
||||
create a ctfile configuration in a directory
|
||||
:param sort_track_by: by which property will track be sorted
|
||||
:param highlight_version: highlight a specific version in light blue
|
||||
:param directory: create CTFILE.txt and RCTFILE.txt in this directory
|
||||
"""
|
||||
|
@ -74,54 +76,31 @@ class CT_Config:
|
|||
)
|
||||
ctfile.write(header); rctfile.write(header)
|
||||
|
||||
# generate cup for undefined track
|
||||
unordered_cups = []
|
||||
|
||||
star_value = []
|
||||
if not self.gui.boolvar_use_1star_track.get(): star_value.append(1)
|
||||
if not self.gui.boolvar_use_2star_track.get(): star_value.append(2)
|
||||
if not self.gui.boolvar_use_3star_track.get(): star_value.append(3)
|
||||
|
||||
track_list = self.search_tracks(not_value=True, values_list=True,
|
||||
only_unordered_track=True, score=star_value)
|
||||
if sort_track_by:
|
||||
track_list.sort(key=lambda track: getattr(track, sort_track_by, None))
|
||||
|
||||
for i, track in enumerate(track_list):
|
||||
if i % 4 == 0:
|
||||
_actual_cup = Cup(name=f"TL{i // 4}", default_track=self.default_track)
|
||||
unordered_cups.append(_actual_cup)
|
||||
_actual_cup.tracks[i % 4] = track
|
||||
|
||||
# all cups
|
||||
for cup in self.ordered_cups + unordered_cups:
|
||||
for cup in self.get_all_cups():
|
||||
kwargs = {"highlight_version": highlight_version, "ct_config": self}
|
||||
ctfile.write(cup.get_ctfile_cup(race=False, **kwargs))
|
||||
rctfile.write(cup.get_ctfile_cup(race=True, **kwargs))
|
||||
ctfile.write(cup.get_ctfile(race=False, **kwargs))
|
||||
rctfile.write(cup.get_ctfile(race=True, **kwargs))
|
||||
|
||||
def get_cup_icon(self, cup_id: [str, int], font_path: str = "./assets/SuperMario256.ttf") -> Image:
|
||||
"""
|
||||
:param cup_id: id of the cup
|
||||
:param font_path: path to the font used to generate icon
|
||||
:return: cup icon
|
||||
"""
|
||||
def get_tracks(self):
|
||||
for data in self.unordered_tracks + self.ordered_cups:
|
||||
for track in data.get_tracks(): yield track
|
||||
|
||||
dir = self.file_process['placement'].get('cup_icon_dir')
|
||||
if not dir: dir = "/ct_icons/"
|
||||
cup_icon_dir = f"{self.pack_path}/file/{dir}/"
|
||||
def search_tracks(self, **condition):
|
||||
for track in self.get_tracks():
|
||||
for property, (possibility, filter_func) in condition.items():
|
||||
if not filter_func: filter_func = lambda a, b: a == b
|
||||
if not filter_func(getattr(track, property, None), possibility):
|
||||
break
|
||||
else:
|
||||
yield track
|
||||
|
||||
if os.path.exists(f"{cup_icon_dir}/{cup_id}.png"):
|
||||
cup_icon = Image.open(f"{cup_icon_dir}/{cup_id}.png").resize((128, 128))
|
||||
def get_cup_count(self) -> int:
|
||||
return math.ceil(len(self.unordered_tracks) / 4) + len(self.ordered_cups)
|
||||
|
||||
else:
|
||||
cup_icon = Image.new("RGBA", (128, 128))
|
||||
draw = ImageDraw.Draw(cup_icon)
|
||||
font = ImageFont.truetype(font_path, 90)
|
||||
draw.text((4, 4), "CT", (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
|
||||
font = ImageFont.truetype(font_path, 60)
|
||||
draw.text((5, 80), "%03i" % cup_id, (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
|
||||
|
||||
return cup_icon
|
||||
def get_all_cups(self):
|
||||
for cup in self.ordered_cups: yield cup
|
||||
for cup in self.unordered_tracks_to_cup(): yield cup
|
||||
|
||||
def get_cticon(self) -> Image:
|
||||
"""
|
||||
|
@ -129,18 +108,27 @@ class CT_Config:
|
|||
:return: ct_icon image
|
||||
"""
|
||||
CT_ICON_WIDTH = 128
|
||||
icon_files = ["left", "right"]
|
||||
|
||||
total_cup_count = math.ceil(len(self.all_tracks) // 4) + 10 # +10 because left, right, start at 0, 8 normal cup
|
||||
default_cups = [
|
||||
"left", "right",
|
||||
|
||||
"mushroom", "shell", "flower", "banana",
|
||||
"star", "leaf", "special", "lightning",
|
||||
|
||||
"random"
|
||||
]
|
||||
|
||||
total_cup_count = self.get_cup_count() + len(default_cups) # +10 because left, right, start at 0, 8 normal cup
|
||||
ct_icon = Image.new("RGBA", (CT_ICON_WIDTH, CT_ICON_WIDTH * (total_cup_count + 2)))
|
||||
# +2 because of left and right arrow
|
||||
|
||||
icon_files.extend(range(total_cup_count))
|
||||
def icon_cups_generator():
|
||||
for id, name in enumerate(default_cups): yield Cup(name=name, id=-id) # default cup have a negative id
|
||||
for cup in self.get_all_cups(): yield cup
|
||||
|
||||
for index, cup_id in enumerate(icon_files):
|
||||
for index, cup in enumerate(icon_cups_generator()):
|
||||
# index is a number, id can be string or number ("left", 0, 12, ...)
|
||||
cup_icon = self.get_cup_icon(cup_id)
|
||||
ct_icon.paste(cup_icon, (0, index * CT_ICON_WIDTH))
|
||||
ct_icon.paste(cup.get_icon(), (0, index * CT_ICON_WIDTH))
|
||||
|
||||
return ct_icon
|
||||
|
||||
|
@ -167,21 +155,30 @@ class CT_Config:
|
|||
|
||||
self.pack_path = pack_path
|
||||
|
||||
# default track
|
||||
self.default_track = Track(track_wu8_dir=f"{self.pack_path}/file/Track-WU8/")
|
||||
if "default_track" in ctconfig_json: self.default_track.load_from_json(ctconfig_json["default_track"])
|
||||
with open(f"{pack_path}/file_process.json", encoding="utf8") as fp_file:
|
||||
self.file_process = json.load(fp_file)
|
||||
with open(f"{pack_path}/file_structure.json", encoding="utf8") as fs_file:
|
||||
self.file_structure = json.load(fs_file)
|
||||
|
||||
for cup_json in ctconfig_json["cup"] if "cup" in ctconfig_json else []: # tracks with defined order
|
||||
cup = Cup(default_track=self.default_track)
|
||||
dir = self.file_process['placement'].get('cup_icon_dir') if 'placement' in self.file_process else None
|
||||
if not dir: dir = "/ct_icons/"
|
||||
Cup.icon_dir = f"{self.pack_path}/file/{dir}/"
|
||||
|
||||
# default track
|
||||
self.default_track = Track()
|
||||
if "default_track" in ctconfig_json: self.default_track.load_from_json(ctconfig_json["default_track"])
|
||||
Cup.default_track = self.default_track
|
||||
|
||||
for id, cup_json in enumerate(ctconfig_json["cup"] if "cup" in ctconfig_json else []):
|
||||
# tracks with defined order
|
||||
cup = Cup(id=id)
|
||||
cup.load_from_json(cup_json)
|
||||
self.ordered_cups.append(cup)
|
||||
self.all_tracks.extend(cup.tracks)
|
||||
|
||||
for track_json in ctconfig_json["tracks_list"] if "tracks_list" in ctconfig_json else []: # unordered tracks
|
||||
track = Track(track_wu8_dir=f"{self.pack_path}/file/Track-WU8/")
|
||||
track.load_from_json(track_json)
|
||||
for track_json in ctconfig_json["tracks_list"] if "tracks_list" in ctconfig_json else []:
|
||||
# unordered tracks
|
||||
track = get_trackdata_from_json(track_json)
|
||||
self.unordered_tracks.append(track)
|
||||
self.all_tracks.append(track)
|
||||
|
||||
self.version = ctconfig_json.get("version")
|
||||
|
||||
|
@ -192,39 +189,23 @@ class CT_Config:
|
|||
for param in ["region", "cheat_region", "tags_color", "prefix_list", "suffix_list", "tag_retro"]:
|
||||
setattr(self, param, ctconfig_json.get(param))
|
||||
|
||||
with open(f"{pack_path}/file_process.json", encoding="utf8") as fp_file:
|
||||
self.file_process = json.load(fp_file)
|
||||
with open(f"{pack_path}/file_structure.json", encoding="utf8") as fs_file:
|
||||
self.file_structure = json.load(fs_file)
|
||||
wu8_dirname = self.file_process["track_dir"] if "track_dir" in self.file_process else "/Track-WU8/"
|
||||
Track._wu8_dir = f"{self.pack_path}/file/{wu8_dirname}/"
|
||||
Track._szs_dir = "./file/Track/"
|
||||
Track.tag_retro = self.tag_retro
|
||||
Track.prefix_list = self.prefix_list
|
||||
Track.suffix_list = self.suffix_list
|
||||
Track.tags_color = self.tags_color
|
||||
|
||||
return self
|
||||
|
||||
def search_tracks(self, values_list=False, not_value=False, only_unordered_track=False, **kwargs) -> list:
|
||||
"""
|
||||
:param only_unordered_track: only search in unordered track
|
||||
:param values_list: search track with a value list instead of a single value
|
||||
:param not_value: search track that does not have value
|
||||
:param kwargs: any track property = any value
|
||||
:return: track list respecting condition
|
||||
"""
|
||||
track = self.all_tracks.copy() if not only_unordered_track else self.unordered_tracks.copy()
|
||||
|
||||
if values_list:
|
||||
if not_value: filter_func = lambda track: getattr(track, keyword) not in value
|
||||
else: filter_func = lambda track: getattr(track, keyword) in value
|
||||
else:
|
||||
if not_value: filter_func = lambda track: getattr(track, keyword) != value
|
||||
else: filter_func = lambda track: getattr(track, keyword) == value
|
||||
|
||||
for keyword, value in kwargs.items():
|
||||
track = list(filter(filter_func, track))
|
||||
return track
|
||||
def get_tracks_count(self) -> int:
|
||||
return sum(1 for _ in self.get_tracks())
|
||||
|
||||
def get_all_track_possibilities(self) -> dict:
|
||||
possibilities = {}
|
||||
for track in self.all_tracks:
|
||||
for track in self.get_tracks():
|
||||
for key, value in track.__dict__.items():
|
||||
if key in HiddenTrackAttr: continue
|
||||
if not key in possibilities: possibilities[key] = []
|
||||
|
||||
if type(value) == list:
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
from source.Track import Track
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import os
|
||||
|
||||
from source.Track import Track, TrackGroup, get_trackdata_from_json
|
||||
|
||||
|
||||
class Cup:
|
||||
icon_dir = None
|
||||
default_track = None
|
||||
|
||||
def __init__(self,
|
||||
default_track: Track,
|
||||
name: str = None,
|
||||
track1: Track = None,
|
||||
track2: Track = None,
|
||||
track3: Track = None,
|
||||
track4: Track = None,
|
||||
id: int = 0,
|
||||
track1: [Track, TrackGroup] = None,
|
||||
track2: [Track, TrackGroup] = None,
|
||||
track3: [Track, TrackGroup] = None,
|
||||
track4: [Track, TrackGroup] = None,
|
||||
*args, **kwargs):
|
||||
"""
|
||||
class of a cup
|
||||
|
@ -22,14 +28,15 @@ class Cup:
|
|||
"""
|
||||
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.tracks = [
|
||||
track1 if track1 else default_track.copy(),
|
||||
track2 if track2 else default_track.copy(),
|
||||
track3 if track3 else default_track.copy(),
|
||||
track4 if track4 else default_track.copy()
|
||||
track1 if track1 else self.default_track.copy() if self.default_track else None,
|
||||
track2 if track2 else self.default_track.copy() if self.default_track else None,
|
||||
track3 if track3 else self.default_track.copy() if self.default_track else None,
|
||||
track4 if track4 else self.default_track.copy() if self.default_track else None,
|
||||
]
|
||||
|
||||
def get_ctfile_cup(self, *args, **kwargs) -> str:
|
||||
def get_ctfile(self, *args, **kwargs) -> str:
|
||||
"""
|
||||
get the ctfile definition for the cup
|
||||
:param race: is it a text used for Race_*.szs ?
|
||||
|
@ -40,7 +47,7 @@ class Cup:
|
|||
ctfile_cup += track.get_ctfile(*args, **kwargs)
|
||||
return ctfile_cup
|
||||
|
||||
def load_from_json(self, cup: dict):
|
||||
def load_from_json(self, cup: dict, *args, **kwargs):
|
||||
"""
|
||||
load the cup from a dictionnary
|
||||
:param cup: dictionnary cup
|
||||
|
@ -48,8 +55,35 @@ class Cup:
|
|||
for key, value in cup.items(): # load all value in the json as class attribute
|
||||
if key == "tracks": # if the key is tracks
|
||||
for i, track_json in enumerate(value): # load all tracks from their json
|
||||
self.tracks[i].load_from_json(track_json)
|
||||
self.tracks[i] = get_trackdata_from_json(track_json)
|
||||
|
||||
else:
|
||||
setattr(self, key, value)
|
||||
|
||||
return self
|
||||
|
||||
def get_tracks(self):
|
||||
for trackdata in self.tracks:
|
||||
for track in trackdata.get_tracks():
|
||||
yield track
|
||||
|
||||
def get_icon(self, font_path: str = "./assets/SuperMario256.ttf") -> Image:
|
||||
"""
|
||||
:param font_path: path to the font used to generate icon
|
||||
:return: cup icon
|
||||
"""
|
||||
cup_icon = None
|
||||
|
||||
for name in [self.id, self.name]:
|
||||
if os.path.exists(f"{self.icon_dir}/{name}.png"):
|
||||
cup_icon = Image.open(f"{self.icon_dir}/{name}.png").resize((128, 128))
|
||||
|
||||
if not cup_icon:
|
||||
cup_icon = Image.new("RGBA", (128, 128))
|
||||
draw = ImageDraw.Draw(cup_icon)
|
||||
font = ImageFont.truetype(font_path, 90)
|
||||
draw.text((4, 4), "CT", (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
|
||||
font = ImageFont.truetype(font_path, 60)
|
||||
draw.text((5, 80), "%03i" % self.id, (255, 165, 0), font=font, stroke_width=2, stroke_fill=(0, 0, 0))
|
||||
|
||||
return cup_icon
|
||||
|
|
|
@ -29,11 +29,6 @@ class TooMuchSha1CheckFailed(Exception):
|
|||
super().__init__("Too much sha1 check failed !")
|
||||
|
||||
|
||||
class CantConvertTrack(Exception):
|
||||
def __init__(self):
|
||||
super().__init__("Can't convert track.")
|
||||
|
||||
|
||||
class NoGui:
|
||||
"""
|
||||
'fake' gui if no gui are used for compatibility.
|
||||
|
@ -280,7 +275,7 @@ class Game:
|
|||
if not lpar_path: f""
|
||||
lpar_path = (
|
||||
f"{self.ctconfig.pack_path}/file/{lpar_path}/"
|
||||
f"lpar-{'debug' if self.gui.boolvar_use_debug_mode.get() else 'normal'}"
|
||||
f"lpar-{'debug' if self.gui.boolvar_use_debug_mode.get() else 'normal'}.txt"
|
||||
)
|
||||
|
||||
lecode_file = self.ctconfig.file_process["placement"].get("lecode_bin_dir")
|
||||
|
@ -493,7 +488,6 @@ class Game:
|
|||
self.gui.progress(statut=self.gui.translate("Configurating LE-CODE"), add=1)
|
||||
self.ctconfig.create_ctfile(
|
||||
highlight_version=self.gui.stringvar_mark_track_from_version.get(),
|
||||
sort_track_by=self.gui.stringvar_sort_track_by.get()
|
||||
)
|
||||
|
||||
self.generate_cticons()
|
||||
|
@ -614,16 +608,9 @@ class Game:
|
|||
"""
|
||||
nonlocal error_count, error_max, thread_list
|
||||
|
||||
if os.path.exists(track.file_szs) and os.path.getsize(track.file_szs) < 1000:
|
||||
os.remove(track.file_szs) # File under this size are corrupted
|
||||
|
||||
if not track.check_szs_sha1(): # if sha1 of track's szs is incorrect or track's szs does not exist
|
||||
if os.path.exists(track.file_wu8):
|
||||
track.convert_wu8_to_szs()
|
||||
else:
|
||||
messagebox.showerror(self.gui.translate("Error"),
|
||||
self.gui.translate("Can't convert track.\nEnable track download and retry."))
|
||||
raise CantConvertTrack()
|
||||
try: track.convert_wu8_to_szs()
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def clean_process() -> int:
|
||||
"""
|
||||
|
@ -639,10 +626,10 @@ class Game:
|
|||
|
||||
return bool(thread_list)
|
||||
|
||||
total_track = len(self.ctconfig.all_tracks)
|
||||
total_track = self.ctconfig.get_tracks_count()
|
||||
self.gui.progress(max=total_track, indeter=False, show=True)
|
||||
|
||||
for i, track in enumerate(self.ctconfig.all_tracks):
|
||||
for i, track in enumerate(self.ctconfig.get_tracks()):
|
||||
while error_count <= error_max:
|
||||
if len(thread_list) < max_process:
|
||||
thread_list[track.sha1] = Thread(target=add_process, args=[track])
|
||||
|
|
163
source/Track.py
|
@ -2,19 +2,25 @@ from source.definition import *
|
|||
from source.wszst import *
|
||||
|
||||
|
||||
HiddenTrackAttr = [
|
||||
"file_wu8",
|
||||
"file_szs",
|
||||
"_track_wu8_dir",
|
||||
"_track_szs_dir"
|
||||
] # These attribute shouldn't be used to reference all the possibilities of values
|
||||
|
||||
|
||||
class CantDownloadTrack(Exception):
|
||||
def __init__(self, track, http_error: [str, int]):
|
||||
super().__init__(f"Can't download track {track.name} ({track.sha1}) (error {http_error}) !")
|
||||
|
||||
|
||||
class CantConvertTrack(Exception):
|
||||
def __init__(self):
|
||||
super().__init__("Can't convert track.")
|
||||
|
||||
|
||||
class MissingTrackWU8(Exception):
|
||||
def __init__(self):
|
||||
super().__init__("The original wu8 track file is missing !")
|
||||
|
||||
|
||||
def get_trackdata_from_json(track_json, *args, **kwargs):
|
||||
return (TrackGroup if "group" in track_json else Track)(*args, **kwargs).load_from_json(track_json)
|
||||
|
||||
|
||||
def check_file_sha1(file: str, excepted_sha1: str) -> int:
|
||||
"""
|
||||
check if track szs sha1 is correct
|
||||
|
@ -22,17 +28,24 @@ def check_file_sha1(file: str, excepted_sha1: str) -> int:
|
|||
"""
|
||||
if not os.path.exists(file): return 0
|
||||
calculated_sha1 = szs.sha1(file=file)
|
||||
if calculated_sha1 == excepted_sha1: return 1
|
||||
if calculated_sha1 == excepted_sha1:
|
||||
return 1
|
||||
else:
|
||||
print(f"incorrect sha1 for {file} {calculated_sha1} : (expected {excepted_sha1})")
|
||||
return 0
|
||||
|
||||
|
||||
class Track:
|
||||
_wu8_dir = None
|
||||
_szs_dir = None
|
||||
tag_retro = None
|
||||
prefix_list = None
|
||||
suffix_list = None
|
||||
tags_color = None
|
||||
|
||||
def __init__(self, name: str = " ", author: str = "Nintendo", special: str = "T11", music: str = "T11",
|
||||
sha1: str = None, since_version: str = None, score: int = -1, warning: int = 0, note: str = "",
|
||||
track_wu8_dir: str = "./file/Track-WU8/", track_szs_dir: str = "./file/Track/",
|
||||
version: str = None, tags: list = [], *args, **kwargs):
|
||||
sha1: str = None, since_version: str = None, score: int = -1, warning: int = 0,
|
||||
version: str = None, tags: list = None, is_in_group: bool = False, *args, **kwargs):
|
||||
"""
|
||||
Track class
|
||||
:param name: track name
|
||||
|
@ -57,21 +70,19 @@ class Track:
|
|||
:param kwargs: /
|
||||
"""
|
||||
|
||||
self.name = name # Track name
|
||||
self.author = author # Track author
|
||||
self.sha1 = sha1 # Track sha1 from wszst SHA1
|
||||
self.special = special # Special slot of the track
|
||||
self.music = music # Music of the track
|
||||
self.name = name # Track name
|
||||
self.author = author # Track author
|
||||
self.sha1 = sha1 # Track sha1 from wszst SHA1
|
||||
self.special = special # Special slot of the track
|
||||
self.music = music # Music of the track
|
||||
self.since_version = since_version # Since which version is this track available
|
||||
self.score = score # Track score between 1 and 3 stars
|
||||
self.warning = warning # Track bug level (1 = minor, 2 = major)
|
||||
self.score = score # Track score between 1 and 3 stars
|
||||
self.warning = warning # Track bug level (1 = minor, 2 = major)
|
||||
self.version = version
|
||||
self.tags = tags
|
||||
self.tags = tags if tags else []
|
||||
self.refresh_filename()
|
||||
|
||||
self._track_wu8_dir = track_wu8_dir
|
||||
self._track_szs_dir = track_szs_dir
|
||||
self.file_wu8 = f"{track_wu8_dir}/{self.sha1}.wu8"
|
||||
self.file_szs = f"{track_szs_dir}/{self.sha1}.szs"
|
||||
self._is_in_group = is_in_group
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
|
@ -85,23 +96,33 @@ class Track:
|
|||
check if track wu8 sha1 is correct
|
||||
:return: 0 if yes, -1 if no
|
||||
"""
|
||||
return check_file_sha1(self.file_wu8, self.sha1)
|
||||
return check_file_sha1(self._wu8_file, self.sha1)
|
||||
|
||||
def check_szs_sha1(self) -> int:
|
||||
"""
|
||||
check if track szs sha1 is correct
|
||||
:return: 0 if yes, -1 if no
|
||||
"""
|
||||
return check_file_sha1(self.file_szs, self.sha1)
|
||||
return check_file_sha1(self._szs_file, self.sha1)
|
||||
|
||||
def convert_wu8_to_szs(self) -> None:
|
||||
"""
|
||||
convert track to szs
|
||||
"""
|
||||
szs.normalize(
|
||||
src_file=self.file_wu8,
|
||||
dest_dir="./file/Track/"
|
||||
)
|
||||
file_wu8 = f"{self._wu8_dir}/{self.sha1}.wu8"
|
||||
file_szs = f"{self._szs_dir}/{self.sha1}.szs"
|
||||
|
||||
if os.path.exists(file_szs) and os.path.getsize(file_szs) < 1000:
|
||||
os.remove(file_szs) # File under this size are corrupted
|
||||
|
||||
if not self.check_szs_sha1(): # if sha1 of track's szs is incorrect or track's szs does not exist
|
||||
if os.path.exists(file_wu8):
|
||||
szs.normalize(
|
||||
src_file=file_wu8,
|
||||
dest_file=file_szs
|
||||
)
|
||||
else:
|
||||
raise MissingTrackWU8()
|
||||
|
||||
def get_author_str(self) -> str:
|
||||
"""
|
||||
|
@ -109,39 +130,43 @@ class Track:
|
|||
"""
|
||||
return self.author if type(self.author) == str else ", ".join(self.author)
|
||||
|
||||
def get_ctfile(self, ct_config, race=False, *args, **kwargs) -> str:
|
||||
def get_ctfile(self, race=False, *args, **kwargs) -> str:
|
||||
"""
|
||||
get ctfile text to create CTFILE.txt and RCTFILE.txt
|
||||
:param ct_config: ct_config used to generate the Track
|
||||
:param race: is it a text used for Race_*.szs ?
|
||||
:return: ctfile definition for the track
|
||||
"""
|
||||
ctfile_text = (
|
||||
f' T {self.music}; '
|
||||
f'{self.special}; '
|
||||
f'{"0x00" if ct_config.tag_retro in self.tags else "0x01"}; '
|
||||
)
|
||||
if not race:
|
||||
ctfile_text += (
|
||||
f'"{self.sha1}"; ' # track path
|
||||
f'"{self.get_track_formatted_name(ct_config, *args, **kwargs)}"; ' # track text shown ig
|
||||
f'"{self.sha1}"\n') # sha1
|
||||
else:
|
||||
ctfile_text += (
|
||||
track_type = "T"
|
||||
track_flag = 0x00 if self.tag_retro in self.tags else 0x01
|
||||
if self._is_in_group:
|
||||
track_type = "H"
|
||||
track_flag |= 0x04
|
||||
|
||||
ctfile_track = f' {track_type} {self.music}; {self.special}; {hex(track_flag)}; '
|
||||
|
||||
if race:
|
||||
ctfile_track += (
|
||||
f'"-"; ' # track path, not used in Race_*.szs, save a bit of space
|
||||
f'"{self.get_track_formatted_name(ct_config, *args, **kwargs)}\\n{self.get_author_str()}"; '
|
||||
f'"{self.get_track_formatted_name(*args, **kwargs)}\\n{self.get_author_str()}"; '
|
||||
# only in race show author's name
|
||||
f'"-"\n' # sha1, not used in Race_*.szs, save a bit of space
|
||||
)
|
||||
|
||||
return ctfile_text
|
||||
else:
|
||||
ctfile_track += (
|
||||
f'"{self.sha1}"; ' # track path
|
||||
f'"{self.get_track_formatted_name(*args, **kwargs)}"; ' # track text shown ig
|
||||
f'"{self.sha1}"\n' # sha1
|
||||
)
|
||||
|
||||
return ctfile_track
|
||||
|
||||
def select_tag(self, tag_list: list) -> str:
|
||||
for tag in self.tags:
|
||||
if tag in tag_list: return tag
|
||||
return ""
|
||||
|
||||
def get_track_formatted_name(self, ct_config, highlight_version: str = None, *args, **kwargs) -> str:
|
||||
def get_track_formatted_name(self, highlight_version: str = None, *args, **kwargs) -> str:
|
||||
"""
|
||||
get the track name with score, color, ...
|
||||
:param ct_config: ct_config for tags configuration
|
||||
|
@ -150,8 +175,8 @@ class Track:
|
|||
"""
|
||||
hl_prefix = "" # highlight
|
||||
hl_suffix = ""
|
||||
prefix = self.select_tag(ct_config.prefix_list) # tag prefix
|
||||
suffix = self.select_tag(ct_config.suffix_list) # tag suffix
|
||||
prefix = self.select_tag(self.prefix_list) # tag prefix
|
||||
suffix = self.select_tag(self.suffix_list) # tag suffix
|
||||
star_prefix = "" # star
|
||||
star_suffix = ""
|
||||
star_text = ""
|
||||
|
@ -166,8 +191,8 @@ class Track:
|
|||
if self.since_version == highlight_version:
|
||||
hl_prefix, hl_suffix = "\\\\c{blue1}", "\\\\c{off}"
|
||||
|
||||
if prefix: prefix = "\\\\c{"+ct_config.tags_color[prefix]+"}"+prefix+"\\\\c{off} "
|
||||
if suffix: suffix = " (\\\\c{"+ct_config.tags_color[suffix]+"}"+suffix+"\\\\c{off})"
|
||||
if prefix: prefix = "\\\\c{" + self.tags_color[prefix] + "}" + prefix + "\\\\c{off} "
|
||||
if suffix: suffix = " (\\\\c{" + self.tags_color[suffix] + "}" + suffix + "\\\\c{off})"
|
||||
|
||||
name = f"{star_prefix}{star_text}{star_suffix}{prefix}{hl_prefix}{self.name}{hl_suffix}{suffix}"
|
||||
return name
|
||||
|
@ -179,6 +204,10 @@ class Track:
|
|||
"""
|
||||
return f"{self.select_tag(ct_config.prefix_list)}{self.name}{self.select_tag(ct_config.suffix_list)}"
|
||||
|
||||
def refresh_filename(self):
|
||||
self._wu8_file = f"{self._wu8_dir}/{self.sha1}.wu8"
|
||||
self._szs_file = f"{self._szs_dir}/{self.sha1}.szs"
|
||||
|
||||
def load_from_json(self, track_json: dict):
|
||||
"""
|
||||
load the track from a dictionary
|
||||
|
@ -187,8 +216,7 @@ class Track:
|
|||
for key, value in track_json.items(): # load all value in the json as class attribute
|
||||
setattr(self, key, value)
|
||||
|
||||
self.file_wu8 = f"{self._track_wu8_dir}/{self.sha1}.wu8"
|
||||
self.file_szs = f"{self._track_szs_dir}/{self.sha1}.szs"
|
||||
self.refresh_filename()
|
||||
|
||||
return self
|
||||
|
||||
|
@ -200,3 +228,32 @@ class Track:
|
|||
for k, v in self.__dict__.items():
|
||||
setattr(new, k, v)
|
||||
return new
|
||||
|
||||
def get_tracks(self): yield self
|
||||
|
||||
|
||||
class TrackGroup(Track):
|
||||
def __init__(self, tracks: list = None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.tracks = tracks if tracks else []
|
||||
|
||||
def load_from_json(self, group_json: dict, *args, **kwargs):
|
||||
for key, value in group_json.items(): # load all value in the json as class attribute
|
||||
if key == "group":
|
||||
for track_json in value:
|
||||
self.tracks.append(Track(is_in_group=True, *args, **kwargs).load_from_json(track_json))
|
||||
|
||||
else:
|
||||
setattr(self, key, value)
|
||||
|
||||
return self
|
||||
|
||||
def get_tracks(self):
|
||||
for track in self.tracks: yield track
|
||||
|
||||
def get_ctfile(self, *args, **kwargs):
|
||||
ctfile_group = f' T T11; T11; 0x02; "-"; "{self.get_track_formatted_name()}"; "-"\n'
|
||||
for track in self.tracks:
|
||||
ctfile_group += track.get_ctfile(*args, **kwargs)
|
||||
|
||||
return ctfile_group
|
|
@ -48,17 +48,16 @@ def sha1(file, autoadd_path: str = "./file/auto-add/") -> str:
|
|||
|
||||
|
||||
@error.better_wszst_error(wszst_tools=WSZST_PATH)
|
||||
def normalize(src_file: str, dest_dir: str = "./file/Track/", dest_name: str = "%N.szs",
|
||||
def normalize(src_file: str, dest_file: str = "./file/Track/%N.szs",
|
||||
output_format: str = "szs", autoadd_path: str = "./file/auto-add/") -> None:
|
||||
"""
|
||||
convert a track into an another format
|
||||
:param src_file: source file
|
||||
:param dest_dir: destination directory
|
||||
:param dest_name: destination filename (%N mean same name as src_file)
|
||||
:param dest_file: destination filename (%N mean same name as src_file)
|
||||
:param output_format: format of the destination track
|
||||
:param autoadd_path: path of the auto-add directory
|
||||
"""
|
||||
subprocess.run([WSZST_PATH, "NORMALIZE", src_file, "--DEST", dest_dir + dest_name, "--" + output_format,
|
||||
subprocess.run([WSZST_PATH, "NORMALIZE", src_file, "--DEST", dest_file, "--" + output_format,
|
||||
"--overwrite", "--autoadd-path", autoadd_path],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW, stderr=subprocess.PIPE)
|
||||
|
||||
|
|