| 
									
										
										
										
											2021-07-12 15:33:20 +02:00
										 |  |  | from __future__ import annotations | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | import hashlib | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  | import logging | 
					
						
							| 
									
										
										
										
											2022-08-18 00:27:37 +02:00
										 |  |  | import pathlib | 
					
						
							| 
									
										
										
										
											2024-03-10 12:47:45 -05:00
										 |  |  | import random | 
					
						
							| 
									
										
										
										
											2023-11-10 22:06:54 -08:00
										 |  |  | import re | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | import sys | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  | import time | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  | from dataclasses import make_dataclass | 
					
						
							| 
									
										
										
										
											2024-02-28 17:22:42 -08:00
										 |  |  | from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, | 
					
						
							|  |  |  |                     Optional, Set, TextIO, Tuple, TYPE_CHECKING, Type, Union) | 
					
						
							| 
									
										
										
										
											2021-07-12 13:54:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  | from Options import PerGameCommonOptions | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | from BaseClasses import CollectionState | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if TYPE_CHECKING: | 
					
						
							| 
									
										
										
										
											2023-07-02 09:23:47 -07:00
										 |  |  |     import random | 
					
						
							| 
									
										
										
										
											2024-02-23 10:32:14 +01:00
										 |  |  |     from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |     from . import GamesPackage | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     from settings import Group | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  | perf_logger = logging.getLogger("performance") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | class AutoWorldRegister(type): | 
					
						
							| 
									
										
										
										
											2022-09-28 14:54:10 -07:00
										 |  |  |     world_types: Dict[str, Type[World]] = {} | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |     __file__: str | 
					
						
							|  |  |  |     zip_path: Optional[str] | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     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 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-03 22:14:03 +02:00
										 |  |  |     def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister: | 
					
						
							| 
									
										
										
										
											2022-04-03 19:08:50 +02:00
										 |  |  |         if "web" in dct: | 
					
						
							|  |  |  |             assert isinstance(dct["web"], WebWorld), "WebWorld has to be instantiated." | 
					
						
							| 
									
										
										
										
											2021-07-12 18:47:58 +02:00
										 |  |  |         # filter out any events | 
					
						
							|  |  |  |         dct["item_name_to_id"] = {name: id for name, id in dct["item_name_to_id"].items() if id} | 
					
						
							|  |  |  |         dct["location_name_to_id"] = {name: id for name, id in dct["location_name_to_id"].items() if id} | 
					
						
							|  |  |  |         # build reverse lookups | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  |         dct["item_id_to_name"] = {code: name for name, code in dct["item_name_to_id"].items()} | 
					
						
							|  |  |  |         dct["location_id_to_name"] = {code: name for name, code in dct["location_name_to_id"].items()} | 
					
						
							| 
									
										
										
										
											2021-07-29 20:27:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # build rest | 
					
						
							|  |  |  |         dct["item_names"] = frozenset(dct["item_name_to_id"]) | 
					
						
							| 
									
										
										
										
											2022-08-23 23:33:30 +02:00
										 |  |  |         dct["item_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set | 
					
						
							|  |  |  |                                    in dct.get("item_name_groups", {}).items()} | 
					
						
							| 
									
										
										
										
											2022-02-05 16:55:11 +01:00
										 |  |  |         dct["item_name_groups"]["Everything"] = dct["item_names"] | 
					
						
							| 
									
										
										
										
											2023-11-10 22:06:54 -08:00
										 |  |  |         dct["item_descriptions"] = {name: _normalize_description(description) for name, description | 
					
						
							|  |  |  |                                     in dct.get("item_descriptions", {}).items()} | 
					
						
							|  |  |  |         dct["item_descriptions"]["Everything"] = "All items in the entire game." | 
					
						
							| 
									
										
										
										
											2021-07-29 20:27:41 +02:00
										 |  |  |         dct["location_names"] = frozenset(dct["location_name_to_id"]) | 
					
						
							| 
									
										
										
										
											2023-03-25 13:35:19 -05:00
										 |  |  |         dct["location_name_groups"] = {group_name: frozenset(group_set) for group_name, group_set | 
					
						
							|  |  |  |                                        in dct.get("location_name_groups", {}).items()} | 
					
						
							|  |  |  |         dct["location_name_groups"]["Everywhere"] = dct["location_names"] | 
					
						
							| 
									
										
										
										
											2022-01-16 02:20:37 +01:00
										 |  |  |         dct["all_item_and_group_names"] = frozenset(dct["item_names"] | set(dct.get("item_name_groups", {}))) | 
					
						
							| 
									
										
										
										
											2023-11-10 22:06:54 -08:00
										 |  |  |         dct["location_descriptions"] = {name: _normalize_description(description) for name, description | 
					
						
							|  |  |  |                                     in dct.get("location_descriptions", {}).items()} | 
					
						
							|  |  |  |         dct["location_descriptions"]["Everywhere"] = "All locations in the entire game." | 
					
						
							| 
									
										
										
										
											2021-07-29 20:27:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-08 11:16:36 +02:00
										 |  |  |         # move away from get_required_client_version function | 
					
						
							|  |  |  |         if "game" in dct: | 
					
						
							|  |  |  |             assert "get_required_client_version" not in dct, f"{name}: required_client_version is an attribute now" | 
					
						
							|  |  |  |         # set minimum required_client_version from bases | 
					
						
							|  |  |  |         if "required_client_version" in dct and bases: | 
					
						
							|  |  |  |             for base in bases: | 
					
						
							|  |  |  |                 if "required_client_version" in base.__dict__: | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |                     dct["required_client_version"] = max(dct["required_client_version"], | 
					
						
							|  |  |  |                                                          base.__dict__["required_client_version"]) | 
					
						
							| 
									
										
										
										
											2022-04-08 11:16:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         # create missing options_dataclass from legacy option_definitions | 
					
						
							|  |  |  |         # TODO - remove this once all worlds use options dataclasses | 
					
						
							|  |  |  |         if "options_dataclass" not in dct and "option_definitions" in dct: | 
					
						
							| 
									
										
										
										
											2023-12-16 15:21:05 -06:00
										 |  |  |             # TODO - switch to deprecate after a version | 
					
						
							|  |  |  |             if __debug__: | 
					
						
							| 
									
										
										
										
											2024-01-13 20:04:12 -06:00
										 |  |  |                 logging.warning(f"{name} Assigned options through option_definitions which is now deprecated. " | 
					
						
							|  |  |  |                                 "Please use options_dataclass instead.") | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |             dct["options_dataclass"] = make_dataclass(f"{name}Options", dct["option_definitions"].items(), | 
					
						
							|  |  |  |                                                       bases=(PerGameCommonOptions,)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-12 18:47:58 +02:00
										 |  |  |         # construct class | 
					
						
							| 
									
										
										
										
											2022-05-03 22:14:03 +02:00
										 |  |  |         new_class = super().__new__(mcs, name, bases, dct) | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         if "game" in dct: | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |             if dct["game"] in AutoWorldRegister.world_types: | 
					
						
							|  |  |  |                 raise RuntimeError(f"""Game {dct["game"]} already registered.""") | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |             AutoWorldRegister.world_types[dct["game"]] = new_class | 
					
						
							| 
									
										
										
										
											2022-05-30 01:11:01 -04:00
										 |  |  |         new_class.__file__ = sys.modules[new_class.__module__].__file__ | 
					
						
							| 
									
										
										
										
											2022-08-18 00:27:37 +02:00
										 |  |  |         if ".apworld" in new_class.__file__: | 
					
						
							|  |  |  |             new_class.zip_path = pathlib.Path(new_class.__file__).parents[1] | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |         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 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         return new_class | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  | class AutoLogicRegister(type): | 
					
						
							| 
									
										
										
										
											2022-08-18 00:27:37 +02:00
										 |  |  |     def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoLogicRegister: | 
					
						
							|  |  |  |         new_class = super().__new__(mcs, name, bases, dct) | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |         function: Callable[..., Any] | 
					
						
							| 
									
										
										
										
											2021-07-16 12:23:05 +02:00
										 |  |  |         for item_name, function in dct.items(): | 
					
						
							| 
									
										
										
										
											2022-02-17 07:07:34 +01:00
										 |  |  |             if item_name == "copy_mixin": | 
					
						
							|  |  |  |                 CollectionState.additional_copy_functions.append(function) | 
					
						
							|  |  |  |             elif item_name == "init_mixin": | 
					
						
							|  |  |  |                 CollectionState.additional_init_functions.append(function) | 
					
						
							|  |  |  |             elif not item_name.startswith("__"): | 
					
						
							| 
									
										
										
										
											2021-07-16 12:23:05 +02:00
										 |  |  |                 if hasattr(CollectionState, item_name): | 
					
						
							|  |  |  |                     raise Exception(f"Name conflict on Logic Mixin {name} trying to overwrite {item_name}") | 
					
						
							|  |  |  |                 setattr(CollectionState, item_name, function) | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  |         return new_class | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  | def _timed_call(method: Callable[..., Any], *args: Any, | 
					
						
							|  |  |  |                 multiworld: Optional["MultiWorld"] = None, player: Optional[int] = None) -> Any: | 
					
						
							|  |  |  |     start = time.perf_counter() | 
					
						
							|  |  |  |     ret = method(*args) | 
					
						
							|  |  |  |     taken = time.perf_counter() - start | 
					
						
							|  |  |  |     if taken > 1.0: | 
					
						
							|  |  |  |         if player and multiworld: | 
					
						
							| 
									
										
										
										
											2023-11-13 06:49:31 +01:00
										 |  |  |             perf_logger.info(f"Took {taken:.4f} seconds in {method.__qualname__} for player {player}, " | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  |                              f"named {multiworld.player_name[player]}.") | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2023-11-13 06:49:31 +01:00
										 |  |  |             perf_logger.info(f"Took {taken:.4f} seconds in {method.__qualname__}.") | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  |     return ret | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-30 20:45:22 -06:00
										 |  |  | def call_single(multiworld: "MultiWorld", method_name: str, player: int, *args: Any) -> Any: | 
					
						
							|  |  |  |     method = getattr(multiworld.worlds[player], method_name) | 
					
						
							| 
									
										
										
										
											2023-07-25 02:18:39 +02:00
										 |  |  |     try: | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  |         ret = _timed_call(method, *args, multiworld=multiworld, player=player) | 
					
						
							| 
									
										
										
										
											2023-07-25 02:18:39 +02:00
										 |  |  |     except Exception as e: | 
					
						
							|  |  |  |         message = f"Exception in {method} for player {player}, named {multiworld.player_name[player]}." | 
					
						
							|  |  |  |         if sys.version_info >= (3, 11, 0): | 
					
						
							|  |  |  |             e.add_note(message)  # PEP 678 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             logging.error(message) | 
					
						
							|  |  |  |         raise e | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         return ret | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-30 20:45:22 -06:00
										 |  |  | def call_all(multiworld: "MultiWorld", method_name: str, *args: Any) -> None: | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     world_types: Set[AutoWorldRegister] = set() | 
					
						
							| 
									
										
										
										
											2022-11-30 20:45:22 -06:00
										 |  |  |     for player in multiworld.player_ids: | 
					
						
							|  |  |  |         prev_item_count = len(multiworld.itempool) | 
					
						
							|  |  |  |         world_types.add(multiworld.worlds[player].__class__) | 
					
						
							|  |  |  |         call_single(multiworld, method_name, player, *args) | 
					
						
							| 
									
										
										
										
											2022-10-20 10:42:33 +02:00
										 |  |  |         if __debug__: | 
					
						
							| 
									
										
										
										
											2022-11-30 20:45:22 -06:00
										 |  |  |             new_items = multiworld.itempool[prev_item_count:] | 
					
						
							| 
									
										
										
										
											2022-10-20 10:42:33 +02:00
										 |  |  |             for i, item in enumerate(new_items): | 
					
						
							|  |  |  |                 for other in new_items[i+1:]: | 
					
						
							|  |  |  |                     assert item is not other, ( | 
					
						
							| 
									
										
										
										
											2022-11-30 20:45:22 -06:00
										 |  |  |                         f"Duplicate item reference of \"{item.name}\" in \"{multiworld.worlds[player].game}\" " | 
					
						
							|  |  |  |                         f"of player \"{multiworld.player_name[player]}\". Please make a copy instead.") | 
					
						
							| 
									
										
										
										
											2021-10-06 11:32:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  |     call_stage(multiworld, method_name, *args) | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-30 20:45:22 -06:00
										 |  |  | def call_stage(multiworld: "MultiWorld", method_name: str, *args: Any) -> None: | 
					
						
							|  |  |  |     world_types = {multiworld.worlds[player].__class__ for player in multiworld.player_ids} | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  |     for world_type in sorted(world_types, key=lambda world: world.__name__): | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |         stage_callable = getattr(world_type, f"stage_{method_name}", None) | 
					
						
							|  |  |  |         if stage_callable: | 
					
						
							| 
									
										
										
										
											2023-10-30 04:06:40 +01:00
										 |  |  |             _timed_call(stage_callable, multiworld, *args) | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-20 21:54:00 +01:00
										 |  |  | class WebWorld: | 
					
						
							|  |  |  |     """Webhost integration""" | 
					
						
							| 
									
										
										
										
											2023-01-02 19:26:34 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-23 19:20:08 -05:00
										 |  |  |     options_page: Union[bool, str] = True | 
					
						
							| 
									
										
										
										
											2022-08-22 16:50:16 -05:00
										 |  |  |     """display a settings page. Can be a link to a specific page or external tool.""" | 
					
						
							| 
									
										
										
										
											2023-01-02 19:26:34 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-11 13:05:53 -05:00
										 |  |  |     game_info_languages: List[str] = ['en'] | 
					
						
							| 
									
										
										
										
											2022-08-22 16:50:16 -05:00
										 |  |  |     """docs folder will be scanned for game info pages using this list in the format '{language}_{game_name}.md'""" | 
					
						
							| 
									
										
										
										
											2022-05-11 13:05:53 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     tutorials: List["Tutorial"] | 
					
						
							| 
									
										
										
										
											2022-08-22 16:50:16 -05:00
										 |  |  |     """docs folder will also be scanned for tutorial guides. Each Tutorial class is to be used for one guide.""" | 
					
						
							| 
									
										
										
										
											2022-05-11 13:05:53 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
											  
											
												Website Style Upgrade (#353)
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Seed download page improvements
* Add styles to weighted-settings page
* Minor adjustments to styles
* Revert base theme to grass
* Add more items to ArchipIDLE
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Seed download page improvements
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Add styles to weighted-settings page
* Minor adjustments to styles
* Revert base theme to grass
* Add more items to ArchipIDLE
* Improve Archipidle item name
* [WebHost] Update background images, waiting on jungle.png, added partyTime theme
* [WebHost] Fix tab ordering on landing page, remove islands on screen scale, fix tutorial page width scaling
* [WebHost] Final touches to WebHost
* Improve get_world_theme function, add partyTime theme to ArchipIDLE WebWorld
* Remove sending_visible from AutoWorld
* AP Ocarina of Time Client (#352)
* Core: update jinja (#351)
* some typing and cleaning, mostly in Fill.py (#349)
* some typing and cleaning, mostly in Fill.py
* address missing Option types
* resolve a few TODOs discussed in pull request
* SM: Optimize a bit (#350)
* SM: Optimize a bit
* SM: init bosses only once
* New World Order (#355)
* Core: update jinja
* SM: Optimize a bit
* AutoWorld: import worlds in alphabetical order, to be predictable rather than arbitrary
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
* Remove references to Z5Client in English OoT setup guide
* Prevent markdown code blocks from overflowing their container
Co-authored-by: espeon65536 <81029175+espeon65536@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
											
										 
											2022-03-28 20:12:17 -04:00
										 |  |  |     theme = "grass" | 
					
						
							| 
									
										
										
										
											2022-08-22 16:50:16 -05:00
										 |  |  |     """Choose a theme for you /game/* pages.
 | 
					
						
							|  |  |  |     Available: dirt, grass, grassFlowers, ice, jungle, ocean, partyTime, stone"""
 | 
					
						
							| 
									
										
											  
											
												Website Style Upgrade (#353)
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Seed download page improvements
* Add styles to weighted-settings page
* Minor adjustments to styles
* Revert base theme to grass
* Add more items to ArchipIDLE
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Seed download page improvements
* [WebHost] Update WebHost to include modular themes system, remove unused and outdated assets
* Landing Page Updates
* Markdown updates, colors coming later
* Remove testing theme from FF1
* Color updates for markdown styles
* Updates to generated pages, so many updates
* Add styles to weighted-settings page
* Minor adjustments to styles
* Revert base theme to grass
* Add more items to ArchipIDLE
* Improve Archipidle item name
* [WebHost] Update background images, waiting on jungle.png, added partyTime theme
* [WebHost] Fix tab ordering on landing page, remove islands on screen scale, fix tutorial page width scaling
* [WebHost] Final touches to WebHost
* Improve get_world_theme function, add partyTime theme to ArchipIDLE WebWorld
* Remove sending_visible from AutoWorld
* AP Ocarina of Time Client (#352)
* Core: update jinja (#351)
* some typing and cleaning, mostly in Fill.py (#349)
* some typing and cleaning, mostly in Fill.py
* address missing Option types
* resolve a few TODOs discussed in pull request
* SM: Optimize a bit (#350)
* SM: Optimize a bit
* SM: init bosses only once
* New World Order (#355)
* Core: update jinja
* SM: Optimize a bit
* AutoWorld: import worlds in alphabetical order, to be predictable rather than arbitrary
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
* Remove references to Z5Client in English OoT setup guide
* Prevent markdown code blocks from overflowing their container
Co-authored-by: espeon65536 <81029175+espeon65536@users.noreply.github.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
Co-authored-by: Hussein Farran <hmfarran@gmail.com>
											
										 
											2022-03-28 20:12:17 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-12 14:37:05 -07:00
										 |  |  |     bug_report_page: Optional[str] | 
					
						
							| 
									
										
										
										
											2022-08-22 16:50:16 -05:00
										 |  |  |     """display a link to a bug report page, most likely a link to a GitHub issue page.""" | 
					
						
							| 
									
										
										
										
											2022-04-12 14:37:05 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-16 04:37:06 -06:00
										 |  |  |     options_presets: Dict[str, Dict[str, Any]] = {} | 
					
						
							|  |  |  |     """A dictionary containing a collection of developer-defined game option presets.""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-20 21:54:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | class World(metaclass=AutoWorldRegister): | 
					
						
							|  |  |  |     """A World object encompasses a game's Items, Locations, Rules and additional data or functionality required.
 | 
					
						
							|  |  |  |     A Game should have its own subclass of World in which it defines the required data structures."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |     options_dataclass: ClassVar[Type[PerGameCommonOptions]] = PerGameCommonOptions | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """link your Options mapping""" | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |     options: PerGameCommonOptions | 
					
						
							|  |  |  |     """resulting options for the player of this world""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     game: ClassVar[str] | 
					
						
							|  |  |  |     """name the game""" | 
					
						
							|  |  |  |     topology_present: ClassVar[bool] = False | 
					
						
							|  |  |  |     """indicate if world type has any meaningful layout/pathing""" | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     all_item_and_group_names: ClassVar[FrozenSet[str]] = frozenset() | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """gets automatically populated with all item and item group names""" | 
					
						
							| 
									
										
										
										
											2021-07-12 15:33:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     item_name_to_id: ClassVar[Dict[str, int]] = {} | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """map item names to their IDs""" | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     location_name_to_id: ClassVar[Dict[str, int]] = {} | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """map location names to their IDs""" | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     item_name_groups: ClassVar[Dict[str, Set[str]]] = {} | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """maps item group names to sets of items. Example: {"Weapons": {"Sword", "Bow"}}""" | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-10 22:06:54 -08:00
										 |  |  |     item_descriptions: ClassVar[Dict[str, str]] = {} | 
					
						
							|  |  |  |     """An optional map from item names (or item group names) to brief descriptions for users.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Individual newlines and indentation will be collapsed into spaces before these descriptions are | 
					
						
							|  |  |  |     displayed. This may cover only a subset of items. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-08 15:15:28 -06:00
										 |  |  |     location_name_groups: ClassVar[Dict[str, Set[str]]] = {} | 
					
						
							|  |  |  |     """maps location group names to sets of locations. Example: {"Sewer": {"Sewer Key Drop 1", "Sewer Key Drop 2"}}""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-10 22:06:54 -08:00
										 |  |  |     location_descriptions: ClassVar[Dict[str, str]] = {} | 
					
						
							|  |  |  |     """An optional map from location names (or location group names) to brief descriptions for users.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Individual newlines and indentation will be collapsed into spaces before these descriptions are | 
					
						
							|  |  |  |     displayed. This may cover only a subset of locations. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-06 01:19:58 +02:00
										 |  |  |     data_version: ClassVar[int] = 0 | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |     Increment this every time something in your world's names/id mappings changes. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     When this is set to 0, that world's DataPackage is considered in "testing mode", which signals to servers/clients | 
					
						
							|  |  |  |     that it should not be cached, and clients should request that world's DataPackage every connection. Not | 
					
						
							|  |  |  |     recommended for production-ready worlds. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Deprecated. Clients should utilize `checksum` to determine if DataPackage has changed since last connection and | 
					
						
							|  |  |  |     request a new DataPackage, if necessary. | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-08 11:16:36 +02:00
										 |  |  |     required_client_version: Tuple[int, int, int] = (0, 1, 6) | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     override this if changes to a world break forward-compatibility of the client | 
					
						
							|  |  |  |     The base version of (0, 1, 6) is provided for backwards compatibility and does *not* need to be updated in the | 
					
						
							|  |  |  |     future. Protocol level compatibility check moved to MultiServer.min_client_version. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2022-04-08 11:16:36 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     required_server_version: Tuple[int, int, int] = (0, 2, 4) | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """update this if the resulting multidata breaks forward-compatibility of the server""" | 
					
						
							| 
									
										
										
										
											2022-04-08 11:16:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     hint_blacklist: ClassVar[FrozenSet[str]] = frozenset() | 
					
						
							|  |  |  |     """any names that should not be hintable""" | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     hidden: ClassVar[bool] = False | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """Hide World Type from various views. Does not remove functionality.""" | 
					
						
							| 
									
										
										
										
											2021-08-27 20:46:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     web: ClassVar[WebWorld] = WebWorld() | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """see WebWorld for options""" | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |     multiworld: "MultiWorld" | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """autoset on creation. The MultiWorld object for the currently generating multiworld.""" | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  |     player: int | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """autoset on creation. The player number for this World""" | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     item_id_to_name: ClassVar[Dict[int, str]] | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """automatically generated reverse lookup of item id to name""" | 
					
						
							| 
									
										
										
										
											2022-11-28 02:25:53 +01:00
										 |  |  |     location_id_to_name: ClassVar[Dict[int, str]] | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     """automatically generated reverse lookup of location id to name""" | 
					
						
							| 
									
										
										
										
											2021-07-29 20:27:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     item_names: ClassVar[Set[str]] | 
					
						
							|  |  |  |     """set of all potential item names""" | 
					
						
							|  |  |  |     location_names: ClassVar[Set[str]] | 
					
						
							|  |  |  |     """set of all potential location names""" | 
					
						
							| 
									
										
										
										
											2021-07-29 20:27:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-02 05:50:14 -05:00
										 |  |  |     random: random.Random | 
					
						
							|  |  |  |     """This world's random object. Should be used for any randomization needed in world for this player slot.""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     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""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |     zip_path: ClassVar[Optional[pathlib.Path]] = None | 
					
						
							|  |  |  |     """If loaded from a .apworld, this is the Path to it.""" | 
					
						
							|  |  |  |     __file__: ClassVar[str] | 
					
						
							|  |  |  |     """path it was loaded from""" | 
					
						
							| 
									
										
										
										
											2022-02-20 21:54:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-30 20:45:22 -06:00
										 |  |  |     def __init__(self, multiworld: "MultiWorld", player: int): | 
					
						
							| 
									
										
										
										
											2024-03-10 01:18:25 -06:00
										 |  |  |         assert multiworld is not None | 
					
						
							| 
									
										
										
										
											2022-11-30 20:45:22 -06:00
										 |  |  |         self.multiworld = multiworld | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  |         self.player = player | 
					
						
							| 
									
										
										
										
											2024-03-10 12:47:45 -05:00
										 |  |  |         self.random = random.Random(multiworld.random.getrandbits(64)) | 
					
						
							|  |  |  |         multiworld.per_slot_randoms[player] = self.random | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-05 22:39:35 +02:00
										 |  |  |     def __getattr__(self, item: str) -> Any: | 
					
						
							|  |  |  |         if item == "settings": | 
					
						
							|  |  |  |             return self.__class__.settings | 
					
						
							|  |  |  |         raise AttributeError | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 15:51:50 +02:00
										 |  |  |     # overridable methods that get called by Main.py, sorted by execution order | 
					
						
							| 
									
										
										
										
											2021-11-06 16:17:10 +01:00
										 |  |  |     # can also be implemented as a classmethod and called "stage_<original_name>", | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |     # in that case the MultiWorld object is passed as an argument, and it gets called once for the entire multiworld. | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |     # An example of this can be found in alttp as stage_pre_fill | 
					
						
							| 
									
										
										
										
											2023-02-16 00:28:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 20:37:28 -05:00
										 |  |  |     @classmethod | 
					
						
							| 
									
										
										
										
											2023-02-16 00:28:02 +01:00
										 |  |  |     def stage_assert_generate(cls, multiworld: "MultiWorld") -> None: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Checks that a game is capable of generating, such as checking for some base file like a ROM. | 
					
						
							|  |  |  |         This gets called once per present world type. Not run for unittests since they don't produce output. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2022-04-29 20:37:28 -05:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def generate_early(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Run before any general steps of the MultiWorld other than options. Useful for getting and adjusting option | 
					
						
							|  |  |  |         results and determining layouts for entrance rando etc. start inventory gets pushed after this step. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2021-07-15 08:50:08 +02:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def create_regions(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |         """Method for creating and connecting regions for the World.""" | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  |         pass | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def create_items(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-01-13 19:15:35 -06:00
										 |  |  |         Method for creating and submitting items to the itempool. Items and Regions must *not* be created and submitted | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |         to the MultiWorld after this step. If items need to be placed during pre_fill use `get_prefill_items`. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2021-07-22 15:51:50 +02:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def set_rules(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |         """Method for setting the rules on the World's regions and locations.""" | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def generate_basic(self) -> None: | 
					
						
							| 
									
										
										
										
											2023-02-18 12:24:43 -06:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Useful for randomizing things that don't affect logic but are better to be determined before the output stage. | 
					
						
							|  |  |  |         i.e. checking what the player has marked as priority or randomizing enemies | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def pre_fill(self) -> None: | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |         """Optional method that is supposed to be used for special fill stages. This is run *after* plando.""" | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-16 00:28:02 +01:00
										 |  |  |     def fill_hook(self, | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |                   progitempool: List["Item"], | 
					
						
							| 
									
										
										
										
											2022-09-16 20:06:25 -04:00
										 |  |  |                   usefulitempool: List["Item"], | 
					
						
							|  |  |  |                   filleritempool: List["Item"], | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |                   fill_locations: List["Location"]) -> None: | 
					
						
							| 
									
										
										
										
											2023-02-16 00:28:02 +01:00
										 |  |  |         """Special method that gets called as part of distribute_items_restrictive (main fill).""" | 
					
						
							| 
									
										
										
										
											2021-08-10 09:03:44 +02:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def post_fill(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Optional Method that is called after regular fill. Can be used to do adjustments before output generation. | 
					
						
							|  |  |  |         This happens before progression balancing, so the items may not be in their final locations yet. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2021-08-30 01:16:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def generate_output(self, output_directory: str) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         This method gets called from a threadpool, do not use multiworld.random here. | 
					
						
							|  |  |  |         If you need any last-second randomization, use self.random instead. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         pass | 
					
						
							| 
									
										
										
										
											2021-06-27 00:23:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-28 17:22:42 -08:00
										 |  |  |     def fill_slot_data(self) -> Mapping[str, Any]:  # json of WebHostLib.models.Slot | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         What is returned from this function will be in the `slot_data` field | 
					
						
							| 
									
										
										
										
											2024-02-28 17:22:42 -08:00
										 |  |  |         in the `Connected` network package. | 
					
						
							|  |  |  |         It should be a `dict` with `str` keys, and should be serializable with json. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 14:54:10 -07:00
										 |  |  |         This is a way the generator can give custom data to the client. | 
					
						
							| 
									
										
										
										
											2022-12-09 17:54:29 -08:00
										 |  |  |         The client will receive this as JSON in the `Connected` response. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         The generation does not wait for `generate_output` to complete before calling this. | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         `threading.Event` can be used if you need to wait for something from `generate_output`. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-02-28 17:22:42 -08:00
										 |  |  |         # The reason for the `Mapping` type annotation, rather than `dict` | 
					
						
							|  |  |  |         # is so that type checkers won't worry about the mutability of `dict`, | 
					
						
							|  |  |  |         # so you can have more specific typing in your world implementation. | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |         return {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-18 14:30:43 +02:00
										 |  |  |     def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Fill in additional entrance information text into locations, which is displayed when hinted. | 
					
						
							|  |  |  |         structure is {player_id: {location_id: text}} You will need to insert your own player_id. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2022-09-18 14:30:43 +02:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def modify_multidata(self, multidata: Dict[str, Any]) -> None:  # TODO: TypedDict for multidata? | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |         """For deeper modification of server multidata.""" | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-02 12:29:29 +01:00
										 |  |  |     # Spoiler writing is optional, these may not get called. | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def write_spoiler_header(self, spoiler_handle: TextIO) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Write to the spoiler header. If individual it's right at the end of that player's options, | 
					
						
							|  |  |  |         if as stage it's right under the common header before per-player options. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2021-11-02 12:29:29 +01:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def write_spoiler(self, spoiler_handle: TextIO) -> None: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Write to the spoiler "middle", this is after the per-player options and before locations, | 
					
						
							|  |  |  |         meant for useful or interesting info. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2021-11-02 12:29:29 +01:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |     def write_spoiler_end(self, spoiler_handle: TextIO) -> None: | 
					
						
							| 
									
										
										
										
											2021-11-02 12:29:29 +01:00
										 |  |  |         """Write to the end of the spoiler""" | 
					
						
							|  |  |  |         pass | 
					
						
							| 
									
										
										
										
											2022-02-17 07:07:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-19 23:23:48 +02:00
										 |  |  |     # end of ordered Main.py calls | 
					
						
							| 
									
										
										
										
											2021-07-08 05:09:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     def create_item(self, name: str) -> "Item": | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Create an item for this world type and player. | 
					
						
							|  |  |  |         Warning: this may be called with self.world = None, for example by MultiServer | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  |         raise NotImplementedError | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_filler_item_name(self) -> str: | 
					
						
							|  |  |  |         """Called when the item pool needs to be filled with additional items to match location count.""" | 
					
						
							|  |  |  |         logging.warning(f"World {self} is generating a filler item without custom filler pool.") | 
					
						
							| 
									
										
										
										
											2022-10-31 21:41:21 -05:00
										 |  |  |         return self.multiworld.random.choice(tuple(self.item_name_to_id.keys())) | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-24 01:51:26 +02:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set[int]) -> World: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Creates a group, which is an instance of World that is responsible for multiple others. | 
					
						
							|  |  |  |         An example case is ItemLinks creating these. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         # TODO remove loop when worlds use options dataclass | 
					
						
							|  |  |  |         for option_key, option in cls.options_dataclass.type_hints.items(): | 
					
						
							| 
									
										
										
										
											2024-03-07 01:40:09 -06:00
										 |  |  |             getattr(multiworld, option_key)[new_player_id] = option.from_any(option.default) | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         group = cls(multiworld, new_player_id) | 
					
						
							| 
									
										
										
										
											2024-03-07 01:40:09 -06:00
										 |  |  |         group.options = cls.options_dataclass(**{option_key: option.from_any(option.default) | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |                                                  for option_key, option in cls.options_dataclass.type_hints.items()}) | 
					
						
							| 
									
										
										
										
											2023-09-24 01:51:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 15:30:20 -05:00
										 |  |  |         return group | 
					
						
							| 
									
										
										
										
											2023-09-24 01:51:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  |     # decent place to implement progressive items, in most cases can stay as-is | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Collect an item name into state. For speed reasons items that aren't logically useful get skipped. | 
					
						
							| 
									
										
										
										
											2021-08-21 06:55:08 +02:00
										 |  |  |         Collect None to skip item. | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  |         :param state: CollectionState to collect into | 
					
						
							|  |  |  |         :param item: Item to decide on if it should be collected into state | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         :param remove: indicate if this is meant to remove from state instead of adding. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2021-07-04 15:47:11 +02:00
										 |  |  |         if item.advancement: | 
					
						
							| 
									
										
										
										
											2021-08-10 09:47:28 +02:00
										 |  |  |             return item.name | 
					
						
							| 
									
										
										
										
											2022-04-28 09:03:44 -07:00
										 |  |  |         return None | 
					
						
							| 
									
										
										
										
											2021-07-04 15:47:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     def get_pre_fill_items(self) -> List["Item"]: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Used to return items that need to be collected when creating a fresh all_state, but don't exist in the | 
					
						
							|  |  |  |         multiworld itempool. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2022-02-13 23:02:18 +01:00
										 |  |  |         return [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-15 23:34:29 +01:00
										 |  |  |     # these two methods can be extended for pseudo-items on state | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     def collect(self, state: "CollectionState", item: "Item") -> bool: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """Called when an item is collected in to state. Useful for things such as progressive items or currency.""" | 
					
						
							| 
									
										
										
										
											2021-08-10 09:47:28 +02:00
										 |  |  |         name = self.collect_item(state, item) | 
					
						
							|  |  |  |         if name: | 
					
						
							| 
									
										
										
										
											2023-11-02 00:41:20 -05:00
										 |  |  |             state.prog_items[self.player][name] += 1 | 
					
						
							| 
									
										
										
										
											2021-08-10 09:47:28 +02:00
										 |  |  |             return True | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     def remove(self, state: "CollectionState", item: "Item") -> bool: | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |         """Called when an item is removed from to state. Useful for things such as progressive items or currency.""" | 
					
						
							| 
									
										
										
										
											2021-08-21 06:55:08 +02:00
										 |  |  |         name = self.collect_item(state, item, True) | 
					
						
							| 
									
										
										
										
											2021-08-10 09:47:28 +02:00
										 |  |  |         if name: | 
					
						
							| 
									
										
										
										
											2023-11-02 00:41:20 -05:00
										 |  |  |             state.prog_items[self.player][name] -= 1 | 
					
						
							|  |  |  |             if state.prog_items[self.player][name] < 1: | 
					
						
							|  |  |  |                 del (state.prog_items[self.player][name]) | 
					
						
							| 
									
										
										
										
											2021-08-10 09:47:28 +02:00
										 |  |  |             return True | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-07-16 12:23:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |     # following methods should not need to be overridden. | 
					
						
							| 
									
										
										
										
											2022-08-15 23:52:03 +02:00
										 |  |  |     def create_filler(self) -> "Item": | 
					
						
							| 
									
										
										
										
											2022-03-20 11:07:51 -04:00
										 |  |  |         return self.create_item(self.get_filler_item_name()) | 
					
						
							| 
									
										
										
										
											2022-02-05 15:49:19 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-23 10:32:14 +01:00
										 |  |  |     # convenience methods | 
					
						
							|  |  |  |     def get_location(self, location_name: str) -> "Location": | 
					
						
							|  |  |  |         return self.multiworld.get_location(location_name, self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_entrance(self, entrance_name: str) -> "Entrance": | 
					
						
							|  |  |  |         return self.multiworld.get_entrance(entrance_name, self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_region(self, region_name: str) -> "Region": | 
					
						
							|  |  |  |         return self.multiworld.get_region(region_name, self.player) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  |     @classmethod | 
					
						
							|  |  |  |     def get_data_package_data(cls) -> "GamesPackage": | 
					
						
							|  |  |  |         sorted_item_name_groups = { | 
					
						
							|  |  |  |             name: sorted(cls.item_name_groups[name]) for name in sorted(cls.item_name_groups) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         sorted_location_name_groups = { | 
					
						
							|  |  |  |             name: sorted(cls.location_name_groups[name]) for name in sorted(cls.location_name_groups) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         res: "GamesPackage" = { | 
					
						
							|  |  |  |             # sorted alphabetically | 
					
						
							|  |  |  |             "item_name_groups": sorted_item_name_groups, | 
					
						
							|  |  |  |             "item_name_to_id": cls.item_name_to_id, | 
					
						
							|  |  |  |             "location_name_groups": sorted_location_name_groups, | 
					
						
							|  |  |  |             "location_name_to_id": cls.location_name_to_id, | 
					
						
							|  |  |  |             "version": cls.data_version, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         res["checksum"] = data_package_checksum(res) | 
					
						
							|  |  |  |         return res | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-21 06:55:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  | # any methods attached to this can be used as part of CollectionState, | 
					
						
							|  |  |  | # please use a prefix as all of them get clobbered together | 
					
						
							|  |  |  | class LogicMixin(metaclass=AutoLogicRegister): | 
					
						
							| 
									
										
										
										
											2021-07-16 12:23:05 +02:00
										 |  |  |     pass | 
					
						
							| 
									
										
										
										
											2023-03-20 11:01:08 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def data_package_checksum(data: "GamesPackage") -> str: | 
					
						
							|  |  |  |     """Calculates the data package checksum for a game from a dict""" | 
					
						
							|  |  |  |     assert "checksum" not in data, "Checksum already in data" | 
					
						
							|  |  |  |     assert sorted(data) == list(data), "Data not ordered" | 
					
						
							|  |  |  |     from NetUtils import encode | 
					
						
							|  |  |  |     return hashlib.sha1(encode(data).encode()).hexdigest() | 
					
						
							| 
									
										
										
										
											2023-11-10 22:06:54 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _normalize_description(description): | 
					
						
							| 
									
										
										
										
											2024-03-12 03:27:41 -05:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Normalizes a description in item_descriptions or location_descriptions. | 
					
						
							| 
									
										
										
										
											2023-11-10 22:06:54 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     This allows authors to write descritions with nice indentation and line lengths in their world | 
					
						
							|  |  |  |     definitions without having it affect the rendered format. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     # First, collapse the whitespace around newlines and the ends of the description. | 
					
						
							|  |  |  |     description = re.sub(r' *\n *', '\n', description.strip()) | 
					
						
							|  |  |  |     # Next, condense individual newlines into spaces. | 
					
						
							|  |  |  |     description = re.sub(r'(?<!\n)\n(?!\n)', ' ', description) | 
					
						
							|  |  |  |     return description | 
					
						
							|  |  |  | 
 |