Core: Add settings API ("auto settings") for host.yaml (#1871)

* Add settings API ("auto settings") for host.yaml

* settings: no BOM when saving

* settings: fix saving / groups resetting themselves

* settings: fix AutoWorldRegister import

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>

* Lufia2: settings: clean up imports

* settings: more consistent class naming

* Docs: update world api for settings api refactor

* settings: fix access from World instance

* settings: update migration timeline

* Docs: Apply suggestions from code review

Co-authored-by: Zach Parks <zach@alliware.com>

* Settings: correctly resolve .exe in UserPath and LocalPath

---------

Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: Zach Parks <zach@alliware.com>
This commit is contained in:
black-sliver
2023-07-05 22:39:35 +02:00
committed by GitHub
parent d8a8997684
commit 827444f5a4
34 changed files with 1455 additions and 412 deletions

View File

@@ -14,12 +14,23 @@ if TYPE_CHECKING:
import random
from BaseClasses import MultiWorld, Item, Location, Tutorial
from . import GamesPackage
from settings import Group
class AutoWorldRegister(type):
world_types: Dict[str, Type[World]] = {}
__file__: str
zip_path: Optional[str]
settings_key: str
__settings: Any
@property
def settings(cls) -> Any: # actual type is defined in World
# lazy loading + caching to minimize runtime cost
if cls.__settings is None:
from settings import get_settings
cls.__settings = get_settings()[cls.settings_key]
return cls.__settings
def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister:
if "web" in dct:
@@ -61,6 +72,11 @@ class AutoWorldRegister(type):
new_class.__file__ = sys.modules[new_class.__module__].__file__
if ".apworld" in new_class.__file__:
new_class.zip_path = pathlib.Path(new_class.__file__).parents[1]
if "settings_key" not in dct:
mod_name = new_class.__module__
world_folder_name = mod_name[7:].lower() if mod_name.startswith("worlds.") else mod_name.lower()
new_class.settings_key = world_folder_name + "_options"
new_class.__settings = None
return new_class
@@ -207,6 +223,11 @@ class World(metaclass=AutoWorldRegister):
random: random.Random
"""This world's random object. Should be used for any randomization needed in world for this player slot."""
settings_key: ClassVar[str]
"""name of the section in host.yaml for world-specific settings, will default to {folder}_options"""
settings: ClassVar[Optional["Group"]]
"""loaded settings from host.yaml"""
zip_path: ClassVar[Optional[pathlib.Path]] = None
"""If loaded from a .apworld, this is the Path to it."""
__file__: ClassVar[str]
@@ -216,6 +237,11 @@ class World(metaclass=AutoWorldRegister):
self.multiworld = multiworld
self.player = player
def __getattr__(self, item: str) -> Any:
if item == "settings":
return self.__class__.settings
raise AttributeError
# overridable methods that get called by Main.py, sorted by execution order
# can also be implemented as a classmethod and called "stage_<original_name>",
# in that case the MultiWorld object is passed as an argument and it gets called once for the entire multiworld.