| 
									
										
										
										
											2021-06-29 03:49:29 +02:00
										 |  |  | import importlib | 
					
						
							|  |  |  | import os | 
					
						
							| 
									
										
										
										
											2022-10-01 02:47:31 +02:00
										 |  |  | import sys | 
					
						
							|  |  |  | import warnings | 
					
						
							|  |  |  | import zipimport | 
					
						
							| 
									
										
										
										
											2024-02-04 09:09:07 +01:00
										 |  |  | import time | 
					
						
							|  |  |  | import dataclasses | 
					
						
							|  |  |  | from typing import Dict, List, TypedDict, Optional | 
					
						
							| 
									
										
										
										
											2021-02-26 21:03:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  | from Utils import local_path, user_path | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-04 10:26:51 +01:00
										 |  |  | local_folder = os.path.dirname(__file__) | 
					
						
							|  |  |  | user_folder = user_path("worlds") if user_path() != local_path() else None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  | __all__ = { | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     "network_data_package", | 
					
						
							|  |  |  |     "AutoWorldRegister", | 
					
						
							|  |  |  |     "world_sources", | 
					
						
							| 
									
										
										
										
											2023-11-04 10:26:51 +01:00
										 |  |  |     "local_folder", | 
					
						
							|  |  |  |     "user_folder", | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  |     "GamesPackage", | 
					
						
							|  |  |  |     "DataPackage", | 
					
						
							| 
									
										
										
										
											2024-03-28 22:21:56 +01:00
										 |  |  |     "failed_world_loads", | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-28 22:21:56 +01:00
										 |  |  | failed_world_loads: List[str] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  | class GamesPackage(TypedDict, total=False): | 
					
						
							|  |  |  |     item_name_groups: Dict[str, List[str]] | 
					
						
							|  |  |  |     item_name_to_id: Dict[str, int] | 
					
						
							|  |  |  |     location_name_groups: Dict[str, List[str]] | 
					
						
							|  |  |  |     location_name_to_id: Dict[str, int] | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |     checksum: str | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  | class DataPackage(TypedDict): | 
					
						
							|  |  |  |     games: Dict[str, GamesPackage] | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-04 09:09:07 +01:00
										 |  |  | @dataclasses.dataclass(order=True) | 
					
						
							|  |  |  | class WorldSource: | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     path: str  # typically relative path from this module | 
					
						
							|  |  |  |     is_zip: bool = False | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |     relative: bool = True  # relative to regular world import folder | 
					
						
							| 
									
										
										
										
											2024-02-04 09:09:07 +01:00
										 |  |  |     time_taken: Optional[float] = None | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-04 10:26:51 +01:00
										 |  |  |     def __repr__(self) -> str: | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |         return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     def resolved_path(self) -> str: | 
					
						
							|  |  |  |         if self.relative: | 
					
						
							| 
									
										
										
										
											2023-11-04 10:26:51 +01:00
										 |  |  |             return os.path.join(local_folder, self.path) | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |         return self.path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def load(self) -> bool: | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2024-02-04 09:09:07 +01:00
										 |  |  |             start = time.perf_counter() | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |             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]) | 
					
						
							| 
									
										
										
										
											2023-11-04 10:26:51 +01:00
										 |  |  |                     assert spec, f"{self.path} is not a loadable module" | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |                     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") | 
					
						
							| 
									
										
										
										
											2024-02-04 09:09:07 +01:00
										 |  |  |             self.time_taken = time.perf_counter()-start | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |             return True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-04 10:26:51 +01:00
										 |  |  |         except Exception: | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |             # 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()) | 
					
						
							| 
									
										
										
										
											2024-03-28 22:21:56 +01:00
										 |  |  |             failed_world_loads.append(os.path.basename(self.path).rsplit(".", 1)[0]) | 
					
						
							| 
									
										
										
										
											2023-06-20 01:01:18 +02:00
										 |  |  |             return False | 
					
						
							| 
									
										
										
										
											2023-03-20 21:24:47 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | # find potential world containers, currently folders and zip-importable .apworld's | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  | world_sources: List[WorldSource] = [] | 
					
						
							| 
									
										
										
										
											2023-11-04 10:26:51 +01:00
										 |  |  | for folder in (folder for folder in (user_folder, local_folder) if folder): | 
					
						
							|  |  |  |     relative = folder == local_folder | 
					
						
							|  |  |  |     for entry in os.scandir(folder): | 
					
						
							|  |  |  |         # prevent loading of __pycache__ and allow _* for non-world folders, disable files/folders starting with "." | 
					
						
							|  |  |  |         if not entry.name.startswith(("_", ".")): | 
					
						
							|  |  |  |             file_name = entry.name if relative else os.path.join(folder, entry.name) | 
					
						
							|  |  |  |             if entry.is_dir(): | 
					
						
							|  |  |  |                 world_sources.append(WorldSource(file_name, relative=relative)) | 
					
						
							|  |  |  |             elif entry.is_file() and entry.name.endswith(".apworld"): | 
					
						
							|  |  |  |                 world_sources.append(WorldSource(file_name, is_zip=True, relative=relative)) | 
					
						
							| 
									
										
										
										
											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-06-20 01:01:18 +02:00
										 |  |  |     world_source.load() | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | # Build the data package for each game. | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  | from .AutoWorld import AutoWorldRegister | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  | network_data_package: DataPackage = { | 
					
						
							| 
									
										
										
										
											2023-11-18 12:29:35 -06:00
										 |  |  |     "games": {world_name: world.get_data_package_data() for world_name, world in AutoWorldRegister.world_types.items()}, | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | } |