started making a AbstractTrack class to implement more type of "track".

Removed get_suffix and get_prefix to replace them with get_tag_template and tags_template in mod_config.json
This commit is contained in:
Faraphel 2022-08-10 22:33:10 +02:00
parent 7d15d98999
commit 99287bbd56
9 changed files with 234 additions and 194 deletions

View file

@ -33,62 +33,76 @@
}, },
"lpar_template": "{{ mode if (mode := ## SETTINGS_MODE ##) is not None else 'normal' }}.lpar", "lpar_template": "{{ mode if (mode := ## SETTINGS_MODE ##) is not None else 'normal' }}.lpar",
"tags_prefix": { "tags_templates": {
"MSRDS": "{{ bmg_color_text('green', tag) }}", "prefix": {
"CTR": "{{ bmg_color_text('orange', tag) }}", "MSRDS": "{{ bmg_color_text('green', tag) }}",
"CTTR": "{{ bmg_color_text('dark orange', tag) }}", "CTR": "{{ bmg_color_text('orange', tag) }}",
"CNR": "{{ bmg_color_text('orange', tag) }}", "CTTR": "{{ bmg_color_text('dark orange', tag) }}",
"DKR": "{{ bmg_color_text('dark red', tag) }}", "CNR": "{{ bmg_color_text('orange', tag) }}",
"LCP": "{{ bmg_color_text('green', tag) }}", "DKR": "{{ bmg_color_text('dark red', tag) }}",
"LEGO-R": "{{ bmg_color_text('light red', tag) }}", "LCP": "{{ bmg_color_text('green', tag) }}",
"MP9": "{{ bmg_color_text('neon yellow', tag) }}", "LEGO-R": "{{ bmg_color_text('light red', tag) }}",
"MSUSA": "{{ bmg_color_text('green', tag) }}", "MP9": "{{ bmg_color_text('neon yellow', tag) }}",
"FZMV": "{{ bmg_color_text('yellow', tag) }}", "MSUSA": "{{ bmg_color_text('green', tag) }}",
"KAR": "{{ bmg_color_text('green', tag) }}", "FZMV": "{{ bmg_color_text('yellow', tag) }}",
"KO": "{{ bmg_color_text('dark orange', tag) }}", "KAR": "{{ bmg_color_text('green', tag) }}",
"FZ": "{{ bmg_color_text('yellow', tag) }}", "KO": "{{ bmg_color_text('dark orange', tag) }}",
"RV": "{{ bmg_color_text('white', tag) }}", "FZ": "{{ bmg_color_text('yellow', tag) }}",
"SADX": "{{ bmg_color_text('dark blue', tag) }}", "RV": "{{ bmg_color_text('white', tag) }}",
"SCR": "{{ bmg_color_text('yellow', tag) }}", "SADX": "{{ bmg_color_text('dark blue', tag) }}",
"SH": "{{ bmg_color_text('pink', tag) }}", "SCR": "{{ bmg_color_text('yellow', tag) }}",
"SM64": "{{ bmg_color_text('pink', tag) }}", "SH": "{{ bmg_color_text('pink', tag) }}",
"SMB1": "{{ bmg_color_text('light red', tag) }}", "SM64": "{{ bmg_color_text('pink', tag) }}",
"SMB2": "{{ bmg_color_text('red', tag) }}", "SMB1": "{{ bmg_color_text('light red', tag) }}",
"SSBB": "{{ bmg_color_text('vivid red', tag) }}", "SMB2": "{{ bmg_color_text('red', tag) }}",
"SMS": "{{ bmg_color_text('dark red', tag) }}", "SSBB": "{{ bmg_color_text('vivid red', tag) }}",
"SMO": "{{ bmg_color_text('apple red', tag) }}", "SMS": "{{ bmg_color_text('dark red', tag) }}",
"VVVVVV": "{{ bmg_color_text('azure', tag) }}", "SMO": "{{ bmg_color_text('apple red', tag) }}",
"WF": "{{ bmg_color_text('green', tag) }}", "VVVVVV": "{{ bmg_color_text('azure', tag) }}",
"WP": "{{ bmg_color_text('neon yellow 2', tag) }}", "WF": "{{ bmg_color_text('green', tag) }}",
"Zelda OoT": "{{ bmg_color_text('green', tag) }}", "WP": "{{ bmg_color_text('neon yellow 2', tag) }}",
"Zelda TP": "{{ bmg_color_text('green', tag) }}", "Zelda OoT": "{{ bmg_color_text('green', tag) }}",
"Zelda WW": "{{ bmg_color_text('green', tag) }}", "Zelda TP": "{{ bmg_color_text('green', tag) }}",
"PMWR": "{{ bmg_color_text('neon yellow 2', tag) }}", "Zelda WW": "{{ bmg_color_text('green', tag) }}",
"SHR": "{{ bmg_color_text('green', tag) }}", "PMWR": "{{ bmg_color_text('neon yellow 2', tag) }}",
"SK64": "{{ bmg_color_text('green', tag) }}", "SHR": "{{ bmg_color_text('green', tag) }}",
"SMG": "{{ bmg_color_text('light red', tag) }}", "SK64": "{{ bmg_color_text('green', tag) }}",
"Spyro 1": "{{ bmg_color_text('azure', tag) }}", "SMG": "{{ bmg_color_text('light red', tag) }}",
"Switch": "{{ bmg_color_text('vivid red', tag) }}", "Spyro 1": "{{ bmg_color_text('azure', tag) }}",
"Wii": "{{ bmg_color_text('azure', tag) }}", "Switch": "{{ bmg_color_text('vivid red', tag) }}",
"3DS": "{{ bmg_color_text('light orange', tag) }}", "Wii": "{{ bmg_color_text('azure', tag) }}",
"DS": "{{ bmg_color_text('white', tag) }}", "3DS": "{{ bmg_color_text('light orange', tag) }}",
"GCN": "{{ bmg_color_text('azure', tag) }}", "DS": "{{ bmg_color_text('white', tag) }}",
"GBA": "{{ bmg_color_text('dark blue', tag) }}", "GCN": "{{ bmg_color_text('azure', tag) }}",
"N64": "{{ bmg_color_text('pink', tag) }}", "GBA": "{{ bmg_color_text('dark blue', tag) }}",
"SNES": "{{ bmg_color_text('green', tag) }}", "N64": "{{ bmg_color_text('pink', tag) }}",
"RMX": "{{ bmg_color_text('orange', tag) }}", "SNES": "{{ bmg_color_text('green', tag) }}",
"MKT": "{{ bmg_color_text('dark orange', tag) }}", "RMX": "{{ bmg_color_text('orange', tag) }}",
"GP": "{{ bmg_color_text('dark red', tag) }}", "MKT": "{{ bmg_color_text('dark orange', tag) }}",
"UT": "{{ bmg_color_text('red', tag) }}", "GP": "{{ bmg_color_text('dark red', tag) }}",
"GK2": "{{ bmg_color_text('green', tag) }}", "UT": "{{ bmg_color_text('red', tag) }}",
"GK3": "{{ bmg_color_text('green', tag) }}", "GK2": "{{ bmg_color_text('green', tag) }}",
"GK7": "{{ bmg_color_text('green', tag) }}", "GK3": "{{ bmg_color_text('green', tag) }}",
"FGKR": "{{ bmg_color_text('dark orange', tag) }}" "GK7": "{{ bmg_color_text('green', tag) }}",
"FGKR": "{{ bmg_color_text('dark orange', tag) }}"
},
"suffix": {
"Boost": "{{ bmg_color_text('orange', tag) }}"
}
}, },
"tags_suffix": { "tags_cups": [
"Boost": "{{ bmg_color_text('orange', tag) }}" "Switch",
}, "3DS",
"tags_cups": ["Switch", "3DS", "DS", "GCN", "GBA", "N64", "SNES", "MKT", "RMX", "DX", "GP"], "DS",
"GCN",
"GBA",
"N64",
"SNES",
"MKT",
"RMX",
"DX",
"GP"
],
"track_file_template": "{{ getattr(track, 'sha1', '_') }}", "track_file_template": "{{ getattr(track, 'sha1', '_') }}",
"multiplayer_disable_if": "getattr(track, 'warning', 0) >= 1", "multiplayer_disable_if": "getattr(track, 'warning', 0) >= 1",

View file

@ -9,7 +9,7 @@ from source.mkw import Tag
from source.mkw.Cup import Cup from source.mkw.Cup import Cup
from source.mkw.MKWColor import bmg_color_text, bmg_color_raw from source.mkw.MKWColor import bmg_color_text, bmg_color_raw
from source.mkw.ModSettings import AbstractModSettings from source.mkw.ModSettings import AbstractModSettings
from source.mkw.Track import Track from source.mkw.Track.CustomTrack import CustomTrack
import json import json
from source.mkw.OriginalTrack import OriginalTrack from source.mkw.OriginalTrack import OriginalTrack
@ -68,20 +68,20 @@ class ModConfig:
Representation of a mod Representation of a mod
""" """
__slots__ = ("name", "path", "nickname", "variant", "tags_prefix", "tags_suffix", __slots__ = ("name", "path", "nickname", "variant", "default_track", "_tracks", "version",
"default_track", "_tracks", "version", "original_track_prefix", "swap_original_order", "original_track_prefix", "swap_original_order", "keep_original_track",
"keep_original_track", "enable_random_cup", "tags_cups", "track_file_template", "enable_random_cup", "tags_cups", "track_file_template",
"multiplayer_disable_if", "macros", "messages", "global_settings", "multiplayer_disable_if", "macros", "messages", "global_settings",
"specific_settings", "lpar_template") "specific_settings", "lpar_template", "tags_template")
def __init__(self, path: Path | str, name: str, nickname: str = None, version: str = None, variant: str = None, def __init__(self, path: Path | str, name: str, nickname: str = None, version: str = None, variant: str = None,
tags_prefix: dict[Tag, str] = None, tags_suffix: dict[Tag, str] = None,
tags_cups: list[Tag] = None, default_track: "Track | TrackGroup" = None, tags_cups: list[Tag] = None, default_track: "Track | TrackGroup" = None,
tracks: list["Track | TrackGroup"] = None, original_track_prefix: bool = 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, swap_original_order: bool = None, keep_original_track: bool = None, enable_random_cup: bool = None,
track_file_template: str = None, multiplayer_disable_if: str = None, macros: dict[str, str] = None, track_file_template: str = None, multiplayer_disable_if: str = None, macros: dict[str, str] = None,
messages: dict[str, dict[str, str]] = None, global_settings: dict[str, dict[str, str]] = None, messages: dict[str, dict[str, str]] = None, global_settings: dict[str, dict[str, str]] = None,
specific_settings: dict[str, dict[str, str]] = None, lpar_template: str = None): specific_settings: dict[str, dict[str, str]] = None, lpar_template: str = None,
tags_template: dict[str, str] = None):
self.path = Path(path) self.path = Path(path)
self.macros: dict = macros if macros is not None else {} self.macros: dict = macros if macros is not None else {}
@ -100,8 +100,7 @@ class ModConfig:
self.version: str = version if version is not None else "v1.0.0" 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.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_template: dict[str, str] = tags_template if tags_template 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.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.default_track: "Track | TrackGroup" = default_track if default_track is not None else None
@ -145,8 +144,8 @@ class ModConfig:
**kwargs, **kwargs,
default_track=Track.from_dict(config_dict.get("default_track", {})), default_track=CustomTrack.from_dict(config_dict.get("default_track", {})),
tracks=[Track.from_dict(track) for track in config_dict.get("tracks", [])], tracks=[CustomTrack.from_dict(track) for track in config_dict.get("tracks", [])],
macros=macros, macros=macros,
messages=messages, messages=messages,
) )
@ -204,7 +203,7 @@ class ModConfig:
""" """
return self.path.parent return self.path.parent
def get_all_tracks(self, *args, **kwargs) -> Generator["Track", None, None]: def get_all_tracks(self, *args, **kwargs) -> Generator["CustomTrack", None, None]:
""" """
Same as get_tracks, but track group are divided into subtracks Same as get_tracks, but track group are divided into subtracks
""" """
@ -425,7 +424,7 @@ class ModConfig:
) )
@threaded @threaded
def normalize_track(track: Track, track_file: Path): def normalize_track(track: CustomTrack, track_file: Path):
SZSPath(track_file).normalize( SZSPath(track_file).normalize(
autoadd_path, autoadd_path,
destination_path / f"{track_file.stem}.szs", destination_path / f"{track_file.stem}.szs",

View file

@ -1,7 +1,7 @@
import re import re
from source.mkw.Patch.PatchOperation.BmgTxtEditor import AbstractLayer from source.mkw.Patch.PatchOperation.BmgTxtEditor import AbstractLayer
from source.mkw.Track import Track from source.mkw.Track.CustomTrack import CustomTrack
from source.wt import bmg from source.wt import bmg
@ -46,7 +46,7 @@ class FormatOriginalTrackLayer(AbstractLayer):
break break
else: tag = "Wii" else: tag = "Wii"
patched_name = Track( patched_name = CustomTrack(
name=name, name=name,
tags=[tag] tags=[tag]
).repr_format( ).repr_format(

View file

@ -1,121 +0,0 @@
from typing import Generator
from source.mkw import Tag, Slot
ModConfig: any
# representation of a custom track
class Track:
def __init__(self, special: Slot = None, music: Slot = None, tags: list[Tag] = None, weight: int = None, **kwargs):
self.special: Slot = special if special is not None else "T11"
self.music: Slot = music if music is not None else "T11"
self.tags: list[Tag] = tags if tags is not None else []
self.weight: int = weight if weight is not None else 1
# others not mandatory attributes
for key, value in kwargs.items():
# if the attribute start with __, this is a magic attribute, and it should not be modified
if key.startswith("__"): continue
setattr(self, key, value)
def __repr__(self):
return f"<Track name={getattr(self, 'name', '/')} tags={getattr(self, 'tags', '/')}>"
@classmethod
def from_dict(cls, track_dict: dict) -> "Track | TrackGroup":
"""
create a track from a dict, or create a track group is it is a group
:param track_dict: dict containing the track information
:return: Track
"""
if "group" in track_dict:
from source.mkw.TrackGroup import TrackGroup
return TrackGroup.from_dict(track_dict)
return cls(**track_dict)
def get_tracks(self) -> Generator["Track", None, None]:
"""
Get all the track elements
:return: track elements
"""
for _ in range(self.weight):
yield self
def repr_format(self, mod_config: "ModConfig", template: str) -> str:
"""
return the representation of the track from the format
:param template: template for the way the text will be represented
:param mod_config: configuration of the mod
:return: formatted representation of the track
"""
return mod_config.multiple_safe_eval(
template,
env={
"track": self,
"prefix": self.get_prefix(mod_config, ""),
"suffix": self.get_suffix(mod_config, "")
},
)
def get_tag_template(self, mod_config: "ModConfig", templates: dict[str, str], default: any = None) -> any:
"""
Return the tag template found in templates. If not found, return default
:param mod_config: mod configuration
:param templates: template with all the tags and its replacement
:param default: default value if no tag template is found
:return: formatted representation of the tag
"""
for tag in filter(lambda tag: tag in templates, self.tags):
template: str = templates[tag]
return mod_config.multiple_safe_eval(template, env={"tag": tag})
return default
def get_prefix(self, mod_config: "ModConfig", default: any = None) -> any:
"""
return the prefix of the track
:param default: default value if no prefix is found
:param mod_config: mod configuration
:return: formatted representation of the track prefix
"""
return self.get_tag_template(mod_config, mod_config.tags_prefix, default)
def get_suffix(self, mod_config: "ModConfig", default: any = None) -> any:
"""
return the suffix of the track
:param default: default value if no suffix is found
:param mod_config: mod configuration
:return: formatted representation of the track suffix
"""
return self.get_tag_template(mod_config, mod_config.tags_suffix, default)
def is_new(self, mod_config: "ModConfig") -> bool:
"""
Return if the track should be considered as new for random selection
:param mod_config: mod configuration
:return: is the track new
"""
return mod_config.safe_eval(
mod_config.global_settings["replace_random_new"].value,
env={"track": self}
) is True
def get_ctfile(self, mod_config: "ModConfig", template: str, hidden: bool = False) -> str:
"""
return the ctfile of the track
:hidden: if the track is in a group
:template: format of the track's name
:return: ctfile
"""
name = repr(self.repr_format(mod_config=mod_config, template=template))
file_name = repr(self.repr_format(mod_config=mod_config, template=mod_config.track_file_template))
return (
f'{"H" if hidden else "T"} {self.music}; ' # track type
f'{self.special}; {(0x04 if hidden else 0) | (0x01 if self.is_new(mod_config) else 0):#04x}; ' # lecode flags
f'{file_name}; ' # filename
f'{name}; ' # name of the track in the menu
f'{file_name}\n' # unique identifier for each track
)

View file

@ -0,0 +1,103 @@
from abc import ABC, abstractmethod
from typing import Generator
from source.mkw import Slot, Tag, ModConfig
class TrackForbiddenCustomAttribute(Exception):
def __init__(self, attribute_name: str):
super().__init__(f"Forbidden track attribute : {attribute_name!r}")
class AbstractTrack(ABC):
music: Slot
special: Slot
tags: list[Tag]
weight: int
def __init__(self, music: Slot = "T11", special: Slot = "T11", tags: list[Tag] = None, weight: int = 1, **kwargs):
self.music = music
self.special = special
self.tags = tags if tags is not None else []
self.weight = weight
# others not mandatory attributes
for key, value in kwargs.items():
# if the attribute start with __, this is a magic attribute, and it should not be modified
if "__" in key or hasattr(self, key): raise TrackForbiddenCustomAttribute(key)
setattr(self, key, value)
def __repr__(self):
return f"<{self.__class__.__name__} {id(self)}>"
def get_tracks(self) -> Generator["AbstractTrack", None, None]:
"""
Return all the track itself or the subtracks if available
:return: all the track itself or the subtracks if available
"""
for _ in range(self.weight):
yield self
def get_tag_template(self, mod_config: "ModConfig", template_name: str, default: any = None) -> any:
"""
Return the tag template found in templates. If not found, return default
:param mod_config: mod configuration
:param template_name: name of the template of the tags
:param default: default value if no tag template is found
:return: formatted representation of the tag
"""
for tag in filter(lambda tag: tag in mod_config.tags_template[template_name], self.tags):
return mod_config.multiple_safe_eval(mod_config.tags_template[template_name][tag], env={"tag": tag})
return default
def repr_format(self, mod_config: "ModConfig", template: str) -> str:
"""
return the representation of the track from the format
:param template: template for the way the text will be represented
:param mod_config: configuration of the mod
:return: formatted representation of the track
"""
return mod_config.multiple_safe_eval(template, env={"track": self, "get_tag_template": self.get_tag_template})
@abstractmethod
def get_filename(self, mod_config: "ModConfig") -> str:
"""
Return the filename of the track
:param mod_config: the mod_config object
:return: the filename of the track
"""
...
@abstractmethod
def is_new(self, mod_config: "ModConfig") -> bool:
"""
Return if the track should be considered as new for random selection
:param mod_config: mod configuration
:return: is the track new
"""
...
def get_ctfile(self, mod_config: "ModConfig", template: str, hidden: bool = False) -> str:
"""
return the ctfile of the track
:hidden: if the track is in a group
:template: format of the track's name
:return: ctfile
"""
category: str = "H" if hidden else "T"
name: str = self.repr_format(mod_config=mod_config, template=template)
filename: str = self.get_filename(mod_config=mod_config)
flags: int = (
(0x04 if hidden else 0) |
(0x01 if self.is_new(mod_config) else 0)
)
return (
f'{category} ' # category (is the track hidden, visible, an arena, ...)
f'{self.music}; ' # music
f'{self.special}; ' # property of the tracks
f'{flags:#04x}; ' # lecode flags
f'{filename!r}; ' # filename
f'{name!r}; ' # name of the track in the menu
f'{filename!r}\n' # unique identifier for each track
)

View file

@ -0,0 +1,35 @@
from source.mkw.Track.AbstractTrack import AbstractTrack
ModConfig: any
class CustomTrack(AbstractTrack):
"""
Represent a custom track
"""
def __repr__(self):
return f"<{self.__class__.__name__} name={getattr(self, 'name', '/')} tags={getattr(self, 'tags', '/')}>"
@classmethod
def from_dict(cls, track_dict: dict) -> "Track | TrackGroup":
"""
create a track from a dict, or create a track group is it is a group
:param track_dict: dict containing the track information
:return: Track
"""
if "group" in track_dict:
from source.mkw.TrackGroup import TrackGroup
return TrackGroup.from_dict(track_dict)
return cls(**track_dict)
def get_filename(self, mod_config: "ModConfig") -> str:
return self.repr_format(mod_config=mod_config, template=mod_config.track_file_template)
def is_new(self, mod_config: "ModConfig") -> bool:
"""
Return if the track should be considered as new for random selection
:param mod_config: mod configuration
:return: is the track new
"""
return mod_config.safe_eval(mod_config.global_settings["replace_random_new"].value, env={"track": self}) is True

View file

@ -0,0 +1,9 @@
from source.mkw.Track.AbstractTrack import AbstractTrack
class DefaultTrack(AbstractTrack):
def get_filename(self, mod_config: "ModConfig") -> str:
return "beginner_course"
def is_new(self, mod_config: "ModConfig") -> bool:
return False

View file

@ -0,0 +1 @@
from source.mkw.Track import CustomTrack, DefaultTrack, AbstractTrack

View file

@ -28,11 +28,11 @@ class TrackGroup:
:param group_dict: dict containing the track information :param group_dict: dict containing the track information
:return: TrackGroup or Track :return: TrackGroup or Track
""" """
from source.mkw.Track import Track from source.mkw.Track.CustomTrack import CustomTrack
if "group" not in group_dict: return Track.from_dict(group_dict) if "group" not in group_dict: return CustomTrack.from_dict(group_dict)
return cls( return cls(
tracks=[Track.from_dict(track) for track in group_dict["group"]], tracks=[CustomTrack.from_dict(track) for track in group_dict["group"]],
tags=group_dict.get("tags"), tags=group_dict.get("tags"),
name=group_dict.get("name"), name=group_dict.get("name"),
) )