mirror of
https://github.com/Faraphel/Atlas-Install.git
synced 2025-07-02 10:48:29 +02:00
244 lines
9.3 KiB
Python
244 lines
9.3 KiB
Python
from pathlib import Path
|
|
from typing import Generator
|
|
|
|
from PIL import Image
|
|
|
|
from source.mkw import Tag, Color
|
|
from source.mkw.Cup import Cup
|
|
from source.mkw.Track import Track
|
|
import json
|
|
|
|
|
|
CT_ICON_SIZE: int = 128
|
|
|
|
|
|
# representation of the configuration of a mod
|
|
class ModConfig:
|
|
__slots__ = ("name", "path", "nickname", "variant", "tags_prefix", "tags_suffix",
|
|
"default_track", "_tracks", "version", "original_track_prefix", "swap_original_order",
|
|
"keep_original_track", "enable_random_cup", "tags_cups", "track_formatting")
|
|
|
|
def __init__(self, path: Path | str, name: str, nickname: str = None, version: str = None, variant: str = None,
|
|
tags_prefix: dict[Tag, Color] = None, tags_suffix: dict[Tag, Color] = None,
|
|
tags_cups: list[Tag] = None,
|
|
default_track: "Track | TrackGroup" = None, tracks: list["Track | TrackGroup"] = None,
|
|
original_track_prefix: bool = None, swap_original_order: bool = None,
|
|
keep_original_track: bool = None, enable_random_cup: bool = None,
|
|
track_formatting: dict[str, str] = None):
|
|
|
|
self.path = Path(path)
|
|
|
|
self.name: str = name
|
|
self.nickname: str = nickname if nickname is not None else name
|
|
self.version: str = version if version is not None else "v1.0.0"
|
|
self.variant: str = variant if variant is not None else "01"
|
|
|
|
self.tags_prefix: dict[Tag] = tags_prefix if tags_prefix is not None else {}
|
|
self.tags_suffix: dict[Tag] = tags_suffix if tags_suffix is not None else {}
|
|
self.tags_cups: list[Tag] = tags_cups if tags_cups is not None else []
|
|
|
|
self.default_track: "Track | TrackGroup" = default_track if default_track is not None else None
|
|
self._tracks: list["Track | TrackGroup"] = tracks if tracks is not None else []
|
|
self.track_formatting: dict[str, str] = {
|
|
"menu_name": "{{ getattr(track, 'name', '') }}",
|
|
"race_name": "{{ getattr(track, 'name', '') }}",
|
|
"file_name": "{{ getattr(track, 'sha1', '_') }}",
|
|
} | (track_formatting if track_formatting is not None else {})
|
|
|
|
self.original_track_prefix: bool = original_track_prefix if original_track_prefix is not None else True
|
|
self.swap_original_order: bool = swap_original_order if swap_original_order is not None else True
|
|
self.keep_original_track: bool = keep_original_track if keep_original_track is not None else True
|
|
self.enable_random_cup: bool = enable_random_cup if enable_random_cup is not None else True
|
|
|
|
def __repr__(self):
|
|
return f"<ModConfig name={self.name} version={self.version}>"
|
|
|
|
@classmethod
|
|
def from_dict(cls, path: Path | str, config_dict: dict) -> "ModConfig":
|
|
"""
|
|
Create a ModConfig from a dict
|
|
:param path: path of the mod_config.json
|
|
:param config_dict: dict containing the configuration
|
|
:return: ModConfig
|
|
"""
|
|
kwargs = {
|
|
attr: config_dict.get(attr)
|
|
for attr in cls.__slots__
|
|
if attr not in ["name", "default_track", "_tracks", "tracks", "path"]
|
|
# these keys are treated after or are reserved
|
|
}
|
|
|
|
return cls(
|
|
path=Path(path),
|
|
name=config_dict["name"],
|
|
|
|
**kwargs,
|
|
|
|
default_track=Track.from_dict(config_dict.get("default_track", {})),
|
|
tracks=[Track.from_dict(track) for track in config_dict.get("tracks", [])],
|
|
)
|
|
|
|
@classmethod
|
|
def from_file(cls, config_file: str | Path) -> "ModConfig":
|
|
"""
|
|
Create a ModConfig from a file
|
|
:param config_file: file containing the configuration
|
|
:return: ModConfig
|
|
"""
|
|
config_file = Path(config_file)
|
|
return cls.from_dict(
|
|
path=config_file,
|
|
config_dict=json.loads(config_file.read_text(encoding="utf8"))
|
|
)
|
|
|
|
def get_mod_directory(self) -> Path:
|
|
"""
|
|
Get the directory of the mod
|
|
:return: directory of the mod
|
|
"""
|
|
return self.path.parent
|
|
|
|
def get_tracks(self) -> Generator["Track", None, None]:
|
|
"""
|
|
Get all the track elements
|
|
:return: track elements
|
|
"""
|
|
for track in self._tracks:
|
|
yield from track.get_tracks()
|
|
|
|
def get_ordered_cups(self) -> Generator["Cup", None, None]:
|
|
"""
|
|
Get all the cups with cup tags
|
|
:return: cups with cup tags
|
|
"""
|
|
# use self._tracks instead of self._get_tracks() because we want the TrackGroup
|
|
# for track that have a tag in self.tags_cups
|
|
for tag_cup in self.tags_cups:
|
|
track_buffer: "Track | TrackGroup" = []
|
|
current_tag_name, current_tag_count = tag_cup, 0
|
|
|
|
# every four 4 tracks, create a cup
|
|
for track in filter(lambda track: tag_cup in getattr(track, "tags", []), self._tracks):
|
|
track_buffer.append(track)
|
|
|
|
if len(track_buffer) > 4:
|
|
current_tag_count += 1
|
|
yield Cup(tracks=track_buffer, cup_name=f"{current_tag_name}/{current_tag_count}")
|
|
track_buffer = []
|
|
|
|
# if there is still tracks in the buffer, create a cup with them and fill with default>
|
|
if len(track_buffer) > 0:
|
|
track_buffer.extend([self.default_track] * (4 - len(track_buffer)))
|
|
yield Cup(tracks=track_buffer, cup_name=f"{current_tag_name}/{current_tag_count+1}")
|
|
|
|
def get_unordered_cups(self) -> Generator["Cup", None, None]:
|
|
"""
|
|
Get all the cups with no cup tags
|
|
:return: cups with no cup tags
|
|
"""
|
|
# for track that have don't have a tag in self.tags_cups
|
|
track_buffer: "Track | TrackGroup" = []
|
|
for track in filter(
|
|
lambda track: not any(item in getattr(track, "tags", []) for item in self.tags_cups),
|
|
self._tracks
|
|
):
|
|
track_buffer.append(track)
|
|
|
|
if len(track_buffer) > 4:
|
|
yield Cup(tracks=track_buffer)
|
|
track_buffer = []
|
|
|
|
# if there is still tracks in the buffer, create a cup with them and fill with default
|
|
if len(track_buffer) > 0:
|
|
track_buffer.extend([self.default_track] * (4 - len(track_buffer)))
|
|
yield Cup(tracks=track_buffer)
|
|
|
|
def get_cups(self) -> Generator["Cup", None, None]:
|
|
"""
|
|
Get all the cups
|
|
:return: cups
|
|
"""
|
|
yield from self.get_ordered_cups()
|
|
yield from self.get_unordered_cups()
|
|
|
|
def get_ctfile(self) -> str:
|
|
"""
|
|
Return the ct_file generated from the ModConfig
|
|
:return: ctfile content
|
|
"""
|
|
lecode_flags = filter(lambda v: v is not None, [
|
|
"N$SHOW" if self.keep_original_track else "N$NONE",
|
|
"N$F_WII" if self.original_track_prefix else None,
|
|
"N$SWAP" if self.swap_original_order else None
|
|
])
|
|
|
|
ctfile = (
|
|
f"#CT-CODE\n" # magic number
|
|
f"[RACING-TRACK-LIST]\n" # start of the track section
|
|
f"%LE-FLAGS=1\n" # enable lecode mode
|
|
f"%WIIMM-CUP={int(self.enable_random_cup)}\n" # enable random cup
|
|
f"N {' | '.join(lecode_flags)}\n" # other flags to disable default tracks, ...
|
|
f"\n"
|
|
)
|
|
|
|
for cup in self.get_cups():
|
|
ctfile += cup.get_ctfile(mod_config=self)
|
|
|
|
return ctfile
|
|
|
|
def get_base_cticons(self) -> Generator[Image.Image, None, None]:
|
|
"""
|
|
Return the base cticon
|
|
:return:
|
|
"""
|
|
icon_names = ["left", "right"]
|
|
|
|
if self.keep_original_track:
|
|
icon_names += [
|
|
f"_DEFAULT/{name}"
|
|
for name in (
|
|
["mushroom", "shell", "flower", "banana", "star", "leaf", "special", "lightning"]
|
|
if self.swap_original_order else
|
|
["mushroom", "flower", "star", "special", "shell", "banana", "leaf", "lightning"]
|
|
)
|
|
]
|
|
if self.enable_random_cup: icon_names.append("random")
|
|
|
|
for icon_name in icon_names:
|
|
yield Image.open(self.get_mod_directory() / f"_CUPS/{icon_name}.png").resize((CT_ICON_SIZE, CT_ICON_SIZE))
|
|
|
|
def get_custom_cticons(self) -> Generator[Image.Image, None, None]:
|
|
"""
|
|
Return the custom ct_icon generated from the ModConfig
|
|
:return:
|
|
"""
|
|
for cup in self.get_cups():
|
|
yield cup.get_cticon(mod_config=self).resize((CT_ICON_SIZE, CT_ICON_SIZE))
|
|
|
|
def get_cticons(self) -> Generator[Image.Image, None, None]:
|
|
"""
|
|
Return all the ct_icon generated from the ModConfig
|
|
:return:
|
|
"""
|
|
yield from self.get_base_cticons()
|
|
yield from self.get_custom_cticons()
|
|
|
|
@staticmethod
|
|
def get_default_font() -> Path:
|
|
"""
|
|
Return the default font for creating ct_icons
|
|
:return: the path to the default font file
|
|
"""
|
|
return Path("./assets/SuperMario256.ttf")
|
|
|
|
def get_full_cticon(self) -> Image.Image:
|
|
"""
|
|
Return the full ct_icon generated from the ModConfig
|
|
:return:
|
|
"""
|
|
cticons = list(self.get_cticons())
|
|
|
|
full_cticon = Image.new("RGBA", (CT_ICON_SIZE * len(cticons), CT_ICON_SIZE))
|
|
for i, cticon in enumerate(cticons): full_cticon.paste(cticon, (i * CT_ICON_SIZE, 0))
|
|
|
|
return full_cticon
|