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

This commit is contained in:
Faraphel 2022-01-21 16:39:29 +01:00
parent 5111689872
commit 03e85fb442
44 changed files with 279 additions and 189 deletions

View file

@ -1805,6 +1805,37 @@
} }
], ],
"tracks_list":[ "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", "name":"4IT Clown's Road",
"author":[ "author":[

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

View file

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -155,6 +155,7 @@
"bmg_patch_dir": "/generated/", "bmg_patch_dir": "/generated/",
"cup_icon_dir": "/cup_icon/", "cup_icon_dir": "/cup_icon/",
"lecode_bin_dir": "/essentials/", "lecode_bin_dir": "/essentials/",
"lpar_dir": "/essentials/" "lpar_dir": "/essentials/",
"track_dir": "/Track-WU8/"
} }
} }

View file

@ -1,16 +1,16 @@
from PIL import Image, ImageDraw, ImageFont from PIL import Image
import math import math
import json import json
import os import os
from source.Cup import Cup from source.Cup import Cup
from source.Track import Track, HiddenTrackAttr from source.Track import Track, get_trackdata_from_json
class CT_Config: class CT_Config:
def __init__(self, version: str = None, name: str = None, nickname: str = None, 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, 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 = "", tag_retro: str = "Retro", default_track: Track = None, pack_path: str = "",
file_process: dict = None, file_structure: dict = None): file_process: dict = None, file_structure: dict = None):
@ -23,12 +23,11 @@ class CT_Config:
self.ordered_cups = [] self.ordered_cups = []
self.unordered_tracks = [] self.unordered_tracks = []
self.all_tracks = []
self.all_version = {version}
self.gui = gui self.gui = gui
self.tags_color = tags_color
self.prefix_list = prefix_list self.tags_color = tags_color if tags_color else {}
self.suffix_list = suffix_list self.prefix_list = prefix_list if tags_color else []
self.suffix_list = suffix_list if tags_color else []
self.tag_retro = tag_retro self.tag_retro = tag_retro
self.default_track = default_track self.default_track = default_track
@ -43,9 +42,6 @@ class CT_Config:
:param cup: a Cup object to add as an ordered cup :param cup: a Cup object to add as an ordered cup
""" """
self.ordered_cups.append(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: 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 :param track: a Track object to add as an unordered tracks
""" """
self.unordered_tracks.append(track) 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 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 highlight_version: highlight a specific version in light blue
:param directory: create CTFILE.txt and RCTFILE.txt in this directory :param directory: create CTFILE.txt and RCTFILE.txt in this directory
""" """
@ -74,54 +76,31 @@ class CT_Config:
) )
ctfile.write(header); rctfile.write(header) 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 # 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} kwargs = {"highlight_version": highlight_version, "ct_config": self}
ctfile.write(cup.get_ctfile_cup(race=False, **kwargs)) ctfile.write(cup.get_ctfile(race=False, **kwargs))
rctfile.write(cup.get_ctfile_cup(race=True, **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: def get_tracks(self):
""" for data in self.unordered_tracks + self.ordered_cups:
:param cup_id: id of the cup for track in data.get_tracks(): yield track
:param font_path: path to the font used to generate icon
:return: cup icon
"""
dir = self.file_process['placement'].get('cup_icon_dir')
if not dir: dir = "/ct_icons/"
cup_icon_dir = f"{self.pack_path}/file/{dir}/"
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 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: else:
cup_icon = Image.new("RGBA", (128, 128)) yield track
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_cup_count(self) -> int:
return math.ceil(len(self.unordered_tracks) / 4) + len(self.ordered_cups)
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: def get_cticon(self) -> Image:
""" """
@ -129,18 +108,27 @@ class CT_Config:
:return: ct_icon image :return: ct_icon image
""" """
CT_ICON_WIDTH = 128 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))) ct_icon = Image.new("RGBA", (CT_ICON_WIDTH, CT_ICON_WIDTH * (total_cup_count + 2)))
# +2 because of left and right arrow # +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, ...) # 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.get_icon(), (0, index * CT_ICON_WIDTH))
ct_icon.paste(cup_icon, (0, index * CT_ICON_WIDTH))
return ct_icon return ct_icon
@ -167,21 +155,30 @@ class CT_Config:
self.pack_path = pack_path self.pack_path = pack_path
# default track with open(f"{pack_path}/file_process.json", encoding="utf8") as fp_file:
self.default_track = Track(track_wu8_dir=f"{self.pack_path}/file/Track-WU8/") self.file_process = json.load(fp_file)
if "default_track" in ctconfig_json: self.default_track.load_from_json(ctconfig_json["default_track"]) 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 dir = self.file_process['placement'].get('cup_icon_dir') if 'placement' in self.file_process else None
cup = Cup(default_track=self.default_track) 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) cup.load_from_json(cup_json)
self.ordered_cups.append(cup) 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 for track_json in ctconfig_json["tracks_list"] if "tracks_list" in ctconfig_json else []:
track = Track(track_wu8_dir=f"{self.pack_path}/file/Track-WU8/") # unordered tracks
track.load_from_json(track_json) track = get_trackdata_from_json(track_json)
self.unordered_tracks.append(track) self.unordered_tracks.append(track)
self.all_tracks.append(track)
self.version = ctconfig_json.get("version") 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"]: for param in ["region", "cheat_region", "tags_color", "prefix_list", "suffix_list", "tag_retro"]:
setattr(self, param, ctconfig_json.get(param)) setattr(self, param, ctconfig_json.get(param))
with open(f"{pack_path}/file_process.json", encoding="utf8") as fp_file: wu8_dirname = self.file_process["track_dir"] if "track_dir" in self.file_process else "/Track-WU8/"
self.file_process = json.load(fp_file) Track._wu8_dir = f"{self.pack_path}/file/{wu8_dirname}/"
with open(f"{pack_path}/file_structure.json", encoding="utf8") as fs_file: Track._szs_dir = "./file/Track/"
self.file_structure = json.load(fs_file) 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 return self
def search_tracks(self, values_list=False, not_value=False, only_unordered_track=False, **kwargs) -> list: def get_tracks_count(self) -> int:
""" return sum(1 for _ in self.get_tracks())
: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_all_track_possibilities(self) -> dict: def get_all_track_possibilities(self) -> dict:
possibilities = {} possibilities = {}
for track in self.all_tracks: for track in self.get_tracks():
for key, value in track.__dict__.items(): for key, value in track.__dict__.items():
if key in HiddenTrackAttr: continue
if not key in possibilities: possibilities[key] = [] if not key in possibilities: possibilities[key] = []
if type(value) == list: if type(value) == list:

View file

@ -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: class Cup:
icon_dir = None
default_track = None
def __init__(self, def __init__(self,
default_track: Track,
name: str = None, name: str = None,
track1: Track = None, id: int = 0,
track2: Track = None, track1: [Track, TrackGroup] = None,
track3: Track = None, track2: [Track, TrackGroup] = None,
track4: Track = None, track3: [Track, TrackGroup] = None,
track4: [Track, TrackGroup] = None,
*args, **kwargs): *args, **kwargs):
""" """
class of a cup class of a cup
@ -22,14 +28,15 @@ class Cup:
""" """
self.name = name self.name = name
self.id = id
self.tracks = [ self.tracks = [
track1 if track1 else default_track.copy(), track1 if track1 else self.default_track.copy() if self.default_track else None,
track2 if track2 else default_track.copy(), track2 if track2 else self.default_track.copy() if self.default_track else None,
track3 if track3 else default_track.copy(), track3 if track3 else self.default_track.copy() if self.default_track else None,
track4 if track4 else default_track.copy() 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 get the ctfile definition for the cup
:param race: is it a text used for Race_*.szs ? :param race: is it a text used for Race_*.szs ?
@ -40,7 +47,7 @@ class Cup:
ctfile_cup += track.get_ctfile(*args, **kwargs) ctfile_cup += track.get_ctfile(*args, **kwargs)
return ctfile_cup 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 load the cup from a dictionnary
:param cup: dictionnary cup :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 for key, value in cup.items(): # load all value in the json as class attribute
if key == "tracks": # if the key is tracks if key == "tracks": # if the key is tracks
for i, track_json in enumerate(value): # load all tracks from their json 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: else:
setattr(self, key, value) setattr(self, key, value)
return self 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

View file

@ -29,11 +29,6 @@ class TooMuchSha1CheckFailed(Exception):
super().__init__("Too much sha1 check failed !") super().__init__("Too much sha1 check failed !")
class CantConvertTrack(Exception):
def __init__(self):
super().__init__("Can't convert track.")
class NoGui: class NoGui:
""" """
'fake' gui if no gui are used for compatibility. 'fake' gui if no gui are used for compatibility.
@ -280,7 +275,7 @@ class Game:
if not lpar_path: f"" if not lpar_path: f""
lpar_path = ( lpar_path = (
f"{self.ctconfig.pack_path}/file/{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") 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.gui.progress(statut=self.gui.translate("Configurating LE-CODE"), add=1)
self.ctconfig.create_ctfile( self.ctconfig.create_ctfile(
highlight_version=self.gui.stringvar_mark_track_from_version.get(), highlight_version=self.gui.stringvar_mark_track_from_version.get(),
sort_track_by=self.gui.stringvar_sort_track_by.get()
) )
self.generate_cticons() self.generate_cticons()
@ -614,16 +608,9 @@ class Game:
""" """
nonlocal error_count, error_max, thread_list nonlocal error_count, error_max, thread_list
if os.path.exists(track.file_szs) and os.path.getsize(track.file_szs) < 1000: try: track.convert_wu8_to_szs()
os.remove(track.file_szs) # File under this size are corrupted except Exception as e:
raise e
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()
def clean_process() -> int: def clean_process() -> int:
""" """
@ -639,10 +626,10 @@ class Game:
return bool(thread_list) 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) 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: while error_count <= error_max:
if len(thread_list) < max_process: if len(thread_list) < max_process:
thread_list[track.sha1] = Thread(target=add_process, args=[track]) thread_list[track.sha1] = Thread(target=add_process, args=[track])

View file

@ -2,19 +2,25 @@ from source.definition import *
from source.wszst 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): class CantDownloadTrack(Exception):
def __init__(self, track, http_error: [str, int]): def __init__(self, track, http_error: [str, int]):
super().__init__(f"Can't download track {track.name} ({track.sha1}) (error {http_error}) !") 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: def check_file_sha1(file: str, excepted_sha1: str) -> int:
""" """
check if track szs sha1 is correct 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 if not os.path.exists(file): return 0
calculated_sha1 = szs.sha1(file=file) calculated_sha1 = szs.sha1(file=file)
if calculated_sha1 == excepted_sha1: return 1 if calculated_sha1 == excepted_sha1:
return 1
else: else:
print(f"incorrect sha1 for {file} {calculated_sha1} : (expected {excepted_sha1})") print(f"incorrect sha1 for {file} {calculated_sha1} : (expected {excepted_sha1})")
return 0 return 0
class Track: 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", 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 = "", sha1: str = None, since_version: str = None, score: int = -1, warning: int = 0,
track_wu8_dir: str = "./file/Track-WU8/", track_szs_dir: str = "./file/Track/", version: str = None, tags: list = None, is_in_group: bool = False, *args, **kwargs):
version: str = None, tags: list = [], *args, **kwargs):
""" """
Track class Track class
:param name: track name :param name: track name
@ -66,12 +79,10 @@ class Track:
self.score = score # Track score between 1 and 3 stars self.score = score # Track score between 1 and 3 stars
self.warning = warning # Track bug level (1 = minor, 2 = major) self.warning = warning # Track bug level (1 = minor, 2 = major)
self.version = version self.version = version
self.tags = tags self.tags = tags if tags else []
self.refresh_filename()
self._track_wu8_dir = track_wu8_dir self._is_in_group = is_in_group
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"
def __repr__(self) -> str: def __repr__(self) -> str:
""" """
@ -85,23 +96,33 @@ class Track:
check if track wu8 sha1 is correct check if track wu8 sha1 is correct
:return: 0 if yes, -1 if no :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: def check_szs_sha1(self) -> int:
""" """
check if track szs sha1 is correct check if track szs sha1 is correct
:return: 0 if yes, -1 if no :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: def convert_wu8_to_szs(self) -> None:
""" """
convert track to szs convert track to szs
""" """
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( szs.normalize(
src_file=self.file_wu8, src_file=file_wu8,
dest_dir="./file/Track/" dest_file=file_szs
) )
else:
raise MissingTrackWU8()
def get_author_str(self) -> str: def get_author_str(self) -> str:
""" """
@ -109,39 +130,43 @@ class Track:
""" """
return self.author if type(self.author) == str else ", ".join(self.author) 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 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 ? :param race: is it a text used for Race_*.szs ?
:return: ctfile definition for the track :return: ctfile definition for the track
""" """
ctfile_text = ( track_type = "T"
f' T {self.music}; ' track_flag = 0x00 if self.tag_retro in self.tags else 0x01
f'{self.special}; ' if self._is_in_group:
f'{"0x00" if ct_config.tag_retro in self.tags else "0x01"}; ' track_type = "H"
) track_flag |= 0x04
if not race:
ctfile_text += ( ctfile_track = f' {track_type} {self.music}; {self.special}; {hex(track_flag)}; '
f'"{self.sha1}"; ' # track path
f'"{self.get_track_formatted_name(ct_config, *args, **kwargs)}"; ' # track text shown ig if race:
f'"{self.sha1}"\n') # sha1 ctfile_track += (
else:
ctfile_text += (
f'"-"; ' # track path, not used in Race_*.szs, save a bit of space 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 # only in race show author's name
f'"-"\n' # sha1, not used in Race_*.szs, save a bit of space 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: def select_tag(self, tag_list: list) -> str:
for tag in self.tags: for tag in self.tags:
if tag in tag_list: return tag if tag in tag_list: return tag
return "" 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, ... get the track name with score, color, ...
:param ct_config: ct_config for tags configuration :param ct_config: ct_config for tags configuration
@ -150,8 +175,8 @@ class Track:
""" """
hl_prefix = "" # highlight hl_prefix = "" # highlight
hl_suffix = "" hl_suffix = ""
prefix = self.select_tag(ct_config.prefix_list) # tag prefix prefix = self.select_tag(self.prefix_list) # tag prefix
suffix = self.select_tag(ct_config.suffix_list) # tag suffix suffix = self.select_tag(self.suffix_list) # tag suffix
star_prefix = "" # star star_prefix = "" # star
star_suffix = "" star_suffix = ""
star_text = "" star_text = ""
@ -166,8 +191,8 @@ class Track:
if self.since_version == highlight_version: if self.since_version == highlight_version:
hl_prefix, hl_suffix = "\\\\c{blue1}", "\\\\c{off}" hl_prefix, hl_suffix = "\\\\c{blue1}", "\\\\c{off}"
if prefix: prefix = "\\\\c{"+ct_config.tags_color[prefix]+"}"+prefix+"\\\\c{off} " if prefix: prefix = "\\\\c{" + self.tags_color[prefix] + "}" + prefix + "\\\\c{off} "
if suffix: suffix = " (\\\\c{"+ct_config.tags_color[suffix]+"}"+suffix+"\\\\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}" name = f"{star_prefix}{star_text}{star_suffix}{prefix}{hl_prefix}{self.name}{hl_suffix}{suffix}"
return name 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)}" 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): def load_from_json(self, track_json: dict):
""" """
load the track from a dictionary 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 for key, value in track_json.items(): # load all value in the json as class attribute
setattr(self, key, value) setattr(self, key, value)
self.file_wu8 = f"{self._track_wu8_dir}/{self.sha1}.wu8" self.refresh_filename()
self.file_szs = f"{self._track_szs_dir}/{self.sha1}.szs"
return self return self
@ -200,3 +228,32 @@ class Track:
for k, v in self.__dict__.items(): for k, v in self.__dict__.items():
setattr(new, k, v) setattr(new, k, v)
return new 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

View file

@ -48,17 +48,16 @@ def sha1(file, autoadd_path: str = "./file/auto-add/") -> str:
@error.better_wszst_error(wszst_tools=WSZST_PATH) @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: output_format: str = "szs", autoadd_path: str = "./file/auto-add/") -> None:
""" """
convert a track into an another format convert a track into an another format
:param src_file: source file :param src_file: source file
:param dest_dir: destination directory :param dest_file: destination filename (%N mean same name as src_file)
:param dest_name: destination filename (%N mean same name as src_file)
:param output_format: format of the destination track :param output_format: format of the destination track
:param autoadd_path: path of the auto-add directory :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], "--overwrite", "--autoadd-path", autoadd_path],
creationflags=subprocess.CREATE_NO_WINDOW, stderr=subprocess.PIPE) creationflags=subprocess.CREATE_NO_WINDOW, stderr=subprocess.PIPE)