| 
									
										
										
										
											2021-07-12 15:33:20 +02:00
										 |  |  | from __future__ import annotations | 
					
						
							| 
									
										
										
										
											2021-08-10 09:47:28 +02:00
										 |  |  | from typing import Dict, Set, Tuple, List, Optional | 
					
						
							| 
									
										
										
										
											2021-07-12 13:54:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 09:03:44 +02:00
										 |  |  | from BaseClasses import MultiWorld, Item, CollectionState, Location | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | class AutoWorldRegister(type): | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |     world_types: Dict[str, World] = {} | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __new__(cls, name, bases, dct): | 
					
						
							| 
									
										
										
										
											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"]) | 
					
						
							|  |  |  |         dct["location_names"] = frozenset(dct["location_name_to_id"]) | 
					
						
							|  |  |  |         dct["all_names"] = dct["item_names"] | dct["location_names"] | set(dct.get("item_name_groups", {})) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-12 18:47:58 +02:00
										 |  |  |         # construct class | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         new_class = super().__new__(cls, name, bases, dct) | 
					
						
							|  |  |  |         if "game" in dct: | 
					
						
							|  |  |  |             AutoWorldRegister.world_types[dct["game"]] = new_class | 
					
						
							|  |  |  |         return new_class | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  | class AutoLogicRegister(type): | 
					
						
							|  |  |  |     def __new__(cls, name, bases, dct): | 
					
						
							|  |  |  |         new_class = super().__new__(cls, name, bases, dct) | 
					
						
							| 
									
										
										
										
											2021-07-16 12:23:05 +02:00
										 |  |  |         for item_name, function in dct.items(): | 
					
						
							|  |  |  |             if not item_name.startswith("__"): | 
					
						
							|  |  |  |                 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
										 |  |  | 
 | 
					
						
							|  |  |  | def call_single(world: MultiWorld, method_name: str, player: int, *args): | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     method = getattr(world.worlds[player], method_name) | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |     return method(*args) | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  | def call_all(world: MultiWorld, method_name: str, *args): | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |     world_types = set() | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |     for player in world.player_ids: | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |         world_types.add(world.worlds[player].__class__) | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |         call_single(world, method_name, player, *args) | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |     for world_type in world_types: | 
					
						
							|  |  |  |         stage_callable = getattr(world_type, f"stage_{method_name}", None) | 
					
						
							|  |  |  |         if stage_callable: | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |             stage_callable(world, *args) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def call_stage(world: MultiWorld, method_name: str, *args): | 
					
						
							|  |  |  |     world_types = {world.worlds[player].__class__ for player in world.player_ids} | 
					
						
							|  |  |  |     for world_type in world_types: | 
					
						
							|  |  |  |         stage_callable = getattr(world_type, f"stage_{method_name}", None) | 
					
						
							|  |  |  |         if stage_callable: | 
					
						
							|  |  |  |             stage_callable(world, *args) | 
					
						
							| 
									
										
										
										
											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."""
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  |     options: dict = {}  # link your Options mapping | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |     game: str  # name the game | 
					
						
							| 
									
										
										
										
											2021-07-08 11:07:41 +02:00
										 |  |  |     topology_present: bool = False  # indicate if world type has any meaningful layout/pathing | 
					
						
							| 
									
										
										
										
											2021-07-12 15:33:20 +02:00
										 |  |  |     all_names: Set[str] = frozenset()  # gets automatically populated with all item, item group and location names | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  |     # map names to their IDs | 
					
						
							|  |  |  |     item_name_to_id: Dict[str, int] = {} | 
					
						
							|  |  |  |     location_name_to_id: Dict[str, int] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-29 20:27:41 +02:00
										 |  |  |     # maps item group names to sets of items. Example: "Weapons" -> {"Sword", "Bow"} | 
					
						
							|  |  |  |     item_name_groups: Dict[str, Set[str]] = {} | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-22 04:22:34 +02:00
										 |  |  |     # increment this every time something in your world's names/id mappings changes. | 
					
						
							|  |  |  |     # While this is set to 0 in *any* AutoWorld, the entire DataPackage is considered in testing mode and will be | 
					
						
							|  |  |  |     # retrieved by clients on every connection. | 
					
						
							|  |  |  |     data_version = 1 | 
					
						
							| 
									
										
										
										
											2021-07-12 18:05:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-12 15:33:20 +02:00
										 |  |  |     hint_blacklist: Set[str] = frozenset()  # any names that should not be hintable | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-13 19:14:57 +02:00
										 |  |  |     # if a world is set to remote_items, then it just needs to send location checks to the server and the server | 
					
						
							|  |  |  |     # sends back the items | 
					
						
							|  |  |  |     # if a world is set to remote_items = False, then the server never sends an item where receiver == finder, | 
					
						
							|  |  |  |     # the client finds its own items in its own world. | 
					
						
							|  |  |  |     remote_items: bool = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-31 16:04:37 +02:00
										 |  |  |     # For games where after a victory it is impossible to go back in and get additional/remaining Locations checked. | 
					
						
							|  |  |  |     # this forces forfeit:  auto for those games. | 
					
						
							|  |  |  |     forced_auto_forfeit: bool = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-27 20:46:23 +02:00
										 |  |  |     # Hide World Type from various views. Does not remove functionality. | 
					
						
							|  |  |  |     hidden = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  |     # autoset on creation: | 
					
						
							|  |  |  |     world: MultiWorld | 
					
						
							|  |  |  |     player: int | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-29 20:27:41 +02:00
										 |  |  |     # automatically generated | 
					
						
							|  |  |  |     item_id_to_name: Dict[int, str] | 
					
						
							|  |  |  |     location_id_to_name: Dict[int, str] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     item_names: Set[str]  # set of all potential item names | 
					
						
							|  |  |  |     location_names: Set[str]  # set of all potential location names | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  |     def __init__(self, world: MultiWorld, player: int): | 
					
						
							|  |  |  |         self.world = world | 
					
						
							|  |  |  |         self.player = player | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 15:51:50 +02:00
										 |  |  |     # overridable methods that get called by Main.py, sorted by execution order | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |     # can also be implemented as a classmethod and called "stage_<original_name", | 
					
						
							|  |  |  |     # in that case the MultiWorld object is passed as an argument and it gets called once for the entire multiworld. | 
					
						
							|  |  |  |     # An example of this can be found in alttp as stage_pre_fill | 
					
						
							| 
									
										
										
										
											2021-07-15 08:50:08 +02:00
										 |  |  |     def generate_early(self): | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-08 05:09:34 +02:00
										 |  |  |     def create_regions(self): | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  |         pass | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 15:51:50 +02:00
										 |  |  |     def create_items(self): | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-11 18:02:48 +02:00
										 |  |  |     def set_rules(self): | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-08 05:09:34 +02:00
										 |  |  |     def generate_basic(self): | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-09 06:50:11 +02:00
										 |  |  |     def pre_fill(self): | 
					
						
							|  |  |  |         """Optional method that is supposed to be used for special fill stages. This is run *after* plando.""" | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 09:03:44 +02:00
										 |  |  |     def fill_hook(cls, progitempool: List[Item], nonexcludeditempool: List[Item], | 
					
						
							| 
									
										
										
										
											2021-08-30 22:20:44 +02:00
										 |  |  |                   localrestitempool: Dict[int, List[Item]], nonlocalrestitempool: Dict[int, List[Item]], | 
					
						
							|  |  |  |                   restitempool: List[Item], fill_locations: List[Location]): | 
					
						
							| 
									
										
										
										
											2021-08-10 09:03:44 +02:00
										 |  |  |         """Special method that gets called as part of distribute_items_restrictive (main fill).
 | 
					
						
							|  |  |  |         This gets called once per present world type."""
 | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-30 01:16:04 +02:00
										 |  |  |     def post_fill(self): | 
					
						
							|  |  |  |         """Optional Method that is called after regular fill. Can be used to do adjustments before output generation.""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |     def generate_output(self, output_directory: str): | 
					
						
							| 
									
										
										
										
											2021-07-08 05:09:34 +02:00
										 |  |  |         """This method gets called from a threadpool, do not use world.random here.
 | 
					
						
							|  |  |  |         If you need any last-second randomization, use MultiWorld.slot_seeds[slot] instead."""
 | 
					
						
							| 
									
										
										
										
											2021-06-11 14:22:44 +02:00
										 |  |  |         pass | 
					
						
							| 
									
										
										
										
											2021-06-27 00:23:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-22 15:51:50 +02:00
										 |  |  |     def fill_slot_data(self) -> dict: | 
					
						
							| 
									
										
										
										
											2021-07-21 18:08:15 +02:00
										 |  |  |         """Fill in the slot_data field in the Connected network package.""" | 
					
						
							|  |  |  |         return {} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-09 09:15:41 +02:00
										 |  |  |     def modify_multidata(self, multidata: dict): | 
					
						
							|  |  |  |         """For deeper modification of server multidata.""" | 
					
						
							|  |  |  |         pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-12 13:54:47 +02:00
										 |  |  |     def get_required_client_version(self) -> Tuple[int, int, int]: | 
					
						
							| 
									
										
										
										
											2021-07-08 05:09:34 +02:00
										 |  |  |         return 0, 0, 3 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # end of Main.py calls | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-21 06:55:08 +02:00
										 |  |  |     def collect_item(self, state: CollectionState, item: Item, remove=False) -> Optional[str]: | 
					
						
							| 
									
										
										
										
											2021-08-10 09:47:28 +02: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. | 
					
						
							|  |  |  |         :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 | 
					
						
							| 
									
										
										
										
											2021-07-04 15:47:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-12 13:54:47 +02:00
										 |  |  |     def create_item(self, name: str) -> Item: | 
					
						
							|  |  |  |         """Create an item for this world type and player.
 | 
					
						
							|  |  |  |         Warning: this may be called with self.world = None, for example by MultiServer"""
 | 
					
						
							|  |  |  |         raise NotImplementedError | 
					
						
							| 
									
										
										
										
											2021-07-15 13:31:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-10 09:47:28 +02:00
										 |  |  |     # following methods should not need to be overriden. | 
					
						
							|  |  |  |     def collect(self, state: CollectionState, item: Item) -> bool: | 
					
						
							|  |  |  |         name = self.collect_item(state, item) | 
					
						
							|  |  |  |         if name: | 
					
						
							|  |  |  |             state.prog_items[name, item.player] += 1 | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def remove(self, state: CollectionState, item: Item) -> bool: | 
					
						
							| 
									
										
										
										
											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: | 
					
						
							|  |  |  |             state.prog_items[name, item.player] -= 1 | 
					
						
							|  |  |  |             if state.prog_items[name, item.player] < 1: | 
					
						
							|  |  |  |                 del (state.prog_items[name, item.player]) | 
					
						
							|  |  |  |             return True | 
					
						
							|  |  |  |         return False | 
					
						
							| 
									
										
										
										
											2021-07-16 12:23:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 |