Archipelago/worlds/__init__.py

126 lines
4.3 KiB
Python
Raw Normal View History

import importlib
import os
import sys
import typing
import warnings
import zipimport
folder = os.path.dirname(__file__)
__all__ = {
"lookup_any_item_id_to_name",
"lookup_any_location_id_to_name",
"network_data_package",
"AutoWorldRegister",
"world_sources",
"folder",
}
if typing.TYPE_CHECKING:
from .AutoWorld import World
class GamesData(typing.TypedDict):
item_name_groups: typing.Dict[str, typing.List[str]]
item_name_to_id: typing.Dict[str, int]
location_name_groups: typing.Dict[str, typing.List[str]]
location_name_to_id: typing.Dict[str, int]
version: int
class GamesPackage(GamesData, total=False):
checksum: str
class DataPackage(typing.TypedDict):
games: typing.Dict[str, GamesPackage]
class WorldSource(typing.NamedTuple):
path: str # typically relative path from this module
is_zip: bool = False
relative: bool = True # relative to regular world import folder
2023-03-20 20:24:47 +00:00
def __repr__(self):
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"
@property
def resolved_path(self) -> str:
if self.relative:
return os.path.join(folder, self.path)
return self.path
def load(self) -> bool:
try:
if self.is_zip:
importer = zipimport.zipimporter(self.resolved_path)
if hasattr(importer, "find_spec"): # new in Python 3.10
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
mod = importlib.util.module_from_spec(spec)
else: # TODO: remove with 3.8 support
mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0])
mod.__package__ = f"worlds.{mod.__package__}"
mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="__package__ != __spec__.parent")
# Found no equivalent for < 3.10
if hasattr(importer, "exec_module"):
importer.exec_module(mod)
else:
importlib.import_module(f".{self.path}", "worlds")
return True
except Exception as e:
# A single world failing can still mean enough is working for the user, log and carry on
import traceback
import io
file_like = io.StringIO()
print(f"Could not load world {self}:", file=file_like)
traceback.print_exc(file=file_like)
file_like.seek(0)
import logging
logging.exception(file_like.read())
return False
2023-03-20 20:24:47 +00:00
# find potential world containers, currently folders and zip-importable .apworld's
world_sources: typing.List[WorldSource] = []
file: os.DirEntry # for me (Berserker) at least, PyCharm doesn't seem to infer the type correctly
for file in os.scandir(folder):
# prevent loading of __pycache__ and allow _* for non-world folders, disable files/folders starting with "."
if not file.name.startswith(("_", ".")):
if file.is_dir():
world_sources.append(WorldSource(file.name))
elif file.is_file() and file.name.endswith(".apworld"):
world_sources.append(WorldSource(file.name, is_zip=True))
# import all submodules to trigger AutoWorldRegister
world_sources.sort()
for world_source in world_sources:
world_source.load()
lookup_any_item_id_to_name = {}
lookup_any_location_id_to_name = {}
games: typing.Dict[str, GamesPackage] = {}
from .AutoWorld import AutoWorldRegister
# Build the data package for each game.
for world_name, world in AutoWorldRegister.world_types.items():
games[world_name] = world.get_data_package_data()
lookup_any_item_id_to_name.update(world.item_id_to_name)
lookup_any_location_id_to_name.update(world.location_id_to_name)
network_data_package: DataPackage = {
"games": games,
}
# Set entire datapackage to version 0 if any of them are set to 0
if any(not world.data_version for world in AutoWorldRegister.world_types.values()):
2021-09-26 07:10:27 +00:00
import logging
2022-03-18 03:53:09 +00:00
logging.warning(f"Datapackage is in custom mode. Custom Worlds: "
f"{[world for world in AutoWorldRegister.world_types.values() if not world.data_version]}")