From 03e85fb44267187d42e3aeb6e37be0470eee9071 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Fri, 21 Jan 2022 16:39:29 +0100 Subject: [PATCH] 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 --- Pack/MKWFaraphel/ct_config.json | 31 +++ .../file/cup_icon/{15.png => DS1.png} | Bin .../file/cup_icon/{16.png => DS2.png} | Bin .../file/cup_icon/{17.png => DS3.png} | Bin .../file/cup_icon/{11.png => DS31.png} | Bin .../file/cup_icon/{12.png => DS32.png} | Bin .../file/cup_icon/{13.png => DS33.png} | Bin .../file/cup_icon/{14.png => DS34.png} | Bin .../file/cup_icon/{36.png => DX1.png} | Bin .../file/cup_icon/{21.png => GBA1.png} | Bin .../file/cup_icon/{22.png => GBA2.png} | Bin .../file/cup_icon/{23.png => GBA3.png} | Bin .../file/cup_icon/{24.png => GBA4.png} | Bin .../file/cup_icon/{25.png => GBA5.png} | Bin .../file/cup_icon/{18.png => GCN1.png} | Bin .../file/cup_icon/{19.png => GCN2.png} | Bin .../file/cup_icon/{20.png => GCN3.png} | Bin .../file/cup_icon/{34.png => MKT1.png} | Bin .../file/cup_icon/{35.png => MKT2.png} | Bin .../file/cup_icon/{26.png => N641.png} | Bin .../file/cup_icon/{27.png => N642.png} | Bin .../file/cup_icon/{28.png => N643.png} | Bin .../file/cup_icon/{29.png => SNES1.png} | Bin .../file/cup_icon/{30.png => SNES2.png} | Bin .../file/cup_icon/{31.png => SNES3.png} | Bin .../file/cup_icon/{32.png => SNES4.png} | Bin .../file/cup_icon/{33.png => SNES5.png} | Bin .../file/cup_icon/{9.png => Switch1.png} | Bin .../file/cup_icon/{10.png => Switch2.png} | Bin .../file/cup_icon/{3.png => banana.png} | Bin .../file/cup_icon/{2.png => flower.png} | Bin .../file/cup_icon/{5.png => leaf.png} | Bin .../file/cup_icon/{7.png => lightning.png} | Bin .../file/cup_icon/{0.png => mushroom.png} | Bin .../file/cup_icon/{8.png => random.png} | Bin .../file/cup_icon/{1.png => shell.png} | Bin .../file/cup_icon/{6.png => special.png} | Bin .../file/cup_icon/{4.png => star.png} | Bin Pack/MKWFaraphel/file_process.json | 3 +- source/CT_Config.py | 179 ++++++++---------- source/Cup.py | 60 ++++-- source/Game.py | 25 +-- source/Track.py | 163 ++++++++++------ source/wszst/szs.py | 7 +- 44 files changed, 279 insertions(+), 189 deletions(-) rename Pack/MKWFaraphel/file/cup_icon/{15.png => DS1.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{16.png => DS2.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{17.png => DS3.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{11.png => DS31.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{12.png => DS32.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{13.png => DS33.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{14.png => DS34.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{36.png => DX1.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{21.png => GBA1.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{22.png => GBA2.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{23.png => GBA3.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{24.png => GBA4.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{25.png => GBA5.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{18.png => GCN1.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{19.png => GCN2.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{20.png => GCN3.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{34.png => MKT1.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{35.png => MKT2.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{26.png => N641.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{27.png => N642.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{28.png => N643.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{29.png => SNES1.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{30.png => SNES2.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{31.png => SNES3.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{32.png => SNES4.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{33.png => SNES5.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{9.png => Switch1.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{10.png => Switch2.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{3.png => banana.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{2.png => flower.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{5.png => leaf.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{7.png => lightning.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{0.png => mushroom.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{8.png => random.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{1.png => shell.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{6.png => special.png} (100%) rename Pack/MKWFaraphel/file/cup_icon/{4.png => star.png} (100%) diff --git a/Pack/MKWFaraphel/ct_config.json b/Pack/MKWFaraphel/ct_config.json index 853968e..fd1cfab 100644 --- a/Pack/MKWFaraphel/ct_config.json +++ b/Pack/MKWFaraphel/ct_config.json @@ -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":[ diff --git a/Pack/MKWFaraphel/file/cup_icon/15.png b/Pack/MKWFaraphel/file/cup_icon/DS1.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/15.png rename to Pack/MKWFaraphel/file/cup_icon/DS1.png diff --git a/Pack/MKWFaraphel/file/cup_icon/16.png b/Pack/MKWFaraphel/file/cup_icon/DS2.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/16.png rename to Pack/MKWFaraphel/file/cup_icon/DS2.png diff --git a/Pack/MKWFaraphel/file/cup_icon/17.png b/Pack/MKWFaraphel/file/cup_icon/DS3.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/17.png rename to Pack/MKWFaraphel/file/cup_icon/DS3.png diff --git a/Pack/MKWFaraphel/file/cup_icon/11.png b/Pack/MKWFaraphel/file/cup_icon/DS31.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/11.png rename to Pack/MKWFaraphel/file/cup_icon/DS31.png diff --git a/Pack/MKWFaraphel/file/cup_icon/12.png b/Pack/MKWFaraphel/file/cup_icon/DS32.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/12.png rename to Pack/MKWFaraphel/file/cup_icon/DS32.png diff --git a/Pack/MKWFaraphel/file/cup_icon/13.png b/Pack/MKWFaraphel/file/cup_icon/DS33.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/13.png rename to Pack/MKWFaraphel/file/cup_icon/DS33.png diff --git a/Pack/MKWFaraphel/file/cup_icon/14.png b/Pack/MKWFaraphel/file/cup_icon/DS34.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/14.png rename to Pack/MKWFaraphel/file/cup_icon/DS34.png diff --git a/Pack/MKWFaraphel/file/cup_icon/36.png b/Pack/MKWFaraphel/file/cup_icon/DX1.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/36.png rename to Pack/MKWFaraphel/file/cup_icon/DX1.png diff --git a/Pack/MKWFaraphel/file/cup_icon/21.png b/Pack/MKWFaraphel/file/cup_icon/GBA1.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/21.png rename to Pack/MKWFaraphel/file/cup_icon/GBA1.png diff --git a/Pack/MKWFaraphel/file/cup_icon/22.png b/Pack/MKWFaraphel/file/cup_icon/GBA2.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/22.png rename to Pack/MKWFaraphel/file/cup_icon/GBA2.png diff --git a/Pack/MKWFaraphel/file/cup_icon/23.png b/Pack/MKWFaraphel/file/cup_icon/GBA3.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/23.png rename to Pack/MKWFaraphel/file/cup_icon/GBA3.png diff --git a/Pack/MKWFaraphel/file/cup_icon/24.png b/Pack/MKWFaraphel/file/cup_icon/GBA4.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/24.png rename to Pack/MKWFaraphel/file/cup_icon/GBA4.png diff --git a/Pack/MKWFaraphel/file/cup_icon/25.png b/Pack/MKWFaraphel/file/cup_icon/GBA5.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/25.png rename to Pack/MKWFaraphel/file/cup_icon/GBA5.png diff --git a/Pack/MKWFaraphel/file/cup_icon/18.png b/Pack/MKWFaraphel/file/cup_icon/GCN1.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/18.png rename to Pack/MKWFaraphel/file/cup_icon/GCN1.png diff --git a/Pack/MKWFaraphel/file/cup_icon/19.png b/Pack/MKWFaraphel/file/cup_icon/GCN2.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/19.png rename to Pack/MKWFaraphel/file/cup_icon/GCN2.png diff --git a/Pack/MKWFaraphel/file/cup_icon/20.png b/Pack/MKWFaraphel/file/cup_icon/GCN3.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/20.png rename to Pack/MKWFaraphel/file/cup_icon/GCN3.png diff --git a/Pack/MKWFaraphel/file/cup_icon/34.png b/Pack/MKWFaraphel/file/cup_icon/MKT1.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/34.png rename to Pack/MKWFaraphel/file/cup_icon/MKT1.png diff --git a/Pack/MKWFaraphel/file/cup_icon/35.png b/Pack/MKWFaraphel/file/cup_icon/MKT2.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/35.png rename to Pack/MKWFaraphel/file/cup_icon/MKT2.png diff --git a/Pack/MKWFaraphel/file/cup_icon/26.png b/Pack/MKWFaraphel/file/cup_icon/N641.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/26.png rename to Pack/MKWFaraphel/file/cup_icon/N641.png diff --git a/Pack/MKWFaraphel/file/cup_icon/27.png b/Pack/MKWFaraphel/file/cup_icon/N642.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/27.png rename to Pack/MKWFaraphel/file/cup_icon/N642.png diff --git a/Pack/MKWFaraphel/file/cup_icon/28.png b/Pack/MKWFaraphel/file/cup_icon/N643.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/28.png rename to Pack/MKWFaraphel/file/cup_icon/N643.png diff --git a/Pack/MKWFaraphel/file/cup_icon/29.png b/Pack/MKWFaraphel/file/cup_icon/SNES1.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/29.png rename to Pack/MKWFaraphel/file/cup_icon/SNES1.png diff --git a/Pack/MKWFaraphel/file/cup_icon/30.png b/Pack/MKWFaraphel/file/cup_icon/SNES2.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/30.png rename to Pack/MKWFaraphel/file/cup_icon/SNES2.png diff --git a/Pack/MKWFaraphel/file/cup_icon/31.png b/Pack/MKWFaraphel/file/cup_icon/SNES3.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/31.png rename to Pack/MKWFaraphel/file/cup_icon/SNES3.png diff --git a/Pack/MKWFaraphel/file/cup_icon/32.png b/Pack/MKWFaraphel/file/cup_icon/SNES4.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/32.png rename to Pack/MKWFaraphel/file/cup_icon/SNES4.png diff --git a/Pack/MKWFaraphel/file/cup_icon/33.png b/Pack/MKWFaraphel/file/cup_icon/SNES5.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/33.png rename to Pack/MKWFaraphel/file/cup_icon/SNES5.png diff --git a/Pack/MKWFaraphel/file/cup_icon/9.png b/Pack/MKWFaraphel/file/cup_icon/Switch1.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/9.png rename to Pack/MKWFaraphel/file/cup_icon/Switch1.png diff --git a/Pack/MKWFaraphel/file/cup_icon/10.png b/Pack/MKWFaraphel/file/cup_icon/Switch2.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/10.png rename to Pack/MKWFaraphel/file/cup_icon/Switch2.png diff --git a/Pack/MKWFaraphel/file/cup_icon/3.png b/Pack/MKWFaraphel/file/cup_icon/banana.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/3.png rename to Pack/MKWFaraphel/file/cup_icon/banana.png diff --git a/Pack/MKWFaraphel/file/cup_icon/2.png b/Pack/MKWFaraphel/file/cup_icon/flower.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/2.png rename to Pack/MKWFaraphel/file/cup_icon/flower.png diff --git a/Pack/MKWFaraphel/file/cup_icon/5.png b/Pack/MKWFaraphel/file/cup_icon/leaf.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/5.png rename to Pack/MKWFaraphel/file/cup_icon/leaf.png diff --git a/Pack/MKWFaraphel/file/cup_icon/7.png b/Pack/MKWFaraphel/file/cup_icon/lightning.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/7.png rename to Pack/MKWFaraphel/file/cup_icon/lightning.png diff --git a/Pack/MKWFaraphel/file/cup_icon/0.png b/Pack/MKWFaraphel/file/cup_icon/mushroom.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/0.png rename to Pack/MKWFaraphel/file/cup_icon/mushroom.png diff --git a/Pack/MKWFaraphel/file/cup_icon/8.png b/Pack/MKWFaraphel/file/cup_icon/random.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/8.png rename to Pack/MKWFaraphel/file/cup_icon/random.png diff --git a/Pack/MKWFaraphel/file/cup_icon/1.png b/Pack/MKWFaraphel/file/cup_icon/shell.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/1.png rename to Pack/MKWFaraphel/file/cup_icon/shell.png diff --git a/Pack/MKWFaraphel/file/cup_icon/6.png b/Pack/MKWFaraphel/file/cup_icon/special.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/6.png rename to Pack/MKWFaraphel/file/cup_icon/special.png diff --git a/Pack/MKWFaraphel/file/cup_icon/4.png b/Pack/MKWFaraphel/file/cup_icon/star.png similarity index 100% rename from Pack/MKWFaraphel/file/cup_icon/4.png rename to Pack/MKWFaraphel/file/cup_icon/star.png diff --git a/Pack/MKWFaraphel/file_process.json b/Pack/MKWFaraphel/file_process.json index 7ef944f..fba06fa 100644 --- a/Pack/MKWFaraphel/file_process.json +++ b/Pack/MKWFaraphel/file_process.json @@ -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/" } } \ No newline at end of file diff --git a/source/CT_Config.py b/source/CT_Config.py index d1bb9de..f182058 100644 --- a/source/CT_Config.py +++ b/source/CT_Config.py @@ -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: diff --git a/source/Cup.py b/source/Cup.py index 99b83b7..8fa6cdc 100644 --- a/source/Cup.py +++ b/source/Cup.py @@ -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 diff --git a/source/Game.py b/source/Game.py index 1fd7020..0a0486a 100644 --- a/source/Game.py +++ b/source/Game.py @@ -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]) diff --git a/source/Track.py b/source/Track.py index cab4a3c..b5a917c 100644 --- a/source/Track.py +++ b/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 \ No newline at end of file diff --git a/source/wszst/szs.py b/source/wszst/szs.py index 733727f..390e251 100644 --- a/source/wszst/szs.py +++ b/source/wszst/szs.py @@ -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)