| 
									
										
										
										
											2021-06-29 03:49:29 +02:00
										 |  |  | import importlib | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2022-10-01 02:47:31 +02:00
										 |  |  | import sys | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | import typing | 
					
						
							| 
									
										
										
										
											2022-10-01 02:47:31 +02:00
										 |  |  | import warnings | 
					
						
							|  |  |  | import zipimport | 
					
						
							| 
									
										
										
										
											2021-02-26 21:03:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | class GamesData(typing.TypedDict): | 
					
						
							|  |  |  |     item_name_groups: typing.Dict[str, typing.List[str]] | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     item_name_to_id: typing.Dict[str, int] | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |     location_name_groups: typing.Dict[str, typing.List[str]] | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     location_name_to_id: typing.Dict[str, int] | 
					
						
							|  |  |  |     version: int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | class GamesPackage(GamesData, total=False): | 
					
						
							|  |  |  |     checksum: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  | class DataPackage(typing.TypedDict): | 
					
						
							|  |  |  |     games: typing.Dict[str, GamesPackage] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | class WorldSource(typing.NamedTuple): | 
					
						
							|  |  |  |     path: str  # typically relative path from this module | 
					
						
							|  |  |  |     is_zip: bool = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 21:24:47 +01:00
										 |  |  |     def __repr__(self): | 
					
						
							|  |  |  |         return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip})" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02: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): | 
					
						
							| 
									
										
										
										
											2022-09-18 13:02:05 +02:00
										 |  |  |     # prevent loading of __pycache__ and allow _* for non-world folders, disable files/folders starting with "." | 
					
						
							|  |  |  |     if not file.name.startswith(("_", ".")): | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |         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)) | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-29 03:49:29 +02:00
										 |  |  | # import all submodules to trigger AutoWorldRegister | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | world_sources.sort() | 
					
						
							|  |  |  | for world_source in world_sources: | 
					
						
							| 
									
										
										
										
											2023-03-20 21:24:47 +01:00
										 |  |  |     try: | 
					
						
							|  |  |  |         if world_source.is_zip: | 
					
						
							|  |  |  |             importer = zipimport.zipimporter(os.path.join(folder, world_source.path)) | 
					
						
							|  |  |  |             if hasattr(importer, "find_spec"):  # new in Python 3.10 | 
					
						
							|  |  |  |                 spec = importer.find_spec(world_source.path.split(".", 1)[0]) | 
					
						
							|  |  |  |                 mod = importlib.util.module_from_spec(spec) | 
					
						
							|  |  |  |             else:  # TODO: remove with 3.8 support | 
					
						
							|  |  |  |                 mod = importer.load_module(world_source.path.split(".", 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".{world_source.path}", "worlds") | 
					
						
							|  |  |  |     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 {world_source}:", file=file_like) | 
					
						
							|  |  |  |         traceback.print_exc(file=file_like) | 
					
						
							|  |  |  |         file_like.seek(0) | 
					
						
							|  |  |  |         import logging | 
					
						
							|  |  |  |         logging.exception(file_like.read()) | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | lookup_any_item_id_to_name = {} | 
					
						
							|  |  |  | lookup_any_location_id_to_name = {} | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  | games: typing.Dict[str, GamesPackage] = {} | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | from .AutoWorld import AutoWorldRegister | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | # Build the data package for each game. | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | for world_name, world in AutoWorldRegister.world_types.items(): | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |     games[world_name] = world.get_data_package_data() | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  |     lookup_any_item_id_to_name.update(world.item_id_to_name) | 
					
						
							|  |  |  |     lookup_any_location_id_to_name.update(world.location_id_to_name) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  | network_data_package: DataPackage = { | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  |     "games": games, | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-08-22 04:22:34 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | # 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 09:10:27 +02:00
										 |  |  |     import logging | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-18 04:53:09 +01: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]}") |